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}