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

codeigniter4 / CodeIgniter4 / 8677009716

13 Apr 2024 11:45PM UTC coverage: 84.44% (-2.2%) from 86.607%
8677009716

push

github

web-flow
Merge pull request #8776 from kenjis/fix-findQualifiedNameFromPath-Cannot-declare-class-v3

fix: Cannot declare class CodeIgniter\Config\Services, because the name is already in use

0 of 3 new or added lines in 1 file covered. (0.0%)

478 existing lines in 72 files now uncovered.

20318 of 24062 relevant lines covered (84.44%)

188.23 hits per line

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

97.72
/system/HTTP/URI.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\HTTP;
15

16
use BadMethodCallException;
17
use CodeIgniter\HTTP\Exceptions\HTTPException;
18
use Config\App;
19
use InvalidArgumentException;
20
use Stringable;
21

22
/**
23
 * Abstraction for a uniform resource identifier (URI).
24
 *
25
 * @see \CodeIgniter\HTTP\URITest
26
 */
27
class URI implements Stringable
28
{
29
    /**
30
     * Sub-delimiters used in query strings and fragments.
31
     */
32
    public const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
33

34
    /**
35
     * Unreserved characters used in paths, query strings, and fragments.
36
     */
37
    public const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
38

39
    /**
40
     * Current URI string
41
     *
42
     * @var string
43
     *
44
     * @deprecated 4.4.0 Not used.
45
     */
46
    protected $uriString;
47

48
    /**
49
     * The Current baseURL.
50
     *
51
     * @deprecated 4.4.0 Use SiteURI instead.
52
     */
53
    private ?string $baseURL = null;
54

55
    /**
56
     * List of URI segments.
57
     *
58
     * Starts at 1 instead of 0
59
     *
60
     * @var array
61
     */
62
    protected $segments = [];
63

64
    /**
65
     * The URI Scheme.
66
     *
67
     * @var string
68
     */
69
    protected $scheme = 'http';
70

71
    /**
72
     * URI User Info
73
     *
74
     * @var string
75
     */
76
    protected $user;
77

78
    /**
79
     * URI User Password
80
     *
81
     * @var string
82
     */
83
    protected $password;
84

85
    /**
86
     * URI Host
87
     *
88
     * @var string
89
     */
90
    protected $host;
91

92
    /**
93
     * URI Port
94
     *
95
     * @var int
96
     */
97
    protected $port;
98

99
    /**
100
     * URI path.
101
     *
102
     * @var string
103
     */
104
    protected $path;
105

106
    /**
107
     * The name of any fragment.
108
     *
109
     * @var string
110
     */
111
    protected $fragment = '';
112

113
    /**
114
     * The query string.
115
     *
116
     * @var array
117
     */
118
    protected $query = [];
119

120
    /**
121
     * Default schemes/ports.
122
     *
123
     * @var array
124
     */
125
    protected $defaultPorts = [
126
        'http'  => 80,
127
        'https' => 443,
128
        'ftp'   => 21,
129
        'sftp'  => 22,
130
    ];
131

132
    /**
133
     * Whether passwords should be shown in userInfo/authority calls.
134
     * Default to false because URIs often show up in logs
135
     *
136
     * @var bool
137
     */
138
    protected $showPassword = false;
139

140
    /**
141
     * If true, will continue instead of throwing exceptions.
142
     *
143
     * @var bool
144
     */
145
    protected $silent = false;
146

147
    /**
148
     * If true, will use raw query string.
149
     *
150
     * @var bool
151
     */
152
    protected $rawQueryString = false;
153

154
    /**
155
     * Builds a representation of the string from the component parts.
156
     *
157
     * @param string|null $scheme URI scheme. E.g., http, ftp
158
     *
159
     * @return string URI string with only passed parts. Maybe incomplete as a URI.
160
     */
161
    public static function createURIString(
162
        ?string $scheme = null,
163
        ?string $authority = null,
164
        ?string $path = null,
165
        ?string $query = null,
166
        ?string $fragment = null
167
    ): string {
168
        $uri = '';
1,562✔
169
        if ($scheme !== null && $scheme !== '') {
1,562✔
170
            $uri .= $scheme . '://';
1,560✔
171
        }
172

173
        if ($authority !== null && $authority !== '') {
1,562✔
174
            $uri .= $authority;
1,539✔
175
        }
176

177
        if (isset($path) && $path !== '') {
1,562✔
178
            $uri .= ! str_ends_with($uri, '/')
1,555✔
179
                ? '/' . ltrim($path, '/')
1,532✔
180
                : ltrim($path, '/');
23✔
181
        }
182

183
        if ($query !== '' && $query !== null) {
1,562✔
184
            $uri .= '?' . $query;
62✔
185
        }
186

187
        if ($fragment !== '' && $fragment !== null) {
1,562✔
188
            $uri .= '#' . $fragment;
8✔
189
        }
190

191
        return $uri;
1,562✔
192
    }
193

194
    /**
195
     * Used when resolving and merging paths to correctly interpret and
196
     * remove single and double dot segments from the path per
197
     * RFC 3986 Section 5.2.4
198
     *
199
     * @see http://tools.ietf.org/html/rfc3986#section-5.2.4
200
     *
201
     * @internal
202
     */
203
    public static function removeDotSegments(string $path): string
204
    {
205
        if ($path === '' || $path === '/') {
1,654✔
206
            return $path;
1,488✔
207
        }
208

209
        $output = [];
1,591✔
210

211
        $input = explode('/', $path);
1,591✔
212

213
        if ($input[0] === '') {
1,591✔
214
            unset($input[0]);
1,566✔
215
            $input = array_values($input);
1,566✔
216
        }
217

218
        // This is not a perfect representation of the
219
        // RFC, but matches most cases and is pretty
220
        // much what Guzzle uses. Should be good enough
221
        // for almost every real use case.
222
        foreach ($input as $segment) {
1,591✔
223
            if ($segment === '..') {
1,591✔
224
                array_pop($output);
16✔
225
            } elseif ($segment !== '.' && $segment !== '') {
1,589✔
226
                $output[] = $segment;
1,586✔
227
            }
228
        }
229

230
        $output = implode('/', $output);
1,591✔
231
        $output = trim($output, '/ ');
1,591✔
232

233
        // Add leading slash if necessary
234
        if (str_starts_with($path, '/')) {
1,591✔
235
            $output = '/' . $output;
1,566✔
236
        }
237

238
        // Add trailing slash if necessary
239
        if ($output !== '/' && str_ends_with($path, '/')) {
1,591✔
240
            $output .= '/';
967✔
241
        }
242

243
        return $output;
1,591✔
244
    }
245

246
    /**
247
     * Constructor.
248
     *
249
     * @param string|null $uri The URI to parse.
250
     *
251
     * @throws HTTPException
252
     *
253
     * @TODO null for param $uri should be removed.
254
     *      See https://www.php-fig.org/psr/psr-17/#26-urifactoryinterface
255
     */
256
    public function __construct(?string $uri = null)
257
    {
258
        if ($uri !== null) {
1,720✔
259
            $this->setURI($uri);
1,585✔
260
        }
261
    }
262

263
    /**
264
     * If $silent == true, then will not throw exceptions and will
265
     * attempt to continue gracefully.
266
     *
267
     * @deprecated 4.4.0 Method not in PSR-7
268
     *
269
     * @return URI
270
     */
271
    public function setSilent(bool $silent = true)
272
    {
273
        $this->silent = $silent;
14✔
274

275
        return $this;
14✔
276
    }
277

278
    /**
279
     * If $raw == true, then will use parseStr() method
280
     * instead of native parse_str() function.
281
     *
282
     * Note: Method not in PSR-7
283
     *
284
     * @return URI
285
     */
286
    public function useRawQueryString(bool $raw = true)
287
    {
288
        $this->rawQueryString = $raw;
142✔
289

290
        return $this;
142✔
291
    }
292

293
    /**
294
     * Sets and overwrites any current URI information.
295
     *
296
     * @return URI
297
     *
298
     * @throws HTTPException
299
     *
300
     * @deprecated 4.4.0 This method will be private.
301
     */
302
    public function setURI(?string $uri = null)
303
    {
304
        if ($uri !== null) {
1,608✔
305
            $parts = parse_url($uri);
1,608✔
306

307
            if ($parts === false) {
1,608✔
308
                if ($this->silent) {
4✔
309
                    return $this;
1✔
310
                }
311

312
                throw HTTPException::forUnableToParseURI($uri);
3✔
313
            }
314

315
            $this->applyParts($parts);
1,604✔
316
        }
317

318
        return $this;
1,604✔
319
    }
320

321
    /**
322
     * Retrieve the scheme component of the URI.
323
     *
324
     * If no scheme is present, this method MUST return an empty string.
325
     *
326
     * The value returned MUST be normalized to lowercase, per RFC 3986
327
     * Section 3.1.
328
     *
329
     * The trailing ":" character is not part of the scheme and MUST NOT be
330
     * added.
331
     *
332
     * @see    https://tools.ietf.org/html/rfc3986#section-3.1
333
     *
334
     * @return string The URI scheme.
335
     */
336
    public function getScheme(): string
337
    {
338
        return $this->scheme;
1,559✔
339
    }
340

341
    /**
342
     * Retrieve the authority component of the URI.
343
     *
344
     * If no authority information is present, this method MUST return an empty
345
     * string.
346
     *
347
     * The authority syntax of the URI is:
348
     *
349
     * <pre>
350
     * [user-info@]host[:port]
351
     * </pre>
352
     *
353
     * If the port component is not set or is the standard port for the current
354
     * scheme, it SHOULD NOT be included.
355
     *
356
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
357
     *
358
     * @return string The URI authority, in "[user-info@]host[:port]" format.
359
     */
360
    public function getAuthority(bool $ignorePort = false): string
361
    {
362
        if (empty($this->host)) {
1,564✔
363
            return '';
69✔
364
        }
365

366
        $authority = $this->host;
1,542✔
367

368
        if (! empty($this->getUserInfo())) {
1,542✔
369
            $authority = $this->getUserInfo() . '@' . $authority;
7✔
370
        }
371

372
        // Don't add port if it's a standard port for
373
        // this scheme
374
        if (! empty($this->port) && ! $ignorePort && $this->port !== $this->defaultPorts[$this->scheme]) {
1,542✔
375
            $authority .= ':' . $this->port;
81✔
376
        }
377

378
        $this->showPassword = false;
1,542✔
379

380
        return $authority;
1,542✔
381
    }
382

383
    /**
384
     * Retrieve the user information component of the URI.
385
     *
386
     * If no user information is present, this method MUST return an empty
387
     * string.
388
     *
389
     * If a user is present in the URI, this will return that value;
390
     * additionally, if the password is also present, it will be appended to the
391
     * user value, with a colon (":") separating the values.
392
     *
393
     * NOTE that be default, the password, if available, will NOT be shown
394
     * as a security measure as discussed in RFC 3986, Section 7.5. If you know
395
     * the password is not a security issue, you can force it to be shown
396
     * with $this->showPassword();
397
     *
398
     * The trailing "@" character is not part of the user information and MUST
399
     * NOT be added.
400
     *
401
     * @return string|null The URI user information, in "username[:password]" format.
402
     */
403
    public function getUserInfo()
404
    {
405
        $userInfo = $this->user;
1,542✔
406

407
        if ($this->showPassword === true && ! empty($this->password)) {
1,542✔
408
            $userInfo .= ':' . $this->password;
1✔
409
        }
410

411
        return $userInfo;
1,542✔
412
    }
413

414
    /**
415
     * Temporarily sets the URI to show a password in userInfo. Will
416
     * reset itself after the first call to authority().
417
     *
418
     * Note: Method not in PSR-7
419
     *
420
     * @return URI
421
     */
422
    public function showPassword(bool $val = true)
423
    {
424
        $this->showPassword = $val;
1✔
425

426
        return $this;
1✔
427
    }
428

429
    /**
430
     * Retrieve the host component of the URI.
431
     *
432
     * If no host is present, this method MUST return an empty string.
433
     *
434
     * The value returned MUST be normalized to lowercase, per RFC 3986
435
     * Section 3.2.2.
436
     *
437
     * @see    http://tools.ietf.org/html/rfc3986#section-3.2.2
438
     *
439
     * @return string The URI host.
440
     */
441
    public function getHost(): string
442
    {
443
        return $this->host ?? '';
1,616✔
444
    }
445

446
    /**
447
     * Retrieve the port component of the URI.
448
     *
449
     * If a port is present, and it is non-standard for the current scheme,
450
     * this method MUST return it as an integer. If the port is the standard port
451
     * used with the current scheme, this method SHOULD return null.
452
     *
453
     * If no port is present, and no scheme is present, this method MUST return
454
     * a null value.
455
     *
456
     * If no port is present, but a scheme is present, this method MAY return
457
     * the standard port for that scheme, but SHOULD return null.
458
     *
459
     * @return int|null The URI port.
460
     */
461
    public function getPort()
462
    {
463
        return $this->port;
54✔
464
    }
465

466
    /**
467
     * Retrieve the path component of the URI.
468
     *
469
     * The path can either be empty or absolute (starting with a slash) or
470
     * rootless (not starting with a slash). Implementations MUST support all
471
     * three syntaxes.
472
     *
473
     * Normally, the empty path "" and absolute path "/" are considered equal as
474
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
475
     * do this normalization because in contexts with a trimmed base path, e.g.
476
     * the front controller, this difference becomes significant. It's the task
477
     * of the user to handle both "" and "/".
478
     *
479
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
480
     * any characters. To determine what characters to encode, please refer to
481
     * RFC 3986, Sections 2 and 3.3.
482
     *
483
     * As an example, if the value should include a slash ("/") not intended as
484
     * delimiter between path segments, that value MUST be passed in encoded
485
     * form (e.g., "%2F") to the instance.
486
     *
487
     * @see    https://tools.ietf.org/html/rfc3986#section-2
488
     * @see    https://tools.ietf.org/html/rfc3986#section-3.3
489
     *
490
     * @return string The URI path.
491
     */
492
    public function getPath(): string
493
    {
494
        return $this->path ?? '';
1,576✔
495
    }
496

497
    /**
498
     * Retrieve the query string
499
     */
500
    public function getQuery(array $options = []): string
501
    {
502
        $vars = $this->query;
1,559✔
503

504
        if (array_key_exists('except', $options)) {
1,559✔
505
            if (! is_array($options['except'])) {
2✔
506
                $options['except'] = [$options['except']];
1✔
507
            }
508

509
            foreach ($options['except'] as $var) {
2✔
510
                unset($vars[$var]);
2✔
511
            }
512
        } elseif (array_key_exists('only', $options)) {
1,558✔
513
            $temp = [];
3✔
514

515
            if (! is_array($options['only'])) {
3✔
516
                $options['only'] = [$options['only']];
1✔
517
            }
518

519
            foreach ($options['only'] as $var) {
3✔
520
                if (array_key_exists($var, $vars)) {
3✔
521
                    $temp[$var] = $vars[$var];
3✔
522
                }
523
            }
524

525
            $vars = $temp;
3✔
526
        }
527

528
        return empty($vars) ? '' : http_build_query($vars);
1,559✔
529
    }
530

531
    /**
532
     * Retrieve a URI fragment
533
     */
534
    public function getFragment(): string
535
    {
536
        return $this->fragment ?? '';
1,554✔
537
    }
538

539
    /**
540
     * Returns the segments of the path as an array.
541
     */
542
    public function getSegments(): array
543
    {
544
        return $this->segments;
39✔
545
    }
546

547
    /**
548
     * Returns the value of a specific segment of the URI path.
549
     * Allows to get only existing segments or the next one.
550
     *
551
     * @param int    $number  Segment number starting at 1
552
     * @param string $default Default value
553
     *
554
     * @return string The value of the segment. If you specify the last +1
555
     *                segment, the $default value. If you specify the last +2
556
     *                or more throws HTTPException.
557
     */
558
    public function getSegment(int $number, string $default = ''): string
559
    {
560
        if ($number < 1) {
18✔
561
            throw HTTPException::forURISegmentOutOfRange($number);
1✔
562
        }
563

564
        if ($number > count($this->segments) + 1 && ! $this->silent) {
17✔
565
            throw HTTPException::forURISegmentOutOfRange($number);
4✔
566
        }
567

568
        // The segment should treat the array as 1-based for the user
569
        // but we still have to deal with a zero-based array.
570
        $number--;
13✔
571

572
        return $this->segments[$number] ?? $default;
13✔
573
    }
574

575
    /**
576
     * Set the value of a specific segment of the URI path.
577
     * Allows to set only existing segments or add new one.
578
     *
579
     * Note: Method not in PSR-7
580
     *
581
     * @param int        $number Segment number starting at 1
582
     * @param int|string $value
583
     *
584
     * @return $this
585
     */
586
    public function setSegment(int $number, $value)
587
    {
588
        if ($number < 1) {
21✔
589
            throw HTTPException::forURISegmentOutOfRange($number);
1✔
590
        }
591

592
        if ($number > count($this->segments) + 1) {
20✔
593
            if ($this->silent) {
5✔
594
                return $this;
2✔
595
            }
596

597
            throw HTTPException::forURISegmentOutOfRange($number);
3✔
598
        }
599

600
        // The segment should treat the array as 1-based for the user
601
        // but we still have to deal with a zero-based array.
602
        $number--;
16✔
603

604
        $this->segments[$number] = $value;
16✔
605
        $this->refreshPath();
16✔
606

607
        return $this;
16✔
608
    }
609

610
    /**
611
     * Returns the total number of segments.
612
     *
613
     * Note: Method not in PSR-7
614
     */
615
    public function getTotalSegments(): int
616
    {
617
        return count($this->segments);
35✔
618
    }
619

620
    /**
621
     * Formats the URI as a string.
622
     *
623
     * Warning: For backwards-compatability this method
624
     * assumes URIs with the same host as baseURL should
625
     * be relative to the project's configuration.
626
     * This aspect of __toString() is deprecated and should be avoided.
627
     */
628
    public function __toString(): string
629
    {
630
        $path   = $this->getPath();
1,478✔
631
        $scheme = $this->getScheme();
1,478✔
632

633
        // If the hosts matches then assume this should be relative to baseURL
634
        [$scheme, $path] = $this->changeSchemeAndPath($scheme, $path);
1,478✔
635

636
        return static::createURIString(
1,478✔
637
            $scheme,
1,478✔
638
            $this->getAuthority(),
1,478✔
639
            $path, // Absolute URIs should use a "/" for an empty path
1,478✔
640
            $this->getQuery(),
1,478✔
641
            $this->getFragment()
1,478✔
642
        );
1,478✔
643
    }
644

645
    /**
646
     * Change the path (and scheme) assuming URIs with the same host as baseURL
647
     * should be relative to the project's configuration.
648
     *
649
     * @deprecated This method will be deleted.
650
     */
651
    private function changeSchemeAndPath(string $scheme, string $path): array
652
    {
653
        // Check if this is an internal URI
654
        $config  = config(App::class);
1,478✔
655
        $baseUri = new self($config->baseURL);
1,478✔
656

657
        if (
658
            str_starts_with($this->getScheme(), 'http')
1,478✔
659
            && $this->getHost() === $baseUri->getHost()
1,478✔
660
        ) {
661
            // Check for additional segments
662
            $basePath = trim($baseUri->getPath(), '/') . '/';
1,439✔
663
            $trimPath = ltrim($path, '/');
1,439✔
664

665
            if ($basePath !== '/' && ! str_starts_with($trimPath, $basePath)) {
1,439✔
UNCOV
666
                $path = $basePath . $trimPath;
×
667
            }
668

669
            // Check for forced HTTPS
670
            if ($config->forceGlobalSecureRequests) {
1,439✔
671
                $scheme = 'https';
5✔
672
            }
673
        }
674

675
        return [$scheme, $path];
1,478✔
676
    }
677

678
    /**
679
     * Parses the given string and saves the appropriate authority pieces.
680
     *
681
     * Note: Method not in PSR-7
682
     *
683
     * @return $this
684
     */
685
    public function setAuthority(string $str)
686
    {
687
        $parts = parse_url($str);
70✔
688

689
        if (! isset($parts['path'])) {
70✔
690
            $parts['path'] = $this->getPath();
1✔
691
        }
692

693
        if (empty($parts['host']) && $parts['path'] !== '') {
70✔
694
            $parts['host'] = $parts['path'];
49✔
695
            unset($parts['path']);
49✔
696
        }
697

698
        $this->applyParts($parts);
70✔
699

700
        return $this;
70✔
701
    }
702

703
    /**
704
     * Sets the scheme for this URI.
705
     *
706
     * Because of the large number of valid schemes we cannot limit this
707
     * to only http or https.
708
     *
709
     * @see https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
710
     *
711
     * @return $this
712
     *
713
     * @deprecated 4.4.0 Use `withScheme()` instead.
714
     */
715
    public function setScheme(string $str)
716
    {
717
        $str          = strtolower($str);
1,605✔
718
        $this->scheme = preg_replace('#:(//)?$#', '', $str);
1,605✔
719

720
        return $this;
1,605✔
721
    }
722

723
    /**
724
     * Return an instance with the specified scheme.
725
     *
726
     * This method MUST retain the state of the current instance, and return
727
     * an instance that contains the specified scheme.
728
     *
729
     * Implementations MUST support the schemes "http" and "https" case
730
     * insensitively, and MAY accommodate other schemes if required.
731
     *
732
     * An empty scheme is equivalent to removing the scheme.
733
     *
734
     * @param string $scheme The scheme to use with the new instance.
735
     *
736
     * @return static A new instance with the specified scheme.
737
     *
738
     * @throws InvalidArgumentException for invalid or unsupported schemes.
739
     */
740
    public function withScheme(string $scheme)
741
    {
742
        $uri = clone $this;
7✔
743

744
        $scheme = strtolower($scheme);
7✔
745

746
        $uri->scheme = preg_replace('#:(//)?$#', '', $scheme);
7✔
747

748
        return $uri;
7✔
749
    }
750

751
    /**
752
     * Sets the userInfo/Authority portion of the URI.
753
     *
754
     * @param string $user The user's username
755
     * @param string $pass The user's password
756
     *
757
     * @return $this
758
     *
759
     * @TODO PSR-7: Should be `withUserInfo($user, $password = null)`.
760
     */
761
    public function setUserInfo(string $user, string $pass)
762
    {
763
        $this->user     = trim($user);
2✔
764
        $this->password = trim($pass);
2✔
765

766
        return $this;
2✔
767
    }
768

769
    /**
770
     * Sets the host name to use.
771
     *
772
     * @return $this
773
     *
774
     * @TODO PSR-7: Should be `withHost($host)`.
775
     */
776
    public function setHost(string $str)
777
    {
778
        $this->host = trim($str);
107✔
779

780
        return $this;
107✔
781
    }
782

783
    /**
784
     * Sets the port portion of the URI.
785
     *
786
     * @return $this
787
     *
788
     * @TODO PSR-7: Should be `withPort($port)`.
789
     */
790
    public function setPort(?int $port = null)
791
    {
792
        if ($port === null) {
5✔
793
            return $this;
×
794
        }
795

796
        if ($port <= 0 || $port > 65535) {
5✔
797
            if ($this->silent) {
4✔
798
                return $this;
1✔
799
            }
800

801
            throw HTTPException::forInvalidPort($port);
3✔
802
        }
803

804
        $this->port = $port;
1✔
805

806
        return $this;
1✔
807
    }
808

809
    /**
810
     * Sets the path portion of the URI.
811
     *
812
     * @return $this
813
     *
814
     * @TODO PSR-7: Should be `withPath($port)`.
815
     */
816
    public function setPath(string $path)
817
    {
818
        $this->path = $this->filterPath($path);
89✔
819

820
        $tempPath = trim($this->path, '/');
89✔
821

822
        $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath);
89✔
823

824
        return $this;
89✔
825
    }
