LimitStream.php 4.1 KB

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