Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   1  <?php
   2  /**
   3   * This file is part of FPDI
   4   *
   5   * @package   setasign\Fpdi
   6   * @copyright Copyright (c) 2019 Setasign - Jan Slabon (https://www.setasign.com)
   7   * @license   http://opensource.org/licenses/mit-license The MIT License
   8   */
   9  
  10  namespace setasign\Fpdi\PdfParser;
  11  
  12  /**
  13   * A stream reader class
  14   *
  15   * @package setasign\Fpdi\PdfParser
  16   */
  17  class StreamReader
  18  {
  19      /**
  20       * Creates a stream reader instance by a string value.
  21       *
  22       * @param string $content
  23       * @param int $maxMemory
  24       * @return StreamReader
  25       */
  26      public static function createByString($content, $maxMemory = 2097152)
  27      {
  28          $h = \fopen('php://temp/maxmemory:' . ((int) $maxMemory), 'r+b');
  29          \fwrite($h, $content);
  30          \rewind($h);
  31  
  32          return new self($h, true);
  33      }
  34  
  35      /**
  36       * Creates a stream reader instance by a filename.
  37       *
  38       * @param string $filename
  39       * @return StreamReader
  40       */
  41      public static function createByFile($filename)
  42      {
  43          $h = \fopen($filename, 'rb');
  44          return new self($h, true);
  45      }
  46  
  47      /**
  48       * Defines whether the stream should be closed when the stream reader instance is deconstructed or not.
  49       *
  50       * @var bool
  51       */
  52      protected $closeStream;
  53  
  54      /**
  55       * The stream resource.
  56       *
  57       * @var resource
  58       */
  59      protected $stream;
  60  
  61      /**
  62       * The byte-offset position in the stream.
  63       *
  64       * @var int
  65       */
  66      protected $position;
  67  
  68      /**
  69       * The byte-offset position in the buffer.
  70       *
  71       * @var int
  72       */
  73      protected $offset;
  74  
  75      /**
  76       * The buffer length.
  77       *
  78       * @var int
  79       */
  80      protected $bufferLength;
  81  
  82      /**
  83       * The total length of the stream.
  84       *
  85       * @var int
  86       */
  87      protected $totalLength;
  88  
  89      /**
  90       * The buffer.
  91       *
  92       * @var string
  93       */
  94      protected $buffer;
  95  
  96      /**
  97       * StreamReader constructor.
  98       *
  99       * @param resource $stream
 100       * @param bool $closeStream Defines whether to close the stream resource if the instance is destructed or not.
 101       */
 102      public function __construct($stream, $closeStream = false)
 103      {
 104          if (!\is_resource($stream)) {
 105              throw new \InvalidArgumentException(
 106                  'No stream given.'
 107              );
 108          }
 109  
 110          $metaData = \stream_get_meta_data($stream);
 111          if (!$metaData['seekable']) {
 112              throw new \InvalidArgumentException(
 113                  'Given stream is not seekable!'
 114              );
 115          }
 116  
 117          $this->stream = $stream;
 118          $this->closeStream = $closeStream;
 119          $this->reset();
 120      }
 121  
 122      /**
 123       * The destructor.
 124       */
 125      public function __destruct()
 126      {
 127          $this->cleanUp();
 128      }
 129  
 130      /**
 131       * Closes the file handle.
 132       */
 133      public function cleanUp()
 134      {
 135          if ($this->closeStream && is_resource($this->stream)) {
 136              \fclose($this->stream);
 137          }
 138      }
 139  
 140      /**
 141       * Returns the byte length of the buffer.
 142       *
 143       * @param bool $atOffset
 144       * @return int
 145       */
 146      public function getBufferLength($atOffset = false)
 147      {
 148          if ($atOffset === false) {
 149              return $this->bufferLength;
 150          }
 151  
 152          return $this->bufferLength - $this->offset;
 153      }
 154  
 155      /**
 156       * Get the current position in the stream.
 157       *
 158       * @return int
 159       */
 160      public function getPosition()
 161      {
 162          return $this->position;
 163      }
 164  
 165      /**
 166       * Returns the current buffer.
 167       *
 168       * @param bool $atOffset
 169       * @return string
 170       */
 171      public function getBuffer($atOffset = true)
 172      {
 173          if ($atOffset === false) {
 174              return $this->buffer;
 175          }
 176  
 177          $string = \substr($this->buffer, $this->offset);
 178  
 179          return (string) $string;
 180      }
 181  
 182      /**
 183       * Gets a byte at a specific position in the buffer.
 184       *
 185       * If the position is invalid the method will return false.
 186       *
 187       * If the $position parameter is set to null the value of $this->offset will be used.
 188       *
 189       * @param int|null $position
 190       * @return string|bool
 191       */
 192      public function getByte($position = null)
 193      {
 194          $position = (int) ($position !== null ? $position : $this->offset);
 195          if ($position >= $this->bufferLength &&
 196              (!$this->increaseLength() || $position >= $this->bufferLength)
 197          ) {
 198              return false;
 199          }
 200  
 201          return $this->buffer[$position];
 202      }
 203  
 204      /**
 205       * Returns a byte at a specific position, and set the offset to the next byte position.
 206       *
 207       * If the position is invalid the method will return false.
 208       *
 209       * If the $position parameter is set to null the value of $this->offset will be used.
 210       *
 211       * @param int|null $position
 212       * @return string|bool
 213       */
 214      public function readByte($position = null)
 215      {
 216          if ($position !== null) {
 217              $position = (int) $position;
 218              // check if needed bytes are available in the current buffer
 219              if (!($position >= $this->position && $position < $this->position + $this->bufferLength)) {
 220                  $this->reset($position);
 221                  $offset = $this->offset;
 222              } else {
 223                  $offset = $position - $this->position;
 224              }
 225          } else {
 226              $offset = $this->offset;
 227          }
 228  
 229          if ($offset >= $this->bufferLength &&
 230              ((!$this->increaseLength()) || $offset >= $this->bufferLength)
 231          ) {
 232              return false;
 233          }
 234  
 235          $this->offset = $offset + 1;
 236          return $this->buffer[$offset];
 237      }
 238  
 239      /**
 240       * Read bytes from the current or a specific offset position and set the internal pointer to the next byte.
 241       *
 242       * If the position is invalid the method will return false.
 243       *
 244       * If the $position parameter is set to null the value of $this->offset will be used.
 245       *
 246       * @param int $length
 247       * @param int|null $position
 248       * @return string
 249       */
 250      public function readBytes($length, $position = null)
 251      {
 252          $length = (int) $length;
 253          if ($position !== null) {
 254              // check if needed bytes are available in the current buffer
 255              if (!($position >= $this->position && $position < $this->position + $this->bufferLength)) {
 256                  $this->reset($position, $length);
 257                  $offset = $this->offset;
 258              } else {
 259                  $offset = $position - $this->position;
 260              }
 261          } else {
 262              $offset = $this->offset;
 263          }
 264  
 265          if (($offset + $length) > $this->bufferLength &&
 266              ((!$this->increaseLength($length)) || ($offset + $length) > $this->bufferLength)
 267          ) {
 268              return false;
 269          }
 270  
 271          $bytes = \substr($this->buffer, $offset, $length);
 272          $this->offset = $offset + $length;
 273  
 274          return $bytes;
 275      }
 276  
 277      /**
 278       * Read a line from the current position.
 279       *
 280       * @param int $length
 281       * @return string|bool
 282       */
 283      public function readLine($length = 1024)
 284      {
 285          if ($this->ensureContent() === false) {
 286              return false;
 287          }
 288  
 289          $line = '';
 290          while ($this->ensureContent()) {
 291              $char = $this->readByte();
 292  
 293              if ($char === "\n") {
 294                  break;
 295              }
 296  
 297              if ($char === "\r") {
 298                  if ($this->getByte() === "\n") {
 299                      $this->addOffset(1);
 300                  }
 301                  break;
 302              }
 303  
 304              $line .= $char;
 305  
 306              if (\strlen($line) >= $length) {
 307                  break;
 308              }
 309          }
 310  
 311          return $line;
 312      }
 313  
 314      /**
 315       * Set the offset position in the current buffer.
 316       *
 317       * @param int $offset
 318       */
 319      public function setOffset($offset)
 320      {
 321          if ($offset > $this->bufferLength || $offset < 0) {
 322              throw new \OutOfRangeException(
 323                  \sprintf('Offset (%s) out of range (length: %s)', $offset, $this->bufferLength)
 324              );
 325          }
 326  
 327          $this->offset = (int) $offset;
 328      }
 329  
 330      /**
 331       * Returns the current offset in the current buffer.
 332       *
 333       * @return int
 334       */
 335      public function getOffset()
 336      {
 337          return $this->offset;
 338      }
 339  
 340      /**
 341       * Add an offset to the current offset.
 342       *
 343       * @param int $offset
 344       */
 345      public function addOffset($offset)
 346      {
 347          $this->setOffset($this->offset + $offset);
 348      }
 349  
 350      /**
 351       * Make sure that there is at least one character beyond the current offset in the buffer.
 352       *
 353       * @return bool
 354       */
 355      public function ensureContent()
 356      {
 357          while ($this->offset >= $this->bufferLength) {
 358              if (!$this->increaseLength()) {
 359                  return false;
 360              }
 361          }
 362          return true;
 363      }
 364  
 365      /**
 366       * Returns the stream.
 367       *
 368       * @return resource
 369       */
 370      public function getStream()
 371      {
 372          return $this->stream;
 373      }
 374  
 375      /**
 376       * Gets the total available length.
 377       *
 378       * @return int
 379       */
 380      public function getTotalLength()
 381      {
 382          if ($this->totalLength === null) {
 383              $stat = \fstat($this->stream);
 384              $this->totalLength = $stat['size'];
 385          }
 386  
 387          return $this->totalLength;
 388      }
 389  
 390      /**
 391       * Resets the buffer to a position and re-read the buffer with the given length.
 392       *
 393       * If the $pos parameter is negative the start buffer position will be the $pos'th position from
 394       * the end of the file.
 395       *
 396       * If the $pos parameter is negative and the absolute value is bigger then the totalLength of
 397       * the file $pos will set to zero.
 398       *
 399       * @param int|null $pos Start position of the new buffer
 400       * @param int $length Length of the new buffer. Mustn't be negative
 401       */
 402      public function reset($pos = 0, $length = 200)
 403      {
 404          if ($pos === null) {
 405              $pos = $this->position + $this->offset;
 406          } elseif ($pos < 0) {
 407              $pos = \max(0, $this->getTotalLength() + $pos);
 408          }
 409  
 410          \fseek($this->stream, $pos);
 411  
 412          $this->position = $pos;
 413          $this->buffer = $length > 0 ? \fread($this->stream, $length) : '';
 414          $this->bufferLength = \strlen($this->buffer);
 415          $this->offset = 0;
 416  
 417          // If a stream wrapper is in use it is possible that
 418          // length values > 8096 will be ignored, so use the
 419          // increaseLength()-method to correct that behavior
 420          if ($this->bufferLength < $length && $this->increaseLength($length - $this->bufferLength)) {
 421              // increaseLength parameter is $minLength, so cut to have only the required bytes in the buffer
 422              $this->buffer = \substr($this->buffer, 0, $length);
 423              $this->bufferLength = \strlen($this->buffer);
 424          }
 425      }
 426  
 427      /**
 428       * Ensures bytes in the buffer with a specific length and location in the file.
 429       *
 430       * @param int $pos
 431       * @param int $length
 432       * @see reset()
 433       */
 434      public function ensure($pos, $length)
 435      {
 436          if ($pos >= $this->position
 437              && $pos < ($this->position + $this->bufferLength)
 438              && ($this->position + $this->bufferLength) >= ($pos + $length)
 439          ) {
 440              $this->offset = $pos - $this->position;
 441          } else {
 442              $this->reset($pos, $length);
 443          }
 444      }
 445  
 446      /**
 447       * Forcefully read more data into the buffer.
 448       *
 449       * @param int $minLength
 450       * @return bool Returns false if the stream reaches the end
 451       */
 452      public function increaseLength($minLength = 100)
 453      {
 454          $length = \max($minLength, 100);
 455  
 456          if (\feof($this->stream) || $this->getTotalLength() === $this->position + $this->bufferLength) {
 457              return false;
 458          }
 459  
 460          $newLength = $this->bufferLength + $length;
 461          do {
 462              $this->buffer .= \fread($this->stream, $newLength - $this->bufferLength);
 463              $this->bufferLength = \strlen($this->buffer);
 464          } while (($this->bufferLength !== $newLength) && !\feof($this->stream));
 465  
 466          return true;
 467      }
 468  }