001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.util;
020
021import java.net.MalformedURLException;
022import java.net.URL;
023
024import lucee.commons.io.CharsetUtil;
025import lucee.commons.lang.StringUtil;
026import lucee.runtime.exp.PageException;
027import lucee.runtime.op.Caster;
028import lucee.runtime.text.xml.XMLUtil;
029import lucee.transformer.util.CFMLString;
030
031import org.w3c.dom.Element;
032import org.w3c.dom.NamedNodeMap;
033import org.w3c.dom.Node;
034import org.w3c.dom.NodeList;
035
036/**
037 * Transform a HTML String, set all relative Pathes inside HTML File to absolute
038 * TODO Test this  
039 *
040 */
041public final class URLResolver {
042        
043        private Tag[] tags=new Tag[]{
044                        new Tag("a","href"),
045                        new Tag("link","href"),
046                        new Tag("form","action"),
047                        new Tag("applet","code"),
048                        new Tag("script","src"),
049                        new Tag("body","background"),
050                        new Tag("frame","src"),
051                        new Tag("bgsound","src"),
052                        new Tag("img","src"),
053                        
054                        new Tag("embed",new String[]{"src","pluginspace"}),
055                        new Tag("object",new String[]{"data","classid","codebase","usemap"})
056                        
057        };
058        
059
060        public void transform(Node node, URL url) throws MalformedURLException {
061                Element el;
062                if(node.getNodeType()==Node.DOCUMENT_NODE) {
063                        transform(XMLUtil.getRootElement(node, true), url);
064                }
065                else if(node.getNodeType()==Node.ELEMENT_NODE) {
066                        el=(Element) node;
067                        String[] attr;
068                        NamedNodeMap map;
069                        String attrName,value,value2,nodeName=el.getNodeName();
070                        int len;
071                        // translate attribute
072                        for(int i=0;i<tags.length;i++) {
073                                if(tags[i].tag.equalsIgnoreCase(nodeName)) {
074                                        
075                                        attr=tags[i].attributes;
076                                        map = el.getAttributes();
077                                        len = map.getLength();
078                                        for(int y=0;y<attr.length;y++) {
079                                                for(int z=0;z<len;z++) {
080                                                        attrName=map.item(z).getNodeName();
081                                                        if(attrName.equalsIgnoreCase(attr[y])) {
082                                                                value=el.getAttribute(attrName);
083                                                                value2=add(url, value);
084                                                                
085                                                                if(value!=value2){
086                                                                        el.setAttribute(attrName, value2);
087                                                                }
088                                                                
089                                                                break;
090                                                        }
091                                                }
092                                        }
093                                }
094                        }
095                        
096                        // list children
097                        NodeList nodes = el.getChildNodes();
098                        len=nodes.getLength();
099                        for(int i=0;i<len;i++) {
100                                transform(nodes.item(i), url);
101                        }
102                }
103        }
104        
105        /**
106         * transform the HTML String
107         * @param html HTML String to transform
108         * @param url Absolute URL path to set
109         * @return transformed HTMl String
110         * @throws PageException
111         */
112        public String transform(String html, URL url, boolean setBaseTag) throws PageException {
113                StringBuffer target=new StringBuffer();
114                CFMLString cfml=new CFMLString(html,CharsetUtil.UTF8);
115                while(!cfml.isAfterLast()) {
116                        if(cfml.forwardIfCurrent('<')) {
117                                target.append('<');
118                                try {
119                                        for(int i=0;i<tags.length;i++) {
120                                                if(cfml.forwardIfCurrent(tags[i].tag+" ")) {
121                                                        target.append(tags[i].tag+" ");
122                                                        transformTag(target,cfml,tags[i],url);
123                                                }
124                                        }
125                                }
126                                catch(MalformedURLException me) {
127                                        throw Caster.toPageException(me);
128                                }
129                        }
130                        else {
131                                target.append(cfml.getCurrent());
132                                cfml.next();
133                        }
134                        
135                }
136                if(!setBaseTag)return target.toString();
137                
138                html=target.toString();
139                String prefix="",postfix="";
140                int index = StringUtil.indexOfIgnoreCase(html, "</head>");
141                if(index==-1) {
142                        prefix="<head>";
143                        postfix="</head>";
144                        index = StringUtil.indexOfIgnoreCase(html, "</html>");
145                }
146                        
147                
148                if(index!=-1) {
149                        StringBuffer sb=new StringBuffer();
150                        sb.append(html.substring(0,index));
151                        String port=url.getPort()==-1?"":":"+url.getPort();
152                        sb.append(prefix+"<base href=\""+(url.getProtocol()+"://"+url.getHost()+port)+"\">"+postfix);
153                        sb.append(html.substring(index));
154                        html=sb.toString();
155                        
156                }
157                return html;
158        }
159        
160        /**
161         * transform a single tag
162         * @param target target to write to
163         * @param cfml CFMl String Object containing plain HTML
164         * @param tag current tag totransform
165         * @param url absolute URL to Set at tag attribute
166         * @throws MalformedURLException
167         */
168        private void transformTag(StringBuffer target, CFMLString cfml, Tag tag,URL url) throws MalformedURLException {
169                // TODO attribute inside other attribute
170                
171                char quote=0;
172                boolean inside=false;
173                StringBuffer value=new StringBuffer();
174                
175                while(!cfml.isAfterLast()) {
176                        if(inside) {
177                                if(quote!=0 && cfml.forwardIfCurrent(quote)) {
178                                        inside=false;
179                                        target.append(add(url,value.toString()));
180                                        target.append(quote);
181                                }
182                                else if(quote==0 && (cfml.isCurrent(' ')||cfml.isCurrent("/>")||cfml.isCurrent('>')||cfml.isCurrent('\t')||cfml.isCurrent('\n'))) {
183                                        
184                                        inside=false;
185                                        target.append(new URL(url,value.toString()));
186                                        target.append(cfml.getCurrent());
187                                        cfml.next();
188                                }
189                                else {
190                                        value.append(cfml.getCurrent());
191                                        cfml.next();
192                                }
193                        }
194                        else if(cfml.forwardIfCurrent('>')) {
195                                target.append('>');
196                                break;
197                        }
198                        else {
199                                
200                                for(int i=0;i<tag.attributes.length;i++) {
201                                        if(cfml.forwardIfCurrent(tag.attributes[i])) {
202                                                target.append(tag.attributes[i]);
203                                                cfml.removeSpace();
204                                                // =
205                                                if(cfml.isCurrent('=')) {
206                                                        inside=true;
207                                                        target.append('=');
208                                                        cfml.next();
209                                                        cfml.removeSpace();
210                                                        
211                                                        quote=cfml.getCurrent();
212                                                        value=new StringBuffer();
213                                                        if(quote!='"' && quote!='\'')quote=0;
214                                                        else {
215                                                                target.append(quote);
216                                                                cfml.next();
217                                                        }
218                                                }
219                                        }
220                                }
221                                if(!inside) {
222                                        target.append(cfml.getCurrent());
223                                        cfml.next();
224                                }
225                        }
226                }
227        }
228
229
230        private String add(URL url, String value) {
231                value=value.trim();
232                String lcValue=value.toLowerCase();
233                if(lcValue.startsWith("http://") || lcValue.startsWith("file://") || lcValue.startsWith("news://") || lcValue.startsWith("goopher://") || lcValue.startsWith("javascript:"))
234                        return (value);
235                try {
236                        return new URL(url,value.toString()).toExternalForm();
237                } catch (MalformedURLException e) {
238                        return value;
239                }
240        }
241
242        private class Tag {
243                private String tag;
244                private String[] attributes;
245                private Tag(String tag,String[] attributes) {
246                        this.tag=tag.toLowerCase();
247                        this.attributes=new String[attributes.length];
248                        for(int i=0;i<attributes.length;i++) {
249                                this.attributes[i]=attributes[i].toLowerCase();
250                        }
251                        
252                }
253                private Tag(String tag,String attribute1) {
254                        this.tag=tag.toLowerCase();
255                        this.attributes=new String[]{attribute1.toLowerCase()};
256                }
257        
258        }
259
260        public static URLResolver getInstance() {
261                return new URLResolver();
262        }
263        
264}