vendor/setasign/fpdi/src/PdfParser/StreamReader.php line 40

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