Skip to content

Commit fb77eab

Browse files
GreyWyvernk00ni
andauthored
Account for inaccurate offsets in getXrefData() (#692)
* Account for inaccurate offsets in getXrefData() Normally offset pointers to `xref` keywords in a PDF document are exact to the byte. However, in some cases the pointer may point to some whitespace *before* the `xref` keyword. Adobe Acrobat takes these 'errors' in stride, displaying the document anyway, and so should PdfParser. Clean up the getXrefData() function in **RawDataParser.php**. It now only needs to do one `preg_match_all()` and pushes the caret past whitespace when looking for `xref` keywords. Use existing **Issue557.pdf** to create a new file: **Issue673.pdf** where the last `/Prev 13486` command has been decremented to `/Prev 13485`. Trying to parse this file would cause an Exception without this fix. * Drop unnecessary PREG_OFFSET_CAPTURE No need to use `PREG_OFFSET_CAPTURE` here. --------- Co-authored-by: Konrad Abicht <hi@inspirito.de>
1 parent ed3fc0b commit fb77eab

3 files changed

Lines changed: 48 additions & 26 deletions

File tree

samples/bugs/Issue673.pdf

23.3 KB
Binary file not shown.

src/Smalot/PdfParser/RawData/RawDataParser.php

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -864,39 +864,39 @@ private function getHeaderValue(?array $headerDic, string $key, string $type, $d
864864
*/
865865
protected function getXrefData(string $pdfData, int $offset = 0, array $xref = []): array
866866
{
867-
$startxrefPreg = preg_match(
868-
'/[\r\n]startxref[\s]*[\r\n]+([0-9]+)[\s]*[\r\n]+%%EOF/i',
867+
// If the $offset is currently pointed at whitespace, bump it
868+
// forward until it isn't; affects loosely targetted offsets
869+
// for the 'xref' keyword
870+
// See: https://github.com/smalot/pdfparser/issues/673
871+
$bumpOffset = $offset;
872+
while (preg_match('/\s/', substr($pdfData, $bumpOffset, 1))) {
873+
++$bumpOffset;
874+
}
875+
876+
// Find all startxref tables from this $offset forward
877+
$startxrefPreg = preg_match_all(
878+
'/(?<=[\r\n])startxref[\s]*[\r\n]+([0-9]+)[\s]*[\r\n]+%%EOF/i',
869879
$pdfData,
870-
$matches,
871-
\PREG_OFFSET_CAPTURE,
880+
$startxrefMatches,
881+
\PREG_SET_ORDER,
872882
$offset
873883
);
874884

875-
if (0 == $offset) {
876-
// find last startxref
877-
$pregResult = preg_match_all(
878-
'/[\r\n]startxref[\s]*[\r\n]+([0-9]+)[\s]*[\r\n]+%%EOF/i',
879-
$pdfData,
880-
$matches,
881-
\PREG_SET_ORDER,
882-
$offset
883-
);
884-
if (0 == $pregResult) {
885-
throw new \Exception('Unable to find startxref');
886-
}
887-
$matches = array_pop($matches);
888-
$startxref = $matches[1];
889-
} elseif (strpos($pdfData, 'xref', $offset) == $offset) {
885+
if (0 == $startxrefPreg) {
886+
// No startxref tables were found
887+
throw new \Exception('Unable to find startxref');
888+
} elseif (0 == $offset) {
889+
// Use the last startxref in the document
890+
$startxref = (int) $startxrefMatches[\count($startxrefMatches) - 1][1];
891+
} elseif (strpos($pdfData, 'xref', $bumpOffset) == $bumpOffset) {
890892
// Already pointing at the xref table
891-
$startxref = $offset;
892-
} elseif (preg_match('/([0-9]+[\s][0-9]+[\s]obj)/i', $pdfData, $matches, \PREG_OFFSET_CAPTURE, $offset)) {
893+
$startxref = $bumpOffset;
894+
} elseif (preg_match('/([0-9]+[\s][0-9]+[\s]obj)/i', $pdfData, $matches, 0, $bumpOffset)) {
893895
// Cross-Reference Stream object
894-
$startxref = $offset;
895-
} elseif ($startxrefPreg) {
896-
// startxref found
897-
$startxref = $matches[1][0];
896+
$startxref = $bumpOffset;
898897
} else {
899-
throw new \Exception('Unable to find startxref');
898+
// Use the next startxref from this $offset
899+
$startxref = (int) $startxrefMatches[0][1];
900900
}
901901

902902
if ($startxref > \strlen($pdfData)) {

tests/PHPUnit/Integration/RawData/RawDataParserTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,26 @@ public function testDecodeXrefStreamIssue479(): void
172172
$this->assertArrayHasKey('Subject', $details);
173173
$this->assertArrayHasKey('Title', $details);
174174
}
175+
176+
/**
177+
* Account for inaccurate offset values in getXrefData.
178+
*
179+
* Normally offset values extracted from the PDF document are exact.
180+
* However in some cases, they may point to whitespace *before* a
181+
* valid xref keyword. Move the offset forward past whitespace to
182+
* make this function a little more lenient.
183+
*
184+
* @see https://github.com/smalot/pdfparser/issues/673
185+
*/
186+
public function testGetXrefDataIssue673(): void
187+
{
188+
$filename = $this->rootDir.'/samples/bugs/Issue673.pdf';
189+
190+
// Parsing this document would previously throw an Exception
191+
$parser = $this->getParserInstance();
192+
$document = $parser->parseFile($filename);
193+
$text = $document->getText();
194+
195+
self::assertStringContainsString('6 rue des Goutais', $text);
196+
}
175197
}

0 commit comments

Comments
 (0)