Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
36 / 36 |
|
100.00% |
1 / 1 |
CRAP | |
100.00% |
1 / 1 |
| Table | |
100.00% |
36 / 36 |
|
100.00% |
1 / 1 |
15 | |
100.00% |
1 / 1 |
| parse | |
100.00% |
36 / 36 |
|
100.00% |
1 / 1 |
15 | |||
| 1 | <?php |
| 2 | declare(strict_types=1); |
| 3 | /** |
| 4 | * Pop PHP Framework (https://www.popphp.org/) |
| 5 | * |
| 6 | * @link https://github.com/popphp/popphp-framework |
| 7 | * @author Nick Sagona, III <nick@popphp.org> |
| 8 | * @copyright Copyright (c) 2009-2026 Nick Sagona, III |
| 9 | * @license https://www.popphp.org/license New BSD License |
| 10 | */ |
| 11 | |
| 12 | /** |
| 13 | * @namespace |
| 14 | */ |
| 15 | namespace Pop\Pdf\Extract\Xref; |
| 16 | |
| 17 | use Pop\Pdf\Extract\Exception; |
| 18 | use Pop\Pdf\Extract\ObjectParser; |
| 19 | use Pop\Pdf\Extract\Tokenizer; |
| 20 | |
| 21 | /** |
| 22 | * Pdf extract xref table class |
| 23 | * |
| 24 | * @category Pop |
| 25 | * @package Pop\Pdf |
| 26 | * @author Nick Sagona, III <nick@popphp.org> |
| 27 | * @copyright Copyright (c) 2009-2026 Nick Sagona, III |
| 28 | * @license https://www.popphp.org/license New BSD License |
| 29 | * @version 6.0.0 |
| 30 | */ |
| 31 | class Table |
| 32 | { |
| 33 | |
| 34 | /** |
| 35 | * Parse a classic xref table at a byte position |
| 36 | * |
| 37 | * @param string $data |
| 38 | * @param int $pos |
| 39 | * @throws Exception |
| 40 | * @return array |
| 41 | */ |
| 42 | public static function parse(string $data, int $pos): array |
| 43 | { |
| 44 | $tokenizer = new Tokenizer($data, $pos); |
| 45 | $token = $tokenizer->next(); |
| 46 | |
| 47 | if (($token['type'] !== 'keyword') || ($token['value'] !== 'xref')) { |
| 48 | throw new Exception('Error: Expected xref keyword.'); |
| 49 | } |
| 50 | |
| 51 | $offsets = []; |
| 52 | |
| 53 | while (true) { |
| 54 | $savedPos = $tokenizer->getPosition(); |
| 55 | $startToken = $tokenizer->next(); |
| 56 | |
| 57 | if (($startToken['type'] === 'keyword') && ($startToken['value'] === 'trailer')) { |
| 58 | break; |
| 59 | } |
| 60 | if ($startToken['type'] !== 'number') { |
| 61 | $tokenizer->setPosition($savedPos); |
| 62 | break; |
| 63 | } |
| 64 | |
| 65 | $countToken = $tokenizer->next(); |
| 66 | if ($countToken['type'] !== 'number') { |
| 67 | throw new Exception('Error: Malformed xref subsection header.'); |
| 68 | } |
| 69 | |
| 70 | $startObj = (int) $startToken['value']; |
| 71 | $count = (int) $countToken['value']; |
| 72 | |
| 73 | // Each classic xref entry is a fixed 20 bytes on disk. A |
| 74 | // malformed subsection header (e.g. a huge declared $count with |
| 75 | // little/no actual data behind it) must not be trusted to drive |
| 76 | // the loop bound - clamp it against what could plausibly remain, |
| 77 | // and bail out cleanly if we run past the end of the data before |
| 78 | // reading $count entries. |
| 79 | $maxPossibleEntries = intdiv(strlen($data) - $tokenizer->getPosition(), 20) + 1; |
| 80 | if ($count > $maxPossibleEntries) { |
| 81 | $count = $maxPossibleEntries; |
| 82 | } |
| 83 | |
| 84 | for ($i = 0; $i < $count; $i++) { |
| 85 | $offsetToken = $tokenizer->next(); |
| 86 | |
| 87 | if ($offsetToken['type'] === 'eof') { |
| 88 | throw new Exception('Error: Unexpected end of data while parsing an xref subsection.'); |
| 89 | } |
| 90 | |
| 91 | $tokenizer->next(); // generation number - not needed for extraction |
| 92 | $typeToken = $tokenizer->next(); |
| 93 | |
| 94 | if (($typeToken['type'] === 'keyword') && ($typeToken['value'] === 'n')) { |
| 95 | $objNum = $startObj + $i; |
| 96 | if (!isset($offsets[$objNum])) { |
| 97 | $offsets[$objNum] = ['offset' => (int) $offsetToken['value']]; |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | $parser = new ObjectParser($tokenizer); |
| 104 | $trailer = $parser->parseValue(); |
| 105 | |
| 106 | if (!is_array($trailer)) { |
| 107 | throw new Exception('Error: Malformed xref trailer dictionary.'); |
| 108 | } |
| 109 | |
| 110 | return ['offsets' => $offsets, 'trailer' => $trailer, 'endPos' => $tokenizer->getPosition()]; |
| 111 | } |
| 112 | |
| 113 | } |