826

827
    /**
828
     * Sets the current baseURL.
829
     *
830
     * @interal
831
     *
832
     * @deprecated Use SiteURI instead.
833
     */
834
    public function setBaseURL(string $baseURL): void
835
    {
UNCOV
836
        $this->baseURL = $baseURL;
×
837
    }
838

839
    /**
840
     * Returns the current baseURL.
841
     *
842
     * @interal
843
     *
844
     * @deprecated Use SiteURI instead.
845
     */
846
    public function getBaseURL(): string
847
    {
UNCOV
848
        if ($this->baseURL === null) {
×
UNCOV
849
            throw new BadMethodCallException('The $baseURL is not set.');
×
850
        }
851

UNCOV
852
        return $this->baseURL;
×
853
    }
854

855
    /**
856
     * Sets the path portion of the URI based on segments.
857
     *
858
     * @return $this
859
     *
860
     * @deprecated This method will be private.
861
     */
862
    public function refreshPath()
863
    {
864
        $this->path = $this->filterPath(implode('/', $this->segments));
11✔
865

866
        $tempPath = trim($this->path, '/');
11✔
867

868
        $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath);
11✔
869

870
        return $this;
11✔
871
    }
872

873
    /**
874
     * Sets the query portion of the URI, while attempting
875
     * to clean the various parts of the query keys and values.
876
     *
877
     * @return $this
878
     *
879
     * @TODO PSR-7: Should be `withQuery($query)`.
880
     */
