PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/include/fpdi/pdf_parser.php

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