Completed
Push — master ( 047ed3...54fd6d )
by Ivan
01:36
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 (!isset($tmp[$k])) {
219 2
                    $tmp[$k] = [];
220
                }
221 2
                $tmp = &$tmp[$k];
222
            }
223 2
            if ($name[count($name) - 1] == '') {
224
                if (!is_array($tmp)) {
225
                    $tmp = [];
226
                }
227
                $tmp[] = $value;
228
            } else {
229 2
                $tmp = $value;
230
            }
231
        }
232 2
        return $data;
233
    }
234 1
    private static function parseCookieHeader($cookieHeader)
235
    {
236 1
        preg_match_all('(
237
            (?:^\\n?[ \t]*|;[ ])
238
            (?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+)
239
            =
240
            (?P<DQUOTE>"?)
241
                (?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)
242
            (?P=DQUOTE)
243
            (?=\\n?[ \t]*$|;[ ])
244 1
        )x', $cookieHeader, $matches, PREG_SET_ORDER);
245
246 1
        $cookies = [];
247
248 1
        if (is_array($matches)) {
249 1
            foreach ($matches as $match) {
250 1
                $cookies[$match['name']] = urldecode($match['value']);
251
            }
252
        }
253
254 1
        return $cookies;
255
    }
256 1
    public function __construct(
257
        array $serverParams = [],
258
        array $uploadedFiles = [],
259
        $uri = null,
260
        $method = null,
261
        $body = 'php://input',
262
        array $headers = [],
263
        array $cookies = [],
264
        array $queryParams = [],
265
        $parsedBody = null,
266
        $protocol = '1.1',
267
        string $certificateNumber = null,
268
        string $certificateData = null
269
    ) {
270 1
        $uri = new Uri((string)$uri);
271 1
        parent::__construct(
272 1
            $serverParams,
273 1
            $uploadedFiles,
274 1
            $uri,
275 1
            $method,
276 1
            $body,
277 1
            $headers,
278 1
            $cookies,
279 1
            $queryParams,
280 1
            $parsedBody,
281 1
            $protocol
282
        );
283 1
        $this->certificateNumber = $certificateNumber ? strtoupper(ltrim(trim($certificateNumber), '0')) : null;
284 1
        $this->certificateData = $certificateData;
285 1
    }
286 1
    protected function cleanValue($value, $mode = null)
287
    {
288 1
        if (is_array($value)) {
289
            $temp = [];
290
            foreach ($value as $k => $v) {
291
                $temp[$k] = $this->cleanValue($v, $mode);
292
            }
293
            return $temp;
294
        }
295
        // normalize newlines
296 1
        if (strpos((string)$value, "\r") !== false) {
297
            $value = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $value);
298
        }
299
        // remove invalid utf8 chars
300 1
        if (preg_match('/[^\x00-\x7F]/S', $value) != 0) {
301
            $temp = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
302
            if ($temp !== false) {
303
                $value = $temp;
304
            }
305
        }
306
        // remove non-printable chars
307
        do {
308 1
            $count = 0;
309 1
            $value = preg_replace(['/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'], '', $value, -1, $count);
310 1
        } while ((int)$count > 0);
311
312
        switch ($mode) {
313 1
            case 'int':
314 1
                $value = (int) $value;
315 1
                break;
316 1
            case 'float':
317
                $value = (float) $value;
318
                break;
319 1
            case 'nohtml':
320
                $value = strip_tags((string) $value);
321
                break;
322 1
            case 'escape':
323
                $value = htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
324
                break;
325 1
            case 'string':
326
                $value = (string) $value;
327
                break;
328 1
            case 'raw':
329
            default:
330 1
                break;
331
        }
332
333 1
        return $value;
334
    }
335 1
    protected function getValue(array $collection, $key, $default, $mode)
336
    {
337 1
        if ($key === null) {
338
            return $this->cleanValue($collection, $mode);
339
        }
340 1
        return isset($collection[$key]) ? $this->cleanValue($collection[$key], $mode) : $default;
341
    }
