Completed
Push — master ( 54fd6d...863321 )
by Ivan
01:39
created

Request::fixedQueryParams()   C

Complexity

Conditions 11
Paths 146

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.0935

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 19
cts 24
cp 0.7917
rs 6.9333
c 0
b 0
f 0
cc 11
nc 146
nop 1
crap 12.0935

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace vakata\http;
4
5
use Laminas\Diactoros\Uri as LaminasUri;
6
use Laminas\Diactoros\Stream;
7
use Laminas\Diactoros\UploadedFile;
8
use Laminas\Diactoros\ServerRequest;
9
use Laminas\Diactoros\ServerRequestFactory;
10
11
class Request extends ServerRequest
12
{
13
    protected $certificateNumber;
14
    protected $certificateData;
15
    /**
16
     * Create an instance from globals
17
     *
18
     * @param array $server
19
     * @param array $query
20
     * @param array $body
21
     * @param array $cookies
22
     * @param array $files
23
     * @return Request
24
     */
25
    public static function fromGlobals(
26
        array $server = null,
27
        array $query = null,
28
        array $body = null,
29
        array $cookies = null,
30
        array $files = null
31
    ) {
32
        $server  = \Laminas\Diactoros\normalizeServer($server ?: $_SERVER);
33
        $files   = \Laminas\Diactoros\normalizeUploadedFiles($files ?: $_FILES);
34
        $headers = [];
35
        foreach ($server as $key => $value) {
36
            if (strpos($key, 'REDIRECT_') === 0) {
37
                $key = substr($key, 9);
38
                if (array_key_exists($key, $server)) {
39
                    continue;
40
                }
41
            }
42
            if (is_string($value) && strlen($value) && strpos($key, 'HTTP_') === 0) {
43
                $name = strtr(strtolower(substr($key, 5)), '_', '-');
44
                $headers[$name] = $value;
45
                continue;
46
            }
47
            if (is_string($value) && strlen($value) && strpos($key, 'CONTENT_') === 0) {
48
                $name = 'content-' . strtolower(substr($key, 8));
49
                $headers[$name] = $value;
50
                continue;
51
            }
52
        }
53
54
        $method  = \Laminas\Diactoros\marshalMethodFromSapi($server);
55
        $uri     = \Laminas\Diactoros\marshalUriFromSapi($server, $headers);
56
57
        if (null === $cookies && array_key_exists('cookie', $headers)) {
58
            $cookies = self::parseCookieHeader($headers['cookie']);
59
        }
60
        
61
62
        if ($body === null) {
63
            $body = [];
64
            if (isset($headers['content-type']) && strpos($headers['content-type'], 'json') !== false) {
65
                $body = json_decode($body, true);
66
                if ($body === null) {
67
                    $body = [];
68
                }
69
            } else {
70
                $body = static::fixedQueryParams(file_get_contents('php://input'));
71
            }
72
        }
73
74
        return new static(
75
            $server,
76
            $files,
77
            $uri,
78
            $method,
79
            'php://input',
80
            $headers,
81
            $cookies ?: $_COOKIE,
82
            $query ?: static::fixedQueryParams($uri->getQuery()),
83
            $body ?: (count($_POST) ? $_POST : json_decode(file_get_contents('php://input'), true)),
84
            \Laminas\Diactoros\marshalProtocolVersionFromSapi($server),
85
            $server['SSL_CLIENT_M_SERIAL'] ?? null,
86
            $server['SSL_CLIENT_CERT'] ?? null
87
        );
88
    }
89 1
    public static function fromString(string $str) : Request
90
    {
91 1
        $method = 'GET';
92 1
        $version = '1.1';
93 1
        $uri = '/';
94 1
        $headers = [];
95 1
        $files = [];
96 1
        $body = '';
97
98 1
        $break = strpos($str, "\r\n\r\n") === false ? "\n" : "\r\n"; // just in case someone breaks RFC 2616
99
100 1
        list($headers, $message) = array_pad(explode($break . $break, $str, 2), 2, '');
101 1
        $headers = explode($break, preg_replace("(" . $break . "\s+)", " ", $headers));
102 1
        if (isset($headers[0]) && strlen($headers[0])) {
103 1
            $temp = explode(' ', $headers[0]);
104 1
            if (in_array($temp[0], ['GET', 'POST', 'HEAD', 'PATCH', 'PUT', 'OPTIONS', 'TRACE', 'DELETE'])) {
105 1
                $method = $temp[0];
106 1
                $uri = $temp[1];
107 1
                if (isset($temp[2])) {
108 1
                    $version = substr($temp[2], 5);
109
                }
110 1
                unset($headers[0]);
111 1
                $headers = array_values($headers);
112
            }
113
        }
114 1
        $temp = array_filter($headers);
115 1
        $headers = [];
116 1
        foreach ($temp as $v) {
117 1
            $v = explode(':', $v, 2);
118 1
            $name = trim($v[0]);
119 1
            $name = str_replace('_', ' ', strtolower($name));
120 1
            $name = str_replace('-', ' ', strtolower($name));
121 1
            $name = str_replace(' ', '-', ucwords($name));
122 1
            $headers[$name] = trim($v[1]);
123
        }
124 1
        if (isset($headers['Host'])) {
125
            $uri = $headers['Host'] . $uri;
126
        } else {
127 1
            $uri = 'localhost' . $uri;
128
        }
129 1
        if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart') !== false) {
130
            $bndr = trim(explode(' boundary=', $headers['Content-Type'])[1], '"');
131
            $parts = explode($break . '--' . $bndr, $break . $message);
132
            if (count($parts) == 1) {
133
                $body = $message;
134
            } else {
135
                array_pop($parts);
136
                array_shift($parts);
137
                $post = [];
138
                $fres = [];
139
                foreach ($parts as $k => $item) {
140
                    list($head, $pbody) = explode($break . $break, $item, 2);
141
                    $head = explode($break, preg_replace("(" . $break . "\s+)", " ", $head));
142
                    foreach ($head as $h) {
143
                        if (strpos(strtolower($h), 'content-disposition') === 0) {
144
                            $cd = explode(';', $h);
145
                            $name = '';
146
                            $file = '';
147
                            foreach ($cd as $p) {
148
                                if (strpos(trim($p), 'name=') === 0) {
149
                                    $name = trim(explode('name=', $p)[1], ' "');
150
                                }
151
                                if (strpos(trim($p), 'filename=') === 0) {
152
                                    $file = trim(explode('filename=', $p)[1], ' "');
153
                                }
154
                            }
155
                            if ($file) {
156
                                // create resource manually
157
                                $fres[$k] = fopen('php://temp', 'wb+');
158
                                fwrite($fres[$k], $pbody);
159
                                rewind($fres[$k]);
160
                                $files[$name] = new UploadedFile(
161
                                    $fres[$k],
162
                                    strlen($pbody),
163
                                    UPLOAD_ERR_OK,
164
                                    $file
165
                                );
166
                            } else {
167
                                $post[$name] = $pbody;
168
                            }
169
                        }
170
                    }
171
                }
172
                $body = http_build_query($post);
173
            }
174 1
        } elseif (strlen($message)) {
175
            $body = $message;
176
        }
177 1
        if (strpos($uri, '://') === false) {
178 1
            $uri = 'http://' . $uri;
179
        }
180
181 1
        if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'json') !== false) {
182
            $params = json_decode($body, true);
183
        } else {
184 1
            $params = static::fixedQueryParams($body);
185
        }
