Completed
Pull Request — master (#243)
by Дмитрий
05:05
created

Generic::parseStr()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 5
eloc 11
c 1
b 1
f 0
nc 8
nop 3
dl 0
loc 17
rs 8.8571
1
<?php
2
namespace PHPDaemon\HTTPRequest;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Core\Debug;
6
use PHPDaemon\FS\File;
7
use PHPDaemon\FS\FileSystem;
8
use PHPDaemon\Request\RequestHeadersAlreadySent;
9
use PHPDaemon\Utils\MIME;
10
use PHPDaemon\Traits\DeferredEventHandlers;
11
use PHPDaemon\Traits\EventHandlers;
12
use PHPDaemon\Traits\StaticObjectWatchdog;
13
use PHPDaemon\Traits\ClassWatchdog;
14
15
/**
16
 * HTTP request
17
 * @package PHPDaemon\HTTPRequest
18
 * @author  Vasily Zorin <[email protected]>
19
 */
20
abstract class Generic extends \PHPDaemon\Request\Generic
21
{
22
    use DeferredEventHandlers;
23
    use \PHPDaemon\Traits\Sessions;
24
25
    /**
26
     * @var array Status codes
27
     */
28
    protected static $codes = [
29
        100 => 'Continue',
30
        101 => 'Switching Protocols',
31
        200 => 'OK',
32
        201 => 'Created',
33
        202 => 'Accepted',
34
        203 => 'Non-Authoritative Information',
35
        204 => 'No Content',
36
        205 => 'Reset Content',
37
        206 => 'Partial Content',
38
        300 => 'Multiple Choices',
39
        301 => 'Moved Permanently',
40
        302 => 'Found',
41
        303 => 'See Other',
42
        304 => 'Not Modified',
43
        305 => 'Use Proxy',
44
        306 => '(Unused)',
45
        307 => 'Temporary Redirect',
46
        400 => 'Bad Request',
47
        401 => 'Unauthorized',
48
        402 => 'Payment Required',
49
        403 => 'Forbidden',
50
        404 => 'Not Found',
51
        405 => 'Method Not Allowed',
52
        406 => 'Not Acceptable',
53
        407 => 'Proxy Authentication Required',
54
        408 => 'Request Timeout',
55
        409 => 'Conflict',
56
        410 => 'Gone',
57
        411 => 'Length Required',
58
        412 => 'Precondition Failed',
59
        413 => 'Request Entity Too Large',
60
        414 => 'Request-URI Too Long',
61
        415 => 'Unsupported Media Type',
62
        416 => 'Requested Range Not Satisfiable',
63
        417 => 'Expectation Failed',
64
        422 => 'Unprocessable Entity',
65
        423 => 'Locked',
66
        500 => 'Internal Server Error',
67
        501 => 'Not Implemented',
68
        502 => 'Bad Gateway',
69
        503 => 'Service Unavailable',
70
        504 => 'Gateway Timeout',
71
        505 => 'HTTP Version Not Supported',
72
    ];
73
74
    /**
75
     * @var boolean Keepalive?
76
     */
77
    public $keepalive = false;
78
79
    /**
80
     * @var integer Current response length
81
     */
82
    public $responseLength = 0;
83
84
    /**
85
     * @var integer Content length from header() method
86
     */
87
    protected $contentLength;
88
89
    /**
90
     * @var integer Number of outgoing cookie-headers
91
     */
92
    protected $cookieNum = 0;
93
94
    /**
95
     * @var array Replacement pairs for processing some header values in parse_str()
96
     */
97
    public static $hvaltr = ['; ' => '&', ';' => '&', ' ' => '%20'];
98
99
    /**
100
     * @var array State
101
     */
102
    public static $htr = ['-' => '_'];
103
104
    /**
105
     * @var array Outgoing headers
106
     */
107
    protected $headers = ['STATUS' => '200 OK'];
108
109
    /**
110
     * @var boolean Headers sent?
111
     */
112
    protected $headers_sent = false;
113
114
    /**
115
     * @var boolean File name where output started in the file and line variables
116
     */
117
    protected $headers_sent_file;
118
119
    /**
120
     * @var boolean Line number where output started in the file and line variables
121
     */
122
    protected $headers_sent_line;
123
124
    /**
125
     * @var File File pointer to send output (X-Sendfile)
126
     */
127
    protected $sendfp;
128
129
    /**
130
     * @var boolean Frozen input?
131
     */
132
    protected $frozenInput = false;
133
134
    /**
135
     * @var array Content type parameters
136
     */
137
    protected $contype;
138
139
    /**
140
     * Preparing before init
141
     * @param  object $req Source request
142
     * @return void
143
     */
144
    protected function preinit($req)
145
    {
146
        if ($req === null) {
147
            $req = new \stdClass;
148
            $req->attrs = new \stdClass;
149
            $req->attrs->inputDone = true;
150
            $req->attrs->paramsDone = true;
151
            $req->attrs->chunked = false;
152
        }
153
154
        $this->attrs = $req->attrs;
155
156
        if ($this->upstream->pool->config->expose->value) {
157
            $this->header('X-Powered-By: phpDaemon/' . Daemon::$version);
158
        }
159
160
        $this->attrs->input->setRequest($this);
161
162
        $this->parseParams();
163
    }
164
165
    /**
166
     * Called when first deferred event used
167
     * @return void
168
     */
169
    public function firstDeferredEventUsed()
170
    {
171
        $this->bind('finish', [$this, 'cleanupDeferredEventHandlers']);
172
    }
173
174
    /**
175
     * Output whole contents of file
176
     * @param  string $path Path
177
     * @param  callable $cb Callback
178
     * @param  integer $pri Priority
179
     * @return boolean        Success
180
     */
181
    public function sendfile($path, $cb, $pri = EIO_PRI_DEFAULT)
182
    {
183
        if ($this->state === self::STATE_FINISHED) {
184
            return false;
185
        }
186
        try {
187
            $this->header('Content-Type: ' . MIME::get($path));
188
        } catch (RequestHeadersAlreadySent $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
189
        }
190
        if ($this->upstream->checkSendfileCap()) {
191
            FileSystem::sendfile($this->upstream, $path, $cb, function ($file, $length, $handler) {
192
                try {
193
                    $this->header('Content-Length: ' . $length);
194
                } catch (RequestHeadersAlreadySent $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
195
                }
196
                $this->ensureSentHeaders();
197
                $this->upstream->onWriteOnce(function ($conn) use ($handler, $file) {
0 ignored issues
show
Unused Code introduced by
The parameter $conn is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
198
                    $handler($file);
199
                });
200
                return true;
201
            }, 0, null, $pri);
202
            return true;
203
        }
204
        $first = true;
205
206
        FileSystem::readfileChunked(
207
            $path,
208
            $cb,
209
            function ($file, $chunk) use (&$first) {
210
                // readed chunk
211
                if ($this->upstream->isFreed()) {
212
                    return false;
213
                }
214
215
                if ($first) {
216
                    try {
217
                        $this->header('Content-Length: ' . $file->stat['size']);
218
                    } catch (RequestHeadersAlreadySent $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
219
                    }
220
                    $first = false;
221
                }
222
                $this->out($chunk);
223
                return true;
224
            }
225
        );
226
227
        return true;
228
    }
229
230
    /**
231
     * Get cookie by name
232
     * @param  string $name Name of cookie
233
     * @return string       Contents
234
     */
235
    protected function getCookieStr($name)
236
    {
237
        return static::getString($this->attrs->cookie[$name]);
238
    }
239
240
    /**
241
     * Called to check if Request is ready
242
     * @return boolean Ready?
243
     */
244
    public function checkIfReady()
245
    {
246
        if (!$this->attrs->paramsDone || !$this->attrs->inputDone) {
247
            return false;
248
        }
249
        if (isset($this->appInstance->passphrase)) {
250
            if (!isset($this->attrs->server['PASSPHRASE'])
251
                || ($this->appInstance->passphrase !== $this->attrs->server['PASSPHRASE'])) {
252
                $this->finish();
253
            }
254
255
            return false;
256
        }
257
258
        if ($this->attrs->input->isFrozen()) {
259
            return false;
260
        }
261
262
        if ($this->sleepTime === 0) {
263
            $this->wakeup();
264
        }
265
266
        return true;
267
    }
268
269
    /**
270
     * Upload maximum file size
271
     * @return integer
272
     */
273
    public function getUploadMaxSize()
274
    {
275
        return $this->upstream->pool->config->uploadmaxsize->value;
276
    }
277
278
    /**
279
     * Parses GET-query string and other request's headers
280
     * @return void
281
     */
282
    protected function parseParams()
283
    {
284
        if (!isset($this->attrs->server['HTTP_CONTENT_LENGTH'])) {
285
            $this->attrs->contentLength = 0;
286
        } else {
287
            $this->attrs->contentLength = (int)$this->attrs->server['HTTP_CONTENT_LENGTH'];
288
        }
289
290
        if (isset($this->attrs->server['CONTENT_TYPE']) && !isset($this->attrs->server['HTTP_CONTENT_TYPE'])) {
291
            $this->attrs->server['HTTP_CONTENT_TYPE'] = $this->attrs->server['CONTENT_TYPE'];
292
        }
293
294 View Code Duplication
        if (isset($this->attrs->server['QUERY_STRING'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
295
            self::parseStr($this->attrs->server['QUERY_STRING'], $this->attrs->get);
296
        }
297
        if (isset($this->attrs->server['REQUEST_METHOD'])
298
            && ($this->attrs->server['REQUEST_METHOD'] === 'POST' || $this->attrs->server['REQUEST_METHOD'] === 'PUT')
299
            && isset($this->attrs->server['HTTP_CONTENT_TYPE'])) {
300
            $this->attrs->server['REQUEST_METHOD_POST'] = true;
301
            self::parseStr($this->attrs->server['HTTP_CONTENT_TYPE'], $this->contype, true);
302
            $found = false;
303
            foreach ($this->contype as $k => $v) {
304
                if (mb_orig_strpos($k, '/') === false) {
305
                    continue;
306
                }
307
                if (!$found) {
308
                    $found = true;
309
                } else {
310
                    unset($this->contype[$k]);
311
                }
312
            }
313
314
            if (isset($this->contype['multipart/form-data'])
315
                && (isset($this->contype['boundary']))
316
            ) {
317
                $this->attrs->input->setBoundary($this->contype['boundary']);
318
            }
319
        } else {
320
            $this->attrs->server['REQUEST_METHOD_POST'] = false;
321
        }
322
323 View Code Duplication
        if (isset($this->attrs->server['HTTP_COOKIE'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
324
            self::parseStr($this->attrs->server['HTTP_COOKIE'], $this->attrs->cookie, true);
0 ignored issues
show
Documentation introduced by
$this->attrs->server['HTTP_COOKIE'] is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
325
        }
326
327
        if (isset($this->attrs->server['HTTP_AUTHORIZATION'])) {
328
            $e = explode(' ', $this->attrs->server['HTTP_AUTHORIZATION'], 2);
329
330
            if (($e[0] === 'Basic') && isset($e[1])) {
331
                $e[1] = base64_decode($e[1]);
332
                $e = explode(':', $e[1], 2);
333
334
                if (isset($e[1])) {
335
                    list($this->attrs->server['PHP_AUTH_USER'], $this->attrs->server['PHP_AUTH_PW']) = $e;
336
                }
337
            }
338
        }
339
340
        $this->onParsedParams();
341
    }
342
343
    /**
344
     * Prepares the request body
345
     * @return void
346
     */
347
    public function postPrepare()
348
    {
349
        if (!$this->attrs->server['REQUEST_METHOD_POST']) {
350
            return;
351
        }
352
        if (isset($this->attrs->server['REQUEST_PREPARED_UPLOADS']) && $this->attrs->server['REQUEST_PREPARED_UPLOADS'] === 'nginx') {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
353
            if (isset($this->attrs->server['REQUEST_PREPARED_UPLOADS_URL_PREFIX'])) {
354
                $URLprefix = $this->attrs->server['REQUEST_PREPARED_UPLOADS_URL_PREFIX'];
355
                $l = mb_orig_strlen($URLprefix);
356
                foreach (['PHP_SELF', 'REQUEST_URI', 'SCRIPT_NAME', 'DOCUMENT_URI'] as $k) {
357
                    if (!isset($this->attrs->server[$k])) {
358
                        continue;
359
                    }
360
                    if (strncmp($this->attrs->server[$k], $URLprefix, $l) === 0) {
361
                        $this->attrs->server[$k] = substr($this->attrs->server[$k], $l - 1);
362
                    }
363
                }
364
            }
365
            $prefix = 'file.';
366
            $prefixlen = mb_orig_strlen($prefix);
367
            foreach ($this->attrs->post as $k => $v) {
368
                if (strncmp($k, $prefix, $prefixlen) === 0) {
369
                    $e = explode('.', substr($k, $prefixlen));
370
                    if (!isset($e[1])) {
371
                        $e = ['file', $e[0]];
372
                    }
373
                    if (!isset($this->attrs->files[$e[0]])) {
374
                        $this->attrs->files[$e[0]] = ['error' => UPLOAD_ERR_OK];
375
                    }
376
                    $this->attrs->files[$e[0]][$e[1]] = $v;
377
                    unset($this->attrs->post[$k]);
378
                }
379
            }
380
            $uploadTmp = $this->getUploadTempDir();
381
            foreach ($this->attrs->files as $k => &$file) {
382
                if (!isset($file['tmp_name'])
383
                    || !isset($file['name'])
384
                    || !ctype_digit(basename($file['tmp_name']))
385
                    || (mb_orig_strpos(pathinfo($file['tmp_name'], PATHINFO_DIRNAME), $uploadTmp) !== 0)
386
                ) {
387
                    unset($this->attrs->files[$k]);
388
                    continue;
389
                }
390
                FileSystem::open($file['tmp_name'], 'c+!', function ($fp) use (&$file) {
391
                    if (!$fp) {
392
                        return;
393
                    }
394
                    $file['fp'] = $fp;
395
                });
396
            }
397
            unset($file);
398
        }
399
        if (isset($this->attrs->server['REQUEST_BODY_FILE'])
400
            && $this->upstream->pool->config->autoreadbodyfile->value
401
        ) {
402
            $this->readBodyFile();
403
        }
404
    }
405
406
    /**
407
     * Ensure that headers are sent
408
     * @return boolean Were already sent?
409
     */
410
    public function ensureSentHeaders()
411
    {
412
        if ($this->headers_sent) {
413
            return true;
414
        }
415
        if (isset($this->headers['STATUS'])) {
416
            $h = (isset($this->attrs->noHttpVer) && ($this->attrs->noHttpVer) ? 'Status: ' : $this->attrs->server['SERVER_PROTOCOL']) . ' ' . $this->headers['STATUS'] . "\r\n";
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 176 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
417
        } else {
418
            $h = '';
419
        }
420
        $http11 = $this->attrs->server['SERVER_PROTOCOL'] === 'HTTP/1.1';
421
        if ($this->contentLength === null
422
            && $this->upstream->checkChunkedEncCap()
423
            && $http11
424
        ) {
425
            $this->attrs->chunked = true;
426
        }
427
        if ($this->attrs->chunked) {
428
            $this->header('Transfer-Encoding: chunked');
429
        }
430
431
        if ($http11) {
432
            $connection = isset($this->attrs->server['HTTP_CONNECTION']) ? strtolower($this->attrs->server['HTTP_CONNECTION']) : 'keep-alive';
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 142 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
433
            if ($connection === 'keep-alive' && $this->upstream->getKeepaliveTimeout() > 0) {
434
                $this->header('Connection: keep-alive');
435
                $this->keepalive = true;
436
            } else {
437
                $this->header('Connection: close');
438
            }
439
        } else {
440
            $this->header('Connection: close');
441
        }
442
443
        foreach ($this->headers as $k => $line) {
444
            if ($k !== 'STATUS') {
445
                $h .= $line . "\r\n";
446
            }
447
        }
448
        $h .= "\r\n";
449
        $this->headers_sent_file = __FILE__;
0 ignored issues
show
Documentation Bug introduced by
The property $headers_sent_file was declared of type boolean, but __FILE__ is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
450
        $this->headers_sent_line = __LINE__;
0 ignored issues
show
Documentation Bug introduced by
The property $headers_sent_line was declared of type boolean, but __LINE__ is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
451
        $this->headers_sent = true;
452
        $this->upstream->requestOut($this, $h);
453
        return false;
454
    }
455
456
    /**
457
     * Output some data
458
     * @param  string $s String to out
459
     * @param  boolean $flush ob_flush?
460
     * @return boolean        Success
461
     */
462
    public function out($s, $flush = true)
463
    {
464
        if ($flush) {
465
            if (!Daemon::$obInStack) { // preventing recursion
466
                ob_flush();
467
            }
468
        }
469
470
        if ($this->aborted) {
471
            return false;
472
        }
473
        if (!isset($this->upstream)) {
474
            return false;
475
        }
476
477
        $l = mb_orig_strlen($s);
478
        $this->responseLength += $l;
479
480
        $this->ensureSentHeaders();
481
482
        if ($this->attrs->chunked) {
483
            for ($o = 0; $o < $l;) {
484
                $c = min($this->upstream->pool->config->chunksize->value, $l - $o);
485
486
                $chunk = dechex($c) . "\r\n"
487
                    . ($c === $l ? $s : mb_orig_substr($s, $o, $c)) // content
488
                    . "\r\n";
489
490
                if ($this->sendfp) {
491
                    $this->sendfp->write($chunk);
492
                } else {
493
                    $this->upstream->requestOut($this, $chunk);
494
                }
495
496
                $o += $c;
497
            }
498
            return true;
499
        } else {
500
            if ($this->sendfp) {
501
                $this->sendfp->write($s);
502
                return true;
503
            }
504
505
            if (Daemon::$compatMode) {
506
                echo $s;
507
                return true;
508
            }
509
510
            return $this->upstream->requestOut($this, $s);
511
        }
512
    }
513
514
    /**
515
     * Called when request's headers parsed
516
     * @return void
517
     */
518
    public function onParsedParams()
519
    {
520
    }
521
522
    /**
523
     * Outputs data with headers (split by \r\n\r\n)
524
     * @param  string $s Data
525
     * @return boolean    Success
526
     */
527
    public function combinedOut($s)
528
    {
529
        if (!$this->headers_sent) {
530
            $e = explode("\r\n\r\n", $s, 2);
531
            $h = explode("\r\n", $e[0]);
532
533
            foreach ($h as $l) {
534
                $this->header($l);
535
            }
536
537
            if (isset($e[1])) {
538
                return $this->out($e[1]);
539
            }
540
541
            return true;
542
        } else {
543
            return $this->out($s);
544
        }
545
    }
546
547
    /**
548
     * Use chunked encoding
549
     * @return void
550
     */
551
    public function chunked()
552
    {
553
        $this->header('Transfer-Encoding: chunked');
554
        $this->attrs->chunked = true;
555
    }
556
557
    /**
558
     * Called when the request wakes up
559
     * @return void
560
     */
561
    public function onWakeup()
0 ignored issues
show
Coding Style introduced by
onWakeup uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onWakeup uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
562
    {
563
        parent::onWakeup();
564
        if (!Daemon::$obInStack) { // preventing recursion
565
            @ob_flush();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
566
        }
567
        $_GET = &$this->attrs->get;
568
        $_POST = &$this->attrs->post;
569
        $_COOKIE = &$this->attrs->cookie;
570
        $_REQUEST = &$this->attrs->request;
571
        $_SESSION = &$this->attrs->session;
572
        $_FILES = &$this->attrs->files;
573
        $_SERVER = &$this->attrs->server;
574
    }
575
576
    /**
577
     * Called when the request starts sleep
578
     * @return void
579
     */
580
    public function onSleep()
0 ignored issues
show
Coding Style introduced by
onSleep uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
onSleep uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
581
    {
582
        if (!Daemon::$obInStack) { // preventing recursion
583
            @ob_flush();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
584
        }
585
        unset($_GET);
586
        unset($_POST);
587
        unset($_COOKIE);
588
        unset($_REQUEST);
589
        unset($_SESSION);
590
        unset($_FILES);
591
        unset($_SERVER);
592
        parent::onSleep();
593
    }
594
595
    /**
596
     * Send HTTP-status
597
     * @param  integer $code Code
598
     * @throws RequestHeadersAlreadySent
599
     * @return boolean Success
600
     */
601
    public function status($code = 200)
602
    {
603
        if (!isset(self::$codes[$code])) {
604
            return false;
605
        }
606
        $this->header($code . ' ' . self::$codes[$code]);
607
        return true;
608
    }
609
610
    /**
611
     * Checks if headers have been sent
612
     * @param  string &$file File name
613
     * @param  integer &$line Line in file
614
     * @return boolean        Success
615
     */
616
    public function headersSent(&$file, &$line)
617
    {
618
        $file = $this->headers_sent_file;
619
        $line = $this->headers_sent_line;
620
        return $this->headers_sent;
621
    }
622
623
    /**
624
     * Return current list of headers
625
     * @return array Headers
626
     */
627
    public function headersList()
628
    {
629
        return array_values($this->headers);
630
    }
631
632
    /**
633
     * Set the cookie
634
     * @param string $name Name of cookie
635
     * @param string $value Value
636
     * @param integer $maxage Optional. Max-Age. Default is 0
637
     * @param string $path Optional. Path. Default is empty string
638
     * @param string $domain Optional. Domain. Default is empty string
639
     * @param boolean $secure Optional. Secure. Default is false
640
     * @param boolean $HTTPOnly Optional. HTTPOnly. Default is false
641
     * @return void
642
     */
643 View Code Duplication
    public function setcookie(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
644
        $name,
645
        $value = '',
646
        $maxage = 0,
647
        $path = '',
648
        $domain = '',
649
        $secure = false,
650
        $HTTPOnly = false
651
    ) {
652
        $this->header(
653
            'Set-Cookie: ' . $name . '=' . rawurlencode($value)
654
            . (empty($domain) ? '' : '; Domain=' . $domain)
655
            . (empty($maxage) ? '' : '; Max-Age=' . $maxage)
656
            . (empty($path) ? '' : '; Path=' . $path)
657
            . (!$secure ? '' : '; Secure')
658
            . (!$HTTPOnly ? '' : '; HttpOnly'),
659
            false
660
        );
661
    }
662
663
    /**
664
     * Send the header
665
     * @param  string $s Header. Example: 'Location: http://php.net/'
666
     * @param  boolean $replace Optional. Replace?
667
     * @param  integer $code Optional. HTTP response code
0 ignored issues
show
Documentation introduced by
Should the type for parameter $code not be false|integer?

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...
668
     * @throws \PHPDaemon\Request\RequestHeadersAlreadySent
669
     * @return boolean Success
670
     */
671 View Code Duplication
    public function header($s, $replace = true, $code = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
672
    {
673
        if (!$code) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $code of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

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
Loading history...
674
            $this->status($code);
0 ignored issues
show
Bug introduced by
It seems like $code defined by parameter $code on line 671 can also be of type false; however, PHPDaemon\HTTPRequest\Generic::status() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
675
        }
676
677
        if ($this->headers_sent) {
678
            throw new RequestHeadersAlreadySent;
679
        }
680
        $s = strtr($s, "\r\n", '  ');
681
682
        $e = explode(':', $s, 2);
683
684
        if (!isset($e[1])) {
685
            $e[0] = 'STATUS';
686
687
            if (strncmp($s, 'HTTP/', 5) === 0) {
688
                $s = substr($s, 9);
689
            }
690
        }
691
692
        $k = strtr(strtoupper($e[0]), Generic::$htr);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
693
694
        if ($k === 'CONTENT_TYPE') {
695
            self::parseStr(strtolower($e[1]), $ctype, true);
696
            if (!isset($ctype['charset'])) {
697
                $ctype['charset'] = $this->upstream->pool->config->defaultcharset->value;
698
699
                $s = $e[0] . ': ';
700
                $i = 0;
701
                foreach ($ctype as $k => $v) {
702
                    $s .= ($i > 0 ? '; ' : '') . $k . ($v !== '' ? '=' . $v : '');
703
                    ++$i;
704
                }
705
            }
706
        }
707
708
        if ($k === 'SET_COOKIE') {
709
            $k .= '_' . ++$this->cookieNum;
710
        } elseif (!$replace && isset($this->headers[$k])) {
711
            return false;
712
        }
713
714
        $this->headers[$k] = $s;
715
716
        if ($k === 'CONTENT_LENGTH') {
717
            $this->contentLength = (int)$e[1];
718
        } elseif ($k === 'LOCATION') {
719
            $this->status(301);
720
        }
721
722
        if (Daemon::$compatMode) {
723
            is_callable('header_native') ? header_native($s) : header($s);
724
        }
725
726
        return true;
727
    }
728
729
    /**
730
     * Removes a header
731
     * @param  string $s Header name. Example: 'Location'
732
     * @return void
733
     */
734
    public function removeHeader($s)
735
    {
736
        unset($this->headers[strtr(strtoupper($s), Generic::$htr)]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
737
    }
738
739
    /**
740
     * Converts human-readable representation of size to number of bytes
741
     * @param  string $value String of size
742
     * @return integer
743
     */
744 View Code Duplication
    public static function parseSize($value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
745
    {
746
        $l = substr($value, -1);
747
748
        if ($l === 'b' || $l === 'B') {
749
            return ((int)substr($value, 0, -1));
750
        }
751
752
        if ($l === 'k') {
753
            return ((int)substr($value, 0, -1) * 1000);
754
        }
755
756
        if ($l === 'K') {
757
            return ((int)substr($value, 0, -1) * 1024);
758
        }
759
760
        if ($l === 'm') {
761
            return ((int)substr($value, 0, -1) * 1000 * 1000);
762
        }
763
764
        if ($l === 'M') {
765
            return ((int)substr($value, 0, -1) * 1024 * 1024);
766
        }
767
768
        if ($l === 'g') {
769
            return ((int)substr($value, 0, -1) * 1000 * 1000 * 1000);
770
        }
771
772
        if ($l === 'G') {
773
            return ((int)substr($value, 0, -1) * 1024 * 1024 * 1024);
774
        }
775
776
        return (int)$value;
777
    }
778
779
    /**
780
     * Called when file upload started
781
     * @param  Input $in Input buffer
782
     * @return void
783
     */
784
    public function onUploadFileStart($in)
785
    {
786
        $this->freezeInput();
787
        FileSystem::tempnam(ini_get('upload_tmp_dir'), 'php', function ($fp) use ($in) {
788
            if (!$fp) {
789
                $in->curPart['fp'] = false;
790
                $in->curPart['error'] = UPLOAD_ERR_NO_TMP_DIR;
791
            } else {
792
                $in->curPart['fp'] = $fp;
793
                $in->curPart['tmp_name'] = $fp->path;
794
            }
795
            $this->unfreezeInput();
796
        });
797
    }
798
799
    /**
800
     * Called when chunk of incoming file has arrived
801
     * @param  Input $in Input buffer
802
     * @param  boolean $last Last?
803
     * @return void
804
     */
805
    public function onUploadFileChunk($in, $last = false)
806
    {
807
        if ($in->curPart['error'] !== UPLOAD_ERR_OK) {
808
            // just drop the chunk
809
            return;
810
        }
811
        $cb = function ($fp, $result) use ($last, $in) {
0 ignored issues
show
Unused Code introduced by
The parameter $fp is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $result is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
812
            if ($last) {
813
                unset($in->curPart['fp']);
814
            }
815
            $this->unfreezeInput();
816
        };
817
        if ($in->writeChunkToFd($in->curPart['fp']->getFd())) {
818
            // We had written via internal method
819
            return;
820
        }
821
        // Internal method is not available, let's get chunk data into $chunk and then use File->write()
822
        $chunk = $in->getChunkString();
823
        if ($chunk === false) {
824
            return;
825
        }
826
        $this->freezeInput();
827
        $in->curPart['fp']->write($chunk, $cb);
828
    }
829
830
    /**
831
     * Freeze input
832
     * @return void
833
     */
834
    protected function freezeInput()
835
    {
836
        $this->upstream->freezeInput();
837
        $this->attrs->input->freeze();
838
    }
839
840
    /**
841
     * Unfreeze input
842
     * @return void
843
     */
844
    protected function unfreezeInput()
845
    {
846
        $this->upstream->unfreezeInput();
847
        if (isset($this->attrs->input)) {
848
            $this->attrs->input->unfreeze();
849
        }
850
    }
851
852
    /**
853
     * Returns path to directory of temporary upload files
854
     * @return string
855
     */
856
    public function getUploadTempDir()
857
    {
858
        if ($r = ini_get('upload_tmp_dir')) {
859
            return $r;
860
        }
861
        return sys_get_temp_dir();
862
    }
863
864
    /**
865
     * Tells whether the file was uploaded via HTTP POST
866
     * @param  string $path The filename being checked
867
     * @return boolean       Whether if this is uploaded file
868
     */
869
    public function isUploadedFile($path)
870
    {
871
        if (!$path) {
872
            return false;
873
        }
874
        if (mb_orig_strpos($path, $this->getUploadTempDir() . '/') !== 0) {
875
            return false;
876
        }
877
        foreach ($this->attrs->files as $file) {
878
            if ($file['tmp_name'] === $path) {
879
                return true;
880
            }
881
        }
882
        return false;
883
    }
884
885
    /**
886
     * Moves an uploaded file to a new location
887
     * @param  string $filename The filename of the uploaded file
888
     * @param  string $dest The destination of the moved file
889
     * @return boolean           Success
0 ignored issues
show
Documentation introduced by
Should the return type not be resource|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
890
     */
891
    public function moveUploadedFile($filename, $dest)
892
    {
893
        if (!$this->isUploadedFile($filename)) {
894
            return false;
895
        }
896
        return FileSystem::rename($filename, $dest);
897
    }
898
899
    /**
900
     * Read request body from the file given in REQUEST_BODY_FILE parameter
901
     * @return boolean Success
902
     */
903
    public function readBodyFile()
904
    {
905
        if (!isset($this->attrs->server['REQUEST_BODY_FILE'])) {
906
            return false;
907
        }
908
909
        FileSystem::readfileChunked(
910
            $this->attrs->server['REQUEST_BODY_FILE'],
911
            function ($file, $success) {
0 ignored issues
show
Unused Code introduced by
The parameter $file is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $success is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
912
                $this->attrs->inputDone = true;
913
                if ($this->sleepTime === 0) {
914
                    $this->wakeup();
915
                }
916
            },
917
            function ($file, $chunk) {
918
                // readed chunk
919
                $this->stdin($chunk);
0 ignored issues
show
Documentation Bug introduced by
The method stdin does not exist on object<PHPDaemon\HTTPRequest\Generic>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
920
            }
921
        );
922
923
        return true;
924
    }
925
926
    /**
927
     * Replacement for default parse_str(), it supoorts UCS-2 like this: %uXXXX
928
     * @param  string $s String to parse
929
     * @param  array &$var Reference to the resulting array
930
     * @param  boolean $header Header-style string
931
     * @return void
932
     */
933
    public static function parseStr($s, &$var, $header = false)
934
    {
935
        static $cb;
936
        if ($cb === null) {
937
            $cb = function ($m) {
938
                return urlencode(html_entity_decode('&#' . hexdec($m[1]) . ';', ENT_NOQUOTES, 'utf-8'));
939
            };
940
        }
941
        if ($header) {
942
            $s = strtr($s, Generic::$hvaltr);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
943
        }
944
        if ((stripos($s, '%u') !== false)
945
            && preg_match('~(%u[a-f\d]{4}|%[c-f][a-f\d](?!%[89a-f][a-f\d]))~is', $s, $m)) {
946
            $s = preg_replace_callback('~%(u[a-f\d]{4}|[a-f\d]{2})~i', $cb, $s);
947
        }
948
        parse_str($s, $var);
949
    }
950
951
    /**
952
     * Called after request finish
953
     * @param  callable $cb Callback
0 ignored issues
show
Documentation introduced by
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...
954
     * @return void
955
     */
956
    protected function postFinishHandler($cb = null)
957
    {
958
        if (!$this->headers_sent) {
959
            $this->out('');
960
        }
961
        $this->sendfp = null;
962
        if (isset($this->attrs->files)) {
963
            foreach ($this->attrs->files as $f) {
964
                if (isset($f['tmp_name'])) {
965
                    FileSystem::unlink($f['tmp_name']);
966
                }
967
            }
968
        }
969
        if (isset($this->attrs->session)) {
970
            $this->sessionCommit($cb);
971
        } else {
972
            $cb === null || $cb();
973
        }
974
    }
975
}
976