Skip to content

Answer the charset declaration question from <head> - #87

Merged
tamnd merged 1 commit into
mainfrom
fix/charset-head-declaration
Aug 10, 2026
Merged

Answer the charset declaration question from <head>#87
tamnd merged 1 commit into
mainfrom
fix/charset-head-declaration

Conversation

@tamnd

@tamnd tamnd commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Follow-up to #81.

fixCharsetMetas sets declared from a <meta charset> anywhere in the tree, and ensureCharset then returns early. So a page whose only charset meta sits in <body>, which is exactly the malformed-markup case the tree-wide walk was added to handle, comes out of sanitize with no declaration in <head> at all.

Same input against both trees, body padded past the prescan window:

before #81:
  CharsetAdded=true
  head: <html><head><meta charset="utf-8"/><title>x</title></head>
  first "charset" at byte 18

after #81:
  CharsetAdded=false CharsetRewritten=true
  head: <html><head><title>x</title></head>
  first "charset" at byte 3247

Browsers and ZIM readers determine the encoding by pre-scanning the first 1024 bytes. A declaration at byte 3247 is not found, the reader falls back to its locale encoding, and every multibyte character mojibakes. That is the failure #16 reports and the one ensureCharset exists to prevent.

The fix keeps the two questions apart. Rewriting stale values stays whole-document, since a declaration in <body> still contradicts the bytes on disk. Only "does the document declare an encoding" moves back to <head>, via headDeclaresCharset, which also skips inert <template> content. A stray body meta is now rewritten to utf-8 and <head> gets its own declaration first: two occurrences, no contradiction, correct prescan.

Also folded in the one-character bound from the same review: rewriteContentTypeCharset started at parts[1], so content="charset=iso-8859-1" with no media type was never rewritten and survived alongside the injected UTF-8 declaration. A real media type has no =, so the existing Cut rejects it without the offset.

TestCharsetRewritesNonUTF8 case 3 previously asserted the buggy shape (CharsetAdded == false, one charset). It now covers the no-media-type Content-Type instead, and the body case moves to TestCharsetInBodyStillDeclaresInHead, which asserts both metas are present, neither is stale, and the first one lands inside the 1024-byte window.

fixCharsetMetas set declared from a <meta charset> anywhere in the tree, so
a page whose only charset meta sits in <body>, which is the malformed markup
case the walk exists to handle, came out of sanitize with no declaration in
<head> at all. Readers pre-scan the first 1024 bytes for the encoding, so the
declaration was never found and every multibyte character mojibaked, which
is the failure issue #16 reports.

Rewriting stale values stays whole-document. Only the declared question moves
back to <head>, so a stray body meta is rewritten to utf-8 and <head> still
gets its own declaration first.

rewriteContentTypeCharset also starts at the first field rather than the
second, so content="charset=iso-8859-1" with no media type is rewritten
instead of being left to contradict the injected declaration. A real media
type has no equals sign, so the existing Cut rejects it.

Follow-up to #81.
@tamnd
tamnd merged commit 4533a71 into main Aug 10, 2026
9 checks passed
@tamnd
tamnd deleted the fix/charset-head-declaration branch August 10, 2026 07:04
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