Completed
Push — master ( 201321...f974f3 )
by Ivan
01:52
created

Request::fromString()   F

Complexity

Conditions 23
Paths 512

Size

Total Lines 114
Code Lines 89

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 51
CRAP Score 58.6647

Importance

Changes 0
Metric Value
dl 0
loc 114
ccs 51
cts 86
cp 0.593
rs 2.8929
c 0
b 0
f 0
cc 23
eloc 89
nc 512
nop 1
crap 58.6647

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  = ServerRequestFactory::normalizeServer($server ?: $_SERVER);
31
        $files   = ServerRequestFactory::normalizeFiles($files ?: $_FILES);
32
        $headers = ServerRequestFactory::marshalHeaders($server);
33
34
        if (null === $cookies && array_key_exists('cookie', $headers)) {
35
            $cookies = self::parseCookieHeader($headers['cookie']);
36
        }
37
        $uri = ServerRequestFactory::marshalUriFromServer($server, $headers);
38
39
        if ($body === null) {
40
            $body = [];
41
            if (isset($headers['content-type']) && strpos($headers['content-type'], 'json') !== false) {
42
                $body = json_decode($body, true);
43
                if ($body === null) {
44
                    $body = [];
45
                }
46
            } else {
47
                $body = static::fixedQueryParams(file_get_contents('php://input'));
48
            }
49
        }
50
51
        return new static(
52
            $server,
53
            $files,
54
            $uri,
55
            ServerRequestFactory::get('REQUEST_METHOD', $server, 'GET'),
56
            'php://input',
57
            $headers,
58
            $cookies ?: $_COOKIE,
59
            $query ?: static::fixedQueryParams($uri->getQuery()),
60
            $body ?: (count($_POST) ? $_POST : json_decode(file_get_contents('php://input'), true)),
61
            self::marshalProtocolVersion($server)
62
        );
63
    }
64 1
    public static function fromString(string $str) : Request
65
    {
66 1
        $method = 'GET';
67 1
        $version = '1.1';
68 1
        $uri = '/';
69 1
        $headers = [];
70 1
        $files = [];
71 1
        $body = '';
72
73 1
        $break = strpos($str, "\r\n\r\n") === false ? "\n" : "\r\n"; // just in case someone breaks RFC 2616
74
75 1
        list($headers, $message) = array_pad(explode($break . $break, $str, 2), 2, '');
76 1
        $headers = explode($break, preg_replace("(" . $break . "\s+)", " ", $headers));
77 1
        if (isset($headers[0]) && strlen($headers[0])) {
78 1
            $temp = explode(' ', $headers[0]);
79 1
            if (in_array($temp[0], ['GET', 'POST', 'HEAD', 'PATCH', 'PUT', 'OPTIONS', 'TRACE', 'DELETE'])) {
80 1
                $method = $temp[0];
81 1
                $uri = $temp[1];
82 1
                if (isset($temp[2])) {
83 1
                    $version = substr($temp[2], 5);
84
                }
85 1
                unset($headers[0]);
86 1
                $headers = array_values($headers);
87
            }
88
        }
89 1
        $temp = array_filter($headers);
90 1
        $headers = [];
91 1
        foreach ($temp as $v) {
92 1
            $v = explode(':', $v, 2);
93 1
            $name = trim($v[0]);
94 1
            $name = str_replace('_', ' ', strtolower($name));
95 1
            $name = str_replace('-', ' ', strtolower($name));
96 1
            $name = str_replace(' ', '-', ucwords($name));
97 1
            $headers[$name] = trim($v[1]);
98
        }
99 1
        if (isset($headers['Host'])) {
100
            $uri = $headers['Host'] . $uri;
101
        } else {
102 1
            $uri = 'localhost' . $uri;
103
        }
104 1
        if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart') !== false) {
105
            $bndr = trim(explode(' boundary=', $headers['Content-Type'])[1], '"');
106
            $parts = explode($break . '--' . $bndr, $break . $message);
107
            if (count($parts) == 1) {
108
                $body = $message;
109
            } else {
110
                array_pop($parts);
111
                array_shift($parts);
112
                $post = [];
113
                $fres = [];
114
                foreach ($parts as $k => $item) {
115
                    list($head, $pbody) = explode($break . $break, $item, 2);
116
                    $head = explode($break, preg_replace("(" . $break . "\s+)", " ", $head));
117
                    foreach ($head as $h) {
118
                        if (strpos(strtolower($h), 'content-disposition') === 0) {
119
                            $cd = explode(';', $h);
120
                            $name = '';
121
                            $file = '';
122
                            foreach ($cd as $p) {
123
                                if (strpos(trim($p), 'name=') === 0) {
124
                                    $name = trim(explode('name=', $p)[1], ' "');
125
                                }
126
                                if (strpos(trim($p), 'filename=') === 0) {
127
                                    $file = trim(explode('filename=', $p)[1], ' "');
128
                                }
129
                            }
130
                            if ($file) {
131
                                // create resource manually
132
                                $fres[$k] = fopen('php://temp', 'wb+');
133
                                fwrite($fres[$k], $pbody);
134
                                rewind($fres[$k]);
135
                                $files[$name] = new UploadedFile(
136
                                    $fres[$k],
137
                                    strlen($pbody),
138
                                    UPLOAD_ERR_OK,
139
                                    $file
140
                                );
141
                            } else {
142
                                $post[$name] = $pbody;
143
                            }
144
                        }
145
                    }
146
                }
147
                $body = http_build_query($post);
148
            }
149 1
        } elseif (strlen($message)) {
150
            $body = $message;
151
        }
152 1
        if (strpos($uri, '://') === false) {
153 1
            $uri = 'http://' . $uri;
154
        }
155
156 1
        $params = [];
0 ignored issues
show
Unused Code introduced by
$params is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
157 1
        if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'json') !== false) {
158
            $params = json_decode($body, true);
159
        } else {
160 1
            $params = static::fixedQueryParams($body);
161
        }
162 1
        $temp = (new Stream('php://temp', 'wb+'));
163 1
        $temp->write($body);
164 1
        $uri = new ZendUri($uri);
165 1
        return new static(
166 1
            [],
167 1
            ServerRequestFactory::normalizeFiles($files),
168 1
            $uri,
169 1
            $method,
170 1
            $temp,
171 1
            $headers,
172 1
            isset($headers['Cookie']) ? self::parseCookieHeader($headers['Cookie']) : [],
173 1
            static::fixedQueryParams($uri->getQuery()),
174 1
            $params ?? [],
175 1
            $version
176
        );
177
    }
178 2
    public static function fixedQueryParams($query)
179
    {
180 2
        $data = [];
181 2
        $temp = strlen($query) ? explode('&', $query) : [];
182 2
        foreach ($temp as $var) {
183 2
            $var   = explode('=', $var, 2);
184 2
            $name  = urldecode($var[0]);
185 2
            $value = isset($var[1]) ? urldecode($var[1]) : '';
186 2
            $name  = explode(']', str_replace(['][', '['], ']', $name));
187 2
            $name  = count($name) > 1 ? array_slice($name, 0, -1) : $name;
188
189 2
            $tmp = &$data;
190 2
            foreach ($name as $k) {
191 2
                if ($k === "") {
192
                    continue;
193
                }
194 2
                if (!isset($tmp[$k])) {
195 2
                    $tmp[$k] = [];
196
                }
197 2
                $tmp = &$tmp[$k];
198
            }
199 2
            if ($name[count($name) - 1] == '') {
200
                $tmp[] = $value;
201
            } else {
202 2
                $tmp = $value;
203
            }
204
        }
205 2
        return $data;
206
    }
207 1
    private static function parseCookieHeader($cookieHeader)
