Passed
Push — master ( 4ac306...d2aa89 )
by Mihail
05:30
created

ServerRequest::isXHR()   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 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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 InvalidArgumentException;
16
use Koded\Http\Interfaces\Request;
17
use Psr\Http\Message\ServerRequestInterface;
18
19
20
class ServerRequest extends ClientRequest implements Request
21
{
22
    use CookieTrait, FilesTrait, ValidatableTrait;
23
24
    /** @var string */
25
    protected $serverSoftware = '';
26
27
    /** @var array */
28
    protected $attributes = [];
29
30
    /** @var array */
31
    protected $queryParams = [];
32
33
    /** @var null|array */
34
    protected $parsedBody;
35
36
    /**
37
     * ServerRequest constructor.
38
     *
39
     * @param array $attributes
40
     */
41 52
    public function __construct(array $attributes = [])
42
    {
43 52
        parent::__construct($_SERVER['REQUEST_METHOD'] ?? Request::GET, $this->buildUri());
44 52
        $this->attributes = $attributes;
45 52
        $this->extractHttpHeaders($_SERVER);
46 52
        $this->extractServerData($_SERVER);
47 52
    }
48
49 3
    public function getServerParams(): array
50
    {
51 3
        return $_SERVER;
52
    }
53
54 3
    public function getQueryParams(): array
55
    {
56 3
        return $this->queryParams;
57
    }
58
59 2
    public function withQueryParams(array $query): ServerRequest
60
    {
61 2
        $instance              = clone $this;
62 2
        $instance->queryParams = array_merge($instance->queryParams, $query);
63
64 2
        return $instance;
65
    }
66
67 17
    public function getParsedBody()
68
    {
69 17
        if ($this->useOnlyPost()) {
70 2
            return $_POST;
71
        }
72
73 15
        if (false === empty($_POST)) {
74 5
            return $_POST;
75
        }
76
77 10
        return $this->parsedBody;
78
    }
79
80 13
    public function withParsedBody($data): ServerRequest
81
    {
82 13
        $instance = clone $this;
83
84 13
        if ($this->useOnlyPost()) {
85 1
            $instance->parsedBody = $_POST;
86
87 1
            return $instance;
88
        }
89
90
        // If nothing is available for the body
91 12
        if (null === $data) {
92 2
            $instance->parsedBody = null;
93
94 2
            return $instance;
95
        }
96
97
        // Supports array or iterable object
98 10
        if (is_iterable($data)) {
99 4
            $instance->parsedBody = is_array($data) ? $data : iterator_to_array($data);
100
101 4
            return $instance;
102
        }
103
104 6
        if (is_object($data)) {
105 1
            $instance->parsedBody = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type object is incompatible with the declared type array|null of property $parsedBody.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
106
107 1
            return $instance;
108
        }
109
110 5
        throw new InvalidArgumentException(
111 5
            sprintf('Unsupported data provided (%s), Expects NULL, array or iterable', gettype($data))
112
        );
113
    }
114
115 3
    public function getAttributes(): array
116
    {
117 3
        return $this->attributes;
118
    }
119
120 3
    public function getAttribute($name, $default = null)
121
    {
122 3
        return $this->attributes[$name] ?? $default;
123
    }
124
125 4
    public function withAttribute($name, $value): ServerRequest
126
    {
127 4
        $instance                    = clone $this;
128 4
        $instance->attributes[$name] = $value;
129
130 4
        return $instance;
131
    }
132
133 2
    public function withoutAttribute($name): ServerRequest
134
    {
135 2
        $instance = clone $this;
136 2
        unset($instance->attributes[$name]);
137
138 2
        return $instance;
139
    }
140
141 1
    public function withAttributes(array $attributes): Request
142
    {
143 1
        $instance = clone $this;
144
145 1
        foreach ($attributes as $name => $value) {
146 1
            $instance->attributes[$name] = $value;
147
        }
148
149 1
        return $instance;
150
    }
151
152 2
    public function isXHR(): bool
153
    {
154 2
        return 'XMLHTTPREQUEST' === strtoupper($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '');
155
    }
156
157 52
    protected function buildUri(): Uri
158
    {
159 52
        if (strpos($_SERVER['REQUEST_URI'] ?? '', '://')) {
160 1
            return new Uri($_SERVER['REQUEST_URI']);
161
        }
162
163 52
        if ($host = $_SERVER['SERVER_NAME'] ?? $_SERVER['SERVER_ADDR'] ?? '') {
164 50
            return new Uri('http' . ($_SERVER['HTTPS'] ?? $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? false ? 's' : '')
165 50
                . '://' . $host
166 50
                . ':' . ($_SERVER['SERVER_PORT'] ?? 80)
167 50
                . ($_SERVER['REQUEST_URI'] ?? '')
168
            );
169
        }
170
171 5
        return new Uri($_SERVER['REQUEST_URI'] ?? '');
172
    }
173
174 52
    protected function extractHttpHeaders(array $server): void
175
    {
176 52
        foreach ($server as $k => $v) {
177
            // Calisthenics :)
178 52
            0 === strpos($k, 'HTTP_', 0) and $this->normalizeHeader(str_replace('HTTP_', '', $k), $v, false);
179
        }
180
181 52
        unset($this->headers['X-Forwarded-For'], $this->headers['X-Forwarded-Proto']);
182 52
        unset($this->headersMap['x-forwarded-for'], $this->headersMap['x-forwarded-proto']);
183
184 52
        if (isset($server['HTTP_IF_NONE_MATCH'])) {
185
            // ETag workaround for various broken Apache2 versions
186 22
            $this->headers['ETag']    = str_replace('-gzip', '', $server['HTTP_IF_NONE_MATCH']);
187 22
            $this->headersMap['etag'] = 'ETag';
188
        }
189
190 52
        if (isset($server['CONTENT_TYPE'])) {
191 1
            $this->headers['Content-Type']    = strtolower($server['CONTENT_TYPE']);
192 1
            $this->headersMap['content-type'] = 'Content-Type';
193
        }
194
195 52
        $this->setHost();
196 52
    }
197
198 52
    protected function extractServerData(array $server): void
199
    {
200 52
        $this->protocolVersion = str_ireplace('HTTP/', '', $server['SERVER_PROTOCOL'] ?? $this->protocolVersion);
201 52
        $this->serverSoftware  = $server['SERVER_SOFTWARE'] ?? '';
202 52
        $this->queryParams     = $_GET;
203 52
        $this->cookieParams    = $_COOKIE;
204
205 52
        if (false === $this->isSafeMethod()) {
206 22
            $this->parseInput();
207
        }
208
209 52
        if ($_FILES) {
210 4
            $this->uploadedFiles = $this->parseUploadedFiles($_FILES);
211
        }
212 52
    }
213
214
    /**
215
     * Per recommendation:
216
     *
217
     * @return bool If the request Content-Type is either
218
     * application/x-www-form-urlencoded or multipart/form-data
219
     * and the request method is POST,
220
     * then it MUST return the contents of $_POST
221
     * @see ServerRequestInterface::withParsedBody()
222
     *
223
     * @see ServerRequestInterface::getParsedBody()
224
     */
225 23
    protected function useOnlyPost(): bool
226
    {
227 23
        if (empty($contentType = $this->getHeaderLine('Content-Type'))) {
228 16
            return false;
229
        }
230
231 7
        return $this->method === self::POST && (
232 4
                false !== strpos('application/x-www-form-urlencoded', $contentType) ||
233 7
                false !== strpos('multipart/form-data', $contentType));
234
    }
235
236
    /**
237
     * Try to unserialize a JSON string or form encoded request body.
238
     * Very useful if JavaScript app stringify objects in AJAX requests.
239
     */
240 22
    protected function parseInput(): void
241
    {
242 22
        if (empty($input = $this->getRawInput())) {
243 22
            return;
244
        }
245
246
        // Try JSON deserialization
247 2
        $this->parsedBody = json_decode($input, true, 512, JSON_BIGINT_AS_STRING);
248
249 2
        if (null === $this->parsedBody) {
250 1
            parse_str($input, $this->parsedBody);
251
        }
252 2
    }
253
254 22
    protected function getRawInput(): string
255
    {
256 22
        return file_get_contents('php://input') ?: '';
257
    }
258
}
259