1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of Blitz PHP framework. |
5
|
|
|
* |
6
|
|
|
* (c) 2022 Dimitri Sitchet Tomkeu <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view |
9
|
|
|
* the LICENSE file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace BlitzPHP\Http; |
13
|
|
|
|
14
|
|
|
use BlitzPHP\Exceptions\HttpException; |
15
|
|
|
use InvalidArgumentException; |
16
|
|
|
use Psr\Http\Message\UriInterface; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Abstraction pour un identificateur de ressource uniforme (URI). |
20
|
|
|
* |
21
|
|
|
* @credit CodeIgniter 4 <a href="https://codeigniter.com">CodeIgniter\HTTP\URI</a> |
22
|
|
|
*/ |
23
|
|
|
class Uri implements UriInterface |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* Sous-délimiteurs utilisés dans les chaînes de requête et les fragments. |
27
|
|
|
*/ |
28
|
|
|
public const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Caractères non réservés utilisés dans les chemins, les chaînes de requête et les fragments. |
32
|
|
|
*/ |
33
|
|
|
public const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Chaîne d'URI actuelle |
37
|
|
|
* |
38
|
|
|
* @var string |
39
|
|
|
*/ |
40
|
|
|
protected $uriString; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Liste des segments d'URI. |
44
|
|
|
* |
45
|
|
|
* Commence à 1 au lieu de 0 |
46
|
|
|
*/ |
47
|
|
|
protected array $segments = []; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Schéma |
51
|
|
|
*/ |
52
|
|
|
protected string $scheme = 'http'; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Informations utilisateur |
56
|
|
|
*/ |
57
|
|
|
protected ?string $user = null; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Mot de passe |
61
|
|
|
*/ |
62
|
|
|
protected ?string $password = null; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Hôte |
66
|
|
|
*/ |
67
|
|
|
protected ?string $host = null; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Port |
71
|
|
|
*/ |
72
|
|
|
protected ?int $port = null; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Chemin. |
76
|
|
|
*/ |
77
|
|
|
protected ?string $path = null; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Le nom de n'importe quel fragment. |
81
|
|
|
*/ |
82
|
|
|
protected string $fragment = ''; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* La chaîne de requête. |
86
|
|
|
*/ |
87
|
|
|
protected array $query = []; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Default schemes/ports. |
91
|
|
|
*/ |
92
|
|
|
protected array $defaultPorts = [ |
93
|
|
|
'http' => 80, |
94
|
|
|
'https' => 443, |
95
|
|
|
'ftp' => 21, |
96
|
|
|
'sftp' => 22, |
97
|
|
|
]; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Indique si les mots de passe doivent être affichés dans les appels userInfo/authority. |
101
|
|
|
* La valeur par défaut est false car les URI apparaissent souvent dans les journaux |
102
|
|
|
*/ |
103
|
|
|
protected bool $showPassword = false; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Constructeur. |
107
|
|
|
* |
108
|
|
|
* @throws InvalidArgumentException |
109
|
|
|
*/ |
110
|
|
|
public function __construct(?string $uri = null) |
111
|
|
|
{ |
112
|
39 |
|
$this->setURI($uri); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Définit et écrase toute information URI actuelle. |
117
|
|
|
*/ |
118
|
|
|
public function setURI(?string $uri = null): self |
119
|
|
|
{ |
120
|
|
|
if (null !== $uri) { |
121
|
39 |
|
$parts = parse_url($uri); |
122
|
|
|
|
123
|
|
|
if ($parts === false) { |
124
|
4 |
|
throw HttpException::unableToParseURI($uri); |
125
|
|
|
} |
126
|
|
|
|
127
|
39 |
|
$this->applyParts($parts); |
128
|
|
|
} |
129
|
|
|
|
130
|
39 |
|
return $this; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* {@inheritDoc} |
135
|
|
|
*/ |
136
|
|
|
public function getScheme(): string |
137
|
|
|
{ |
138
|
12 |
|
return $this->scheme; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* {@inheritDoc} |
143
|
|
|
*/ |
144
|
|
|
public function getAuthority(bool $ignorePort = false): string |
145
|
|
|
{ |
146
|
|
|
if (empty($authority = $this->host)) { |
147
|
2 |
|
return ''; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
if (! empty($userInfo = $this->getUserInfo())) { |
151
|
4 |
|
$authority = $userInfo . '@' . $authority; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
// N'ajoute pas de port s'il s'agit d'un port standard pour ce schéma |
155
|
|
|
if ($this->port !== null && $this->port !== 0 && ! $ignorePort && $this->port !== $this->defaultPorts[$this->scheme]) { |
156
|
4 |
|
$authority .= ':' . $this->port; |
157
|
|
|
} |
158
|
|
|
|
159
|
10 |
|
$this->showPassword = false; |
160
|
|
|
|
161
|
10 |
|
return $authority; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* {@inheritDoc} |
166
|
|
|
*/ |
167
|
|
|
public function getUserInfo(): string |
168
|
|
|
{ |
169
|
10 |
|
$userInfo = $this->user ?: ''; |
170
|
|
|
|
171
|
|
|
if ($this->showPassword === true && ($this->password !== null && $this->password !== '' && $this->password !== '0')) { |
172
|
2 |
|
$userInfo .= ':' . $this->password; |
173
|
|
|
} |
174
|
|
|
|
175
|
10 |
|
return $userInfo; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Définit temporairement l'URI pour afficher un mot de passe dans userInfo. |
180
|
|
|
* Se réinitialisera après le premier appel à l'autorité(). |
181
|
|
|
*/ |
182
|
|
|
public function showPassword(bool $val = true): self |
183
|
|
|
{ |
184
|
2 |
|
$this->showPassword = $val; |
185
|
|
|
|
186
|
2 |
|
return $this; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* {@inheritDoc} |
191
|
|
|
*/ |
192
|
|
|
public function getHost(): string |
193
|
|
|
{ |
194
|
37 |
|
return $this->host ?? ''; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* {@inheritDoc} |
199
|
|
|
*/ |
200
|
|
|
public function getPort(): ?int |
201
|
|
|
{ |
202
|
16 |
|
return $this->port; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* {@inheritDoc} |
207
|
|
|
*/ |
208
|
|
|
public function getPath(): string |
209
|
|
|
{ |
210
|
16 |
|
return $this->path ?? ''; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* {@inheritDoc} |
215
|
|
|
*/ |
216
|
|
|
public function getQuery(array $options = []): string |
217
|
|
|
{ |
218
|
14 |
|
$vars = $this->query; |
219
|
|
|
|
220
|
|
|
if (array_key_exists('except', $options)) { |
221
|
|
|
if (! is_array($options['except'])) { |
222
|
|
|
$options['except'] = [$options['except']]; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
foreach ($options['except'] as $var) { |
226
|
|
|
unset($vars[$var]); |
227
|
|
|
} |
228
|
|
|
} elseif (array_key_exists('only', $options)) { |
229
|
14 |
|
$temp = []; |
230
|
|
|
|
231
|
|
|
if (! is_array($options['only'])) { |
232
|
|
|
$options['only'] = [$options['only']]; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
foreach ($options['only'] as $var) { |
236
|
|
|
if (array_key_exists($var, $vars)) { |
237
|
|
|
$temp[$var] = $vars[$var]; |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
$vars = $temp; |
242
|
|
|
} |
243
|
|
|
|
244
|
14 |
|
return $vars === [] ? '' : http_build_query($vars); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* {@inheritDoc} |
249
|
|
|
*/ |
250
|
|
|
public function getFragment(): string |
251
|
|
|
{ |
252
|
10 |
|
return $this->fragment ?? ''; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Renvoie les segments du chemin sous forme de tableau. |
257
|
|
|
*/ |
258
|
|
|
public function getSegments(): array |
259
|
|
|
{ |
260
|
2 |
|
return $this->segments; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Renvoie la valeur d'un segment spécifique du chemin URI. |
265
|
|
|
* |
266
|
|
|
* @return string La valeur du segment. Si aucun segment n'est trouvé, lance InvalidArgumentError |
267
|
|
|
*/ |
268
|
|
|
public function getSegment(int $number, string $default = ''): string |
269
|
|
|
{ |
270
|
|
|
if ($number < 1) { |
271
|
2 |
|
throw HttpException::uriSegmentOutOfRange($number); |
272
|
|
|
} |
273
|
|
|
if ($number > count($this->segments) + 1) { |
274
|
2 |
|
throw HttpException::uriSegmentOutOfRange($number); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
// Le segment doit traiter le tableau comme basé sur 1 pour l'utilisateur |
278
|
|
|
// mais nous devons encore gérer un tableau de base zéro. |
279
|
2 |
|
$number--; |
280
|
|
|
|
281
|
2 |
|
return $this->segments[$number] ?? $default; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Définissez la valeur d'un segment spécifique du chemin URI. |
286
|
|
|
* Permet de définir uniquement des segments existants ou d'en ajouter un nouveau. |
287
|
|
|
* |
288
|
|
|
* @param mixed $value (string ou int) |
289
|
|
|
*/ |
290
|
|
|
public function setSegment(int $number, $value) |
291
|
|
|
{ |
292
|
|
|
if ($number < 1) { |
293
|
|
|
throw HttpException::uriSegmentOutOfRange($number); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
if ($number > count($this->segments) + 1) { |
297
|
|
|
throw HttpException::uriSegmentOutOfRange($number); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
// Le segment doit traiter le tableau comme basé sur 1 pour l'utilisateur |
301
|
|
|
// mais nous devons encore gérer un tableau de base zéro. |
302
|
|
|
$number--; |
303
|
|
|
|
304
|
|
|
$this->segments[$number] = $value; |
305
|
|
|
$this->refreshPath(); |
306
|
|
|
|
307
|
|
|
return $this; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Renvoie le nombre total de segments. |
312
|
|
|
*/ |
313
|
|
|
public function getTotalSegments(): int |
314
|
|
|
{ |
315
|
2 |
|
return count($this->segments); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Autoriser la sortie de l'URI sous forme de chaîne en le convertissant simplement en chaîne |
320
|
|
|
* ou en écho. |
321
|
|
|
*/ |
322
|
|
|
public function __toString(): string |
323
|
|
|
{ |
324
|
6 |
|
$path = $this->getPath(); |
325
|
6 |
|
$scheme = $this->getScheme(); |
326
|
|
|
|
327
|
|
|
// Si les hôtes correspondent, il faut supposer que l'URL est relative à l'URL de base. |
328
|
6 |
|
[$scheme, $path] = $this->changeSchemeAndPath($scheme, $path); |
|
|
|
|
329
|
|
|
|
330
|
|
|
return static::createURIString( |
331
|
|
|
$scheme, |
332
|
|
|
$this->getAuthority(), |
333
|
|
|
$path, // Les URI absolus doivent utiliser un "/" pour un chemin vide |
334
|
|
|
$this->getQuery(), |
335
|
|
|
$this->getFragment() |
336
|
6 |
|
); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Construit une représentation de la chaîne à partir des parties du composant. |
341
|
|
|
*/ |
342
|
|
|
public static function createURIString(?string $scheme = null, ?string $authority = null, ?string $path = null, ?string $query = null, ?string $fragment = null): string |
343
|
|
|
{ |
344
|
10 |
|
$uri = ''; |
345
|
|
|
if ($scheme !== null && $scheme !== '' && $scheme !== '0') { |
346
|
10 |
|
$uri .= $scheme . '://'; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
if ($authority !== null && $authority !== '' && $authority !== '0') { |
350
|
10 |
|
$uri .= $authority; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
if (isset($path) && $path !== '') { |
354
|
|
|
$uri .= ! str_ends_with($uri, '/') |
355
|
|
|
? '/' . ltrim($path, '/') |
356
|
2 |
|
: ltrim($path, '/'); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
if ($query !== '' && $query !== null) { |
360
|
2 |
|
$uri .= '?' . $query; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
if ($fragment !== '' && $fragment !== null) { |
364
|
2 |
|
$uri .= '#' . $fragment; |
365
|
|
|
} |
366
|
|
|
|
367
|
10 |
|
return $uri; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Analyse la chaîne donnée et enregistre les pièces d'autorité appropriées. |
372
|
|
|
*/ |
373
|
|
|
public function setAuthority(string $str): self |
374
|
|
|
{ |
375
|
|
|
$parts = parse_url($str); |
376
|
|
|
|
377
|
|
|
if (! isset($parts['path'])) { |
378
|
|
|
$parts['path'] = $this->getPath(); |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
if (empty($parts['host']) && $parts['path'] !== '') { |
382
|
|
|
$parts['host'] = $parts['path']; |
383
|
|
|
unset($parts['path']); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
$this->applyParts($parts); |
387
|
|
|
|
388
|
|
|
return $this; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Définit le schéma pour cet URI. |
393
|
|
|
* |
394
|
|
|
* En raison du grand nombre de schémas valides, nous ne pouvons pas limiter ce |
395
|
|
|
* uniquement sur http ou https. |
396
|
|
|
* |
397
|
|
|
* @see https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml |
398
|
|
|
*/ |
399
|
|
|
public function setScheme(string $str): self |
400
|
|
|
{ |
401
|
39 |
|
$str = strtolower($str); |
402
|
39 |
|
$this->scheme = preg_replace('#:(//)?$#', '', $str); |
403
|
|
|
|
404
|
39 |
|
return $this; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
/** |
408
|
|
|
* {@inheritDoc} |
409
|
|
|
*/ |
410
|
|
|
public function withScheme(string $scheme): static |
411
|
|
|
{ |
412
|
14 |
|
$uri = clone $this; |
413
|
|
|
|
414
|
14 |
|
$scheme = strtolower($scheme); |
415
|
|
|
|
416
|
14 |
|
$uri->scheme = preg_replace('#:(//)?$#', '', $scheme); |
417
|
|
|
|
418
|
14 |
|
return $uri; |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
/** |
422
|
|
|
* Définit la partie userInfo/Authority de l'URI. |
423
|
|
|
* |
424
|
|
|
* @param string $user Le nom d'utilisateur de l'utilisateur |
425
|
|
|
* @param string $pass Le mot de passe de l'utilisateur |
426
|
|
|
*/ |
427
|
|
|
public function setUserInfo(string $user, string $pass): self |
428
|
|
|
{ |
429
|
2 |
|
$this->user = trim($user); |
430
|
2 |
|
$this->password = trim($pass); |
431
|
|
|
|
432
|
2 |
|
return $this; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* {@inheritDoc} |
437
|
|
|
*/ |
438
|
|
|
public function withUserInfo(string $user, ?string $password = null): static |
439
|
|
|
{ |
440
|
|
|
$new = clone $this; |
441
|
|
|
|
442
|
|
|
$new->setUserInfo($user, $password); |
|
|
|
|
443
|
|
|
|
444
|
|
|
return $new; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Définit le nom d'hôte à utiliser. |
449
|
|
|
*/ |
450
|
|
|
public function setHost(string $str): self |
451
|
|
|
{ |
452
|
29 |
|
$this->host = trim($str); |
453
|
|
|
|
454
|
29 |
|
return $this; |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
/** |
458
|
|
|
* {@inheritDoc} |
459
|
|
|
*/ |
460
|
|
|
public function withHost(string $host): static |
461
|
|
|
{ |
462
|
27 |
|
$new = clone $this; |
463
|
|
|
|
464
|
27 |
|
$new->setHost($host); |
465
|
|
|
|
466
|
27 |
|
return $new; |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
/** |
470
|
|
|
* Définit la partie port de l'URI. |
471
|
|
|
*/ |
472
|
|
|
public function setPort(?int $port = null): self |
473
|
|
|
{ |
474
|
|
|
if (null === $port) { |
475
|
|
|
return $this; |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
if ($port <= 0 || $port > 65535) { |
479
|
2 |
|
throw HttpException::invalidPort($port); |
480
|
|
|
} |
481
|
|
|
|
482
|
2 |
|
$this->port = $port; |
483
|
|
|
|
484
|
2 |
|
return $this; |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
/** |
488
|
|
|
* {@inheritDoc} |
489
|
|
|
*/ |
490
|
|
|
public function withPort(?int $port): static |
491
|
|
|
{ |
492
|
|
|
$new = clone $this; |
493
|
|
|
|
494
|
|
|
$new->setPort($port); |
495
|
|
|
|
496
|
|
|
return $new; |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
/** |
500
|
|
|
* Définit la partie chemin de l'URI. |
501
|
|
|
*/ |
502
|
|
|
public function setPath(string $path): self |
503
|
|
|
{ |
504
|
|
|
$this->path = $this->filterPath($path); |
505
|
|
|
|
506
|
|
|
$tempPath = trim($this->path, '/'); |
507
|
|
|
|
508
|
|
|
$this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); |
509
|
|
|
|
510
|
|
|
return $this; |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* {@inheritDoc} |
515
|
|
|
*/ |
516
|
|
|
public function withPath(string $path): static |
517
|
|
|
{ |
518
|
|
|
$new = clone $this; |
519
|
|
|
|
520
|
|
|
$new->setPath($path); |
521
|
|
|
|
522
|
|
|
return $new; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Définit la partie chemin de l'URI en fonction des segments. |
527
|
|
|
*/ |
528
|
|
|
private function refreshPath(): self |
529
|
|
|
{ |
530
|
|
|
$this->path = $this->filterPath(implode('/', $this->segments)); |
531
|
|
|
|
532
|
|
|
$tempPath = trim($this->path, '/'); |
533
|
|
|
|
534
|
|
|
$this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); |
535
|
|
|
|
536
|
|
|
return $this; |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
/** |
540
|
|
|
* Définit la partie requête de l'URI, tout en essayant |
541
|
|
|
* de nettoyer les différentes parties des clés et des valeurs de la requête. |
542
|
|
|
*/ |
543
|
|
|
public function setQuery(string $query): self |
544
|
|
|
{ |
545
|
|
|
if (str_contains($query, '#')) { |
546
|
4 |
|
throw HttpException::malformedQueryString(); |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
// Ne peut pas avoir de début ? |
550
|
|
|
if ($query !== '' && str_starts_with($query, '?')) { |
551
|
4 |
|
$query = substr($query, 1); |
552
|
|
|
} |
553
|
|
|
|
554
|
4 |
|
parse_str($query, $this->query); |
555
|
|
|
|
556
|
4 |
|
return $this; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
/** |
560
|
|
|
* {@inheritDoc} |
561
|
|
|
*/ |
562
|
|
|
public function withQuery(string $query): static |
563
|
|
|
{ |
564
|
|
|
$new = clone $this; |
565
|
|
|
|
566
|
|
|
$new->setQuery($query); |
567
|
|
|
|
568
|
|
|
return $new; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* Une méthode pratique pour transmettre un tableau d'éléments en tant que requête |
573
|
|
|
* partie de l'URI. |
574
|
|
|
*/ |
575
|
|
|
public function setQueryArray(array $query): self |
576
|
|
|
{ |
577
|
|
|
$query = http_build_query($query); |
578
|
|
|
|
579
|
|
|
return $this->setQuery($query); |
580
|
|
|
} |
581
|
|
|
|
582
|
|
|
/** |
583
|
|
|
* Une méthode pratique pour transmettre un tableau d'éléments en tant que requête |
584
|
|
|
* partie de l'URI. |
585
|
|
|
*/ |
586
|
|
|
public function withQueryParams(array $query): static |
587
|
|
|
{ |
588
|
|
|
$uri = clone $this; |
589
|
|
|
|
590
|
|
|
$uri->setQueryArray($query); |
591
|
|
|
|
592
|
|
|
return $uri; |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
/** |
596
|
|
|
* Ajoute un seul nouvel élément à la requête vars. |
597
|
|
|
*/ |
598
|
|
|
public function addQuery(string $key, mixed $value = null): self |
599
|
|
|
{ |
600
|
|
|
$this->query[$key] = $value; |
601
|
|
|
|
602
|
|
|
return $this; |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
/** |
606
|
|
|
* Supprime une ou plusieurs variables de requête de l'URI. |
607
|
|
|
*/ |
608
|
|
|
public function stripQuery(...$params): self |
609
|
|
|
{ |
610
|
|
|
foreach ($params as $param) { |
611
|
|
|
unset($this->query[$param]); |
612
|
|
|
} |
613
|
|
|
|
614
|
|
|
return $this; |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
/** |
618
|
|
|
* Filtre les variables de requête afin que seules les clés transmises |
619
|
|
|
* sont gardés. Le reste est supprimé de l'objet. |
620
|
|
|
*/ |
621
|
|
|
public function keepQuery(...$params): self |
622
|
|
|
{ |
623
|
|
|
$temp = []; |
624
|
|
|
|
625
|
|
|
foreach ($this->query as $key => $value) { |
626
|
|
|
if (! in_array($key, $params, true)) { |
627
|
|
|
continue; |
628
|
|
|
} |
629
|
|
|
|
630
|
|
|
$temp[$key] = $value; |
631
|
|
|
} |
632
|
|
|
|
633
|
|
|
$this->query = $temp; |
634
|
|
|
|
635
|
|
|
return $this; |
636
|
|
|
} |
637
|
|
|
|
638
|
|
|
/** |
639
|
|
|
* Définit la partie fragment de l'URI. |
640
|
|
|
* |
641
|
|
|
* @see https://tools.ietf.org/html/rfc3986#section-3.5 |
642
|
|
|
*/ |
643
|
|
|
public function setFragment(string $string): self |
644
|
|
|
{ |
645
|
|
|
$this->fragment = trim($string, '# '); |
646
|
|
|
|
647
|
|
|
return $this; |
648
|
|
|
} |
649
|
|
|
|
650
|
|
|
/** |
651
|
|
|
* {@inheritDoc} |
652
|
|
|
*/ |
653
|
|
|
public function withFragment(string $fragment): static |
654
|
|
|
{ |
655
|
|
|
$new = clone $this; |
656
|
|
|
|
657
|
|
|
$new->setFragment($fragment); |
658
|
|
|
|
659
|
|
|
return $new; |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
/** |
663
|
|
|
* Encode tous les caractères dangereux et supprime les segments de points. |
664
|
|
|
* Bien que les segments de points aient des utilisations valides selon la spécification, |
665
|
|
|
* cette classe ne les autorise pas. |
666
|
|
|
*/ |
667
|
|
|
protected function filterPath(?string $path = null): string |
668
|
|
|
{ |
669
|
16 |
|
$orig = $path; |
670
|
|
|
|
671
|
|
|
// Décode/normalise les caractères codés en pourcentage afin que |
672
|
|
|
// nous pouissions toujours avoir une correspondance pour les routes, etc. |
673
|
16 |
|
$path = urldecode($path); |
|
|
|
|
674
|
|
|
|
675
|
|
|
// Supprimer les segments de points |
676
|
16 |
|
$path = self::removeDotSegments($path); |
677
|
|
|
|
678
|
|
|
// Correction de certains cas de bord de barre oblique... |
679
|
|
|
if (str_starts_with($orig, './')) { |
|
|
|
|
680
|
16 |
|
$path = '/' . $path; |
681
|
|
|
} |
682
|
|
|
if (str_starts_with($orig, '../')) { |
683
|
16 |
|
$path = '/' . $path; |
684
|
|
|
} |
685
|
|
|
|
686
|
|
|
// Encode les caractères |
687
|
|
|
$path = preg_replace_callback( |
688
|
|
|
'/(?:[^' . static::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', |
689
|
|
|
static fn (array $matches) => rawurlencode($matches[0]), |
690
|
|
|
$path |
691
|
16 |
|
); |
692
|
|
|
|
693
|
16 |
|
return $path; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
/** |
697
|
|
|
* Enregistre nos pièces à partir d'un appel parse_url. |
698
|
|
|
*/ |
699
|
|
|
protected function applyParts(array $parts) |
700
|
|
|
{ |
701
|
|
|
if (! empty($parts['host'])) { |
702
|
37 |
|
$this->host = $parts['host']; |
703
|
|
|
} |
704
|
|
|
if (! empty($parts['user'])) { |
705
|
2 |
|
$this->user = $parts['user']; |
706
|
|
|
} |
707
|
|
|
if (! empty($parts['path'])) { |
708
|
16 |
|
$this->path = $this->filterPath($parts['path']); |
709
|
|
|
} |
710
|
|
|
if (! empty($parts['query'])) { |
711
|
4 |
|
$this->setQuery($parts['query']); |
712
|
|
|
} |
713
|
|
|
if (! empty($parts['fragment'])) { |
714
|
2 |
|
$this->fragment = $parts['fragment']; |
715
|
|
|
} |
716
|
|
|
|
717
|
|
|
if (isset($parts['scheme'])) { |
718
|
37 |
|
$this->setScheme(rtrim($parts['scheme'], ':/')); |
719
|
|
|
} else { |
720
|
4 |
|
$this->setScheme('http'); |
721
|
|
|
} |
722
|
|
|
|
723
|
|
|
if (isset($parts['port'])) { |
724
|
|
|
// Les numéros de port valides sont appliqués par les précédents parse_url ou setPort() |
725
|
2 |
|
$this->port = $parts['port']; |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
if (isset($parts['pass'])) { |
729
|
2 |
|
$this->password = $parts['pass']; |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
if (isset($parts['path']) && $parts['path'] !== '') { |
733
|
16 |
|
$tempPath = trim($parts['path'], '/'); |
734
|
|
|
|
735
|
16 |
|
$this->segments = ($tempPath === '') ? [] : explode('/', $tempPath); |
736
|
|
|
} |
737
|
|
|
} |
738
|
|
|
|
739
|
|
|
/** |
740
|
|
|
* Combine une chaîne d'URI avec celle-ci en fonction des règles définies dans |
741
|
|
|
* RFC 3986 Section 2 |
742
|
|
|
* |
743
|
|
|
* @see http://tools.ietf.org/html/rfc3986#section-5.2 |
744
|
|
|
*/ |
745
|
|
|
public function resolveRelativeURI(string $uri): self |
746
|
|
|
{ |
747
|
|
|
/* |
748
|
|
|
* REMARQUE : Nous n'utilisons pas removeDotSegments dans cet |
749
|
|
|
* algorithme puisque c'est déjà fait par cette ligne ! |
750
|
|
|
*/ |
751
|
|
|
$relative = new self(); |
752
|
|
|
$relative->setURI($uri); |
753
|
|
|
|
754
|
|
|
if ($relative->getScheme() === $this->getScheme()) { |
755
|
|
|
$relative->setScheme(''); |
756
|
|
|
} |
757
|
|
|
|
758
|
|
|
$transformed = clone $relative; |
759
|
|
|
|
760
|
|
|
// 5.2.2 Transformer les références dans une méthode non stricte (pas de schéma) |
761
|
|
|
if ($relative->getAuthority() !== '' && $relative->getAuthority() !== '0') { |
762
|
|
|
$transformed->setAuthority($relative->getAuthority()) |
763
|
|
|
->setPath($relative->getPath()) |
764
|
|
|
->setQuery($relative->getQuery()); |
765
|
|
|
} else { |
766
|
|
|
if ($relative->getPath() === '') { |
767
|
|
|
$transformed->setPath($this->getPath()); |
768
|
|
|
|
769
|
|
|
if ($relative->getQuery() !== '') { |
770
|
|
|
$transformed->setQuery($relative->getQuery()); |
771
|
|
|
} else { |
772
|
|
|
$transformed->setQuery($this->getQuery()); |
773
|
|
|
} |
774
|
|
|
} else { |
775
|
|
|
if (str_starts_with($relative->getPath(), '/')) { |
776
|
|
|
$transformed->setPath($relative->getPath()); |
777
|
|
|
} else { |
778
|
|
|
$transformed->setPath($this->mergePaths($this, $relative)); |
779
|
|
|
} |
780
|
|
|
|
781
|
|
|
$transformed->setQuery($relative->getQuery()); |
782
|
|
|
} |
783
|
|
|
|
784
|
|
|
$transformed->setAuthority($this->getAuthority()); |
785
|
|
|
} |
786
|
|
|
|
787
|
|
|
$transformed->setScheme($this->getScheme()); |
788
|
|
|
|
789
|
|
|
$transformed->setFragment($relative->getFragment()); |
790
|
|
|
|
791
|
|
|
return $transformed; |
792
|
|
|
} |
793
|
|
|
|
794
|
|
|
/** |
795
|
|
|
* Étant donné 2 chemins, les fusionnera conformément aux règles énoncées dans RFC 2986, section 5.2 |
796
|
|
|
* |
797
|
|
|
* @see http://tools.ietf.org/html/rfc3986#section-5.2.3 |
798
|
|
|
*/ |
799
|
|
|
protected function mergePaths(self $base, self $reference): string |
800
|
|
|
{ |
801
|
|
|
if ($base->getAuthority() !== '' && '' === $base->getPath()) { |
802
|
|
|
return '/' . ltrim($reference->getPath(), '/ '); |
803
|
|
|
} |
804
|
|
|
|
805
|
|
|
$path = explode('/', $base->getPath()); |
806
|
|
|
|
807
|
|
|
if ('' === $path[0]) { |
808
|
|
|
unset($path[0]); |
809
|
|
|
} |
810
|
|
|
|
811
|
|
|
array_pop($path); |
812
|
|
|
$path[] = $reference->getPath(); |
813
|
|
|
|
814
|
|
|
return implode('/', $path); |
815
|
|
|
} |
816
|
|
|
|
817
|
|
|
/** |
818
|
|
|
* Utilisé lors de la résolution et de la fusion de chemins pour interpréter et |
819
|
|
|
* supprimer correctement les segments à un ou deux points du chemin selon RFC 3986 Section 5.2.4 |
820
|
|
|
* |
821
|
|
|
* @see http://tools.ietf.org/html/rfc3986#section-5.2.4 |
822
|
|
|
*/ |
823
|
|
|
public static function removeDotSegments(string $path): string |
824
|
|
|
{ |
825
|
|
|
if ($path === '' || $path === '/') { |
826
|
10 |
|
return $path; |
827
|
|
|
} |
828
|
|
|
|
829
|
16 |
|
$output = []; |
830
|
|
|
|
831
|
16 |
|
$input = explode('/', $path); |
832
|
|
|
|
833
|
|
|
if (empty($input[0])) { |
834
|
16 |
|
unset($input[0]); |
835
|
16 |
|
$input = array_values($input); |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
// Ce n'est pas une représentation parfaite de la |
839
|
|
|
// RFC, mais correspond à la plupart des cas et est joli |
840
|
|
|
// beaucoup ce que Guzzle utilise. Devrait être assez bon |
841
|
|
|
// pour presque tous les cas d'utilisation réels. |
842
|
|
|
foreach ($input as $segment) { |
843
|
|
|
if ($segment === '..') { |
844
|
16 |
|
array_pop($output); |
845
|
|
|
} elseif ($segment !== '.' && $segment !== '') { |
846
|
16 |
|
$output[] = $segment; |
847
|
|
|
} |
848
|
|
|
} |
849
|
|
|
|
850
|
16 |
|
$output = implode('/', $output); |
851
|
16 |
|
$output = ltrim($output, '/ '); |
852
|
|
|
|
853
|
|
|
if ($output !== '/') { |
854
|
|
|
// Ajouter une barre oblique au début si nécessaire |
855
|
|
|
if (str_starts_with($path, '/')) { |
856
|
16 |
|
$output = '/' . $output; |
857
|
|
|
} |
858
|
|
|
|
859
|
|
|
// Ajouter une barre oblique à la fin si nécessaire |
860
|
|
|
if (str_ends_with($path, '/')) { |
861
|
4 |
|
$output .= '/'; |
862
|
|
|
} |
863
|
|
|
} |
864
|
|
|
|
865
|
16 |
|
return $output; |
866
|
|
|
} |
867
|
|
|
|
868
|
|
|
/** |
869
|
|
|
* Modifier le chemin (et le schéma) en supposant que les URI ayant le même hôte que baseURL doivent être relatifs à la configuration du projet. |
870
|
|
|
* |
871
|
|
|
* @deprecated Cette methode pourrait etre supprimer |
872
|
|
|
*/ |
873
|
|
|
private function changeSchemeAndPath(string $scheme, string $path): array |
874
|
|
|
{ |
875
|
|
|
// Vérifier s'il s'agit d'un URI interne |
876
|
6 |
|
$config = (object) config('app'); |
877
|
6 |
|
$baseUri = new self($config->base_url); |
878
|
|
|
|
879
|
|
|
if (str_starts_with($this->getScheme(), 'http') && $this->getHost() === $baseUri->getHost()) { |
880
|
|
|
// Vérifier la présence de segments supplémentaires |
881
|
6 |
|
$basePath = trim($baseUri->getPath(), '/') . '/'; |
882
|
6 |
|
$trimPath = ltrim($path, '/'); |
883
|
|
|
|
884
|
|
|
if ($basePath !== '/' && ! str_starts_with($trimPath, $basePath)) { |
885
|
|
|
$path = $basePath . $trimPath; |
886
|
|
|
} |
887
|
|
|
|
888
|
|
|
// Vérifier si le protocole HTTPS est forcé |
889
|
|
|
if ($config->force_global_secure_requests) { |
890
|
6 |
|
$scheme = 'https'; |
891
|
|
|
} |
892
|
|
|
} |
893
|
|
|
|
894
|
6 |
|
return [$scheme, $path]; |
895
|
|
|
} |
896
|
|
|
} |
897
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.