881
    public function setQuery(string $query)
882
    {
883
        if (str_contains($query, '#')) {
222✔
884
            if ($this->silent) {
2✔
885
                return $this;
1✔
886
            }
887

888
            throw HTTPException::forMalformedQueryString();
1✔
889
        }
890

891
        // Can't have leading ?
892
        if ($query !== '' && str_starts_with($query, '?')) {
220✔
893
            $query = substr($query, 1);
2✔
894
        }
895

896
        if ($this->rawQueryString) {
220✔
897
            $this->query = $this->parseStr($query);
2✔
898
        } else {
899
            parse_str($query, $this->query);
218✔
900
        }
901

902
        return $this;
220✔
903
    }
904

905
    /**
906
     * A convenience method to pass an array of items in as the Query
907
     * portion of the URI.
908
     *
909
     * @return URI
910
     *
911
     * @TODO: PSR-7: Should be `withQueryParams(array $query)`
912
     */
913
    public function setQueryArray(array $query)
914
    {
915
        $query = http_build_query($query);
16✔
916

917
        return $this->setQuery($query);
16✔
918
    }
919

920
    /**
921
     * Adds a single new element to the query vars.
922
     *
923
     * Note: Method not in PSR-7
924
     *
925
     * @param int|string|null $value
926
     *
927
     * @return $this
928
     */
