Skip to content

Auto-port 5.0: Pin HTTP/RTSP version + method normalization to Locale.US#16769

Merged
chrisvest merged 2 commits into
5.0from
auto-port-pr-16765-to-5.0
May 10, 2026
Merged

Auto-port 5.0: Pin HTTP/RTSP version + method normalization to Locale.US#16769
chrisvest merged 2 commits into
5.0from
auto-port-pr-16765-to-5.0

Conversation

@netty-project-bot

Copy link
Copy Markdown
Contributor

Auto-port of #16765 to 5.0
Cherry-picked commit: e6d7cf7


Problem

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.

Fix

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.

Tests

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
# Tests run: 52, Failures: 0, Errors: 0, Skipped: 0

Impact

  • 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).

## Problem

`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.

## Fix

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.

## Tests

`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
# Tests run: 52, Failures: 0, Errors: 0, Skipped: 0
```

## Impact

- 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`).

(cherry picked from commit e6d7cf7)
@chrisvest chrisvest added this to the 5.0.0.Final milestone May 9, 2026
@chrisvest chrisvest enabled auto-merge (squash) May 9, 2026 21:46
@chrisvest chrisvest merged commit 4906f0a into 5.0 May 10, 2026
13 checks passed
@chrisvest chrisvest deleted the auto-port-pr-16765-to-5.0 branch May 10, 2026 00:10
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.

3 participants