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

voku / httpful / 5623107679

pending completion
5623107679

push

github

voku
[+]: fix test for the new release

1596 of 2486 relevant lines covered (64.2%)

81.28 hits per line

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

73.99
/src/Httpful/Uri.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Httpful;
6

7
use Psr\Http\Message\UriInterface;
8

9
class Uri implements UriInterface
10
{
11
    /**
12
     * Absolute http and https URIs require a host per RFC 7230 Section 2.7
13
     * but in generic URIs the host can be empty. So for http(s) URIs
14
     * we apply this default host when no host is given yet to form a
15
     * valid URI.
16
     */
17
    const HTTP_DEFAULT_HOST = 'localhost';
18

19
    /**
20
     * @var array
21
     */
22
    private static $defaultPorts = [
23
        'http'   => 80,
24
        'https'  => 443,
25
        'ftp'    => 21,
26
        'gopher' => 70,
27
        'nntp'   => 119,
28
        'news'   => 119,
29
        'telnet' => 23,
30
        'tn3270' => 23,
31
        'imap'   => 143,
32
        'pop'    => 110,
33
        'ldap'   => 389,
34
    ];
35

36
    /**
37
     * @var string
38
     */
39
    private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
40

41
    /**
42
     * @var string
43
     */
44
    private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
45

46
    /**
47
     * @var array
48
     */
49
    private static $replaceQuery = [
50
        '=' => '%3D',
51
        '&' => '%26',
52
    ];
53

54
    /**
55
     * @var string uri scheme
56
     */
57
    private $scheme = '';
58

59
    /**
60
     * @var string uri user info
61
     */
62
    private $userInfo = '';
63

64
    /**
65
     * @var string uri host
66
     */
67
    private $host = '';
68

69
    /**
70
     * @var int|null uri port
71
     */
72
    private $port;
73

74
    /**
75
     * @var string uri path
76
     */
77
    private $path = '';
78

79
    /**
80
     * @var string uri query string
81
     */
82
    private $query = '';
83

84
    /**
85
     * @var string uri fragment
86
     */
87
    private $fragment = '';
88

89
    /**
90
     * @param string $uri URI to parse
91
     */
92
    public function __construct($uri = '')
93
    {
94
        // weak type check to also accept null until we can add scalar type hints
95
        if ($uri !== '') {
676✔
96
            $parts = \parse_url($uri);
452✔
97

98
            if ($parts === false) {
452✔
99
                throw new \InvalidArgumentException("Unable to parse URI: {$uri}");
12✔
100
            }
101

102
            $this->_applyParts($parts);
440✔
103
        }
104
    }
105

106
    /**
107
     * @return string
108
     */
109
    public function __toString(): string
110
    {
111
        return self::composeComponents(
604✔
112
            $this->scheme,
604✔
113
            $this->getAuthority(),
604✔
114
            $this->path,
604✔
115
            $this->query,
604✔
116
            $this->fragment
604✔
117
        );
604✔
118
    }
119

120
    /**
121
     * @return string
122
     */
123
    public function getAuthority(): string
124
    {
125
        if ($this->host === '') {
636✔
126
            return '';
580✔
127
        }
128

129
        $authority = $this->host;
252✔
130
        if ($this->userInfo !== '') {
252✔
131
            $authority = $this->userInfo . '@' . $authority;
16✔
132
        }
133

134
        if ($this->port !== null) {
252✔
135
            $authority .= ':' . $this->port;
28✔
136
        }
137

138
        return $authority;
252✔
139
    }
140

141
    /**
142
     * @return string
143
     */
144
    public function getFragment(): string
145
    {
146
        return $this->fragment;
56✔
147
    }
148

149
    /**
150
     * @return string
151
     */
152
    public function getHost(): string
153
    {
154
        return $this->host;
332✔
155
    }
156

157
    /**
158
     * @return string
159
     */
160
    public function getPath(): string
161
    {
162
        return $this->path;
264✔
163
    }
164

165
    /**
166
     * @return int|null
167
     */
168
    public function getPort(): ?int
169
    {
170
        return $this->port;
292✔
171
    }
172

173
    /**
174
     * @return string
175
     */
176
    public function getQuery(): string
177
    {
178
        return $this->query;
76✔
179
    }
180

181
    /**
182
     * @return string
183
     */
184
    public function getScheme(): string
185
    {
186
        return $this->scheme;
244✔
187
    }
188

189
    /**
190
     * @return string
191
     */
192
    public function getUserInfo(): string
193
    {
194
        return $this->userInfo;
24✔
195
    }
196

197
    /**
198
     * @param string $fragment
199
     *
200
     * @return $this|Uri|UriInterface
201
     */
202
    public function withFragment($fragment): UriInterface
203
    {
204
        $fragment = $this->_filterQueryAndFragment($fragment);
20✔
205

206
        if ($this->fragment === $fragment) {
16✔
207
            return $this;
×
208
        }
209

210
        $new = clone $this;
16✔
211
        $new->fragment = $fragment;
16✔
212

213
        return $new;
16✔
214
    }
215

216
    /**
217
     * @param string $host
218
     *
219
     * @return $this|Uri|UriInterface
220
     */
221
    public function withHost($host): UriInterface
222
    {
223
        $host = $this->_filterHost($host);
24✔
224

225
        if ($this->host === $host) {
20✔
226
            return $this;
×
227
        }
228

229
        $new = clone $this;
20✔
230
        $new->host = $host;
20✔
231
        $new->_validateState();
20✔
232

233
        return $new;
20✔
234
    }
235

236
    /**
237
     * @param string $path
238
     *
239
     * @return $this|Uri|UriInterface
240
     */
241
    public function withPath($path): UriInterface
242
    {
243
        $path = $this->_filterPath($path);
216✔
244

245
        if ($this->path === $path) {
212✔
246
            return $this;
180✔
247
        }
248

249
        $new = clone $this;
32✔
250
        $new->path = $path;
32✔
251
        $new->_validateState();
32✔
252

253
        return $new;
28✔
254
    }
255

256
    /**
257
     * @param int|null $port
258
     *
259
     * @return $this|Uri|UriInterface
260
     */
261
    public function withPort($port): UriInterface
262
    {
263
        $port = $this->_filterPort($port);
40✔
264

265
        if ($this->port === $port) {
32✔
266
            return $this;
×
267
        }
268

269
        $new = clone $this;
32✔
270
        $new->port = $port;
32✔
271
        $new->_removeDefaultPort();
32✔
272
        $new->_validateState();
32✔
273

274
        return $new;
32✔
275
    }
276

277
    /**
278
     * @param string $query
279
     *
280
     * @return $this|Uri|UriInterface
281
     */
282
    public function withQuery($query): UriInterface
283
    {
284
        $query = $this->_filterQueryAndFragment($query);
60✔
285

286
        if ($this->query === $query) {
56✔
287
            return $this;
×
288
        }
289

290
        $new = clone $this;
56✔
291
        $new->query = $query;
56✔
292

293
        return $new;
56✔
294
    }
295

296
    /**
297
     * @param string $scheme
298
     *
299
     * @return $this|Uri|UriInterface
300
     */
301
    public function withScheme($scheme): UriInterface
302
    {
303
        $scheme = $this->_filterScheme($scheme);
24✔
304

305
        if ($this->scheme === $scheme) {
20✔
306
            return $this;
×
307
        }
308

309
        $new = clone $this;
20✔
310
        $new->scheme = $scheme;
20✔
311
        $new->_removeDefaultPort();
20✔
312
        $new->_validateState();
20✔
313

314
        return $new;
20✔
315
    }
316

317
    /**
318
     * @param string      $user
319
     * @param string|null $password
320
     *
321
     * @return $this|Uri|UriInterface
322
     */
323
    public function withUserInfo($user, $password = null): UriInterface
324
    {
325
        $info = $this->_filterUserInfoComponent($user);
16✔
326
        if ($password !== null) {
16✔
327
            $info .= ':' . $this->_filterUserInfoComponent($password);
16✔
328
        }
329

330
        if ($this->userInfo === $info) {
16✔
331
            return $this;
×
332
        }
333

334
        $new = clone $this;
16✔
335
        $new->userInfo = $info;
16✔
336
        $new->_validateState();
16✔
337

338
        return $new;
16✔
339
    }
340

341
    /**
342
     * Composes a URI reference string from its various components.
343
     *
344
     * Usually this method does not need to be called manually but instead is used indirectly via
345
     * `Psr\Http\Message\UriInterface::__toString`.
346
     *
347
     * PSR-7 UriInterface treats an empty component the same as a missing component as
348
     * getQuery(), getFragment() etc. always return a string. This explains the slight
349
     * difference to RFC 3986 Section 5.3.
350
     *
351
     * Another adjustment is that the authority separator is added even when the authority is missing/empty
352
     * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
353
     * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
354
     * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
355
     * that format).
356
     *
357
     * @param string $scheme
358
     * @param string $authority
359
     * @param string $path
360
     * @param string $query
361
     * @param string $fragment
362
     *
363
     * @return string
364
     *
365
     * @see https://tools.ietf.org/html/rfc3986#section-5.3
366
     */
367
    public static function composeComponents($scheme, $authority, $path, $query, $fragment): string
368
    {
369
        // init
370
        $uri = '';
604✔
371

372
        // weak type checks to also accept null until we can add scalar type hints
373
        if ($scheme !== '') {
604✔
374
            $uri .= $scheme . ':';
232✔
375
        }
376

377
        if ($authority !== '' || $scheme === 'file') {
604✔
378
            $uri .= '//' . $authority;
228✔
379
        }
380

381
        $uri .= $path;
604✔
382

383
        if ($query !== '') {
604✔
384
            $uri .= '?' . $query;
100✔
385
        }
386

387
        if ($fragment !== '') {
604✔
388
            $uri .= '#' . $fragment;
56✔
389
        }
390

391
        return $uri;
604✔
392
    }
393

394
    /**
395
     * Creates a URI from a hash of `parse_url` components.
396
     *
397
     * @param array $parts
398
     *
399
     * @throws \InvalidArgumentException if the components do not form a valid URI
400
     *
401
     * @return UriInterface
402
     *
403
     * @see http://php.net/manual/en/function.parse-url.php
404
     */
405
    public static function fromParts(array $parts): UriInterface
406
    {
407
        $uri = new self();
×
408
        $uri->_applyParts($parts);
×
409
        $uri->_validateState();
×
410

411
        return $uri;
×
412
    }
413

414
    /**
415
     * Whether the URI is absolute, i.e. it has a scheme.
416
     *
417
     * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
418
     * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
419
     * to another URI, the base URI. Relative references can be divided into several forms:
420
     * - network-path references, e.g. '//example.com/path'
421
     * - absolute-path references, e.g. '/path'
422
     * - relative-path references, e.g. 'subpath'
423
     *
424
     * @param UriInterface $uri
425
     *
426
     * @return bool
427
     *
428
     * @see Uri::isNetworkPathReference
429
     * @see Uri::isAbsolutePathReference
430
     * @see Uri::isRelativePathReference
431
     * @see https://tools.ietf.org/html/rfc3986#section-4
432
     */
433
    public static function isAbsolute(UriInterface $uri): bool
434
    {
435
        return $uri->getScheme() !== '';
×
436
    }
437

438
    /**
439
     * Whether the URI is a absolute-path reference.
440
     *
441
     * A relative reference that begins with a single slash character is termed an absolute-path reference.
442
     *
443
     * @param UriInterface $uri
444
     *
445
     * @return bool
446
     *
447
     * @see https://tools.ietf.org/html/rfc3986#section-4.2
448
     */
449
    public static function isAbsolutePathReference(UriInterface $uri): bool
450
    {
451
        return $uri->getScheme() === ''
×
452
               &&
×
453
               $uri->getAuthority() === ''
×
454
               &&
×
455
               isset($uri->getPath()[0])
×
456
               &&
×
457
               $uri->getPath()[0] === '/';
×
458
    }
459

460
    /**
461
     * Whether the URI has the default port of the current scheme.
462
     *
463
     * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
464
     * independently of the implementation.
465
     *
466
     * @param UriInterface $uri
467
     *
468
     * @return bool
469
     */
470
    public static function isDefaultPort(UriInterface $uri): bool
471
    {
472
        return $uri->getPort() === null
56✔
473
               ||
56✔
474
               (
56✔
475
                   isset(self::$defaultPorts[$uri->getScheme()])
56✔
476
                   &&
56✔
477
                   $uri->getPort() === self::$defaultPorts[$uri->getScheme()]
56✔
478
               );
56✔
479
    }
480

481
    /**
482
     * Whether the URI is a network-path reference.
483
     *
484
     * A relative reference that begins with two slash characters is termed an network-path reference.
485
     *
486
     * @param UriInterface $uri
487
     *
488
     * @return bool
489
     *
490
     * @see https://tools.ietf.org/html/rfc3986#section-4.2
491
     */
492
    public static function isNetworkPathReference(UriInterface $uri): bool
493
    {
494
        return $uri->getScheme() === '' && $uri->getAuthority() !== '';
×
495
    }
496

497
    /**
498
     * Whether the URI is a relative-path reference.
499
     *
500
     * A relative reference that does not begin with a slash character is termed a relative-path reference.
501
     *
502
     * @param UriInterface $uri
503
     *
504
     * @return bool
505
     *
506
     * @see https://tools.ietf.org/html/rfc3986#section-4.2
507
     */
508
    public static function isRelativePathReference(UriInterface $uri): bool
509
    {
510
        return $uri->getScheme() === ''
×
511
               &&
×
512
               $uri->getAuthority() === ''
×
513
               &&
×
514
               (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
×
515
    }
516

517
    /**
518
     * Whether the URI is a same-document reference.
519
     *
520
     * A same-document reference refers to a URI that is, aside from its fragment
521
     * component, identical to the base URI. When no base URI is given, only an empty
522
     * URI reference (apart from its fragment) is considered a same-document reference.
523
     *
524
     * @param UriInterface      $uri  The URI to check
525
     * @param UriInterface|null $base An optional base URI to compare against
526
     *
527
     * @return bool
528
     *
529
     * @see https://tools.ietf.org/html/rfc3986#section-4.4
530
     */
531
    public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null): bool
532
    {
533
        if ($base !== null) {
×
534
            $uri = UriResolver::resolve($base, $uri);
×
535

536
            return ($uri->getScheme() === $base->getScheme())
×
537
                   &&
×
538
                   ($uri->getAuthority() === $base->getAuthority())
×
539
                   &&
×
540
                   ($uri->getPath() === $base->getPath())
×
541
                   &&
×
542
                   ($uri->getQuery() === $base->getQuery());
×
543
        }
544

545
        return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
×
546
    }
547

548
    /**
549
     * Creates a new URI with a specific query string value.
550
     *
551
     * Any existing query string values that exactly match the provided key are
552
     * removed and replaced with the given key value pair.
553
     *
554
     * A value of null will set the query string key without a value, e.g. "key"
555
     * instead of "key=value".
556
     *
557
     * @param UriInterface $uri   URI to use as a base
558
     * @param string       $key   key to set
559
     * @param string|null  $value Value to set
560
     *
561
     * @return UriInterface
562
     */
563
    public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface
564
    {
565
        $result = self::_getFilteredQueryString($uri, [$key]);
×
566

567
        $result[] = self::_generateQueryString($key, $value);
×
568

569
        /** @noinspection ImplodeMissUseInspection */
570
        return $uri->withQuery(\implode('&', $result));
×
571
    }
572

573
    /**
574
     * Creates a new URI with multiple specific query string values.
575
     *
576
     * It has the same behavior as withQueryValue() but for an associative array of key => value.
577
     *
578
     * @param UriInterface $uri           URI to use as a base
579
     * @param array        $keyValueArray Associative array of key and values
580
     *
581
     * @return UriInterface
582
     */
583
    public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
584
    {
585
        $result = self::_getFilteredQueryString($uri, \array_keys($keyValueArray));
×
586

587
        foreach ($keyValueArray as $key => $value) {
×
588
            $result[] = self::_generateQueryString($key, $value);
×
589
        }
590

591
        /** @noinspection ImplodeMissUseInspection */
592
        return $uri->withQuery(\implode('&', $result));
×
593
    }
594

595
    /**
596
     * Creates a new URI with a specific query string value removed.
597
     *
598
     * Any existing query string values that exactly match the provided key are
599
     * removed.
600
     *
601
     * @param uriInterface $uri URI to use as a base
602
     * @param string       $key query string key to remove
603
     *
604
     * @return UriInterface
605
     */
606
    public static function withoutQueryValue(UriInterface $uri, $key): UriInterface
607
    {
608
        $result = self::_getFilteredQueryString($uri, [$key]);
×
609

610
        /** @noinspection ImplodeMissUseInspection */
611
        return $uri->withQuery(\implode('&', $result));
×
612
    }
613

614
    /**
615
     * Apply parse_url parts to a URI.
616
     *
617
     * @param array<string,mixed> $parts array of parse_url parts to apply
618
     *
619
     * @return void
620
     */
621
    private function _applyParts(array $parts)
622
    {
623
        $this->scheme = isset($parts['scheme'])
440✔
624
            ? $this->_filterScheme($parts['scheme'])
308✔
625
            : '';
144✔
626
        $this->userInfo = isset($parts['user'])
440✔
627
            ? $this->_filterUserInfoComponent($parts['user'])
8✔
628
            : '';
432✔
629
        $this->host = isset($parts['host'])
440✔
630
            ? $this->_filterHost($parts['host'])
312✔
631
            : '';
136✔
632
        $this->port = isset($parts['port'])
440✔
633
            ? $this->_filterPort($parts['port'])
40✔
634
            : null;
408✔
635
        $this->path = isset($parts['path'])
436✔
636
            ? $this->_filterPath($parts['path'])
324✔
637
            : '';
124✔
638
        $this->query = isset($parts['query'])
436✔
639
            ? $this->_filterQueryAndFragment($parts['query'])
120✔
640
            : '';
344✔
641
        $this->fragment = isset($parts['fragment'])
436✔
642
            ? $this->_filterQueryAndFragment($parts['fragment'])
44✔
643
            : '';
392✔
644
        if (isset($parts['pass'])) {
436✔
645
            $this->userInfo .= ':' . $this->_filterUserInfoComponent($parts['pass']);
8✔
646
        }
647

648
        $this->_removeDefaultPort();
436✔
649
    }
650

651
    /**
652
     * @param string $host
653
     *
654
     * @throws \InvalidArgumentException if the host is invalid
655
     *
656
     * @return string
657
     */
658
    private function _filterHost($host): string
659
    {
660
        if (!\is_string($host)) {
332✔
661
            throw new \InvalidArgumentException('Host must be a string');
4✔
662
        }
663

664
        return \strtolower($host);
328✔
665
    }
666

667
    /**
668
     * Filters the path of a URI
669
     *
670
     * @param string $path
671
     *
672
     * @throws \InvalidArgumentException if the path is invalid
673
     *
674
     * @return string
675
     */
676
    private function _filterPath($path): string
677
    {
678
        if (!\is_string($path)) {
408✔
679
            throw new \InvalidArgumentException('Path must be a string');
4✔
680
        }
681

682
        return (string) \preg_replace_callback(
404✔
683
            '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
404✔
684
            [$this, '_rawurlencodeMatchZero'],
404✔
685
            $path
404✔
686
        );
404✔
687
    }
688

689
    /**
690
     * @param int|null $port
691
     *
692
     * @throws \InvalidArgumentException if the port is invalid
693
     *
694
     * @return int|null
695
     */
696
    private function _filterPort($port)
697
    {
698
        if ($port === null) {
68✔
699
            return null;
4✔
700
        }
701

702
        $port = (int) $port;
68✔
703
        if ($port < 1 || $port > 0xffff) {
68✔
704
            throw new \InvalidArgumentException(
12✔
705
                \sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
12✔
706
            );
12✔
707
        }
708

709
        return $port;
56✔
710
    }
711

712
    /**
713
     * Filters the query string or fragment of a URI.
714
     *
715
     * @param string $str
716
     *
717
     * @throws \InvalidArgumentException if the query or fragment is invalid
718
     *
719
     * @return string
720
     */
721
    private function _filterQueryAndFragment($str): string
722
    {
723
        if (!\is_string($str)) {
152✔
724
            throw new \InvalidArgumentException('Query and fragment must be a string');
8✔
725
        }
726

727
        return (string) \preg_replace_callback(
144✔
728
            '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
144✔
729
            [$this, '_rawurlencodeMatchZero'],
144✔
730
            $str
144✔
731
        );
144✔
732
    }
733

734
    /**
735
     * @param string $scheme
736
     *
737
     * @throws \InvalidArgumentException if the scheme is invalid
738
     *
739
     * @return string
740
     */
741
    private function _filterScheme($scheme): string
742
    {
743
        if (!\is_string($scheme)) {
324✔
744
            throw new \InvalidArgumentException('Scheme must be a string');
4✔
745
        }
746

747
        return \strtolower($scheme);
320✔
748
    }
749

750
    /**
751
     * @param string $component
752
     *
753
     * @throws \InvalidArgumentException if the user info is invalid
754
     *
755
     * @return string
756
     */
757
    private function _filterUserInfoComponent($component): string
758
    {
759
        if (!\is_string($component)) {
24✔
760
            throw new \InvalidArgumentException('User info must be a string');
×
761
        }
762

763
        return (string) \preg_replace_callback(
24✔
764
            '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/',
24✔
765
            [$this, '_rawurlencodeMatchZero'],
24✔
766
            $component
24✔
767
        );
24✔
768
    }
769

770
    /**
771
     * @param string      $key
772
     * @param string|null $value
773
     *
774
     * @return string
775
     */
776
    private static function _generateQueryString($key, $value): string
777
    {
778
        // Query string separators ("=", "&") within the key or value need to be encoded
779
        // (while preventing double-encoding) before setting the query string. All other
780
        // chars that need percent-encoding will be encoded by withQuery().
781
        $queryString = \strtr($key, self::$replaceQuery);
×
782

783
        if ($value !== null) {
×
784
            $queryString .= '=' . \strtr($value, self::$replaceQuery);
×
785
        }
786

787
        return $queryString;
×
788
    }
789

790
    /**
791
     * @param UriInterface $uri
792
     * @param string[]     $keys
793
     *
794
     * @return array
795
     */
796
    private static function _getFilteredQueryString(UriInterface $uri, array $keys): array
797
    {
798
        $current = $uri->getQuery();
×
799

800
        if ($current === '') {
×
801
            return [];
×
802
        }
803

804
        $decodedKeys = \array_map('rawurldecode', $keys);
×
805

806
        return \array_filter(
×
807
            \explode('&', $current),
×
808
            static function ($part) use ($decodedKeys) {
×
809
                return !\in_array(\rawurldecode(\explode('=', $part, 2)[0]), $decodedKeys, true);
×
810
            }
×
811
        );
×
812
    }
813

814
    /**
815
     * @param string[] $match
816
     *
817
     * @return string
818
     */
819
    private function _rawurlencodeMatchZero(array $match): string
820
    {
821
        return \rawurlencode($match[0]);
32✔
822
    }
823

824
    /**
825
     * @return void
826
     */
827
    private function _removeDefaultPort()
828
    {
829
        if ($this->port !== null && self::isDefaultPort($this)) {
448✔
830
            $this->port = null;
16✔
831
        }
832
    }
833

834
    /**
835
     * @return void
836
     */
837
    private function _validateState()
838
    {
839
        if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
72✔
840
            $this->host = self::HTTP_DEFAULT_HOST;
8✔
841
        }
842

843
        if ($this->getAuthority() === '') {
72✔
844
            if (\strpos($this->path, '//') === 0) {
32✔
845
                throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
4✔
846
            }
847
            if ($this->scheme === '' && \strpos(\explode('/', $this->path, 2)[0], ':') !== false) {
28✔
848
                throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
28✔
849
            }
850
        } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
52✔
851
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
852
            @\trigger_error(
4✔
853
                'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
4✔
854
                'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
4✔
855
                \E_USER_DEPRECATED
4✔
856
            );
4✔
857
            $this->path = '/' . $this->path;
4✔
858
        }
859
    }
860
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc