From 91a64a64f71a59cb193b22a26f4494a0e1466376 Mon Sep 17 00:00:00 2001 From: Neha Garg Date: Mon, 23 Sep 2024 16:24:33 +0530 Subject: [PATCH 1/3] add support for inline style css functions --- .../org/owasp/validator/css/CssValidator.java | 15 +++++- .../owasp/validator/css/CssValidatorTest.java | 51 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/owasp/validator/css/CssValidatorTest.java diff --git a/src/main/java/org/owasp/validator/css/CssValidator.java b/src/main/java/org/owasp/validator/css/CssValidator.java index ebe71025..d3e7df08 100644 --- a/src/main/java/org/owasp/validator/css/CssValidator.java +++ b/src/main/java/org/owasp/validator/css/CssValidator.java @@ -361,10 +361,23 @@ public String lexicalValueToString(LexicalUnit lu) { return "inherit"; case LexicalUnit.SAC_OPERATOR_COMMA: return ","; + case LexicalUnit.SAC_FUNCTION: + final StringBuffer builder = new StringBuffer(); + builder.append(lu.getFunctionName()); + builder.append("("); + LexicalUnit params = lu.getParameters(); + while (params != null) { + builder.append(lexicalValueToString(params)); + params = params.getNextLexicalUnit(); + if (params != null) { + builder.append(", "); + } + } + builder.append(")"); + return builder.toString(); case LexicalUnit.SAC_ATTR: case LexicalUnit.SAC_COUNTER_FUNCTION: case LexicalUnit.SAC_COUNTERS_FUNCTION: - case LexicalUnit.SAC_FUNCTION: case LexicalUnit.SAC_RECT_FUNCTION: case LexicalUnit.SAC_SUB_EXPRESSION: case LexicalUnit.SAC_UNICODERANGE: diff --git a/src/test/java/org/owasp/validator/css/CssValidatorTest.java b/src/test/java/org/owasp/validator/css/CssValidatorTest.java new file mode 100644 index 00000000..ee5207df --- /dev/null +++ b/src/test/java/org/owasp/validator/css/CssValidatorTest.java @@ -0,0 +1,51 @@ +package org.owasp.validator.css; + +import org.apache.batik.css.parser.CSSLexicalUnit; +import org.junit.Test; +import org.w3c.css.sac.LexicalUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class CssValidatorTest { + @Test + public void testLexicalValueToStringSacFunction() { + CssValidator cssValidator = new CssValidator(null); + + final CSSLexicalUnit param = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--ds-text-purple", null); + final CSSLexicalUnit varFunc = CSSLexicalUnit.createFunction("var", param, null); + + assertEquals("var(--ds-text-purple)", cssValidator.lexicalValueToString(varFunc)); + + final CSSLexicalUnit hslaParam = CSSLexicalUnit.createInteger(100, null); + final CSSLexicalUnit hslaParam1 = CSSLexicalUnit.createDimension(98, "%", hslaParam); + final CSSLexicalUnit hslaParam2 = CSSLexicalUnit.createDimension(50, "%", hslaParam1); + CSSLexicalUnit.createFloat(LexicalUnit.SAC_REAL, 0.3f, hslaParam2); + + final CSSLexicalUnit hslaFunc = CSSLexicalUnit.createFunction("hsla", hslaParam, null); + assertEquals("hsla(100, 98.0%, 50.0%, 0.3)", cssValidator.lexicalValueToString(hslaFunc)); + } + + @Test + public void testLexicalValueToStringSacFunctionTwoParams() { + CssValidator cssValidator = new CssValidator(null); + + final CSSLexicalUnit param = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--ds-text-purple", null); + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "#FFFFFF", param); + final CSSLexicalUnit func = CSSLexicalUnit.createFunction("var", param, null); + + assertEquals("var(--ds-text-purple, #FFFFFF)", cssValidator.lexicalValueToString(func)); + } + + @Test + public void testLexicalValueToStringUnsupported() { + CssValidator cssValidator = new CssValidator(null); + final CSSLexicalUnit param = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "section", null); + final CSSLexicalUnit func = + CSSLexicalUnit.createPredefinedFunction(LexicalUnit.SAC_COUNTER_FUNCTION, param, null); + assertNull(cssValidator.lexicalValueToString(func)); + } +} From 0b051593939867040cb68cccd9031b16e0923cab Mon Sep 17 00:00:00 2001 From: Neha Garg Date: Mon, 30 Sep 2024 11:11:53 +0530 Subject: [PATCH 2/3] added support for nested functions and fallback support --- .../org/owasp/validator/css/CssValidator.java | 38 +++- .../owasp/validator/css/CssValidatorTest.java | 177 +++++++++++++----- 2 files changed, 165 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/owasp/validator/css/CssValidator.java b/src/main/java/org/owasp/validator/css/CssValidator.java index d3e7df08..49e9795d 100644 --- a/src/main/java/org/owasp/validator/css/CssValidator.java +++ b/src/main/java/org/owasp/validator/css/CssValidator.java @@ -333,13 +333,20 @@ public String lexicalValueToString(LexicalUnit lu) { return String.valueOf(lu.getFloatValue()); case LexicalUnit.SAC_STRING_VALUE: case LexicalUnit.SAC_IDENT: - // just a string/identifier + // Ensure that JavaScript URLs are not allowed String stringValue = lu.getStringValue(); + if (stringValue == null || stringValue.toLowerCase().startsWith("javascript:")) { + return null; + } if (stringValue.indexOf(" ") != -1) stringValue = "'" + stringValue + "'"; return stringValue; case LexicalUnit.SAC_URI: - // this is a URL - return "url(" + lu.getStringValue() + ")"; + // Ensure that JavaScript URLs are not allowed + String url = lu.getStringValue(); + if (url == null || url.toLowerCase().startsWith("javascript:")) { + return null; + } + return "url(" + url + ")"; case LexicalUnit.SAC_RGBCOLOR: // this is a rgb encoded color StringBuffer sb = new StringBuffer("rgb("); @@ -362,17 +369,34 @@ public String lexicalValueToString(LexicalUnit lu) { case LexicalUnit.SAC_OPERATOR_COMMA: return ","; case LexicalUnit.SAC_FUNCTION: - final StringBuffer builder = new StringBuffer(); - builder.append(lu.getFunctionName()); - builder.append("("); + StringBuilder builder = new StringBuilder(); + + // Append the function name, e.g., "var" + builder.append(lu.getFunctionName()).append("("); + LexicalUnit params = lu.getParameters(); while (params != null) { - builder.append(lexicalValueToString(params)); + String paramsValue = lexicalValueToString(params); + if (paramsValue == null) { + return null; + } + builder.append(paramsValue); params = params.getNextLexicalUnit(); if (params != null) { builder.append(", "); } } + + // Check for fallback (some functions like "var" have fallback values) + LexicalUnit fallback = lu.getPreviousLexicalUnit(); + if (fallback != null) { + String fallbackValue = lexicalValueToString(fallback); + if (fallbackValue == null) { + return null; + } + builder.append(", ").append(fallbackValue); + } + builder.append(")"); return builder.toString(); case LexicalUnit.SAC_ATTR: diff --git a/src/test/java/org/owasp/validator/css/CssValidatorTest.java b/src/test/java/org/owasp/validator/css/CssValidatorTest.java index ee5207df..42de0370 100644 --- a/src/test/java/org/owasp/validator/css/CssValidatorTest.java +++ b/src/test/java/org/owasp/validator/css/CssValidatorTest.java @@ -1,51 +1,142 @@ package org.owasp.validator.css; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + import org.apache.batik.css.parser.CSSLexicalUnit; import org.junit.Test; import org.w3c.css.sac.LexicalUnit; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - public class CssValidatorTest { - @Test - public void testLexicalValueToStringSacFunction() { - CssValidator cssValidator = new CssValidator(null); - - final CSSLexicalUnit param = - CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--ds-text-purple", null); - final CSSLexicalUnit varFunc = CSSLexicalUnit.createFunction("var", param, null); - - assertEquals("var(--ds-text-purple)", cssValidator.lexicalValueToString(varFunc)); - - final CSSLexicalUnit hslaParam = CSSLexicalUnit.createInteger(100, null); - final CSSLexicalUnit hslaParam1 = CSSLexicalUnit.createDimension(98, "%", hslaParam); - final CSSLexicalUnit hslaParam2 = CSSLexicalUnit.createDimension(50, "%", hslaParam1); - CSSLexicalUnit.createFloat(LexicalUnit.SAC_REAL, 0.3f, hslaParam2); - - final CSSLexicalUnit hslaFunc = CSSLexicalUnit.createFunction("hsla", hslaParam, null); - assertEquals("hsla(100, 98.0%, 50.0%, 0.3)", cssValidator.lexicalValueToString(hslaFunc)); - } - - @Test - public void testLexicalValueToStringSacFunctionTwoParams() { - CssValidator cssValidator = new CssValidator(null); - - final CSSLexicalUnit param = - CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--ds-text-purple", null); - CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "#FFFFFF", param); - final CSSLexicalUnit func = CSSLexicalUnit.createFunction("var", param, null); - - assertEquals("var(--ds-text-purple, #FFFFFF)", cssValidator.lexicalValueToString(func)); - } - - @Test - public void testLexicalValueToStringUnsupported() { - CssValidator cssValidator = new CssValidator(null); - final CSSLexicalUnit param = - CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "section", null); - final CSSLexicalUnit func = - CSSLexicalUnit.createPredefinedFunction(LexicalUnit.SAC_COUNTER_FUNCTION, param, null); - assertNull(cssValidator.lexicalValueToString(func)); - } + @Test + public void testLexicalValueToStringSacFunction() { + CssValidator cssValidator = new CssValidator(null); + + final CSSLexicalUnit param = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--ds-text-purple", null); + final CSSLexicalUnit varFunc = CSSLexicalUnit.createFunction("var", param, null); + + assertEquals("var(--ds-text-purple)", cssValidator.lexicalValueToString(varFunc)); + + final CSSLexicalUnit hslaParam = CSSLexicalUnit.createInteger(100, null); + final CSSLexicalUnit hslaParam1 = CSSLexicalUnit.createDimension(98, "%", hslaParam); + final CSSLexicalUnit hslaParam2 = CSSLexicalUnit.createDimension(50, "%", hslaParam1); + CSSLexicalUnit.createFloat(LexicalUnit.SAC_REAL, 0.3f, hslaParam2); + + final CSSLexicalUnit hslaFunc = CSSLexicalUnit.createFunction("hsla", hslaParam, null); + assertEquals("hsla(100, 98.0%, 50.0%, 0.3)", cssValidator.lexicalValueToString(hslaFunc)); + } + + @Test + public void testLexicalValueToStringSacFunctionTwoParams() { + CssValidator cssValidator = new CssValidator(null); + + final CSSLexicalUnit param = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--ds-text-purple", null); + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "#FFFFFF", param); + final CSSLexicalUnit func = CSSLexicalUnit.createFunction("var", param, null); + + assertEquals("var(--ds-text-purple, #FFFFFF)", cssValidator.lexicalValueToString(func)); + } + + @Test + public void testLexicalValueToStringUnsupported() { + CssValidator cssValidator = new CssValidator(null); + final CSSLexicalUnit param = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "section", null); + final CSSLexicalUnit func = + CSSLexicalUnit.createPredefinedFunction(LexicalUnit.SAC_COUNTER_FUNCTION, param, null); + assertNull(cssValidator.lexicalValueToString(func)); + } + + @Test + public void testLexicalValueToStringNestedVarsWithFallback() { + CssValidator cssValidator = new CssValidator(null); + + // Create fallback first: --ds-text-purple, #FFFFFF + final CSSLexicalUnit param = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--ds-text-purple", null); + final CSSLexicalUnit fallback = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "#FFFFFF", null); + + // Create first var() function with fallback + final CSSLexicalUnit function = CSSLexicalUnit.createFunction("var", param, fallback); + + // Check if the output is as expected for first var() + assertEquals("var(--ds-text-purple, #FFFFFF)", cssValidator.lexicalValueToString(function)); + + // Create outer variable: --custom-prop + final CSSLexicalUnit outerParam = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--custom-prop", null); + + // Create outer var() with the first function as fallback + final CSSLexicalUnit outerFunction = CSSLexicalUnit.createFunction("var", outerParam, function); + + // Ensure the output is as expected for the nested var() function + assertEquals( + "var(--custom-prop, var(--ds-text-purple, #FFFFFF))", + cssValidator.lexicalValueToString(outerFunction)); + } + + @Test + public void testDefaultPolicyUrlFunction() { + CssValidator cssValidator = new CssValidator(null); + + // Test a simple url function + final CSSLexicalUnit urlParam = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "http://example.com", null); + final CSSLexicalUnit urlFunc = CSSLexicalUnit.createFunction("url", urlParam, null); + + assertEquals("url(http://example.com)", cssValidator.lexicalValueToString(urlFunc)); + } + + @Test + public void testDefaultPolicyUrlFunctionWithJavaScript() { + CssValidator cssValidator = new CssValidator(null); + + // Test a url function with a JavaScript URL + final CSSLexicalUnit urlParam = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "javascript:alert(1)", null); + final CSSLexicalUnit urlFunc = CSSLexicalUnit.createFunction("url", urlParam, null); + + // Ensure that JavaScript URLs are not allowed + assertNull(cssValidator.lexicalValueToString(urlFunc)); + } + + @Test + public void testLexicalValueToStringNestedVarsWithJavaScriptAsFallback() { + CssValidator cssValidator = new CssValidator(null); + + // Create fallback first: --ds-text-purple, #FFFFFF + final CSSLexicalUnit param = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--custom-url", null); + final CSSLexicalUnit fallback = + CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "javascript:alert(1)", null); + + // Create first var() function with fallback + final CSSLexicalUnit function = CSSLexicalUnit.createFunction("var", param, fallback); + + // Check if the output is as expected for first var() + assertNull(cssValidator.lexicalValueToString(function)); + } + + @Test + public void testSacUriWithJavaScriptUrl() { + CssValidator cssValidator = new CssValidator(null); + + // Test with a JavaScript URL, which should be blocked + final CSSLexicalUnit jsUrl = + CSSLexicalUnit.createString(LexicalUnit.SAC_URI, "javascript:alert(1)", null); + assertNull("JavaScript URL should be blocked", cssValidator.lexicalValueToString(jsUrl)); + } + + @Test + public void testSacUriWithValidUrl() { + CssValidator cssValidator = new CssValidator(null); + + // Test with a valid URL, which should be allowed + final CSSLexicalUnit validUrl = + CSSLexicalUnit.createString(LexicalUnit.SAC_URI, "https://example.com", null); + assertEquals("url(https://example.com)", cssValidator.lexicalValueToString(validUrl)); + } } From 135af00d37aae306951c0d36b07484b8e4dee7a1 Mon Sep 17 00:00:00 2001 From: Neha Garg Date: Mon, 7 Oct 2024 11:52:07 +0530 Subject: [PATCH 3/3] addressed review comments, removed url+javascript checks --- .../org/owasp/validator/css/CssValidator.java | 13 ++---- .../owasp/validator/css/CssValidatorTest.java | 40 ------------------- 2 files changed, 3 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/owasp/validator/css/CssValidator.java b/src/main/java/org/owasp/validator/css/CssValidator.java index 49e9795d..9423cb9d 100644 --- a/src/main/java/org/owasp/validator/css/CssValidator.java +++ b/src/main/java/org/owasp/validator/css/CssValidator.java @@ -333,20 +333,13 @@ public String lexicalValueToString(LexicalUnit lu) { return String.valueOf(lu.getFloatValue()); case LexicalUnit.SAC_STRING_VALUE: case LexicalUnit.SAC_IDENT: - // Ensure that JavaScript URLs are not allowed + // just a string/identifier String stringValue = lu.getStringValue(); - if (stringValue == null || stringValue.toLowerCase().startsWith("javascript:")) { - return null; - } if (stringValue.indexOf(" ") != -1) stringValue = "'" + stringValue + "'"; return stringValue; case LexicalUnit.SAC_URI: - // Ensure that JavaScript URLs are not allowed - String url = lu.getStringValue(); - if (url == null || url.toLowerCase().startsWith("javascript:")) { - return null; - } - return "url(" + url + ")"; + // this is a URL + return "url(" + lu.getStringValue() + ")"; case LexicalUnit.SAC_RGBCOLOR: // this is a rgb encoded color StringBuffer sb = new StringBuffer("rgb("); diff --git a/src/test/java/org/owasp/validator/css/CssValidatorTest.java b/src/test/java/org/owasp/validator/css/CssValidatorTest.java index 42de0370..28290f0f 100644 --- a/src/test/java/org/owasp/validator/css/CssValidatorTest.java +++ b/src/test/java/org/owasp/validator/css/CssValidatorTest.java @@ -90,46 +90,6 @@ public void testDefaultPolicyUrlFunction() { assertEquals("url(http://example.com)", cssValidator.lexicalValueToString(urlFunc)); } - @Test - public void testDefaultPolicyUrlFunctionWithJavaScript() { - CssValidator cssValidator = new CssValidator(null); - - // Test a url function with a JavaScript URL - final CSSLexicalUnit urlParam = - CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "javascript:alert(1)", null); - final CSSLexicalUnit urlFunc = CSSLexicalUnit.createFunction("url", urlParam, null); - - // Ensure that JavaScript URLs are not allowed - assertNull(cssValidator.lexicalValueToString(urlFunc)); - } - - @Test - public void testLexicalValueToStringNestedVarsWithJavaScriptAsFallback() { - CssValidator cssValidator = new CssValidator(null); - - // Create fallback first: --ds-text-purple, #FFFFFF - final CSSLexicalUnit param = - CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "--custom-url", null); - final CSSLexicalUnit fallback = - CSSLexicalUnit.createString(LexicalUnit.SAC_STRING_VALUE, "javascript:alert(1)", null); - - // Create first var() function with fallback - final CSSLexicalUnit function = CSSLexicalUnit.createFunction("var", param, fallback); - - // Check if the output is as expected for first var() - assertNull(cssValidator.lexicalValueToString(function)); - } - - @Test - public void testSacUriWithJavaScriptUrl() { - CssValidator cssValidator = new CssValidator(null); - - // Test with a JavaScript URL, which should be blocked - final CSSLexicalUnit jsUrl = - CSSLexicalUnit.createString(LexicalUnit.SAC_URI, "javascript:alert(1)", null); - assertNull("JavaScript URL should be blocked", cssValidator.lexicalValueToString(jsUrl)); - } - @Test public void testSacUriWithValidUrl() { CssValidator cssValidator = new CssValidator(null);