Completed
Push — master ( 29a6a6...73ebf0 )
by Mihail
15:03
created

ServerRequest.php (2 issues)

1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Http;
14
15
use Koded\Http\Interfaces\Request;
16
use Psr\Http\Message\ServerRequestInterface;
17
18
class ServerRequest extends ClientRequest implements Request
19
{
20
    use CookieTrait, FilesTrait, ValidatableTrait;
21
22
    protected string $serverSoftware = '';
23
    protected array  $attributes     = [];
24
    protected array  $queryParams    = [];
25
26
    protected object|array|null $parsedBody = null;
0 ignored issues
show
The type Koded\Http\null was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
28
    /**
29
     * ServerRequest constructor.
30
     *
31
     * @param array $attributes
32
     */
33
    public function __construct(array $attributes = [])
34
    {
35
        parent::__construct($_SERVER['REQUEST_METHOD'] ?? Request::GET, $this->buildUri());
36
        $this->attributes = $attributes;
37
        $this->extractHttpHeaders($_SERVER);
38
        $this->extractServerData($_SERVER);
39
    }
40
41 52
    public function getServerParams(): array
42
    {
43 52
        return $_SERVER;
44 52
    }
45 52
46 52
    public function getQueryParams(): array
47 52
    {
48
        return $this->queryParams;
49 3
    }
50
51 3
    public function withQueryParams(array $query): static
52
    {
53
        $instance              = clone $this;
54 3
        $instance->queryParams = \array_merge($instance->queryParams, $query);
55
        return $instance;
56 3
    }
57
58
    public function getParsedBody(): object|array|null
59 2
    {
60
        if ($this->useOnlyPost()) {
61 2
            return $_POST;
62 2
        }
63
        if (false === empty($_POST)) {
64 2
            return $_POST;
65
        }
66
        return $this->parsedBody;
67 17
    }
68
69 17
    public function withParsedBody($data): static
70 2
    {
71
        $instance = clone $this;
72
        if ($this->useOnlyPost()) {
73 15
            $instance->parsedBody = $_POST;
74 5
            return $instance;
75
        }
76
        // If nothing is available for the body
77 10
        if (null === $data) {
78
            $instance->parsedBody = null;
79
            return $instance;
80 13
        }
81
        // Supports array or iterable object
82 13
        if (\is_iterable($data)) {
83
            $instance->parsedBody = \is_array($data) ? $data : \iterator_to_array($data);
84 13
            return $instance;
85 1
        }
86
        if (\is_object($data)) {
87 1
            $instance->parsedBody = $data;
88
            return $instance;
89
        }
90
        throw new \InvalidArgumentException(
91 12
            \sprintf('Unsupported data provided (%s), Expects NULL, array or iterable', \gettype($data))
92 2
        );
93
    }
94 2
95
    public function getAttributes(): array
96
    {
97
        return $this->attributes;
98 10
    }
99 4
100
    public function getAttribute($name, $default = null): mixed
101 4
    {
102
        return $this->attributes[$name] ?? $default;
103
    }
104 6
105 1
    public function withAttribute($name, $value): static
106
    {
107 1
        $instance                    = clone $this;
108
        $instance->attributes[$name] = $value;
109
        return $instance;
110 5
    }
111 5
112
    public function withoutAttribute($name): static
113
    {
114
        $instance = clone $this;
115 3
        unset($instance->attributes[$name]);
116
        return $instance;
117 3
    }
118
119
    public function withAttributes(array $attributes): static
120 3
    {
121
        $instance = clone $this;
122 3
        foreach ($attributes as $name => $value) {
123
            $instance->attributes[$name] = $value;
124
        }
125 4
        return $instance;
126
    }
127 4
128 4
    public function isXHR(): bool
129
    {
130 4
        return 'XMLHTTPREQUEST' === \strtoupper($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '');
131
    }
132
133 2
    protected function buildUri(): Uri
134
    {
135 2
        if (\strpos($_SERVER['REQUEST_URI'] ?? '', '://')) {
136 2
            return new Uri($_SERVER['REQUEST_URI']);
137
        }
138 2
        if ($host = $_SERVER['SERVER_NAME'] ?? $_SERVER['SERVER_ADDR'] ?? '') {
139
            return new Uri('http' . ($_SERVER['HTTPS'] ?? $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? false ? 's' : '')
140
                . '://' . $host
141 1
                . ':' . ($_SERVER['SERVER_PORT'] ?? 80)
142
                . ($_SERVER['REQUEST_URI'] ?? '')
143 1
            );
144
        }
145 1
        return new Uri($_SERVER['REQUEST_URI'] ?? '');
146 1
    }
147
148
    protected function extractHttpHeaders(array $server): void
149 1
    {
150
        foreach ($server as $k => $v) {
151
            // Calisthenics :)
152 2
            \str_starts_with($k, 'HTTP_') && $this->normalizeHeader(\str_replace('HTTP_', '', $k), $v, false);
153
        }
154 2
        unset($this->headers['X-Forwarded-For'], $this->headers['X-Forwarded-Proto']);
155
        unset($this->headersMap['x-forwarded-for'], $this->headersMap['x-forwarded-proto']);
156
        if (isset($server['HTTP_IF_NONE_MATCH'])) {
157 52
            // ETag workaround for various broken Apache2 versions
158
            $this->headers['ETag']    = \str_replace('-gzip', '', $server['HTTP_IF_NONE_MATCH']);
159 52
            $this->headersMap['etag'] = 'ETag';
160 1
        }
161
        if (isset($server['CONTENT_TYPE'])) {
162
            $this->headers['Content-Type']    = \strtolower($server['CONTENT_TYPE']);
163 52
            $this->headersMap['content-type'] = 'Content-Type';
164 50
        }
165 50
        $this->setHost();
166 50
    }
167 50
168
    protected function extractServerData(array $server): void
169
    {
170
        $this->protocolVersion = \str_ireplace('HTTP/', '', $server['SERVER_PROTOCOL'] ?? $this->protocolVersion);
171 5
        $this->serverSoftware  = $server['SERVER_SOFTWARE'] ?? '';
172
        $this->queryParams     = $_GET;
173
        $this->cookieParams    = $_COOKIE;
174 52
        if (false === $this->isSafeMethod()) {
175
            $this->parseInput();
176 52
        }
177
        if ($_FILES) {
178 52
            $this->uploadedFiles = $this->parseUploadedFiles($_FILES);
179
        }
180
    }
181 52
182 52
    /**
183
     * Per recommendation:
184 52
     *
185
     * @return bool If the request Content-Type is either
186 22
     * application/x-www-form-urlencoded or multipart/form-data
187 22
     * and the request method is POST,
188
     * then it MUST return the contents of $_POST
189
     * @see ServerRequestInterface::withParsedBody()
190 52
     *
191 1
     * @see ServerRequestInterface::getParsedBody()
192 1
     */
193
    protected function useOnlyPost(): bool
194
    {
195 52
        if (empty($contentType = $this->getHeaderLine('Content-Type'))) {
196 52
            return false;
197
        }
198 52
        return $this->method === self::POST && (
199
            \str_contains('application/x-www-form-urlencoded', $contentType) ||
200 52
            \str_contains('multipart/form-data', $contentType));
201 52
    }
202 52
203 52
    /**
204
     * Try to unserialize a JSON string or form encoded request body.
205 52
     * Very useful if JavaScript app stringify objects in AJAX requests.
206 22
     */
207
    protected function parseInput(): void
208
    {
209 52
        if (empty($input = $this->getRawInput())) {
210 4
            return;
211
        }
212 52
        // Try JSON deserialization
213
        $this->parsedBody = \json_decode($input, true, 512, JSON_BIGINT_AS_STRING);
214
        if (null === $this->parsedBody) {
215
            \parse_str($input, $this->parsedBody);
0 ignored issues
show
It seems like $this->parsedBody can also be of type Koded\Http\null and object; however, parameter $result of parse_str() 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

215
            \parse_str($input, /** @scrutinizer ignore-type */ $this->parsedBody);
Loading history...
216
        }
217
    }
218
219
    protected function getRawInput(): string
220
    {
221
        return \file_get_contents('php://input') ?: '';
222
    }
223
}
224