fix: implement the RFC 9110 media-type grammar strictly - #82
Open
mcollina wants to merge 1 commit into
Open
Conversation
Addresses the review on #81: the quirks carried over from the previous regular expressions were bugs, not behaviour worth preserving. - tchar includes "`" in type and subtype, as in parameter names - qdtext accepts HTAB, not VT - quoted-pair accepts HTAB / SP / VCHAR / obs-text; DEL (0x7f) is rejected - whitespace is OWS (SP / HTAB) only: the String.prototype.trim() set, CR, LF, FF, VT and Unicode whitespace are rejected - OWS is accepted on both sides of ";" and after the last parameter - empty parameters ("text/html;", "text/html; ; a=b") are accepted, as allowed by RFC 9110 Section 5.6.6 - the first occurrence of a duplicate parameter wins, matching util.MIMEType, the WHATWG MIME Sniffing Standard and content-type Verified by differential fuzzing against a reference parser transcribed from the RFC 9110 ABNF: 5M inputs, 0 mismatches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018GeeQqdAmma5RNEEr7r75C
4 tasks
jsumners
approved these changes
Aug 24, 2026
jsumners
left a comment
Member
There was a problem hiding this comment.
I am not familiar with the code in this repo, so the diff doesn't really tell me much. But the intention is a good one. If it works as advertised, that's a good thing. So have a stamp.
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.
Stacked on #81. Addresses @blakeembrey's review there: the "quirks" the scanner had carried over from the previous regular expressions were bugs, and last-parameter-wins diverged from the rest of the ecosystem.
Adversarial review against RFC 9110
I went through the scanner clause by clause against RFC 9110 §8.3.1, §5.6.2 (token), §5.6.3 (OWS), §5.6.4 (quoted-string), §5.6.6 (parameters) and §5.5 (field value whitespace). Findings, all fixed here:
te`xt/ht`ml`is atchartext/html; a="x<HTAB>y"qdtexttext/html; a="x<VT>y"qdtexttext/html; a="\<DEL>"quoted-pairis HTAB / SP / VCHAR / obs-text; DEL is none of those<HTAB>text/html<HTAB>; a=b<HTAB>;/ before the value end)\r\ntext/html\r\n,text/html,text/html\n; a=b, …String.prototype.trim()set)text/html;,text/html; ; a=bparameters = *( OWS ";" OWS [ parameter ] ); Appendix B: "Parameters in media type … can be empty via one or more trailing semicolons"text/html; charset=1; charset=2charset=2charset=1, matchingutil.MIMEType, WHATWG MIME Sniffing andcontent-type@2Things I checked and left as they were, for the record:
type/subtype/parameter names are lower-cased; values are untouched (§8.3.1: "Parameter values might or might not be case-sensitive").foo="") are valid; empty token values (foo=) are not.0xffare rejected everywhere: header values are octets, andobs-textstops at%xFF.__proto__/constructoras parameter names land as ordinary own keys on the null-prototype object (test added).`/'/*/%/|/~). Not enforced: this parser implements the HTTP grammar, which istoken, and*/*-style ranges would break.=(§5.6.6), nor betweentype,/andsubtype.Implementation
TCHARclass shared by type, subtype, names and token values.QDTEXTandQUOTED_PAIRbits in the lookup table (QUOTED_PAIR = QDTEXT ∪ { DQUOTE, "\" }), so the escape check is a single table lookup.isTrimWhitespaceis gone; every whitespace loop iscode !== SP && code !== HTAB.parameters[name] === undefinedcheck on the null-prototype object.Verification
^OWS token "/" token ( OWS ";" OWS [ token "=" ( token / quoted-string ) ] )* OWS $): two generators (random structural characters; well-formed headers with mutations), 5 seeds × 1M inputs, 0 mismatches in accepted/rejected status, error message, type and parameters.npm test: 98 tests, 100% coverage, tstyche;npm run lintclean.npm run benchmark: within noise of perf: replace regex parser with a charCode scanner #81 (application/json; charset=utf-8: 5.13M vs 5.17M ops/sec).Breaking changes
This is semver-major. Inputs that previously parsed and now throw / return
defaultContentType: CR, LF, FF, VT or Unicode whitespace around the media type (#6), VT inside quoted strings (#3),\<DEL>(#4). Inputs that previously threw and now parse: #1, #2, #5, #7. Duplicate parameters now resolve to the first value (#8). The README gains a "Grammar" section documenting all of this.Checklist
npm run testandnpm run benchmark🤖 Generated with Claude Code
https://claude.ai/code/session_018GeeQqdAmma5RNEEr7r75C