Skip to content

Fix UTF-8 corruption in HTML serialization - #784

Merged
mrobinson merged 1 commit into
servo:mainfrom
DragonWork:fix-serializer-utf8
Sep 13, 2026
Merged

mrobinson merged 1 commit into
servo:mainfrom
DragonWork:fix-serializer-utf8

Conversation

@DragonWork

Copy link
Copy Markdown
Contributor

Serializing ordinary HTML such as <p>©</p> produces invalid UTF-8 in 0.40.0: © (C2 A9) becomes a lone A9 byte. Attribute values and other characters in U+0080–U+00BF, except NBSP, are also affected. Consumers that validate UTF-8 can therefore reject otherwise valid HTML.

The HTML specification's escaping algorithm replaces &, NBSP, <, >, and double quotes in attribute mode. Other characters must be preserved.

The byte scanner introduced in #740 looks for 0xC2 to recognize NBSP (C2 A0). When the following byte is not A0, it advances past C2 without writing it. This patch writes that byte before continuing; the next iteration writes the continuation byte. NBSP escaping and the separate raw-text path remain unchanged.

Adds text and attribute regressions mixing ©, ®, « and » with NBSP and ampersand escaping. Both tests fail before the fix.

Verification:

  • 18 library tests and all 45 HTML serializer tests pass.
  • The existing html2html example matches independently calculated escaping rules for 198 cases covering all 64 code points U+0080–U+00BF, adjacent escapes, repeated characters, long spans, and wider Unicode; script/style raw text is preserved.
  • Formatting and html5ever Clippy checks pass.

Local checks used an untracked lockfile with jiff 0.2.35: the published 0.2.36 package fails to compile because included documentation files are missing. No dependency changes are included.

@github-actions github-actions Bot added the V-non-breaking A non-breaking change label Sep 12, 2026
@mrobinson
mrobinson enabled auto-merge September 13, 2026 15:30
@mrobinson
mrobinson added this pull request to the merge queue Sep 13, 2026
Merged via the queue into servo:main with commit c553a21 Sep 13, 2026
10 of 11 checks passed
Goldziher added a commit to xberg-io/html-to-markdown that referenced this pull request Sep 14, 2026
Minor rather than patch: the regen carries source-breaking enum renames in
several bindings. Wire values are unchanged throughout, so no serialized
payload moves -- only the names consumers spell in source.

Java enum constants become SCREAMING_SNAKE (LinkStyle.Inline -> INLINE) across
every generated enum, which is the broad one. The RDFa structured-data variant
previously generated seven different spellings from one Rust variant; each
binding now uses the correct acronym segmentation for its own convention, and
Dart keeps rdFa because flutter_rust_bridge declares that name, not alef.

Three of these are bug fixes rather than cosmetics. Python's .pyi declared
StructuredDataType.RD_FA while the extension declares RDFA, so a type checker
accepted the name that raises AttributeError and rejected the one that works.
Elixir's VisitResult typedoc named a payload key the NIF struct never had.
Ruby's from_hash read `_0` for an adjacently tagged enum whose content key is
`output`, silently discarding the payload in both directions.

html5ever stays at 0.39.0. 0.40.0's serializer drops the C2 lead byte for every
codepoint in U+0080-U+00BF except NBSP, emitting invalid UTF-8; here that makes
repair_with_html5ever fail its from_utf8 check and silently skip the repair pass
on any document containing a section sign, copyright sign or degree sign. Fixed
upstream in servo/html5ever#784, merged but unpublished. Caught by the
corpus_fixpoint_sweep ratchet, which went from 8 unexplained documents to 10.
OGKevin pushed a commit to OGKevin/html5ever that referenced this pull request Sep 15, 2026
Prepare html5ever patch release for servo#784

Signed-off-by: Simon Sapin <simon@igalia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

V-non-breaking A non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants