Completed
Push — master ( 145ada...967fa1 )
by Ivan
04:12
created

Request::getCertificateNumber()   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
    protected $certficateNumber;
14
    protected $certficateData;
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  = \Zend\Diactoros\normalizeServer($server ?: $_SERVER);
33
        $files   = \Zend\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  = \Zend\Diactoros\marshalMethodFromSapi($server);
55
        $uri     = \Zend\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
            \Zend\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 ZendUri($uri);
189 1
        return new static(
190 1
            [],
191 1
            \Zend\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
                $tmp[] = $value;
225
            } else {
226 2
                $tmp = $value;
227
            }
228
        }
229 2
        return $data;
230
    }
231 1
    private static function parseCookieHeader($cookieHeader)
232
    {
233 1
        preg_match_all('(
234
            (?:^\\n?[ \t]*|;[ ])
235
            (?P<name>[!#$%&\'*+-.0-9A-Z^_`a-z|~]+)
236
            =
237
            (?P<DQUOTE>"?)
238
                (?P<value>[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)
239
            (?P=DQUOTE)
240
            (?=\\n?[ \t]*$|;[ ])
241 1
        )x', $cookieHeader, $matches, PREG_SET_ORDER);
242
243 1
        $cookies = [];
244
245 1
        if (is_array($matches)) {
246 1
            foreach ($matches as $match) {
247 1
                $cookies[$match['name']] = urldecode($match['value']);
248
            }
249
        }
250
251 1
        return $cookies;
252
    }
253 1
    public function __construct(
254
        array $serverParams = [],
255
        array $uploadedFiles = [],
256
        $uri = null,
257
        $method = null,
258
        $body = 'php://input',
259
        array $headers = [],
260
        array $cookies = [],
261
        array $queryParams = [],
262
        $parsedBody = null,
263
        $protocol = '1.1',
264
        string $certficateNumber = null,
265
        string $certficateData = null
266
    ) {
267 1
        $uri = new Uri((string)$uri);
268 1
        parent::__construct(
269 1
            $serverParams,
270 1
            $uploadedFiles,
271 1
            $uri,
272 1
            $method,
273 1
            $body,
274 1
            $headers,
275 1
            $cookies,
276 1
            $queryParams,
277 1
            $parsedBody,
278 1
            $protocol
279
        );
280 1
        $this->certificateNumber = $certficateNumber ? strtoupper(ltrim(trim($certficateNumber), '0')) : null;
0 ignored issues
show
Bug introduced by
The property certificateNumber does not seem to exist. Did you mean certficateNumber?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
281 1
        $this->certficateData = $certficateData;
282 1
    }
283 1
    protected function cleanValue($value, $mode = null)
284
    {
285 1
        if (is_array($value)) {
286
            $temp = [];
287
            foreach ($value as $k => $v) {
288
                $temp[$k] = $this->cleanValue($v, $mode);
289
            }
290
            return $temp;
291
        }
292
        // normalize newlines
293 1
        if (strpos((string)$value, "\r") !== false) {
294
            $value = str_replace(array("\r\n", "\r", "\r\n\n"), PHP_EOL, $value);
295
        }
296
        // remove invalid utf8 chars
297 1
        if (preg_match('/[^\x00-\x7F]/S', $value) != 0) {
298
            $temp = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
299
            if ($temp !== false) {
300
                $value = $temp;
301
            }
302
        }
303
        // remove non-printable chars
304
        do {
305 1
            $count = 0;
306 1
            $value = preg_replace(['/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S'], '', $value, -1, $count);
307 1
        } while ((int)$count > 0);
308
309
        switch ($mode) {
310 1
            case 'int':
311 1
                $value = (int) $value;
312 1
                break;
313 1
            case 'float':
314
                $value = (float) $value;
315
                break;
316 1
            case 'nohtml':
317
                $value = strip_tags((string) $value);
318
                break;
319 1
            case 'escape':
320
                $value = htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
321
                break;
322 1
            case 'string':
323
                $value = (string) $value;
324
                break;
325 1
            case 'raw':
326
            default:
327 1
                break;
328
        }
329
330 1
        return $value;
331
    }
332 1
    protected function getValue(array $collection, $key, $default, $mode)
333
    {
334 1
        if ($key === null) {
335
            return $this->cleanValue($collection, $mode);
336
        }
337 1
        return isset($collection[$key]) ? $this->cleanValue($collection[$key], $mode) : $default;
338
    }
339
    /**
340
     * Gets a value from a cookie that came with the request
341
     * @param  string    $key     the cookie name
342
     * @param  mixed     $default optional default value to return if the key is not present (default to `null`)
343
     * @param  string    $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
344
     * @return mixed             the value (or values)
345
     */
346 1
    public function getCookie($key = null, $default = null, $mode = null)
347
    {
348 1
        return $this->getValue($this->getCookieParams(), $key, $default, $mode);
349
    }
350
    /**
351
     * Get a GET param from the request URL
352
     * @param  string   $key     the GET param name
353
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
354
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
355
     * @return mixed             the value (or values)
356
     */
357 1
    public function getQuery($key = null, $default = null, $mode = null)
358
    {
359 1
        return $this->getValue($this->getQueryParams(), $key, $default, $mode);
360
    }
361
    /**
362
     * Get a param from the request body (if it is in JSON format it will be parsed out as well)
363
     * @param  string   $key     the param name
364
     * @param  mixed    $default optional default value to return if the key is not present (default to `null`)
365
     * @param  string   $mode    optional cleanup of the value, available modes are: int, float, nohtml, escape, string
366
     * @return mixed             the value (or values if no key was specified)
367
     */
368
    public function getPost($key = null, $default = null, $mode = null)
369
    {
370
        $body = $this->getParsedBody();
371
        if (!is_array($body)) {
372
            $body = [];
373
        }
374
        return $this->getValue($body, $key, $default, $mode);
375
    }
376
    /**
377
     * Get any authorization details supplied with the request.
378
     * @return array|null           array of extracted values or null (possible keys are username, password and token)
379
     */
380
    public function getAuthorization()
381
    {
382
        if (!$this->hasHeader('Authorization')) {
383
            return null;
384
        }
385
        $temp = explode(' ', trim($this->getHeaderLine('Authorization')), 2);
386
        switch (strtolower($temp[0])) {
387
            case 'basic':
388
                $temp[1] = base64_decode($temp[1]);
389
                $temp[1] = explode(':', $temp[1], 2);
390
                return ['username' => $temp[1][0], 'password' => $temp[1][1] ?? null];
391
            case 'token':
392
            case 'oauth':
393
            case 'bearer':
394
                return ['token' => $temp[1] ?? null];
395
            default:
396
                return null;
397
        }
398
    }
399
    /**
400
     * Get the Uri object
401
     * @return Uri
402
     */
403
    public function getUrl()
404
    {
405
        return $this->getUri();
406
    }
407
    /**
408
     * Determine if this is an AJAX request
409
     * @return boolean is the request AJAX
410
     */
411 1
    public function isAjax()
412
    {
413 1
        return ($this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest');
414
    }
415
    /**
416
     * Determine if this is an CORS request
417
     * @return boolean is the request CORS
418
     */
419 1
    public function isCors()
420
    {
421 1
        if (!$this->hasHeader('Origin')) {
422 1
            return false;
423
        }
424
        $origin = parse_url($this->getHeaderLine('Origin'));
425
        $host   = $this->getUri()->getHost();
426
        $scheme = $this->getUri()->getScheme();
427
        return (
428
            !$host ||
429
            strtolower($origin['scheme']?? '') !== strtolower($scheme) ||
430
            strpos(strtolower($origin['host'] ?? ''), strtolower($host)) === false
431
        );
432
    }
433
    /**
434
     * Get the prefered response languages (parses the Accept-Language header if present).
435
     * @param  bool    $shortNames should values like "en-US", be truncated to "en", defaults to true
436
     * @return array   array of ordered lowercase language codes
437
     */
438 1
    public function getPreferredResponseLanguages(bool $shortNames = true) : array
439
    {
440 1
        $acpt = $this->getHeaderLine('Accept-Language') ?: '*';
441 1
        $acpt = explode(',', $acpt);
442 1
        foreach ($acpt as $k => $v) {
443 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
444 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
445 1
            $v[0] = $shortNames ? explode('-', $v[0], 2)[0] : $v[0];
446 1
            $v[2] = $k;
447 1
            $acpt[$k] = $v;
448
        }
449
        usort($acpt, function ($a, $b) {
450 1
            if ($a[1] > $b[1]) {
451 1
                return -1;
452
            }
453
            if ($a[1] < $b[1]) {
454
                return 1;
455
            }
456
            return $a[2] < $b[2] ? -1 : 1;
457 1
        });
458
        $acpt = array_map(function ($v) {
459 1
            return strtolower($v[0]);
460 1
        }, $acpt);
461
        $acpt = array_filter($acpt, function ($v) {
462 1
            return $v !== '*';
463 1
        });
464 1
        return array_unique($acpt);
465
    }
466
    /**
467
     * Get the preffered response language (parses the Accept-Language header if present).
468
     * @param  string       $default the default code to return if the header is not found
469
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
470
     * @return string       the prefered language code
471
     */
472 1
    public function getPreferredResponseLanguage(string $default = 'en', array $allowed = null) : string
473
    {
474 1
        $acpt = $this->getPreferredResponseLanguages(true);
475 1
        foreach ($acpt as $lang) {
476 1
            if ($allowed === null) {
477 1
                return $lang;
478
            }
479
            if (in_array($lang, $allowed)) {
480
                return $lang;
481
            }
482
        }
483 1
        return $default;
484
    }
485
    /**
486
     * Get the prefered response formats.
487
     * @param  string                    $default the default value to return if the Accept header is not present.
488
     * @return string[]                  the desired response formats
489
     */
490 1
    public function getPreferredResponseFormats($default = 'text/html')
491
    {
492
        // parse accept header (uses default instead of 406 header)
493 1
        $acpt = $this->getHeaderLine('Accept') ?: $default;
494 1
        $acpt = explode(',', $acpt);
495 1
        foreach ($acpt as $k => $v) {
496 1
            $v = array_pad(explode(';', $v, 2), 2, 'q=1');
497 1
            $v[1] = (float) array_pad(explode('q=', $v[1], 2), 2, '1')[1];
498 1
            $v[0] = $v[0];
499 1
            $v[2] = $k;
500 1
            $acpt[$k] = $v;
501
        }
502
        usort($acpt, function ($a, $b) {
503 1
            if ($a[1] > $b[1]) {
504 1
                return -1;
505
            }
506 1
            if ($a[1] < $b[1]) {
507
                return 1;
508
            }
509 1
            return $a[2] < $b[2] ? -1 : 1;
510 1
        });
511
        $acpt = array_map(function ($v) {
512 1
            return strtolower($v[0]);
513 1
        }, $acpt);
514 1
        $acpt = array_filter($acpt, function ($v) {
515 1
            return $v !== '*/*';
516 1
        });
517 1
        return array_unique($acpt);
518
    }
519
    /**
520
     * Get the preffered response language (parses the Accept-Language header if present).
521
     * @param  string       $default the default code to return if the header is not found
522
     * @param  array|null   $allowed an optional list of lowercase language codes to intersect with, defaults to null
523
     * @return string       the prefered language code
524
     */
525 1
    public function getPreferredResponseFormat(string $default = 'text/html', array $allowed = null) : string
526
    {
527
        // parse accept header (uses default instead of 406 header)
528 1
        $acpt = $this->getPreferredResponseFormats();
529 1
        foreach ($acpt as $format) {
530 1
            if ($allowed === null) {
531 1
                return $format;
532
            }
533
            if (in_array($format, $allowed)) {
534
                return $format;
535
            }
536
        }
537
        return $default;
538
    }
539
    public function hasCertificate()
540
    {
541
        return $this->certficateNumber !== null;
542
    }
543
    public function getCertificateNumber()
544
    {
545
        return $this->certficateNumber;
546
    }
547
    public function getCertificate()
548
    {
549
        return $this->certficateData;
550
    }
551
    public function withCertificate(string $number, string $data = null)
552
    {
553
        $ret = clone $this;
554
        $ret->certficateNumber = strtoupper(ltrim(trim($number), '0'));
555
        $ret->certficateData = $data;
556
        return $ret;
557
    }
558
}
559