Skip to content

Pin HTTP/RTSP version + method normalization to Locale.US (#16765)#16770

Merged
normanmaurer merged 1 commit into
4.1from
pin41
May 8, 2026
Merged

Pin HTTP/RTSP version + method normalization to Locale.US (#16765)#16770
normanmaurer merged 1 commit into
4.1from
pin41

Conversation

@normanmaurer
Copy link
Copy Markdown
Member

HttpVersion, RtspVersions and RtspMethods all uppercase their
input via String.toUpperCase() without an explicit Locale. The JVM
default locale governs that call, and in Turkish (tr_TR) the ASCII
letter 'i' uppercases to 'İ' (U+0130), not 'I'.

Concretely, on a Turkish-locale JVM:

  • RtspMethods.valueOf("describe") produces "DESCRİBE" after the
    uppercase, fails the cache lookup, falls through to
    HttpMethod.valueOf(...), and throws IllegalArgumentException: Illegal character in HTTP Method: 0x130 for what is otherwise a perfectly valid
    RTSP method. Same for "redirect" (REDİRECT).
  • HttpVersion.valueOf("icap/1.0") and the (protocolName, major, minor, ...) constructor with "icap" end up with protocolName()
    containing U+0130 instead of plain ASCII 'I'. The version no longer
    matches its own canonical form.

The protocol grammars are ASCII-only (RFC 7230 §2.6 / RFC 2326 §1.4), so
the uppercase has no business consulting the JVM locale.

Pin every String.toUpperCase() in these three classes to Locale.US:

codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java

  • (text, strict, keepAliveDefault) constructor — line that normalizes
    the supplied protocol string.
  • (protocolName, major, minor, keepAliveDefault, bytes) constructor —
    line that normalizes the supplied protocol name.

codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java

  • valueOf(String name) — line that normalizes the supplied RTSP method
    name before the cache lookup.

codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspVersions.java

  • valueOf(String text) — line that normalizes the supplied version
    string before comparing to the cached RTSP/1.0.

AsciiString.toUpperCase() / AsciiString.toLowerCase() paths in the
same module are already ASCII-byte-level and not affected by this issue,
so they are left alone.

codec-http/src/test/java/io/netty/handler/codec/http/HttpVersionParsingTest.java:

Change Point Test
HttpVersion.valueOf(text) under Turkish locale
testLowercaseIotaProtocolNameUnderTurkishLocale — installs
Locale.setDefault(new Locale("tr","TR")) (restored in finally) and
asserts valueOf("icap/1.0") returns a version whose protocolName()
is the ASCII "ICAP" and whose text() is "ICAP/1.0".
HttpVersion(protocolName, major, minor, ...) under Turkish locale
testProtocolNameConstructorUnderTurkishLocale — same locale setup,
asserts new HttpVersion("icap", 1, 0, true) ends up with
protocolName() = "ICAP" and text() = "ICAP/1.0".

New
codec-http/src/test/java/io/netty/handler/codec/rtsp/RtspMethodsTest.java:

Change Point Test
Cached uppercase lookup still works
valueOfReturnsCachedInstanceForUppercaseName
Lowercase normalization under default locale
valueOfNormalizesLowercaseInputUnderUsLocale
Lowercase normalization under Turkish locale
valueOfNormalizesLowercaseInputUnderTurkishLocale — installs tr_TR,
calls valueOf("describe") and valueOf("redirect"), asserts both
resolve to the cached DESCRIBE / REDIRECT instances.

All three Turkish-locale tests fail deterministically against the
unfixed code (one with a clean assertion failure, the RtspMethods one
with IllegalArgumentException: Illegal character in HTTP Method: 0x130) and pass after the Locale.US change.

Verification:

mvn -pl codec-http -am test -Dtest='HttpVersionParsingTest,RtspMethodsTest' -Dsurefire.failIfNoSpecifiedTests=false
  • Behavior on en/CJK/most locales: unchanged (the previous
    default-locale uppercase already produced ASCII).
  • Behavior on Turkish (and other locales with non-ASCII case mappings,
    e.g. Azerbaijani): RTSP method parsing and HTTP-derived version parsing
    of lowercase or mixed-case inputs now succeed instead of throwing or
    returning a U+0130-tainted result.
  • API: no signature change. All callers continue to use
    valueOf(String) / the existing constructors.
  • Risk: bounded — Locale.US is the standard Netty pattern for
    protocol-string normalization (see e.g.
    WebSocketClientHandshaker.java:761).

`HttpVersion`, `RtspVersions` and `RtspMethods` all uppercase their
input via `String.toUpperCase()` without an explicit `Locale`. The JVM
default locale governs that call, and in Turkish (`tr_TR`) the ASCII
letter `'i'` uppercases to `'İ'` (U+0130), not `'I'`.

Concretely, on a Turkish-locale JVM:

- `RtspMethods.valueOf("describe")` produces `"DESCRİBE"` after the
uppercase, fails the cache lookup, falls through to
`HttpMethod.valueOf(...)`, and throws `IllegalArgumentException: Illegal
character in HTTP Method: 0x130` for what is otherwise a perfectly valid
RTSP method. Same for `"redirect"` (`REDİRECT`).
- `HttpVersion.valueOf("icap/1.0")` and the `(protocolName, major,
minor, ...)` constructor with `"icap"` end up with `protocolName()`
containing U+0130 instead of plain ASCII `'I'`. The version no longer
matches its own canonical form.

The protocol grammars are ASCII-only (RFC 7230 §2.6 / RFC 2326 §1.4), so
the uppercase has no business consulting the JVM locale.

Pin every `String.toUpperCase()` in these three classes to `Locale.US`:

-
`codec-http/src/main/java/io/netty/handler/codec/http/HttpVersion.java`
- `(text, strict, keepAliveDefault)` constructor — line that normalizes
the supplied protocol string.
- `(protocolName, major, minor, keepAliveDefault, bytes)` constructor —
line that normalizes the supplied protocol name.
-
`codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspMethods.java`
- `valueOf(String name)` — line that normalizes the supplied RTSP method
name before the cache lookup.
-
`codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspVersions.java`
- `valueOf(String text)` — line that normalizes the supplied version
string before comparing to the cached `RTSP/1.0`.

`AsciiString.toUpperCase()` / `AsciiString.toLowerCase()` paths in the
same module are already ASCII-byte-level and not affected by this issue,
so they are left alone.

`codec-http/src/test/java/io/netty/handler/codec/http/HttpVersionParsingTest.java`:

| Change Point | Test |
|--------------|------|
| `HttpVersion.valueOf(text)` under Turkish locale |
`testLowercaseIotaProtocolNameUnderTurkishLocale` — installs
`Locale.setDefault(new Locale("tr","TR"))` (restored in `finally`) and
asserts `valueOf("icap/1.0")` returns a version whose `protocolName()`
is the ASCII `"ICAP"` and whose `text()` is `"ICAP/1.0"`. |
| `HttpVersion(protocolName, major, minor, ...)` under Turkish locale |
`testProtocolNameConstructorUnderTurkishLocale` — same locale setup,
asserts `new HttpVersion("icap", 1, 0, true)` ends up with
`protocolName()` = `"ICAP"` and `text()` = `"ICAP/1.0"`. |

New
`codec-http/src/test/java/io/netty/handler/codec/rtsp/RtspMethodsTest.java`:

| Change Point | Test |
|--------------|------|
| Cached uppercase lookup still works |
`valueOfReturnsCachedInstanceForUppercaseName` |
| Lowercase normalization under default locale |
`valueOfNormalizesLowercaseInputUnderUsLocale` |
| Lowercase normalization under Turkish locale |
`valueOfNormalizesLowercaseInputUnderTurkishLocale` — installs `tr_TR`,
calls `valueOf("describe")` and `valueOf("redirect")`, asserts both
resolve to the cached `DESCRIBE` / `REDIRECT` instances. |

All three Turkish-locale tests fail deterministically against the
unfixed code (one with a clean assertion failure, the `RtspMethods` one
with `IllegalArgumentException: Illegal character in HTTP Method:
0x130`) and pass after the `Locale.US` change.

Verification:

```
mvn -pl codec-http -am test -Dtest='HttpVersionParsingTest,RtspMethodsTest' -Dsurefire.failIfNoSpecifiedTests=false
```

- Behavior on en/CJK/most locales: unchanged (the previous
default-locale uppercase already produced ASCII).
- Behavior on Turkish (and other locales with non-ASCII case mappings,
e.g. Azerbaijani): RTSP method parsing and HTTP-derived version parsing
of lowercase or mixed-case inputs now succeed instead of throwing or
returning a U+0130-tainted result.
- API: no signature change. All callers continue to use
`valueOf(String)` / the existing constructors.
- Risk: bounded — `Locale.US` is the standard Netty pattern for
protocol-string normalization (see e.g.
`WebSocketClientHandshaker.java:761`).
@normanmaurer normanmaurer added this to the 4.1.134.Final milestone May 8, 2026
@normanmaurer normanmaurer merged commit cf4657d into 4.1 May 8, 2026
19 checks passed
@normanmaurer normanmaurer deleted the pin41 branch May 8, 2026 13:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants