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

nette / http / 12735233527

12 Jan 2025 04:27PM UTC coverage: 83.456%. Remained the same
12735233527

push

github

dg
FileUpload::__construct() accepts path

8 of 8 new or added lines in 1 file covered. (100.0%)

20 existing lines in 3 files now uncovered.

908 of 1088 relevant lines covered (83.46%)

0.83 hits per line

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

92.57
/src/Http/Url.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

14

15
/**
16
 * Mutable representation of a URL.
17
 *
18
 * <pre>
19
 * scheme  user  password  host  port      path        query    fragment
20
 *   |      |      |        |      |        |            |         |
21
 * /--\   /--\ /------\ /-------\ /--\/------------\ /--------\ /------\
22
 * http://john:x0y17575@nette.org:8042/en/manual.php?name=param#fragment  <-- absoluteUrl
23
 * \______\__________________________/
24
 *     |               |
25
 *  hostUrl        authority
26
 * </pre>
27
 *
28
 * @property   string $scheme
29
 * @property   string $user
30
 * @property   string $password
31
 * @property   string $host
32
 * @property   int $port
33
 * @property   string $path
34
 * @property   string $query
35
 * @property   string $fragment
36
 * @property-read string $absoluteUrl
37
 * @property-read string $authority
38
 * @property-read string $hostUrl
39
 * @property-read string $basePath
40
 * @property-read string $baseUrl
41
 * @property-read string $relativeUrl
42
 * @property-read array $queryParameters
43
 */
