Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 43 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,46 @@ 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
# 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" \
org.owasp.html.java8check.Java8ConsumerCheck "$1"
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
15 changes: 15 additions & 0 deletions change_log.md
Original file line number Diff line number Diff line change
@@ -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 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
Expand Down
8 changes: 8 additions & 0 deletions docs/maven.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ 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 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:

Expand Down
8 changes: 8 additions & 0 deletions java10-shim/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
35 changes: 35 additions & 0 deletions java10-shim/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,41 @@
<maven.deploy.skip>true</maven.deploy.skip>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<!-- The sanitizer jar is Java 8, and Java 8 builds commonly reject
a dependency holding any class compiled for a later release
outside META-INF/versions/. So ForJava10AndLater is compiled
a second time, into target/classes/META-INF/versions/10/, and
the sanitizer's shade configuration takes only that copy into
its multi-release jar, where Java 10 and later find it and
Java 8 and 9 never look. The default execution still compiles
it to the root of target/classes, because that is the copy the
reactor's test class paths see: `mvn test` puts this module's
target/classes on them, a directory, and the JVM reads
versioned entries only from a jar. See issue #515. -->
<execution>
<id>compile-versioned</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<multiReleaseOutput>true</multiReleaseOutput>
<includes>
<include>org/owasp/shim/ForJava10AndLater.java</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
Expand Down
4 changes: 3 additions & 1 deletion java8-shim/src/main/java/org/owasp/shim/Java8Shim.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions owasp-java-html-sanitizer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 69 additions & 6 deletions owasp-java-html-sanitizer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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</_fixupmessages>
<!-- The jar is a multi-release jar for one reason: to carry the
JPMS module descriptor compiled from src/main/java9 at
META-INF/versions/9/module-info.class, which Java 9 and
later read only when this header is present and Java 8
never looks at. There are no versioned copies of any
class. See issue #389. -->
<!-- The jar is a multi-release jar for two reasons, both read
only when this header is present and neither seen by
Java 8. It carries the JPMS module descriptor compiled
from src/main/java9 at META-INF/versions/9/module-info.class,
which Java 9 and later read (issue #389). And the shade
plugin below adds java10-shim's ForJava10AndLater at
META-INF/versions/10/, the only place a Java 8 build's
bytecode check allows a class compiled for Java 10, and
where Java 10 and later find it (issue #515). Without the
header every runtime falls back to the Java 8 shim. -->
<Multi-Release>true</Multi-Release>
<!-- Java 9 and later take the module name from the descriptor
and ignore this header. It is kept so that tools which
Expand Down Expand Up @@ -199,6 +203,21 @@
<exclude>META-INF/maven/com.googlecode.owasp-java-html-sanitizer/java*/**</exclude>
</excludes>
</filter>
<!-- java10-shim is compiled for Java 10. Take only the copy
of ForJava10AndLater it compiles into
META-INF/versions/10/, which Java 10 and later read
because bnd marked this jar Multi-Release, and which
Java 8 and 9 never see. Its classes at the root would
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. -->
<filter>
<artifact>com.googlecode.owasp-java-html-sanitizer:java10-shim</artifact>
<includes>
<include>META-INF/versions/**</include>
</includes>
</filter>
</filters>
<!-- Replace the original JAR with the shaded one as the
main artifact; do not attach a separate shaded artifact -->
Expand Down Expand Up @@ -268,6 +287,50 @@
</arguments>
</configuration>
</execution>
<!-- Compile and run src/it/java8-consumer against the packaged
jar on the class path. Java8ConsumerCheck fails the build if
any class in the jar is newer than a Java 8 build allows, which
is Java 8 outside META-INF/versions/ and release N inside
META-INF/versions/N/, or if the sanitizer does not pick the
shim meant for the JDK running it. CI also runs it on a Java 8
runtime. See issue #515. -->
<execution>
<id>compile-java8-consumer</id>
<phase>integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/bin/javac</executable>
<arguments>
<argument>--release</argument>
<argument>8</argument>
<!-- Newer JDKs warn that release 8 is obsolete. -->
<argument>-Xlint:-options</argument>
<argument>-d</argument>
<argument>${project.build.directory}/java8-consumer</argument>
<argument>-classpath</argument>
<argument>${project.build.directory}/${project.build.finalName}.jar</argument>
<argument>${project.basedir}/src/it/java8-consumer/org/owasp/html/java8check/Java8ConsumerCheck.java</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>run-java8-consumer</id>
<phase>integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/bin/java</executable>
<arguments>
<argument>-classpath</argument>
<argument>${project.build.directory}/${project.build.finalName}.jar${path.separator}${project.build.directory}/java8-consumer</argument>
<argument>org.owasp.html.java8check.Java8ConsumerCheck</argument>
<argument>${project.build.directory}/${project.build.finalName}.jar</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
Expand Down
Loading
Loading