929
    public function addQuery(string $key, $value = null)
930
    {
931
        $this->query[$key] = $value;
40✔
932

933
        return $this;
40✔
934
    }
935

936
    /**
937
     * Removes one or more query vars from the URI.
938
     *
939
     * Note: Method not in PSR-7
940
     *
941
     * @param string ...$params
942
     *
943
     * @return $this
944
     */
945
    public function stripQuery(...$params)
946
    {
947
        foreach ($params as $param) {
1✔
948
            unset($this->query[$param]);
1✔
949
        }
950

951
        return $this;
1✔
952
    }
953

954
    /**
955
     * Filters the query variables so that only the keys passed in
956
     * are kept. The rest are removed from the object.
957
     *
958
     * Note: Method not in PSR-7
959
     *
960
     * @param string ...$params
961
     *
962
     * @return $this
963
     */
964
    public function keepQuery(...$params)
965
    {
966
        $temp = [];
1✔
967

968
        foreach ($this->query as $key => $value) {
1✔
969
            if (! in_array($key, $params, true)) {
1✔
970
                continue;
1✔
971
            }
972

973
            $temp[$key] = $value;
1✔
974
        }
975

976
        $this->query = $temp;
1✔
977

978
        return $this;
1✔
979
    }
980

981
    /**
982
     * Sets the fragment portion of the URI.
983
     *
984
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
985
     *
986
     * @return $this
987
     *
988
     * @TODO PSR-7: Should be `withFragment($fragment)`.
989
     */