44
class Url implements \JsonSerializable
45
{
46
        use Nette\SmartObject;
47

48
        public static array $defaultPorts = [
49
                'http' => 80,
50
                'https' => 443,
51
                'ftp' => 21,
52
        ];
53

54
        private string $scheme = '';
55
        private string $user = '';
56
        private string $password = '';
57
        private string $host = '';
58
        private ?int $port = null;
59
        private string $path = '';
60
        private array $query = [];
61
        private string $fragment = '';
62

63

64
        /**
65
         * @throws Nette\InvalidArgumentException if URL is malformed
66
         */
67
        public function __construct(string|self|UrlImmutable|null $url = null)
1✔
68
        {
69
                if (is_string($url)) {
1✔
70
                        $p = @parse_url($url); // @ - is escalated to exception
1✔
71
                        if ($p === false) {
1✔
72
                                throw new Nette\InvalidArgumentException("Malformed or unsupported URI '$url'.");
1✔
73
                        }
74

75
                        $this->scheme = $p['scheme'] ?? '';
1✔
76
                        $this->port = $p['port'] ?? null;
1✔
77
                        $this->host = rawurldecode($p['host'] ?? '');
1✔
78
                        $this->user = rawurldecode($p['user'] ?? '');
1✔
79
                        $this->password = rawurldecode($p['pass'] ?? '');
1✔
80
                        $this->setPath($p['path'] ?? '');
1✔
81
                        $this->setQuery($p['query'] ?? []);
1✔
82
                        $this->fragment = rawurldecode($p['fragment'] ?? '');
1✔
83

84
                } elseif ($url instanceof UrlImmutable || $url instanceof self) {
1✔
85
                        [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment] = $url->export();
1✔
86
                }
87
        }
1✔
88

89

90
        public function setScheme(string $scheme): static
1✔
91
        {
92
                $this->scheme = $scheme;
1✔
93
                return $this;
1✔
94
        }
95

96

97
        public function getScheme(): string
98
        {
99
                return $this->scheme;
1✔
100
        }
101

102

103
        public function setUser(string $user): static
104
        {
UNCOV
105
                $this->user = $user;
×
106
                return $this;
×
107
        }
108

109

110
        public function getUser(): string
111
        {
112
                return $this->user;
1✔
113
        }
114

115

116
        public function setPassword(string $password): static
117
        {
UNCOV
118
                $this->password = $password;
×
UNCOV
119
                return $this;
×
120
        }
121

122

123
        public function getPassword(): string
124
        {
125
                return $this->password;
1✔
126
        }
127

128

129
        public function setHost(string $host): static
1✔
130
        {
131
                $this->host = $host;
1✔
132
                $this->setPath($this->path);
1✔
133
                return $this;
1✔
134
        }
135

136

137
        public function getHost(): string
138
        {
139
                return $this->host;
1✔
140
        }
141

142

143
        /**
144
         * Returns the part of domain.
145
         */
146
        public function getDomain(int $level = 2): string
1✔
147
        {
148
                $parts = ip2long($this->host)
1✔
149
                        ? [$this->host]
1✔
150
                        : explode('.', $this->host);
1✔
151
                $parts = $level >= 0
1✔
152
                        ? array_slice($parts, -$level)
1✔
153
                        : array_slice($parts, 0, $level);
1✔
154
                return implode('.', $parts);
1✔
155
        }
156

157

158
        public function setPort(int $port): static
1✔
159
        {
160
                $this->port = $port;
1✔
161
                return $this;
1✔
162
        }
163

164

165
        public function getPort(): ?int
166
        {
167
                return $this->port ?: $this->getDefaultPort();
1✔
168
        }
169

170

171
        public function getDefaultPort(): ?int
172
        {
173
                return self::$defaultPorts[$this->scheme] ?? null;
1✔
174
        }
175

176

177
        public function setPath(string $path): static
1✔
178
        {
179
                $this->path = $path;
1✔
180
                if ($this->host && !str_starts_with($this->path, '/')) {
1✔
181
                        $this->path = '/' . $this->path;
1✔
182
                }
183

184
                return $this;
1✔
185
        }
186

187

188
        public function getPath(): string
189
        {
190
                return $this->path;
1✔
191
        }
192

193

194
        public function setQuery(string|array $query): static
1✔
195
        {
196
                $this->query = is_array($query) ? $query : self::parseQuery($query);
1✔
197
                return $this;
1✔
198
        }
199

200

201
        public function appendQuery(string|array $query): static
1✔
202
        {
203
                $this->query = is_array($query)
1✔
204
                        ? $query + $this->query
1✔
205
                        : self::parseQuery($this->getQuery() . '&' . $query);
1✔
206
                return $this;
1✔
207
        }
208

209

210
        public function getQuery(): string
211
        {
212
                return http_build_query($this->query, '', '&', PHP_QUERY_RFC3986);
1✔
213
        }
214

215

216
        public function getQueryParameters(): array
217
        {
218
                return $this->query;
1✔
219
        }
220

221

222
        public function getQueryParameter(string $name): mixed
1✔
223
        {
224
                return $this->query[$name] ?? null;
1✔
225
        }
226

227

228
        public function setQueryParameter(string $name, mixed $value): static
1✔
229
        {
230
                $this->query[$name] = $value;
1✔
231
                return $this;
1✔
232
        }
233

234

235
        public function setFragment(string $fragment): static
236
        {
UNCOV
237
                $this->fragment = $fragment;
×
UNCOV
238
                return $this;
×
239
        }
240

241

242
        public function getFragment(): string
243
        {
244
                return $this->fragment;
1✔
245
        }
246

247

248
        public function getAbsoluteUrl(): string
249
        {
250
                return $this->getHostUrl() . $this->path
1✔
251
                        . (($tmp = $this->getQuery()) ? '?' . $tmp : '')
1✔
252
                        . ($this->fragment === '' ? '' : '#' . $this->fragment);
1✔
253
        }
254

255

256
        /**
257
         * Returns the [user[:pass]@]host[:port] part of URI.
258
         */
259
        public function getAuthority(): string
260
        {
261
                return $this->host === ''
1✔
262
                        ? ''
1✔
263
                        : ($this->user !== ''
1✔
264
                                ? rawurlencode($this->user) . ($this->password === '' ? '' : ':' . rawurlencode($this->password)) . '@'
1✔
265
                                : '')
1✔
266
                        . $this->host
1✔
267
                        . ($this->port && $this->port !== $this->getDefaultPort()
1✔
268
                                ? ':' . $this->port
1✔
269
                                : '');
1✔
270
        }
271

272

273
        /**
274
         * Returns the scheme and authority part of URI.
275
         */
276
        public function getHostUrl(): string
277
        {
278
                return ($this->scheme ? $this->scheme . ':' : '')
1✔
279
                        . (($authority = $this->getAuthority()) !== '' ? '//' . $authority : '');
1✔
280
        }
281

282

283
        /** @deprecated use UrlScript::getBasePath() instead */
284
        public function getBasePath(): string
285
        {
UNCOV
286
                $pos = strrpos($this->path, '/');
×
UNCOV
287
                return $pos === false ? '' : substr($this->path, 0, $pos + 1);
×
288
        }
289

290

291
        /** @deprecated use UrlScript::getBaseUrl() instead */
292
        public function getBaseUrl(): string
293
        {
UNCOV
294
                return $this->getHostUrl() . $this->getBasePath();
×
295
        }
296

297

298
        /** @deprecated use UrlScript::getRelativeUrl() instead */
299
        public function getRelativeUrl(): string
300
        {
UNCOV
301
                return substr($this->getAbsoluteUrl(), strlen($this->getBaseUrl()));
×
302
        }
303

304

305
        /**
306
         * URL comparison.
307
         */
308
        public function isEqual(string|self|UrlImmutable $url): bool
1✔
309
        {
310
                $url = new self($url);
1✔
311
                $query = $url->query;
1✔
312
                ksort($query);
1✔
313
                $query2 = $this->query;
1✔
314
                ksort($query2);
1✔
315
                $host = rtrim($url->host, '.');
1✔
316
                $host2 = rtrim($this->host, '.');
1✔
317
                return $url->scheme === $this->scheme
1✔
318
                        && (!strcasecmp($host, $host2)
1✔
319
                                || self::idnHostToUnicode($host) === self::idnHostToUnicode($host2))
1✔
320
                        && $url->getPort() === $this->getPort()
1✔
321
                        && $url->user === $this->user
1✔
322
                        && $url->password === $this->password
1✔
323
                        && self::unescape($url->path, '%/') === self::unescape($this->path, '%/')
1✔
324
                        && $query === $query2
1✔
325
                        && $url->fragment === $this->fragment;
1✔
326
        }
327

328

329
        /**
330
         * Transforms URL to canonical form.
331
         */
332
        public function canonicalize(): static
333
        {
334
                $this->path = preg_replace_callback(
1✔
335
                        '#[^!$&\'()*+,/:;=@%"]+#',
1✔
336
                        fn(array $m): string => rawurlencode($m[0]),
1✔
337
                        self::unescape($this->path, '%/'),
1✔
338
                );
339
                $this->host = rtrim($this->host, '.');
1✔
340
                $this->host = self::idnHostToUnicode(strtolower($this->host));
1✔
341
                return $this;
1✔
342
        }
343

344

345
        public function __toString(): string
346
        {
347
                return $this->getAbsoluteUrl();
1✔
348
        }
349

350

351
        public function jsonSerialize(): string
352
        {
353
                return $this->getAbsoluteUrl();
1✔
354
        }
355

356

357
        /** @internal */
358
        final public function export(): array
359
        {
360
                return [$this->scheme, $this->user, $this->password, $this->host, $this->port, $this->path, $this->query, $this->fragment];
1✔
361
        }
362

363

364
        /**
365
         * Converts IDN ASCII host to UTF-8.
366
         */
367
        private static function idnHostToUnicode(string $host): string
1✔
368
        {
369
                if (!str_contains($host, '--')) { // host does not contain IDN
1✔
370
                        return $host;
1✔
371
                }
372

373
                if (function_exists('idn_to_utf8') && defined('INTL_IDNA_VARIANT_UTS46')) {
1✔
374
                        return idn_to_utf8($host, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46) ?: $host;
1✔
375
                }
376

UNCOV
377
                trigger_error('PHP extension intl is not loaded or is too old', E_USER_WARNING);
×
378
        }
379

380

381
        /**
382
         * Similar to rawurldecode, but preserves reserved chars encoded.
383
         */
384
        public static function unescape(string $s, string $reserved = '%;/?:@&=+$,'): string
1✔
385
        {
386
                // reserved (@see RFC 2396) = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
387
                // within a path segment, the characters "/", ";", "=", "?" are reserved
388
                // within a query component, the characters ";", "/", "?", ":", "@", "&", "=", "+", ",", "$" are reserved.
389
                if ($reserved !== '') {
1✔
390
                        $s = preg_replace_callback(
1✔
391
                                '#%(' . substr(chunk_split(bin2hex($reserved), 2, '|'), 0, -1) . ')#i',
1✔
392
                                fn(array $m): string => '%25' . strtoupper($m[1]),
1✔
393
                                $s,
394
                        );
395
                }
396

397
                return rawurldecode($s);
1✔
398
        }
399

400

401
        /**
402
         * Parses query string. Is affected by directive arg_separator.input.
403
         */
404
        public static function parseQuery(string $s): array
1✔
405
        {
406
                $s = str_replace(['%5B', '%5b'], '[', $s);
1✔
407
                $sep = preg_quote(ini_get('arg_separator.input'));
1✔
408
                $s = preg_replace("#([$sep])([^[$sep=]+)([^$sep]*)#", '&0[$2]$3', '&' . $s);
1✔
409
                parse_str($s, $res);
1✔
410
                return $res[0] ?? [];
1✔
411
        }
412

413

414
        /**
415
         * Determines if URL is absolute, ie if it starts with a scheme followed by colon.
416
         */
417
        public static function isAbsolute(string $url): bool
1✔
418
        {
419
                return (bool) preg_match('#^[a-z][a-z0-9+.-]*:#i', $url);
1✔
420
        }
421

422

423
        /**
424
         * Normalizes a path by handling and removing relative path references like '.', '..' and directory traversal.
425
         */
426
        public static function removeDotSegments(string $path): string
1✔
427
        {
428
                $prefix = $segment = '';
1✔
429
                if (str_starts_with($path, '/')) {
1✔
430
                        $prefix = '/';
1✔
431
                        $path = substr($path, 1);
1✔
432
                }
433
                $segments = explode('/', $path);
1✔
434
                $res = [];
1✔
435
                foreach ($segments as $segment) {
1✔
436
                        if ($segment === '..') {
1✔
437
                                array_pop($res);
1✔
438
                        } elseif ($segment !== '.') {
1✔
439
                                $res[] = $segment;
1✔
440
                        }
441
                }
442

443
                if ($segment === '.' || $segment === '..') {
1✔
444
                        $res[] = '';
1✔
445
                }
446
                return $prefix . implode('/', $res);
1✔
447
        }
448
}
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