>\n" . "startxref\n" . "1\n" . "%%EOF", PdfDictionary::create([ 'Size' => PdfNumeric::create(5), 'Root' => PdfIndirectObjectReference::create(1, 0) ]), [ 1 => 1000, 2 => 2000, 3 => 3000, 4 => 4000 ] ]; $data[] = [ " " . "xref\n" . "0 5\r\n" . "0000000000 65535 f\r\n" . "0000001000 00000 n\r\n" . "0000002000 00000 n\r\n" . "0000003000 00000 n\r\n" . "0000004000 00000 n\r\n" . "trailer\n" . "<>\n" . "startref\n" . // <-------- without "x" "1\n" . "%%EOF", PdfDictionary::create([ 'Size' => PdfNumeric::create(5), 'Root' => PdfIndirectObjectReference::create(1, 0) ]), [ 1 => 1000, 2 => 2000, 3 => 3000, 4 => 4000 ] ]; $data[] = [ " " . "xref\n" . "1 5\r\n" . // <--- faulty subsection header "0000000000 65535 f\r\n" . "0000001000 00000 n\r\n" . "0000002000 00000 n\r\n" . "0000003000 00000 n\r\n" . "0000004000 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "1\n" . "%%EOF", PdfDictionary::create([ 'Size' => PdfNumeric::create(5), 'Root' => PdfIndirectObjectReference::create(1, 0) ]), [ 1 => 1000, 2 => 2000, 3 => 3000, 4 => 4000 ] ]; $data[] = [ " " . "xref\n" . "0 3\r\n" . "0000000000 65535 f\r\n" . "0000001000 00000 n\r\n" . "0000002000 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "1\n" . "%%EOF\n" . "xref\n" . "3 2\n" . "0000003000 00000 n\r\n" . "0000004000 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "121\n" . "%%EOF\n", PdfDictionary::create([ 'Size' => PdfNumeric::create(5), 'Prev' => PdfNumeric::create(1), 'Root' => PdfIndirectObjectReference::create(1, 0) ]), [ 1 => 1000, 2 => 2000, 3 => 3000, 4 => 4000 ] ]; $data[] = [ " " . "xref\n" . "0 3\r\n" . "0000000000 65535 f\r\n" . "0000001000 00000 n\r\n" . "0000002000 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "1\n" . "%%EOF\n" . "xref\n" . "1 2\n" . "0000001100 00000 n\r\n" . "0000002100 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "121\n" . "%%EOF\n", PdfDictionary::create([ 'Size' => PdfNumeric::create(3), 'Prev' => PdfNumeric::create(1), 'Root' => PdfIndirectObjectReference::create(1, 0) ]), [ 1 => 1100, 2 => 2100 ] ]; $data[] = [ " " . "xref\n" . "1 3\r\n" . // faulty subsection header but handled as it is, because there are more tables. "0000000000 65535 f\r\n" . "0000001000 00000 n\r\n" . "0000002000 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "1\n" . "%%EOF\n" . "xref\n" . "2 2\n" . "0000001100 00000 n\r\n" . "0000002100 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "121\n" . "%%EOF\n", PdfDictionary::create([ 'Size' => PdfNumeric::create(3), 'Prev' => PdfNumeric::create(1), 'Root' => PdfIndirectObjectReference::create(1, 0) ]), [ 1 => 1000, 2 => 1100, 3 => 2100 ] ]; $data[] = [ "endobj\n" . "xref\n" . "0 18\n" . "0000000008 65535 f \n" . "0000000017 00000 n \n" . "0000000124 00000 n \n" . "0000000180 00000 n \n" . "0000000416 00000 n \n" . "0000000633 00000 n \n" . "0000000800 00000 n \n" . "0000001039 00000 n \n" . "0000000009 65535 f \n" . "0000000010 65535 f \n" . "0000000011 65535 f \n" . "0000000012 65535 f \n" . "0000000013 65535 f \n" . "0000000014 65535 f \n" . "0000000000 65535 f \n" . "0000001646 00000 n \n" . "0000001801 00000 n \n" . "0000081568 00000 n \n" . "trailer\n" . "<<4D4F0C8F2B4F3F498F00EB925AE7B9D0>] >>\n" . "startxref\n" . "7\n" . "%%EOF\n" . "xref\n" . "0 0\n" . "trailer\n" . "<<4D4F0C8F2B4F3F498F00EB925AE7B9D0>] /Prev 7/XRefStm 81568>>\n" . "startxref\n" . "512\n" . "%%EOF", PdfDictionary::create([ 'Size' => PdfNumeric::create(18), 'Root' => PdfIndirectObjectReference::create(1, 0), 'Info' => PdfIndirectObjectReference::create(7, 0), 'ID' => PdfArray::create([ PdfHexString::create('4D4F0C8F2B4F3F498F00EB925AE7B9D0'), PdfHexString::create('4D4F0C8F2B4F3F498F00EB925AE7B9D0'), ]), 'Prev' => PdfNumeric::create(7), 'XRefStm' => PdfNumeric::create(81568) ]), [ 1 => 17, 2 => 124, 6 => 800, 9 => false, 10 => false ] ]; // test additional token after %%EOF $data[] = [ " " . "xref\n" . "0 5\r\n" . "0000000000 65535 f\r\n" . "0000001000 00000 n\r\n" . "0000002000 00000 n\r\n" . "0000003000 00000 n\r\n" . "0000004000 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "1\n" . "%%EOF\n" . " PdfNumeric::create(5), 'Root' => PdfIndirectObjectReference::create(1, 0) ]), [ 1 => 1000, 2 => 2000, 3 => 3000, 4 => 4000 ] ]; $data[] = [ " " . "xref\n" . "0 2\r\n" . "0000000000 65535 f\r\n" . "0000001000 00000 n\r\n" . "trailer\n" . "<>\n" . // this /Prev entry is ignored silently "startxref\n" . "1\n" . "%%EOF", PdfDictionary::create([ 'Size' => PdfNumeric::create(2), 'Root' => PdfIndirectObjectReference::create(1, 0), 'Prev' => PdfNumeric::create(0) ]), [ 1 => 1000, ] ]; return $data; } /** * @param $string * @param $expectedTrailer * @param $expectedObjects * @dataProvider handlingProvider */ public function testHandling($string, $expectedTrailer, $expectedObjects) { $stream = StreamReader::createByString($string); $parser = new PdfParser($stream); $xref = new CrossReference($parser); $trailer = $xref->getTrailer(); $this->assertEquals($expectedTrailer, $trailer); foreach ($expectedObjects as $objectId => $position) { $this->assertSame($position, $xref->getOffsetFor($objectId)); } } public function testWithInvalidTokenAfterStartXrefKeyword() { $pdf = "startxref\nTOKEN"; $stream = StreamReader::createByString($pdf); $parser = new PdfParser($stream); $this->expectException(CrossReferenceException::class); $this->expectExceptionCode(CrossReferenceException::INVALID_DATA); new CrossReference($parser); } public function testWithInvalidPrevValue() { $pdf = "xref\n" . "0 3\r\n" . "0000000000 65535 f\r\n" . "0000001000 00000 n\r\n" . "0000002000 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "0\n" . "%%EOF\n" . "xref\n" . "1 2\n" . "0000001100 00000 n\r\n" . "0000002100 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "120\n" . "%%EOF\n"; $stream = StreamReader::createByString($pdf); $parser = new PdfParser($stream); $this->expectException(CrossReferenceException::class); $this->expectExceptionCode(CrossReferenceException::INVALID_DATA); new CrossReference($parser); } public function getIndirectObjectProvider() { $data = []; $path = __DIR__ . '/../../../_files/pdfs/'; $data[] = [ $path . 'Word2010.pdf', [ 1 => PdfIndirectObject::create( 1, 0, PdfDictionary::create([ 'Type' => PdfName::create('Catalog'), 'Pages' => PdfIndirectObjectReference::create(2, 0), 'Lang' => PdfString::create('de-DE'), 'StructTreeRoot' => PdfIndirectObjectReference::create(8, 0), 'MarkInfo' => PdfDictionary::create([ 'Marked' => PdfBoolean::create(true) ]) ]) ), 2 => PdfIndirectObject::create( 2, 0, PdfDictionary::create([ 'Type' => PdfName::create('Pages'), 'Count' => PdfNumeric::create(1), 'Kids' => PdfArray::create([ PdfIndirectObjectReference::create(3, 0) ]) ]) ), 17 => PdfIndirectObject::create( 17, 0, PdfStream::create( PdfDictionary::create([ 'Type' => PdfName::create('XRef'), 'Size' => PdfNumeric::create(17), 'W' => PdfArray::create([ PdfNumeric::create(1), PdfNumeric::create(4), PdfNumeric::create(2) ]), 'Root' => PdfIndirectObjectReference::create(1, 0), 'Info' => PdfIndirectObjectReference::create(7, 0), 'ID' => PdfArray::create([ PdfHexString::create('4D4F0C8F2B4F3F498F00EB925AE7B9D0'), PdfHexString::create('4D4F0C8F2B4F3F498F00EB925AE7B9D0') ]), 'Filter' => PdfName::create('FlateDecode'), 'Length' => PdfNumeric::create(71) ]), 'anything for testing' ) ) ] ]; $data[] = [ $path . 'Boombastic-Box.pdf', [ 1 => PdfIndirectObject::create( 1, 0, PdfDictionary::create([ 'Nums' => PdfArray::create([ PdfNumeric::create(0), PdfIndirectObjectReference::create(2, 0) ]) ]) ), 2 => PdfIndirectObject::create( 2, 0, PdfDictionary::create([ 'S' => PdfName::create('D'), 'St' => PdfNumeric::create(9) ]) ), 11 => PdfIndirectObject::create( 11, 0, PdfStream::create( PdfDictionary::create([ 'Filter' => PdfName::create('FlateDecode'), 'Length' => PdfNumeric::create(1430) ]), 'anything for testing' ) ), 30 => PdfIndirectObject::create( 30, 0, PdfStream::create( PdfDictionary::create([ 'Length' => PdfNumeric::create(4932), 'Subtype' => PdfName::create('XML'), 'Type' => PdfName::create('Metadata') ]), 'anything for testing' ) ) ] ]; $data[] = [ $path . 'filters/lzw/999998.pdf', [ 2 => PdfIndirectObject::create( 2, 0, PdfStream::create( PdfDictionary::create([ 'Filter' => PdfName::create('LZWDecode'), 'Length' => PdfIndirectObjectReference::create(3, 0) ]), 'anything for testing' ) ) ] ]; return $data; } /** * @param $filename * @param array $expectedResults * @dataProvider getIndirectObjectProvider */ public function testGetIndirectObject($filename, array $expectedResults) { $stream = StreamReader::createByFile($filename); $parser = new PdfParser($stream); $xref = new CrossReference($parser); foreach ($expectedResults AS $objectId => $expectedResult) { $object = $xref->getIndirectObject($objectId); $this->assertEquals($expectedResult->objectNumber, $object->objectNumber); $this->assertEquals($expectedResult->generationNumber, $object->generationNumber); $this->assertSame(get_class($expectedResult->value), get_class($object->value)); if ($object->value instanceof PdfStream) { $this->assertEquals($expectedResult->value->value, $object->value->value); } else { $this->assertEquals($expectedResult->value, $object->value); } #$this->assertEquals($expectedResult, $object); } $this->assertGreaterThan(0, $expectedResults); } public function testBehaviourOnCompressedXref() { $stream = StreamReader::createByFile(__DIR__ . '/../../../_files/pdfs/compressed-xref.pdf'); $parser = new PdfParser($stream); $this->expectException(CrossReferenceException::class); $this->expectExceptionCode(CrossReferenceException::COMPRESSED_XREF); new CrossReference($parser); } /** * Test the behavior of a single cross-reference while the startxref key points to the offset 0. * * @throws CrossReferenceException * @throws \setasign\Fpdi\PdfParser\Type\PdfTypeException */ public function testBehaviourWithByteOffsetZero() { $pdf = "%PDF-1.7\n" . "%\xE2\xE3\xCF\xD3\n" . "1 0 obj\n" . "<<>>" . "xref\n" . "0 2\r\n" . "0000000000 65535 f\r\n" . "0000000015 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "0\n" . // this offset is ignored because it is faulty "%%EOF"; $stream = StreamReader::createByString($pdf); $parser = new PdfParser($stream); $this->expectException(CrossReferenceException::class); $this->expectExceptionCode(CrossReferenceException::NO_XREF_FOUND); new CrossReference($parser); } public function testBehaviourWithHeaderOffsetWhichIsUsedInRealByteOffsets() { $pdf = "abcd\n" . // 5 "%PDF-1.7\n" . // 9 "%\xE2\xE3\xCF\xD3\n" . // 6 "1 0 obj\n" . // 8 "<<>>" . // 4 "xref\n" . // 5 "0 2\r\n" . "0000000000 65535 f\r\n" . "0000000020 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "32\n" . "%%EOF"; $stream = StreamReader::createByString($pdf); $parser = new PdfParser($stream); $xref = $parser->getCrossReference(); $object = $xref->getIndirectObject(1); $this->assertEquals(PdfIndirectObject::create(1, 0, PdfDictionary::create([])), $object); } public function testBehaviourWithWrongObjectTypeAttXrefOffset() { $pdf = "%PDF-1.7\n" . "%\xE2\xE3\xCF\xD3\n" . "1 0 obj\n" . "<<>>" . "xref\n" . "0 2\r\n" . "0000000000 65535 f\r\n" . "0000000015 00000 n\r\n" . "trailer\n" . "<>\n" . "startxref\n" . "15\n" . // "%%EOF"; $stream = StreamReader::createByString($pdf); $parser = new PdfParser($stream); $this->expectException(CrossReferenceException::class); $this->expectExceptionCode(CrossReferenceException::INVALID_DATA); new CrossReference($parser); } }