LimitStream.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <?php
  2. namespace GuzzleHttp\Psr7;
  3. use Psr\Http\Message\StreamInterface;
  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 $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. return $this->stream->tell() >= $this->offset + $this->limit;
  41. }
  42. /**
  43. * Returns the size of the limited subset of data
  44. * {@inheritdoc}
  45. */
  46. public function getSize()
  47. {
  48. if (null === ($length = $this->stream->getSize())) {
  49. return null;
  50. } elseif ($this->limit == -1) {
  51. return $length - $this->offset;
  52. } else {
  53. return min($this->limit, $length - $this->offset);
  54. }
  55. }
  56. /**
  57. * Allow for a bounded seek on the read limited stream
  58. * {@inheritdoc}
  59. */
  60. public function seek($offset, $whence = SEEK_SET)
  61. {
  62. if ($whence !== SEEK_SET || $offset < 0) {
  63. throw new \RuntimeException(sprintf(
  64. 'Cannot seek to offset %s with whence %s',
  65. $offset,
  66. $whence
  67. ));
  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. $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. * @throws \RuntimeException if the stream cannot be seeked.
  91. */
  92. public function setOffset($offset)
  93. {
  94. $current = $this->stream->tell();
  95. if ($current !== $offset) {
  96. // If the stream cannot seek to the offset position, then read to it
  97. if ($this->stream->isSeekable()) {
  98. $this->stream->seek($offset);
  99. } elseif ($current > $offset) {
  100. throw new \RuntimeException("Could not seek to stream offset $offset");
  101. } else {
  102. $this->stream->read($offset - $current);
  103. }
  104. }
  105. $this->offset = $offset;
  106. }
  107. /**
  108. * Set the limit of bytes that the decorator allows to be read from the
  109. * stream.
  110. *
  111. * @param int $limit Number of bytes to allow to be read from the stream.
  112. * Use -1 for no limit.
  113. */
  114. public function setLimit($limit)
  115. {
  116. $this->limit = $limit;
  117. }
  118. public function read($length)
  119. {
  120. if ($this->limit == -1) {
  121. return $this->stream->read($length);
  122. }
  123. // Check if the current position is less than the total allowed
  124. // bytes + original offset
  125. $remaining = ($this->offset + $this->limit) - $this->stream->tell();
  126. if ($remaining > 0) {
  127. // Only return the amount of requested data, ensuring that the byte
  128. // limit is not exceeded
  129. return $this->stream->read(min($remaining, $length));
  130. }
  131. return '';
  132. }
  133. }