This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Thruster\Component\HttpMessage; |
||
4 | |||
5 | use Psr\Http\Message\StreamInterface; |
||
6 | |||
7 | /** |
||
8 | * Class AppendStream |
||
9 | * |
||
10 | * @package Thruster\Component\HttpMessage |
||
11 | * @author Aurimas Niekis <[email protected]> |
||
12 | */ |
||
13 | class AppendStream implements StreamInterface |
||
14 | { |
||
15 | /** |
||
16 | * @var StreamInterface[] Streams being decorated |
||
17 | */ |
||
18 | private $streams; |
||
19 | |||
20 | /** |
||
21 | * @var bool |
||
22 | */ |
||
23 | private $seekable; |
||
24 | |||
25 | /** |
||
26 | * @var int |
||
27 | */ |
||
28 | private $current; |
||
29 | |||
30 | /** |
||
31 | * @var int |
||
32 | */ |
||
33 | private $pos; |
||
34 | |||
35 | /** |
||
36 | * @var bool |
||
37 | */ |
||
38 | private $detached; |
||
39 | |||
40 | /** |
||
41 | * @param StreamInterface[] $streams Streams to decorate. Each stream must |
||
42 | * be readable. |
||
43 | */ |
||
44 | 23 | public function __construct(array $streams = []) |
|
45 | { |
||
46 | 23 | $this->streams = []; |
|
47 | 23 | $this->seekable = true; |
|
48 | 23 | $this->current = 0; |
|
49 | 23 | $this->pos = 0; |
|
50 | 23 | $this->detached = false; |
|
51 | |||
52 | 23 | foreach ($streams as $stream) { |
|
53 | 7 | $this->addStream($stream); |
|
54 | } |
||
55 | 23 | } |
|
56 | |||
57 | 5 | public function __toString() |
|
58 | { |
||
59 | try { |
||
60 | 5 | $this->rewind(); |
|
61 | 5 | return $this->getContents(); |
|
62 | 1 | } catch (\Exception $e) { |
|
63 | 1 | return ''; |
|
64 | } |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Add a stream to the AppendStream |
||
69 | * |
||
70 | * @param StreamInterface $stream Stream to append. Must be readable. |
||
71 | * |
||
72 | * @return $this |
||
73 | * @throws \InvalidArgumentException if the stream is not readable |
||
74 | */ |
||
75 | 17 | public function addStream(StreamInterface $stream) : self |
|
76 | { |
||
77 | 17 | if (false === $stream->isReadable()) { |
|
78 | 1 | throw new \InvalidArgumentException('Each stream must be readable'); |
|
79 | } |
||
80 | |||
81 | // The stream is only seekable if all streams are seekable |
||
82 | 16 | if (false === $stream->isSeekable()) { |
|
83 | $this->seekable = false; |
||
84 | } |
||
85 | |||
86 | 16 | $this->streams[] = $stream; |
|
87 | |||
88 | 16 | return $this; |
|
89 | } |
||
90 | |||
91 | 5 | public function getContents() |
|
92 | { |
||
93 | 5 | return copy_to_string($this); |
|
94 | } |
||
95 | |||
96 | /** |
||
97 | * Closes each attached stream. |
||
98 | * |
||
99 | * {@inheritdoc} |
||
100 | */ |
||
101 | 3 | public function close() |
|
102 | { |
||
103 | 3 | $this->pos = $this->current = 0; |
|
104 | |||
105 | 3 | foreach ($this->streams as $stream) { |
|
106 | 2 | $stream->close(); |
|
107 | } |
||
108 | |||
109 | 3 | $this->streams = []; |
|
110 | 3 | } |
|
111 | |||
112 | /** |
||
113 | * Detaches each attached stream |
||
114 | * |
||
115 | * {@inheritdoc} |
||
116 | */ |
||
117 | 2 | public function detach() |
|
118 | { |
||
119 | 2 | $this->close(); |
|
120 | |||
121 | 2 | $this->detached = true; |
|
122 | 2 | } |
|
123 | |||
124 | 2 | public function tell() |
|
125 | { |
||
126 | 2 | return $this->pos; |
|
127 | } |
||
128 | |||
129 | /** |
||
130 | * Tries to calculate the size by adding the size of each stream. |
||
131 | * |
||
132 | * If any of the streams do not return a valid number, then the size of the |
||
133 | * append stream cannot be determined and null is returned. |
||
134 | * |
||
135 | * {@inheritdoc} |
||
136 | */ |
||
137 | 3 | public function getSize() |
|
138 | { |
||
139 | 3 | $size = 0; |
|
140 | |||
141 | 3 | foreach ($this->streams as $stream) { |
|
142 | 2 | $s = $stream->getSize(); |
|
143 | |||
144 | 2 | if (null === $s) { |
|
145 | 1 | return null; |
|
146 | } |
||
147 | |||
148 | 2 | $size += $s; |
|
149 | } |
||
150 | |||
151 | 3 | return $size; |
|
152 | } |
||
153 | |||
154 | 11 | public function eof() : bool |
|
155 | { |
||
156 | 11 | return !$this->streams || ($this->current >= count($this->streams) - 1 && |
|
0 ignored issues
–
show
|
|||
157 | 11 | $this->streams[$this->current]->eof()); |
|
158 | } |
||
159 | |||
160 | 5 | public function rewind() |
|
161 | { |
||
162 | 5 | $this->seek(0); |
|
163 | 5 | } |
|
164 | |||
165 | /** |
||
166 | * Attempts to seek to the given position. Only supports SEEK_SET. |
||
167 | * |
||
168 | * {@inheritdoc} |
||
169 | */ |
||
170 | 12 | public function seek($offset, $whence = SEEK_SET) |
|
171 | { |
||
172 | 12 | if (false === $this->seekable) { |
|
173 | throw new \RuntimeException('This AppendStream is not seekable'); |
||
174 | 12 | } elseif ($whence !== SEEK_SET) { |
|
175 | 1 | throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); |
|
176 | } |
||
177 | |||
178 | 11 | $this->pos = $this->current = 0; |
|
179 | |||
180 | // Rewind each stream |
||
181 | 11 | foreach ($this->streams as $i => $stream) { |
|
182 | try { |
||
183 | 9 | $stream->rewind(); |
|
184 | 1 | } catch (\Exception $e) { |
|
185 | 1 | throw new \RuntimeException( |
|
186 | 1 | 'Unable to seek stream ' . $i . ' of the AppendStream', |
|
187 | 9 | 0, |
|
188 | $e |
||
189 | ); |
||
190 | } |
||
191 | } |
||
192 | |||
193 | // Seek to the actual position by reading from each stream |
||
194 | 10 | while ($this->pos < $offset && false === $this->eof()) { |
|
195 | 1 | $result = $this->read(min(8096, $offset - $this->pos)); |
|
196 | |||
197 | 1 | if ('' === $result) { |
|
198 | break; |
||
199 | } |
||
200 | } |
||
201 | 10 | } |
|
202 | |||
203 | /** |
||
204 | * Reads from all of the appended streams until the length is met or EOF. |
||
205 | * |
||
206 | * {@inheritdoc} |
||
207 | */ |
||
208 | 9 | public function read($length) |
|
209 | { |
||
210 | 9 | $buffer = ''; |
|
211 | 9 | $total = count($this->streams) - 1; |
|
212 | 9 | $remaining = $length; |
|
213 | 9 | $progressToNext = false; |
|
214 | |||
215 | 9 | while (0 < $remaining) { |
|
216 | // Progress to the next stream if needed. |
||
217 | 9 | if ($progressToNext || $this->streams[$this->current]->eof()) { |
|
218 | 8 | $progressToNext = false; |
|
219 | |||
220 | 8 | if ($total === $this->current) { |
|
221 | 7 | break; |
|
222 | } |
||
223 | |||
224 | 7 | $this->current++; |
|
225 | } |
||
226 | |||
227 | 9 | $result = $this->streams[$this->current]->read($remaining); |
|
228 | |||
229 | // Using a loose comparison here to match on '', false, and null |
||
230 | 8 | if (null === $result) { |
|
231 | $progressToNext = true; |
||
232 | |||
233 | continue; |
||
234 | } |
||
235 | |||
236 | 8 | $buffer .= $result; |
|
237 | 8 | $remaining = $length - strlen($buffer); |
|
238 | } |
||
239 | |||
240 | 8 | $this->pos += strlen($buffer); |
|
241 | |||
242 | 8 | return $buffer; |
|
243 | } |
||
244 | |||
245 | 1 | public function isReadable() : bool |
|
246 | { |
||
247 | 1 | return true; |
|
248 | } |
||
249 | |||
250 | 1 | public function isWritable() : bool |
|
251 | { |
||
252 | 1 | return false; |
|
253 | } |
||
254 | |||
255 | 5 | public function isSeekable() : bool |
|
256 | { |
||
257 | 5 | return $this->seekable; |
|
258 | } |
||
259 | |||
260 | 1 | public function write($string) |
|
261 | { |
||
262 | 1 | throw new \RuntimeException('Cannot write to an AppendStream'); |
|
263 | } |
||
264 | |||
265 | 1 | public function getMetadata($key = null) |
|
266 | { |
||
267 | 1 | return $key ? null : []; |
|
268 | } |
||
269 | } |
||
270 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.