Conversation
The constructor unescapes quoted-pairs per RFC 9110 §5.6.4, so a parameter value in `#parameters` can hold a DQUOTE or a backslash. `toString()` wrapped that value in DQUOTEs verbatim, producing a header that no longer parses back to the same value: input text/plain; p="a\"b" text/plain; p="a\\b" parsed a"b a\b toString() text/plain; p="a"b" text/plain; p="a\b" re-parsed a ab That matters beyond serialization, because `toString()` is the key custom parsers are registered and looked up under in content-type-parser.js. Two different content types could normalize to one key: text/plain; p="a\"; q=\"b" -> one parameter, p == 'a"; q="b' text/plain; p="a"; q="b" -> two parameters, p == 'a', q == 'b' Both serialized to `text/plain; p="a"; q="b"`, so a parser registered for the first ran for requests carrying the second. Precede each DQUOTE and backslash with a backslash on the way out, which is what RFC 9110 §5.6.4 requires of a sender. Values without either octet are unchanged. Signed-off-by: Khizar <Khizarchaudhryy@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Asadshah7950
left a comment
There was a problem hiding this comment.
Technical Review
This is a clean and spec-compliant fix adhering to RFC 9110 §5.6.4.
Key Observations:
- RFC 9110 §5.6.4 Compliance: Correctly identifies
"(DQUOTE) and\(backslash) as the only two characters that cannot appear literally inside aquoted-stringand must be escaped asquoted-pairby a sender. - Parser Registration & Security: Directly resolves the key collision issue where an unescaped parameter value containing
"; q="could synthesize additional parameters upontoString(), causing unrelated requests to alias into custom parsers registered viaaddContentTypeParser. - Round-Trip Fidelity: Guarantees that
new ContentType(ct.toString())reconstructs the exact original parameter key-value pairs without loss or truncation. - Test Coverage: The test additions in
test/content-type.test.jsare thorough, verifying DQUOTE escaping, backslash escaping, parameter injection prevention, and runtime parser isolation throughapp.inject().
LGTM!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist
npm run testandnpm run benchmarkNo open issue — found by auditing
lib/content-type.js.The bug
The constructor unescapes quoted-pairs, as RFC 9110 §5.6.4 requires of a recipient:
So a stored parameter value can contain a DQUOTE or a backslash.
toString()then wrapped it in DQUOTEs verbatim:which produces a header that no longer parses back to the same value:
toString()p="a\"b"a"bp="a"b"ap="a\\b"a\bp="a\b"abWhy it is not only cosmetic
toString()is the normalized key that custom parsers are registered and looked up under —ContentTypeParser.prototype.addstoresct.toString(), andgetParserlooks upcontentType.toString()for the incoming request. Because the escaping is dropped, two different content types collapse onto one key:A parser registered for the first therefore runs for requests carrying the second. There is a test for exactly this through
inject.The fix
Precede each DQUOTE and backslash with a backslash when serializing, which is what RFC 9110 §5.6.4 requires of a sender. A value containing neither octet is unchanged, so the existing
toString()assertions intest/content-type.test.jsare untouched.I deliberately did not also stop quoting values that are bare tokens (
charset=utf-8still serializes ascharset="utf-8"). That round-trips correctly today, and changing it would alter every existing parser key — a separate question from this bug.Verification
npm run unit: 2345 tests, 0 failuresnpm run lintclean,npm run test:typesclean (1282 assertions)