• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / http / 21833059815

09 Feb 2026 04:31PM UTC coverage: 83.62%. Remained the same
21833059815

push

github

dg
added CLAUDE.md

924 of 1105 relevant lines covered (83.62%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.68
/src/Http/Request.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Http;
11

12
use Nette;
13
use function array_change_key_case, base64_decode, count, explode, func_num_args, gethostbyaddr, implode, in_array, preg_match, preg_match_all, rsort, strcasecmp, strtr;
14
use const CASE_LOWER;
15

16

17
/**
18
 * HttpRequest provides access scheme for request sent via HTTP.
19
 *
20
 * @property-read UrlScript $url
21
 * @property-read array<string,mixed> $query
22
 * @property-read array<string,mixed> $post
23
 * @property-read array<string,mixed> $files
24
 * @property-read array<string,string> $cookies
25
 * @property-read string $method
26
 * @property-read array<string,string> $headers
27
 * @property-read ?UrlImmutable $referer
28
 * @property-read bool $secured
29
 * @property-read bool $ajax
30
 * @property-read ?string $remoteAddress
31
 * @property-read ?string $remoteHost
32
 * @property-read ?string $rawBody
33
 */
34
class Request implements IRequest
35
{
36
        use Nette\SmartObject;
37

38
        /** @var array<string, string> */
39
        private readonly array $headers;
40

41
        /** @var (\Closure(): string)|null */
42
        private readonly ?\Closure $rawBodyCallback;
43

44

45
        /**
46
         * @param array<string, string> $headers
47
         * @param ?(callable(): string) $rawBodyCallback
48
         */
49
        public function __construct(
1✔
50
                private UrlScript $url,
51
                /** @var mixed[] */
52
                private readonly array $post = [],
53
                /** @var mixed[] */
54
                private readonly array $files = [],
55
                /** @var array<string, string> */
56
                private readonly array $cookies = [],
57
                array $headers = [],
58
                private readonly string $method = 'GET',
59
                private readonly ?string $remoteAddress = null,
60
                private ?string $remoteHost = null,
61
                ?callable $rawBodyCallback = null,
62
        ) {
63
                $this->headers = array_change_key_case($headers, CASE_LOWER);
1✔
64
                $this->rawBodyCallback = $rawBodyCallback ? $rawBodyCallback(...) : null;
1✔
65
        }
1✔
66

67

68
        /**
69
         * Returns a clone with a different URL.
70
         */
71
        public function withUrl(UrlScript $url): static
1✔
72
        {
73
                $dolly = clone $this;
1✔
74
                $dolly->url = $url;
1✔
75
                return $dolly;
1✔
76
        }
77

78

79
        /**
80
         * Returns the URL of the request.
81
         */
82
        public function getUrl(): UrlScript
83
        {
84
                return $this->url;
1✔
85
        }
86

87

88
        /********************* query, post, files & cookies ****************d*g**/
89

90

91
        /**
92
         * Returns variable provided to the script via URL query ($_GET).
93
         * If no key is passed, returns the entire array.
94
         */
95
        public function getQuery(?string $key = null): mixed
1✔
96
        {
97
                if (func_num_args() === 0) {
1✔
98
                        return $this->url->getQueryParameters();
1✔
99
                }
100

101
                return $this->url->getQueryParameter($key);
1✔
102
        }
103

104

105
        /**
106
         * Returns variable provided to the script via POST method ($_POST).
107
         * If no key is passed, returns the entire array.
108
         */
109
        public function getPost(?string $key = null): mixed
1✔
110
        {
111
                if (func_num_args() === 0) {
1✔
112
                        return $this->post;
1✔
113
                }
114

115
                return $this->post[$key] ?? null;
1✔
116
        }
117

118

119
        /**
120
         * Returns uploaded file.
121
         * @param  string|string[]  $key
122
         */
123
        public function getFile($key): ?FileUpload
124
        {
125
                $res = Nette\Utils\Arrays::get($this->files, $key, null);
1✔
126
                return $res instanceof FileUpload ? $res : null;
1✔
127
        }
128

129

130
        /**
131
         * Returns tree of upload files in a normalized structure, with each leaf an instance of Nette\Http\FileUpload.
132
         * @return mixed[]
133
         */
134
        public function getFiles(): array
135
        {
136
                return $this->files;
1✔
137
        }
138

139

140
        /**
141
         * Returns a cookie or `null` if it does not exist.
142
         */
143
        public function getCookie(string $key): mixed
1✔
144
        {
145
                return $this->cookies[$key] ?? null;
1✔
146
        }
147

148

149
        /**
150
         * Returns all cookies.
151
         * @return array<string, string>
152
         */
153
        public function getCookies(): array
154
        {
155
                return $this->cookies;
1✔
156
        }
157

158

159
        /********************* method & headers ****************d*g**/
160

161

162
        /**
163
         * Returns the HTTP method with which the request was made (GET, POST, HEAD, PUT, ...).
164
         */
165
        public function getMethod(): string
166
        {
167
                return $this->method;
1✔
168
        }
169

170

171
        /**
172
         * Checks the HTTP method with which the request was made. The parameter is case-insensitive.
173
         */
174
        public function isMethod(string $method): bool
175
        {
176
                return strcasecmp($this->method, $method) === 0;
×
177
        }
178

179

180
        /**
181
         * Returns an HTTP header or `null` if it does not exist. The parameter is case-insensitive.
182
         */
183
        public function getHeader(string $header): ?string
1✔
184
        {
185
                $header = strtolower($header);
1✔
186
                return $this->headers[$header] ?? null;
1✔
187
        }
188

189

190
        /**
191
         * Returns all HTTP headers as associative array.
192
         * @return array<string, string>
193
         */
194
        public function getHeaders(): array
195
        {
196
                return $this->headers;
1✔
197
        }
198

199

200
        /**
201
         * What URL did the user come from? Beware, it is not reliable at all.
202
         * @deprecated  deprecated in favor of the getOrigin()
203
         */
204
        public function getReferer(): ?UrlImmutable
205
        {
206
                return isset($this->headers['referer'])
×
207
                        ? new UrlImmutable($this->headers['referer'])
×
208
                        : null;
×
209
        }
210

211

212
        /**
213
         * What origin did the user come from? It contains scheme, hostname and port.
214
         */
215
        public function getOrigin(): ?UrlImmutable
216
        {
217
                $header = $this->headers['origin'] ?? '';
1✔
218
                if (!preg_match('~^[a-z][a-z0-9+.-]*://[^/]+$~i', $header)) {
1✔
219
                        return null;
1✔
220
                }
221
                return new UrlImmutable($header);
1✔
222
        }
223

224

225
        /**
226
         * Is the request sent via secure channel (https)?
227
         */
228
        public function isSecured(): bool
229
        {
230
                return $this->url->getScheme() === 'https';
1✔
231
        }
232

233

234
        /**
235
         * Is the request coming from the same site and is initiated by clicking on a link?
236
         */
237
        public function isSameSite(): bool
238
        {
239
                return isset($this->cookies[Helpers::StrictCookieName]);
×
240
        }
241

242

243
        /**
244
         * Checks whether Sec-Fetch headers match the expected values.
245
         * @param string|list<string>|null  $site
246
         * @param string|list<string>|null  $initiator
247
         */
248
        public function isFrom(string|array|null $site = null, string|array|null $initiator = null): bool
1✔
249
        {
250
                $actualSite = $this->headers['sec-fetch-site'] ?? null;
1✔
251
                $actualDest = $this->headers['sec-fetch-dest'] ?? null;
1✔
252

253
                if ($actualSite === null && ($origin = $this->getOrigin())) { // fallback for Safari < 16.4
1✔
254
                        $actualSite = strcasecmp($origin->getScheme(), $this->url->getScheme()) === 0
1✔
255
                                        && strcasecmp(rtrim($origin->getHost(), '.'), rtrim($this->url->getHost(), '.')) === 0
1✔
256
                                        && $origin->getPort() === $this->url->getPort()
1✔
257
                                ? 'same-origin'
1✔
258
                                : 'cross-site';
1✔
259
                }
260

261
                return ($site === null || ($actualSite !== null && in_array($actualSite, (array) $site, strict: true)))
1✔
262
                        && ($initiator === null || ($actualDest !== null && in_array($actualDest, (array) $initiator, strict: true)));
1✔
263
        }
264

265

266
        /**
267
         * Is it an AJAX request?
268
         */
269
        public function isAjax(): bool
270
        {
271
                return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
×
272
        }
273

274

275
        /**
276
         * Returns the IP address of the remote client.
277
         */
278
        public function getRemoteAddress(): ?string
279
        {
280
                return $this->remoteAddress;
1✔
281
        }
282

283

284
        /**
285
         * Returns the host of the remote client.
286
         */
287
        public function getRemoteHost(): ?string
288
        {
289
                if ($this->remoteHost === null && $this->remoteAddress !== null) {
1✔
290
                        $this->remoteHost = gethostbyaddr($this->remoteAddress);
1✔
291
                }
292

293
                return $this->remoteHost;
1✔
294
        }
295

296

297
        /**
298
         * Returns raw content of HTTP request body.
299
         */
300
        public function getRawBody(): ?string
301
        {
302
                return $this->rawBodyCallback ? ($this->rawBodyCallback)() : null;
1✔
303
        }
304

305

306
        /**
307
         * Returns basic HTTP authentication credentials.
308
         * @return array{string, string}|null
309
         */
310
        public function getBasicCredentials(): ?array
311
        {
312
                return preg_match(
1✔
313
                        '~^Basic (\S+)$~',
1✔
314
                        $this->headers['authorization'] ?? '',
1✔
315
                        $t,
1✔
316
                )
317
                        && ($t = base64_decode($t[1], strict: true))
1✔
318
                        && ($t = explode(':', $t, 2))
1✔
319
                        && (count($t) === 2)
1✔
320
                        ? $t
1✔
321
                        : null;
1✔
322
        }
323

324

325
        /**
326
         * Returns the most preferred language by browser. Uses the `Accept-Language` header. If no match is reached, it returns `null`.
327
         * @param list<string>  $langs supported languages
328
         */
329
        public function detectLanguage(array $langs): ?string
1✔
330
        {
331
                $header = $this->getHeader('Accept-Language');
1✔
332
                if (!$header) {
1✔
333
                        return null;
1✔
334
                }
335

336
                $s = strtolower($header);  // case insensitive
1✔
337
                $s = strtr($s, '_', '-');  // cs_CZ means cs-CZ
1✔
338
                rsort($langs);             // first more specific
1✔
339
                preg_match_all('#(' . implode('|', $langs) . ')(?:-[^\s,;=]+)?\s*(?:;\s*q=([0-9.]+))?#', $s, $matches);
1✔
340

341
                if (!$matches[0]) {
1✔
342
                        return null;
1✔
343
                }
344

345
                $max = 0;
1✔
346
                $lang = null;
1✔
347
                foreach ($matches[1] as $key => $value) {
1✔
348
                        $q = $matches[2][$key] === '' ? 1.0 : (float) $matches[2][$key];
1✔
349
                        if ($q > $max) {
1✔
350
                                $max = $q;
1✔
351
                                $lang = $value;
1✔
352
                        }
353
                }
354

355
                return $lang;
1✔
356
        }
357
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc