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

Request::__construct()   A

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
            $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