Fix out-of-bounds panic when a meta content value ends in a bare charset token#762
Open
noahskelton wants to merge 1 commit into
Open
Fix out-of-bounds panic when a meta content value ends in a bare charset token#762noahskelton wants to merge 1 commit into
noahskelton wants to merge 1 commit into
Conversation
…set token extract_a_character_encoding_from_a_meta_element indexed one byte past the end of the input when the content value ended in "charset" with optional trailing whitespace and no "=" after it (e.g. content="text/html; charset"). Guard the step-4 lookahead with .get() and return None when the input ends there.
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.
AI Disclosure
This fix was made with the help of Claude Fable 5 and reviewed additionally with Codex GPT 5.5. The bug was encountered in the wild (production HTML scraping pipeline feeding pages through a downstream binding of this crate), and the fix and regression tests have been reviewed and verified by hand.
Summary
extract_a_character_encoding_from_a_meta_elementinhtml5ever/src/encoding.rspanics with an out-of-bounds index when a<meta http-equiv="Content-Type">element'scontentvalue ends in a barecharsettoken with no=after it, e.g.content="text/html; charset". Because the function runs on the parser's hot path (the in-head<meta>rule intree_builder/rules.rscalls it for everyhttp-equiv="Content-Type"element), any such page hard-panicsparse_document. In embedded/FFI contexts this surfaces as an uncatchable process abort:Pages like this exist in the wild — typically a Content-Type meta truncated mid-attribute (
...; charset), or pages that literally emitcontent="text/html; charset".Root cause
After matching the word
charset, step 3 of the WHATWG "extract a character encoding from a meta element" algorithm skips trailing ASCII whitespace, which can leaveposition == input.len()when nothing follows the token. Step 4 then reads the next byte with an unchecked index:(18 is the length of
text/html; charset.)Reproducer
Minimal offending page:
Any parse of it reaches the panic, e.g. with this temporary example (not included in the PR):
Before this change it aborts at
encoding.rs:39with the backtrace pointing atextract_a_character_encoding_from_a_meta_element; after it, it printsparsed ok.The fix
Guard the step-4 lookahead with
.get(). Per the spec, if there is no next character the outer loop can never find anothercharsetmatch, so the function returnsNone(no encoding):This is the only unchecked index in the function — everything after the
=already uses.get(position)?or range slices, which are safe atposition == input.len().Regression test
meta_element_charset_at_end_does_not_paniccovers the three wayspositioncan land at the end of the input:"text/html; charset","charset", and"charset \t"(trailing whitespace). All three panic before the fix and returnNoneafter it.cargo test -p html5ever,cargo fmtandcargo clippy -p html5everall pass.