990
    public function setFragment(string $string)
991
    {
992
        $this->fragment = trim($string, '# ');
164✔
993

994
        return $this;
164✔
995
    }
996

997
    /**
998
     * Encodes any dangerous characters, and removes dot segments.
999
     * While dot segments have valid uses according to the spec,
1000
     * this URI class does not allow them.
1001
     */
1002
    protected function filterPath(?string $path = null): string
1003
    {
1004
        $orig = $path;
1,613✔
1005

1006
        // Decode/normalize percent-encoded chars so
1007
        // we can always have matching for Routes, etc.
1008
        $path = urldecode($path);
1,613✔
1009

1010
        // Remove dot segments
1011
        $path = self::removeDotSegments($path);
1,613✔
1012

1013
        // Fix up some leading slash edge cases...
1014
        if (str_starts_with($orig, './')) {
1,613✔
1015
            $path = '/' . $path;
1✔
1016
        }
1017
        if (str_starts_with($orig, '../')) {
1,613✔
1018
            $path = '/' . $path;
1✔
1019
        }
1020

1021
        // Encode characters
1022
        $path = preg_replace_callback(
1,613✔
1023
            '/(?:[^' . static::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/',
1,613✔
1024
            static fn (array $matches) => rawurlencode($matches[0]),
1,613✔
1025
            $path
1,613✔
1026
        );
1,613✔
1027

1028
        return $path;
1,613✔
1029
    }
