LimitStream.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. declare(strict_types=1);
  3. namespace GuzzleHttp\Psr7;
  4. use Psr\Http\Message\StreamInterface;
  5. /**
  6. * Decorator used to return only a subset of a stream.
  7. */
  8. final class LimitStream implements StreamInterface
  9. {
  10. use StreamDecoratorTrait;
  11. /** @var int Offset to start reading from */
  12. private $offset;
  13. /** @var int Limit the number of bytes that can be read */
  14. private $limit;
  15. /** @var StreamInterface */
  16. private $stream;
  17. /**
  18. * @param StreamInterface $stream Stream to wrap
  19. * @param int $limit Total number of bytes to allow to be read
  20. * from the stream. Pass -1 for no limit.
  21. * @param int $offset Position to seek to before reading (only
  22. * works on seekable streams).
  23. */
  24. public function __construct(
  25. StreamInterface $stream,
  26. int $limit = -1,
  27. int $offset = 0
  28. ) {
  29. $this->stream = $stream;
  30. $this->setLimit($limit);
  31. $this->setOffset($offset);
  32. }
  33. public function eof(): bool
  34. {
  35. // Always return true if the underlying stream is EOF
  36. if ($this->stream->eof()) {
  37. return true;
  38. }
  39. // No limit and the underlying stream is not at EOF
  40. if ($this->limit === -1) {
  41. return false;
  42. }
  43. return $this->stream->tell() >= $this->offset + $this->limit;
  44. }
  45. /**
  46. * Returns the size of the limited subset of data
  47. */
  48. public function getSize(): ?int
  49. {
  50. if (null === ($length = $this->stream->getSize())) {
  51. return null;
  52. } elseif ($this->limit === -1) {
  53. return $length - $this->offset;
  54. } else {
  55. return min($this->limit, $length - $this->offset);
  56. }
  57. }
  58. /**
  59. * Allow for a bounded seek on the read limited stream
  60. */
  61. public function seek($offset, $whence = SEEK_SET): void
  62. {
  63. if ($whence !== SEEK_SET || $offset < 0) {
  64. throw new \RuntimeException(sprintf(
  65. 'Cannot seek to offset %s with whence %s',
  66. $offset,
  67. $whence
  68. ));
  69. }
  70. $offset += $this->offset;
  71. if ($this->limit !== -1) {
  72. if ($offset > $this->offset + $this->limit) {
  73. $offset = $this->offset + $this->limit;
  74. }
  75. }
  76. $this->stream->seek($offset);
  77. }
  78. /**
  79. * Give a relative tell()
  80. */
  81. public function tell(): int
  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(int $offset): void
  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(int $limit): void
  115. {
  116. $this->limit = $limit;
  117. }
  118. public function read($length): string
  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. }