186 1
        $temp = (new Stream('php://temp', 'wb+'));
187 1
        $temp->write($body);
188 1
        $uri = new LaminasUri($uri);
189 1
        return new static(
190 1
            [],
191 1
            \Laminas\Diactoros\normalizeUploadedFiles($files),
192 1
            $uri,
193 1
            $method,
194 1
            $temp,
195 1
            $headers,
196 1
            isset($headers['Cookie']) ? self::parseCookieHeader($headers['Cookie']) : [],
197 1
            static::fixedQueryParams($uri->getQuery()),
198 1
            $params ?? [],
199 1
            $version
200
        );
201
    }
202 2
    public static function fixedQueryParams($query)
203
    {
204 2
        $data = [];
205 2
        $temp = strlen($query) ? explode('&', $query) : [];
206 2
        foreach ($temp as $var) {
207 2
            $var   = explode('=', $var, 2);
208 2
            $name  = urldecode($var[0]);
209 2
            $value = isset($var[1]) ? urldecode($var[1]) : '';
210 2
            $name  = explode(']', str_replace(['][', '['], ']', $name));
211 2
            $name  = count($name) > 1 ? array_slice($name, 0, -1) : $name;
212
213 2
            $tmp = &$data;
214 2
            foreach ($name as $k) {
215 2
                if ($k === "") {
216
                    continue;
217
                }
218 2
                if (!is_array($tmp)) {
219
                    $tmp = [];
220
                }
221 2
                if (!isset($tmp[$k])) {
222 2
                    $tmp[$k] = [];
223
                }
224 2
                $tmp = &$tmp[$k];
225
            }
226 2
            if ($name[count($name) - 1] == '') {
227
                if (!is_array($tmp)) {
228
                    $tmp = [];
229
                }
230
                $tmp[] = $value;
231
            } else {
232 2
                $tmp = $value;
233
            }
234
        }
235 2
        return $data;
236
    }
