Passed
Push — master ( ffe39b...f5e112 )
by Alexander
02:34
created

ServerRequestFactory::normalizeHeaderName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\ServerRequestFactoryInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Message\StreamFactoryInterface;
11
use Psr\Http\Message\StreamInterface;
12
use Psr\Http\Message\UploadedFileFactoryInterface;
13
use Psr\Http\Message\UriFactoryInterface;
14
use Psr\Http\Message\UriInterface;
15
use RuntimeException;
16
17
use function array_filter;
18
use function array_key_exists;
19
use function explode;
20
use function fopen;
21
use function function_exists;
22
use function getallheaders;
23
use function is_array;
24
use function is_resource;
25
use function is_string;
26
use function preg_match;
27
use function str_replace;
28
use function strncmp;
29
use function strtolower;
30
use function substr;
31
use function ucwords;
32
33
final class ServerRequestFactory
34
{
35
    private ServerRequestFactoryInterface $serverRequestFactory;
36
    private UriFactoryInterface $uriFactory;
37
    private UploadedFileFactoryInterface $uploadedFileFactory;
38
    private StreamFactoryInterface $streamFactory;
39
40 34
    public function __construct(
41
        ServerRequestFactoryInterface $serverRequestFactory,
42
        UriFactoryInterface $uriFactory,
43
        UploadedFileFactoryInterface $uploadedFileFactory,
44
        StreamFactoryInterface $streamFactory
45
    ) {
46 34
        $this->serverRequestFactory = $serverRequestFactory;
47 34
        $this->uriFactory = $uriFactory;
48 34
        $this->uploadedFileFactory = $uploadedFileFactory;
49 34
        $this->streamFactory = $streamFactory;
50 34
    }
51
52 17
    public function createFromGlobals(): ServerRequestInterface
53
    {
54 17
        return $this->createFromParameters(
55
            $_SERVER,
56 17
            $this->getHeadersFromGlobals(),
57
            $_COOKIE,
58
            $_GET,
59
            $_POST,
60
            $_FILES,
61 17
            fopen('php://input', 'rb') ?: null
62
        );
63
    }
64
65
    /**
66
     * @param array $server
67
     * @param array $headers
68
     * @param array $cookies
69
     * @param array $get
70
     * @param array $post
71
     * @param array $files
72
     * @param resource|StreamInterface|string|null $body
73
     *
74
     * @return ServerRequestInterface
75
     */
76 34
    public function createFromParameters(array $server, array $headers = [], array $cookies = [], array $get = [], array $post = [], array $files = [], $body = null): ServerRequestInterface
77
    {
78 34
        $method = $server['REQUEST_METHOD'] ?? null;
79 34
        if ($method === null) {
80 1
            throw new RuntimeException('Unable to determine HTTP request method.');
81
        }
82
83 33
        $uri = $this->getUri($server);
84 33
        $request = $this->serverRequestFactory->createServerRequest($method, $uri, $server);
85
86 33
        foreach ($headers as $name => $value) {
87 8
            if ($name === 'Host' && $request->hasHeader('Host')) {
88 8
                continue;
89
            }
90
91
            $request = $request->withAddedHeader($name, $value);
92
        }
93
94 33
        $protocol = '1.1';
95 33
        if (array_key_exists('SERVER_PROTOCOL', $server) && $server['SERVER_PROTOCOL'] !== '') {
96 2
            $protocol = str_replace('HTTP/', '', $server['SERVER_PROTOCOL']);
97
        }
98
99
        $request = $request
100 33
            ->withProtocolVersion($protocol)
101 33
            ->withQueryParams($get)
102 33
            ->withParsedBody($post)
103 33
            ->withCookieParams($cookies)
104 33
            ->withUploadedFiles($this->getUploadedFilesArray($files));
105
106 33
        if ($body === null) {
107 16
            return $request;
108
        }
109
110 17
        if (is_resource($body)) {
111 17
            $body = $this->streamFactory->createStreamFromResource($body);
112
        } elseif (is_string($body)) {
113
            $body = $this->streamFactory->createStream($body);
114
        } elseif (!$body instanceof StreamInterface) {
0 ignored issues
show
introduced by
$body is always a sub-type of Psr\Http\Message\StreamInterface.
Loading history...
115
            throw new InvalidArgumentException('Body parameter for ServerRequestFactory::createFromParameters() must be instance of StreamInterface, resource or null.');
116
        }
117
118 17
        return $request->withBody($body);
119
    }
120
121 33
    private function getUri(array $server): UriInterface
122
    {
123 33
        $uri = $this->uriFactory->createUri();
124
125 33
        if (array_key_exists('HTTPS', $server) && $server['HTTPS'] !== '' && $server['HTTPS'] !== 'off') {
126 4
            $uri = $uri->withScheme('https');
127
        } else {
128 29
            $uri = $uri->withScheme('http');
129
        }
130
131 33
        if (isset($server['HTTP_HOST'])) {
132 15
            if (1 === preg_match('/^(.+):(\d+)$/', $server['HTTP_HOST'], $matches)) {
133 6
                $uri = $uri->withHost($matches[1])->withPort((int) $matches[2]);
134
            } else {
135 15
                $uri = $uri->withHost($server['HTTP_HOST']);
136
            }
137 18
        } elseif (isset($server['SERVER_NAME'])) {
138 2
            $uri = $uri->withHost($server['SERVER_NAME']);
139
        }
140
141 33
        if (isset($server['SERVER_PORT'])) {
142 2
            $uri = $uri->withPort($server['SERVER_PORT']);
143
        }
144
145 33
        if (isset($server['REQUEST_URI'])) {
146 2
            $uri = $uri->withPath(explode('?', $server['REQUEST_URI'])[0]);
147
        }
148
149 33
        if (isset($server['QUERY_STRING'])) {
150 2
            $uri = $uri->withQuery($server['QUERY_STRING']);
151
        }
152
153 33
        return $uri;
154
    }
155
156 17
    private function getHeadersFromGlobals(): array
157
    {
158 17
        if (function_exists('getallheaders')) {
159
            $headers = getallheaders();
160
            return $headers === false ? [] : array_filter($headers, static fn (string $value): bool => $value !== '');
0 ignored issues
show
Bug introduced by
It seems like $headers can also be of type true; however, parameter $array of array_filter() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

160
            return $headers === false ? [] : array_filter(/** @scrutinizer ignore-type */ $headers, static fn (string $value): bool => $value !== '');
Loading history...
161
        }
162
163 17
        $headers = [];
164
165 17
        foreach ($_SERVER as $name => $value) {
166 17
            if ($value === '') {
167
                continue;
168
            }
169
170 17
            if (strncmp($name, 'REDIRECT_', 9) === 0) {
171
                $name = substr($name, 9);
172
173
                if (array_key_exists($name, $_SERVER)) {
174
                    continue;
175
                }
176
            }
177
178 17
            if (strncmp($name, 'HTTP_', 5) === 0) {
179 8
                $headers[$this->normalizeHeaderName(substr($name, 5))] = $value;
180 8
                continue;
181
            }
182
183 17
            if (strncmp($name, 'CONTENT_', 8) === 0) {
184
                $headers[$this->normalizeHeaderName($name)] = $value;
185
                continue;
186
            }
187
        }
188
189 17
        return $headers;
190
    }
191
192 8
    private function normalizeHeaderName(string $name): string
193
    {
194 8
        return str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $name))));
