Skip to content

Fix ASCII case-fold collision between distinct punctuation bytes - #3713

Open
shoemoney wants to merge 2 commits into
apple:mainfrom
shoemoney:fix/header-name-case-fold-punctuation-collision
Open

shoemoney wants to merge 2 commits into
apple:mainfrom
shoemoney:fix/header-name-case-fold-punctuation-collision

Conversation

@shoemoney

@shoemoney shoemoney commented Aug 21, 2026 •

Copy link
Copy Markdown

Preserve distinct punctuation when comparing ASCII header names.

Motivation:

The unconditional byte & 0xdf mask treats ^ and ~ as equal. Both are valid in HTTP header names, so looking up or removing X-Foo~Bar can incorrectly match X-Foo^Bar.

Modifications:

Fold only ASCII lowercase letters in both comparison paths. Add direct byte-comparison tests and an HTTPHeaders regression covering lookup, containment, canonical values and removal. The other punctuation pairs in the comparator test are general byte-comparison controls; they are not all valid HTTP header characters.

Result:

Distinct punctuation remains distinct while ASCII letters still compare without case sensitivity. No performance measurements are claimed.

Validation after rebasing onto c1ccdbcc95c0: the two regressions produce nine failed assertions with current main and pass with this patch. All 333 NIOHTTP1Tests XCTest tests and 33 Swift Testing tests pass on Apple Swift 6.4 / arm64. Strict formatting checks pass. The supported Swift/platform matrix still requires repository CI.

@shoemoney
shoemoney force-pushed the fix/header-name-case-fold-punctuation-collision branch 2 times, most recently from 2f15915 to 5bff062 Compare September 17, 2026 07:43
@shoemoney shoemoney changed the title Fix ASCII case-fold collision between distinct tchar punctuation bytes Fix ASCII case-fold collision between distinct punctuation bytes Sep 17, 2026
@shoemoney
shoemoney force-pushed the fix/header-name-case-fold-punctuation-collision branch 2 times, most recently from e99d093 to f7fa0a3 Compare September 17, 2026 14:19
@shoemoney
shoemoney force-pushed the fix/header-name-case-fold-punctuation-collision branch 3 times, most recently from c45a300 to 7eb9988 Compare September 30, 2026 16:35
Motivation:

compareCaseInsensitiveASCIIBytes(to:) in ByteCollectionUtils.swift folds
case by masking every byte with & 0xdf, clearing bit 0x20. That bit is
what separates an ASCII lowercase letter from its uppercase form, but it
is also the only bit separating five unrelated punctuation byte pairs
from one another: ^(0x5e)/~(0x7e), [(0x5b)/{(0x7b), ](0x5d)/}(0x7d),
\(0x5c)/|(0x7c), and @(0x40)/`(0x60). Masking a byte from any of the
right-hand column produces the same value as its left-hand partner, so
the comparison treats them as identical even though every one of these
ten bytes is a legal tchar character in an HTTP header field name per
RFC 7230 3.2.6, and is accepted as such by NIOHTTP1's own header name
validator. In practice this means two genuinely distinct header names,
e.g. X-Foo^Bar and X-Foo~Bar, compare equal, and the collision reaches
every HTTPHeaders API built on this comparison: first(name:), the
subscript family (including canonicalForm:), contains(name:), and
remove(name:).

Modifications:

Replace the unconditional & 0xdf mask with a small helper that only
clears bit 0x20 when the byte is actually an ASCII lowercase letter,
leaving every other byte untouched. The helper is a single UInt8(ascii:)
range switch, matching the idiom already used elsewhere in NIOHTTP1
(HTTPHeaderValidator.swift, HTTPHeaders+Validation.swift), so both the
fast withContiguousStorageIfAvailable loop and the elementsEqual
fallback stay branch-light.

Result:

Header names differing only by one of these five punctuation pairs are
no longer treated as equal, while ASCII letter case-folding is
unaffected. Added a byte-level test covering all five colliding pairs
plus same-byte and letter-case sanity checks, and an HTTPHeaders-level
test exercising the bug through first(name:), contains(name:),
subscript(canonicalForm:), and remove(name:). Both fail against the old
implementation and pass with the fix; the full NIOHTTP1Tests suite
passes.
Motivation:

The comments incorrectly identify every tested punctuation byte as valid in
HTTP header names, and two added tests need standard formatting.

Modifications:

Identify the valid caret/tilde header collision and describe the remaining
pairs as general byte-comparison controls. Apply swift-format wrapping.

Result:

Regression coverage is documented accurately without changing behavior.
@shoemoney
shoemoney force-pushed the fix/header-name-case-fold-punctuation-collision branch from 7eb9988 to 502bafe Compare October 5, 2026 09:08

This branch has not been deployed

No deployments
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.

1 participant