237 1
    private static function parseCookieHeader($cookieHeader)
238
    {
239 1
        preg_match_all('(
240
            (?:^\\n?[ \t]*|;[ ])
241
            (?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+)
242
            =
243
            (?P<DQUOTE>"?)
244
                (?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)
245
            (?P=DQUOTE)
246
            (?=\\n?[ \t]*$|;[ ])
247 1
        )x', $cookieHeader, $matches, PREG_SET_ORDER);
248
249 1
        $cookies = [];
250
251 1
        if (is_array($matches)) {
252 1
            foreach ($matches as $match) {
253 1
                $cookies[$match['name']] = urldecode($match['value']);
254
            }
255
        }
256
257 1
        return $cookies;
258
    }
259 1
    public function __construct(
260
        array $serverParams = [],
261
        array $uploadedFiles = [],
262
        $uri = null,
263
        $method = null,
264
        $body = 'php://input',
265
        array $headers = [],
266
        array $cookies = [],
267
        array $queryParams = [],
268
        $parsedBody = null,
269
        $protocol = '1.1',
270
        string $certificateNumber = null,
271
        string $certificateData = null
272
    ) {
273 1
        $uri = new Uri((string)$uri);
274 1
        parent::__construct(
275 1
            $serverParams,
276 1
            $uploadedFiles,
277 1
            $uri,
278 1
            $method,
279 1
            $body,
280 1
            $headers,
281 1
            $cookies,
282 1
            $queryParams,
283 1
            $parsedBody,
284 1
            $protocol
285
        );
286 1
        $this->certificateNumber = $certificateNumber ? strtoupper(ltrim(trim($certificateNumber), '0')) : null;
287 1
        $this->certificateData = $certificateData;
288 1
    }
289 1
    protected function cleanValue($value, $mode = null)
290
    {
291 1
        if (is_array($value)) {
292
            $temp = [];
293
            foreach ($value as $k => $v) {
294
                $temp[$k] = $this->cleanValue($v, $mode);
295
            }
296
            return $temp;
297
        }
298
        // normalize newlines
299 1
        if (strpos((string)$value, "\r") !== false) {
300
            $value = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $value);
301
        }
302
        // remove invalid utf8 chars
303 1
        if (preg_match('/[^\x00-\x7F]/S', $value) != 0) {
304
            $temp = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
305
            if ($temp !== false) {
306
                $value = $temp;
307
            }
308
        }
309
        // remove non-printable chars
310
        do {
311 1
            $count = 0;
312 1
            $value = preg_replace(['/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'], '', $value, -1, $count);
313 1
        } while ((int)$count > 0);
314
315
        switch ($mode) {
316 1
            case 'int':
317 1
                $value = (int) $value;
318 1
                break;
319 1
            case 'float':
320
                $value = (float) $value;
321
                break;
322 1
            case 'nohtml':
323
                $value = strip_tags((string) $value);
324
                break;
325 1
            case 'escape':
326
                $value = htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
327
                break;
328 1
            case 'string':
329
                $value = (string) $value;
330
                break;
331 1
            case 'raw':
332
            default:
333 1
                break;
334
        }
335
336 1
        return $value;
337
    }
