ROB: Decode all stream filters with short DecodeParms#3913
Open
VectorPeak wants to merge 1 commit into
Open
Conversation
VectorPeak
force-pushed
the
rob-short-decodeparms
branch
from
July 6, 2026 13:50
cf8bac6 to
bae737f
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3913 +/- ##
=======================================
Coverage 97.86% 97.86%
=======================================
Files 57 57
Lines 10738 10739 +1
Branches 2006 2006
=======================================
+ Hits 10509 10510 +1
Misses 127 127
Partials 102 102 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
This is a small robustness change for malformed PDFs. In normal PDFs,
/DecodeParmsshould line up with/Filter, so this probably does not affect common valid files. I also think it is reasonable if maintainers decide this edge case is not worth handling.What Problem This Solves
/Filteris the ordered decoding pipeline for a stream, while/DecodeParmsonly provides the per-filter options for that pipeline. In the normal case, they have the same length. If/DecodeParmsis omitted entirely, pypdf already treats every filter as using default parameters.The malformed edge case is when
/DecodeParmsis present but shorter than/Filter.decode_stream_data()currently walks the two arrays withzip(filters, decode_parms), andzip()stops as soon as the shorter sequence is exhausted. That means the decode loop can stop early instead of continuing with default parameters for the remaining filters.For example:
or:
The stream data still needs both decoding steps: first
ASCIIHexDecode, thenFlateDecode. With an empty/DecodeParmsarray, the current loop does not run any filter at all. With a one-entry/DecodeParmsarray, it only runsASCIIHexDecode, so the output is still Flate-compressed bytes instead of the original payload. This PR keeps walking the full/Filterlist and uses default parameters when a trailing/DecodeParmsentry is missing.Change
/Filterchain./DecodeParmsentries.NullObjecthandling and normal behavior for missing or fully aligned/DecodeParms.Evidence
The added regression test builds a stream with two filters:
It covers these
/DecodeParmsshapes:I also checked the same shape with a tiny generated PDF, so this is not only a direct
StreamObjecttest. The PDF has one page whose content stream is encoded as Flate data wrapped in ASCIIHex data, with the stream dictionary intentionally using a short/DecodeParmsarray:and:
Reading that file through the normal path:
now returns the original page content stream:
Simulating the old
zip(filters, decode_parms)loop shows the difference:So with an empty
/DecodeParmsarray, the old loop leaves the data at the ASCIIHex layer. With a one-entry/DecodeParmsarray, it runsASCIIHexDecodebut leaves the data at the Flate-compressed layer. The fixed loop keeps decoding through the full/Filterlist and reaches the original content bytes.Possible call chain / impact
A reader can reach this path when stream data is decoded through
decode_stream_data(), including malformed PDFs whose stream dictionary has an aligned/Filterarray but a short/DecodeParmsarray.This PR only changes the fallback behavior for missing trailing decode-parameter entries. It does not change predictor handling, individual filter implementations, stream writing, or already aligned
/Filter+/DecodeParmscases.Validation
mamba run -n prw-pypdf-py3.14 python -m pytest tests/test_filters.py -k "short_decode_parms or flate or asciihex" -q- passed, 23 testsmamba run -n prw-pypdf-py3.14 ruff check pypdf/filters.py tests/test_filters.py- passedgit diff --check- passedI also tried the full
tests/test_filters.pyfile locally. It did not complete in this workspace because several cached PDF/image fixtures undertests/pdf_cacheare missing; the focused regression and nearby filter tests above pass.AI assistance
AI assistance was used to identify and review this small robustness change. The final patch and PR text were reviewed before submission.