Completed
Push — master ( 03e0b9...4c67b7 )
by Ivan
13:00
created

Request::getUri()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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