338 1
    protected function getValue(array $collection, $key, $default, $mode)
339
    {
340 1
        if ($key === null) {
341
            return $this->cleanValue($collection, $mode);
342
        }
343 1
        return isset($collection[$key]) ? $this->cleanValue($collection[$key], $mode) : $default;
344
    }
345
    /**
346
     * Gets a value from a cookie that came with the request
347
     * @param  string    $key     the cookie name
348
     * @param  mixed     $default optional default value to return if the key is not present (default to `null`)
349
     * @param  string    $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
350
     * @return mixed             the value (or values)
351
     */
352 1
    public function getCookie($key = null, $default = null, $mode = null)
353
    {
354 1
        return $this->getValue($this->getCookieParams(), $key, $default, $mode);
355
    }
356
    /**
357
     * Get a GET param from the request URL
358
     * @param  string   $key     the GET param name
359
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
360
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
361
     * @return mixed             the value (or values)
362
     */
363 1
    public function getQuery($key = null, $default = null, $mode = null)
364
    {
365 1
        return $this->getValue($this->getQueryParams(), $key, $default, $mode);
366
    }
367
    /**
368
     * Get a param from the request body (if it is in JSON format it will be parsed out as well)
369
     * @param  string   $key     the param name
370
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
371
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
372
     * @return mixed             the value (or values if no key was specified)
373
     */
374
    public function getPost($key = null, $default = null, $mode = null)
375
    {
376
        $body = $this->getParsedBody();
377
        if (!is_array($body)) {
378
            $body = [];
379
        }
380
        return $this->getValue($body, $key, $default, $mode);
381
    }
382
    /**
383
     * Get any authorization details supplied with the request.
384
     * @return array|null           array of extracted values or null (possible keys are username, password and token)
385
     */
386
    public function getAuthorization()
387
    {
388
        if (!$this->hasHeader('Authorization')) {
389
            return null;
390
        }
391
        $temp = explode(' ', trim($this->getHeaderLine('Authorization')), 2);
392
        switch (strtolower($temp[0])) {
393
            case 'basic':
394
                $temp[1] = base64_decode($temp[1]);
395
                $temp[1] = explode(':', $temp[1], 2);
396
                return ['username' => $temp[1][0], 'password' => $temp[1][1] ?? null];
397
            case 'token':
398
            case 'oauth':
399
            case 'bearer':
400
                return ['token' => $temp[1] ?? null];
401
            default:
402
                return null;
403
        }
404
    }
405
    /**
406
     * Get the Uri object
407
     * @return Uri
408
     */
409
    public function getUrl()
410
    {
411
        return $this->getUri();
412
    }
413
    /**
414
     * Determine if this is an AJAX request
415
     * @return boolean is the request AJAX
416
     */
417 1
    public function isAjax()