342
    /**
343
     * Gets a value from a cookie that came with the request
344
     * @param  string    $key     the cookie name
345
     * @param  mixed     $default optional default value to return if the key is not present (default to `null`)
346
     * @param  string    $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
347
     * @return mixed             the value (or values)
348
     */
349 1
    public function getCookie($key = null, $default = null, $mode = null)
350
    {
351 1
        return $this->getValue($this->getCookieParams(), $key, $default, $mode);
352
    }
353
    /**
354
     * Get a GET param from the request URL
355
     * @param  string   $key     the GET param name
356
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
357
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
358
     * @return mixed             the value (or values)
359
     */
360 1
    public function getQuery($key = null, $default = null, $mode = null)
361
    {
362 1
        return $this->getValue($this->getQueryParams(), $key, $default, $mode);
363
    }
364
    /**
365
     * Get a param from the request body (if it is in JSON format it will be parsed out as well)
366
     * @param  string   $key     the param name
367
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
368
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
369
     * @return mixed             the value (or values if no key was specified)
370
     */
371
    public function getPost($key = null, $default = null, $mode = null)
372
    {
373
        $body = $this->getParsedBody();
374
        if (!is_array($body)) {
375
            $body = [];
376
        }
377
        return $this->getValue($body, $key, $default, $mode);
378
    }
379
    /**
380
     * Get any authorization details supplied with the request.
381
     * @return array|null           array of extracted values or null (possible keys are username, password and token)
382
     */
383
    public function getAuthorization()
384
    {
385
        if (!$this->hasHeader('Authorization')) {
386
            return null;
387
        }
388
        $temp = explode(' ', trim($this->getHeaderLine('Authorization')), 2);
389
        switch (strtolower($temp[0])) {
390
            case 'basic':
391
                $temp[1] = base64_decode($temp[1]);
392
                $temp[1] = explode(':', $temp[1], 2);
393
                return ['username' => $temp[1][0], 'password' => $temp[1][1] ?? null];
394
            case 'token':
395
            case 'oauth':
396
            case 'bearer':
397
                return ['token' => $temp[1] ?? null];
398
            default:
399
                return null;
400
        }
401
    }
402
    /**
403
     * Get the Uri object
404
     * @return Uri
405
     */
406
    public function getUrl()
407
    {
408
        return $this->getUri();
409
    }
410
    /**
411
     * Determine if this is an AJAX request
412
     * @return boolean is the request AJAX
413
     */
414 1
    public function isAjax()
