Request::__construct()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 16
cts 16
cp 1
rs 9.44
c 0
b 0
f 0
cc 2
nc 2
nop 12
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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