GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — 3.x (#2327)
by Rob
01:42
created

Uri::filterUserInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 1
1
<?php
2
/**
3
 * Slim Framework (https://slimframework.com)
4
 *
5
 * @link      https://github.com/slimphp/Slim
6
 * @copyright Copyright (c) 2011-2017 Josh Lockhart
7
 * @license   https://github.com/slimphp/Slim/blob/3.x/LICENSE.md (MIT License)
8
 */
9
namespace Slim\Http;
10
11
use InvalidArgumentException;
12
use \Psr\Http\Message\UriInterface;
13
use Slim\Http\Environment;
14
15
/**
16
 * Value object representing a URI.
17
 *
18
 * This interface is meant to represent URIs according to RFC 3986 and to
19
 * provide methods for most common operations. Additional functionality for
20
 * working with URIs can be provided on top of the interface or externally.
21
 * Its primary use is for HTTP requests, but may also be used in other
22
 * contexts.
23
 *
24
 * Instances of this interface are considered immutable; all methods that
25
 * might change state MUST be implemented such that they retain the internal
26
 * state of the current instance and return an instance that contains the
27
 * changed state.
28
 *
29
 * Typically the Host header will be also be present in the request message.
30
 * For server-side requests, the scheme will typically be discoverable in the
31
 * server parameters.
32
 *
33
 * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
34
 */
35
class Uri implements UriInterface
36
{
37
    /**
38
     * Uri scheme (without "://" suffix)
39
     *
40
     * @var string
41
     */
42
    protected $scheme = '';
43
44
    /**
45
     * Uri user
46
     *
47
     * @var string
48
     */
49
    protected $user = '';
50
51
    /**
52
     * Uri password
53
     *
54
     * @var string
55
     */
56
    protected $password = '';
57
58
    /**
59
     * Uri host
60
     *
61
     * @var string
62
     */
63
    protected $host = '';
64
65
    /**
66
     * Uri port number
67
     *
68
     * @var null|int
69
     */
70
    protected $port;
71
72
    /**
73
     * Uri base path
74
     *
75
     * @var string
76
     */
77
    protected $basePath = '';
78
79
    /**
80
     * Uri path
81
     *
82
     * @var string
83
     */
84
    protected $path = '';
85
86
    /**
87
     * Uri query string (without "?" prefix)
88
     *
89
     * @var string
90
     */
91
    protected $query = '';
92
93
    /**
94
     * Uri fragment string (without "#" prefix)
95
     *
96
     * @var string
97
     */
98
    protected $fragment = '';
99
100
    /**
101
     * Create new Uri.
102
     *
103
     * @param string $scheme   Uri scheme.
104
     * @param string $host     Uri host.
105
     * @param int    $port     Uri port number.
106
     * @param string $path     Uri path.
107
     * @param string $query    Uri query string.
108
     * @param string $fragment Uri fragment.
109
     * @param string $user     Uri user.
110
     * @param string $password Uri password.
111
     */
112
    public function __construct(
113
        $scheme,
114
        $host,
115
        $port = null,
116
        $path = '/',
117
        $query = '',
118
        $fragment = '',
119
        $user = '',
120
        $password = ''
121
    ) {
122
        $this->scheme = $this->filterScheme($scheme);
123
        $this->host = $host;
124
        $this->port = $this->filterPort($port);
125
        $this->path = empty($path) ? '/' : $this->filterPath($path);
126
        $this->query = $this->filterQuery($query);
127
        $this->fragment = $this->filterQuery($fragment);
128
        $this->user = $user;
129
        $this->password = $password;
130
    }
131
132
    /**
133
     * Create new Uri from string.
134
     *
135
     * @param  string $uri Complete Uri string
136
     *     (i.e., https://user:pass@host:443/path?query).
137
     *
138
     * @return self
139
     */
140
    public static function createFromString($uri)
141
    {
142
        if (!is_string($uri) && !method_exists($uri, '__toString')) {
143
            throw new InvalidArgumentException('Uri must be a string');
144
        }
145
146
        $parts = parse_url($uri);
147
        $scheme = isset($parts['scheme']) ? $parts['scheme'] : '';
148
        $user = isset($parts['user']) ? $parts['user'] : '';
149
        $pass = isset($parts['pass']) ? $parts['pass'] : '';
150
        $host = isset($parts['host']) ? $parts['host'] : '';
151
        $port = isset($parts['port']) ? $parts['port'] : null;
152
        $path = isset($parts['path']) ? $parts['path'] : '';
153
        $query = isset($parts['query']) ? $parts['query'] : '';
154
        $fragment = isset($parts['fragment']) ? $parts['fragment'] : '';
155
156
        return new static($scheme, $host, $port, $path, $query, $fragment, $user, $pass);
157
    }
158
159
    /**
160
     * Create new Uri from environment.
161
     *
162
     * @param Environment $env
163
     *
164
     * @return self
165
     */
166
    public static function createFromEnvironment(Environment $env)
167
    {
168
        // Scheme
169
        $isSecure = $env->get('HTTPS');
170
        $scheme = (empty($isSecure) || $isSecure === 'off') ? 'http' : 'https';
171
172
        // Authority: Username and password
173
        $username = $env->get('PHP_AUTH_USER', '');
174
        $password = $env->get('PHP_AUTH_PW', '');
175
176
        // Authority: Host
177
        if ($env->has('HTTP_HOST')) {
178
            $host = $env->get('HTTP_HOST');
179
        } else {
180
            $host = $env->get('SERVER_NAME');
181
        }
182
183
        // Authority: Port
184
        $port = (int)$env->get('SERVER_PORT', 80);
185
        if (preg_match('/^(\[[a-fA-F0-9:.]+\])(:\d+)?\z/', $host, $matches)) {
186
            $host = $matches[1];
187
188
            if (isset($matches[2])) {
189
                $port = (int) substr($matches[2], 1);
190
            }
191
        } else {
192
            $pos = strpos($host, ':');
193
            if ($pos !== false) {
194
                $port = (int) substr($host, $pos + 1);
195
                $host = strstr($host, ':', true);
196
            }
197
        }
198
199
        // Path
200
        $requestScriptName = parse_url($env->get('SCRIPT_NAME'), PHP_URL_PATH);
201
        $requestScriptDir = dirname($requestScriptName);
202
203
        // parse_url() requires a full URL. As we don't extract the domain name or scheme,
204
        // we use a stand-in.
205
        $requestUri = parse_url('http://example.com' . $env->get('REQUEST_URI'), PHP_URL_PATH);
206
207
        $basePath = '';
208
        $virtualPath = $requestUri;
209
        if (stripos($requestUri, $requestScriptName) === 0) {
210
            $basePath = $requestScriptName;
211
        } elseif ($requestScriptDir !== '/' && stripos($requestUri, $requestScriptDir) === 0) {
212
            $basePath = $requestScriptDir;
213
        }
214
215
        if ($basePath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basePath of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
216
            $virtualPath = ltrim(substr($requestUri, strlen($basePath)), '/');
217
        }
218
219
        // Query string
220
        $queryString = $env->get('QUERY_STRING', '');
221
        if ($queryString === '') {
222
            $queryString = parse_url('http://example.com' . $env->get('REQUEST_URI'), PHP_URL_QUERY);
223
        }
224
225
        // Fragment
226
        $fragment = '';
227
228
        // Build Uri
229
        $uri = new static($scheme, $host, $port, $virtualPath, $queryString, $fragment, $username, $password);
0 ignored issues
show
Security Bug introduced by
It seems like $virtualPath defined by $requestUri on line 208 can also be of type false; however, Slim\Http\Uri::__construct() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
230
        if ($basePath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $basePath of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
231
            $uri = $uri->withBasePath($basePath);
232
        }
233
234
        return $uri;
235
    }
236
237
    /********************************************************************************
238
     * Scheme
239
     *******************************************************************************/
240
241
    /**
242
     * Retrieve the scheme component of the URI.
243
     *
244
     * If no scheme is present, this method MUST return an empty string.
245
     *
246
     * The value returned MUST be normalized to lowercase, per RFC 3986
247
     * Section 3.1.
248
     *
249
     * The trailing ":" character is not part of the scheme and MUST NOT be
250
     * added.
251
     *
252
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
253
     * @return string The URI scheme.
254
     */
255
    public function getScheme()
256
    {
257
        return $this->scheme;
258
    }
259
260
    /**
261
     * Return an instance with the specified scheme.
262
     *
263
     * This method MUST retain the state of the current instance, and return
264
     * an instance that contains the specified scheme.
265
     *
266
     * Implementations MUST support the schemes "http" and "https" case
267
     * insensitively, and MAY accommodate other schemes if required.
268
     *
269
     * An empty scheme is equivalent to removing the scheme.
270
     *
271
     * @param string $scheme The scheme to use with the new instance.
272
     * @return self A new instance with the specified scheme.
273
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
274
     */
275
    public function withScheme($scheme)
276
    {
277
        $scheme = $this->filterScheme($scheme);
278
        $clone = clone $this;
279
        $clone->scheme = $scheme;
280
281
        return $clone;
282
    }
283
284
    /**
285
     * Filter Uri scheme.
286
     *
287
     * @param  string $scheme Raw Uri scheme.
288
     * @return string
289
     *
290
     * @throws InvalidArgumentException If the Uri scheme is not a string.
291
     * @throws InvalidArgumentException If Uri scheme is not "", "https", or "http".
292
     */
293
    protected function filterScheme($scheme)
294
    {
295
        static $valid = [
296
            '' => true,
297
            'https' => true,
298
            'http' => true,
299
        ];
300
301
        if (!is_string($scheme) && !method_exists($scheme, '__toString')) {
302
            throw new InvalidArgumentException('Uri scheme must be a string');
303
        }
304
305
        $scheme = str_replace('://', '', strtolower((string)$scheme));
306
        if (!isset($valid[$scheme])) {
307
            throw new InvalidArgumentException('Uri scheme must be one of: "", "https", "http"');
308
        }
309
310
        return $scheme;
311
    }
312
313
    /********************************************************************************
314
     * Authority
315
     *******************************************************************************/
316
317
    /**
318
     * Retrieve the authority component of the URI.
319
     *
320
     * If no authority information is present, this method MUST return an empty
321
     * string.
322
     *
323
     * The authority syntax of the URI is:
324
     *
325
     * <pre>
326
     * [user-info@]host[:port]
327
     * </pre>
328
     *
329
     * If the port component is not set or is the standard port for the current
330
     * scheme, it SHOULD NOT be included.
331
     *
332
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
333
     * @return string The URI authority, in "[user-info@]host[:port]" format.
334
     */
335
    public function getAuthority()
336
    {
337
        $userInfo = $this->getUserInfo();
338
        $host = $this->getHost();
339
        $port = $this->getPort();
340
341
        return ($userInfo ? $userInfo . '@' : '') . $host . ($port !== null ? ':' . $port : '');
342
    }
343
344
    /**
345
     * Retrieve the user information component of the URI.
346
     *
347
     * If no user information is present, this method MUST return an empty
348
     * string.
349
     *
350
     * If a user is present in the URI, this will return that value;
351
     * additionally, if the password is also present, it will be appended to the
352
     * user value, with a colon (":") separating the values.
353
     *
354
     * The trailing "@" character is not part of the user information and MUST
355
     * NOT be added.
356
     *
357
     * @return string The URI user information, in "username[:password]" format.
358
     */
359
    public function getUserInfo()
360
    {
361
        return $this->user . ($this->password ? ':' . $this->password : '');
362
    }
363
364
    /**
365
     * Return an instance with the specified user information.
366
     *
367
     * This method MUST retain the state of the current instance, and return
368
     * an instance that contains the specified user information.
369
     *
370
     * Password is optional, but the user information MUST include the
371
     * user; an empty string for the user is equivalent to removing user
372
     * information.
373
     *
374
     * @param string $user The user name to use for authority.
375
     * @param null|string $password The password associated with $user.
376
     * @return self A new instance with the specified user information.
377
     */
378
    public function withUserInfo($user, $password = null)
379
    {
380
        $clone = clone $this;
381
        $clone->user = $this->filterUserInfo($user);
382
        if ($clone->user) {
383
            $clone->password = $password ? $this->filterUserInfo($password) : '';
384
        }
385
386
        return $clone;
387
    }
388
389
    /**
390
     * Filters the user info string.
391
     *
392
     * @param string $query The raw uri query string.
393
     * @return string The percent-encoded query string.
394
     */
395
    protected function filterUserInfo($query)
396
    {
397
        return preg_replace_callback(
398
            '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=]+|%(?![A-Fa-f0-9]{2}))/u',
399
            function ($match) {
400
                return rawurlencode($match[0]);
401
            },
402
            $query
403
        );
404
    }
405
406
    /**
407
     * Retrieve the host component of the URI.
408
     *
409
     * If no host is present, this method MUST return an empty string.
410
     *
411
     * The value returned MUST be normalized to lowercase, per RFC 3986
412
     * Section 3.2.2.
413
     *
414
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
415
     * @return string The URI host.
416
     */
417
    public function getHost()
418
    {
419
        return $this->host;
420
    }
421
422
    /**
423
     * Return an instance with the specified host.
424
     *
425
     * This method MUST retain the state of the current instance, and return
426
     * an instance that contains the specified host.
427
     *
428
     * An empty host value is equivalent to removing the host.
429
     *
430
     * @param string $host The hostname to use with the new instance.
431
     * @return self A new instance with the specified host.
432
     * @throws \InvalidArgumentException for invalid hostnames.
433
     */
434
    public function withHost($host)
435
    {
436
        $clone = clone $this;
437
        $clone->host = $host;
438
439
        return $clone;
440
    }
441
442
    /**
443
     * Retrieve the port component of the URI.
444
     *
445
     * If a port is present, and it is non-standard for the current scheme,
446
     * this method MUST return it as an integer. If the port is the standard port
447
     * used with the current scheme, this method SHOULD return null.
448
     *
449
     * If no port is present, and no scheme is present, this method MUST return
450
     * a null value.
451
     *
452
     * If no port is present, but a scheme is present, this method MAY return
453
     * the standard port for that scheme, but SHOULD return null.
454
     *
455
     * @return null|int The URI port.
456
     */
457
    public function getPort()
458
    {
459
        return $this->port && !$this->hasStandardPort() ? $this->port : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->port of type null|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
460
    }
461
462
    /**
463
     * Return an instance with the specified port.
464
     *
465
     * This method MUST retain the state of the current instance, and return
466
     * an instance that contains the specified port.
467
     *
468
     * Implementations MUST raise an exception for ports outside the
469
     * established TCP and UDP port ranges.
470
     *
471
     * A null value provided for the port is equivalent to removing the port
472
     * information.
473
     *
474
     * @param null|int $port The port to use with the new instance; a null value
475
     *     removes the port information.
476
     * @return self A new instance with the specified port.
477
     * @throws \InvalidArgumentException for invalid ports.
478
     */
479
    public function withPort($port)
480
    {
481
        $port = $this->filterPort($port);
482
        $clone = clone $this;
483
        $clone->port = $port;
484
485
        return $clone;
486
    }
487
488
    /**
489
     * Does this Uri use a standard port?
490
     *
491
     * @return bool
492
     */
493
    protected function hasStandardPort()
494
    {
495
        return ($this->scheme === 'http' && $this->port === 80) || ($this->scheme === 'https' && $this->port === 443);
496
    }
497
498
    /**
499
     * Filter Uri port.
500
     *
501
     * @param  null|int $port The Uri port number.
502
     * @return null|int
503
     *
504
     * @throws InvalidArgumentException If the port is invalid.
505
     */
506
    protected function filterPort($port)
507
    {
508
        if (is_null($port) || (is_integer($port) && ($port >= 1 && $port <= 65535))) {
509
            return $port;
510
        }
511
512
        throw new InvalidArgumentException('Uri port must be null or an integer between 1 and 65535 (inclusive)');
513
    }
514
515
    /********************************************************************************
516
     * Path
517
     *******************************************************************************/
518
519
    /**
520
     * Retrieve the path component of the URI.
521
     *
522
     * The path can either be empty or absolute (starting with a slash) or
523
     * rootless (not starting with a slash). Implementations MUST support all
524
     * three syntaxes.
525
     *
526
     * Normally, the empty path "" and absolute path "/" are considered equal as
527
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
528
     * do this normalization because in contexts with a trimmed base path, e.g.
529
     * the front controller, this difference becomes significant. It's the task
530
     * of the user to handle both "" and "/".
531
     *
532
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
533
     * any characters. To determine what characters to encode, please refer to
534
     * RFC 3986, Sections 2 and 3.3.
535
     *
536
     * As an example, if the value should include a slash ("/") not intended as
537
     * delimiter between path segments, that value MUST be passed in encoded
538
     * form (e.g., "%2F") to the instance.
539
     *
540
     * @see https://tools.ietf.org/html/rfc3986#section-2
541
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
542
     * @return string The URI path.
543
     */
544
    public function getPath()
545
    {
546
        return $this->path;
547
    }
548
549
    /**
550
     * Return an instance with the specified path.
551
     *
552
     * This method MUST retain the state of the current instance, and return
553
     * an instance that contains the specified path.
554
     *
555
     * The path can either be empty or absolute (starting with a slash) or
556
     * rootless (not starting with a slash). Implementations MUST support all
557
     * three syntaxes.
558
     *
559
     * If the path is intended to be domain-relative rather than path relative then
560
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
561
     * are assumed to be relative to some base path known to the application or
562
     * consumer.
563
     *
564
     * Users can provide both encoded and decoded path characters.
565
     * Implementations ensure the correct encoding as outlined in getPath().
566
     *
567
     * @param string $path The path to use with the new instance.
568
     * @return self A new instance with the specified path.
569
     * @throws \InvalidArgumentException for invalid paths.
570
     */
571
    public function withPath($path)
572
    {
573
        if (!is_string($path)) {
574
            throw new InvalidArgumentException('Uri path must be a string');
575
        }
576
577
        $clone = clone $this;
578
        $clone->path = $this->filterPath($path);
579
580
        // if the path is absolute, then clear basePath
581
        if (substr($path, 0, 1) == '/') {
582
            $clone->basePath = '';
583
        }
584
585
        return $clone;
586
    }
587
588
    /**
589
     * Retrieve the base path segment of the URI.
590
     *
591
     * Note: This method is not part of the PSR-7 standard.
592
     *
593
     * This method MUST return a string; if no path is present it MUST return
594
     * an empty string.
595
     *
596
     * @return string The base path segment of the URI.
597
     */
598
    public function getBasePath()
599
    {
600
        return $this->basePath;
601
    }
602
603
    /**
604
     * Set base path.
605
     *
606
     * Note: This method is not part of the PSR-7 standard.
607
     *
608
     * @param  string $basePath
609
     * @return self
610
     */
611
    public function withBasePath($basePath)
612
    {
613
        if (!is_string($basePath)) {
614
            throw new InvalidArgumentException('Uri path must be a string');
615
        }
616
        if (!empty($basePath)) {
617
            $basePath = '/' . trim($basePath, '/'); // <-- Trim on both sides
618
        }
619
        $clone = clone $this;
620
621
        if ($basePath !== '/') {
622
            $clone->basePath = $this->filterPath($basePath);
623
        }
624
625
        return $clone;
626
    }
627
628
    /**
629
     * Filter Uri path.
630
     *
631
     * This method percent-encodes all reserved
632
     * characters in the provided path string. This method
633
     * will NOT double-encode characters that are already
634
     * percent-encoded.
635
     *
636
     * @param  string $path The raw uri path.
637
     * @return string       The RFC 3986 percent-encoded uri path.
638
     * @link   http://www.faqs.org/rfcs/rfc3986.html
639
     */
640
    protected function filterPath($path)
641
    {
642
        return preg_replace_callback(
643
            '/(?:[^a-zA-Z0-9_\-\.~:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/',
644
            function ($match) {
645
                return rawurlencode($match[0]);
646
            },
647
            $path
648
        );
649
    }
650
651
    /********************************************************************************
652
     * Query
653
     *******************************************************************************/
654
655
    /**
656
     * Retrieve the query string of the URI.
657
     *
658
     * If no query string is present, this method MUST return an empty string.
659
     *
660
     * The leading "?" character is not part of the query and MUST NOT be
661
     * added.
662
     *
663
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
664
     * any characters. To determine what characters to encode, please refer to
665
     * RFC 3986, Sections 2 and 3.4.
666
     *
667
     * As an example, if a value in a key/value pair of the query string should
668
     * include an ampersand ("&") not intended as a delimiter between values,
669
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
670
     *
671
     * @see https://tools.ietf.org/html/rfc3986#section-2
672
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
673
     * @return string The URI query string.
674
     */
675
    public function getQuery()
676
    {
677
        return $this->query;
678
    }
679
680
    /**
681
     * Return an instance with the specified query string.
682
     *
683
     * This method MUST retain the state of the current instance, and return
684
     * an instance that contains the specified query string.
685
     *
686
     * Users can provide both encoded and decoded query characters.
687
     * Implementations ensure the correct encoding as outlined in getQuery().
688
     *
689
     * An empty query string value is equivalent to removing the query string.
690
     *
691
     * @param string $query The query string to use with the new instance.
692
     * @return self A new instance with the specified query string.
693
     * @throws \InvalidArgumentException for invalid query strings.
694
     */
695 View Code Duplication
    public function withQuery($query)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
696
    {
697
        if (!is_string($query) && !method_exists($query, '__toString')) {
698
            throw new InvalidArgumentException('Uri query must be a string');
699
        }
700
        $query = ltrim((string)$query, '?');
701
        $clone = clone $this;
702
        $clone->query = $this->filterQuery($query);
703
704
        return $clone;
705
    }
706
707
    /**
708
     * Filters the query string or fragment of a URI.
709
     *
710
     * @param string $query The raw uri query string.
711
     * @return string The percent-encoded query string.
712
     */
713
    protected function filterQuery($query)
714
    {
715
        return preg_replace_callback(
716
            '/(?:[^a-zA-Z0-9_\-\.~!\$&\'\(\)\*\+,;=%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
717
            function ($match) {
718
                return rawurlencode($match[0]);
719
            },
720
            $query
721
        );
722
    }
723
724
    /********************************************************************************
725
     * Fragment
726
     *******************************************************************************/
727
728
    /**
729
     * Retrieve the fragment component of the URI.
730
     *
731
     * If no fragment is present, this method MUST return an empty string.
732
     *
733
     * The leading "#" character is not part of the fragment and MUST NOT be
734
     * added.
735
     *
736
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
737
     * any characters. To determine what characters to encode, please refer to
738
     * RFC 3986, Sections 2 and 3.5.
739
     *
740
     * @see https://tools.ietf.org/html/rfc3986#section-2
741
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
742
     * @return string The URI fragment.
743
     */
744
    public function getFragment()
745
    {
746
        return $this->fragment;
747
    }
748
749
    /**
750
     * Return an instance with the specified URI fragment.
751
     *
752
     * This method MUST retain the state of the current instance, and return
753
     * an instance that contains the specified URI fragment.
754
     *
755
     * Users can provide both encoded and decoded fragment characters.
756
     * Implementations ensure the correct encoding as outlined in getFragment().
757
     *
758
     * An empty fragment value is equivalent to removing the fragment.
759
     *
760
     * @param string $fragment The fragment to use with the new instance.
761
     * @return self A new instance with the specified fragment.
762
     */
763 View Code Duplication
    public function withFragment($fragment)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
764
    {
765
        if (!is_string($fragment) && !method_exists($fragment, '__toString')) {
766
            throw new InvalidArgumentException('Uri fragment must be a string');
767
        }
768
        $fragment = ltrim((string)$fragment, '#');
769
        $clone = clone $this;
770
        $clone->fragment = $this->filterQuery($fragment);
771
772
        return $clone;
773
    }
774
775
    /********************************************************************************
776
     * Helpers
777
     *******************************************************************************/
778
779
    /**
780
     * Return the string representation as a URI reference.
781
     *
782
     * Depending on which components of the URI are present, the resulting
783
     * string is either a full URI or relative reference according to RFC 3986,
784
     * Section 4.1. The method concatenates the various components of the URI,
785
     * using the appropriate delimiters:
786
     *
787
     * - If a scheme is present, it MUST be suffixed by ":".
788
     * - If an authority is present, it MUST be prefixed by "//".
789
     * - The path can be concatenated without delimiters. But there are two
790
     *   cases where the path has to be adjusted to make the URI reference
791
     *   valid as PHP does not allow to throw an exception in __toString():
792
     *     - If the path is rootless and an authority is present, the path MUST
793
     *       be prefixed by "/".
794
     *     - If the path is starting with more than one "/" and no authority is
795
     *       present, the starting slashes MUST be reduced to one.
796
     * - If a query is present, it MUST be prefixed by "?".
797
     * - If a fragment is present, it MUST be prefixed by "#".
798
     *
799
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
800
     * @return string
801
     */
802
    public function __toString()
803
    {
804
        $scheme = $this->getScheme();
805
        $authority = $this->getAuthority();
806
        $basePath = $this->getBasePath();
807
        $path = $this->getPath();
808
        $query = $this->getQuery();
809
        $fragment = $this->getFragment();
810
811
        $path = $basePath . '/' . ltrim($path, '/');
812
813
        return ($scheme ? $scheme . ':' : '')
814
            . ($authority ? '//' . $authority : '')
815
            . $path
816
            . ($query ? '?' . $query : '')
817
            . ($fragment ? '#' . $fragment : '');
818
    }
819
820
    /**
821
     * Return the fully qualified base URL.
822
     *
823
     * Note that this method never includes a trailing /
824
     *
825
     * This method is not part of PSR-7.
826
     *
827
     * @return string
828
     */
829
    public function getBaseUrl()
830
    {
831
        $scheme = $this->getScheme();
832
        $authority = $this->getAuthority();
833
        $basePath = $this->getBasePath();
834
835
        if ($authority && substr($basePath, 0, 1) !== '/') {
836
            $basePath = $basePath . '/' . $basePath;
837
        }
838
839
        return ($scheme ? $scheme . ':' : '')
840
            . ($authority ? '//' . $authority : '')
841
            . rtrim($basePath, '/');
842
    }
843
}
844