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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ always encodes `%`, so an already percent-encoded URI is double-encoded.

- For an entire untrusted URL, parse it with `java.net.URI`, allow-list its scheme
(for example `http` and `https`, rejecting a missing scheme unless relative URLs are
intended), and then encode the whole value for the enclosing context:
intended), enforce application-specific restrictions on its destination and
path, and then encode the whole value for the enclosing context. Parsing alone
does not establish safety:

```jsp
<a href="<%=Encode.forHtmlAttribute(validatedUri.toString())%>">
Expand All @@ -171,6 +173,12 @@ always encodes `%`, so an already percent-encoded URI is double-encoded.
`forUri` is retained for compatibility in all 1.x releases. Whether 2.0 removes it is
tracked in [#142](https://github.com/OWASP/owasp-java-encoder/issues/142).

The ESAPI adapter's `encodeForURL` changes separately in unreleased 1.5: it now
uses `forUriComponent`, escaping URL delimiters while retaining `%20` for spaces.
Pass raw component data, not a complete or already encoded URL. See the
[adapter migration and context contracts](esapi/README.md#url-encoding-migration-in-15-unreleased)
for differences from earlier adapter releases and ESAPI's reference form encoder.

Development
-----------

Expand All @@ -193,6 +201,7 @@ News
### Unreleased - 1.5.0
Development builds use `1.5.0-SNAPSHOT`; this is not a published release.

* fix: the ESAPI adapter's `encodeForURL` now encodes individual URL components with UTF-8, including reserved delimiters and literal `+`, instead of preserving whole-URI delimiters. Spaces remain `%20`, null remains the string `"null"`, and unpaired surrogates remain `-`. See the [migration guide](esapi/README.md#url-encoding-migration-in-15-unreleased) for output changes, form-encoding differences, and the retained quoted HTML/CSS/JavaScript contracts [#100](https://github.com/OWASP/owasp-java-encoder/issues/100).
* feat: all four `forJavaScript*` methods encode dollar sign (`$`) as `\x24`, backtick as `\x60`, and opening brace (`{`) as `\x7b` [#129](https://github.com/OWASP/owasp-java-encoder/issues/129). Escaping `{` prevents input after a trusted `$` from completing `${...}`. Encoded output now supports literal text in ordinary (untagged) template literals as well as single- and double-quoted strings. This changes the encoded output while preserving its decoded JavaScript string value. Tagged templates (including `String.raw`), `${...}` expression bodies, JSON, and script URLs are unsupported; each method's HTML context restrictions still apply.
* fix: all four `forJavaScript*` methods escape unpaired UTF-16 surrogates as `\uXXXX`, preserving their JavaScript string values through UTF-8 serialization [#135](https://github.com/OWASP/owasp-java-encoder/issues/135), and escape DEL/C1 controls (U+007F to U+009F) as `\xNN` [#163](https://github.com/OWASP/owasp-java-encoder/issues/163). Valid surrogate pairs and other non-ASCII text remain unescaped except U+2028/U+2029. These are output-fidelity changes; NEL was already ordinary JavaScript string data.
* feat: add `Encode.forJson` String/Writer methods, the `json` encoder context, and `forJson` tags and EL functions in both JSP and Jakarta tag libraries [#145](https://github.com/OWASP/owasp-java-encoder/issues/145). The caller supplies double quotes. Output uses RFC 8259 string escapes and also escapes HTML script delimiters. Java `null` becomes the text `null` (the JSON string `"null"` when quoted); unpaired surrogates use Unicode escapes and may not interoperate with every JSON consumer. Prefer a serializer for complete JSON documents. The ESAPI adapter retains its existing JSON delegation and null behavior.
Expand Down
7 changes: 7 additions & 0 deletions compatibility/src/EsapiConsumer.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,12 @@ public static void main(String[] args) throws Exception {
Checks.origin(org.owasp.encoder.esapi.ESAPIEncoder.class);
org.owasp.esapi.Encoder encoder = org.owasp.encoder.esapi.ESAPIEncoder.getInstance();
Checks.encoded(encoder.encodeForHTML("A&B<"));
if (!"a%20b%2Bc%26admin%3Dtrue%2F%23".equals(
encoder.encodeForURL("a b+c&admin=true/#"))) {
throw new AssertionError("URL component delimiters were not encoded");
}
if (!"null".equals(encoder.encodeForURL(null))) {
throw new AssertionError("URL null contract changed");
}
}
}
23 changes: 15 additions & 8 deletions core/src/main/java/org/owasp/encoder/Encode.java
Original file line number Diff line number Diff line change
Expand Up @@ -667,9 +667,10 @@ public static void forCssUrl(Writer out, String input)
* <li>The single-quote character({@code '}) <b>is not encoded</b>.</li>
*
* <li>This encoding is not intended to be used standalone. The
* output should be encoded to the target context. For example:
* output still requires application-level URL validation and encoding
* for the target context. Prefer using a validated URL directly:
* {@code <a
* href="<%=Encode.forHtmlAttribute(Encode.forUri(uri))%>">...</a>}.
* href="<%=Encode.forHtmlAttribute(validatedUri.toString())%>">...</a>}.
* (Note, the single-quote character ({@code '}) is not
* encoded.)</li>
*
Expand All @@ -694,9 +695,11 @@ public static void forCssUrl(Writer out, String input)
* safe. Encode each untrusted value inserted into a URL with
* {@link #forUriComponent(String)} instead. To use an entire untrusted
* URL, parse it with {@link java.net.URI}, allow-list its scheme (for
* example {@code http} and {@code https}), and then encode it for the
* example {@code http} and {@code https}), enforce any other application
* restrictions, and then encode it for the
* enclosing output context, for example with
* {@link #forHtmlAttribute(String)}. This method always encodes
* {@link #forHtmlAttribute(String)}. Parsing alone does not establish safety.
* This method always encodes
* {@code %}, so never apply it to a URI that is already percent-encoded.
* It is retained for compatibility in all 1.x releases; removal in 2.0 is
* under consideration.
Expand Down Expand Up @@ -724,7 +727,7 @@ public static void forCssUrl(Writer out, String input)

/**
* Performs percent-encoding for a component of a URI, such as a query
* parameter name or value, path or query-string. In particular this
* parameter name or value, path segment, or fragment. In particular this
* method ensures that special characters in the component do not get
* interpreted as part of another component.
*
Expand All @@ -745,9 +748,13 @@ public static void forCssUrl(Writer out, String input)
* <b>Encoding Notes</b>
* <ul>
*
* <li>The output contains only unreserved characters and
* {@code %xx} escapes, so it is safe to be used in most containing
* contexts, including: HTML/XML, CSS, and JavaScript contexts.</li>
* <li>The output contains only unreserved characters and {@code %xx}
* escapes. Assemble the URL from trusted structure and encoded raw
* components, validate it for its intended use, then encode the complete
* value for the enclosing context (for example,
* {@link #forHtmlAttribute(String)} for a quoted HTML attribute).
* Component encoding does not validate a URL or make arbitrary CSS or
* JavaScript code safe.</li>
*
* <li>URL encoding is an encoding for bytes, not unicode. The
* input string is thus first encoded as a sequence of UTF-8
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/java/org/owasp/encoder/Encoders.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,9 @@ public final class Encoders {
*
* @deprecated Encoding a complete URI does not make an untrusted URI
* safe. Use {@link #URI_COMPONENT} for each untrusted value inserted
* into a URL, or validate an entire URL with {@link java.net.URI} and an
* allow-listed scheme before encoding it for the enclosing context. See
* into a URL. For a complete URL, parse with {@link java.net.URI} and enforce
* application rules, including allowed schemes, before encoding it for the
* enclosing context. Parsing alone does not establish safety. See
* {@link Encode#forUri(String)}. Retained for compatibility in all 1.x
* releases.
*/
Expand Down
15 changes: 15 additions & 0 deletions core/src/test/java/org/owasp/encoder/EncodersTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,21 @@ public void testJsonContext() throws Exception {
assertNotSame(Encoders.JAVASCRIPT_SOURCE_ENCODER, encoder);
}

@SuppressWarnings("deprecation") // Retained 1.x output is part of the contract.
public void testLegacyUriOutputIsUnchangedAcrossEntryPoints() throws Exception {
String input = "https://example.test/a b?q=a+b&next=%20#fragment";
String expected = "https://example.test/a%20b?q=a+b&next=%2520#fragment";
assertEquals(expected, Encode.forUri(input));
StringWriter direct = new StringWriter();
Encode.forUri(direct, input);
assertEquals(expected, direct.toString());
StringWriter registered = new StringWriter();
EncodedWriter writer = new EncodedWriter(registered, "uri");
writer.write(input);
writer.close();
assertEquals(expected, registered.toString());
}

public void testXML11Names() {
assertXML11Encoder("xml-1.1", XMLEncoder.Mode.ALL,
"&#x01;&amp;&lt;&gt;&#34;&#39;");
Expand Down
58 changes: 58 additions & 0 deletions esapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,63 @@ Applications can select another tested version with normal Maven dependency
management. The signed 1.4.1 POM and current development POM default
deterministically to 2.7.0.0; the Central 1.4.0 POM still uses the range above.

## URL encoding migration in 1.5 (unreleased)

Starting with `1.5.0-SNAPSHOT`, `ESAPIEncoder.getInstance().encodeForURL(value)`
uses `Encode.forUriComponent` to encode **one raw URL component** with UTF-8.
Earlier adapter releases, including signed 1.4.1, use deprecated `Encode.forUri`
and leave delimiters such as `& = / ? # +` unchanged. For example:

| Raw input | Adapter through 1.4.1 | Adapter 1.5 | ESAPI reference with UTF-8 |
| --- | --- | --- | --- |
| `a b+c&admin=true` | `a%20b+c&admin=true` | `a%20b%2Bc%26admin%3Dtrue` | `a+b%2Bc%26admin%3Dtrue` |
| `~*` | `~*` | `~%2A` | `%7E*` |
| `%20` | `%2520` | `%2520` | `%2520` |
| Java `null` | String `"null"` | String `"null"` | Java `null` |
| Unpaired UTF-16 surrogate | `-` | `-` | `%3F` (replacement `?`) |

The adapter deliberately uses RFC 3986 component encoding with `%20` for spaces,
preserving its existing space, null, and malformed-Unicode policies. ESAPI's
[reference implementation][esapi-url-reference] uses form encoding with `+` for
spaces and the configured `Encryptor.CharacterEncoding`. Both escape URL
delimiters, but their output is not interchangeable for byte comparisons or
request signatures. Use `java.net.URLEncoder.encode(value, "UTF-8")` if a
protocol requires form encoding. The adapter still declares `EncodingException`
and does not read ESAPI configuration for this operation.

Encode each raw parameter name/value or path segment once, then assemble the
URL using trusted delimiters. Validate the final URL against application rules,
including allowed schemes and any destination/path restrictions. Component
encoding does not prevent every application-specific path issue (for example,
`.` and `..` are unreserved). URI parsing alone is not a safety check. For a
quoted HTML URL attribute, encode the assembled, validated URL with
`Encode.forHtmlAttribute`. See the [shared migration guidance](../README.md#migrating-from-foruri).

Callers that passed complete URLs must change that call pattern before adopting
1.5: component encoding escapes the scheme colon, slashes, and other structural
delimiters. Already percent-encoded input is encoded again. Existing core,
registry, JSP, and Jakarta `forUri` entry points retain their behavior throughout
1.x; only this adapter delegate changes. This is unreleased 1.5 behavior, not a
change to the retained 1.4.1 artifacts.

## Supported output contexts

The adapter intentionally preserves these contexts rather than matching every
escape emitted by ESAPI's reference implementation:

| Method | Supported context and caller responsibility |
| --- | --- |
| `encodeForHTMLAttribute` | A quoted HTML text attribute. Supply single or double quotes. HTML escaping alone does not make event-handler code or an unvalidated URL safe. |
| `encodeForCSS` | A quoted CSS string using `Encode.forCssString`; not arbitrary unquoted CSS values or expressions. |
| `encodeForJavaScript` | A single/double-quoted string or literal text in an ordinary untagged template, using `Encode.forJavaScript`. Not JSON, tagged templates (including `String.raw`), `${...}` expression bodies, arbitrary unquoted code, or script URLs. |
| `encodeForURL` (1.5) | One raw URL component; assemble and validate the URL, then encode for its enclosing context. |

More escaping does not make arbitrary unquoted JavaScript or CSS safe. The CSS
size fix, JavaScript template-boundary and Unicode handling, JSON delegation,
lazy reference lookup, and ESAPI's default disablement of unsafe SQL encoding
remain intact. Parser and contract tests run against the stable ESAPI matrix
listed above; that matrix remains separate from upstream security support.

## Runtime and security notes

`ESAPIEncoder.getInstance()` and its OWASP Java Encoder-backed methods do not
Expand Down Expand Up @@ -127,6 +184,7 @@ dependency declaration.
[esapi-security]: https://github.com/ESAPI/esapi-java-legacy/security
[esapi-latest]: https://github.com/ESAPI/esapi-java-legacy/releases/latest
[esapi-release]: https://github.com/ESAPI/esapi-java-legacy/releases/tag/esapi-2.7.0.0
[esapi-url-reference]: https://github.com/ESAPI/esapi-java-legacy/blob/esapi-2.7.0.0/src/main/java/org/owasp/esapi/reference/DefaultEncoder.java#L506-L516
[encoder-release]: https://github.com/OWASP/owasp-java-encoder/releases/tag/v1.4.1
[encoder-verification]: ../releases/1.4.1.md#verification
[encoder-advisories]: ../releases/1.4.1.md#security-fixes
Expand Down
7 changes: 7 additions & 0 deletions esapi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@
<artifactId>esapi</artifactId>
<version>${esapi.version}</version>
</dependency>
<!-- Independently parse the adapter's quoted HTML attribute output. -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.23.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
47 changes: 35 additions & 12 deletions esapi/src/main/java/org/owasp/encoder/esapi/ESAPIEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,29 @@
* <p>The adapter's {@code encodeForCSS} encodes only quoted CSS strings, and
* {@code encodeForJavaScript} encodes single- or double-quoted JavaScript
* strings and literal text in ordinary (untagged) template literals, not JSON,
* tagged templates (including {@code String.raw}), or script URLs. It escapes
* tagged templates (including {@code String.raw}), template expression bodies,
* or script URLs. It escapes
* DEL/C1 controls and unpaired UTF-16 surrogates while preserving valid pairs.
* Its {@code encodeForURL} delegates to deprecated {@link Encode#forUri(String)}:
* it preserves URI delimiters such as {@code &amp; = / ? #} and therefore is
* not a URL-component or form encoder. For an inserted component, use
* {@link Encode#forUriComponent(String)}. Validate complete URLs and their
* schemes, then encode for the enclosing output context.</p>
* Neither method encodes arbitrary unquoted CSS or JavaScript code.</p>
*
* <p>{@code encodeForHTMLAttribute} encodes quoted HTML text attributes using
* {@link Encode#forHtmlAttribute(String)}. It does not make event-handler code
* or an untrusted URL safe. For a URL-valued attribute, validate the complete
* URL against application rules, including allowed schemes, then encode it for
* the enclosing HTML attribute.</p>
*
* <p>Starting with 1.5, {@code encodeForURL} encodes one raw URL component using
* {@link Encode#forUriComponent(String)}. It percent-encodes UTF-8 bytes,
* including delimiters such as {@code & = / ? # +}, and uses {@code %20}
* for spaces. This intentionally differs from ESAPI's reference form encoding,
* which uses {@code +} for spaces and a configurable character encoding. The
* adapter retains its historical {@code "null"} result for {@code null} input
* and replaces unpaired UTF-16 surrogates with {@code -}. Already percent-encoded
* input is encoded again. It neither validates a complete URL nor reads ESAPI
* configuration. Assemble the URL from trusted structure and encoded raw
* components, validate it for its intended use, then encode for the enclosing
* output context. For form encoding specifically, use
* {@link java.net.URLEncoder} with an explicit character encoding.</p>
*
* <p>The following methods delegate to ESAPI. Most are outside the scope of
* contextual output encoding; JSON encoding retains the reference behavior
Expand Down Expand Up @@ -210,7 +226,11 @@
return reference().decodeForHTML(s);
}

/** {@inheritDoc} */
/**
* Encodes a quoted HTML text attribute, not an unquoted attribute,
* event-handler program, or unvalidated URL. See
* {@link Encode#forHtmlAttribute(String)} for the enclosing-context rules.
*/
@Override
public String encodeForHTMLAttribute(String s) {
return Encode.forHtmlAttribute(s);
Expand All @@ -219,7 +239,8 @@
/**
* Encodes a single- or double-quoted JavaScript string or literal text in an
* ordinary (untagged) template literal using {@link Encode#forJavaScript(String)}.
* Not for JSON, tagged templates (including {@code String.raw}), or script URLs.
* Not for JSON, tagged templates (including {@code String.raw}), template
* expression bodies, unquoted code, or script URLs.
* DEL/C1 controls and unpaired UTF-16 surrogates are escaped; valid pairs
* remain unescaped.
*/
Expand All @@ -236,8 +257,8 @@

/** {@inheritDoc} */
@Override
public String encodeForSQL(Codec codec, String s) {

Check warning on line 260 in esapi/src/main/java/org/owasp/encoder/esapi/ESAPIEncoder.java

View workflow job for this annotation

GitHub Actions / ESAPI 2.7.0.0

encodeForSQL(org.owasp.esapi.codecs.Codec,java.lang.String) in org.owasp.esapi.Encoder has been deprecated

Check warning on line 260 in esapi/src/main/java/org/owasp/encoder/esapi/ESAPIEncoder.java

View workflow job for this annotation

GitHub Actions / Core, JSP, and ESAPI unit tests on Java 8

encodeForSQL(org.owasp.esapi.codecs.Codec,java.lang.String) in org.owasp.esapi.Encoder has been deprecated
return reference().encodeForSQL(codec, s);

Check warning on line 261 in esapi/src/main/java/org/owasp/encoder/esapi/ESAPIEncoder.java

View workflow job for this annotation

GitHub Actions / ESAPI 2.7.0.0

encodeForSQL(org.owasp.esapi.codecs.Codec,java.lang.String) in org.owasp.esapi.Encoder has been deprecated

Check warning on line 261 in esapi/src/main/java/org/owasp/encoder/esapi/ESAPIEncoder.java

View workflow job for this annotation

GitHub Actions / Core, JSP, and ESAPI unit tests on Java 8

encodeForSQL(org.owasp.esapi.codecs.Codec,java.lang.String) in org.owasp.esapi.Encoder has been deprecated
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -283,13 +304,15 @@
}

/**
* Encodes a complete URI using deprecated {@link Encode#forUri(String)}.
* Preserves delimiters such as {@code &amp; = / ? #}; not a component
* or form encoder. The caller must validate the URI and its scheme.
* Encodes a raw URL component as UTF-8 with spaces as {@code %20}, using
* {@link Encode#forUriComponent(String)}. Retains the adapter's
* {@code "null"} result for null and {@code -} for unpaired surrogates.
* The checked exception remains in the ESAPI interface contract;
* this implementation does not depend on a configurable charset.
*/
@Override
public String encodeForURL(String s) throws EncodingException {
return Encode.forUri(s);
return Encode.forUriComponent(s);
}

/** {@inheritDoc} */
Expand Down
Loading
Loading