From 5ae6c89bf88bdc5af90bb3e6c02c3a76df80ad36 Mon Sep 17 00:00:00 2001 From: csware Date: Fri, 2 Feb 2024 04:55:45 +0100 Subject: [PATCH 1/9] Drop unnecessary import (#316) Signed-off-by: Sven Strickroth --- src/main/java/org/owasp/html/HtmlPolicyBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/owasp/html/HtmlPolicyBuilder.java b/src/main/java/org/owasp/html/HtmlPolicyBuilder.java index fa1a30ef..52524c5d 100644 --- a/src/main/java/org/owasp/html/HtmlPolicyBuilder.java +++ b/src/main/java/org/owasp/html/HtmlPolicyBuilder.java @@ -32,7 +32,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; From 3e6d6d8ec4b1570575e92cfbdaa7232980ddf9b1 Mon Sep 17 00:00:00 2001 From: csware Date: Fri, 2 Feb 2024 04:55:57 +0100 Subject: [PATCH 2/9] Move test helper to test directory (#317) Signed-off-by: Sven Strickroth --- src/{main => test}/java/org/owasp/html/AllExamples.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{main => test}/java/org/owasp/html/AllExamples.java (100%) diff --git a/src/main/java/org/owasp/html/AllExamples.java b/src/test/java/org/owasp/html/AllExamples.java similarity index 100% rename from src/main/java/org/owasp/html/AllExamples.java rename to src/test/java/org/owasp/html/AllExamples.java From 4870e56845d467cd13d30723c9d52815cdb06099 Mon Sep 17 00:00:00 2001 From: csware Date: Fri, 2 Feb 2024 05:05:22 +0100 Subject: [PATCH 3/9] Add .gitattributes (#308) Signed-off-by: Sven Strickroth --- .gitattributes | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..140a0f9c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +/COPYING text +/src/test/resources/org/owasp/html/* text eol=lf +.gitattributes text +.gitignore text +*.html text +*.java text +*.js text +*.json text +*.md text +*.sh text +*.txt text +*.xml text +*.yml text From e4e82ffc8a0f849a2e9f0bd5e61cd6b8671ab01b Mon Sep 17 00:00:00 2001 From: csware Date: Fri, 2 Feb 2024 05:06:45 +0100 Subject: [PATCH 4/9] Remove mentionings of guava (#303) * Remove the guava dependency from README.md Signed-off-by: Sven Strickroth * Remove guava also from the documentation Signed-off-by: Sven Strickroth * Update RELEASE-checklist.sh to not contain guava any more Signed-off-by: Sven Strickroth --------- Signed-off-by: Sven Strickroth --- README.md | 2 +- RELEASE-checklist.sh | 1 - docs/getting_started.md | 5 ++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e6249953..ec9591d6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A fast and easy to configure HTML Sanitizer written in Java which lets you include HTML authored by third-parties in your web application while protecting against XSS. -The existing dependencies are on guava and JSR 305. The other jars +The existing dependency is on JSR 305. The other jars are only needed by the test suite. The JSR 305 dependency is a compile-only dependency, only needed for annotations. diff --git a/RELEASE-checklist.sh b/RELEASE-checklist.sh index 57e611ba..4f9ba18d 100644 --- a/RELEASE-checklist.sh +++ b/RELEASE-checklist.sh @@ -8,7 +8,6 @@ set -e # Make sure the build is ok via -mvn -Dguava.version=27.0-jre -f aggregate clean verify javadoc:jar source:jar mvn -f aggregate clean verify jacoco:report site javadoc:jar source:jar mvn install mvn org.sonatype.ossindex.maven:ossindex-maven-plugin:audit -f aggregate diff --git a/docs/getting_started.md b/docs/getting_started.md index 44de7fe8..fdb5addf 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -4,12 +4,11 @@ If you are using Maven then follow the [maven](maven.md) directions to add a dependency. Otherwise, -[download prebuilt jars](https://search.maven.org/#artifactdetails%7Ccom.googlecode.owasp-java-html-sanitizer%7Cowasp-java-html-sanitizer%7C20180219.1%7Cjar) +[download prebuilt jars](https://search.maven.org/artifact/com.googlecode.owasp-java-html-sanitizer/owasp-java-html-sanitizer/) or `git clone git@github.com:OWASP/java-html-sanitizer.git` and build the latest source. -Unless maven is managing your CLASSPATH for you, you need to add both `owasp-java-html-sanitizer.jar` and the -Guava JAR. +Unless maven is managing your CLASSPATH for you, you need to add `owasp-java-html-sanitizer.jar`. Once you have your CLASSPATH set up correctly with the relevant JARs you should be able to add From 0166eb4da7c9ed00a95911aeaa66d0aa83b19527 Mon Sep 17 00:00:00 2001 From: csware Date: Fri, 2 Feb 2024 05:07:29 +0100 Subject: [PATCH 5/9] Add GitHub actions for testing (#305) * Add GitHub actions for testing Signed-off-by: Sven Strickroth * Store artifacts Signed-off-by: Sven Strickroth * Only store one artifact Signed-off-by: Sven Strickroth --------- Signed-off-by: Sven Strickroth --- .github/workflows/maven.yml | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 00000000..0487ce5c --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,43 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + paths-ignore: + - '.github/workflows/*.yml' + - '!.github/workflows/maven.yml' + pull_request: + paths-ignore: + - '.github/workflows/*.yml' + - '!.github/workflows/maven.yml' + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + java: [ '11', '17', '21' ] + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK ${{ matrix.Java }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.Java }} + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn --batch-mode --errors --fail-at-end --show-version --update-snapshots verify + - name: Store artifact + if: ${{ matrix.Java == 11 }} + uses: actions/upload-artifact@v4 + with: + name: jar-jdk${{ matrix.Java }} + path: target/*.jar + retention-days: 7 From 68662fcade34686a7dffef3000e26cf7efe9a825 Mon Sep 17 00:00:00 2001 From: mymhealthltd-joshengland <98313117+mymhealthltd-joshengland@users.noreply.github.com> Date: Fri, 2 Feb 2024 04:15:05 +0000 Subject: [PATCH 6/9] Fix allowAttributes().globally() (#247) (#248) * Fix allowAttributes().globally() (#247) Add guard to .globally() method of HtmlPolicyBuilder to prevent ArrayOutOfBoundsException when checking to see if the zeroth element of the attributeNames list contains 'style'. This restores behaviour present in version 202180219.1 which allowed for an empty allowed attributes names list to be specified globally through the builder. * Allow styling when any attribute name matches "style" globally --------- Co-authored-by: Mike Samuel --- src/main/java/org/owasp/html/HtmlPolicyBuilder.java | 9 ++++----- src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java | 6 ++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/owasp/html/HtmlPolicyBuilder.java b/src/main/java/org/owasp/html/HtmlPolicyBuilder.java index 52524c5d..82ee2554 100644 --- a/src/main/java/org/owasp/html/HtmlPolicyBuilder.java +++ b/src/main/java/org/owasp/html/HtmlPolicyBuilder.java @@ -964,12 +964,11 @@ public AttributeBuilder matching( */ @SuppressWarnings("synthetic-access") public HtmlPolicyBuilder globally() { - if(attributeNames.get(0).equals("style")) { - return allowStyling(); - } else { - return HtmlPolicyBuilder.this.allowAttributesGlobally( - policy, attributeNames); + if (attributeNames.contains("style")) { + allowStyling(); } + return HtmlPolicyBuilder.this.allowAttributesGlobally( + policy, attributeNames); } /** diff --git a/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java b/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java index f19b28d6..3f04d98d 100644 --- a/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java +++ b/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java @@ -1007,6 +1007,12 @@ public static final void testTextareaIsNotTextArea() { assertEquals("x", textAreaPolicy.sanitize(input)); } + @Test + public static final void testHtmlPolicyBuilderDefinitionWithNoAttributesDefinedGlobally() { + // Does not crash with a runtime exception + new HtmlPolicyBuilder().allowElements().allowAttributes().globally().toFactory(); + } + @Test public static final void testCSSFontSize() { HtmlPolicyBuilder builder = new HtmlPolicyBuilder(); From 62750d527ad2ad58b1b9a935f8632092d743bdc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:16:44 -0700 Subject: [PATCH 7/9] Bump protobuf-java from 3.9.1 to 3.16.3 in /parent (#274) Bumps [protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.9.1 to 3.16.3. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.9.1...v3.16.3) --- updated-dependencies: - dependency-name: com.google.protobuf:protobuf-java dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index ceaac711..968711c2 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -257,7 +257,7 @@ application while protecting against XSS. com.google.protobuf protobuf-java - 3.9.1 + 3.16.3 provided From 73b86b585456e2113daba14835d57709f69b7aec Mon Sep 17 00:00:00 2001 From: csware Date: Fri, 2 Feb 2024 05:19:44 +0100 Subject: [PATCH 8/9] Increase test coverage (#314) Signed-off-by: Sven Strickroth --- .../java/org/owasp/html/CssSchemaTest.java | 3 + .../org/owasp/html/HtmlPolicyBuilderTest.java | 165 ++++++++++++++++++ .../java/org/owasp/html/SanitizersTest.java | 52 ++++++ 3 files changed, 220 insertions(+) diff --git a/src/test/java/org/owasp/html/CssSchemaTest.java b/src/test/java/org/owasp/html/CssSchemaTest.java index 91c6a792..e3b8e2f9 100644 --- a/src/test/java/org/owasp/html/CssSchemaTest.java +++ b/src/test/java/org/owasp/html/CssSchemaTest.java @@ -55,6 +55,9 @@ public static final void testDangerousProperties() { // Prefix corner cases. "-", "-moz-", + "-ms-", + "-o-", + "-webkit-", }) { assertSame(key, CssSchema.DISALLOWED, CssSchema.DEFAULT.forKey(key)); } diff --git a/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java b/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java index 3f04d98d..e85ff4d6 100644 --- a/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java +++ b/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java @@ -229,6 +229,48 @@ public static final void testStyleFiltering() { .allowStandardUrlProtocols())); } + @Test + public void testSpecificStyleFilterung() { + assertEquals( + Arrays.stream(new String[] { + "

Header

", + "

Paragraph 1

", + "

Click me out

", + "

", + "

Fancy with soupy tags.", + "

Stylish Para 1

", + "

Stylish Para 2

", + ""}).collect(Collectors.joining("\n")), + apply(new HtmlPolicyBuilder() + .allowCommonInlineFormattingElements() + .allowCommonBlockElements() + .allowStyling(CssSchema.withProperties( + List.of("color", "text-align", "font-size"))) + .allowStandardUrlProtocols())); + } + + @Test + public void testUnionStyleFilterung() { + assertEquals( + Arrays.stream(new String[] { + "

Header

", + "

Paragraph 1

", + "

Click me out

", + "

", + "

Fancy with soupy tags.", + "

Stylish Para 1

", + "

Stylish Para 2

", + ""}).collect(Collectors.joining("\n")), + apply(new HtmlPolicyBuilder() + .allowCommonInlineFormattingElements() + .allowCommonBlockElements() + .allowStyling(CssSchema.withProperties( + List.of("color", "text-align"))) + .allowStyling( // union allowed style properties + CssSchema.withProperties(List.of("font-size"))) + .allowStandardUrlProtocols())); + } + @Test public static final void testElementTransforming() { assertEquals( @@ -289,6 +331,25 @@ public static final void testAllowUrlProtocols() { .allowUrlProtocols("http"))); } + @Test + public static final void testDisallowUrlProtocols() { + assertEquals( + Arrays.stream(new String[] { + "Header", + "Paragraph 1", + "Click me out", + "\"local-canary\"", + "Fancy with soupy tags.", + "Stylish Para 1", + "Stylish Para 2", + ""}).collect(Collectors.joining("\n")), + apply(new HtmlPolicyBuilder() + .allowElements("img") + .allowAttributes("src", "alt").onElements("img") + .allowUrlProtocols("http", "https") + .disallowUrlProtocols("http"))); + } + @Test public static final void testPossibleFalloutFromIssue5() { assertEquals( @@ -847,6 +908,52 @@ public static final void testEmptyDefaultLinkRelsSet() { pf.sanitize("eg")); } + @Test + public static final void testRequireAndSkipRels() { + PolicyFactory pf = new HtmlPolicyBuilder() + .allowElements("a") + .allowAttributes("href", "target").onElements("a") + .allowStandardUrlProtocols() + .requireRelsOnLinks("noreferrer") + .skipRelsOnLinks("noopener", "noreferrer") + .toFactory(); + + assertEquals( + "eg", + pf.sanitize("eg")); + + assertEquals( + "eg", + pf.sanitize("eg")); + + assertEquals( + "eg", + pf.sanitize("eg")); + } + + @Test + public static final void testSkipAndRequireRels() { + PolicyFactory pf = new HtmlPolicyBuilder() + .allowElements("a") + .allowAttributes("href", "target").onElements("a") + .allowStandardUrlProtocols() + .skipRelsOnLinks("noopener", "noreferrer") + .requireRelsOnLinks("noreferrer") + .toFactory(); + + assertEquals( + "eg", + pf.sanitize("eg")); + + assertEquals( + "eg", + pf.sanitize("eg")); + + assertEquals( + "eg", + pf.sanitize("eg")); + } + @Test public static final void testExplicitRelsSkip() { PolicyFactory pf = new HtmlPolicyBuilder() @@ -913,6 +1020,64 @@ public static final void testDirLi() { "
  • something
  • ")); } + @Test + public void testDisallowTextIn() { + HtmlPolicyBuilder sharedPolicyBuilder = new HtmlPolicyBuilder() + .allowElements("div") + .allowAttributes("style").onElements("div"); + + PolicyFactory allowPolicy = sharedPolicyBuilder.toFactory(); + assertEquals("
    Some Text
    ", + allowPolicy.sanitize("
    Some Text
    ")); + + PolicyFactory disallowTextPolicy = + sharedPolicyBuilder.disallowTextIn("div").toFactory(); + assertEquals("
    ", + disallowTextPolicy.sanitize( + "
    Some Text
    ")); + } + + @Test + public void testDisallowAttribute() { + HtmlPolicyBuilder sharedPolicyBuilder = new HtmlPolicyBuilder() + .allowElements("div", "p") + .allowAttributes("style").onElements("div", "p"); + + PolicyFactory allowPolicy = sharedPolicyBuilder.toFactory(); + assertEquals( + "

    Some

    Text
    ", + allowPolicy.sanitize( + "

    Some

    Text
    ")); + + PolicyFactory disallowTextPolicy = + sharedPolicyBuilder.disallowAttributes("style").onElements("p").toFactory(); + assertEquals("

    Some

    Text
    ", + disallowTextPolicy.sanitize( + "

    Some

    Text
    ")); + } + + @Test + public void testCreativeCSSStyling() { + PolicyFactory policy = new HtmlPolicyBuilder() + .allowElements("p") + .allowAttributes("style").onElements("p").allowStyling().toFactory(); + + assertEquals("

    Some

    ", + policy.sanitize("

    Some

    ")); + + assertEquals("

    Some

    ", + policy.sanitize("

    Some

    ")); + + assertEquals("

    Some

    ", + policy.sanitize("

    Some

    ")); + + assertEquals("

    Some

    ", + policy.sanitize("

    Some

    ")); + + assertEquals("

    Some

    ", + policy.sanitize("

    Some

    ")); + } + @Test public static void testScriptTagWithCommentBlockContainingHtmlCommentEnd() { PolicyFactory scriptSanitizer = new HtmlPolicyBuilder() diff --git a/src/test/java/org/owasp/html/SanitizersTest.java b/src/test/java/org/owasp/html/SanitizersTest.java index 66f0d661..5cdadace 100644 --- a/src/test/java/org/owasp/html/SanitizersTest.java +++ b/src/test/java/org/owasp/html/SanitizersTest.java @@ -157,6 +157,58 @@ public static final void testImages() { ); } + @Test + public static final void testIntegerAttributePolicy() { + PolicyFactory s = Sanitizers.IMAGES; + assertEquals( + "\"y\"", + s.sanitize( + "\"y\"") + ); + + assertEquals( + "\"y\"", + s.sanitize( + "\"y\"") + ); + + assertEquals( + "\"y\"", + s.sanitize( + "\"y\"") + ); + + assertEquals( + "\"y\"", + s.sanitize( + "\"y\"") + ); + + assertEquals( + "\"y\"", + s.sanitize( + "\"y\"") + ); + + assertEquals( + "\"y\"", + s.sanitize( + "\"y\"") + ); + + assertEquals( + "\"y\"", + s.sanitize( + "\"y\"") + ); + + assertEquals( + "\"y\"", + s.sanitize( + "\"y\"") + ); + } + @Test public static final void testLinks() { PolicyFactory s = Sanitizers.LINKS; From e8aa0f1b75ab81eea97ef173fefc023a3e16963d Mon Sep 17 00:00:00 2001 From: Mike Samuel Date: Thu, 1 Feb 2024 21:24:23 -0700 Subject: [PATCH 9/9] Get tests passing and recognize foreign content nodes better (#318) Prepping for a release. One test was not passing: HtmlBuilderTest.testRelLinksWhenRelisPartOfData I was also getting IDE warnings that there were some uses of `@since 10` APIs which made it hard to run tests outside mvn. This commit cleans up those issues and a few warnings about `@deprecated` in javadoc without a corresponding annotation. Signed-off-by: Mike Samuel --- src/main/java/org/owasp/html/Encoding.java | 1 + .../java/org/owasp/html/HtmlEntities.java | 1 + .../org/owasp/html/HtmlPolicyBuilder.java | 16 ++++++++-- .../org/owasp/html/HtmlStreamRenderer.java | 29 ++++++++++++++++++- src/test/java/org/owasp/html/Benchmark.java | 2 +- .../java/org/owasp/html/HtmlLexerTest.java | 4 +-- .../org/owasp/html/HtmlPolicyBuilderTest.java | 4 +-- 7 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/owasp/html/Encoding.java b/src/main/java/org/owasp/html/Encoding.java index 20dbed99..7ffc7e1e 100644 --- a/src/main/java/org/owasp/html/Encoding.java +++ b/src/main/java/org/owasp/html/Encoding.java @@ -43,6 +43,7 @@ public final class Encoding { * @return text/plain * @deprecated specify whether s is in an attribute value */ + @Deprecated public static String decodeHtml(String s) { return decodeHtml(s, false); } diff --git a/src/main/java/org/owasp/html/HtmlEntities.java b/src/main/java/org/owasp/html/HtmlEntities.java index ca7a2248..c7138d04 100644 --- a/src/main/java/org/owasp/html/HtmlEntities.java +++ b/src/main/java/org/owasp/html/HtmlEntities.java @@ -2308,6 +2308,7 @@ final class HtmlEntities { * @return The offset after the end of the decoded sequence in {@code html}. * @deprecated specify whether html is in an attribute value. */ + @Deprecated public static int appendDecodedEntity( String html, int offset, int limit, StringBuilder sb) { return appendDecodedEntity(html, offset, limit, false, sb); diff --git a/src/main/java/org/owasp/html/HtmlPolicyBuilder.java b/src/main/java/org/owasp/html/HtmlPolicyBuilder.java index 82ee2554..bae6d13e 100644 --- a/src/main/java/org/owasp/html/HtmlPolicyBuilder.java +++ b/src/main/java/org/owasp/html/HtmlPolicyBuilder.java @@ -1037,6 +1037,7 @@ public String apply(String elementName, List attrs) { relValue = DEFAULT_RELS_ON_TARGETTED_LINKS_STR; } else { StringBuilder sb = new StringBuilder(); + Set present = new HashSet(); if (relIndex >= 0) { // Preserve values that are not explicitly skipped. String rels = attrs.get(relIndex); @@ -1047,7 +1048,9 @@ public String apply(String elementName, List attrs) { if (skip.isEmpty() || !skip.contains( Strings.toLowerCase(rels.substring(left, i)))) { - sb.append(rels, left, i).append(' '); + String rel = rels.substring(left, i); + present.add(rel); + sb.append(rel).append(' '); } } left = i + 1; @@ -1055,17 +1058,24 @@ public String apply(String elementName, List attrs) { } } for (String s : extra) { - sb.append(s).append(' '); + if (!present.contains(s)) { + sb.append(s).append(' '); + present.add(s); + } } if (hasTarget) { for (String s : whenTargetPresent) { - sb.append(s).append(' '); + if (!present.contains(s)) { + sb.append(s).append(' '); + present.add(s); + } } } int sblen = sb.length(); if (sblen == 0) { relValue = ""; } else { + // Trim last space. relValue = sb.substring(0, sb.length() - 1); } } diff --git a/src/main/java/org/owasp/html/HtmlStreamRenderer.java b/src/main/java/org/owasp/html/HtmlStreamRenderer.java index bb14e3ee..b81283aa 100644 --- a/src/main/java/org/owasp/html/HtmlStreamRenderer.java +++ b/src/main/java/org/owasp/html/HtmlStreamRenderer.java @@ -33,6 +33,7 @@ import java.io.IOException; import java.util.Iterator; import java.util.List; +import java.util.Set; import javax.annotation.WillCloseWhenClosed; import javax.annotation.concurrent.NotThreadSafe; @@ -57,6 +58,8 @@ public class HtmlStreamRenderer implements HtmlStreamEventReceiver { private StringBuilder pendingUnescaped; private HtmlTextEscapingMode escapingMode = HtmlTextEscapingMode.PCDATA; private boolean open; + /** The count of {@link #foreignContentRootElementNames} opened and not subsequently closed. */ + private int foreignContentDepth = 0; /** * Factory. @@ -168,7 +171,25 @@ private void writeOpenTag( return; } - escapingMode = HtmlTextEscapingMode.getModeForTag(elementName); + if (foreignContentRootElementNames.contains(elementName)) { + foreignContentDepth += 1; + } + + HtmlTextEscapingMode tentativeEscapingMode = HtmlTextEscapingMode.getModeForTag(elementName); + if (foreignContentDepth == 0) { + escapingMode = tentativeEscapingMode; + } else { + switch (tentativeEscapingMode) { + case PCDATA: + case VOID: + escapingMode = tentativeEscapingMode; + break; + default: // escape special characters but do not allow tags + escapingMode = HtmlTextEscapingMode.RCDATA; + break; + } + } + switch (escapingMode) { case CDATA_SOMETIMES: @@ -240,6 +261,10 @@ private final void writeCloseTag(String uncanonElementName) return; } + if (foreignContentDepth != 0 && foreignContentRootElementNames.contains(elementName)) { + foreignContentDepth -= 1; + } + if (pendingUnescaped != null) { if (!lastTagOpened.equals(elementName)) { error("Tag content cannot appear inside CDATA element", elementName); @@ -436,4 +461,6 @@ public void close() throws IOException { private static boolean isTagEnd(char ch) { return ch < 63 && 0 != (TAG_ENDS & (1L << ch)); } + + private static final Set foreignContentRootElementNames = Set.of("svg", "math"); } diff --git a/src/test/java/org/owasp/html/Benchmark.java b/src/test/java/org/owasp/html/Benchmark.java index 2a937fc6..e4a917ad 100644 --- a/src/test/java/org/owasp/html/Benchmark.java +++ b/src/test/java/org/owasp/html/Benchmark.java @@ -58,7 +58,7 @@ public class Benchmark { * specifies a benchmark to run and unspecified ones are not run. */ public static void main(String[] args) throws Exception { - String html = Files.readString(new File(args[0]).toPath(), StandardCharsets.UTF_8); + String html = new String(Files.readAllBytes(new File(args[0]).toPath()), StandardCharsets.UTF_8); boolean timeLibhtmlparser = true; boolean timeSanitize = true; diff --git a/src/test/java/org/owasp/html/HtmlLexerTest.java b/src/test/java/org/owasp/html/HtmlLexerTest.java index d2a680df..2ebf55ea 100644 --- a/src/test/java/org/owasp/html/HtmlLexerTest.java +++ b/src/test/java/org/owasp/html/HtmlLexerTest.java @@ -45,12 +45,12 @@ public class HtmlLexerTest extends TestCase { @Test public final void testHtmlLexer() throws Exception { // Do the lexing. - String input = new String(Files.readString(Paths.get(getClass().getResource("htmllexerinput1.html").toURI()), StandardCharsets.UTF_8)); + String input = new String(Files.readAllBytes(Paths.get(getClass().getResource("htmllexerinput1.html").toURI())), StandardCharsets.UTF_8); StringBuilder actual = new StringBuilder(); lex(input, actual); // Get the golden. - String golden = new String(Files.readString(Paths.get(getClass().getResource("htmllexergolden1.txt").toURI()), StandardCharsets.UTF_8)); + String golden = new String(Files.readAllBytes(Paths.get(getClass().getResource("htmllexergolden1.txt").toURI())), StandardCharsets.UTF_8); // Compare. assertEquals(golden, actual.toString()); diff --git a/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java b/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java index e85ff4d6..746a1017 100644 --- a/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java +++ b/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java @@ -862,7 +862,7 @@ public static final void testLinkRelsWhenRelPresent() { } @Test - public static final void testRelLinksWhenRelisPartOfData() { + public final void testRelLinksWhenRelIsPartOfData() { PolicyFactory pf = new HtmlPolicyBuilder() .allowElements("a") .allowAttributes("href").onElements("a") @@ -871,7 +871,7 @@ public static final void testRelLinksWhenRelisPartOfData() { .allowStandardUrlProtocols() .toFactory(); String toSanitize = "test"; - assertTrue("Failure in testRelLinksWhenRelisPartOfData", pf.sanitize(toSanitize).equals(toSanitize)); + assertEquals(toSanitize, pf.sanitize(toSanitize)); } @Test