b47ea7ac83d21524d7c4541cd5b764aef45560e6
[fa-stable.git] / reporting / includes / fpdi / pdf_parser.php
1 <?php
2 //
3 //  FPDI - Version 1.2.1
4 //
5 //    Copyright 2004-2008 Setasign - Jan Slabon
6 //
7 //  Licensed under the Apache License, Version 2.0 (the "License");
8 //  you may not use this file except in compliance with the License.
9 //  You may obtain a copy of the License at
10 //
11 //      http://www.apache.org/licenses/LICENSE-2.0
12 //
13 //  Unless required by applicable law or agreed to in writing, software
14 //  distributed under the License is distributed on an "AS IS" BASIS,
15 //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 //  See the License for the specific language governing permissions and
17 //  limitations under the License.
18 //
19
20 if (!defined ('PDF_TYPE_NULL'))
21     define ('PDF_TYPE_NULL', 0);
22 if (!defined ('PDF_TYPE_NUMERIC'))
23     define ('PDF_TYPE_NUMERIC', 1);
24 if (!defined ('PDF_TYPE_TOKEN'))
25     define ('PDF_TYPE_TOKEN', 2);
26 if (!defined ('PDF_TYPE_HEX'))
27     define ('PDF_TYPE_HEX', 3);
28 if (!defined ('PDF_TYPE_STRING'))
29     define ('PDF_TYPE_STRING', 4);
30 if (!defined ('PDF_TYPE_DICTIONARY'))
31     define ('PDF_TYPE_DICTIONARY', 5);
32 if (!defined ('PDF_TYPE_ARRAY'))
33     define ('PDF_TYPE_ARRAY', 6);
34 if (!defined ('PDF_TYPE_OBJDEC'))
35     define ('PDF_TYPE_OBJDEC', 7);
36 if (!defined ('PDF_TYPE_OBJREF'))
37     define ('PDF_TYPE_OBJREF', 8);
38 if (!defined ('PDF_TYPE_OBJECT'))
39     define ('PDF_TYPE_OBJECT', 9);
40 if (!defined ('PDF_TYPE_STREAM'))
41     define ('PDF_TYPE_STREAM', 10);
42 if (!defined ('PDF_TYPE_BOOLEAN'))
43     define ('PDF_TYPE_BOOLEAN', 11);
44 if (!defined ('PDF_TYPE_REAL'))
45     define ('PDF_TYPE_REAL', 12);
46     
47 require_once("pdf_context.php");
48 require_once("wrapper_functions.php");
49
50 class pdf_parser {
51         
52         /**
53      * Filename
54      * @var string
55      */
56     var $filename;
57     
58     /**
59      * File resource
60      * @var resource
61      */
62     var $f;
63     
64     /**
65      * PDF Context
66      * @var object pdf_context-Instance
67      */
68     var $c;
69     
70     /**
71      * xref-Data
72      * @var array
73      */
74     var $xref;
75
76     /**
77      * root-Object
78      * @var array
79      */
80     var $root;
81         
82     /**
83      * PDF version of the loaded document
84      * @var string
85      */
86     var $pdfVersion;
87     
88     /**
89      * Constructor
90      *
91      * @param string $filename  Source-Filename
92      */
93         function pdf_parser($filename) {
94         $this->filename = $filename;
95         
96         $this->f = @fopen($this->filename, "rb");
97
98         if (!$this->f)
99             $this->error(sprintf("Cannot open %s !", $filename));
100
101         $this->getPDFVersion();
102
103         $this->c = new pdf_context($this->f);
104         // Read xref-Data
105         $this->pdf_read_xref($this->xref, $this->pdf_find_xref());
106
107         // Check for Encryption
108         $this->getEncryption();
109
110         // Read root
111         $this->pdf_read_root();
112     }
113     
114     /**
115      * Close the opened file
116      */
117     function closeFile() {
118         if (isset($this->f)) {
119             fclose($this->f);   
120                 unset($this->f);
121         }       
122     }
123     
124     /**
125      * Print Error and die
126      *
127      * @param string $msg  Error-Message
128      */
129     function error($msg) {
130         die("<b>PDF-Parser Error:</b> ".$msg);  
131     }
132     
133     /**
134      * Check Trailer for Encryption
135      */
136     function getEncryption() {
137         if (isset($this->xref['trailer'][1]['/Encrypt'])) {
138             $this->error("File is encrypted!");
139         }
140     }
141     
142         /**
143      * Find/Return /Root
144      *
145      * @return array
146      */
147     function pdf_find_root() {
148         if ($this->xref['trailer'][1]['/Root'][0] != PDF_TYPE_OBJREF) {
149             $this->error("Wrong Type of Root-Element! Must be an indirect reference");
150         }
151         return $this->xref['trailer'][1]['/Root'];
152     }
153
154     /**
155      * Read the /Root
156      */
157     function pdf_read_root() {
158         // read root
159         $this->root = $this->pdf_resolve_object($this->c, $this->pdf_find_root());
160     }
161     
162     /**
163      * Get PDF-Version
164      *
165      * And reset the PDF Version used in FPDI if needed
166      */
167     function getPDFVersion() {
168         fseek($this->f, 0);
169         preg_match("/\d\.\d/",fread($this->f,16),$m);
170         if (isset($m[0]))
171             $this->pdfVersion = $m[0];
172         return $this->pdfVersion;
173     }
174     
175     /**
176      * Find the xref-Table
177      */
178     function pdf_find_xref() {
179         $toRead = 1500;
180                 
181         $stat = fseek ($this->f, -$toRead, SEEK_END);
182         if ($stat === -1) {
183             fseek ($this->f, 0);
184         }
185         $data = fread($this->f, $toRead);
186         
187         $pos = strlen($data) - strpos(strrev($data), strrev('startxref')); 
188         $data = substr($data, $pos);
189         
190         if (!preg_match('/\s*(\d+).*$/s', $data, $matches)) {
191             $this->error("Unable to find pointer to xref table");
192         }
193
194         return (int) $matches[1];
195     }
196
197     /**
198      * Read xref-table
199      *
200      * @param array $result Array of xref-table
201      * @param integer $offset of xref-table
202      */
203     function pdf_read_xref(&$result, $offset) {
204         fseek($this->f, $o_pos = $offset-20); // set some bytes backwards to fetch errorious docs
205             
206         $data = fread($this->f, 100);
207         
208         $xrefPos = strpos($data, 'xref');
209         
210         if ($xrefPos === false) {
211             $this->error('Unable to find xref table.');
212         }
213         
214         if (!isset($result['xref_location'])) {
215             $result['xref_location'] = $o_pos+$xrefPos;
216             $result['max_object'] = 0;
217         }
218
219         $cylces = -1;
220         $bytesPerCycle = 100;
221         
222         fseek($this->f, $o_pos = $o_pos+$xrefPos+4); // set the handle directly after the "xref"-keyword
223         $data = fread($this->f, $bytesPerCycle);
224         
225         while (($trailerPos = strpos($data, 'trailer', max($bytesPerCycle*$cylces++, 0))) === false && !feof($this->f)) {
226             $data .= fread($this->f, $bytesPerCycle);
227         }
228         
229         if ($trailerPos === false) {
230             $this->error('Trailer keyword not found after xref table');
231         }
232         
233         $data = substr($data, 0, $trailerPos);
234         
235         // get Line-Ending
236         preg_match_all("/(\r\n|\n|\r)/", substr($data, 0, 100), $m); // check the first 100 bytes for linebreaks
237
238         $differentLineEndings = count(array_unique($m[0]));
239         if ($differentLineEndings > 1) {
240             $lines = preg_split("/(\r\n|\n|\r)/", $data, -1, PREG_SPLIT_NO_EMPTY);
241         } else {
242             $lines = explode($m[0][1], $data);
243         }
244         
245         $data = $differentLineEndings = $m = null;
246         unset($data, $differentLineEndings, $m);
247         
248         $linesCount = count($lines);
249         
250         $start = 1;
251         
252         for ($i = 0; $i < $linesCount; $i++) {
253             $line = trim($lines[$i]);
254             if ($line) {
255                 $pieces = explode(" ", $line);
256                 $c = count($pieces);
257                 switch($c) {
258                     case 2:
259                         $start = (int)$pieces[0];
260                         $end   = $start+(int)$pieces[1];
261                         if ($end > $result['max_object'])
262                             $result['max_object'] = $end;
263                         break;
264                     case 3:
265                         if (!isset($result['xref'][$start]))
266                             $result['xref'][$start] = array();
267                         
268                         if (!array_key_exists($gen = (int) $pieces[1], $result['xref'][$start])) {
269                                 $result['xref'][$start][$gen] = $pieces[2] == 'n' ? (int) $pieces[0] : null;
270                             }
271                         $start++;
272                         break;
273                     default:
274                         $this->error('Unexpected data in xref table');
275                 }
276             }
277         }
278         
279         $lines = $pieces = $line = $start = $end = $gen = null;
280         unset($lines, $pieces, $line, $start, $end, $gen);
281         
282         fseek($this->f, $o_pos+$trailerPos+7);
283         
284         $c = new pdf_context($this->f);
285             $trailer = $this->pdf_read_value($c);
286             
287             $c = null;
288             unset($c);
289             
290             if (!isset($result['trailer'])) {
291             $result['trailer'] = $trailer;          
292             }
293             
294             if (isset($trailer[1]['/Prev'])) {
295                 $this->pdf_read_xref($result, $trailer[1]['/Prev'][1]);
296             } 
297             
298             $trailer = null;
299             unset($trailer);
300         
301         return true;
302     }
303     
304     /**
305      * Reads an Value
306      *
307      * @param object $c pdf_context
308      * @param string $token a Token
309      * @return mixed
310      */
311     function pdf_read_value(&$c, $token = null) {
312         if (is_null($token)) {
313             $token = $this->pdf_read_token($c);
314         }
315         
316         if ($token === false) {
317             return false;
318         }
319
320         switch ($token) {
321             case        '<':
322                         // This is a hex string.
323                         // Read the value, then the terminator
324
325                 $pos = $c->offset;
326
327                         while(1) {
328
329                     $match = strpos ($c->buffer, '>', $pos);
330                                 
331                                 // If you can't find it, try
332                                 // reading more data from the stream
333
334                                 if ($match === false) {
335                                         if (!$c->increase_length()) {
336                                                 return false;
337                                         } else {
338                                 continue;
339                         }
340                                 }
341
342                                 $result = substr ($c->buffer, $c->offset, $match - $c->offset);
343                                 $c->offset = $match + 1;
344                                 
345                                 return array (PDF_TYPE_HEX, $result);
346                 }
347                 
348                 break;
349                 case    '<<':
350                         // This is a dictionary.
351
352                         $result = array();
353
354                         // Recurse into this function until we reach
355                         // the end of the dictionary.
356                         while (($key = $this->pdf_read_token($c)) !== '>>') {
357                                 if ($key === false) {
358                                         return false;
359                                 }
360                                         
361                                 if (($value =   $this->pdf_read_value($c)) === false) {
362                                         return false;
363                                 }
364                     $result[$key] = $value;
365                         }
366                                 
367                         return array (PDF_TYPE_DICTIONARY, $result);
368
369                 case    '[':
370                         // This is an array.
371
372                         $result = array();
373
374                         // Recurse into this function until we reach
375                         // the end of the array.
376                         while (($token = $this->pdf_read_token($c)) !== ']') {
377                     if ($token === false) {
378                                         return false;
379                                 }
380                                         
381                                 if (($value = $this->pdf_read_value($c, $token)) === false) {
382                         return false;
383                                 }
384                                         
385                                 $result[] = $value;
386                         }
387                         
388                 return array (PDF_TYPE_ARRAY, $result);
389
390                 case    '('             :
391                 // This is a string
392                 $pos = $c->offset;
393                 
394                 $openBrackets = 1;
395                         do {
396                     for (; $openBrackets != 0 && $pos < $c->length; $pos++) {
397                         switch (ord($c->buffer[$pos])) {
398                             case 0x28: // '('
399                                 $openBrackets++;
400                                 break;
401                             case 0x29: // ')'
402                                 $openBrackets--;
403                                 break;
404                             case 0x5C: // backslash
405                                 $pos++;
406                         }
407                     }
408                         } while($openBrackets != 0 && $c->increase_length());
409                         
410                         $result = substr($c->buffer, $c->offset, $pos - $c->offset - 1);
411                         $c->offset = $pos;
412                         
413                         return array (PDF_TYPE_STRING, $result);
414
415                         
416             case "stream":
417                 $o_pos = ftell($c->file)-strlen($c->buffer);
418                         $o_offset = $c->offset;
419                         
420                         $c->reset($startpos = $o_pos + $o_offset);
421                         
422                         $e = 0; // ensure line breaks in front of the stream
423                         if ($c->buffer[0] == chr(10) || $c->buffer[0] == chr(13))
424                                 $e++;
425                         if ($c->buffer[1] == chr(10) && $c->buffer[0] != chr(10))
426                                 $e++;
427                         
428                         if ($this->actual_obj[1][1]['/Length'][0] == PDF_TYPE_OBJREF) {
429                                 $tmp_c = new pdf_context($this->f);
430                                 $tmp_length = $this->pdf_resolve_object($tmp_c,$this->actual_obj[1][1]['/Length']);
431                                 $length = $tmp_length[1][1];
432                         } else {
433                                 $length = $this->actual_obj[1][1]['/Length'][1];        
434                         }
435                         
436                         if ($length > 0) {
437                         $c->reset($startpos+$e,$length);
438                         $v = $c->buffer;
439                         } else {
440                             $v = '';   
441                         }
442                         $c->reset($startpos+$e+$length+9); // 9 = strlen("endstream")
443                         
444                         return array(PDF_TYPE_STREAM, $v);
445                         
446                 case '%':
447                     // this is a comment - just jump over it
448                 $pos = $c->offset;
449                         while(1) {
450                             // PHP 4.3.3 required
451                     #$match = preg_match("/(\r\n|\r|\n)/", $c->buffer, $m, PREG_OFFSET_CAPTURE, $pos);
452                     // alternative
453                     $match = preg_match("/(\r\n|\r|\n)/", substr($c->buffer, $pos), $m);
454                                         if ($match === false) {
455                                         if (!$c->increase_length()) {
456                                                 return false;
457                                         } else {
458                                 continue;
459                         }
460                                 }
461
462                                 // PHP 4.3.3 required
463                     #$c->offset = $m[0][1]+strlen($m[0][0]);
464                                 // alternative
465                     $c->offset = strpos($c->buffer, $m[0], $pos)+strlen($m[0]);
466                                 
467                                 return $this->pdf_read_value($c);
468                 }
469                 
470                 default :
471                 if (is_numeric ($token)) {
472                     // A numeric token. Make sure that
473                                 // it is not part of something else.
474                                 if (($tok2 = $this->pdf_read_token ($c)) !== false) {
475                         if (is_numeric ($tok2)) {
476
477                                                 // Two numeric tokens in a row.
478                                                 // In this case, we're probably in
479                                                 // front of either an object reference
480                                                 // or an object specification.
481                                                 // Determine the case and return the data
482                                                 if (($tok3 = $this->pdf_read_token ($c)) !== false) {
483                                 switch ($tok3) {
484                                                                 case    'obj'   :
485                                         return array (PDF_TYPE_OBJDEC, (int) $token, (int) $tok2);
486                                                                 case    'R'             :
487                                                                         return array (PDF_TYPE_OBJREF, (int) $token, (int) $tok2);
488                                                         }
489                                                         // If we get to this point, that numeric value up
490                                                         // there was just a numeric value. Push the extra
491                                                         // tokens back into the stack and return the value.
492                                                         array_push ($c->stack, $tok3);
493                                                 }
494                                         }
495
496                                         array_push ($c->stack, $tok2);
497                                 }
498
499                                 if ($token === (string)((int)$token))
500                                         return array (PDF_TYPE_NUMERIC, (int)$token);
501                                 else 
502                                         return array (PDF_TYPE_REAL, (float)$token);
503                         } else if ($token == 'true' || $token == 'false') {
504                     return array (PDF_TYPE_BOOLEAN, $token == 'true');
505                         } else {
506
507                     // Just a token. Return it.
508                                 return array (PDF_TYPE_TOKEN, $token);
509                         }
510
511          }
512     }
513     
514     /**
515      * Resolve an object
516      *
517      * @param object $c pdf_context
518      * @param array $obj_spec The object-data
519      * @param boolean $encapsulate Must set to true, cause the parsing and fpdi use this method only without this para
520      */
521     function pdf_resolve_object(&$c, $obj_spec, $encapsulate = true) {
522         // Exit if we get invalid data
523         if (!is_array($obj_spec)) {
524             return false;
525         }
526
527         if ($obj_spec[0] == PDF_TYPE_OBJREF) {
528
529                 // This is a reference, resolve it
530                 if (isset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]])) {
531
532                         // Save current file position
533                         // This is needed if you want to resolve
534                         // references while you're reading another object
535                         // (e.g.: if you need to determine the length
536                         // of a stream)
537
538                         $old_pos = ftell($c->file);
539
540                         // Reposition the file pointer and
541                         // load the object header.
542                                 
543                         $c->reset($this->xref['xref'][$obj_spec[1]][$obj_spec[2]]);
544
545                         $header = $this->pdf_read_value($c,null,true);
546
547                         if ($header[0] != PDF_TYPE_OBJDEC || $header[1] != $obj_spec[1] || $header[2] != $obj_spec[2]) {
548                                 $this->error("Unable to find object ({$obj_spec[1]}, {$obj_spec[2]}) at expected location");
549                         }
550
551                         // If we're being asked to store all the information
552                         // about the object, we add the object ID and generation
553                         // number for later use
554                                 $this->actual_obj =& $result;
555                         if ($encapsulate) {
556                                 $result = array (
557                                         PDF_TYPE_OBJECT,
558                                         'obj' => $obj_spec[1],
559                                         'gen' => $obj_spec[2]
560                                 );
561                         } else {
562                                 $result = array();
563                         }
564
565                         // Now simply read the object data until
566                         // we encounter an end-of-object marker
567                         while(1) {
568                     $value = $this->pdf_read_value($c);
569                                         if ($value === false || count($result) > 4) {
570                                                 // in this case the parser coudn't find an endobj so we break here
571                                                 break;
572                                 }
573
574                                 if ($value[0] == PDF_TYPE_TOKEN && $value[1] === 'endobj') {
575                                         break;
576                                 }
577
578                     $result[] = $value;
579                         }
580
581                         $c->reset($old_pos);
582
583                 if (isset($result[2][0]) && $result[2][0] == PDF_TYPE_STREAM) {
584                     $result[0] = PDF_TYPE_STREAM;
585                 }
586
587                         return $result;
588                 }
589         } else {
590                 return $obj_spec;
591         }
592     }
593
594     
595     
596     /**
597      * Reads a token from the file
598      *
599      * @param object $c pdf_context
600      * @return mixed
601      */
602     function pdf_read_token(&$c)
603     {
604         // If there is a token available
605         // on the stack, pop it out and
606         // return it.
607
608         if (count($c->stack)) {
609                 return array_pop($c->stack);
610         }
611
612         // Strip away any whitespace
613
614         do {
615                 if (!$c->ensure_content()) {
616                         return false;
617                 }
618                 $c->offset += _strspn($c->buffer, " \n\r\t", $c->offset);
619         } while ($c->offset >= $c->length - 1);
620
621         // Get the first character in the stream
622
623         $char = $c->buffer[$c->offset++];
624
625         switch ($char) {
626
627                 case '['        :
628                 case ']'        :
629                 case '('        :
630                 case ')'        :
631
632                         // This is either an array or literal string
633                         // delimiter, Return it
634
635                         return $char;
636
637                 case '<'        :
638                 case '>'        :
639
640                         // This could either be a hex string or
641                         // dictionary delimiter. Determine the
642                         // appropriate case and return the token
643
644                         if ($c->buffer[$c->offset] == $char) {
645                                 if (!$c->ensure_content()) {
646                                     return false;
647                                 }
648                                 $c->offset++;
649                                 return $char . $char;
650                         } else {
651                                 return $char;
652                         }
653
654                 default         :
655
656                         // This is "another" type of token (probably
657                         // a dictionary entry or a numeric value)
658                         // Find the end and return it.
659
660                         if (!$c->ensure_content()) {
661                                 return false;
662                         }
663
664                         while(1) {
665
666                                 // Determine the length of the token
667
668                                 $pos = _strcspn($c->buffer, " []<>()\r\n\t/", $c->offset);
669                                 if ($c->offset + $pos <= $c->length - 1) {
670                                         break;
671                                 } else {
672                                         // If the script reaches this point,
673                                         // the token may span beyond the end
674                                         // of the current buffer. Therefore,
675                                         // we increase the size of the buffer
676                                         // and try again--just to be safe.
677
678                                         $c->increase_length();
679                                 }
680                         }
681
682                         $result = substr($c->buffer, $c->offset - 1, $pos + 1);
683
684                         $c->offset += $pos;
685                         return $result;
686         }
687     }
688
689         
690 }