Uri::changeSchemeAndPath()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 6.105

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 6
eloc 10
c 1
b 1
f 0
nc 5
nop 2
dl 0
loc 22
ccs 6
cts 7
cp 0.8571
crap 6.105
rs 9.2222
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);
0 ignored issues
show
Deprecated Code introduced by
The function BlitzPHP\Http\Uri::changeSchemeAndPath() has been deprecated: Cette methode pourrait etre supprimer ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

328
        [$scheme, $path] = /** @scrutinizer ignore-deprecated */ $this->changeSchemeAndPath($scheme, $path);

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.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $password can also be of type null; however, parameter $pass of BlitzPHP\Http\Uri::setUserInfo() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

442
        $new->setUserInfo($user, /** @scrutinizer ignore-type */ $password);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $path can also be of type null; however, parameter $string of urldecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

673
        $path = urldecode(/** @scrutinizer ignore-type */ $path);
Loading history...
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, './')) {
0 ignored issues
show
Bug introduced by
It seems like $orig can also be of type null; however, parameter $haystack of str_starts_with() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

679
        if (str_starts_with(/** @scrutinizer ignore-type */ $orig, './')) {
Loading history...
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