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

nette / http / 27449837026

12 Jun 2026 11:51PM UTC coverage: 83.128% (+0.1%) from 83.025%
27449837026

push

github

dg
deprecated wip

1079 of 1298 relevant lines covered (83.13%)

0.83 hits per line

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

89.19
/src/Http/Request.php
1
<?php declare(strict_types=1);
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
namespace Nette\Http;
9

10
use Nette;
11
use function array_change_key_case, base64_decode, count, explode, func_num_args, in_array, is_array, preg_match, strcasecmp, strlen, strtr;
12

13

14
/**
15
 * Immutable representation of an HTTP request with access to URL, headers, cookies, uploaded files, and body.
16
 *
17
 * @property-read UrlScript $url
18
 * @property-read array<string,mixed> $query
19
 * @property-read array<string,mixed> $post
20
 * @property-read array<string,mixed> $files
21
 * @property-read array<string,string> $cookies
22
 * @property-read string $method
23
 * @property-read array<string,string> $headers
24
 * @property-read ?UrlImmutable $referer
25
 * @property-read bool $secured
26
 * @property-read bool $ajax
27
 * @property-read ?string $remoteAddress
28
 * @property-read ?string $rawBody
29
 */
30
class Request implements IRequest
31
{
32
        use Nette\SmartObject;
33

34
        /** @var array<string, string> */
35
        private readonly array $headers;
36

37
        /** @var (\Closure(): string)|null */
38
        private readonly ?\Closure $rawBodyCallback;
39

40

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

62

63
        /**
64
         * Returns a clone with a different URL.
65
         */
66
        public function withUrl(UrlScript $url): static
1✔
67
        {
68
                $dolly = clone $this;
1✔
69
                $dolly->url = $url;
1✔
70
                return $dolly;
1✔
71
        }
72

73

74
        /**
75
         * Returns the URL of the request.
76
         */
77
        public function getUrl(): UrlScript
78
        {
79
                return $this->url;
1✔
80
        }
81

82

83
        /********************* query, post, files & cookies ****************d*g**/
84

85

86
        /**
87
         * Returns a URL query parameter, or all parameters as an array if no key is given.
88
         */
89
        public function getQuery(?string $key = null): mixed
1✔
90
        {
91
                if (func_num_args() === 0) {
1✔
92
                        return $this->url->getQueryParameters();
1✔
93
                }
94

95
                assert($key !== null);
96
                return $this->url->getQueryParameter($key);
1✔
97
        }
98

99

100
        /**
101
         * Returns a POST parameter, or all POST parameters as an array if no key is given.
102
         */
103
        public function getPost(?string $key = null): mixed
1✔
104
        {
105
                if (func_num_args() === 0) {
1✔
106
                        return $this->post;
1✔
107
                }
108

109
                assert($key !== null);
110
                return $this->post[$key] ?? null;
1✔
111
        }
112

113

114
        /**
115
         * Returns the uploaded file for the given key, or null if not present.
116
         * Accepts a string key or an array of keys for nested file structures (e.g. ['form', 'avatar']).
117
         * @param  string|string[]  $key
118
         */
119
        public function getFile($key): ?FileUpload
120
        {
121
                $res = Nette\Utils\Arrays::get($this->files, $key, null);
1✔
122
                return $res instanceof FileUpload ? $res : null;
1✔
123
        }
124

125

126
        /**
127
         * Returns the tree of uploaded files, with each leaf being a FileUpload instance.
128
         * @return mixed[]
129
         */
130
        public function getFiles(): array
131
        {
132
                return $this->files;
1✔
133
        }
134

135

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

144

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

154

155
        /********************* method & headers ****************d*g**/
156

157

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

166

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

175

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

185

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

195

196
        #[\Deprecated('deprecated in favor of the getOrigin()')]
197
        public function getReferer(): ?UrlImmutable
198
        {
199
                trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
×
200
                return isset($this->headers['referer'])
×
201
                        ? new UrlImmutable($this->headers['referer'])
×
202
                        : null;
×
203
        }
204

205

206
        /**
207
         * Returns the request origin (scheme + host + port) from the Origin header, or null if absent or invalid.
208
         */
209
        public function getOrigin(): ?UrlImmutable
210
        {
211
                $header = $this->headers['origin'] ?? '';
1✔
212
                if (!preg_match('~^[a-z][a-z0-9+.-]*://[^/]+$~i', $header)) {
1✔
213
                        return null;
1✔
214
                }
215
                return new UrlImmutable($header);
1✔
216
        }
217

218

219
        /**
220
         * Checks whether the request was sent via a secure channel (HTTPS).
221
         */
222
        public function isSecured(): bool
223
        {
224
                return $this->url->getScheme() === 'https';
1✔
225
        }
226

227

228
        /**
229
         * Checks whether the request originated from your own site (same-site), i.e. it was not
230
         * triggered from a foreign website. Serves as a CSRF-like protection for forms and signals.
231
         * @deprecated use isFrom()
232
         */
233
        public function isSameSite(): bool
234
        {
235
                trigger_error(__METHOD__ . '() is deprecated, use isFrom() instead', E_USER_DEPRECATED);
×
236
                return $this->isFrom([FetchSite::SameSite, FetchSite::SameOrigin]);
×
237
        }
238

239

240
        /**
241
         * Checks whether the request matches the given Sec-Fetch-Site, Sec-Fetch-Dest and Sec-Fetch-User values.
242
         * @param  FetchSite|list<FetchSite>  $site
243
         * @param  FetchDest|list<FetchDest>|null  $dest
244
         */
245
        public function isFrom(
1✔
246
                FetchSite|array $site,
247
                FetchDest|array|null $dest = null,
248
                ?bool $user = null,
249
        ): bool
250
        {
251
                $actualSite = FetchSite::tryFrom($this->headers['sec-fetch-site'] ?? '');
1✔
252
                $actualDest = FetchDest::tryFrom($this->headers['sec-fetch-dest'] ?? '');
1✔
253
                $actualUser = ($this->headers['sec-fetch-user'] ?? null) === '?1';
1✔
254
                $site = is_array($site) ? $site : [$site];
1✔
255
                $dest = $dest === null || is_array($dest) ? $dest : [$dest];
1✔
256

257
                return $actualSite !== null
1✔
258
                        && in_array($actualSite, $site, strict: true)
1✔
259
                        && ($dest === null || ($actualDest !== null && in_array($actualDest, $dest, strict: true)))
1✔
260
                        && ($user === null || $user === $actualUser);
1✔
261
        }
262

263

264
        /**
265
         * Checks whether the request was made via AJAX (X-Requested-With: XMLHttpRequest).
266
         */
267
        public function isAjax(): bool
268
        {
269
                return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
×
270
        }
271

272

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

281

282
        /**
283
         * Returns raw content of HTTP request body.
284
         */
285
        public function getRawBody(): ?string
286
        {
287
                return $this->rawBodyCallback ? ($this->rawBodyCallback)() : null;
1✔
288
        }
289

290

291
        /**
292
         * Returns basic HTTP authentication credentials.
293
         * @return array{string, string}|null
294
         */
295
        public function getBasicCredentials(): ?array
296
        {
297
                return preg_match(
1✔
298
                        '~^Basic (\S+)$~',
1✔
299
                        $this->headers['authorization'] ?? '',
1✔
300
                        $t,
1✔
301
                )
302
                        && ($t = base64_decode($t[1], strict: true))
1✔
303
                        && ($t = explode(':', $t, 2))
1✔
304
                        && (count($t) === 2)
1✔
305
                        ? $t
1✔
306
                        : null;
1✔
307
        }
308

309

310
        /**
311
         * Returns the most preferred language from the Accept-Language header that matches one of the supported languages,
312
         * or null if no match is found.
313
         * @param  array<string>  $langs  supported language codes (e.g. ['en', 'cs', 'de'])
314
         */
315
        public function detectLanguage(array $langs): ?string
1✔
316
        {
317
                $header = $this->getHeader('Accept-Language');
1✔
318
                if (!$header) {
1✔
319
                        return null;
1✔
320
                }
321

322
                usort($langs, fn($a, $b) => strlen($b) <=> strlen($a)); // more specific first
1✔
323
                $accepted = Helpers::parseQualityList(strtr($header, '_', '-')); // cs_CZ means cs-CZ
1✔
324

325
                foreach (array_keys($accepted) as $token) {
1✔
326
                        foreach ($langs as $lang) {
1✔
327
                                $l = strtolower($lang);
1✔
328
                                if ($token === '*' || $token === $l || str_starts_with($token, $l . '-')) {
1✔
329
                                        return $lang;
1✔
330
                                }
331
                        }
332
                }
333

334
                return null;
1✔
335
        }
336
}
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