From e5eae4eb9de6f994ddae34552f5071d588ac6f49 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Thu, 23 Apr 2026 23:55:34 -0300 Subject: [PATCH 1/2] fix: preserve absolute xref offsets with pre-header bytes Some PDFs include bytes before the %PDF- header while still using absolute xref offsets from the beginning of the file. The parser trimmed data before %PDF-, which shifted offsets and caused xref lookup failures. This manifested as an Invalid object reference error in the veraPDF corpus header case. Changes: - Keep original byte layout in RawDataParser::parseData - Add stricter trailer key matching for /Size /Root /Encrypt /Info /Prev - Add defensive handling in xref stream resolution when startxref is near, but not exactly at, the xref stream object - Add regression fixture and integration test Regression fixture: - samples/bugs/PullRequestInvalidObjectReference.pdf Test: - DocumentIssueFocusTest::testParseFileWithCompressedObjRefInXrefStream Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .../PullRequestInvalidObjectReference.pdf | Bin 0 -> 9393 bytes .../PdfParser/RawData/RawDataParser.php | 44 ++++++++++++++---- .../Integration/DocumentIssueFocusTest.php | 7 +++ 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 samples/bugs/PullRequestInvalidObjectReference.pdf diff --git a/samples/bugs/PullRequestInvalidObjectReference.pdf b/samples/bugs/PullRequestInvalidObjectReference.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9d15f2474e107c6029772e2ef2107e7d59458dac GIT binary patch literal 9393 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 literal 0 HcmV?d00001 diff --git a/src/Smalot/PdfParser/RawData/RawDataParser.php b/src/Smalot/PdfParser/RawData/RawDataParser.php index ec8d01e5..025d651a 100644 --- a/src/Smalot/PdfParser/RawData/RawDataParser.php +++ b/src/Smalot/PdfParser/RawData/RawDataParser.php @@ -198,16 +198,16 @@ protected function decodeXref(string $pdfData, int $startxref, array $xref = [], // get only the last updated version $xref['trailer'] = []; // parse trailer_data - if (preg_match('/Size[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) { + if (preg_match('/\/Size[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) { $xref['trailer']['size'] = (int) $matches[1]; } - if (preg_match('/Root[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + if (preg_match('/\/Root[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { $xref['trailer']['root'] = (int) $matches[1].'_'.(int) $matches[2]; } - if (preg_match('/Encrypt[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + if (preg_match('/\/Encrypt[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { $xref['trailer']['encrypt'] = (int) $matches[1].'_'.(int) $matches[2]; } - if (preg_match('/Info[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + if (preg_match('/\/Info[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { $xref['trailer']['info'] = (int) $matches[1].'_'.(int) $matches[2]; } if (preg_match('/ID[\s]*[\[][\s]*[<]([^>]*)[>][\s]*[<]([^>]*)[>]/i', $trailer_data, $matches) > 0) { @@ -216,7 +216,7 @@ protected function decodeXref(string $pdfData, int $startxref, array $xref = [], $xref['trailer']['id'][1] = $matches[2]; } } - if (preg_match('/Prev[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) { + if (preg_match('/\/Prev[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) { $offset = (int) $matches[1]; if (0 != $offset) { // get previous xref @@ -246,7 +246,28 @@ protected function decodeXrefStream(string $pdfData, int $startxref, array $xref { // try to read Cross-Reference Stream $xrefobj = $this->getRawObject($pdfData, $startxref); - $xrefcrs = $this->getIndirectObject($pdfData, $xref, $xrefobj[1], $startxref, true); + $xrefObjRef = isset($xrefobj[1]) && \is_string($xrefobj[1]) ? $xrefobj[1] : ''; + $xrefObjOffset = $startxref; + + // Some malformed files have a startxref that points near the xref stream object. + // Try to recover a nearby valid object header instead of failing hard. + if (0 === preg_match('/^[0-9]+_[0-9]+$/', $xrefObjRef)) { + if ( + preg_match('/([0-9]+)[\x20]+([0-9]+)[\x20]+obj/i', $pdfData, $matches, \PREG_OFFSET_CAPTURE, $startxref) > 0 + && ($matches[0][1] - $startxref) <= 64 + ) { + $xrefObjRef = (int) $matches[1][0].'_'.(int) $matches[2][0]; + $xrefObjOffset = $matches[0][1]; + } + } + + if (0 === preg_match('/^[0-9]+_[0-9]+$/', $xrefObjRef)) { + // Could not resolve a valid xref stream object reference at this offset. + // Keep already collected xref data instead of aborting parsing. + return $xref; + } + + $xrefcrs = $this->getIndirectObject($pdfData, $xref, $xrefObjRef, $xrefObjOffset, true); if (!isset($xref['trailer']) || empty($xref['trailer'])) { // get only the last updated version $xref['trailer'] = []; @@ -607,11 +628,15 @@ protected function getObjectVal(string $pdfData, $xref, array $obj): array if (isset($this->objects[$obj[1]])) { // this object has been already parsed return $this->objects[$obj[1]]; - } elseif (isset($xref[$obj[1]])) { + } elseif (isset($xref[$obj[1]]) && $xref[$obj[1]] > 0) { // parse new object $this->objects[$obj[1]] = $this->getIndirectObject($pdfData, $xref, $obj[1], $xref[$obj[1]], false); return $this->objects[$obj[1]]; + } elseif (isset($xref[$obj[1]]) && $xref[$obj[1]] <= 0) { + // Compressed object references are resolved later from object streams in Parser::parseObject(). + // At raw parsing stage, treat unresolved references as null instead of throwing. + return ['null', 'null', 0]; } } @@ -964,8 +989,9 @@ public function parseData(string $data): array throw new MissingPdfHeaderException('Invalid PDF data: Missing `%PDF-` header.'); } - // get PDF content string - $pdfData = $trimpos > 0 ? substr($data, $trimpos) : $data; + // Keep the original byte layout to preserve absolute xref offsets. + // Some PDFs contain bytes before %PDF- and xref offsets still target the full file. + $pdfData = $data; // get xref and trailer data $xref = $this->getXrefData($pdfData); diff --git a/tests/PHPUnit/Integration/DocumentIssueFocusTest.php b/tests/PHPUnit/Integration/DocumentIssueFocusTest.php index 7c7fe7e6..e192a917 100644 --- a/tests/PHPUnit/Integration/DocumentIssueFocusTest.php +++ b/tests/PHPUnit/Integration/DocumentIssueFocusTest.php @@ -111,4 +111,11 @@ public function testPDFDocEncodingDecode(): void $testSubject = '•†‡…—–ƒ⁄‹›−‰„“”‘’‚™ŁŒŠŸŽıłœšž'; self::assertStringContainsString($testSubject, $details['Subject']); } + + public function testParseFileWithCompressedObjRefInXrefStream(): void + { + $document = (new Parser())->parseFile($this->rootDir.'/samples/bugs/PullRequestInvalidObjectReference.pdf'); + + self::assertSame(1, count($document->getPages())); + } } From 917ad5d7ea24781521c44c10efa6cd2f5a4a497e Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:04:37 -0300 Subject: [PATCH 2/2] test: use assertCount for page count assertion Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/PHPUnit/Integration/DocumentIssueFocusTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPUnit/Integration/DocumentIssueFocusTest.php b/tests/PHPUnit/Integration/DocumentIssueFocusTest.php index e192a917..54a9dfbd 100644 --- a/tests/PHPUnit/Integration/DocumentIssueFocusTest.php +++ b/tests/PHPUnit/Integration/DocumentIssueFocusTest.php @@ -116,6 +116,6 @@ public function testParseFileWithCompressedObjRefInXrefStream(): void { $document = (new Parser())->parseFile($this->rootDir.'/samples/bugs/PullRequestInvalidObjectReference.pdf'); - self::assertSame(1, count($document->getPages())); + self::assertCount(1, $document->getPages()); } }