418
    {
419 1
        return ($this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest');
420
    }
421
    /**
422
     * Determine if this is an CORS request
423
     * @return boolean is the request CORS
424
     */
425 1
    public function isCors()
426
    {
427 1
        if (!$this->hasHeader('Origin')) {
428 1
            return false;
429
        }
430
        $origin = parse_url($this->getHeaderLine('Origin'));
431
        $host   = $this->getUri()->getHost();
432
        $scheme = $this->getUri()->getScheme();
433
        return (
434
            !$host ||
435
            strtolower($origin['scheme']?? '') !== strtolower($scheme) ||
436
            strpos(strtolower($origin['host'] ?? ''), strtolower($host)) === false
437
        );
438
    }
439
    /**
440
     * Get the prefered response languages (parses the Accept-Language header if present).
441
     * @param  bool    $shortNames should values like "en-US", be truncated to "en", defaults to true
442
     * @return array   array of ordered lowercase language codes
443
     */
444 1
    public function getPreferredResponseLanguages(bool $shortNames = true) : array
445
    {
446 1
        $acpt = $this->getHeaderLine('Accept-Language') ?: '*';
447 1
        $acpt = explode(',', $acpt);
448 1
        foreach ($acpt as $k => $v) {
449 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
450 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
451 1
            $v[0] = $shortNames ? explode('-', $v[0], 2)[0] : $v[0];
452 1
            $v[2] = $k;
453 1
            $acpt[$k] = $v;
454
        }
455
        usort($acpt, function ($a, $b) {
456 1
            if ($a[1] > $b[1]) {
457 1
                return -1;
458
            }
459
            if ($a[1] < $b[1]) {
460
                return 1;
461
            }
462
            return $a[2] < $b[2] ? -1 : 1;
463 1
        });
464
        $acpt = array_map(function ($v) {
465 1
            return strtolower($v[0]);
466 1
        }, $acpt);
467
        $acpt = array_filter($acpt, function ($v) {
468 1
            return $v !== '*';
469 1
        });
470 1
        return array_unique($acpt);
471
    }
472
    /**
473
     * Get the preffered response language (parses the Accept-Language header if present).
474
     * @param  string       $default the default code to return if the header is not found
475
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
476
     * @return string       the prefered language code
477
     */
478 1
    public function getPreferredResponseLanguage(string $default = 'en', array $allowed = null) : string
479
    {
480 1
        $acpt = $this->getPreferredResponseLanguages(true);
481 1
        foreach ($acpt as $lang) {
482 1
            if ($allowed === null) {
483 1
                return $lang;
484
            }
485
            if (in_array($lang, $allowed)) {
486
                return $lang;
487
            }
488
        }
489 1
        return $default;
490
    }
491
    /**
492
     * Get the prefered response formats.
493
     * @param  string                    $default the default value to return if the Accept header is not present.
494
     * @return string[]                  the desired response formats
495
     */
496 1
    public function getPreferredResponseFormats($default = 'text/html')
497
    {
498
        // parse accept header (uses default instead of 406 header)
499 1
        $acpt = $this->getHeaderLine('Accept') ?: $default;
500 1
        $acpt = explode(',', $acpt);
501 1
        foreach ($acpt as $k => $v) {
502 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
503 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
504 1
            $v[0] = $v[0];
505 1
            $v[2] = $k;
506 1
            $acpt[$k] = $v;
507
        }
508
        usort($acpt, function ($a, $b) {
509 1
            if ($a[1] > $b[1]) {
510 1
                return -1;
511
            }
512 1
            if ($a[1] < $b[1]) {
513
                return 1;
514
            }
515 1
            return $a[2] < $b[2] ? -1 : 1;
516 1
        });
517
        $acpt = array_map(function ($v) {
518 1
            return strtolower($v[0]);
519 1
        }, $acpt);
520 1
        $acpt = array_filter($acpt, function ($v) {
521 1
            return $v !== '*/*';
522 1
        });
523 1
        return array_unique($acpt);
524
    }
525
    /**
526
     * Get the preffered response language (parses the Accept-Language header if present).
527
     * @param  string       $default the default code to return if the header is not found
528
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
529
     * @return string       the prefered language code
530
     */
531 1
    public function getPreferredResponseFormat(string $default = 'text/html', array $allowed = null) : string
532
    {
533
        // parse accept header (uses default instead of 406 header)
534 1
        $acpt = $this->getPreferredResponseFormats();
535 1
        foreach ($acpt as $format) {
536 1
            if ($allowed === null) {
537 1
                return $format;
538
            }
539
            if (in_array($format, $allowed)) {
540
                return $format;
541
            }
542
        }
543
        return $default;
544
    }
545
    public function hasCertificate()
546
    {
547
        return $this->certificateNumber !== null;
548
    }
549
    public function getCertificateNumber()
550
    {
551
        return $this->certificateNumber;
552
    }
553
    public function getCertificate()
554
    {
555
        return $this->certificateData;
556
    }
557
    public function withCertificate(string $number, string $data = null)
558
    {
559
        $ret = clone $this;
560
        $ret->certificateNumber = strtoupper(ltrim(trim($number), '0'));
561
        $ret->certificateData = $data;
562
        return $ret;
563
    }
564
}
565