Skip to content

Fix DecodingReader::set_encoding panic on declarations longer than 64 bytes - #1041

Open
emschwartz wants to merge 3 commits into
tafia:masterfrom
emschwartz:fix-long-xml-declaration
Open

emschwartz wants to merge 3 commits into
tafia:masterfrom
emschwartz:fix-long-xml-declaration

Conversation

@emschwartz

Copy link
Copy Markdown

Fixes #1040.

Previously, quick-xml assumed that the XML declaration could be read out of only the first 64 bytes. However, there are valid documents that have declarations that are a few bytes longer than that.

This PR changes the prefix reader so that it has a concept of whether the declaration is still open. It will keep reading the underlying stream looking for the declaration's closing >, even if it goes beyond 64 bytes.

This prevents a panic that would be caused by calling set_encoding after the 64 bytes and after the prefix bytes had been dropped.

Claude's write-up

The documented DecodingReader loop (set_encoding(e.encoder()) on Event::Decl) panicked when the XML declaration did not fit in the 64-byte detection prefix:

<?xml version="1.0"
      encoding="ISO-8859-1"
      standalone="yes"?><d>é</d>
set_encoding() called after prefix buffer was drained; encoding can only be changed while the prefix is still active

With non-ASCII content after the declaration, reading failed with a UTF-8 error before Decl was even returned.

Fix: when the prefix is used up before the UTF-8 decoder has seen a >, read another 64-byte chunk into it instead of dropping it. The prefix then always ends after the declaration, so the existing "stop at the first >" logic from #1023 and the prefix.is_some() check in set_encoding work unchanged. Only the declaration goes through the small prefix; the rest is decoded by the main path as before (input with no > at all is now read 64 bytes at a time).

UTF-16 documents that declare UTF-8: with a BOM the prefix holds 31 UTF-16 characters, so a complete declaration such as <?xml version="1.0" encoding="UTF-8"?> hit the same panic. A shorter one switched the decoder to the declared encoding and produced garbage. set_encoding now ignores an ASCII-compatible encoding on a UTF-16 stream, since the declaration could only be read as UTF-16. The issue suggested returning an error instead; ignoring is one line and lets these documents parse. Switching to an error is easy if preferred.

This does not cover the UTF-16LE/BE case from #1033 (neither encoding is ASCII-compatible). Both PRs edit the same if in set_encoding, so whichever lands second needs a small rebase.

Tests: declaration_longer_than_prefix and utf16_declared_as_utf8 in tests/encodings.rs fail on master (the first with the UTF-8 error, the second with the panic above) and pass with the fix. A unit test in src/encoding.rs checks that only the declaration is read through the prefix.

Authored by Claude (claude-opus-5-5); reviewed by the submitter before posting.

@Mingun Mingun added bug encoding Issues related to support of various encodings of the XML documents labels Nov 10, 2026
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 92.85714% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.87%. Comparing base (4a56631) to head (329847c).
⚠️ Report is 23 commits behind head on master.

Files with missing lines Patch % Lines
src/encoding.rs 92.85% 2 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1041      +/-   ##
==========================================
+ Coverage   53.71%   53.87%   +0.15%     
==========================================
  Files          51       51              
  Lines       19412    19532     +120     
==========================================
+ Hits        10428    10523      +95     
- Misses       8984     9009      +25     
Flag Coverage Δ
unittests 53.87% <92.85%> (+0.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/encoding.rs Outdated
// bytes decoded with a potentially wrong initial encoding. A longer
// declaration is read in several chunks of this size.
#[cfg(feature = "encoding")]
const PREFIX_CAP: usize = 64;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also suggest to increase this to 128, so the standard prelude can be fully read in one chunk even in UTF-16 case.

<?xml version="1.0" encoding=""?>

contains 33 characters + name of the encoding (the longest name of the supported encoding is "x-mac-cyrillic" which is 14 characters).

emschwartz and others added 3 commits November 14, 2026 14:06
… bytes

DecodingReader decodes its 64-byte prefix only up to the first `>`, so
that set_encoding() takes effect right after the XML declaration. When
the declaration did not fit in the prefix, the main decode path decoded
the rest of the input as UTF-8: set_encoding() panicked, or reading failed
earlier on non-ASCII content. Keep refilling the prefix until a `>` has
been decoded, so it always ends after the declaration.

A UTF-16 document whose declaration names an ASCII-compatible encoding
such as `UTF-8` hit the same panic when the declaration did not fit in
the prefix, and was decoded as garbage when it did. The declaration could
only be read because the document is UTF-16, so set_encoding() now keeps
the detected encoding in that case.

Closes tafia#1040

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cover the Interrupted retry and error propagation in the prefix refill loop.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJX1e6MaZLgfVEdmH1eMn8
A standard declaration with the longest supported encoding name is 47
characters, which is 94 bytes in UTF-16 and did not fit in 64. Pad the
tests' declarations so they still exceed the prefix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJX1e6Wx7WdriMBUFMxHL3
@emschwartz
emschwartz force-pushed the fix-long-xml-declaration branch from 3c690bb to 412976b Compare November 14, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug encoding Issues related to support of various encodings of the XML documents

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DecodingReader::set_encoding panics when the XML declaration is longer than 64 bytes

3 participants