1030

1031
    /**
1032
     * Saves our parts from a parse_url call.
1033
     *
1034
     * @return void
1035
     */
1036
    protected function applyParts(array $parts)
1037
    {
1038
        if (! empty($parts['host'])) {
1,605✔
1039
            $this->host = $parts['host'];
1,585✔
1040
        }
1041
        if (! empty($parts['user'])) {
1,605✔
1042
            $this->user = $parts['user'];
5✔
1043
        }
1044
        if (isset($parts['path']) && $parts['path'] !== '') {
1,605✔
1045
            $this->path = $this->filterPath($parts['path']);
1,603✔
1046
        }
1047
        if (! empty($parts['query'])) {
1,605✔
1048
            $this->setQuery($parts['query']);
10✔
1049
        }
1050
        if (! empty($parts['fragment'])) {
1,605✔
1051
            $this->fragment = $parts['fragment'];
4✔
1052
        }
1053

1054
        // Scheme
1055
        if (isset($parts['scheme'])) {
1,605✔
1056
            $this->setScheme(rtrim($parts['scheme'], ':/'));
1,584✔
1057
        } else {
1058
            $this->setScheme('http');
72✔
1059
        }
1060

1061
        // Port
1062
        if (isset($parts['port']) && $parts['port'] !== null) {
1,605✔
1063
            // Valid port numbers are enforced by earlier parse_url or setPort()
1064
            $this->port = $parts['port'];
90✔
1065
        }
1066

1067
        if (isset($parts['pass'])) {
1,605✔
1068
            $this->password = $parts['pass'];
2✔
1069
        }
1070

1071
        // Populate our segments array
1072
        if (isset($parts['path']) && $parts['path'] !== '') {
1,605✔
1073
            $tempPath = trim($parts['path'], '/');
1,603✔
1074

1075
            $this->segments = ($tempPath === '') ? [] : explode('/', $tempPath);
1,603✔
1076
        }
1077
    }
