package freenet.client.filter;
import freenet.support.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
public class FilterUtils {
private static volatile boolean logDEBUG;
static {
Logger.registerClass(FilterUtils.class);
}
private final static int MAX_NTH = 999999; // Limit range of numbers allowed in isNth, due to incorrect behavior found in webkit based browsers.
//Basic Data types
public static boolean isInteger(String strValue)
{
try
{
Integer.parseInt(strValue);
return true;
}
catch(Exception e)
{
return false;
}
}
public static boolean isNumber(String strNumber)
{
try
{
boolean containsE=false;
String strDecimal,strInteger=null;
if(strNumber.indexOf('e')>0)
{
containsE=true;
strDecimal=strNumber.substring(0,strNumber.indexOf('e'));
strInteger=strNumber.substring(strDecimal.indexOf('e')+1,strNumber.length());
}
else if(strNumber.indexOf('E')>0)
{
containsE=true;
strDecimal=strNumber.substring(0,strNumber.indexOf('E'));
strInteger=strNumber.substring(strDecimal.indexOf('E')+1,strNumber.length());
}
else
strDecimal=strNumber;
Double.parseDouble(strDecimal);
if(containsE)
return isInteger(strInteger);
else
return true;
}
catch(Exception e)
{
return false;
}
}
private final static HashSet<String> allowedUnits=new HashSet<String>();
static
{
// W3C CSS Spec Section 5 (http://www.w3.org/TR/css3-values/)
// "Distance Units: the '<length>' type"
allowedUnits.add("em");
allowedUnits.add("ex");
allowedUnits.add("ch");
allowedUnits.add("rem");
allowedUnits.add("cm");
allowedUnits.add("mm");
allowedUnits.add("in");
allowedUnits.add("pt");
allowedUnits.add("pc");
allowedUnits.add("px");
allowedUnits.add("vw");
allowedUnits.add("vh");
allowedUnits.add("vmin");
allowedUnits.add("vmax");
}
public static boolean isPercentage(String value)
{
if (value.length()>=2 && value.charAt(value.length()-1)=='%') //Valid percentage X%
{
// Percentages are <number>%
// That means they can be positive, negative, zero, >100%, and they can contain decimal points.
try
{
Integer.parseInt(value.substring(0,value.length()-1));
return true;
}
catch(Exception e) { }
try
{
Double.parseDouble(value.substring(0,value.length()-1));
return true;
}
catch(Exception e) { }
}
return false;
}
public static boolean isLength(String value,boolean isSVG) //SVG lengths allow % values
{
String lengthValue=null;
value=value.trim();
if (value.length() == 0) {
return false;
}
if (isSVG) {
if (value.charAt(value.length()-1) == '%') {
lengthValue = value.substring(0, value.length()-1);
}
}
boolean units = false;
if (lengthValue == null) { //Valid unit Vxx[x[x]] (where xx[x[x]] is unit) or V
int pos = 0;
int len = value.length();
for (int i = len - 1; i >= 0; i --) {
char c = value.charAt(i);
if ((c >= '0' && c <= '9') || c == '.') {
pos = i + 1;
break;
}
}
if (len - pos > 0 && allowedUnits.contains(value.substring(pos))) {
lengthValue = value.substring(0, pos);
units = true;
} else {
lengthValue = value;
}
}
try
{
int x = Integer.parseInt(lengthValue);
if(!units && !isSVG && x != 0) return false;
return true;
}
catch(Exception e){ }
try
{
double dval=Double.parseDouble(lengthValue);
if(!units && !isSVG && dval != 0) return false;
if(!(Double.isInfinite(dval) || Double.isNaN(dval)))
return true;
}
catch(Exception e){ }
return false;
}
public static boolean isAngle(String value)
{
boolean isValid=true;
int index=-1;
if(value.indexOf("deg")>-1)
{
index=value.indexOf("deg");
String secondpart=value.substring(index,value.length()).trim();
if(!("deg".equals(secondpart)))
isValid=false;
}
else if(value.indexOf("grad")>-1)
{
index=value.indexOf("grad");
String secondpart=value.substring(index,value.length()).trim();
if(!("grad".equals(secondpart)))
isValid=false;
}
else if(value.indexOf("rad")>-1)
{
index=value.indexOf("rad");
String secondpart=value.substring(index,value.length()).trim();
if(!("rad".equals(secondpart)))
isValid=false;
}
if(index!=-1 && isValid)
{
String firstPart=value.substring(0,index);
try
{
Float.parseFloat(firstPart);
return true;
}
catch(Exception e)
{
}
}
return false;
}
private final static HashSet<String> SVGcolorKeywords=new HashSet<String>();
static
{
SVGcolorKeywords.add("aliceblue");
SVGcolorKeywords.add("antiquewhite");
SVGcolorKeywords.add("aqua");
SVGcolorKeywords.add("aquamarine");
SVGcolorKeywords.add("azure");
SVGcolorKeywords.add("beige");
SVGcolorKeywords.add("bisque");
SVGcolorKeywords.add("black");
SVGcolorKeywords.add("blanchedalmond");
SVGcolorKeywords.add("blue");
SVGcolorKeywords.add("blueviolet");
SVGcolorKeywords.add("brown");
SVGcolorKeywords.add("burlywood");
SVGcolorKeywords.add("cadetblue");
SVGcolorKeywords.add("chartreuse");
SVGcolorKeywords.add("chocolate");
SVGcolorKeywords.add("coral");
SVGcolorKeywords.add("cornflowerblue");
SVGcolorKeywords.add("cornsilk");
SVGcolorKeywords.add("crimson");
SVGcolorKeywords.add("cyan");
SVGcolorKeywords.add("darkblue");
SVGcolorKeywords.add("darkcyan");
SVGcolorKeywords.add("darkgoldenrod");
SVGcolorKeywords.add("darkgray");
SVGcolorKeywords.add("darkgreen");
SVGcolorKeywords.add("darkgrey");
SVGcolorKeywords.add("darkkhaki");
SVGcolorKeywords.add("darkmagenta");
SVGcolorKeywords.add("darkolivegreen");
SVGcolorKeywords.add("darkorange");
SVGcolorKeywords.add("darkorchid");
SVGcolorKeywords.add("darkred");
SVGcolorKeywords.add("darksalmon");
SVGcolorKeywords.add("darkseagreen");
SVGcolorKeywords.add("darkslateblue");
SVGcolorKeywords.add("darkslategray");
SVGcolorKeywords.add("darkslategrey");
SVGcolorKeywords.add("darkturquoise");
SVGcolorKeywords.add("darkviolet");
SVGcolorKeywords.add("deeppink");
SVGcolorKeywords.add("deepskyblue");
SVGcolorKeywords.add("dimgray");
SVGcolorKeywords.add("dimgrey");
SVGcolorKeywords.add("dodgerblue");
SVGcolorKeywords.add("firebrick");
SVGcolorKeywords.add("floralwhite");
SVGcolorKeywords.add("forestgreen");
SVGcolorKeywords.add("fuchsia");
SVGcolorKeywords.add("gainsboro");
SVGcolorKeywords.add("ghostwhite");
SVGcolorKeywords.add("gold");
SVGcolorKeywords.add("goldenrod");
SVGcolorKeywords.add("gray");
SVGcolorKeywords.add("grey");
SVGcolorKeywords.add("green");
SVGcolorKeywords.add("greenyellow");
SVGcolorKeywords.add("honeydew");
SVGcolorKeywords.add("hotpink");
SVGcolorKeywords.add("indianred");
SVGcolorKeywords.add("indigo");
SVGcolorKeywords.add("ivory");
SVGcolorKeywords.add("khaki");
SVGcolorKeywords.add("lavender");
SVGcolorKeywords.add("lavenderblush");
SVGcolorKeywords.add("lawngreen");
SVGcolorKeywords.add("lemonchiffon");
SVGcolorKeywords.add("lightblue");
SVGcolorKeywords.add("lightcoral");
SVGcolorKeywords.add("lightcyan");
SVGcolorKeywords.add("lightgoldenrodyellow");
SVGcolorKeywords.add("lightgray");
SVGcolorKeywords.add("lightgreen");
SVGcolorKeywords.add("lightgrey");
SVGcolorKeywords.add("lightpink");
SVGcolorKeywords.add("lightsalmon");
SVGcolorKeywords.add("lightseagreen");
SVGcolorKeywords.add("lightskyblue");
SVGcolorKeywords.add("lightslategray");
SVGcolorKeywords.add("lightslategrey");
SVGcolorKeywords.add("lightsteelblue");
SVGcolorKeywords.add("lightyellow");
SVGcolorKeywords.add("lime");
SVGcolorKeywords.add("limegreen");
SVGcolorKeywords.add("linen");
SVGcolorKeywords.add("magenta");
SVGcolorKeywords.add("maroon");
SVGcolorKeywords.add("mediumaquamarine");
SVGcolorKeywords.add("mediumblue");
SVGcolorKeywords.add("mediumorchid");
SVGcolorKeywords.add("thistle");
SVGcolorKeywords.add("tomato");
SVGcolorKeywords.add("turquoise");
SVGcolorKeywords.add("violet");
SVGcolorKeywords.add("wheat");
SVGcolorKeywords.add("white");
SVGcolorKeywords.add("whitesmoke");
SVGcolorKeywords.add("yellow");
SVGcolorKeywords.add("yellowgreen");
}
private final static HashSet<String> CSScolorKeywords=new HashSet<String>();
static
{
CSScolorKeywords.add("aqua");
CSScolorKeywords.add("black");
CSScolorKeywords.add("blue");
CSScolorKeywords.add("fuchsia");
CSScolorKeywords.add("gray");
CSScolorKeywords.add("green");
CSScolorKeywords.add("lime");
CSScolorKeywords.add("maroon");
CSScolorKeywords.add("navy");
CSScolorKeywords.add("olive");
CSScolorKeywords.add("orange");
CSScolorKeywords.add("purple");
CSScolorKeywords.add("red");
CSScolorKeywords.add("silver");
CSScolorKeywords.add("teal");
CSScolorKeywords.add("white");
CSScolorKeywords.add("yellow");
// as of CSS3 this is valid: http://www.w3.org/TR/css3-color/#transparent-def
CSScolorKeywords.add("transparent");
}
private final static HashSet<String> CSSsystemColorKeywords=new HashSet<String>();
static {
CSScolorKeywords.add("ActiveBorder");
CSScolorKeywords.add("ActiveCaption");
CSScolorKeywords.add("AppWorkspace");
CSScolorKeywords.add("Background");
CSScolorKeywords.add("ButtonFace");
CSScolorKeywords.add("ButtonHighlight");
CSScolorKeywords.add("ButtonShadow");
CSScolorKeywords.add("ButtonText");
CSScolorKeywords.add("CaptionText");
CSScolorKeywords.add("GrayText");
CSScolorKeywords.add("Highlight");
CSScolorKeywords.add("HighlightText");
CSScolorKeywords.add("InactiveBorder");
CSScolorKeywords.add("InactiveCaption");
CSScolorKeywords.add("InactiveCaptionText");
CSScolorKeywords.add("InfoBackground");
CSScolorKeywords.add("InfoText");
CSScolorKeywords.add("Menu");
CSScolorKeywords.add("MenuText");
CSScolorKeywords.add("Scrollbar");
CSScolorKeywords.add("ThreeDDarkShadow");
CSScolorKeywords.add("ThreeDFace");
CSScolorKeywords.add("ThreeDHighlight");
CSScolorKeywords.add("ThreeDLightShadow");
CSScolorKeywords.add("ThreeDShadow");
CSScolorKeywords.add("Window");
CSScolorKeywords.add("WindowFrame");
CSScolorKeywords.add("WindowText");
}
public static boolean isValidCSSShape(String value)
{
if(value.indexOf("rect(")==0 && value.indexOf(')')==value.length()-1)
{
String[] shapeParts=value.substring(5,value.length()-1).split(",");
if(shapeParts.length==4)
{
for(String s : shapeParts)
{
s = s.trim();
if(!(s.equalsIgnoreCase("auto") || isLength(s, false)))
return false;
}
return true;
}
}
return false;
}
private final static HashSet<String> cssMedia = new HashSet<String>();
static {
cssMedia.addAll(Arrays.asList("all", "aural", "braille", "embossed", "handheld", "print", "projection", "screen", "speech", "tty", "tv"));
}
public static boolean isMedia(String media) {
return cssMedia.contains(media);
}
public static boolean isColor(String value)
{
value=value.trim();
if(CSScolorKeywords.contains(value) || CSSsystemColorKeywords.contains(value) || SVGcolorKeywords.contains(value))
return true;
if(value.indexOf('#')==0)
{
if(value.length()==4)
{
try{
Integer.valueOf(value.substring(1,2),16).intValue();
Integer.valueOf(value.substring(2,3),16).intValue();
Integer.valueOf(value.substring(3,4),16).intValue();
return true;
}
catch(Exception e)
{
}
}
else if(value.length()==7)
{
try{
Integer.valueOf(value.substring(1,3),16).intValue();
Integer.valueOf(value.substring(3,5),16).intValue();
Integer.valueOf(value.substring(5,7),16).intValue();
return true;
}
catch(Exception e)
{
}
}
}
if(value.indexOf("rgb(")==0 && value.indexOf(')')==value.length()-1)
{
String[] colorParts=value.substring(4,value.length()-1).split(",");
if(colorParts.length!=3)
return false;
boolean isValidColorParts=true;
for(int i=0; i<colorParts.length && isValidColorParts;i++)
{
if(!(isPercentage(colorParts[i].trim()) || isInteger(colorParts[i].trim())))
isValidColorParts = false;
}
if(isValidColorParts)
return true;
}
if(value.indexOf("rgba(")==0 && value.indexOf(')')==value.length()-1)
{
String[] colorParts=value.substring(5,value.length()-1).split(",");
if(colorParts.length!=4)
return false;
boolean isValidColorParts=true;
for(int i=0; i<colorParts.length-1 && isValidColorParts;i++)
{
if(!(isPercentage(colorParts[i].trim()) || isInteger(colorParts[i].trim())))
isValidColorParts = false;
}
if(isValidColorParts && isNumber(colorParts[3]))
return true;
}
if(value.indexOf("hsl(")==0 && value.indexOf(')')==value.length()-1)
{
String[] colorParts = value.substring(4, value.length() - 1).split(",");
if (colorParts.length != 3) {
return false;
}
if(isNumber(colorParts[0]) && isPercentage(colorParts[1]) && isPercentage(colorParts[2]))
return true;
}
if(value.indexOf("hsla(")==0 && value.indexOf(')')==value.length()-1)
{
String[] colorParts = value.substring(5, value.length() - 1).split(",");
if (colorParts.length != 4) {
return false;
}
if(isNumber(colorParts[0]) && isPercentage(colorParts[1]) && isPercentage(colorParts[2]) && isNumber(colorParts[3]))
return true;
}
return false;
}
public static boolean isCSSTransform(String value) {
value = value.trim();
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform(\""+value+"\")");
if(value.indexOf("matrix(")==0 && value.indexOf(')')==value.length()-1)
{
String[] parts = value.substring(7, value.length() - 1).split(",");
if (parts.length != 6) {
return false;
}
boolean isValid = true;
for (int i = 0; i < parts.length && isValid; i++) {
if (!isNumber(parts[i].trim())) {
isValid = false;
}
}
if (isValid) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a matrix()");
return true;
}
}
if(value.indexOf("translateX(")==0 && value.indexOf(')')==value.length()-1)
{
String part = value.substring(11, value.length() - 1);
if (isPercentage(part.trim()) || isLength(part.trim(), false)) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a translateX()");
return true;
}
}
if(value.indexOf("translateY(")==0 && value.indexOf(')')==value.length()-1)
{
String part = value.substring(11, value.length() - 1);
if (isPercentage(part.trim()) || isLength(part.trim(), false)) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a translateY()");
return true;
}
}
if(value.indexOf("translate(")==0 && value.indexOf(')')==value.length()-1)
{
String[] parts = value.substring(10, value.length() - 1).split(",");
if (parts.length == 1 && (isPercentage(parts[0].trim()) || isLength(parts[0].trim(), false))) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a translate()");
return true;
}else if (parts.length == 2 && (isPercentage(parts[0].trim()) || isLength(parts[0].trim(), false)) && (isPercentage(parts[1].trim()) || isLength(parts[1].trim(), false))) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a translate()");
return true;
}
}
if(value.indexOf("scale(")==0 && value.indexOf(')')==value.length()-1)
{
String[] parts = value.substring(6, value.length() - 1).split(",");
if (parts.length == 1 && isNumber(parts[0].trim())) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a scale()");
return true;
}else if (parts.length == 2 && isNumber(parts[0].trim()) && isNumber(parts[1].trim())) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a scale()");
return true;
}
}
if(value.indexOf("scaleX(")==0 && value.indexOf(')')==value.length()-1)
{
String part = value.substring(7, value.length() - 1);
if (isNumber(part.trim())) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a scaleX()");
return true;
}
}
if(value.indexOf("scaleY(")==0 && value.indexOf(')')==value.length()-1)
{
String part = value.substring(7, value.length() - 1);
if (isNumber(part.trim())) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a scaleY()");
return true;
}
}
if(value.indexOf("rotate(")==0 && value.indexOf(')')==value.length()-1)
{
String part = value.substring(7, value.length() - 1);
if (isAngle(part.trim())) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a rotate()");
return true;
}
}
if(value.indexOf("skewX(")==0 && value.indexOf(')')==value.length()-1)
{
String part = value.substring(6, value.length() - 1);
if (isNumber(part.trim()) || isAngle(part.trim())) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a skewX()");
return true;
}
}
if(value.indexOf("skewY(")==0 && value.indexOf(')')==value.length()-1)
{
String part = value.substring(6, value.length() - 1);
if (isNumber(part.trim()) || isAngle(part.trim())) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a skewY()");
return true;
}
}
if(value.indexOf("skew(")==0 && value.indexOf(')')==value.length()-1)
{
String[] parts = value.substring(5, value.length() - 1).split(",");
if (parts.length == 1 && (isNumber(parts[0].trim()) || isAngle(parts[0].trim()))) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a skew()");
return true;
}else if (parts.length == 2 && (isNumber(parts[0].trim()) || isAngle(parts[0].trim())) && (isNumber(parts[1].trim()) || isAngle(parts[0].trim()))) {
if(logDEBUG) Logger.debug(FilterUtils.class, "isCSSTransform found a skew()");
return true;
}
}
return false;
}
public static boolean isFrequency(String value)
{
String firstPart;
value=value.trim().toLowerCase();
boolean isValidFrequency=true;
if(value.indexOf("khz")!=-1)
{
int index=value.indexOf("khz");
firstPart=value.substring(0,index).trim();
if(!("khz".equals(value.substring(index,value.length()).trim())))
{
isValidFrequency=false;
}
}
else if(value.indexOf("hz")!=-1)
{
int index=value.indexOf("hz");
firstPart=value.substring(0,index).trim();
if(!("hz".equals(value.substring(index,value.length()).trim())))
{
isValidFrequency=false;
}
}
else
firstPart=value.trim();
if(isValidFrequency)
{
try
{
float temp=Float.parseFloat(firstPart);
if(temp>0)
return true;
}
catch(Exception e)
{
}
}
return false;
}
public static boolean isTime(String value)
{
value=value.toLowerCase();
String intValue;
if(value.indexOf("ms")>-1 && value.length()>2)
intValue=value.substring(0,value.length()-2);
else if(value.indexOf('s')>-1 && value.length()>1)
intValue=value.substring(0,value.length()-1);
else
return false;
return isNumber(intValue);
}
public static String[] removeWhiteSpace(String[] values, boolean stripQuotes)
{
if(values==null) return null;
ArrayList<String> arrayToReturn=new ArrayList<String>();
for(String value:values)
{
value = value.trim();
if(stripQuotes)
value = CSSTokenizerFilter.removeOuterQuotes(value).trim();
if(value!=null && !("".equals(value.trim())))
arrayToReturn.add(value);
}
return arrayToReturn.toArray(new String[0]);
}
public static String sanitizeURI(FilterCallback cb,String URI)
{
try
{
return cb.processURI(URI, null);
}
catch(Exception e)
{
return "";
}
}
public static boolean isURI(FilterCallback cb,String URI)
{
return URI.equals(sanitizeURI(cb,URI));
}
public static String[] splitOnCharArray(String value,String splitOn)
{
ArrayList<String> pointPairs=new ArrayList<String>();
//Creating HashMap for faster search operation
int i;
int prev=0;
for(i=0;i<value.length();i++)
{
if(splitOn.indexOf(value.charAt(i))!=-1)
{
pointPairs.add(value.substring(prev,i));
while(i<value.length() && splitOn.indexOf(value.charAt(i))!=-1)
{
i++;
}
prev=i;
i--;
}
}
boolean isLastElement=false;
for(i=prev;i<value.length();i++)
{
if(splitOn.indexOf(value.charAt(i))==-1)
{
isLastElement=true;
break;
}
}
if(isLastElement)
{
pointPairs.add(value.substring(prev,value.length()));
}
return pointPairs.toArray(new String[0]);
}
public static boolean isPointPair(String value)
{
String[] pointPairs=splitOnCharArray(value," \n\t");
for(String pointPair: pointPairs)
{
String[] strParts=pointPair.split(",");
if(strParts.length!=2)
return false;
try
{
Float.parseFloat(strParts[0]);
Float.parseFloat(strParts[1]);
}
catch(Exception e)
{
return false;
}
}
return true;
}
public static boolean isIntegerInRange(String strValue, int min, int max)
{
try
{
// Strip any leading '+' character, because Integer.parseInt handles it differently between Java 6 (fails) and 7 (succeeds).
if(strValue.length()>1 && strValue.charAt(0)=='+' && Character.isDigit(strValue.charAt(1)))
{
strValue = strValue.substring(1,strValue.length());
}
int value = Integer.parseInt(strValue);
return (value>=min && value<=max);
}
catch(Exception e)
{
return false;
}
}
public static boolean isNth(String value)
{
if(value.equals("odd") || value.equals("even") || isIntegerInRange(value, -MAX_NTH, MAX_NTH))
{
return true;
}
else
{
// Check if value has the form "an+b" - where a and b can be any in range integer.
int nIndex=value.indexOf('n');
if(nIndex!=-1)
{
int aLength=nIndex;
if(aLength==0 || (aLength==1 && value.charAt(0)=='-') || isIntegerInRange(value.substring(0,aLength), -MAX_NTH, MAX_NTH))
{
int bIndex=nIndex+1;
int bLength=value.length()-bIndex;
if(bLength==0 || ((value.charAt(bIndex)=='+' || value.charAt(bIndex)=='-') && isIntegerInRange(value.substring(bIndex,value.length()), -MAX_NTH, MAX_NTH)))
{
return true;
}
}
}
}
return false;
}
// public static HTMLNode getHTMLNodeFromElement(Element node)
// {
// String[] propertyName=new String[node.getAttributes().size()];
// String[] propertyValue=new String[node.getAttributes().size()];
// int index=0;
// List<Attribute> attrList=node.getAttributes();
// for(Attribute currentAttr:attrList)
// {
// propertyName[index]=currentAttr.getName();
// propertyValue[index]=currentAttr.getValue();
// }
// return new HTMLNode(node.getName(),propertyName,propertyValue,node.getValue());
// }
}