208
    {
209 1
        preg_match_all('(
210
            (?:^\\n?[ \t]*|;[ ])
211
            (?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+)
212
            =
213
            (?P<DQUOTE>"?)
214
                (?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)
215
            (?P=DQUOTE)
216
            (?=\\n?[ \t]*$|;[ ])
217 1
        )x', $cookieHeader, $matches, PREG_SET_ORDER);
218
219 1
        $cookies = [];
220
221 1
        if (is_array($matches)) {
222 1
            foreach ($matches as $match) {
223 1
                $cookies[$match['name']] = urldecode($match['value']);
224
            }
225
        }
226
227 1
        return $cookies;
228
    }
229
    private static function marshalProtocolVersion(array $server)
230
    {
231
        if (! isset($server['SERVER_PROTOCOL'])) {
232
            return '1.1';
233
        }
234
235
        if (! preg_match('#^(HTTP/)?(?P<version>[1-9]\d*(?:\.\d)?)$#', $server['SERVER_PROTOCOL'], $matches)) {
236
            throw new \Exception(sprintf(
237
                'Unrecognized protocol version (%s)',
238
                $server['SERVER_PROTOCOL']
239
            ));
240
        }
241
242
        return $matches['version'];
243
    }
244 1
    public function __construct(
245
        array $serverParams = [],
246
        array $uploadedFiles = [],
247
        $uri = null,
248
        $method = null,
249
        $body = 'php://input',
250
        array $headers = [],
251
        array $cookies = [],
252
        array $queryParams = [],
253
        $parsedBody = null,
254
        $protocol = '1.1'
255
    ) {
256 1
        $uri = new Uri((string)$uri);
257 1
        parent::__construct(
258 1
            $serverParams,
259 1
            $uploadedFiles,
260 1
            $uri,
261 1
            $method,
262 1
            $body,
263 1
            $headers,
264 1
            $cookies,
265 1
            $queryParams,
266 1
            $parsedBody,
267 1
            $protocol
268
        );
269 1
    }
270 1
    protected function cleanValue($value, $mode = null)
271
    {
272 1
        if (is_array($value)) {
273
            $temp = [];
274
            foreach ($value as $k => $v) {
275
                $temp[$k] = $this->cleanValue($v, $mode);
276
            }
277
            return $temp;
278
        }
279
        // normalize newlines
280 1
        if (strpos((string)$value, "\r") !== false) {
281
            $value = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $value);
282
        }
283
        // remove invalid utf8 chars
284 1
        if (preg_match('/[^\x00-\x7F]/S', $value) != 0) {
285
            $temp = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
286
            if ($temp !== false) {
287
                $value = $temp;
288
            }
289
        }
290
        // remove non-printable chars
291
        do {
292 1
            $count = 0;
293 1
            $value = preg_replace(['/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'], '', $value, -1, $count);
294 1
        } while ((int)$count > 0);
295
296
        switch ($mode) {
297 1
            case 'int':
298 1
                $value = (int) $value;
299 1
                break;
300 1
            case 'float':
301
                $value = (float) $value;
302
                break;
303 1
            case 'nohtml':
304
                $value = strip_tags((string) $value);
305
                break;
306 1
            case 'escape':
307
                $value = htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
308
                break;
309 1
            case 'string':
310
                $value = (string) $value;
311
                break;
312 1
            case 'raw':
313
            default:
314 1
                break;
315
        }
316
317 1
        return $value;
318
    }
319 1
    protected function getValue(array $collection, $key, $default, $mode)
320
    {
321 1
        if ($key === null) {
322
            return $this->cleanValue($collection, $mode);
323
        }
324 1
        return isset($collection[$key]) ? $this->cleanValue($collection[$key], $mode) : $default;
325
    }
326
    /**
327
     * Gets a value from a cookie that came with the request
328
     * @param  string    $key     the cookie name
329
     * @param  mixed     $default optional default value to return if the key is not present (default to `null`)
330
     * @param  string    $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
331
     * @return string|array             the value (or values)
332
     */
333 1
    public function getCookie($key = null, $default = null, $mode = null)
334
    {
335 1
        return $this->getValue($this->getCookieParams(), $key, $default, $mode);
336
    }
337
    /**
338
     * Get a GET param from the request URL
339
     * @param  string   $key     the GET param name
340
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
341
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
342
     * @return string|array             the value (or values)
343
     */
344 1
    public function getQuery($key = null, $default = null, $mode = null)
345
    {
346 1
        return $this->getValue($this->getQueryParams(), $key, $default, $mode);
347
    }
348
    /**
349
     * Get a param from the request body (if it is in JSON format it will be parsed out as well)
350
     * @param  string   $key     the param name
351
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
352
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
353
     * @return string|array             the value (or values if no key was specified)
354
     */
355
    public function getPost($key = null, $default = null, $mode = null)
356
    {
357
        $body = $this->getParsedBody();
358
        if (!is_array($body)) {
359
            $body = [];
360
        }
361
        return $this->getValue($body, $key, $default, $mode);
362
    }
363
    /**
364
     * Get any authorization details supplied with the request.
365
     * @return array|null           array of extracted values or null (possible keys are username, password and token)
366
     */
367
    public function getAuthorization()
368
    {
369
        if (!$this->hasHeader('Authorization')) {
370
            return null;
371
        }
372
        $temp = explode(' ', trim($this->getHeaderLine('Authorization')), 2);
373
        switch (strtolower($temp[0])) {
374
            case 'basic':
375
                $temp[1] = base64_decode($temp[1]);
376
                $temp[1] = explode(':', $temp[1], 2);
377
                return ['username' => $temp[1][0], 'password' => $temp[1][1] ?? null];
378
            case 'token':
379
            case 'oauth':
380
            case 'bearer':
381
                return ['token' => $temp[1] ?? null];
382
            default:
383
                return null;
384
        }
385
    }
386
    public function getUrl()
387
    {
388
        return $this->getUri();
389
    }
390
    /**
391
     * Determine if this is an AJAX request
392
     * @return boolean is the request AJAX
393
     */
394 1
    public function isAjax()