1078

1079
    /**
1080
     * Combines one URI string with this one based on the rules set out in
1081
     * RFC 3986 Section 2
1082
     *
1083
     * @see http://tools.ietf.org/html/rfc3986#section-5.2
1084
     *
1085
     * @return URI
1086
     */
1087
    public function resolveRelativeURI(string $uri)
1088
    {
1089
        /*
1090
         * NOTE: We don't use removeDotSegments in this
1091
         * algorithm since it's already done by this line!
1092
         */
1093
        $relative = new self();
69✔
1094
        $relative->setURI($uri);
69✔
1095

1096
        if ($relative->getScheme() === $this->getScheme()) {
69✔
1097
            $relative->setScheme('');
61✔
1098
        }
1099

1100
        $transformed = clone $relative;
69✔
1101

1102
        // 5.2.2 Transform References in a non-strict method (no scheme)
1103
        if ($relative->getAuthority() !== '') {
69✔
1104
            $transformed
2✔
1105
                ->setAuthority($relative->getAuthority())
2✔
1106
                ->setPath($relative->getPath())
2✔
1107
                ->setQuery($relative->getQuery());
2✔
1108
        } else {
1109
            if ($relative->getPath() === '') {
67✔
1110
                $transformed->setPath($this->getPath());
4✔
1111

1112
                if ($relative->getQuery() !== '') {
4✔
1113
                    $transformed->setQuery($relative->getQuery());
2✔
1114
                } else {
1115
                    $transformed->setQuery($this->getQuery());
2✔
1116
                }
1117
            } else {
1118
                if (str_starts_with($relative->getPath(), '/')) {
63✔
1119
                    $transformed->setPath($relative->getPath());
24✔
1120
                } else {
1121
                    $transformed->setPath($this->mergePaths($this, $relative));
39✔
1122
                }
1123

1124
                $transformed->setQuery($relative->getQuery());
63✔
1125
            }
1126

1127
            $transformed->setAuthority($this->getAuthority());
67✔
1128
        }
1129

1130
        $transformed->setScheme($this->getScheme());
69✔
1131

1132
        $transformed->setFragment($relative->getFragment());
69✔
1133

1134
        return $transformed;
69✔
1135
    }
