From 3e143af5f3ed92cee3829c8265a88c1f4aaeaad2 Mon Sep 17 00:00:00 2001 From: Jim Manico Date: Sun, 11 Oct 2026 12:46:35 +0200 Subject: [PATCH 1/7] Ship the Java 10 shim in META-INF/versions/10 Since 20260921.1 bundled the shims into the sanitizer jar, its root held two classes compiled for Java 10, org.owasp.shim.ForJava10AndLater and the empty org.owasp.shim.Notice. A Java 8 project that checks its dependencies with the Maven Enforcer rule enforceBytecodeVersion at maxJdkVersion 1.8 failed on the jar unless it listed both in ignoreClasses. java10-shim now compiles ForJava10AndLater a second time, into META-INF/versions/10/, and the shade configuration takes only that copy from the module. The jar is already multi-release for its module descriptor, so Java 10 and later find the shim there and Java 8 and 9, like the enforcer rule, never look. The root copy stays in java10-shim's own output because the reactor's test class paths read it from a directory, where versioned entries do not apply, so `mvn test` and `mvn verify` still run the same shim. src/it/java8-consumer is a Java 8 program the build runs against the packaged jar at integration-test. It fails if any class is newer than Java 8 outside META-INF/versions/, or newer than N inside META-INF/versions/N/, or if the sanitizer does not pick the shim meant for the running JDK. On the old jar it names the two Java 10 classes; on a jar without the Multi-Release header it reports the Java 8 fallback on Java 21. A new CI job packages with JDK 11 and runs it on a Java 8 runtime, the first time CI loads the jar on Java 8. Closes #515. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 41 ++++++ AGENTS.md | 4 +- change_log.md | 15 ++ docs/maven.md | 6 + java10-shim/README.md | 8 ++ java10-shim/pom.xml | 35 +++++ .../main/java/org/owasp/shim/Java8Shim.java | 4 +- owasp-java-html-sanitizer/README.md | 5 + owasp-java-html-sanitizer/pom.xml | 56 ++++++++ .../html/java8check/Java8ConsumerCheck.java | 132 ++++++++++++++++++ 10 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 owasp-java-html-sanitizer/src/it/java8-consumer/org/owasp/html/java8check/Java8ConsumerCheck.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 90c5e507..b4825421 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,3 +30,44 @@ jobs: - name: Build run: ./mvnw -ntp -B verify + + java8-runtime: + # The library targets Java 8, but building it takes JDK 11 or newer, so + # the matrix above never loads the jar on Java 8. Package it with JDK 11, + # then run the Java 8 consumer check from src/it on a Java 8 runtime: it + # checks the jar's bytecode levels and that Java 8 picks the fallback + # shim, and sanitizes. See #515. + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Java 11 + uses: actions/setup-java@v5 + with: + java-version: '11' + distribution: 'zulu' + cache: maven + + - name: Package + run: ./mvnw -ntp -B -DskipTests -pl owasp-java-html-sanitizer -am package + + - name: Setup Java 8 + uses: actions/setup-java@v5 + with: + java-version: '8' + distribution: 'zulu' + + - name: Run the Java 8 consumer check on Java 8 + run: | + set -- owasp-java-html-sanitizer/target/owasp-java-html-sanitizer-*.jar + if [ $# -ne 1 ] || [ ! -f "$1" ]; then + echo "::error::expected one packaged jar, found: $*" + exit 1 + fi + java -version + javac -d target/java8-consumer -classpath "$1" \ + owasp-java-html-sanitizer/src/it/java8-consumer/org/owasp/html/java8check/Java8ConsumerCheck.java + java -classpath "$1:target/java8-consumer" \ + org.owasp.html.java8check.Java8ConsumerCheck "$1" diff --git a/AGENTS.md b/AGENTS.md index 22ca0a74..c8683ec8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,8 +8,8 @@ OWASP Java HTML Sanitizer (`com.googlecode.owasp-java-html-sanitizer:owasp-java- This is a multi-module Maven build: -- `owasp-java-html-sanitizer/` — the library. Core code: `owasp-java-html-sanitizer/src/main/java/org/owasp/html/`. Tests: `owasp-java-html-sanitizer/src/test/java/org/owasp/html/`. `src/main/java9/module-info.java` is the JPMS module descriptor. A second compiler execution compiles it for Java 9 into `META-INF/versions/9/` of the multi-release JAR, which is how a Java 8 JAR carries one; it exports `org.owasp.html` only. `src/it/jpms-consumer/` is a small module that `./mvnw verify` compiles and runs against the packaged JAR to check that the descriptor is read and the shim stays encapsulated. Those two are the only Java 9 sources in the library module. -- `java8-shim/`, `java10-shim/` — in-repo shims that let the Java 8 library use newer JDK collection APIs when running on Java 10+. Both are inlined into the library JAR by the shade plugin and do not appear in the published POM. `java10-shim` is the only module that may use Java 9 and 10 APIs (`maven.compiler.release` 10). Nothing uses anything newer. +- `owasp-java-html-sanitizer/` — the library. Core code: `owasp-java-html-sanitizer/src/main/java/org/owasp/html/`. Tests: `owasp-java-html-sanitizer/src/test/java/org/owasp/html/`. `src/main/java9/module-info.java` is the JPMS module descriptor. A second compiler execution compiles it for Java 9 into `META-INF/versions/9/` of the multi-release JAR, which is how a Java 8 JAR carries one; it exports `org.owasp.html` only. `src/it/jpms-consumer/` is a small module that `./mvnw verify` compiles and runs against the packaged JAR to check that the descriptor is read and the shim stays encapsulated. Those two are the only Java 9 sources in the library module. `src/it/java8-consumer/` is a Java 8 program that `./mvnw verify` runs against the packaged JAR, and CI on a Java 8 runtime, to check every class's bytecode level and that each runtime picks its shim. +- `java8-shim/`, `java10-shim/` — in-repo shims that let the Java 8 library use newer JDK collection APIs when running on Java 10+. Both are inlined into the library JAR by the shade plugin and do not appear in the published POM. `java10-shim` is the only module that may use Java 9 and 10 APIs (`maven.compiler.release` 10). Nothing uses anything newer. Every class outside `META-INF/versions/` of the published JAR must be Java 8 bytecode, because Java 8 consumers reject dependencies with build rules such as `enforceBytecodeVersion`; newer bytecode goes only under `META-INF/versions/N/`, as `java10-shim`'s does. - `empiricism/` — in-browser experiments that record how real browsers nest and balance tags. They generate `HtmlElementTablesCanned.java` in the library's core package. Never hand-edit that file: change the probe or `empiricism/canned-data.json` and run `empiricism/rebuild.sh`. - `examples/`, `docs/` — samples and documentation. Not part of the shipped artifact. diff --git a/change_log.md b/change_log.md index 740bbec6..fd64d816 100644 --- a/change_log.md +++ b/change_log.md @@ -1,6 +1,21 @@ # OWASP Java HTML Sanitizer Change Log Most recent at top. + * Next release + * The jar passes a Java 8 build's bytecode check again. Since + 20260921.1 bundled the shims into it, its root held two classes + compiled for Java 10, `org.owasp.shim.ForJava10AndLater` and an empty + `org.owasp.shim.Notice`, so a Java 8 project that runs the Maven + Enforcer rule `enforceBytecodeVersion` with `maxJdkVersion` 1.8 failed + on this dependency unless it ignored them. `ForJava10AndLater` now + ships in `META-INF/versions/10/` of the jar, which is already + multi-release, and `Notice` is left out. That rule, and Java 8 and 9, + ignore the versioned directory, while Java 10 and later find the shim + there as before, so which shim each runtime uses is unchanged. A + `ignoreClasses` workaround for these two classes can be removed. The + build fails if any class in the jar is newer than Java 8 outside + `META-INF/versions/`, and CI now runs the packaged jar on Java 8. + Closes #515. * Release 20260924.2 * Follow-ups to the CSS nesting limit in 20260924.1, with no change in output. The CSS lexer keeps a bound on its search for a close diff --git a/docs/maven.md b/docs/maven.md index 17e0a1fd..6a648ad5 100644 --- a/docs/maven.md +++ b/docs/maven.md @@ -19,6 +19,12 @@ The sanitizer JAR is self-contained: the `java8-shim` and `java10-shim` artifact are bundled inside it and do **not** need to be declared as separate dependencies, including when using the JPMS module path. +In releases after 20260924.2, every class outside `META-INF/versions/` is +Java 8 bytecode, so a Java 8 build that checks its dependencies with the Maven +Enforcer rule `enforceBytecodeVersion` and a `maxJdkVersion` of 1.8 accepts +the JAR without `ignoreClasses`. The shim for Java 10 and later is in +`META-INF/versions/10/`, which that rule allows and Java 8 never reads. + On the module path the JAR is an explicit module named `owasp.java.html.sanitizer` that exports the `org.owasp.html` package: diff --git a/java10-shim/README.md b/java10-shim/README.md index c2303e28..1d79c659 100644 --- a/java10-shim/README.md +++ b/java10-shim/README.md @@ -16,6 +16,14 @@ and otherwise uses the Java 8 fallback. Both shim modules are shaded into the published sanitizer JAR; this module is not independently published and is not a supported consumer API. +The sanitizer JAR is a Java 8 JAR, and Java 8 builds commonly reject any +dependency with a class compiled for a later release outside +`META-INF/versions/`. So the build compiles `ForJava10AndLater` twice: into +`META-INF/versions/10/`, the only copy shaded into the multi-release sanitizer +JAR, where Java 10 and later find it and Java 8 and 9 never look; and into +the root of this module's classes, the copy the reactor's tests load. Nothing +else in this module is shaded. See issue #515. + ## Build The [build requires JDK 11 or newer](../README.md#repository-layout). diff --git a/java10-shim/pom.xml b/java10-shim/pom.xml index d85ad0fd..ed8e07dc 100644 --- a/java10-shim/pom.xml +++ b/java10-shim/pom.xml @@ -22,6 +22,41 @@ true + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + compile-versioned + compile + + compile + + + true + + org/owasp/shim/ForJava10AndLater.java + + + + + + + + com.googlecode.owasp-java-html-sanitizer diff --git a/java8-shim/src/main/java/org/owasp/shim/Java8Shim.java b/java8-shim/src/main/java/org/owasp/shim/Java8Shim.java index c949a052..181285ed 100644 --- a/java8-shim/src/main/java/org/owasp/shim/Java8Shim.java +++ b/java8-shim/src/main/java/org/owasp/shim/Java8Shim.java @@ -19,7 +19,9 @@ public abstract class Java8Shim { Object _instance; try { try { - // This is compiled with -release 1.10 in a separate project. + // java10-shim compiles this for Java 10, and the sanitizer jar + // carries it in META-INF/versions/10/, so only Java 10 and + // later find it. _instance = Class.forName("org.owasp.shim.ForJava10AndLater").newInstance(); } catch (Throwable e) { // This is co-located with this project and is a fall-back. diff --git a/owasp-java-html-sanitizer/README.md b/owasp-java-html-sanitizer/README.md index 75258cbb..f05f9ef3 100644 --- a/owasp-java-html-sanitizer/README.md +++ b/owasp-java-html-sanitizer/README.md @@ -24,6 +24,11 @@ rendering to produce sanitized HTML. See the against the packaged JAR during the `integration-test` phase, included in `verify`. It checks the descriptor and confirms that the bundled shim package remains encapsulated. +* The [Java 8 consumer](src/it/java8-consumer/) is compiled for Java 8 and run + against the packaged JAR on the class path in the same phase, and on a + Java 8 runtime in CI. It fails if any class in the JAR is newer than a + Java 8 build allows, or if the sanitizer does not pick the shim meant for + the running JDK. `HtmlElementTablesCanned.java` is generated from browser observations. Do not edit it directly; update the experiment or data in diff --git a/owasp-java-html-sanitizer/pom.xml b/owasp-java-html-sanitizer/pom.xml index c63f703d..f924c375 100644 --- a/owasp-java-html-sanitizer/pom.xml +++ b/owasp-java-html-sanitizer/pom.xml @@ -199,6 +199,18 @@ META-INF/maven/com.googlecode.owasp-java-html-sanitizer/java*/** + + + com.googlecode.owasp-java-html-sanitizer:java10-shim + + META-INF/versions/10/** + + @@ -268,6 +280,50 @@ + + + compile-java8-consumer + integration-test + + exec + + + ${java.home}/bin/javac + + --release + 8 + + -Xlint:-options + -d + ${project.build.directory}/java8-consumer + -classpath + ${project.build.directory}/${project.build.finalName}.jar + ${project.basedir}/src/it/java8-consumer/org/owasp/html/java8check/Java8ConsumerCheck.java + + + + + run-java8-consumer + integration-test + + exec + + + ${java.home}/bin/java + + -classpath + ${project.build.directory}/${project.build.finalName}.jar${path.separator}${project.build.directory}/java8-consumer + org.owasp.html.java8check.Java8ConsumerCheck + ${project.build.directory}/${project.build.finalName}.jar + + + diff --git a/owasp-java-html-sanitizer/src/it/java8-consumer/org/owasp/html/java8check/Java8ConsumerCheck.java b/owasp-java-html-sanitizer/src/it/java8-consumer/org/owasp/html/java8check/Java8ConsumerCheck.java new file mode 100644 index 00000000..1ed08635 --- /dev/null +++ b/owasp-java-html-sanitizer/src/it/java8-consumer/org/owasp/html/java8check/Java8ConsumerCheck.java @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause + +package org.owasp.html.java8check; + +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.owasp.html.Sanitizers; +import org.owasp.shim.Java8Shim; + +/** + * Checks, from the class path, that the packaged jar is usable by a consumer + * that builds for or runs on Java 8. See issue #515. + * + *

