| 1 | efrain | 1 | <?php
 | 
        
           |  |  | 2 |   | 
        
           |  |  | 3 | declare(strict_types=1);
 | 
        
           |  |  | 4 |   | 
        
           |  |  | 5 | namespace GuzzleHttp\Psr7;
 | 
        
           |  |  | 6 |   | 
        
           |  |  | 7 | use Psr\Http\Message\StreamInterface;
 | 
        
           |  |  | 8 |   | 
        
           |  |  | 9 | /**
 | 
        
           |  |  | 10 |  * Stream decorator that can cache previously read bytes from a sequentially
 | 
        
           |  |  | 11 |  * read stream.
 | 
        
           |  |  | 12 |  */
 | 
        
           |  |  | 13 | final class CachingStream implements StreamInterface
 | 
        
           |  |  | 14 | {
 | 
        
           |  |  | 15 |     use StreamDecoratorTrait;
 | 
        
           |  |  | 16 |   | 
        
           |  |  | 17 |     /** @var StreamInterface Stream being wrapped */
 | 
        
           |  |  | 18 |     private $remoteStream;
 | 
        
           |  |  | 19 |   | 
        
           |  |  | 20 |     /** @var int Number of bytes to skip reading due to a write on the buffer */
 | 
        
           |  |  | 21 |     private $skipReadBytes = 0;
 | 
        
           |  |  | 22 |   | 
        
           |  |  | 23 |     /**
 | 
        
           |  |  | 24 |      * @var StreamInterface
 | 
        
           |  |  | 25 |      */
 | 
        
           |  |  | 26 |     private $stream;
 | 
        
           |  |  | 27 |   | 
        
           |  |  | 28 |     /**
 | 
        
           |  |  | 29 |      * We will treat the buffer object as the body of the stream
 | 
        
           |  |  | 30 |      *
 | 
        
           |  |  | 31 |      * @param StreamInterface $stream Stream to cache. The cursor is assumed to be at the beginning of the stream.
 | 
        
           |  |  | 32 |      * @param StreamInterface $target Optionally specify where data is cached
 | 
        
           |  |  | 33 |      */
 | 
        
           |  |  | 34 |     public function __construct(
 | 
        
           |  |  | 35 |         StreamInterface $stream,
 | 
        
           | 1441 | ariadna | 36 |         ?StreamInterface $target = null
 | 
        
           | 1 | efrain | 37 |     ) {
 | 
        
           |  |  | 38 |         $this->remoteStream = $stream;
 | 
        
           |  |  | 39 |         $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+'));
 | 
        
           |  |  | 40 |     }
 | 
        
           |  |  | 41 |   | 
        
           |  |  | 42 |     public function getSize(): ?int
 | 
        
           |  |  | 43 |     {
 | 
        
           |  |  | 44 |         $remoteSize = $this->remoteStream->getSize();
 | 
        
           |  |  | 45 |   | 
        
           |  |  | 46 |         if (null === $remoteSize) {
 | 
        
           |  |  | 47 |             return null;
 | 
        
           |  |  | 48 |         }
 | 
        
           |  |  | 49 |   | 
        
           |  |  | 50 |         return max($this->stream->getSize(), $remoteSize);
 | 
        
           |  |  | 51 |     }
 | 
        
           |  |  | 52 |   | 
        
           |  |  | 53 |     public function rewind(): void
 | 
        
           |  |  | 54 |     {
 | 
        
           |  |  | 55 |         $this->seek(0);
 | 
        
           |  |  | 56 |     }
 | 
        
           |  |  | 57 |   | 
        
           |  |  | 58 |     public function seek($offset, $whence = SEEK_SET): void
 | 
        
           |  |  | 59 |     {
 | 
        
           |  |  | 60 |         if ($whence === SEEK_SET) {
 | 
        
           |  |  | 61 |             $byte = $offset;
 | 
        
           |  |  | 62 |         } elseif ($whence === SEEK_CUR) {
 | 
        
           |  |  | 63 |             $byte = $offset + $this->tell();
 | 
        
           |  |  | 64 |         } elseif ($whence === SEEK_END) {
 | 
        
           |  |  | 65 |             $size = $this->remoteStream->getSize();
 | 
        
           |  |  | 66 |             if ($size === null) {
 | 
        
           |  |  | 67 |                 $size = $this->cacheEntireStream();
 | 
        
           |  |  | 68 |             }
 | 
        
           |  |  | 69 |             $byte = $size + $offset;
 | 
        
           |  |  | 70 |         } else {
 | 
        
           |  |  | 71 |             throw new \InvalidArgumentException('Invalid whence');
 | 
        
           |  |  | 72 |         }
 | 
        
           |  |  | 73 |   | 
        
           |  |  | 74 |         $diff = $byte - $this->stream->getSize();
 | 
        
           |  |  | 75 |   | 
        
           |  |  | 76 |         if ($diff > 0) {
 | 
        
           |  |  | 77 |             // Read the remoteStream until we have read in at least the amount
 | 
        
           |  |  | 78 |             // of bytes requested, or we reach the end of the file.
 | 
        
           |  |  | 79 |             while ($diff > 0 && !$this->remoteStream->eof()) {
 | 
        
           |  |  | 80 |                 $this->read($diff);
 | 
        
           |  |  | 81 |                 $diff = $byte - $this->stream->getSize();
 | 
        
           |  |  | 82 |             }
 | 
        
           |  |  | 83 |         } else {
 | 
        
           |  |  | 84 |             // We can just do a normal seek since we've already seen this byte.
 | 
        
           |  |  | 85 |             $this->stream->seek($byte);
 | 
        
           |  |  | 86 |         }
 | 
        
           |  |  | 87 |     }
 | 
        
           |  |  | 88 |   | 
        
           |  |  | 89 |     public function read($length): string
 | 
        
           |  |  | 90 |     {
 | 
        
           |  |  | 91 |         // Perform a regular read on any previously read data from the buffer
 | 
        
           |  |  | 92 |         $data = $this->stream->read($length);
 | 
        
           |  |  | 93 |         $remaining = $length - strlen($data);
 | 
        
           |  |  | 94 |   | 
        
           |  |  | 95 |         // More data was requested so read from the remote stream
 | 
        
           |  |  | 96 |         if ($remaining) {
 | 
        
           |  |  | 97 |             // If data was written to the buffer in a position that would have
 | 
        
           |  |  | 98 |             // been filled from the remote stream, then we must skip bytes on
 | 
        
           |  |  | 99 |             // the remote stream to emulate overwriting bytes from that
 | 
        
           |  |  | 100 |             // position. This mimics the behavior of other PHP stream wrappers.
 | 
        
           |  |  | 101 |             $remoteData = $this->remoteStream->read(
 | 
        
           |  |  | 102 |                 $remaining + $this->skipReadBytes
 | 
        
           |  |  | 103 |             );
 | 
        
           |  |  | 104 |   | 
        
           |  |  | 105 |             if ($this->skipReadBytes) {
 | 
        
           |  |  | 106 |                 $len = strlen($remoteData);
 | 
        
           |  |  | 107 |                 $remoteData = substr($remoteData, $this->skipReadBytes);
 | 
        
           |  |  | 108 |                 $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
 | 
        
           |  |  | 109 |             }
 | 
        
           |  |  | 110 |   | 
        
           |  |  | 111 |             $data .= $remoteData;
 | 
        
           |  |  | 112 |             $this->stream->write($remoteData);
 | 
        
           |  |  | 113 |         }
 | 
        
           |  |  | 114 |   | 
        
           |  |  | 115 |         return $data;
 | 
        
           |  |  | 116 |     }
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 |     public function write($string): int
 | 
        
           |  |  | 119 |     {
 | 
        
           |  |  | 120 |         // When appending to the end of the currently read stream, you'll want
 | 
        
           |  |  | 121 |         // to skip bytes from being read from the remote stream to emulate
 | 
        
           |  |  | 122 |         // other stream wrappers. Basically replacing bytes of data of a fixed
 | 
        
           |  |  | 123 |         // length.
 | 
        
           |  |  | 124 |         $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
 | 
        
           |  |  | 125 |         if ($overflow > 0) {
 | 
        
           |  |  | 126 |             $this->skipReadBytes += $overflow;
 | 
        
           |  |  | 127 |         }
 | 
        
           |  |  | 128 |   | 
        
           |  |  | 129 |         return $this->stream->write($string);
 | 
        
           |  |  | 130 |     }
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |     public function eof(): bool
 | 
        
           |  |  | 133 |     {
 | 
        
           |  |  | 134 |         return $this->stream->eof() && $this->remoteStream->eof();
 | 
        
           |  |  | 135 |     }
 | 
        
           |  |  | 136 |   | 
        
           |  |  | 137 |     /**
 | 
        
           |  |  | 138 |      * Close both the remote stream and buffer stream
 | 
        
           |  |  | 139 |      */
 | 
        
           |  |  | 140 |     public function close(): void
 | 
        
           |  |  | 141 |     {
 | 
        
           |  |  | 142 |         $this->remoteStream->close();
 | 
        
           |  |  | 143 |         $this->stream->close();
 | 
        
           |  |  | 144 |     }
 | 
        
           |  |  | 145 |   | 
        
           |  |  | 146 |     private function cacheEntireStream(): int
 | 
        
           |  |  | 147 |     {
 | 
        
           |  |  | 148 |         $target = new FnStream(['write' => 'strlen']);
 | 
        
           |  |  | 149 |         Utils::copyToStream($this, $target);
 | 
        
           |  |  | 150 |   | 
        
           |  |  | 151 |         return $this->tell();
 | 
        
           |  |  | 152 |     }
 | 
        
           |  |  | 153 | }
 |