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