1136

1137
    /**
1138
     * Given 2 paths, will merge them according to rules set out in RFC 2986,
1139
     * Section 5.2
1140
     *
1141
     * @see http://tools.ietf.org/html/rfc3986#section-5.2.3
1142
     */
1143
    protected function mergePaths(self $base, self $reference): string
1144
    {
1145
        if ($base->getAuthority() !== '' && $base->getPath() === '') {
39✔
1146
            return '/' . ltrim($reference->getPath(), '/ ');
1✔
1147
        }
1148

1149
        $path = explode('/', $base->getPath());
38✔
1150

1151
        if ($path[0] === '') {
38✔
1152
            unset($path[0]);
38✔
1153
        }
1154

1155
        array_pop($path);
38✔
1156
        $path[] = $reference->getPath();
38✔
1157

1158
        return implode('/', $path);
38✔
1159
    }
1160

1161
    /**
1162
     * This is equivalent to the native PHP parse_str() function.
1163
     * This version allows the dot to be used as a key of the query string.
1164
     */
1165
    protected function parseStr(string $query): array
1166
    {
1167
        $return = [];
2✔
1168
        $query  = explode('&', $query);
2✔
1169

1170
        $params = array_map(static fn (string $chunk) => preg_replace_callback(
2✔
1171
            '/^(?<key>[^&=]+?)(?:\[[^&=]*\])?=(?<value>[^&=]+)/',
2✔
1172
            static fn (array $match) => str_replace($match['key'], bin2hex($match['key']), $match[0]),
2✔
1173
            urldecode($chunk)
2✔
1174
        ), $query);
2✔
1175

1176
        $params = implode('&', $params);
2✔
1177
        parse_str($params, $result);
2✔
1178

1179
        foreach ($result as $key => $value) {
2✔
1180
            $return[hex2bin($key)] = $value;
2✔
1181
        }
1182

1183
        return $return;
2✔
1184
    }
1185
}
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

© 2025 Coveralls, Inc