package railo.runtime.util;
import java.net.MalformedURLException;
import java.net.URL;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import railo.commons.lang.StringUtil;
import railo.runtime.exp.PageException;
import railo.runtime.op.Caster;
import railo.runtime.text.xml.XMLUtil;
import railo.transformer.util.CFMLString;
/**
* Transform a HTML String, set all relative Pathes inside HTML File to absolute
* TODO Test this
*
*/
public final class URLResolver {
private Tag[] tags=new Tag[]{
new Tag("a","href"),
new Tag("link","href"),
new Tag("form","action"),
new Tag("applet","code"),
new Tag("script","src"),
new Tag("body","background"),
new Tag("frame","src"),
new Tag("bgsound","src"),
new Tag("img","src"),
new Tag("embed",new String[]{"src","pluginspace"}),
new Tag("object",new String[]{"data","classid","codebase","usemap"})
};
public void transform(Node node, URL url) throws MalformedURLException {
Element el;
if(node.getNodeType()==Node.DOCUMENT_NODE) {
transform(XMLUtil.getRootElement(node, true), url);
}
else if(node.getNodeType()==Node.ELEMENT_NODE) {
el=(Element) node;
String[] attr;
NamedNodeMap map;
String attrName,value,value2,nodeName=el.getNodeName();
int len;
// translate attribute
for(int i=0;i<tags.length;i++) {
if(tags[i].tag.equalsIgnoreCase(nodeName)) {
attr=tags[i].attributes;
map = el.getAttributes();
len = map.getLength();
for(int y=0;y<attr.length;y++) {
for(int z=0;z<len;z++) {
attrName=map.item(z).getNodeName();
if(attrName.equalsIgnoreCase(attr[y])) {
value=el.getAttribute(attrName);
value2=add(url, value);
if(value!=value2){
el.setAttribute(attrName, value2);
}
break;
}
}
}
}
}
// list children
NodeList nodes = el.getChildNodes();
len=nodes.getLength();
for(int i=0;i<len;i++) {
transform(nodes.item(i), url);
}
}
}
/**
* transform the HTML String
* @param html HTML String to transform
* @param url Absolute URL path to set
* @return transformed HTMl String
* @throws PageException
*/
public String transform(String html, URL url, boolean setBaseTag) throws PageException {
StringBuffer target=new StringBuffer();
CFMLString cfml=new CFMLString(html,"UTF-8");
while(!cfml.isAfterLast()) {
if(cfml.forwardIfCurrent('<')) {
target.append('<');
try {
for(int i=0;i<tags.length;i++) {
if(cfml.forwardIfCurrent(tags[i].tag+" ")) {
target.append(tags[i].tag+" ");
transformTag(target,cfml,tags[i],url);
}
}
}
catch(MalformedURLException me) {
throw Caster.toPageException(me);
}
}
else {
target.append(cfml.getCurrent());
cfml.next();
}
}
if(!setBaseTag)return target.toString();
html=target.toString();
String prefix="",postfix="";
int index = StringUtil.indexOfIgnoreCase(html, "</head>");
if(index==-1) {
prefix="<head>";
postfix="</head>";
index = StringUtil.indexOfIgnoreCase(html, "</html>");
}
if(index!=-1) {
StringBuffer sb=new StringBuffer();
sb.append(html.substring(0,index));
String port=url.getPort()==-1?"":":"+url.getPort();
sb.append(prefix+"<base href=\""+(url.getProtocol()+"://"+url.getHost()+port)+"\">"+postfix);
sb.append(html.substring(index));
html=sb.toString();
}
return html;
}
/**
* transform a single tag
* @param target target to write to
* @param cfml CFMl String Object containing plain HTML
* @param tag current tag totransform
* @param url absolute URL to Set at tag attribute
* @throws MalformedURLException
*/
private void transformTag(StringBuffer target, CFMLString cfml, Tag tag,URL url) throws MalformedURLException {
// TODO attribute inside other attribute
char quote=0;
boolean inside=false;
StringBuffer value=new StringBuffer();
while(!cfml.isAfterLast()) {
if(inside) {
if(quote!=0 && cfml.forwardIfCurrent(quote)) {
inside=false;
target.append(add(url,value.toString()));
target.append(quote);
}
else if(quote==0 && (cfml.isCurrent(' ')||cfml.isCurrent("/>")||cfml.isCurrent('>')||cfml.isCurrent('\t')||cfml.isCurrent('\n'))) {
inside=false;
target.append(new URL(url,value.toString()));
target.append(cfml.getCurrent());
cfml.next();
}
else {
value.append(cfml.getCurrent());
cfml.next();
}
}
else if(cfml.forwardIfCurrent('>')) {
target.append('>');
break;
}
else {
for(int i=0;i<tag.attributes.length;i++) {
if(cfml.forwardIfCurrent(tag.attributes[i])) {
target.append(tag.attributes[i]);
cfml.removeSpace();
// =
if(cfml.isCurrent('=')) {
inside=true;
target.append('=');
cfml.next();
cfml.removeSpace();
quote=cfml.getCurrent();
value=new StringBuffer();
if(quote!='"' && quote!='\'')quote=0;
else {
target.append(quote);
cfml.next();
}
}
}
}
if(!inside) {
target.append(cfml.getCurrent());
cfml.next();
}
}
}
}
private String add(URL url, String value) {
value=value.trim();
String lcValue=value.toLowerCase();
if(lcValue.startsWith("http://") || lcValue.startsWith("file://") || lcValue.startsWith("news://") || lcValue.startsWith("goopher://") || lcValue.startsWith("javascript:"))
return (value);
try {
return new URL(url,value.toString()).toExternalForm();
} catch (MalformedURLException e) {
return value;
}
}
private class Tag {
private String tag;
private String[] attributes;
private Tag(String tag,String[] attributes) {
this.tag=tag.toLowerCase();
this.attributes=new String[attributes.length];
for(int i=0;i<attributes.length;i++) {
this.attributes[i]=attributes[i].toLowerCase();
}
}
private Tag(String tag,String attribute1) {
this.tag=tag.toLowerCase();
this.attributes=new String[]{attribute1.toLowerCase()};
}
}
public static URLResolver getInstance() {
return new URLResolver();
}
}