fix(encoding): respect quoting when reading the Content-Type charset - #194
Closed
athul-22 wants to merge 1 commit into
Closed
fix(encoding): respect quoting when reading the Content-Type charset#194athul-22 wants to merge 1 commit into
athul-22 wants to merge 1 commit into
Conversation
`charset_from_content_type` split a `Content-Type` on every `;` and `=`
without tracking quoted strings, so a quoted parameter value was not
opaque and quoting backslashes were never removed. This decides the
document's transport encoding, which outranks every other signal.
Three headers were read wrongly:
text/html; boundary="; charset=gbk"
The only parameter is `boundary`, whose value is the string
`; charset=gbk`. The header declares no charset, but the inner
`;` was treated as a separator and the document decoded as GBK.
text/html; name="a\"; charset=gbk"; charset=utf-8
`\"` does not close the quoted string, so the charset is utf-8.
The `\"` was read as a terminator and the gbk inside the quoted
value won instead.
text/html; charset="utf\-8"
The quoted value unescapes to `utf-8`. The literal `utf\-8` was
returned, which is not a valid label, so the charset was dropped
and the document fell back to windows-1252.
Split parameters only on the `;` characters outside a quoted string, and
unescape a quoted value rather than trimming its delimiters.
Every existing tolerance is deliberately preserved, because the previous
behavior is relied on by real headers: whitespace around `=`, apostrophe
delimiters, an unterminated quoted string, uppercase parameter names, a
trailing `;`, and a value with no media type. Eighteen header forms were
compared before and after; the three above change and the other fifteen
are byte-identical.
Byte indices land only on ASCII bytes, which never occur inside a
multi-byte UTF-8 sequence, so scanning the value stays on character
boundaries.
Verified on aarch64-darwin per AGENTS.md: `cargo fmt --all` and
`cargo clippy --workspace --all-targets --all-features -- -D warnings`
are clean, and `cargo test -p moli-encoding` passes 56 tests.
Closes lexmount#193
ldm0
self-requested a review
August 24, 2026 13:57
Contributor
Author
|
Superseded by #196, which fixes this alongside the same defect in |
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.
Fixes #193.
charset_from_content_typesplit aContent-Typeon every;and=without tracking quoted strings, so a quoted parameter value was not opaque and quoting backslashes were never removed. This decides the document's transport encoding, which outranks themetaprescan and the fallback.Content-Typetext/html; boundary="; charset=gbk"gbktext/html; name="a\"; charset=gbk"; charset=utf-8gbkutf-8text/html; charset="utf\-8"utf\-8→ dropped → windows-1252utf-8In the first, the only parameter is
boundary, whose value is the string; charset=gbk— the header declares no charset at all. In the second,\"does not close the quoted string, sonameisa"; charset=gbkand the real charset isutf-8. In the third, the quoted value unescapes toutf-8, but the literalutf\-8is not a valid label, so the charset was dropped and any non-ASCII content rendered as mojibake.Both the MIME Sniffing Standard's "parse a MIME type" and Blink's
HeaderFieldTokenizer— which this workspace already mirrors inmoli-header-fieldand already uses forContent-Dispositioninmoli-multipart— treat a quoted string as opaque and remove quoting backslashes. The two references agree on all three rows.No behaviour is tightened
This deliberately keeps the parser's existing tolerances rather than swapping in a strict implementation, because real headers depend on them. I compared eighteen header forms before and after; the three above change and the other fifteen are byte-identical, including:
=—charset = utf-8charset='utf-8'charset="utf-8;, a value with no media type, and a,-joined duplicate headerA strict MIME parser would reject the first two, so I did not use one. The change is confined to what quoting means.
Implementation notes
;characters outside a quoted string, and a\inside one steps over the byte it escapes.",\or;, so scanning stays on character boundaries.trim_matchesdid before.Verification
Per AGENTS.md, on aarch64-darwin:
cargo fmt --all— cleancargo clippy --workspace --all-targets --all-features -- -D warnings— cleancargo test -p moli-encoding— 56 passedFour tests were added: the quoted-boundary case, the escaped-quote case, the backslash-unescape case (each asserted through
decode_html_documentas well as the parser), and one pinning the tolerances listed above.cargo nextest run --no-fail-fastwas still running locally when this was opened; CI covers it here.