Issues (1507)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

PHPDaemon/HTTPRequest/Input.php (6 issues)

Upgrade to new PHP Analysis Engine

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
The variable $foo does not exist. Did you forget to declare it?

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.

Loading history...
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
Documentation Bug introduced by
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..

Loading history...
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
Documentation Bug introduced by
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..

Loading history...
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
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
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 @param annotations where the type inferred by our type inference engine differs from the declared type.

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.

Loading history...
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 return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
431
            return false;
432
        }
433
        $this->write($fd, $this->curChunkSize);
434
        $this->curChunkSize = null;
435
        return true;
436
    }
437
}
438