diff --git a/src/freenet/client/filter/CSSTokenizerFilter.java b/src/freenet/client/filter/CSSTokenizerFilter.java index 92cdf1591e9..a697c072c04 100644 --- a/src/freenet/client/filter/CSSTokenizerFilter.java +++ b/src/freenet/client/filter/CSSTokenizerFilter.java @@ -25,7 +25,6 @@ import freenet.support.Fields; import freenet.support.Logger; import freenet.support.api.Bucket; -import freenet.support.io.Closer; import freenet.support.io.FileBucket; /** Comprehensive CSS2.1 filter. The old jflex-based filter was very far @@ -119,6 +118,7 @@ public static T[] concat(T[] a, T[] b) { allelementVerifiers.add("align-self"); allelementVerifiers.add("azimuth"); allelementVerifiers.add("background-attachment"); + allelementVerifiers.add("background-blend-mode"); allelementVerifiers.add("background-clip"); allelementVerifiers.add("background-color"); allelementVerifiers.add("background-image"); @@ -127,6 +127,7 @@ public static T[] concat(T[] a, T[] b) { allelementVerifiers.add("background-repeat"); allelementVerifiers.add("background-size"); allelementVerifiers.add("background"); + allelementVerifiers.add("block-size"); allelementVerifiers.add("border-collapse"); allelementVerifiers.add("border-color"); allelementVerifiers.add("border-top-color"); @@ -216,6 +217,7 @@ public static T[] concat(T[] a, T[] b) { allelementVerifiers.add("flex-wrap"); allelementVerifiers.add("float"); allelementVerifiers.add("font-family"); + allelementVerifiers.add("font-kerning"); allelementVerifiers.add("font-size"); allelementVerifiers.add("font-style"); allelementVerifiers.add("font-variant"); @@ -223,6 +225,7 @@ public static T[] concat(T[] a, T[] b) { allelementVerifiers.add("font"); allelementVerifiers.add("hanging-punctuation"); allelementVerifiers.add("height"); + allelementVerifiers.add("inline-size"); allelementVerifiers.add("justify-content"); allelementVerifiers.add("justify-items"); allelementVerifiers.add("justify-self"); @@ -239,14 +242,20 @@ public static T[] concat(T[] a, T[] b) { allelementVerifiers.add("margin-top"); allelementVerifiers.add("margin-bottom"); allelementVerifiers.add("margin"); + allelementVerifiers.add("max-block-size"); allelementVerifiers.add("max-height"); + allelementVerifiers.add("max-inline-size"); allelementVerifiers.add("max-width"); + allelementVerifiers.add("min-block-size"); allelementVerifiers.add("min-height"); + allelementVerifiers.add("min-inline-size"); allelementVerifiers.add("min-width"); + allelementVerifiers.add("mix-blend-mode"); allelementVerifiers.add("nav-down"); allelementVerifiers.add("nav-left"); allelementVerifiers.add("nav-right"); allelementVerifiers.add("nav-up"); + allelementVerifiers.add("object-fit"); allelementVerifiers.add("opacity"); allelementVerifiers.add("order"); allelementVerifiers.add("orphans"); @@ -256,6 +265,7 @@ public static T[] concat(T[] a, T[] b) { allelementVerifiers.add("outline-width"); allelementVerifiers.add("outline"); allelementVerifiers.add("overflow"); + allelementVerifiers.add("overflow-wrap"); allelementVerifiers.add("padding-top"); allelementVerifiers.add("padding-right"); allelementVerifiers.add("padding-bottom"); @@ -283,6 +293,7 @@ public static T[] concat(T[] a, T[] b) { allelementVerifiers.add("speech-rate"); allelementVerifiers.add("stress"); allelementVerifiers.add("table-layout"); + allelementVerifiers.add("tab-size"); allelementVerifiers.add("text-align"); allelementVerifiers.add("text-align-last"); allelementVerifiers.add("text-autospace"); @@ -316,12 +327,13 @@ public static T[] concat(T[] a, T[] b) { allelementVerifiers.add("voice-family"); allelementVerifiers.add("volume"); allelementVerifiers.add("white-space"); - allelementVerifiers.add("white-space-collapsing"); + allelementVerifiers.add("white-space-collapse"); allelementVerifiers.add("widows"); allelementVerifiers.add("width"); allelementVerifiers.add("word-break"); allelementVerifiers.add("word-spacing"); allelementVerifiers.add("word-wrap"); + allelementVerifiers.add("writing-mode"); allelementVerifiers.add("z-index"); @@ -331,7 +343,7 @@ public static T[] concat(T[] a, T[] b) { * Array for storing additional Verifier objects for validating Regular expressions in CSS Property value * e.g. [ | transparent]{1,4}. It is explained in detail in CSSPropertyVerifier class */ - private final static CSSPropertyVerifier[] auxilaryVerifiers=new CSSPropertyVerifier[148]; + private final static CSSPropertyVerifier[] auxilaryVerifiers=new CSSPropertyVerifier[149]; static { /*CSSPropertyVerifier(String[] allowedValues,String[] possibleValues,String expression,boolean onlyValueVerifier)*/ @@ -349,15 +361,20 @@ public static T[] concat(T[] a, T[] b) { // auxilaryVerifiers[15]=new CSSPropertyVerifier(Arrays.asList("transparent"),Arrays.asList("co"),null,null,true); + //list-style-type + auxilaryVerifiers[35]=new CSSPropertyVerifier(Arrays.asList("disc","circle","square","decimal","decimal-leading-zero","lower-roman","upper-roman","lower-greek","lower-latin","upper-latin","armenian","georgian","lower-alpha","upper-alpha","none","arabic-indic","bengali","cambodian","cjk-decimal","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","devanagari","disclosure-closed","disclosure-open","ethiopic-numeric","gujarati","gurmukhi","hebrew","hiragana","hiragana-iroha","japanese-formal","japanese-informal","kannada","katakana","katakana-iroha","khmer","korean-hangul-formal","korean-hanja-formal","lao","lower-armenian","malayalam","mongolian","myanmar","oriya","persian","simp-chinese-formal","simp-chinese-informal","tamil","telugu","thai","tibetan","trad-chinese-formal","trad-chinese-informal","upper-armenian"),Arrays.asList("st"),null,null,true); + // auxilaryVerifiers[61]=new CSSPropertyVerifier(Arrays.asList("border-box", "padding-box", "content-box"),null,null,null,true); // auxilaryVerifiers[64]=new CSSPropertyVerifier(null,Arrays.asList("le", "pe"),null,null,true); + // + auxilaryVerifiers[73]=new CSSPropertyVerifier(null, Arrays.asList("co"), null, null, true); + // auxilaryVerifiers[71]=new CSSPropertyVerifier(Arrays.asList("inset"), null, null, null, true); auxilaryVerifiers[72]=new CSSPropertyVerifier(null, Arrays.asList("le"), null, null, true); - auxilaryVerifiers[73]=new CSSPropertyVerifier(null, Arrays.asList("co"), null, null, true); auxilaryVerifiers[74]=new CSSPropertyVerifier(null, null, Arrays.asList("72<1,4>"), null, true); auxilaryVerifiers[75]=new CSSPropertyVerifier(null, null, Arrays.asList("71a74a73"), null, true); @@ -373,7 +390,7 @@ public static T[] concat(T[] a, T[] b) { auxilaryVerifiers[78]=new CSSPropertyVerifier(null,null,Arrays.asList("70<1,2>"),null,true); // - auxilaryVerifiers[79]=new CSSPropertyVerifier(null, null, Arrays.asList("74a73"), null, true); + auxilaryVerifiers[79]=new CSSPropertyVerifier(null, null, Arrays.asList("73 72 72 72","73 72 72","72 72 72 73","72 72 73","72 72"), null, true); // auxilaryVerifiers[85]=new CSSPropertyVerifier(Arrays.asList("normal"), Arrays.asList("le","pe"), null, null, true); @@ -384,15 +401,13 @@ public static T[] concat(T[] a, T[] b) { auxilaryVerifiers[102] = new CSSPropertyVerifier(Arrays.asList("line-through"), null, null, null, true); auxilaryVerifiers[115] = new CSSPropertyVerifier(Arrays.asList("none"),null,null,Arrays.asList("100a101a102")); auxilaryVerifiers[116] = new CSSPropertyVerifier(Arrays.asList("blink"), null, null, null, true); - // - auxilaryVerifiers[103] = new CSSPropertyVerifier(null, Arrays.asList("co"), null, null, true); // auxilaryVerifiers[104] = new CSSPropertyVerifier(Arrays.asList("solid", "double", "dotted", "dashed", "wave"), null, null, null, true); // auxilaryVerifiers[105]=new CSSPropertyVerifier(Arrays.asList("filled","open"),null,null,null,true); auxilaryVerifiers[106]=new CSSPropertyVerifier(Arrays.asList("dot","circle","double-circle","triangle","sesame"),null,null,null,true); - auxilaryVerifiers[107]=new CSSPropertyVerifier(Arrays.asList("none"),ElementInfo.VISUALMEDIA,Arrays.asList("st"),Arrays.asList("105a106")); + auxilaryVerifiers[107]=new CSSPropertyVerifier(Arrays.asList("none"),null,Arrays.asList("st"),Arrays.asList("105a106")); // and // auto | | || [ ? && ] @@ -454,6 +469,11 @@ else if("background-attachment".equalsIgnoreCase(element)){ elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("60<1,65535>"), true,true)); allelementVerifiers.remove(element); } + else if("background-blend-mode".equalsIgnoreCase(element)){ + auxilaryVerifiers[148] = new CSSPropertyVerifier(Arrays.asList("normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"), null, null, null, true); + elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("148<1,2>"), true,true)); + allelementVerifiers.remove(element); + } else if("background-clip".equalsIgnoreCase(element)) { elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("61<1,65535>"), true,true)); @@ -512,6 +532,11 @@ else if("background".equalsIgnoreCase(element)) elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("6a7a8a9a10"))); allelementVerifiers.remove(element); } + else if("block-size".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); + allelementVerifiers.remove(element); + } else if("border-collapse".equalsIgnoreCase(element)) { elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("collapse","separate"),ElementInfo.VISUALMEDIA)); @@ -775,12 +800,12 @@ else if("caption-side".equalsIgnoreCase(element)) allelementVerifiers.remove(element); } else if ("caret-color".equalsIgnoreCase(element)) { - elementVerifiers.put(element, new CSSPropertyVerifier(Arrays.asList("auto", "transparent"), ElementInfo.VISUALMEDIA, Arrays.asList("co"))); + elementVerifiers.put(element, new CSSPropertyVerifier(Arrays.asList("auto", "transparent", "currentcolor"), ElementInfo.VISUALMEDIA, Arrays.asList("co"))); allelementVerifiers.remove(element); } else if("clear".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("none","left","right","both"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("none","left","right","both","inline-start","inline-end"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if("clip".equalsIgnoreCase(element)) @@ -988,7 +1013,7 @@ else if("empty-cells".equalsIgnoreCase(element)) } else if("float".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("left","right","none"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("left","right","none","inline-start","inline-end"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if ("flex".equalsIgnoreCase(element)) { // flex: none | ? || @@ -1027,9 +1052,14 @@ else if("font-family".equalsIgnoreCase(element)) elementVerifiers.put(element,new FontPropertyVerifier(false)); allelementVerifiers.remove(element); } + else if("font-kerning".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto","none","normal"),ElementInfo.VISUALMEDIA)); + allelementVerifiers.remove(element); + } else if("font-size".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("xx-small","x-small","small","medium","large","x-large","xx-large","larger","smaller"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("xx-small","x-small","small","medium","large","x-large","xx-large","xxx-large","larger","smaller"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); allelementVerifiers.remove(element); } else if("font-style".equalsIgnoreCase(element)) @@ -1090,6 +1120,11 @@ else if("hanging-punctuation".equalsIgnoreCase(element)) allelementVerifiers.remove(element); } else if("height".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); + allelementVerifiers.remove(element); + } + else if("inline-size".equalsIgnoreCase(element)) { elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); allelementVerifiers.remove(element); @@ -1135,7 +1170,7 @@ else if("list-style-position".equalsIgnoreCase(element)) } else if("list-style-type".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("disc","circle","square","decimal","decimal-leading-zero","lower-roman","upper-roman","lower-greek","lower-latin","upper-latin","armenian","georgian","lower-alpha","upper-alpha","none"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("35"))); allelementVerifiers.remove(element); } else if("list-style".equalsIgnoreCase(element)) @@ -1144,8 +1179,6 @@ else if("list-style".equalsIgnoreCase(element)) auxilaryVerifiers[33]=new CSSPropertyVerifier(Arrays.asList("none"),Arrays.asList("ur"),null,null,true); //list-style-position auxilaryVerifiers[34]=new CSSPropertyVerifier(Arrays.asList("inside","outside"),null,null,null,true); - //list-style-type - auxilaryVerifiers[35]=new CSSPropertyVerifier(Arrays.asList("disc","circle","square","decimal","decimal-leading-zero","lower-roman","upper-roman","lower-greek","lower-latin","upper-latin","armenian","georgian","lower-alpha","upper-alpha","none"),null,null,null,true); elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("33a34a35"))); allelementVerifiers.remove(element); } @@ -1177,26 +1210,52 @@ else if("margin".equalsIgnoreCase(element)) elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("36<1,4>"))); allelementVerifiers.remove(element); } + else if("max-block-size".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("none"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); + allelementVerifiers.remove(element); + } else if("max-height".equalsIgnoreCase(element)) { elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("none"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); allelementVerifiers.remove(element); } + else if("max-inline-size".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("none"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); + allelementVerifiers.remove(element); + } else if("max-width".equalsIgnoreCase(element)) { elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("none"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); allelementVerifiers.remove(element); } + else if("min-block-size".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); + allelementVerifiers.remove(element); + } else if("min-height".equalsIgnoreCase(element)) { elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); allelementVerifiers.remove(element); } + else if("min-inline-size".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); + allelementVerifiers.remove(element); + } else if("min-width".equalsIgnoreCase(element)) { elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto"),ElementInfo.VISUALMEDIA,Arrays.asList("le","pe"))); allelementVerifiers.remove(element); - } else if ("nav-down".equalsIgnoreCase(element)) { + } + else if("mix-blend-mode".equalsIgnoreCase(element)){ + auxilaryVerifiers[148] = new CSSPropertyVerifier(Arrays.asList("normal","multiply","screen","overlay","darken","lighten","color-dodge","color-burn","hard-light","soft-light","difference","exclusion","hue","saturation","color","luminosity"), null, null, null, true); + elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("148<1,2>"), true,true)); + allelementVerifiers.remove(element); + } + else if ("nav-down".equalsIgnoreCase(element)) { elementVerifiers.put(element, new CSSPropertyVerifier(Arrays.asList("auto"), ElementInfo.VISUALINTERACTIVEMEDIA, null, Arrays.asList("143 144?"))); allelementVerifiers.remove(element); } else if ("nav-left".equalsIgnoreCase(element)) { @@ -1208,6 +1267,9 @@ else if("min-width".equalsIgnoreCase(element)) } else if ("nav-up".equalsIgnoreCase(element)) { elementVerifiers.put(element, new CSSPropertyVerifier(Arrays.asList("auto"), ElementInfo.VISUALINTERACTIVEMEDIA, null, Arrays.asList("143 144?"))); allelementVerifiers.remove(element); + } else if ("object-fit".equalsIgnoreCase(element)) { + elementVerifiers.put(element, new CSSPropertyVerifier(Arrays.asList("contain","cover","fill","none","scale-down"), ElementInfo.VISUALMEDIA)); + allelementVerifiers.remove(element); } else if("opacity".equalsIgnoreCase(element)) { @@ -1253,7 +1315,7 @@ else if("outline".equalsIgnoreCase(element)) } else if("overflow".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("visible","hidden","scroll","auto"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("visible","hidden","scroll","auto","clip"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if("padding-top".equalsIgnoreCase(element)) @@ -1411,6 +1473,11 @@ else if("table-layout".equalsIgnoreCase(element)) elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("auto","fixed"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } + else if("tab-size".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,Arrays.asList("le","in"))); + allelementVerifiers.remove(element); + } else if("text-align".equalsIgnoreCase(element)) { // FIXME: We don't support "one character" as the spec says http://www.w3.org/TR/css3-text/#text-align0 elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("start","end","left","right","center","justify","match-parent"),ElementInfo.VISUALMEDIA)); @@ -1433,13 +1500,13 @@ else if("text-autospace".equalsIgnoreCase(element)) } else if("text-decoration".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("115a103a104a116"))); + elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("115a73a104a116"))); allelementVerifiers.remove(element); } else if("text-decoration-color".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("103"))); + elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("73"))); allelementVerifiers.remove(element); } @@ -1467,13 +1534,13 @@ else if("text-decoration-style".equalsIgnoreCase(element)) } else if("text-emphasis".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("103a107"))); + elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("73a107"))); allelementVerifiers.remove(element); } else if("text-emphasis-color".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("103"))); + elementVerifiers.put(element,new CSSPropertyVerifier(null,ElementInfo.VISUALMEDIA,null,Arrays.asList("73"))); allelementVerifiers.remove(element); } @@ -1518,22 +1585,22 @@ else if("text-overflow".equalsIgnoreCase(element)) } else if("text-shadow".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("none"),ElementInfo.VISUALMEDIA,null,Arrays.asList("79<0,65535>"),true,true)); + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("none"),ElementInfo.VISUALMEDIA,null,Arrays.asList("79"),true,true)); allelementVerifiers.remove(element); } else if("text-transform".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("capitalize","uppercase","lowercase","none","fullwidth","large-kana"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("capitalize","uppercase","lowercase","none","fullwidth","full-size-kana","math-auto"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if("text-underline-position".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("auto","under","alphabetic","over"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("auto","under","left","right"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if("text-wrap".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("normal","unrestricted","none","suppress"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("wrap","nowrap","balance"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if("top".equalsIgnoreCase(element)) @@ -1559,7 +1626,7 @@ else if("transform-origin".equalsIgnoreCase(element)) } else if("unicode-bidi".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier( Arrays.asList("normal", "embed", "bidi-override"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("normal", "embed", "bidi-override", "isolate", "isolate-override", "plaintext"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if("vertical-align".equalsIgnoreCase(element)) @@ -1587,12 +1654,9 @@ else if("white-space".equalsIgnoreCase(element)) elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("normal","pre","nowrap","pre-wrap","pre-line"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } - else if("white-space-collapsing".equalsIgnoreCase(element)) + else if("white-space-collapse".equalsIgnoreCase(element)) { - auxilaryVerifiers[80]=new CSSPropertyVerifier(Arrays.asList("preserve","preserve-break"),null,null,null,true); - auxilaryVerifiers[81]=new CSSPropertyVerifier(Arrays.asList("trim-inner"),null,null,null,true); - auxilaryVerifiers[82]=new CSSPropertyVerifier(null,null,Arrays.asList("80a81"),null,true); - elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("collapse" ,"discard"),null,ElementInfo.VISUALMEDIA,Arrays.asList("82"))); + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("preserve","preserve-break","collapse","discard","break-spaces"),null,ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if("widows".equalsIgnoreCase(element)) @@ -1607,7 +1671,7 @@ else if("width".equalsIgnoreCase(element)) } else if("word-break".equalsIgnoreCase(element)) { - elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("normal","break-all","hyphenate"),ElementInfo.VISUALMEDIA)); + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("normal","break-all","hyphenate","keep-all"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } else if("word-spacing".equalsIgnoreCase(element)) @@ -1615,11 +1679,17 @@ else if("word-spacing".equalsIgnoreCase(element)) elementVerifiers.put(element,new CSSPropertyVerifier(null,null,ElementInfo.VISUALMEDIA,Arrays.asList("85<1,3>"))); allelementVerifiers.remove(element); } - else if("word-wrap".equalsIgnoreCase(element)) + else if("word-wrap".equalsIgnoreCase(element) || "overflow-wrap".equalsIgnoreCase(element)) { + // word-wrap was a Microsoft extension that got renamed to overflow-wrap in CSS Text Module Level 3. elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("normal", "break-word", "anywhere"),ElementInfo.VISUALMEDIA)); allelementVerifiers.remove(element); } + else if("writing-mode".equalsIgnoreCase(element)) + { + elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("horizontal-tb", "vertical-rl", "vertical-lr", "lr", "lr-tb", "rl", "tb", "tb-lr", "tb-rl"),ElementInfo.VISUALMEDIA)); + allelementVerifiers.remove(element); + } else if("z-index".equalsIgnoreCase(element)) { elementVerifiers.put(element,new CSSPropertyVerifier(Arrays.asList("auto"),ElementInfo.VISUALMEDIA,Arrays.asList("in"))); @@ -3914,7 +3984,7 @@ public boolean checkValidity(String[] media,String[] elements,ParsedWord[] words // CSS Property has one of the explicitly defined values return true; } - if (lowerCaseWord.equals("initial") || lowerCaseWord.equals("inherit") || lowerCaseWord.equals("unset")) { + if (lowerCaseWord.equals("initial") || lowerCaseWord.equals("inherit") || lowerCaseWord.equals("unset") || lowerCaseWord.equals("revert") || lowerCaseWord.equals("revert-layer")) { // CSS Property is one of the Defaulting Keywords (http://www.w3.org/TR/css3-cascade/#defaulting-keywords) return true; } @@ -3985,10 +4055,15 @@ public boolean checkValidity(String[] media,String[] elements,ParsedWord[] words } } - if(words[0] instanceof ParsedIdentifier && isColor) { - if(FilterUtils.isColor(((ParsedIdentifier)words[0]).original)) + if(words[0] instanceof ParsedIdentifier) { + String value = ((ParsedIdentifier)words[0]).original; + if(isColor && FilterUtils.isColor(value)) { return true; - + } + if(isLength && (value.equalsIgnoreCase("min-content") || value.equalsIgnoreCase("max-content") || value.equalsIgnoreCase("fit-content"))) { + //TODO: support fit-content(20em) + return true; + } } if(isURI && words[0] instanceof ParsedURL) @@ -4865,18 +4940,15 @@ public static void main(String arg[]) throws Throwable { fout.delete(); final Bucket inputBucket = new FileBucket(fin, true, false, false, false); final Bucket outputBucket = new FileBucket(fout, false, true, false, false); - InputStream inputStream = null; - OutputStream outputStream = null; - try { - inputStream = inputBucket.getInputStream(); - outputStream = outputBucket.getOutputStream(); + try ( + InputStream inputStream = inputBucket.getInputStream(); + OutputStream outputStream = outputBucket.getOutputStream() + ) { Logger.setupStdoutLogging(Logger.LogLevel.DEBUG, ""); ContentFilter.filter(inputStream, outputStream, "text/css", new URI("http://127.0.0.1:8888/freenet:USK@ZupQjDFZSc3I4orBpl1iTEAPZKo2733RxCUbZ2Q7iH0,EO8Tuf8SP3lnDjQdAPdCM2ve2RaUEN8m-hod3tQ5oQE,AQACAAE/jFreesite/19/Style/"), null, null, null, null); } finally { - Closer.close(inputStream); - Closer.close(outputStream); inputBucket.free(); outputBucket.free(); } diff --git a/src/freenet/client/filter/ElementInfo.java b/src/freenet/client/filter/ElementInfo.java index 91c40b34867..e31cf259e7f 100644 --- a/src/freenet/client/filter/ElementInfo.java +++ b/src/freenet/client/filter/ElementInfo.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.regex.Pattern; public class ElementInfo { @@ -53,42 +54,6 @@ public class ElementInfo { "button" ))); - // FIXME add some more languages. - public static final Set LANGUAGES = - Collections.unmodifiableSet(new HashSet(Arrays.asList( - "az", - "be", - "bg", - "cs", - "de", - "el", - "en", - "es", - "fi", - "fr", - "id", - "it", - "ja", - "ka", - "kk", - "ky", - "lv", - "mo", - "nl", - "no", - "pl", - "pt", - "ro", - "ru", - "sv", - "tl", - "tr", - "tt", - "uk", - "zh-hans", - "zh-hant" - ))); - public static final Set MEDIA = Collections.unmodifiableSet(new HashSet(Arrays.asList( "all", @@ -182,13 +147,22 @@ public class ElementInfo { "new york6" ))); + // https://developer.mozilla.org/en-US/docs/Web/CSS/font-family public static final Set GENERIC_FONT_KEYWORDS = Collections.unmodifiableSet(new HashSet(Arrays.asList( "serif", "sans-serif", "cursive", "fantasy", - "monospace" + "monospace", + "system-ui", + "ui-serif", + "ui-sans-serif", + "ui-monospace", + "ui-rounded", + "emoji", + "math", + "fangsong" ))); public static final Set GENERIC_VOICE_KEYWORDS = @@ -206,19 +180,37 @@ public class ElementInfo { "nth-last-child", "nth-of-type", "nth-last-of-type", - "link", - "visited", + "link", // inverse of visited (see BANNED_PSEUDOCLASS below) + "visited", // privacy risk (see BANNED_PSEUDOCLASS below) "hover", "active", - "checked", + "checked", // forms "focus", "focus-within", - "lang", "first-line", "first-letter", "before", "after", - "target" + "target", + "any-link", + "default", // forms + "defined", // Javascript only (BANNED_PSEUDOCLASS) + "disabled", // forms + "empty", + "enabled", // forms + "focus-visible", + "indeterminate", // forms + "in-range", // forms + "invalid", // forms + "only-child", + "only-of-type", + "optional", // forms + "out-of-range", // forms + "placeholder-shown", // forms + "read-only", // forms + "read-write", // forms + "required", // forms + "root" ))); public static final Set BANNED_PSEUDOCLASS = @@ -249,7 +241,10 @@ public class ElementInfo { // is considered too much of a danger, so we scrub that pseudoclass. // // [1] http://lcamtuf.coredump.cx/css_calc/ - "visited" + "link", + "visited", + // Javascript only + "defined" ))); public static boolean isSpecificFontFamily(String font) { @@ -432,49 +427,63 @@ public static boolean isBannedPseudoClass(String cname) // Pseudo-classes can be chained, at least dynamic ones can, see CSS2.1 section 5.11.3 String[] split = cname.split(":"); for(String s : split) - if(isBannedPseudoClass(s)) return true; + if(isBannedPseudoClass2(s)) return true; return false; + } else { + return isBannedPseudoClass2(cname); } - cname=cname.toLowerCase(); - return BANNED_PSEUDOCLASS.contains(cname); } + private static boolean isBannedPseudoClass2(String cname) + { + return BANNED_PSEUDOCLASS.contains(cname.toLowerCase()); + } + public static boolean isValidPseudoClass(String cname) { if(cname.indexOf(':') != -1) { // Pseudo-classes can be chained, at least dynamic ones can, see CSS2.1 section 5.11.3 String[] split = cname.split(":"); for(String s : split) - if(!isValidPseudoClass(s)) return false; + if(!isValidPseudoClass2(s)) return false; return true; + } else { + return isValidPseudoClass2(cname); } + } + + private static boolean isValidPseudoClass2(String cname) + { cname=cname.toLowerCase(); if(PSEUDOCLASS.contains(cname)) return true; - - - else if(cname.contains("lang") && LANGUAGES.contains(getPseudoClassArg(cname, "lang"))) + else if(cname.startsWith("lang") && Pattern.matches("[\\w\\-*]{1,30}", getPseudoClassArg(cname, "lang"))) { - // FIXME accept unknown languages as long as they are [a-z-] + // More than 8000 valid BCP-47 language codes. Just let through all of them. return true; } - - else if(cname.contains("nth-child") && FilterUtils.isNth(getPseudoClassArg(cname, "nth-child"))) + else if(cname.startsWith("nth-child") && FilterUtils.isNth(getPseudoClassArg(cname, "nth-child"))) return true; - else if(cname.contains("nth-last-child") && FilterUtils.isNth(getPseudoClassArg(cname, "nth-last-child"))) + else if(cname.startsWith("nth-last-child") && FilterUtils.isNth(getPseudoClassArg(cname, "nth-last-child"))) return true; - else if(cname.contains("nth-of-type") && FilterUtils.isNth(getPseudoClassArg(cname, "nth-of-type"))) + else if(cname.startsWith("nth-of-type") && FilterUtils.isNth(getPseudoClassArg(cname, "nth-of-type"))) return true; - else if(cname.contains("nth-last-of-type") && FilterUtils.isNth(getPseudoClassArg(cname, "nth-last-of-type"))) + else if(cname.startsWith("nth-last-of-type") && FilterUtils.isNth(getPseudoClassArg(cname, "nth-last-of-type"))) return true; - + else if(cname.startsWith("dir")) { + String arg = getPseudoClassArg(cname, "dir"); + return arg.equalsIgnoreCase("ltr") || arg.equalsIgnoreCase("rtl"); + } return false; - } + } + public static String getPseudoClassArg(String cname, String cname_sans_arg) { String arg=""; int cnameIndex=cname.indexOf(cname_sans_arg); int firstIndex=cname.indexOf('('); int secondIndex=cname.lastIndexOf(')'); + if(cnameIndex == -1 || firstIndex == -1 || secondIndex == -1) + return ""; if(cname.substring(cnameIndex + cname_sans_arg.length(), firstIndex).trim().isEmpty() && cname.substring(0, cnameIndex).trim().isEmpty() && cname.substring(secondIndex + 1, cname.length()).trim().isEmpty()) { arg=CSSTokenizerFilter.removeOuterQuotes(cname.substring(firstIndex+1,secondIndex).trim()); diff --git a/src/freenet/client/filter/FilterUtils.java b/src/freenet/client/filter/FilterUtils.java index a8ed13cfd16..9140bce988c 100644 --- a/src/freenet/client/filter/FilterUtils.java +++ b/src/freenet/client/filter/FilterUtils.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.regex.Pattern; public class FilterUtils { private static volatile boolean logDEBUG; @@ -289,6 +290,7 @@ else if(value.contains("rad")) SVGcolorKeywords.add("whitesmoke"); SVGcolorKeywords.add("yellow"); SVGcolorKeywords.add("yellowgreen"); + SVGcolorKeywords.add("rebeccapurple"); // CSS Colors Level 4: #663399 } private final static HashSet CSScolorKeywords=new HashSet(); static @@ -315,34 +317,34 @@ else if(value.contains("rad")) } private final static HashSet CSSsystemColorKeywords=new HashSet(); 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"); + 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) { @@ -370,70 +372,57 @@ public static boolean isValidCSSShape(String value) public static boolean isMedia(String media) { return cssMedia.contains(media); } + + public static final Pattern hexColorPattern = Pattern.compile("#(?>[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})", Pattern.CASE_INSENSITIVE); + public static boolean isColor(String value) { - value=value.trim(); + value=value.trim().toLowerCase(); if(CSScolorKeywords.contains(value) || CSSsystemColorKeywords.contains(value) || SVGcolorKeywords.contains(value)) return true; if(value.indexOf('#')==0) { - - if(value.length()==4) + return hexColorPattern.matcher(value).matches(); + } + if((value.startsWith("rgb(") || value.startsWith("rgba(")) && value.indexOf(')')==value.length()-1) + { + // rgba is an alias to rgb + if(value.contains(",")) { - 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) + // Legacy format rgba(r,g,b,a) + String[] colorParts=value.substring(value.indexOf("(")+1,value.length()-1).split(","); + if(colorParts.length!=3&&colorParts.length!=4) + return false; + for(int i=0; i<3; i++) { + if(!(isPercentage(colorParts[i].trim()) || isInteger(colorParts[i].trim()))) + return false; } - - } - 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(); + if(colorParts.length<=3 || isNumber(colorParts[3])) return true; + }else{ + if(value.contains("/")){ + // Modern format rgba(r g b / a) + String alphaPart=value.substring(value.indexOf("/")+1,value.length()-1).trim(); + if(!alphaPart.isEmpty() && !isPercentage(alphaPart) && !isNumber(alphaPart) && !alphaPart.equalsIgnoreCase("none")) + return false; + value=value.substring(0,value.indexOf("/"))+")"; // Strip alpha value, proceed to the following tests + } + // Modern format rgba(r g b) + String[] colorParts=value.substring(value.indexOf("(")+1,value.length()-1).split(" "); + if(colorParts.length!=3) { + return false; } - catch(Exception e) + for(int i=0; i<3; i++) { + String trimmed = colorParts[i].trim(); + if(!(trimmed.equalsIgnoreCase("none") || isPercentage(trimmed) || (isInteger(trimmed) && isIntegerInRange(trimmed, 0, 255)))) + return false; } - } - } - 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 getAllowedTagVerifiers() emptyStringArray)); String[] group2 = { - "span", - "address", - "em", - "strong", - "dfn", - "code", - "samp", - "kbd", - "var", - "cite", "abbr", "acronym", - "sub", - "sup", - "dt", - "dd", - "tt", - "i", + "address", + "article", + "aside", "b", + "bdi", + "bdo", "big", - "small", - "strike", - "s", - "u", - "noframes", - "fieldset", -// Delete . So we can at least see the non-scripting code. -// "noscript", - "xmp", - "listing", - "plaintext", "center", - "bdo", - "aside", + "cite", + "code", + "dd", + "details", + "dfn", + "dt", + "em", + "fieldset", + "figcaption", + "figure", + "footer", "header", + "hgroup", + "i", + "kbd", + "listing", + "main", + "mark", "nav", - "footer", - "article", + "noframes", + // Delete . So we can at least see the non-scripting code. + //"noscript", + "plaintext", + "rp", + "rt", + "ruby", + "s", + "samp", "section", - "hgroup", - "wbr", + "small", + "span", + "strike", + "strong", + "sub", "summary", - "details"}; + "sup", + "tt", + "u", + "var", + "wbr", + "xmp"}; for (String x: group2) allowedTagsVerifiers.put( x, @@ -1072,7 +1080,7 @@ private static Map getAllowedTagVerifiers() "ol", new CoreTagVerifier( "ol", - new String[] { "type", "compact", "start" }, + new String[] { "type", "compact", "start", "reversed" }, emptyStringArray, emptyStringArray, emptyStringArray, @@ -2291,7 +2299,7 @@ Map sanitizeHash(Map h, // lang, xml:lang and dir can go on anything // lang or xml:lang = language [ "-" country [ "-" variant ] ] // The variant can be just about anything; no way to test (avian) - if (x.equals("xml:lang") ||x.equals("lang") || (x.equals("dir") && (o instanceof String) && (((String)o).equalsIgnoreCase("ltr") || ((String)o).equalsIgnoreCase("rtl")))) { + if (x.equals("xml:lang") ||x.equals("lang") || (x.equals("dir") && (o instanceof String) && (((String)o).equalsIgnoreCase("ltr") || ((String)o).equalsIgnoreCase("rtl") || ((String)o).equalsIgnoreCase("auto")))) { if(logDEBUG) Logger.debug(this, "HTML Filter is putting attribute: "+x+" = "+o); hn.put(x, o); } diff --git a/test/freenet/client/filter/CSSParserTest.java b/test/freenet/client/filter/CSSParserTest.java index ff9ea15df7e..a1d5736a844 100644 --- a/test/freenet/client/filter/CSSParserTest.java +++ b/test/freenet/client/filter/CSSParserTest.java @@ -39,7 +39,7 @@ public class CSSParserTest { private final static HashMap CSS1_SELECTOR= new HashMap<>(); static { CSS1_SELECTOR.put("h1 {}","h1"); - CSS1_SELECTOR.put("h1:link {}","h1:link"); + CSS1_SELECTOR.put("h1:link {color:transparent}",""); CSS1_SELECTOR.put("h1:visited {}",""); CSS1_SELECTOR.put("h1.warning {}","h1.warning"); CSS1_SELECTOR.put("h1#myid {}","h1#myid"); @@ -90,9 +90,9 @@ public class CSSParserTest { CSS2_SELECTOR.put("div > p:FIRST-CHILD { text-indent: 0 }", "div>p:FIRST-CHILD { text-indent: 0 }"); CSS2_SELECTOR.put("p:first-child em { font-weight : bold }", "p:first-child em { font-weight: bold }"); CSS2_SELECTOR.put("* > a:first-child {}", "*>a:first-child {}"); - CSS2_SELECTOR.put(":link { color: red }", ":link { color: red }"); // REDFLAG: link vs visited is safe for Freenet as there is no scripting. // If there was scripting it would not be safe, although datastore probing is probably the greater threat. + CSS2_SELECTOR.put(":link { color: red }", ""); CSS2_SELECTOR.put("a.external:visited { color: blue }", ""); CSS2_SELECTOR.put("a:focus:hover { background: white }", "a:focus:hover { background: white }"); CSS2_SELECTOR.put("p:first-line { text-transform: uppercase;}", "p:first-line { text-transform: uppercase;}"); @@ -137,6 +137,10 @@ public class CSSParserTest { CSS2_BAD_SELECTOR.add("h1[foo,=bar] {}"); CSS2_BAD_SELECTOR.add("h1:langblahblah(fr) {}"); + // java.lang.StringIndexOutOfBoundsException + CSS2_BAD_SELECTOR.add("h1:golang {}"); + // missing argument + CSS2_BAD_SELECTOR.add("h1:lang {}"); // THE FOLLOWING ARE VALID BUT DISALLOWED // ] inside string inside attribute selector: way too confusing for parsers. @@ -222,8 +226,69 @@ public class CSSParserTest { // Whitespace not supported at all. CSS3_BAD_SELECTOR.add("tr:nth-child( n+2) {}"); CSS3_BAD_SELECTOR.add("tr:nth-child(n + 2) {}"); + // java.lang.StringIndexOutOfBoundsException + CSS3_BAD_SELECTOR.add("tr:tenth-child {}"); + CSS3_BAD_SELECTOR.add("tr:tenth-child(2n+1) {}"); } + private final static HashMap CSS_SELECTOR_LEVEL4= new HashMap<>(); + static { + CSS_SELECTOR_LEVEL4.put("div:dir(ltr) {}", "div:dir(ltr)"); + CSS_SELECTOR_LEVEL4.put("div:dir(rtl) {}", "div:dir(rtl)"); + CSS_SELECTOR_LEVEL4.put(":target {}", ":target"); + CSS_SELECTOR_LEVEL4.put(":any-link {}", ":any-link"); + CSS_SELECTOR_LEVEL4.put(":empty {}", ":empty"); + CSS_SELECTOR_LEVEL4.put(":focus-visible {}", ":focus-visible"); + CSS_SELECTOR_LEVEL4.put(":only-child {}", ":only-child"); + CSS_SELECTOR_LEVEL4.put(":only-of-type {}", ":only-of-type"); + CSS_SELECTOR_LEVEL4.put(":root {font-size: xxx-large;}", ":root {font-size: xxx-large;}"); + // forms + CSS_SELECTOR_LEVEL4.put("input:default {}", "input:default"); + CSS_SELECTOR_LEVEL4.put("input:disabled {}", "input:disabled"); + CSS_SELECTOR_LEVEL4.put("input:enabled {}", "input:enabled"); + CSS_SELECTOR_LEVEL4.put("input:indeterminate {}", "input:indeterminate"); + CSS_SELECTOR_LEVEL4.put("input:in-range {}", "input:in-range"); + CSS_SELECTOR_LEVEL4.put("input:invalid {}", "input:invalid"); + CSS_SELECTOR_LEVEL4.put("input:optional {}", "input:optional"); + CSS_SELECTOR_LEVEL4.put("input:out-of-range {}", "input:out-of-range"); + CSS_SELECTOR_LEVEL4.put("input:placeholder-shown {}", "input:placeholder-shown"); + CSS_SELECTOR_LEVEL4.put("input:read-only {}", "input:read-only"); + CSS_SELECTOR_LEVEL4.put("input:read-write {}", "input:read-write"); + CSS_SELECTOR_LEVEL4.put("input:required {}", "input:required"); + } + + private final static HashSet CSS_BAD_SELECTOR_LEVEL4= new HashSet<>(); + static { + // not dir + CSS_BAD_SELECTOR_LEVEL4.add("div:bidir(ltr) {}"); + // missing ltr or rtl + CSS_BAD_SELECTOR_LEVEL4.add("div:dir {}"); + // these selectors don't have arguments + CSS_BAD_SELECTOR_LEVEL4.add(":target() {}"); + CSS_BAD_SELECTOR_LEVEL4.add(":any-link() {}"); + CSS_BAD_SELECTOR_LEVEL4.add(":empty() {}"); + CSS_BAD_SELECTOR_LEVEL4.add(":focus-visible() {}"); + CSS_BAD_SELECTOR_LEVEL4.add(":only-child() {}"); + CSS_BAD_SELECTOR_LEVEL4.add(":only-of-type() {}"); + CSS_BAD_SELECTOR_LEVEL4.add(":root() {}"); + // these forms selectors don't have arguments + CSS_BAD_SELECTOR_LEVEL4.add("input:default() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:disabled() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:enabled() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:indeterminate() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:in-range() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:invalid() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:optional() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:out-of-range() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:placeholder-shown() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:read-only() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:read-write() {}"); + CSS_BAD_SELECTOR_LEVEL4.add("input:required() {}"); + // banned + CSS_BAD_SELECTOR_LEVEL4.add(":defined {}"); + CSS_BAD_SELECTOR_LEVEL4.add(":defined() {}"); + } + private static final String CSS_STRING_NEWLINES = "* { content: \"this string does not terminate\n}\nbody {\nbackground: url(http://www.google.co.uk/intl/en_uk/images/logo.gif); }\n\" }"; private static final String CSS_STRING_NEWLINESC = "* {}\nbody { }\n"; @@ -751,7 +816,9 @@ public class CSSParserTest { propertyTests.put("p { text-indent: 3em }", "p { text-indent: 3em }"); propertyTests.put("p { text-indent: 33% }", "p { text-indent: 33% }"); propertyTests.put("div.important { text-align: center }", "div.important { text-align: center }"); - propertyTests.put("a:visited,a:link { text-decoration: underline }", "a:link { text-decoration: underline }"); + propertyTests.put("a:visited,a:link { text-decoration: underline }", ""); + propertyTests.put("a:any-link { text-decoration: underline }", "a:any-link { text-decoration: underline }"); + propertyTests.put("a:any-link { text-decoration: underline red }", "a:any-link { text-decoration: underline red }"); propertyTests.put("blockquote { text-decoration: underline overline line-through blink } h1 { text-decoration: none } h2 { text-decoration: inherit }","blockquote { text-decoration: underline overline line-through blink } h1 { text-decoration: none } h2 { text-decoration: inherit }"); propertyTests.put("blockquote { letter-spacing: 0.1em }", "blockquote { letter-spacing: 0.1em }"); propertyTests.put("blockquote { letter-spacing: normal }", "blockquote { letter-spacing: normal }"); @@ -776,10 +843,8 @@ public class CSSParserTest { propertyTests.put("table { empty-cells: show }", "table { empty-cells: show }"); // User interface - propertyTests.put(":link,:visited { cursor: url(example.svg#linkcursor) url(hyper.cur) pointer }", ":link { cursor: url(\"example.svg#linkcursor\") url(\"hyper.cur\") pointer }"); - propertyTests.put(":link,:visited { cursor: url(example.svg#linkcursor), url(hyper.cur), pointer }", ":link { cursor: url(\"example.svg#linkcursor\"), url(\"hyper.cur\"), pointer }"); - propertyTests.put(":link,:visited { cursor: url(example.svg#linkcursor) 2 5, url(hyper.cur), pointer }", ":link { cursor: url(\"example.svg#linkcursor\") 2 5, url(\"hyper.cur\"), pointer }"); - propertyTests.put(":link,:visited { cursor: url(example.svg#linkcursor) 2, url(hyper.cur), pointer }", ":link { }"); + propertyTests.put(":link,:visited { cursor: url(example.svg#linkcursor) url(hyper.cur) pointer }", ""); + propertyTests.put(":any-link { cursor: url(example.svg#linkcursor) 2, url(hyper.cur), pointer }", ":any-link { }"); // UI colors propertyTests.put("p { color: WindowText; background-color: Window }", "p { color: WindowText; background-color: Window }"); @@ -902,6 +967,42 @@ public class CSSParserTest { propertyTests.put("div { transition-delay: \"test\"; }", "div { }"); propertyTests.put("div { transition-property: \"test\"; }", "div { }"); propertyTests.put("div { transition-timing-function: \"test\"; }", "div { }"); + + // writing mode + propertyTests.put("#a { writing-mode: vertical-rl; text-underline-position: left; }", "#a { writing-mode: vertical-rl; text-underline-position: left; }"); + propertyTests.put("#b { writing-mode: horizontal-tb; text-underline-position: auto; inline-size: max-content; block-size: 200px; }", "#b { writing-mode: horizontal-tb; text-underline-position: auto; inline-size: max-content; block-size: 200px; }"); + + // Compositing and Blending + propertyTests.put("#foo { background: url(\"1.png\"); background-blend-mode: darken; }", "#foo { background: url(\"1.png\"); background-blend-mode: darken; }"); + propertyTests.put("#foo {mix-blend-mode: luminosity; }", "#foo {mix-blend-mode: luminosity; }"); + + // new property values + propertyTests.put("div { overflow: clip; clear: inline-end; text-decoration: revert; float: inline-end;}", "div { overflow: clip; clear: inline-end; text-decoration: revert; float: inline-end;}"); + propertyTests.put("#a {unicode-bidi: isolate;}", "#a {unicode-bidi: isolate;}"); + propertyTests.put("textarea#x {caret-color: currentcolor;}", "textarea#x {caret-color: currentcolor;}"); + propertyTests.put("div { word-wrap: anywhere; overflow-wrap: anywhere; }", "div { word-wrap: anywhere; overflow-wrap: anywhere; }"); + propertyTests.put("div { white-space-collapse: collapse; }", "div { white-space-collapse: collapse; }"); + propertyTests.put("#a { word-break: keep-all; font-kerning: none; }", "#a { word-break: keep-all; font-kerning: none; }"); + propertyTests.put("#a { tab-size: 4; }", "#a { tab-size: 4; }"); + propertyTests.put("#a { tab-size: 12pt; }", "#a { tab-size: 12pt; }"); + propertyTests.put("img#a { object-fit: scale-down; }", "img#a { object-fit: scale-down; }"); + propertyTests.put("#x { list-style-type: korean-hanja-formal }", "#x { list-style-type: korean-hanja-formal }"); + propertyTests.put("#x { list-style-type: \"*\" }", "#x { list-style-type: \"*\" }"); + propertyTests.put("#x { max-inline-size: none; min-block-size: auto; }", "#x { max-inline-size: none; min-block-size: auto; }"); + + // text-emphasis + propertyTests.put("#x { text-emphasis: triangle blue; }", "#x { text-emphasis: triangle blue; }"); // java.lang.NullPointerException + propertyTests.put("#x { text-emphasis: filled triangle blue; }", "#x { text-emphasis: filled triangle blue; }"); + propertyTests.put("#x { text-emphasis-style: triangle; text-emphasis-color: blue; }", "#x { text-emphasis-style: triangle; text-emphasis-color: blue; }"); + // text-shadow + propertyTests.put("#x { text-shadow: 1px 1px 2px black; }", "#x { text-shadow: 1px 1px 2px black; }"); + propertyTests.put("#x { text-shadow: #fc0 1px 0 10px; }", "#x { text-shadow: #fc0 1px 0 10px; }"); + propertyTests.put("#x { text-shadow: 5px 5px #558abb; }", "#x { text-shadow: 5px 5px #558abb; }"); + propertyTests.put("#x { text-shadow: white 2px 5px; }", "#x { text-shadow: white 2px 5px; }"); + propertyTests.put("#x { text-shadow: 5px 10px; }", "#x { text-shadow: 5px 10px; }"); + propertyTests.put("#x { text-shadow: 1px 1px 2px 1px black; }", "#x { }"); + // not possible to parse a comma separated list? + //propertyTests.put("#x { text-shadow: 1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue; }", "#x { text-shadow: 1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue; }"); } FilterMIMEType cssMIMEType; @@ -960,7 +1061,17 @@ public void testCSS3Selector() throws IOException, URISyntaxException { testCssSelectorFiltering(CSS3_SELECTOR); testBadSelectorFiltering(CSS3_BAD_SELECTOR); } + + @Test + public void testCSS4Selector() throws IOException, URISyntaxException { + testCssSelectorFiltering(CSS_SELECTOR_LEVEL4); + } + @Test + public void testCSS4SelectorBad() throws IOException, URISyntaxException { + testBadSelectorFiltering(CSS_BAD_SELECTOR_LEVEL4); + } + @Test public void testNewlines() throws IOException, URISyntaxException { assertEquals( diff --git a/test/freenet/client/filter/ContentFilterTest.java b/test/freenet/client/filter/ContentFilterTest.java index a2a861920d2..e4bb89ce73a 100644 --- a/test/freenet/client/filter/ContentFilterTest.java +++ b/test/freenet/client/filter/ContentFilterTest.java @@ -115,7 +115,8 @@ public class ContentFilterTest { private static final String SPAN_WITH_STYLE = ""; - private static final String HTML5_TAGS = "
TLDR
Too Long Didn’t Read
"; + private static final String HTML5_TAGS = "
TLDR
Too Long Didn’t Read
Fig.1
"; + private static final String HTML5_BDI_RUBY = "ایران, NorthKoreaNorth Korea"; private static final String BASE_HREF = ""; private static final String BAD_BASE_HREF = ""; @@ -138,6 +139,10 @@ public class ContentFilterTest { HTML_VIDEO_TAG + HTML_AUDIO_TAG, HTML_AUDIO_TAG + HTML_AUDIO_TAG); + private static void testOneHTMLFilter(String html) throws Exception { + assertEquals(html, htmlFilter(html)); + } + @Test public void testHTMLFilter() throws Exception { if (TestProperty.VERBOSE) { @@ -146,7 +151,7 @@ public void testHTMLFilter() throws Exception { // General sanity checks // is "relativization" working? - assertEquals(INTERNAL_RELATIVE_LINK, htmlFilter(INTERNAL_RELATIVE_LINK)); + testOneHTMLFilter(INTERNAL_RELATIVE_LINK); assertEquals(INTERNAL_RELATIVE_LINK, htmlFilter(INTERNAL_RELATIVE_LINK, true)); assertEquals(INTERNAL_RELATIVE_LINK1, htmlFilter(INTERNAL_RELATIVE_LINK1, true)); assertEquals(INTERNAL_RELATIVE_LINK, htmlFilter(INTERNAL_ABSOLUTE_LINK)); @@ -157,15 +162,15 @@ public void testHTMLFilter() throws Exception { // regression testing // bug #710 - assertEquals(ANCHOR_TEST, htmlFilter(ANCHOR_TEST)); - assertEquals(ANCHOR_TEST_EMPTY, htmlFilter(ANCHOR_TEST_EMPTY)); - assertEquals(ANCHOR_TEST_SPECIAL, htmlFilter(ANCHOR_TEST_SPECIAL)); + testOneHTMLFilter(ANCHOR_TEST); + testOneHTMLFilter(ANCHOR_TEST_EMPTY); + testOneHTMLFilter(ANCHOR_TEST_SPECIAL); assertEquals(ANCHOR_TEST_SPECIAL2_RESULT, htmlFilter(ANCHOR_TEST_SPECIAL2)); // bug #2496 - assertEquals(ANCHOR_RELATIVE1, htmlFilter(ANCHOR_RELATIVE1)); - assertEquals(ANCHOR_RELATIVE2, htmlFilter(ANCHOR_RELATIVE2)); - assertEquals(ANCHOR_FALSE_POS1, htmlFilter(ANCHOR_FALSE_POS1)); - assertEquals(ANCHOR_FALSE_POS2, htmlFilter(ANCHOR_FALSE_POS2)); + testOneHTMLFilter(ANCHOR_RELATIVE1); + testOneHTMLFilter(ANCHOR_RELATIVE2); + testOneHTMLFilter(ANCHOR_FALSE_POS1); + testOneHTMLFilter(ANCHOR_FALSE_POS2); // EVIL HACK TEST for #2496 + #2451 assertEquals(ANCHOR_MIXED_RESULT, htmlFilter(ANCHOR_MIXED)); // bug #2451 @@ -176,7 +181,7 @@ public void testHTMLFilter() throws Exception { assertTrue(htmlFilter(PREVENT_EXTERNAL_ACCESS_CSS_SIMPLE).contains("div { }")); assertTrue(htmlFilter(PREVENT_EXTERNAL_ACCESS_CSS_ESCAPE).contains("div { }")); assertTrue(htmlFilter(PREVENT_EXTERNAL_ACCESS_CSS_CASE).contains("div { }")); - assertEquals(WHITELIST_STATIC_CONTENT, htmlFilter(WHITELIST_STATIC_CONTENT)); + testOneHTMLFilter(WHITELIST_STATIC_CONTENT); assertEquals(XHTML_VOIDELEMENTC, htmlFilter(XHTML_VOIDELEMENT)); assertEquals(XHTML_INCOMPLETEDOCUMENTC, htmlFilter(XHTML_INCOMPLETEDOCUMENT)); assertEquals(XHTML_IMPROPERNESTINGC, htmlFilter(XHTML_IMPROPERNESTING)); @@ -193,12 +198,13 @@ public void testHTMLFilter() throws Exception { assertEquals(FRAME_SRC_CHARSET_BADC, htmlFilter(FRAME_SRC_CHARSET_BAD, true)); assertEquals(FRAME_SRC_CHARSET_BAD1C, htmlFilter(FRAME_SRC_CHARSET_BAD1, true)); - assertEquals(CSS_SPEC_EXAMPLE1, htmlFilter(CSS_SPEC_EXAMPLE1)); + testOneHTMLFilter(CSS_SPEC_EXAMPLE1); - assertEquals(SPAN_WITH_STYLE, htmlFilter(SPAN_WITH_STYLE)); - assertEquals(HTML5_TAGS, htmlFilter(HTML5_TAGS)); + testOneHTMLFilter(SPAN_WITH_STYLE); + testOneHTMLFilter(HTML5_TAGS); + testOneHTMLFilter(HTML5_BDI_RUBY); - assertEquals(BASE_HREF, htmlFilter(BASE_HREF)); + testOneHTMLFilter(BASE_HREF); assertEquals(DELETED_BASE_HREF, htmlFilter(BAD_BASE_HREF)); assertEquals(DELETED_BASE_HREF, htmlFilter(BAD_BASE_HREF2)); assertEquals(DELETED_BASE_HREF, htmlFilter(BAD_BASE_HREF3)); diff --git a/test/freenet/client/filter/FilterUtilsTest.java b/test/freenet/client/filter/FilterUtilsTest.java index 14177c329fa..5367d0e63d9 100755 --- a/test/freenet/client/filter/FilterUtilsTest.java +++ b/test/freenet/client/filter/FilterUtilsTest.java @@ -44,4 +44,34 @@ public void testInvalidLengthUnits() { assertFalse(FilterUtils.isLength("1.", false)); assertFalse(FilterUtils.isLength("", false)); } + + @Test + public void testValidColors() { + assertTrue(FilterUtils.isColor("rebeccapurple")); + assertTrue(FilterUtils.isColor("Transparent")); + assertTrue(FilterUtils.isColor("WindowText")); + assertTrue(FilterUtils.isColor("#123ABC")); + assertTrue(FilterUtils.isColor("#123")); + assertTrue(FilterUtils.isColor("#123F")); + assertTrue(FilterUtils.isColor("#123456ff")); + assertTrue(FilterUtils.isColor("rgb(0,10,255)")); + assertTrue(FilterUtils.isColor("rgb(0 10 255)")); + assertTrue(FilterUtils.isColor("rgba(100 200 255 / 0.25)")); + assertTrue(FilterUtils.isColor("rgba(010 00200 255 / 25%)")); + assertTrue(FilterUtils.isColor("rgba(none 0 0% /)")); + } + + @Test + public void testInvalidColors() { + assertFalse(FilterUtils.isColor("rgb(0.1 0.2 0.3)")); // should be between 0 and 255, not 0.1 + assertFalse(FilterUtils.isColor("rgb(")); // completely empty; don't trigger out of range access here! + assertFalse(FilterUtils.isColor("rgb()")); // completely empty; don't trigger out of range access here! + assertFalse(FilterUtils.isColor("rgb(/)")); // completely empty; don't trigger out of range access here! + assertFalse(FilterUtils.isColor("#ABCDEFGH")); // #ABCDEF followed by G and H + assertFalse(FilterUtils.isColor("112233")); // missing # + assertFalse(FilterUtils.isColor("#12")); // not #RGB + assertFalse(FilterUtils.isColor("#12345")); // not #RGBA neither #RRGGBB + assertFalse(FilterUtils.isColor("#1234567")); // not #RRGGBB neither #RRGGBBAA + assertFalse(FilterUtils.isColor("url(/KSK@foo)")); // url not color! + } }