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 | namespace PHPDaemon\HTTPRequest; |
||
3 | |||
4 | use PHPDaemon\Core\Daemon; |
||
5 | use PHPDaemon\Core\Debug; |
||
6 | |||
7 | /** |
||
8 | * HTTP request input buffer |
||
9 | * @package PHPDaemon\HTTPRequest |
||
10 | * @author Vasily Zorin <[email protected]> |
||
11 | */ |
||
12 | class Input extends \EventBuffer |
||
13 | { |
||
14 | use \PHPDaemon\Traits\ClassWatchdog; |
||
15 | use \PHPDaemon\Traits\StaticObjectWatchdog; |
||
16 | |||
17 | /** |
||
18 | * State: seek nearest boundary |
||
19 | */ |
||
20 | const STATE_SEEKBOUNDARY = 0; |
||
21 | /** |
||
22 | * State: headers |
||
23 | */ |
||
24 | const STATE_HEADERS = 1; |
||
25 | /** |
||
26 | * State: body |
||
27 | */ |
||
28 | const STATE_BODY = 2; |
||
29 | /** |
||
30 | * State: upload |
||
31 | */ |
||
32 | const STATE_UPLOAD = 3; |
||
33 | /** |
||
34 | * @var array Current Part |
||
35 | */ |
||
36 | public $curPart; |
||
37 | /** |
||
38 | * @var string Boundary |
||
39 | */ |
||
40 | protected $boundary; |
||
41 | /** |
||
42 | * @var integer Maximum file size from multi-part query |
||
43 | */ |
||
44 | protected $maxFileSize = 0; |
||
45 | /** |
||
46 | * @var integer Readed |
||
47 | */ |
||
48 | protected $readed = 0; |
||
49 | /** |
||
50 | * @var boolean Frozen |
||
51 | */ |
||
52 | protected $frozen = false; |
||
53 | /** |
||
54 | * @var boolean EOF |
||
55 | */ |
||
56 | protected $EOF = false; |
||
57 | /** |
||
58 | * @var array Content dispostion of current Part |
||
59 | */ |
||
60 | protected $curPartDisp = false; |
||
61 | /** |
||
62 | * @var Generic Related Request |
||
63 | */ |
||
64 | protected $req; |
||
65 | /** |
||
66 | * @var integer (self::STATE_*) State of multi-part processor |
||
67 | */ |
||
68 | protected $state = self::STATE_SEEKBOUNDARY; |
||
69 | /** |
||
70 | * @var integer Size of current upload chunk |
||
71 | */ |
||
72 | protected $curChunkSize; |
||
73 | |||
74 | /** |
||
75 | * Set boundary |
||
76 | * @param string $boundary Boundary |
||
77 | * @return void |
||
78 | */ |
||
79 | public function setBoundary($boundary) |
||
80 | { |
||
81 | $this->boundary = $boundary; |
||
82 | } |
||
83 | |||
84 | /** |
||
85 | * Freeze input |
||
86 | * @param boolean $at_front At front. Default is true. If the front of a buffer is frozen, operations that drain data from the front of the buffer, or that prepend data to the buffer, will fail until it is unfrozen. If the back a buffer is frozen, operations that append data from the buffer will fail until it is unfrozen |
||
87 | * @return void |
||
88 | */ |
||
89 | public function freeze($at_front = false) |
||
90 | { |
||
91 | $this->frozen = true; |
||
92 | //parent::freeze($at_front); // @TODO: discuss related pecl-event/libevent bug |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * Unfreeze input |
||
97 | * @param boolean $at_front At front. Default is true. If the front of a buffer is frozen, operations that drain data from the front of the buffer, or that prepend data to the buffer, will fail until it is unfrozen. If the back a buffer is frozen, operations that append data from the buffer will fail until it is unfrozen |
||
98 | * @return void |
||
99 | */ |
||
100 | public function unfreeze($at_front = false) |
||
101 | { |
||
102 | $f = $this->frozen; |
||
103 | $this->frozen = false; |
||
104 | //parent::unfreeze($at_front); // @TODO: discuss related pecl-event/libevent bug |
||
105 | $this->onRead(); |
||
106 | if ($f && $this->EOF) { |
||
107 | $this->onEOF(); |
||
108 | } |
||
109 | $this->req->checkIfReady(); |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * onRead |
||
114 | * @return void |
||
115 | */ |
||
116 | protected function onRead() |
||
117 | { |
||
118 | if (!empty($this->boundary)) { |
||
119 | $this->req->attrs->input->parseMultipart(); |
||
120 | } |
||
121 | if (($this->req->attrs->contentLength <= $this->readed) && !$this->EOF) { |
||
122 | $this->sendEOF(); |
||
123 | } |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * Send EOF |
||
128 | * @return void |
||
129 | */ |
||
130 | public function sendEOF() |
||
131 | { |
||
132 | if (!$this->EOF) { |
||
133 | $this->EOF = true; |
||
134 | $this->onEOF(); |
||
135 | } |
||
136 | } |
||
137 | |||
138 | /** |
||
139 | * onEOF |
||
140 | * @return void |
||
141 | */ |
||
142 | protected function onEOF() |
||
143 | { |
||
144 | if (!$this->req) { |
||
145 | return; |
||
146 | } |
||
147 | if ($this->frozen) { |
||
148 | return; |
||
149 | } |
||
150 | if ($this->req->attrs->inputDone) { |
||
151 | return; |
||
152 | } |
||
153 | $this->curPart =& $foo; |
||
0 ignored issues
–
show
|
|||
154 | $this->req->attrs->inputDone = true; |
||
155 | $this->req->attrs->raw = ''; |
||
156 | if (($l = $this->length) > 0) { |
||
157 | $this->req->attrs->raw = $this->read($l); |
||
158 | if (isset($this->req->contype['application/x-www-form-urlencoded'])) { |
||
159 | Generic::parseStr($this->req->attrs->raw, $this->req->attrs->post); |
||
160 | } |
||
161 | if (isset($this->req->contype['application/json']) || isset($this->req->contype['application/x-json'])) { |
||
162 | $this->req->attrs->post = json_decode($this->req->attrs->raw, true); |
||
163 | } |
||
164 | } |
||
165 | $this->req->postPrepare(); |
||
166 | $this->req->checkIfReady(); |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * Is frozen? |
||
171 | * @return boolean |
||
172 | */ |
||
173 | public function isFrozen() |
||
174 | { |
||
175 | return $this->frozen; |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * Is EOF? |
||
180 | * @return boolean |
||
181 | */ |
||
182 | public function isEof() |
||
183 | { |
||
184 | return $this->EOF; |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * Set request |
||
189 | * @param Generic $req Request |
||
190 | * @return void |
||
191 | */ |
||
192 | public function setRequest(Generic $req) |
||
193 | { |
||
194 | $this->req = $req; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Moves $n bytes from input buffer to arbitrary buffer |
||
199 | * @param \EventBuffer $buf Source nuffer |
||
200 | * @return integer |
||
201 | */ |
||
202 | public function readFromBuffer(\EventBuffer $buf) |
||
203 | { |
||
204 | if (!$this->req) { |
||
205 | return false; |
||
206 | } |
||
207 | $n = min($this->req->attrs->contentLength - $this->readed, $buf->length); |
||
208 | if ($n > 0) { |
||
209 | $m = $this->appendFrom($buf, $n); |
||
210 | $this->readed += $m; |
||
211 | if ($m > 0) { |
||
212 | $this->onRead(); |
||
213 | } |
||
214 | } else { |
||
215 | $this->onRead(); |
||
216 | return 0; |
||
217 | } |
||
218 | return $m; |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Append string to input buffer |
||
223 | * @param string $chunk Piece of request input |
||
224 | * @param boolean $final Final call is THIS SEQUENCE of calls (not mandatory final in request)? |
||
225 | * @return void |
||
226 | */ |
||
227 | public function readFromString($chunk, $final = true) |
||
228 | { |
||
229 | $this->add($chunk); |
||
230 | $this->readed += mb_orig_strlen($chunk); |
||
231 | if ($final) { |
||
232 | $this->onRead(); |
||
233 | } |
||
234 | } |
||
235 | |||
236 | |||
237 | /** |
||
238 | * Read from buffer without draining |
||
239 | * @param integer $n Number of bytes to read |
||
240 | * @param integer $o Offset |
||
241 | * @return string |
||
242 | */ |
||
243 | public function look($n, $o = 0) |
||
244 | { |
||
245 | if ($this->length <= $o) { |
||
246 | return ''; |
||
247 | } |
||
248 | return $this->substr($o, $n); |
||
249 | } |
||
250 | |||
251 | |||
252 | /** |
||
253 | * Parses multipart |
||
254 | * @return void |
||
255 | */ |
||
256 | public function parseMultipart() |
||
257 | { |
||
258 | start: |
||
259 | if ($this->frozen) { |
||
260 | return; |
||
261 | } |
||
262 | if ($this->state === self::STATE_SEEKBOUNDARY) { |
||
263 | // seek to the nearest boundary |
||
264 | if (($p = $this->search('--' . $this->boundary . "\r\n")) === false) { |
||
265 | return; |
||
266 | } |
||
267 | // we have found the nearest boundary at position $p |
||
268 | if ($p > 0) { |
||
269 | $extra = $this->read($p); |
||
270 | if ($extra !== "\r\n") { |
||
271 | $this->log('parseBody(): SEEKBOUNDARY: got unexpected data before boundary (length = ' . $p . '): ' . Debug::exportBytes($extra)); |
||
272 | } |
||
273 | } |
||
274 | $this->drain(mb_orig_strlen($this->boundary) + 4); // drain |
||
275 | $this->state = self::STATE_HEADERS; |
||
276 | } |
||
277 | if ($this->state === self::STATE_HEADERS) { |
||
278 | // parse the part's headers |
||
279 | $this->curPartDisp = false; |
||
0 ignored issues
–
show
It seems like
false of type false is incompatible with the declared type array of property $curPartDisp .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||
280 | $i = 0; |
||
281 | do { |
||
282 | $l = $this->readline(\EventBuffer::EOL_CRLF); |
||
283 | if ($l === null) { |
||
284 | return; |
||
285 | } |
||
286 | if ($l === '') { |
||
287 | break; |
||
288 | } |
||
289 | |||
290 | $e = explode(':', $l, 2); |
||
291 | $e[0] = strtr(strtoupper($e[0]), Generic::$htr); |
||
292 | if (isset($e[1])) { |
||
293 | $e[1] = ltrim($e[1]); |
||
294 | } |
||
295 | if (($e[0] === 'CONTENT_DISPOSITION') && isset($e[1])) { |
||
296 | Generic::parseStr($e[1], $this->curPartDisp, true); |
||
297 | if (!isset($this->curPartDisp['form-data'])) { |
||
298 | break; |
||
299 | } |
||
300 | if (!isset($this->curPartDisp['name'])) { |
||
301 | break; |
||
302 | } |
||
303 | $this->curPartDisp['name'] = trim($this->curPartDisp['name'], '"'); |
||
304 | $name = $this->curPartDisp['name']; |
||
305 | if (isset($this->curPartDisp['filename'])) { |
||
306 | $this->curPartDisp['filename'] = trim($this->curPartDisp['filename'], '"'); |
||
307 | if (!ini_get('file_uploads')) { |
||
308 | break; |
||
309 | } |
||
310 | $this->req->attrs->files[$name] = [ |
||
311 | 'name' => $this->curPartDisp['filename'], |
||
312 | 'type' => '', |
||
313 | 'tmp_name' => null, |
||
314 | 'fp' => null, |
||
315 | 'error' => UPLOAD_ERR_OK, |
||
316 | 'size' => 0, |
||
317 | ]; |
||
318 | $this->curPart = &$this->req->attrs->files[$name]; |
||
319 | $this->req->onUploadFileStart($this); |
||
320 | $this->state = self::STATE_UPLOAD; |
||
321 | } else { |
||
322 | $this->curPart = &$this->req->attrs->post[$name]; |
||
323 | $this->curPart = ''; |
||
0 ignored issues
–
show
It seems like
'' of type string is incompatible with the declared type array of property $curPart .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. ![]() |
|||
324 | } |
||
325 | } elseif (($e[0] === 'CONTENT_TYPE') && isset($e[1])) { |
||
326 | if (isset($this->curPartDisp['name']) && isset($this->curPartDisp['filename'])) { |
||
327 | $this->curPart['type'] = $e[1]; |
||
328 | } |
||
329 | } |
||
330 | } while ($i++ < 10); |
||
331 | if ($this->state === self::STATE_HEADERS) { |
||
332 | $this->state = self::STATE_BODY; |
||
333 | } |
||
334 | goto start; |
||
335 | } |
||
336 | if (($this->state === self::STATE_BODY) || ($this->state === self::STATE_UPLOAD)) { |
||
337 | // process the body |
||
338 | $chunkEnd1 = $this->search("\r\n--" . $this->boundary . "\r\n"); |
||
339 | $chunkEnd2 = $this->search("\r\n--" . $this->boundary . "--\r\n"); |
||
340 | if ($chunkEnd1 === false && $chunkEnd2 === false) { |
||
341 | /* we have only piece of Part in buffer */ |
||
342 | $l = $this->length - mb_orig_strlen($this->boundary) - 8; |
||
343 | if ($l <= 0) { |
||
344 | return; |
||
345 | } |
||
346 | if (($this->state === self::STATE_BODY) && isset($this->curPartDisp['name'])) { |
||
347 | $this->curPart .= $this->read($l); |
||
348 | } elseif (($this->state === self::STATE_UPLOAD) && isset($this->curPartDisp['filename'])) { |
||
349 | $this->curPart['size'] += $l; |
||
350 | if ($this->req->getUploadMaxSize() < $this->curPart['size']) { |
||
351 | $this->curPart['error'] = UPLOAD_ERR_INI_SIZE; |
||
352 | $this->req->header('413 Request Entity Too Large'); |
||
353 | $this->req->out(''); |
||
354 | $this->req->finish(); |
||
355 | } elseif ($this->maxFileSize && ($this->maxFileSize < $this->curPart['size'])) { |
||
356 | $this->curPart['error'] = UPLOAD_ERR_FORM_SIZE; |
||
357 | $this->req->header('413 Request Entity Too Large'); |
||
358 | $this->req->out(''); |
||
359 | $this->req->finish(); |
||
360 | } else { |
||
361 | $this->curChunkSize = $l; |
||
0 ignored issues
–
show
It seems like
$l can also be of type double . However, the property $curChunkSize is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
362 | $this->req->onUploadFileChunk($this); |
||
363 | } |
||
364 | } |
||
365 | } else { /* we have entire Part in buffer */ |
||
366 | |||
367 | if ($chunkEnd1 === false) { |
||
368 | $l = $chunkEnd2; |
||
369 | $endOfMsg = true; |
||
370 | } else { |
||
371 | $l = $chunkEnd1; |
||
372 | $endOfMsg = false; |
||
373 | } |
||
374 | |||
375 | if (($this->state === self::STATE_BODY) && isset($this->curPartDisp['name'])) { |
||
376 | $this->curPart .= $this->read($l); |
||
377 | if ($this->curPartDisp['name'] === 'MAX_FILE_SIZE') { |
||
378 | $this->maxFileSize = (int)$this->curPart; |
||
379 | } |
||
380 | } elseif (($this->state === self::STATE_UPLOAD) && isset($this->curPartDisp['filename'])) { |
||
381 | $this->curPart['size'] += $l; |
||
382 | $this->curChunkSize = $l; |
||
383 | $this->req->onUploadFileChunk($this, true); |
||
384 | } |
||
385 | |||
386 | $this->state = self::STATE_SEEKBOUNDARY; |
||
387 | if ($endOfMsg) { // end of whole message |
||
388 | $this->sendEOF(); |
||
389 | } else { |
||
390 | goto start; // let's read the next part |
||
391 | } |
||
392 | } |
||
393 | } |
||
394 | } |
||
395 | |||
396 | /** |
||
397 | * Log |
||
398 | * @param string $msg Message |
||
399 | * @return void |
||
400 | */ |
||
401 | public function log($msg) |
||
402 | { |
||
403 | Daemon::log(get_class($this) . ': ' . $msg); |
||
404 | } |
||
405 | |||
406 | /** |
||
407 | * Get current upload chunk as string |
||
408 | * @return string Chunk body |
||
409 | */ |
||
410 | public function getChunkString() |
||
411 | { |
||
412 | if (!$this->curChunkSize) { |
||
413 | return false; |
||
414 | } |
||
415 | $chunk = $this->read($this->curChunkSize); |
||
416 | $this->curChunkSize = null; |
||
417 | return $chunk; |
||
418 | } |
||
419 | |||
420 | /** |
||
421 | * Write current upload chunk to file descriptor |
||
422 | * @todo It is not supported yet (callback missing in EventBuffer->write()) |
||
423 | * @param mixed $fd File destriptor |
||
424 | * @param callable $cb Callback |
||
0 ignored issues
–
show
Should the type for parameter
$cb not be callable|null ?
This check looks for It makes a suggestion as to what type it considers more descriptive. Most often this is a case of a parameter that can be null in addition to its declared types. ![]() |
|||
425 | * @return boolean Success |
||
426 | */ |
||
427 | public function writeChunkToFd($fd, $cb = null) |
||
428 | { |
||
429 | return false; // It is not supported yet (callback missing in EventBuffer->write()) |
||
430 | if (!$this->curChunkSize) { |
||
0 ignored issues
–
show
if (!$this->curChunkSize) { return false; } does not seem to be reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||
431 | return false; |
||
432 | } |
||
433 | $this->write($fd, $this->curChunkSize); |
||
434 | $this->curChunkSize = null; |
||
435 | return true; |
||
436 | } |
||
437 | } |
||
438 |
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.