195
    }
196
197 33
    private function getUploadedFilesArray(array $filesArray): array
198
    {
199
        // TODO: simplify?
200 33
        $files = [];
201 33
        foreach ($filesArray as $class => $info) {
202 17
            $files[$class] = [];
203 17
            $this->populateUploadedFileRecursive($files[$class], $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
204
        }
205 33
        return $files;
206
    }
207
208
    /**
209
     * Populates uploaded files array from $_FILE data structure recursively.
210
     *
211
     * @param array $files uploaded files array to be populated.
212
     * @param mixed $names file names provided by PHP
213
     * @param mixed $tempNames temporary file names provided by PHP
214
     * @param mixed $types file types provided by PHP
215
     * @param mixed $sizes file sizes provided by PHP
216
     * @param mixed $errors uploading issues provided by PHP
217
     */
218 17
    private function populateUploadedFileRecursive(array &$files, $names, $tempNames, $types, $sizes, $errors): void
219
    {
220 17
        if (is_array($names)) {
221 17
            foreach ($names as $i => $name) {
222 17
                $files[$i] = [];
223 17
                $this->populateUploadedFileRecursive($files[$i], $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
224
            }
225
        } else {
226
            try {
227 17
                $stream = $this->streamFactory->createStreamFromFile($tempNames);
228 17
            } catch (RuntimeException $e) {
229 17
                $stream = $this->streamFactory->createStream();
230
            }
231
232 17
            $files = $this->uploadedFileFactory->createUploadedFile(
233 17
                $stream,
234 17
                (int)$sizes,
235 17
                (int)$errors,
236
                $names,
237
                $types
238
            );
239
        }
240 17
    }
241
}
242