Passed
Push — master ( 157485...b7615a )
by Nikolaos
09:23
created

ServerRequestFactory::load()   B

Complexity

Conditions 8
Paths 64

Size

Total Lines 69
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
eloc 43
dl 0
loc 69
ccs 0
cts 43
cp 0
rs 7.9875
c 0
b 0
f 0
cc 8
nc 64
nop 5
crap 72

How to fix   Long Method   

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
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 *
11
 * Implementation of this file has been influenced by Zend Diactoros
12
 * @link    https://github.com/zendframework/zend-diactoros
13
 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md
14
 */
15
16
declare(strict_types=1);
17
18
namespace Phalcon\Http\Message;
19
20
use Phalcon\Collection;
21
use Phalcon\Helper\Arr;
22
use Phalcon\Http\Message\Exception\InvalidArgumentException;
23
use Psr\Http\Message\ServerRequestFactoryInterface;
24
use Psr\Http\Message\ServerRequestInterface;
25
use Psr\Http\Message\UriInterface;
26
use Psr\Http\Message\UploadedFileInterface;
27
28
use function is_array;
29
use function is_object;
30
31
/**
32
 * PSR-17 ServerRequestFactory
33
 */
34
class ServerRequestFactory implements ServerRequestFactoryInterface
35
{
36
    /**
37
     * Create a new server request.
38
     *
39
     * Note that server-params are taken precisely as given - no
40
     * parsing/processing of the given values is performed, and, in particular,
41
     * no attempt is made to determine the HTTP method or URI, which must be
42
     * provided explicitly.
43
     *
44
     * @param string              $method       The HTTP method associated with
45
     *                                          the request.
46
     * @param UriInterface|string $uri          The URI associated with the
47
     *                                          request. If the value is a
48
     *                                          string, the factory MUST create
49
     *                                          a UriInterface instance based
50
     *                                          on it.
51
     * @param array               $serverParams Array of SAPI parameters with
52
     *                                          which to seed the generated
53
     *                                          request instance.
54
     *
55
     * @return ServerRequestInterface
56
     */
57
    public function createServerRequest(
58
        string $method,
59
        $uri,
60
        array $serverParams = []
61
    ): ServerRequestInterface {
62
63
        return new ServerRequest($method, $uri, $serverParams);
64
    }
65
66
    /**
67
     * Create a request from the supplied superglobal values.
68
     *
69
     * If any argument is not supplied, the corresponding superglobal value will
70
     * be used.
71
     *
72
     * @param array $server  $_SERVER superglobal
73
     * @param array $get     $_GET superglobal
74
     * @param array $post    $_POST superglobal
75
     * @param array $cookies $_COOKIE superglobal
76
     * @param array $files   $_FILES superglobal
77
     *
78
     * @return ServerRequest
79
     * @see fromServer()
80
     */
81
    public function load(
82
        array $server = null,
83
        array $get = null,
84
        array $post = null,
85
        array $cookies = null,
86
        array $files = null
87
    ): ServerRequest {
88
        $globalCookies = [];
89
        $globalFiles   = [];
90
        $globalGet     = [];
91
        $globalPost    = [];
92
        $globalServer  = [];
93
94
        /**
95
         * Ensure that superglobals are defined if not
96
         */
97
        if (!empty($_COOKIE)) {
98
            $globalCookies = $_COOKIE;
99
        }
100
101
        if (!empty($_FILES)) {
102
            $globalFiles = $_FILES;
103
        }
104
105
        if (!empty($_GET)) {
106
            $globalGet = $_GET;
107
        }
108
109
        if (!empty($_POST)) {
110
            $globalPost = $_POST;
111
        }
112
113
        if (!empty($_SERVER)) {
114
            $globalServer = $_SERVER;
115
        }
116
117
        $server            = $this->checkNullArray($server, $globalServer);
118
        $files             = $this->checkNullArray($files, $globalFiles);
119
        $cookies           = $this->checkNullArray($cookies, $globalCookies);
120
        $get               = $this->checkNullArray($get, $globalGet);
121
        $post              = $this->checkNullArray($post, $globalPost);
122
        $serverCollection  = $this->parseServer($server);
123
        /** @var string $method */
124
        $method            = $serverCollection->get(
125
            "REQUEST_METHOD",
126
            "GET",
127
            "string"
128
        );
129
        $protocol          = $this->parseProtocol($serverCollection);
130
        $headers           = $this->parseHeaders($serverCollection);
131
        $filesCollection   = $this->parseUploadedFiles($files);
132
        $cookiesCollection = $cookies;
133
134
        if (empty($cookies) && $headers->has("cookie")) {
135
            $cookieHeader      = (string) $headers->get("cookie");
136
            $cookiesCollection = $this->parseCookieHeader($cookieHeader);
137
        }
138
139
        return new ServerRequest(
140
            $method,
141
            $this->parseUri($serverCollection, $headers),
142
            $serverCollection->toArray(),
143
            "php://input",
144
            $headers->toArray(),
145
            $cookiesCollection,
146
            $get,
147
            $filesCollection->toArray(),
148
            $post,
149
            $protocol
150
        );
151
    }
152
153
    /**
154
     * Returns the apache_request_headers if it exists
155
     *
156
     * @return array|false
157
     */
158
    protected function getHeaders()
159
    {
160
        if (function_exists("apache_request_headers")) {
161
            return apache_request_headers();
162
        }
163
164
        return false;
165
    }
166
167
    /**
168
     * Calculates the host and port from the headers or the server superglobal
169
     *
170
     * @param Collection $server
171
     * @param Collection $headers
172
     *
173
     * @return array
174
     */
175
    private function calculateUriHost(
176
        Collection $server,
177
        Collection $headers
178
    ): array {
179
        $defaults = ["", null];
180
181
        if ($this->getHeader($headers, "host", false)) {
182
            $host = $this->getHeader($headers, "host");
183
184
            return $this->calculateUriHostFromHeader($host);
185
        }
186
187
        if (!$server->has("SERVER_NAME")) {
188
            return $defaults;
189
        }
190
191
        $host = $server->get("SERVER_NAME");
192
        $port = $server->get("SERVER_PORT", null);
193
194
        return [$host, $port];
195
    }
196
197
    /**
198
     * Get the host and calculate the port if present from the header
199
     *
200
     * @param string $host
201
     *
202
     * @return array
203
     */
204
    private function calculateUriHostFromHeader(string $host): array
205
    {
206
        $port = null;
207
208
        // works for regname, IPv4 & IPv6
209
        if (preg_match("|:(\d+)$|", $host, $matches)) {
210
            $host = substr($host, 0, -1 * (strlen($matches[1]) + 1));
211
            $port = (int) $matches[1];
212
        }
213
214
        return [$host, $port];
215
    }
216
217
    /**
218
     * Get the path from the request from IIS7/Rewrite, REQUEST_URL or
219
     * ORIG_PATH_INFO
220
     *
221
     * @param Collection $server
222
     *
223
     * @return string
224
     */
225
    private function calculateUriPath(Collection $server): string
226
    {
227
        /**
228
         * IIS7 with URL Rewrite - double slash
229
         */
230
        $iisRewrite   = $server->get("IIS_WasUrlRewritten", null);
231
        $unencodedUrl = $server->get("UNENCODED_URL", "");
232
233
        if ("1" === $iisRewrite && !empty($unencodedUrl)) {
234
            return $unencodedUrl;
235
        }
236
237
        /**
238
         * REQUEST_URI
239
         */
240
        $requestUri = $server->get("REQUEST_URI", null);
241
242
        if (null !== $requestUri) {
243
            return preg_replace(
244
                "#^[^/:]+://[^/]+#",
245
                "",
246
                $requestUri
247
            );
248
        }
249
250
        /**
251
         * ORIG_PATH_INFO
252
         */
253
        $origPathInfo = $server->get("ORIG_PATH_INFO", null);
254
        if (empty($origPathInfo)) {
255
            return "/";
256
        }
257
258
        return $origPathInfo;
259
    }
260
261
    /**
262
     * Get the query string from the server array
263
     *
264
     * @param Collection $server
265
     *
266
     * @return string
267
     */
268
    private function calculateUriQuery(Collection $server): string
269
    {
270
        return ltrim($server->get("QUERY_STRING", ""), "?");
271
    }
272
273
    /**
274
     * Calculates the scheme from the server variables
275
     *
276
     * @param Collection $server
277
     * @param Collection $headers
278
     *
279
     * @return string
280
     */
281
    private function calculateUriScheme(
282
        Collection $server,
283
        Collection $headers
284
    ): string {
285
        // URI scheme
286
        $scheme  = "https";
287
        $isHttps = true;
288
        if ($server->has("HTTPS")) {
289
            $isHttps = (string) $server->get("HTTPS", "on");
290
            $isHttps = "off" !== strtolower($isHttps);
291
        }
292
293
        $header = $this->getHeader($headers, "x-forwarded-proto", "https");
294
        if (!$isHttps || "https" !== $header) {
295
            $scheme = "http";
296
        }
297
298
        return $scheme;
299
    }
300
301
    /**
302
     * Checks the source if it null and returns the super, otherwise the source
303
     * array
304
     *
305
     * @param mixed $source
306
     * @param array $super
307
     *
308
     * @return array
309
     */
310
    private function checkNullArray($source, array $super): array
311
    {
312
        if (null === $source) {
313
            return $super;
314
        }
315
316
        return $source;
317
    }
318
319
    /**
320
     * Create an UploadedFile object from an $_FILES array element
321
     *
322
     * @param array $file The $_FILES element
323
     *
324
     * @return UploadedFile
325
     *
326
     * @throws InvalidArgumentException If one of the elements is missing
327
     */
328
    private function createUploadedFile(array $file): UploadedFile
329
    {
330
        if (
331
            !isset($file["tmp_name"]) ||
332
            !isset($file["size"]) ||
333
            !isset($file["error"])
334
        ) {
335
            throw new InvalidArgumentException(
336
                "The file array must contain tmp_name, size and error; " .
337
                "one or more are missing"
338
            );
339
        }
340
341
        $name = Arr::get($file, "name");
342
        $type = Arr::get($file, "type");
343
344
        return new UploadedFile(
345
            $file["tmp_name"],
346
            $file["size"],
347
            $file["error"],
348
            $name,
349
            $type
350
        );
351
    }
352
353
    /**
354
     * Returns a header
355
     *
356
     * @param Collection $headers
357
     * @param string     $name
358
     * @param mixed|null $defaultValue
359
     *
360
     * @return mixed|string
361
     */
362
    private function getHeader(
363
        Collection $headers,
364
        string $name,
365
        $defaultValue = null
366
    ) {
367
        $value = $headers->get($name, $defaultValue);
368
369
        if (is_array($value)) {
370
            $value = implode(",", $value);
371
        }
372
373
        return $value;
374
    }
375
376
    /**
377
     * Parse a cookie header according to RFC 6265.
378
     *
379
     * @param string $cookieHeader A string cookie header value.
380
     *
381
     * @return array key/value cookie pairs.
382
     *
383
     */
384
    private function parseCookieHeader(string $cookieHeader): array
385
    {
386
        $cookies = [];
387
        parse_str(
388
            strtr(
389
                $cookieHeader,
390
                [
391
                    "&" => "%26",
392
                    "+" => "%2B",
393
                    ";" => "&"
394
                ]
395
            ),
396
            $cookies
397
        );
398
399
        return $cookies;
400
    }
401
402
    /**
403
     * Processes headers from SAPI
404
     *
405
     * @param Collection $server
406
     *
407
     * @return Collection
408
     */
409
    private function parseHeaders(Collection $server): Collection
410
    {
411
        /**
412
         * @todo Figure out why server is not iterable
413
         */
414
        $headers     = new Collection();
415
        $serverArray = $server->toArray();
416
417
        foreach ($serverArray as $key => $value) {
418
            if ("" !== $value) {
419
                /**
420
                 * Apache prefixes environment variables with REDIRECT_
421
                 * if they are added by rewrite rules
422
                 */
423
                if (strpos($key, "REDIRECT_") === 0) {
424
                    $key = substr($key, 9);
425
426
                    /**
427
                     * We will not overwrite existing variables with the
428
                     * prefixed versions, though
429
                     */
430
                    if (true === $server->has($key)) {
431
                        continue;
432
                    }
433
                }
434
435
                if (strpos($key, "HTTP_") === 0) {
436
                    $name = str_replace(
437
                        "_",
438
                        "-",
439
                        strtolower(substr($key, 5))
440
                    );
441
442
                    $headers->set($name, $value);
443
                    continue;
444
                }
445
446
                if (strpos($key, "CONTENT_") === 0) {
447
                    $name = "content-" . strtolower(substr($key, 8));
448
449
                    $headers->set($name, $value);
450
                    continue;
451
                }
452
            }
453
        }
454
455
        return $headers;
456
    }
457
458
    /**
459
     * Parse the $_SERVER array amd check the server protocol. Raise an
460
     *
461
     * @param Collection $server The server variables
462
     *
463
     * @return string
464
     */
465
    private function parseProtocol(Collection $server): string
466
    {
467
        if (true !== $server->has("SERVER_PROTOCOL")) {
468
            return "1.1";
469
        }
470
471
        $protocol      = (string) $server->get("SERVER_PROTOCOL", "HTTP/1.1");
472
        $localProtocol = strtolower($protocol);
473
        $protocols     = [
474
            "1.0" => 1,
475
            "1.1" => 1,
476
            "2.0" => 1,
477
            "3.0" => 1
478
        ];
479
480
        if (substr($localProtocol, 0, 5) !== "http/") {
481
            throw new InvalidArgumentException(
482
                "Incorrect protocol value " . $protocol
483
            );
484
        }
485
486
        $localProtocol = str_replace("http/", "", $localProtocol);
487
488
        if (!isset($protocols[$localProtocol])) {
489
            throw new InvalidArgumentException(
490
                "Unsupported protocol " . $protocol
491
            );
492
        }
493
494
        return $localProtocol;
495
    }
496
497
    /**
498
     * Parse the $_SERVER array amd return it back after looking for the
499
     * authorization header
500
     *
501
     * @param array $server Either verbatim, or with an added
502
     *                      HTTP_AUTHORIZATION header.
503
     *
504
     * @return Collection
505
     */
506
    private function parseServer(array $server): Collection
507
    {
508
        $collection = new Collection($server);
509
        $headers    = $this->getHeaders();
510
511
        if (!$collection->has("HTTP_AUTHORIZATION") && false !== $headers) {
512
            $headersCollection = new Collection($headers);
513
514
            if ($headersCollection->has("Authorization")) {
515
                $collection->set(
516
                    "HTTP_AUTHORIZATION",
517
                    $headersCollection->get("Authorization")
518
                );
519
            }
520
        }
521
522
        return $collection;
523
    }
524
525
    /**
526
     * Traverses a $_FILES and creates UploadedFile objects from it. It is used
527
     * recursively
528
     *
529
     * @param array $files
530
     *
531
     * @return Collection
532
     */
533
    private function parseUploadedFiles(array $files): Collection
534
    {
535
        $collection = new Collection();
536
537
        /**
538
         * Loop through the files and check them recursively
539
         */
540
        foreach ($files as $key => $file) {
541
            $key = (string) $key;
542
543
            /**
544
             * UriInterface
545
             */
546
            if (is_object($file) && $file instanceof UploadedFileInterface) {
547
                $collection->set($key, $file);
548
                continue;
549
            }
550
551
            /**
552
             * file is array with 'tmp_name'
553
             */
554
            if (is_array($file) && isset($file["tmp_name"])) {
555
                $collection->set($key, $this->createUploadedFile($file));
556
                continue;
557
            }
558
559
            /**
560
             * file is array of elements - recursion
561
             */
562
            if (is_array($file)) {
563
                $data = $this->parseUploadedFiles($file);
564
565
                $collection->set($key, $data->toArray());
566
                continue;
567
            }
568
        }
569
570
        return $collection;
571
    }
572
573
    /**
574
     * Calculates the Uri from the server superglobal or the headers
575
     *
576
     * @param Collection $server
577
     * @param Collection $headers
578
     *
579
     * @return Uri
580
     */
581
    private function parseUri(Collection $server, Collection $headers): Uri
582
    {
583
        $uri = new Uri();
584
585
        /**
586
         * Scheme
587
         */
588
        $scheme = $this->calculateUriScheme($server, $headers);
589
        $uri    = $uri->withScheme($scheme);
590
591
        /**
592
         * Host/Port
593
         */
594
        $split = $this->calculateUriHost($server, $headers);
595
        if (!empty($split[0])) {
596
            $uri = $uri->withHost($split[0]);
597
            if (!empty($split[1])) {
598
                $uri = $uri->withPort($split[1]);
599
            }
600
        }
601
602
        /**
603
         * Path
604
         */
605
        $path  = $this->calculateUriPath($server);
606
        $split = explode("#", $path);
607
        $path  = explode("?", $split[0]);
608
        $uri   = $uri->withPath($path[0]);
609
610
        if (count($split) > 1) {
611
            $uri = $uri->withFragment($split[1]);
612
        }
613
614
        /**
615
         * Query
616
         */
617
        $query = $this->calculateUriQuery($server);
618
        $uri   = $uri->withQuery($query);
619
620
        return $uri;
621
    }
622
}
623