415
    {
416 1
        return ($this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest');
417
    }
418
    /**
419
     * Determine if this is an CORS request
420
     * @return boolean is the request CORS
421
     */
422 1
    public function isCors()
423
    {
424 1
        if (!$this->hasHeader('Origin')) {
425 1
            return false;
426
        }
427
        $origin = parse_url($this->getHeaderLine('Origin'));
428
        $host   = $this->getUri()->getHost();
429
        $scheme = $this->getUri()->getScheme();
430
        return (
431
            !$host ||
432
            strtolower($origin['scheme']?? '') !== strtolower($scheme) ||
433
            strpos(strtolower($origin['host'] ?? ''), strtolower($host)) === false
434
        );
435
    }
436
    /**
437
     * Get the prefered response languages (parses the Accept-Language header if present).
438
     * @param  bool    $shortNames should values like "en-US", be truncated to "en", defaults to true
439
     * @return array   array of ordered lowercase language codes
440
     */
441 1
    public function getPreferredResponseLanguages(bool $shortNames = true) : array
442
    {
443 1
        $acpt = $this->getHeaderLine('Accept-Language') ?: '*';
444 1
        $acpt = explode(',', $acpt);
445 1
        foreach ($acpt as $k => $v) {
446 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
447 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
448 1
            $v[0] = $shortNames ? explode('-', $v[0], 2)[0] : $v[0];
449 1
            $v[2] = $k;
450 1
            $acpt[$k] = $v;
451
        }
452
        usort($acpt, function ($a, $b) {
453 1
            if ($a[1] > $b[1]) {
454 1
                return -1;
455
            }
456
            if ($a[1] < $b[1]) {
457
                return 1;
458
            }
459
            return $a[2] < $b[2] ? -1 : 1;
460 1
        });
461
        $acpt = array_map(function ($v) {
462 1
            return strtolower($v[0]);
463 1
        }, $acpt);
464
        $acpt = array_filter($acpt, function ($v) {
465 1
            return $v !== '*';
466 1
        });
467 1
        return array_unique($acpt);
468
    }
469
    /**
470
     * Get the preffered response language (parses the Accept-Language header if present).
471
     * @param  string       $default the default code to return if the header is not found
472
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
473
     * @return string       the prefered language code
474
     */
475 1
    public function getPreferredResponseLanguage(string $default = 'en', array $allowed = null) : string
476
    {
477 1
        $acpt = $this->getPreferredResponseLanguages(true);
478 1
        foreach ($acpt as $lang) {
479 1
            if ($allowed === null) {
480 1
                return $lang;
481
            }
482
            if (in_array($lang, $allowed)) {
483
                return $lang;
484
            }
485
        }
486 1
        return $default;
487
    }
488
    /**
489
     * Get the prefered response formats.
490
     * @param  string                    $default the default value to return if the Accept header is not present.
491
     * @return string[]                  the desired response formats
492
     */
493 1
    public function getPreferredResponseFormats($default = 'text/html')
494
    {
495
        // parse accept header (uses default instead of 406 header)
496 1
        $acpt = $this->getHeaderLine('Accept') ?: $default;
497 1
        $acpt = explode(',', $acpt);
498 1
        foreach ($acpt as $k => $v) {
499 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
500 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
501 1
            $v[0] = $v[0];
502 1
            $v[2] = $k;
503 1
            $acpt[$k] = $v;
504
        }
505
        usort($acpt, function ($a, $b) {
506 1
            if ($a[1] > $b[1]) {
507 1
                return -1;
508
            }
509 1
            if ($a[1] < $b[1]) {
510
                return 1;
511
            }
512 1
            return $a[2] < $b[2] ? -1 : 1;
513 1
        });
514
        $acpt = array_map(function ($v) {
515 1
            return strtolower($v[0]);
516 1
        }, $acpt);
517 1
        $acpt = array_filter($acpt, function ($v) {
518 1
            return $v !== '*/*';
519 1
        });
520 1
        return array_unique($acpt);
521
    }
522
    /**
523
     * Get the preffered response language (parses the Accept-Language header if present).
524
     * @param  string       $default the default code to return if the header is not found
525
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
526
     * @return string       the prefered language code
527
     */
528 1
    public function getPreferredResponseFormat(string $default = 'text/html', array $allowed = null) : string
529
    {
530
        // parse accept header (uses default instead of 406 header)
531 1
        $acpt = $this->getPreferredResponseFormats();
532 1
        foreach ($acpt as $format) {
533 1
            if ($allowed === null) {
534 1
                return $format;
535
            }
536
            if (in_array($format, $allowed)) {
537
                return $format;
538
            }
539
        }
540
        return $default;
541
    }
542
    public function hasCertificate()
543
    {
544
        return $this->certificateNumber !== null;
545
    }
546
    public function getCertificateNumber()
547
    {
548
        return $this->certificateNumber;
549
    }
550
    public function getCertificate()
551
    {
552
        return $this->certificateData;
553
    }
554
    public function withCertificate(string $number, string $data = null)
555
    {
556
        $ret = clone $this;
557
        $ret->certificateNumber = strtoupper(ltrim(trim($number), '0'));
558
        $ret->certificateData = $data;
559
        return $ret;
560
    }
561
}
562