Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

   1  <?php
   2  
   3  /**
   4   * This file is part of FPDI
   5   *
   6   * @package   setasign\Fpdi
   7   * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
   8   * @license   http://opensource.org/licenses/mit-license The MIT License
   9   */
  10  
  11  namespace setasign\Fpdi\PdfParser\CrossReference;
  12  
  13  use setasign\Fpdi\PdfParser\PdfParser;
  14  use setasign\Fpdi\PdfParser\StreamReader;
  15  
  16  /**
  17   * Class LineReader
  18   *
  19   * This reader class read all cross-reference entries in a single run.
  20   * It supports reading cross-references with e.g. invalid data (e.g. entries with a length < or > 20 bytes).
  21   */
  22  class LineReader extends AbstractReader implements ReaderInterface
  23  {
  24      /**
  25       * The object offsets.
  26       *
  27       * @var array
  28       */
  29      protected $offsets;
  30  
  31      /**
  32       * LineReader constructor.
  33       *
  34       * @param PdfParser $parser
  35       * @throws CrossReferenceException
  36       */
  37      public function __construct(PdfParser $parser)
  38      {
  39          $this->read($this->extract($parser->getStreamReader()));
  40          parent::__construct($parser);
  41      }
  42  
  43      /**
  44       * @inheritdoc
  45       * @return int|false
  46       */
  47      public function getOffsetFor($objectNumber)
  48      {
  49          if (isset($this->offsets[$objectNumber])) {
  50              return $this->offsets[$objectNumber][0];
  51          }
  52  
  53          return false;
  54      }
  55  
  56      /**
  57       * Get all found offsets.
  58       *
  59       * @return array
  60       */
  61      public function getOffsets()
  62      {
  63          return $this->offsets;
  64      }
  65  
  66      /**
  67       * Extracts the cross reference data from the stream reader.
  68       *
  69       * @param StreamReader $reader
  70       * @return string
  71       * @throws CrossReferenceException
  72       */
  73      protected function extract(StreamReader $reader)
  74      {
  75          $bytesPerCycle = 100;
  76          $reader->reset(null, $bytesPerCycle);
  77  
  78          $cycles = 0;
  79          do {
  80              // 6 = length of "trailer" - 1
  81              $pos = \max(($bytesPerCycle * $cycles) - 6, 0);
  82              $trailerPos = \strpos($reader->getBuffer(false), 'trailer', $pos);
  83              $cycles++;
  84          } while ($trailerPos === false && $reader->increaseLength($bytesPerCycle) !== false);
  85  
  86          if ($trailerPos === false) {
  87              throw new CrossReferenceException(
  88                  'Unexpected end of cross reference. "trailer"-keyword not found.',
  89                  CrossReferenceException::NO_TRAILER_FOUND
  90              );
  91          }
  92  
  93          $xrefContent = \substr($reader->getBuffer(false), 0, $trailerPos);
  94          $reader->reset($reader->getPosition() + $trailerPos);
  95  
  96          return $xrefContent;
  97      }
  98  
  99      /**
 100       * Read the cross-reference entries.
 101       *
 102       * @param string $xrefContent
 103       * @throws CrossReferenceException
 104       */
 105      protected function read($xrefContent)
 106      {
 107          // get eol markers in the first 100 bytes
 108          \preg_match_all("/(\r\n|\n|\r)/", \substr($xrefContent, 0, 100), $m);
 109  
 110          if (\count($m[0]) === 0) {
 111              throw new CrossReferenceException(
 112                  'No data found in cross-reference.',
 113                  CrossReferenceException::INVALID_DATA
 114              );
 115          }
 116  
 117          // count(array_count_values()) is faster then count(array_unique())
 118          // @see https://github.com/symfony/symfony/pull/23731
 119          // can be reverted for php7.2
 120          $differentLineEndings = \count(\array_count_values($m[0]));
 121          if ($differentLineEndings > 1) {
 122              $lines = \preg_split("/(\r\n|\n|\r)/", $xrefContent, -1, PREG_SPLIT_NO_EMPTY);
 123          } else {
 124              $lines = \explode($m[0][0], $xrefContent);
 125          }
 126  
 127          unset($differentLineEndings, $m);
 128          if (!\is_array($lines)) {
 129              $this->offsets = [];
 130              return;
 131          }
 132  
 133          $start = 0;
 134          $offsets = [];
 135  
 136          // trim all lines and remove empty lines
 137          $lines = \array_filter(\array_map('\trim', $lines));
 138          foreach ($lines as $line) {
 139              $pieces = \explode(' ', $line);
 140  
 141              switch (\count($pieces)) {
 142                  case 2:
 143                      $start = (int) $pieces[0];
 144                      break;
 145  
 146                  case 3:
 147                      switch ($pieces[2]) {
 148                          case 'n':
 149                              $offsets[$start] = [(int) $pieces[0], (int) $pieces[1]];
 150                              $start++;
 151                              break 2;
 152                          case 'f':
 153                              $start++;
 154                              break 2;
 155                      }
 156                      // fall through if pieces doesn't match
 157  
 158                  default:
 159                      throw new CrossReferenceException(
 160                          \sprintf('Unexpected data in xref table (%s)', \implode(' ', $pieces)),
 161                          CrossReferenceException::INVALID_DATA
 162                      );
 163              }
 164          }
 165  
 166          $this->offsets = $offsets;
 167      }
 168  }