1<?php 2 3namespace GuzzleHttp\Psr7; 4 5use Psr\Http\Message\StreamInterface; 6 7/** 8 * Decorator used to return only a subset of a stream. 9 * 10 * @final 11 */ 12class LimitStream implements StreamInterface 13{ 14 use StreamDecoratorTrait; 15 16 /** @var int Offset to start reading from */ 17 private $offset; 18 19 /** @var int Limit the number of bytes that can be read */ 20 private $limit; 21 22 /** 23 * @param StreamInterface $stream Stream to wrap 24 * @param int $limit Total number of bytes to allow to be read 25 * from the stream. Pass -1 for no limit. 26 * @param int $offset Position to seek to before reading (only 27 * works on seekable streams). 28 */ 29 public function __construct( 30 StreamInterface $stream, 31 $limit = -1, 32 $offset = 0 33 ) { 34 $this->stream = $stream; 35 $this->setLimit($limit); 36 $this->setOffset($offset); 37 } 38 39 public function eof() 40 { 41 // Always return true if the underlying stream is EOF 42 if ($this->stream->eof()) { 43 return true; 44 } 45 46 // No limit and the underlying stream is not at EOF 47 if ($this->limit == -1) { 48 return false; 49 } 50 51 return $this->stream->tell() >= $this->offset + $this->limit; 52 } 53 54 /** 55 * Returns the size of the limited subset of data 56 * {@inheritdoc} 57 */ 58 public function getSize() 59 { 60 if (null === ($length = $this->stream->getSize())) { 61 return null; 62 } elseif ($this->limit == -1) { 63 return $length - $this->offset; 64 } else { 65 return min($this->limit, $length - $this->offset); 66 } 67 } 68 69 /** 70 * Allow for a bounded seek on the read limited stream 71 * {@inheritdoc} 72 */ 73 public function seek($offset, $whence = SEEK_SET) 74 { 75 if ($whence !== SEEK_SET || $offset < 0) { 76 throw new \RuntimeException(sprintf( 77 'Cannot seek to offset %s with whence %s', 78 $offset, 79 $whence 80 )); 81 } 82 83 $offset += $this->offset; 84 85 if ($this->limit !== -1) { 86 if ($offset > $this->offset + $this->limit) { 87 $offset = $this->offset + $this->limit; 88 } 89 } 90 91 $this->stream->seek($offset); 92 } 93 94 /** 95 * Give a relative tell() 96 * {@inheritdoc} 97 */ 98 public function tell() 99 { 100 return $this->stream->tell() - $this->offset; 101 } 102 103 /** 104 * Set the offset to start limiting from 105 * 106 * @param int $offset Offset to seek to and begin byte limiting from 107 * 108 * @throws \RuntimeException if the stream cannot be seeked. 109 */ 110 public function setOffset($offset) 111 { 112 $current = $this->stream->tell(); 113 114 if ($current !== $offset) { 115 // If the stream cannot seek to the offset position, then read to it 116 if ($this->stream->isSeekable()) { 117 $this->stream->seek($offset); 118 } elseif ($current > $offset) { 119 throw new \RuntimeException("Could not seek to stream offset $offset"); 120 } else { 121 $this->stream->read($offset - $current); 122 } 123 } 124 125 $this->offset = $offset; 126 } 127 128 /** 129 * Set the limit of bytes that the decorator allows to be read from the 130 * stream. 131 * 132 * @param int $limit Number of bytes to allow to be read from the stream. 133 * Use -1 for no limit. 134 */ 135 public function setLimit($limit) 136 { 137 $this->limit = $limit; 138 } 139 140 public function read($length) 141 { 142 if ($this->limit == -1) { 143 return $this->stream->read($length); 144 } 145 146 // Check if the current position is less than the total allowed 147 // bytes + original offset 148 $remaining = ($this->offset + $this->limit) - $this->stream->tell(); 149 if ($remaining > 0) { 150 // Only return the amount of requested data, ensuring that the byte 151 // limit is not exceeded 152 return $this->stream->read(min($remaining, $length)); 153 } 154 155 return ''; 156 } 157} 158