1 | <?php |
||||
2 | |||||
3 | namespace SPSS; |
||||
4 | |||||
5 | class Buffer |
||||
6 | { |
||||
7 | /** |
||||
8 | * @var mixed |
||||
9 | */ |
||||
10 | public $context; |
||||
11 | |||||
12 | /** |
||||
13 | * @var bool |
||||
14 | */ |
||||
15 | public $isBigEndian = false; |
||||
16 | |||||
17 | /** |
||||
18 | * @var string |
||||
19 | */ |
||||
20 | public $charset; |
||||
21 | |||||
22 | /** |
||||
23 | * @var resource |
||||
24 | */ |
||||
25 | private $_stream; |
||||
26 | |||||
27 | /** |
||||
28 | * @var int |
||||
29 | */ |
||||
30 | private $_position = 0; |
||||
31 | |||||
32 | /** |
||||
33 | * Buffer constructor. |
||||
34 | * |
||||
35 | * @param resource $stream Stream resource to wrap. |
||||
36 | * @param array $options Associative array of options. |
||||
37 | */ |
||||
38 | 5 | private function __construct($stream, $options = []) |
|||
39 | { |
||||
40 | 5 | if (! is_resource($stream)) { |
|||
41 | throw new \InvalidArgumentException('Stream must be a resource.'); |
||||
42 | } |
||||
43 | 5 | $this->_stream = $stream; |
|||
44 | |||||
45 | 5 | if (isset($options['context'])) { |
|||
46 | $this->context = $options['context']; |
||||
47 | } |
||||
48 | 5 | } |
|||
49 | |||||
50 | /** |
||||
51 | * Create a new stream based on the input type. |
||||
52 | * |
||||
53 | * @param resource|string $resource Entity body data |
||||
54 | * @param array $options Additional options |
||||
55 | * @return Buffer |
||||
56 | */ |
||||
57 | 5 | public static function factory($resource = '', $options = []) |
|||
58 | { |
||||
59 | 5 | $type = gettype($resource); |
|||
60 | |||||
61 | switch ($type) { |
||||
62 | 5 | case 'string': |
|||
63 | 2 | $stream = isset($options['memory']) ? |
|||
64 | 2 | fopen('php://memory', 'r+') : |
|||
65 | 2 | fopen('php://temp', 'r+'); |
|||
66 | 2 | if ($resource !== '') { |
|||
67 | fwrite($stream, $resource); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() It seems like
$stream can also be of type false ; however, parameter $handle of fwrite() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
68 | fseek($stream, 0); |
||||
0 ignored issues
–
show
It seems like
$stream can also be of type false ; however, parameter $handle of fseek() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
69 | } |
||||
70 | |||||
71 | 2 | return new self($stream, $options); |
|||
0 ignored issues
–
show
It seems like
$stream can also be of type false ; however, parameter $stream of SPSS\Buffer::__construct() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
72 | 3 | case 'resource': |
|||
73 | 3 | return new self($resource, $options); |
|||
0 ignored issues
–
show
It seems like
$resource can also be of type string ; however, parameter $stream of SPSS\Buffer::__construct() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
74 | case 'object': |
||||
75 | if (method_exists($resource, '__toString')) { |
||||
76 | return self::factory((string) $resource, $options); |
||||
77 | } |
||||
78 | } |
||||
79 | |||||
80 | throw new \InvalidArgumentException(sprintf('Invalid resource type: %s.', $type)); |
||||
81 | } |
||||
82 | |||||
83 | /** |
||||
84 | * @param int $length |
||||
85 | * @param bool $skip |
||||
86 | * @return Buffer |
||||
87 | * @throws Exception |
||||
88 | */ |
||||
89 | public function allocate($length, $skip = true) |
||||
90 | { |
||||
91 | $stream = fopen('php://memory', 'r+'); |
||||
92 | if (stream_copy_to_stream($this->_stream, $stream, $length)) { |
||||
0 ignored issues
–
show
It seems like
$stream can also be of type false ; however, parameter $dest of stream_copy_to_stream() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
93 | if ($skip) { |
||||
94 | $this->skip($length); |
||||
95 | } |
||||
96 | |||||
97 | return new self($stream); |
||||
0 ignored issues
–
show
It seems like
$stream can also be of type false ; however, parameter $stream of SPSS\Buffer::__construct() does only seem to accept resource , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
98 | } |
||||
99 | throw new Exception('Buffer allocation failed.'); |
||||
100 | } |
||||
101 | |||||
102 | /** |
||||
103 | * @param int $length |
||||
104 | * @return void |
||||
105 | */ |
||||
106 | 2 | public function skip($length) |
|||
107 | { |
||||
108 | 2 | $this->_position += $length; |
|||
109 | 2 | } |
|||
110 | |||||
111 | /** |
||||
112 | * @param string $file Path to file |
||||
113 | * @return false|int |
||||
114 | */ |
||||
115 | public function saveToFile($file) |
||||
116 | { |
||||
117 | rewind($this->_stream); |
||||
118 | |||||
119 | return file_put_contents($file, $this->_stream); |
||||
120 | } |
||||
121 | |||||
122 | /** |
||||
123 | * @param resource $resource |
||||
124 | * @param null|int $maxlength |
||||
125 | * @return false|int |
||||
126 | */ |
||||
127 | public function writeStream($resource, $maxlength = null) |
||||
128 | { |
||||
129 | if (! is_resource($resource)) { |
||||
130 | throw new \InvalidArgumentException('Invalid resource type.'); |
||||
131 | } |
||||
132 | |||||
133 | if ($maxlength) { |
||||
0 ignored issues
–
show
The expression
$maxlength of type integer|null is loosely compared to true ; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||||
134 | $length = stream_copy_to_stream($resource, $this->_stream, $maxlength); |
||||
135 | } else { |
||||
136 | $length = stream_copy_to_stream($resource, $this->_stream); |
||||
137 | } |
||||
138 | |||||
139 | $this->_position += $length; |
||||
140 | |||||
141 | return $length; |
||||
142 | } |
||||
143 | |||||
144 | /** |
||||
145 | * @return resource |
||||
146 | */ |
||||
147 | public function getStream() |
||||
148 | { |
||||
149 | return $this->_stream; |
||||
150 | } |
||||
151 | |||||
152 | /** |
||||
153 | * @param int $length |
||||
154 | * @param int $round |
||||
155 | * @param null $charset |
||||
0 ignored issues
–
show
|
|||||
156 | * @return false|string |
||||
157 | */ |
||||
158 | public function readString($length, $round = 0, $charset = null) |
||||
159 | { |
||||
160 | if ($bytes = $this->readBytes($length)) { |
||||
161 | if ($round) { |
||||
162 | $this->skip(Utils::roundUp($length, $round) - $length); |
||||
163 | } |
||||
164 | $str = Utils::bytesToString($bytes); |
||||
165 | if ($charset) { |
||||
0 ignored issues
–
show
|
|||||
166 | $str = mb_convert_encoding($str, 'utf8', $charset); |
||||
167 | } elseif ($this->charset) { |
||||
168 | $str = mb_convert_encoding($str, 'utf8', $this->charset); |
||||
169 | } |
||||
170 | |||||
171 | return $str; |
||||
172 | } |
||||
173 | |||||
174 | return false; |
||||
175 | } |
||||
176 | |||||
177 | /** |
||||
178 | * @param $length |
||||
179 | * @return false|array |
||||
180 | */ |
||||
181 | 3 | public function readBytes($length) |
|||
182 | { |
||||
183 | 3 | $bytes = $this->read($length); |
|||
184 | 3 | if ($bytes != false) { |
|||
0 ignored issues
–
show
|
|||||
185 | 3 | return array_values(unpack('C*', $bytes)); |
|||
0 ignored issues
–
show
It seems like
unpack('C*', $bytes) can also be of type false ; however, parameter $input of array_values() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
186 | } |
||||
187 | |||||
188 | return false; |
||||
189 | } |
||||
190 | |||||
191 | /** |
||||
192 | * @param int $length |
||||
193 | * @return false|string |
||||
194 | */ |
||||
195 | 5 | public function read($length = null) |
|||
196 | { |
||||
197 | 5 | $bytes = stream_get_contents($this->_stream, $length, $this->_position); |
|||
198 | 5 | if ($bytes !== false) { |
|||
199 | 5 | $this->_position += $length; |
|||
200 | } |
||||
201 | |||||
202 | 5 | return $bytes; |
|||
203 | } |
||||
204 | |||||
205 | /** |
||||
206 | * @param $data |
||||
207 | * @param int|string $length |
||||
208 | * @param null $charset |
||||
0 ignored issues
–
show
|
|||||
209 | * @return false|int |
||||
210 | */ |
||||
211 | public function writeString($data, $length = '*', $charset = null) |
||||
212 | { |
||||
213 | if ($charset) { |
||||
0 ignored issues
–
show
|
|||||
214 | $data = mb_convert_encoding($data, 'utf8', $charset); |
||||
215 | } elseif ($this->charset) { |
||||
216 | $data = mb_convert_encoding($data, 'utf8', $this->charset); |
||||
217 | } |
||||
218 | |||||
219 | return $this->write(pack('A' . $length, $data)); |
||||
220 | } |
||||
221 | |||||
222 | /** |
||||
223 | * @param string $data |
||||
224 | * @param null|int $length |
||||
225 | * @return false|int |
||||
226 | */ |
||||
227 | 5 | public function write($data, $length = null) |
|||
228 | { |
||||
229 | 5 | $length = $length ? fwrite($this->_stream, $data, $length) : fwrite($this->_stream, $data); |
|||
230 | 5 | $this->_position += $length; |
|||
231 | |||||
232 | 5 | return $length; |
|||
233 | } |
||||
234 | |||||
235 | /** |
||||
236 | * @return double |
||||
237 | */ |
||||
238 | 2 | public function readDouble() |
|||
239 | { |
||||
240 | 2 | return $this->readNumeric(8, 'd'); |
|||
0 ignored issues
–
show
The expression
return $this->readNumeric(8, 'd') could also return false which is incompatible with the documented return type double . Did you maybe forget to handle an error condition?
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled. ![]() |
|||||
241 | } |
||||
242 | |||||
243 | /** |
||||
244 | * @param $data |
||||
245 | * @return false|int |
||||
246 | */ |
||||
247 | 2 | public function writeDouble($data) |
|||
248 | { |
||||
249 | 2 | return $this->writeNumeric($data, 'd', 8); |
|||
250 | } |
||||
251 | |||||
252 | /** |
||||
253 | * @param $data |
||||
254 | * @param $format |
||||
255 | * @param null $length |
||||
0 ignored issues
–
show
|
|||||
256 | * @return false|int |
||||
257 | */ |
||||
258 | 2 | public function writeNumeric($data, $format, $length = null) |
|||
259 | { |
||||
260 | 2 | return $this->write(pack($format, $data), $length); |
|||
261 | } |
||||
262 | |||||
263 | /** |
||||
264 | * @return false|float |
||||
265 | */ |
||||
266 | public function readFloat() |
||||
267 | { |
||||
268 | return $this->readNumeric(4, 'f'); |
||||
269 | } |
||||
270 | |||||
271 | /** |
||||
272 | * @param $data |
||||
273 | * @return false|int |
||||
274 | */ |
||||
275 | public function writeFloat($data) |
||||
276 | { |
||||
277 | return $this->writeNumeric($data, 'f', 4); |
||||
278 | } |
||||
279 | |||||
280 | /** |
||||
281 | * @return int |
||||
282 | */ |
||||
283 | 2 | public function readInt() |
|||
284 | { |
||||
285 | 2 | return $this->readNumeric(4, 'i'); |
|||
0 ignored issues
–
show
The expression
return $this->readNumeric(4, 'i') could also return false which is incompatible with the documented return type integer . Did you maybe forget to handle an error condition?
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled. ![]() |
|||||
286 | } |
||||
287 | |||||
288 | /** |
||||
289 | * @param $data |
||||
290 | * @return false|int |
||||
291 | */ |
||||
292 | 2 | public function writeInt($data) |
|||
293 | { |
||||
294 | 2 | return $this->writeNumeric($data, 'i', 4); |
|||
295 | } |
||||
296 | |||||
297 | /** |
||||
298 | * @return false|int |
||||
299 | */ |
||||
300 | public function readShort() |
||||
301 | { |
||||
302 | return $this->readNumeric(2, 'v'); |
||||
303 | } |
||||
304 | |||||
305 | /** |
||||
306 | * @param $data |
||||
307 | * @return false|int |
||||
308 | */ |
||||
309 | public function writeShort($data) |
||||
310 | { |
||||
311 | return $this->writeNumeric($data, 'v', 2); |
||||
312 | } |
||||
313 | |||||
314 | /** |
||||
315 | * @param $length |
||||
316 | * @return false|int |
||||
317 | */ |
||||
318 | public function writeNull($length) |
||||
319 | { |
||||
320 | return $this->write(pack('x' . $length)); |
||||
321 | } |
||||
322 | |||||
323 | /** |
||||
324 | * @return int |
||||
325 | */ |
||||
326 | 2 | public function position() |
|||
327 | { |
||||
328 | // return ftell($this->_stream); |
||||
329 | 2 | return $this->_position; |
|||
330 | } |
||||
331 | |||||
332 | /** |
||||
333 | * @param int $offset |
||||
334 | * @param int $whence |
||||
335 | * @return int |
||||
336 | */ |
||||
337 | public function seek($offset, $whence = SEEK_SET) |
||||
338 | { |
||||
339 | $this->_position = $offset; |
||||
340 | |||||
341 | return fseek($this->_stream, $offset, $whence); |
||||
342 | } |
||||
343 | |||||
344 | /** |
||||
345 | * @return void |
||||
346 | */ |
||||
347 | 5 | public function rewind() |
|||
348 | { |
||||
349 | 5 | if (rewind($this->_stream)) { |
|||
350 | 5 | $this->_position = 0; |
|||
351 | } |
||||
352 | 5 | } |
|||
353 | |||||
354 | /** |
||||
355 | * @return void |
||||
356 | */ |
||||
357 | public function truncate() |
||||
358 | { |
||||
359 | ftruncate($this->_stream, 0); |
||||
360 | $this->_position = 0; |
||||
361 | } |
||||
362 | |||||
363 | /** |
||||
364 | * @return array |
||||
365 | */ |
||||
366 | public function getMetaData() |
||||
367 | { |
||||
368 | return stream_get_meta_data($this->_stream); |
||||
369 | } |
||||
370 | |||||
371 | /** |
||||
372 | * @param int $length |
||||
373 | * @param string $format |
||||
374 | * @return false|int|float|double |
||||
375 | */ |
||||
376 | 2 | private function readNumeric($length, $format) |
|||
377 | { |
||||
378 | 2 | $bytes = $this->read($length); |
|||
379 | 2 | if ($bytes != false) { |
|||
0 ignored issues
–
show
|
|||||
380 | 2 | if ($this->isBigEndian) { |
|||
381 | $bytes = strrev($bytes); |
||||
382 | } |
||||
383 | 2 | $data = unpack($format, $bytes); |
|||
384 | |||||
385 | 2 | return $data[1]; |
|||
386 | } |
||||
387 | |||||
388 | return false; |
||||
389 | } |
||||
390 | } |
||||
391 |