395
    {
396 1
        return ($this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest');
397
    }
398
    /**
399
     * Determine if this is an CORS request
400
     * @return boolean is the request CORS
401
     */
402 1
    public function isCors()
403
    {
404 1
        if (!$this->hasHeader('Origin')) {
405 1
            return false;
406
        }
407
        $origin = parse_url($this->getHeaderLine('Origin'));
408
        $host   = $this->getUri()->getHost();
409
        $scheme = $this->getUri()->getScheme();
410
        return (
411
            !$host ||
412
            strtolower($origin['scheme']?? '') !== strtolower($scheme) ||
413
            strpos(strtolower($origin['host'] ?? ''), strtolower($host)) === false
414
        );
415
    }
416
    /**
417
     * Get the prefered response languages (parses the Accept-Language header if present).
418
     * @param  bool    $shortNames should values like "en-US", be truncated to "en", defaults to true
419
     * @return array   array of ordered lowercase language codes
420
     */
421 1
    public function getPreferredResponseLanguages(bool $shortNames = true) : array
422
    {
423 1
        $acpt = $this->getHeaderLine('Accept-Language') ?: '*';
424 1
        $acpt = explode(',', $acpt);
425 1
        foreach ($acpt as $k => $v) {
426 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
427 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
428 1
            $v[0] = $shortNames ? explode('-', $v[0], 2)[0] : $v[0];
429 1
            $v[2] = $k;
430 1
            $acpt[$k] = $v;
431
        }
432
        usort($acpt, function ($a, $b) {
433 1
            if ($a[1] > $b[1]) {
434 1
                return -1;
435
            }
436
            if ($a[1] < $b[1]) {
437
                return 1;
438
            }
439
            return $a[2] < $b[2] ? -1 : 1;
440 1
        });
441
        $acpt = array_map(function ($v) {
442 1
            return strtolower($v[0]);
443 1
        }, $acpt);
444
        $acpt = array_filter($acpt, function ($v) {
445 1
            return $v !== '*';
446 1
        });
447 1
        return array_unique($acpt);
448
    }
449
    /**
450
     * Get the preffered response language (parses the Accept-Language header if present).
451
     * @param  string       $default the default code to return if the header is not found
452
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
453
     * @return string       the prefered language code
454
     */
455 1
    public function getPreferredResponseLanguage(string $default = 'en', array $allowed = null) : string
456
    {
457 1
        $acpt = $this->getPreferredResponseLanguages(true);
458 1
        foreach ($acpt as $lang) {
459 1
            if ($allowed === null) {
460 1
                return $lang;
461
            }
462
            if (in_array($lang, $allowed)) {
463
                return $lang;
464
            }
465
        }
466 1
        return $default;
467
    }
468
    /**
469
     * Get the prefered response formats.
470
     * @param  string                    $default the default value to return if the Accept header is not present.
471
     * @return string[]                  the desired response formats
472
     */
473 1
    public function getPreferredResponseFormats($default = 'text/html')
474
    {
475
        // parse accept header (uses default instead of 406 header)
476 1
        $acpt = $this->getHeaderLine('Accept') ?: $default;
477 1
        $acpt = explode(',', $acpt);
478 1
        foreach ($acpt as $k => $v) {
479 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
480 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
481 1
            $v[0] = $v[0];
482 1
            $v[2] = $k;
483 1
            $acpt[$k] = $v;
484
        }
485
        usort($acpt, function ($a, $b) {
486 1
            if ($a[1] > $b[1]) {
487 1
                return -1;
488
            }
489 1
            if ($a[1] < $b[1]) {
490
                return 1;
491
            }
492 1
            return $a[2] < $b[2] ? -1 : 1;
493 1
        });
494
        $acpt = array_map(function ($v) {
495 1
            return strtolower($v[0]);
496 1
        }, $acpt);
497 1
        $acpt = array_filter($acpt, function ($v) {
498 1
            return $v !== '*/*';
499 1
        });
500 1
        return array_unique($acpt);
501
    }
502
    /**
503
     * Get the preffered response language (parses the Accept-Language header if present).
504
     * @param  string       $default the default code to return if the header is not found
505
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
506
     * @return string       the prefered language code
507
     */
508 1
    public function getPreferredResponseFormat(string $default = 'text/html', array $allowed = null) : string
509
    {
510
        // parse accept header (uses default instead of 406 header)
511 1
        $acpt = $this->getPreferredResponseFormats();
512 1
        foreach ($acpt as $format) {
513 1
            if ($allowed === null) {
514 1
                return $format;
515
            }
516
            if (in_array($format, $allowed)) {
517
                return $format;
518
            }
519
        }
520
        return $default;
521
    }
522
}
523