Java 8 builds commonly fail on any dependency that holds a class + * compiled for a later release, with the Maven Enforcer rule + * {@code enforceBytecodeVersion} at a {@code maxJdkVersion} of 1.8. That + * rule allows release N under {@code META-INF/versions/N/} of a + * multi-release jar, where only Java N and later look, and nothing newer + * than Java 8 anywhere else. So this checks every class in the jar against + * the same bound, then checks that the sanitizer picks the shim meant for + * the runtime it is on, which is how a mistake in the versioned directory or + * the {@code Multi-Release} manifest header would show. + * + *

The build runs this at the integration-test phase on the JDK running + * the build, and CI also runs it on a Java 8 runtime, the one place the + * Java 8 fallback shim is ever loaded. Any failure exits non-zero. It is + * compiled for Java 8, so keep to Java 8 APIs here. + */ +public final class Java8ConsumerCheck { + + private Java8ConsumerCheck() {} + + /** A versioned entry of a multi-release jar, capturing its release. */ + private static final Pattern VERSIONED = + Pattern.compile("^META-INF/versions/([0-9]+)/"); + + /** Release N compiles to class file major version N + 44. */ + private static final int MAJOR_VERSION_OFFSET = 44; + + public static void main(String[] args) throws Exception { + check(args.length == 1, "usage: Java8ConsumerCheck "); + File jar = new File(args[0]).getCanonicalFile(); + + // What a Java 8 build checks: nothing outside the versioned directories + // is newer than Java 8, and nothing inside one is newer than its release. + List tooNew = new ArrayList<>(); + int classCount = 0; + try (ZipFile zip = new ZipFile(jar)) { + for (Enumeration entries = zip.entries(); + entries.hasMoreElements();) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + if (entry.isDirectory() || !name.endsWith(".class")) { + continue; + } + ++classCount; + Matcher versioned = VERSIONED.matcher(name); + boolean isVersioned = versioned.find(); + int release = isVersioned ? Integer.parseInt(versioned.group(1)) : 8; + int major = majorVersion(zip, entry); + if (major > release + MAJOR_VERSION_OFFSET) { + tooNew.add(name + " is compiled for Java " + + (major - MAJOR_VERSION_OFFSET) + " but " + + (isVersioned ? "sits in the directory for Java " + release + : "is outside META-INF/versions/")); + } + } + } + check(classCount != 0, "no classes in " + jar); + check(tooNew.isEmpty(), "a Java 8 build rejects this jar: " + tooNew); + + // The classes below must come from the jar under test, not from a stray + // shim or class directory earlier on the class path. + for (Class c : new Class[] { Sanitizers.class, Java8Shim.class }) { + File source = new File( + c.getProtectionDomain().getCodeSource().getLocation().toURI()) + .getCanonicalFile(); + check(jar.equals(source), c.getName() + " loaded from " + source); + } + + // Java 10 and later read the versioned directory and find the shim that + // uses their collection factories; Java 8 and 9 never see it and fall + // back. + int runtime = runtimeRelease(); + String expected = runtime >= 10 + ? "org.owasp.shim.ForJava10AndLater" : "org.owasp.shim.ForJava8"; + String shim = Java8Shim.j8().getClass().getName(); + check(expected.equals(shim), + "on Java " + runtime + " the sanitizer loaded " + shim + + " rather than " + expected); + + String sanitized = Sanitizers.FORMATTING.sanitize( + "safe"); + check("safe".equals(sanitized), "sanitized to " + sanitized); + + System.out.println(classCount + " classes in " + jar.getName() + + " are within a Java 8 consumer's bounds; on Java " + runtime + + " the sanitizer uses " + shim); + } + + private static int majorVersion(ZipFile zip, ZipEntry entry) + throws IOException { + try (InputStream in = zip.getInputStream(entry)) { + DataInputStream data = new DataInputStream(in); + check(data.readInt() == 0xCAFEBABE, entry.getName() + " is not a class"); + data.readUnsignedShort(); // minor version + return data.readUnsignedShort(); + } + } + + /** 8 for "1.8", and the number itself from Java 9's "9" on. */ + private static int runtimeRelease() { + String spec = System.getProperty("java.specification.version"); + return Integer.parseInt(spec.startsWith("1.") ? spec.substring(2) : spec); + } + + private static void check(boolean condition, String failure) { + if (!condition) { + throw new AssertionError(failure); + } + } +} From ea30db21fae07e57aceff30d18258bd6bf5175da Mon Sep 17 00:00:00 2001 From: Jim Manico Date: Sun, 11 Oct 2026 12:49:47 +0200 Subject: [PATCH 2/7] Create the Java 8 job's output directory before javac Java 8's javac fails with 'directory not found' when the -d directory does not exist; later javac versions create it. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4825421..03a186ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,6 +67,8 @@ jobs: exit 1 fi java -version + # Java 8's javac, unlike later ones, does not create the -d directory. + mkdir -p target/java8-consumer javac -d target/java8-consumer -classpath "$1" \ owasp-java-html-sanitizer/src/it/java8-consumer/org/owasp/html/java8check/Java8ConsumerCheck.java java -classpath "$1:target/java8-consumer" \ From 422e87cc03cdfc74fc7885ea2f02610abe35075b Mon Sep 17 00:00:00 2001 From: Jim Manico Date: Sun, 11 Oct 2026 13:11:53 +0200 Subject: [PATCH 3/7] Take the review: say the jar is multi-release for the shim too The bnd comment on the Multi-Release header said the jar is multi-release only for its module descriptor and holds no versioned class. Since the previous commits it also holds ForJava10AndLater in META-INF/versions/10/, and without the header every runtime falls back to ForJava8. Co-Authored-By: Claude Opus 5.5 --- owasp-java-html-sanitizer/pom.xml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/owasp-java-html-sanitizer/pom.xml b/owasp-java-html-sanitizer/pom.xml index f924c375..6b19b65c 100644 --- a/owasp-java-html-sanitizer/pom.xml +++ b/owasp-java-html-sanitizer/pom.xml @@ -141,12 +141,16 @@ wrong directory. It is exactly where the JDK expects it, so drop that one warning and no other. --> <_fixupmessages>Classes found in the wrong directory*module-info*;is:=ignore - + true + fail a Java 8 build's bytecode check. The filter takes + every versioned directory rather than naming 10, so that + a change to the shim's release moves the class instead + of silently dropping it. See issue #515. --> com.googlecode.owasp-java-html-sanitizer:java10-shim - META-INF/versions/10/** + META-INF/versions/** From 6a88fb50b69645d29fc31e2c5eebff30710798bb Mon Sep 17 00:00:00 2001 From: Jim Manico Date: Sun, 11 Oct 2026 13:12:44 +0200 Subject: [PATCH 6/7] Take the review: check the Java 10 shim is in the module path's module JpmsConsumerCheck now loads ForJava10AndLater, uninitialized, through the sanitizer module's class loader on Java 10 and later and checks it is in that module, so the build shows the versioned entry is read on the module path as well as on the class path. A copy of the jar without META-INF/versions/10/ fails with the module has no ForJava10AndLater on Java 21 Co-Authored-By: Claude Opus 5.5 --- .../html/jpmscheck/JpmsConsumerCheck.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/owasp-java-html-sanitizer/src/it/jpms-consumer/org/owasp/html/jpmscheck/JpmsConsumerCheck.java b/owasp-java-html-sanitizer/src/it/jpms-consumer/org/owasp/html/jpmscheck/JpmsConsumerCheck.java index 312e392e..193377c4 100644 --- a/owasp-java-html-sanitizer/src/it/jpms-consumer/org/owasp/html/jpmscheck/JpmsConsumerCheck.java +++ b/owasp-java-html-sanitizer/src/it/jpms-consumer/org/owasp/html/jpmscheck/JpmsConsumerCheck.java @@ -99,6 +99,24 @@ public static void main(String[] args) { throw new AssertionError("the shim did not fail in the expected way", e); } + // On Java 10 and later the module also holds the shim compiled for them, + // which the jar carries only in META-INF/versions/10/ so that Java 8 + // builds accept it. It is as encapsulated as the rest of its package, + // so load it, uninitialized, through the module's class loader. See + // issue #515. + if (Runtime.version().major() >= 10) { + Class shim; + try { + shim = Class.forName( + "org.owasp.shim.ForJava10AndLater", false, module.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new AssertionError("the module has no ForJava10AndLater on Java " + + Runtime.version().major(), e); + } + check(module.equals(shim.getModule()), + "ForJava10AndLater is in " + shim.getModule()); + } + // And the sanitizer works from the module path, which drives the shim // loader inside the module. String sanitized = Sanitizers.FORMATTING.sanitize( From 8582d4ed052f66e68e0b31e2bc31edbace89fc2a Mon Sep 17 00:00:00 2001 From: Jim Manico Date: Sun, 11 Oct 2026 13:13:02 +0200 Subject: [PATCH 7/7] Take the review: fix the change log's grammar and name strict mode 'A `ignoreClasses` workaround' becomes 'An'. The change log and docs/maven.md said enforceBytecodeVersion allows the versioned shim; it does so in its default mode, and with strict set it holds versioned classes to maxJdkVersion too, so say so. Co-Authored-By: Claude Opus 5.5 --- change_log.md | 14 +++++++------- docs/maven.md | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/change_log.md b/change_log.md index fd64d816..dcc9d75e 100644 --- a/change_log.md +++ b/change_log.md @@ -9,13 +9,13 @@ Most recent at top. Enforcer rule `enforceBytecodeVersion` with `maxJdkVersion` 1.8 failed on this dependency unless it ignored them. `ForJava10AndLater` now ships in `META-INF/versions/10/` of the jar, which is already - multi-release, and `Notice` is left out. That rule, and Java 8 and 9, - ignore the versioned directory, while Java 10 and later find the shim - there as before, so which shim each runtime uses is unchanged. A - `ignoreClasses` workaround for these two classes can be removed. The - build fails if any class in the jar is newer than Java 8 outside - `META-INF/versions/`, and CI now runs the packaged jar on Java 8. - Closes #515. + multi-release, and `Notice` is left out. That rule in its default, + non-strict mode, and Java 8 and 9, ignore the versioned directory, + while Java 10 and later find the shim there as before, so which shim + each runtime uses is unchanged. An `ignoreClasses` workaround for + these two classes can be removed. The build fails if any class in the + jar is newer than Java 8 outside `META-INF/versions/`, and CI now runs + the packaged jar on Java 8. Closes #515. * Release 20260924.2 * Follow-ups to the CSS nesting limit in 20260924.1, with no change in output. The CSS lexer keeps a bound on its search for a close diff --git a/docs/maven.md b/docs/maven.md index 6a648ad5..a1f7b830 100644 --- a/docs/maven.md +++ b/docs/maven.md @@ -23,7 +23,9 @@ In releases after 20260924.2, every class outside `META-INF/versions/` is Java 8 bytecode, so a Java 8 build that checks its dependencies with the Maven Enforcer rule `enforceBytecodeVersion` and a `maxJdkVersion` of 1.8 accepts the JAR without `ignoreClasses`. The shim for Java 10 and later is in -`META-INF/versions/10/`, which that rule allows and Java 8 never reads. +`META-INF/versions/10/`, which Java 8 never reads and which that rule allows +unless it is set to `strict`, which holds versioned classes to `maxJdkVersion` +too. On the module path the JAR is an explicit module named `owasp.java.html.sanitizer` that exports the `org.owasp.html` package: