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