Completed
Push — master ( 800bf3...4eaca9 )
by Ivan
03:16
created

Request::fromGlobals()   D

Complexity

Conditions 20
Paths 64

Size

Total Lines 62

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 420

Importance

Changes 0
Metric Value
dl 0
loc 62
ccs 0
cts 39
cp 0
rs 4.1666
c 0
b 0
f 0
cc 20
nc 64
nop 5
crap 420

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