Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8584c21
Allow bdi and main elements and auto value of dir attribute
torusrxxx Oct 17, 2024
7cf3133
Add elements figcaption,figure,mark,rp,rt,ruby
torusrxxx Oct 19, 2024
35a00bf
Add CSS global values revert,revert-layer, attribute writing-mode, up…
torusrxxx Oct 19, 2024
453a446
Add CSS colors rebeccapurple and rgba modern format
torusrxxx Oct 19, 2024
3d857a5
Add fonts system-ui,ui-serif,ui-sans-serif,ui-monospace,ui-rounded,em…
torusrxxx Oct 19, 2024
8617f30
Add more pseudo classes
torusrxxx Oct 19, 2024
ba0d911
Add inline-start,inline-end values of float CSS attribute
torusrxxx Oct 19, 2024
278a10f
Update CSS attributes clear,overflow,text-underline-position
torusrxxx Oct 19, 2024
86f216d
Fix StringIndexOutOfBoundsException when filtering "tr:tenth-child {}"
torusrxxx Oct 22, 2024
46f36f7
Add font size xxx-large and selector tests
torusrxxx Oct 22, 2024
5e0d013
Add background-blend-mode and mix-blend-mode and CSS tests
torusrxxx Oct 24, 2024
e33b534
Fix text-shadow parsing
torusrxxx Oct 25, 2024
c814ae1
Add attributes block-size,inline-size, length values min-content,max-…
torusrxxx Oct 25, 2024
cc74d98
fix NullPointerException with text-emphasis, fix deprecated attribute…
torusrxxx Oct 28, 2024
9591dba
Add tab-size,font-kerning, update word-break
torusrxxx Oct 28, 2024
47e72cc
Add object-fit
torusrxxx Oct 28, 2024
598c52f
Support "reversed" attribute of <ol> element
torusrxxx Oct 29, 2024
5b9f643
Add more types to list-style-type and also allow string
torusrxxx Oct 31, 2024
2dc724b
Add max-block-size,max-inline-size,min-block-size,min-inline-size
torusrxxx Nov 4, 2024
2bc2fc2
Fix text-wrap having nonstandard values
torusrxxx Nov 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 116 additions & 44 deletions src/freenet/client/filter/CSSTokenizerFilter.java

Large diffs are not rendered by default.

125 changes: 67 additions & 58 deletions src/freenet/client/filter/ElementInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

public class ElementInfo {

Expand Down Expand Up @@ -53,42 +54,6 @@ public class ElementInfo {
"button"
)));

// FIXME add some more languages.
public static final Set<String> LANGUAGES =
Collections.unmodifiableSet(new HashSet<String>(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<String> MEDIA =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"all",
Expand Down Expand Up @@ -182,13 +147,22 @@ public class ElementInfo {
"new york6"
)));

// https://developer.mozilla.org/en-US/docs/Web/CSS/font-family
public static final Set<String> GENERIC_FONT_KEYWORDS =
Collections.unmodifiableSet(new HashSet<String>(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<String> GENERIC_VOICE_KEYWORDS =
Expand All @@ -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<String> BANNED_PSEUDOCLASS =
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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());
Expand Down
139 changes: 64 additions & 75 deletions src/freenet/client/filter/FilterUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> CSScolorKeywords=new HashSet<String>();
static
Expand All @@ -315,34 +317,34 @@ else if(value.contains("rad"))
}
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");
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)
{
Expand Down Expand Up @@ -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();
Copy link
Contributor

@ArneBab ArneBab Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! (feel free to mark this resolved once you read it ☺)


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<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)
Expand Down
Loading