Completed
Push — master ( 504e73...4ffb3e )
by Derek
02:22
created

Uri::withPath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 9.4285
cc 2
eloc 8
nc 2
nop 1
crap 2
1
<?php
2
namespace Subreality\Dilmun\Anshar\Http;
3
4
use Psr\Http\Message\UriInterface;
5
use Subreality\Dilmun\Anshar\Http\UriParts\Authority;
6
use Subreality\Dilmun\Anshar\Http\UriParts\Fragment;
7
use Subreality\Dilmun\Anshar\Http\UriParts\Host;
8
use Subreality\Dilmun\Anshar\Http\UriParts\Path;
9
use Subreality\Dilmun\Anshar\Http\UriParts\Port;
10
use Subreality\Dilmun\Anshar\Http\UriParts\Query;
11
use Subreality\Dilmun\Anshar\Http\UriParts\Scheme;
12
use Subreality\Dilmun\Anshar\Http\UriParts\UserInfo;
13
use Subreality\Dilmun\Anshar\Utils\ArrayHelper;
14
use Subreality\Dilmun\Anshar\Utils\StringHelper;
15
16
/**
17
 * Class Uri
18
 * @package Subreality\Dilmun\Anshar\Http
19
 */
20
class Uri implements UriInterface
21
{
22
    protected $uri_parts = array(
23
        "scheme"    => "",
24
        "hier_part" => "",
25
        "authority" => "",
26
        "user_info" => "",
27
        "host"      => "",
28
        "port"      => null,
29
        "path"      => "",
30
        "query"     => "",
31
        "fragment"  => "",
32
    );
33
34
    /** @var  Scheme */
35
    protected $scheme;
36
    /** @var  Authority */
37
    protected $authority;
38
    /** @var  UserInfo */
39
    protected $user_info;
40
    /** @var  Host */
41
    protected $host;
42
    /** @var  Port */
43
    protected $port;
44
    /** @var  Path */
45
    protected $path;
46
    /** @var  Query */
47
    protected $query;
48
    /** @var  Fragment */
49
    protected $fragment;
50
51
    protected $sub_delims = array(
52
        "!",
53
        "$",
54
        "&",
55
        "'",
56
        "(",
57
        ")",
58
        "*",
59
        "+",
60
        ",",
61
        ";",
62
        "=",
63
    );
64
65
    protected $pchar_unencoded = array(
66
        ":",
67
        "@",
68
    );
69
70
    /**
71
     * Uri constructor.  Accepts a string representing a URI and parses the string into the URI's component parts.
72
     *
73
     * @throws \InvalidArgumentException    Throws an \InvalidArgumentException when its parameter is not a string
74
     * @param string $uri
75
     */
76 78
    public function __construct($uri)
77
    {
78 78
        if (!is_string($uri)) {
79 6
            throw new \InvalidArgumentException("New Uri objects must be constructed with a string URI");
80
        }
81
82 72
        $this->explodeUri($uri);
83 72
    }
84
85
    /**
86
     * Retrieve the parsed components of the URI string.
87
     *
88
     * If the class was provided an invalid or empty URI string, URI components will be empty strings, except port,
89
     * which will be null
90
     *
91
     * @return mixed[]
92
     */
93 55
    public function getParsedUri()
94
    {
95 55
        return $this->uri_parts;
96
    }
97
98
    /**
99
     * Retrieve the scheme component of the URI.
100
     *
101
     * If no scheme is present, this method MUST return an empty string.
102
     *
103
     * The value returned MUST be normalized to lowercase, per RFC 3986
104
     * Section 3.1.
105
     *
106
     * The trailing ":" character is not part of the scheme and MUST NOT be
107
     * added.
108
     *
109
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
110
     * @return string The URI scheme.
111
     */
112 3
    public function getScheme()
113
    {
114 3
        return (string) $this->scheme;
115
    }
116
117
    /**
118
     * Retrieve the authority component of the URI.
119
     *
120
     * If no authority information is present, this method MUST return an empty
121
     * string.
122
     *
123
     * The authority syntax of the URI is:
124
     *
125
     * <pre>
126
     * [user-info@]host[:port]
127
     * </pre>
128
     *
129
     * If the port component is not set or is the standard port for the current
130
     * scheme, it SHOULD NOT be included.
131
     *
132
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
133
     * @return string The URI authority, in "[user-info@]host[:port]" format.
134
     */
135 4
    public function getAuthority()
136
    {
137 4
        return $this->user_info->toUriString() . $this->host->toUriString() . $this->port->toUriString($this->scheme);
138
    }
139
140
    /**
141
     * Retrieve the user information component of the URI.
142
     *
143
     * If no user information is present, this method MUST return an empty
144
     * string.
145
     *
146
     * If a user is present in the URI, this will return that value;
147
     * additionally, if the password is also present, it will be appended to the
148
     * user value, with a colon (":") separating the values.
149
     *
150
     * The trailing "@" character is not part of the user information and MUST
151
     * NOT be added.
152
     *
153
     * @return string The URI user information, in "username[:password]" format.
154
     */
155 2
    public function getUserInfo()
156
    {
157 2
        return (string) $this->user_info;
158
    }
159
160
    /**
161
     * Retrieve the host component of the URI.
162
     *
163
     * If no host is present, this method MUST return an empty string.
164
     *
165
     * The value returned MUST be normalized to lowercase, per RFC 3986
166
     * Section 3.2.2.
167
     *
168
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
169
     * @return string The URI host.
170
     */
171 3
    public function getHost()
172
    {
173 3
        return (string) $this->host;
174
    }
175
176
    /**
177
     * Retrieve the port component of the URI.
178
     *
179
     * If a port is present, and it is non-standard for the current scheme,
180
     * this method MUST return it as an integer. If the port is the standard port
181
     * used with the current scheme, this method SHOULD return null.
182
     *
183
     * If no port is present, and no scheme is present, this method MUST return
184
     * a null value.
185
     *
186
     * If no port is present, but a scheme is present, this method MAY return
187
     * the standard port for that scheme, but SHOULD return null.
188
     *
189
     * @return null|int The URI port.
190
     */
191 4
    public function getPort()
192
    {
193 4
        $normalized_port = $this->port->normalizePortAgainstScheme($this->scheme);
194
195 4
        return $normalized_port;
196
    }
197
198
    /**
199
     * Retrieve the path component of the URI.
200
     *
201
     * The path can either be empty or absolute (starting with a slash) or
202
     * rootless (not starting with a slash). Implementations MUST support all
203
     * three syntaxes.
204
     *
205
     * Normally, the empty path "" and absolute path "/" are considered equal as
206
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
207
     * do this normalization because in contexts with a trimmed base path, e.g.
208
     * the front controller, this difference becomes significant. It's the task
209
     * of the user to handle both "" and "/".
210
     *
211
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
212
     * any characters. To determine what characters to encode, please refer to
213
     * RFC 3986, Sections 2 and 3.3.
214
     *
215
     * As an example, if the value should include a slash ("/") not intended as
216
     * delimiter between path segments, that value MUST be passed in encoded
217
     * form (e.g., "%2F") to the instance.
218
     *
219
     * @see https://tools.ietf.org/html/rfc3986#section-2
220
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
221
     * @return string The URI path.
222
     */
223 8
    public function getPath()
224
    {
225 8
        $path_unencoded = array("/");
226 8
        $allowed        = implode($this->pchar_unencoded) . implode($this->sub_delims) . implode($path_unencoded);
227
228 8
        if ($this->containsUnallowedUriCharacters($this->uri_parts["path"], $allowed)) {
229 4
            $encoded_string = $this->encodeComponent($this->uri_parts["path"], $path_unencoded);
230
231 4
            return $encoded_string;
232
        } else {
233 4
            return $this->uri_parts["path"];
234
        }
235
    }
236
237
    /**
238
     * Retrieve the query string of the URI.
239
     *
240
     * If no query string is present, this method MUST return an empty string.
241
     *
242
     * The leading "?" character is not part of the query and MUST NOT be
243
     * added.
244
     *
245
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
246
     * any characters. To determine what characters to encode, please refer to
247
     * RFC 3986, Sections 2 and 3.4.
248
     *
249
     * As an example, if a value in a key/value pair of the query string should
250
     * include an ampersand ("&") not intended as a delimiter between values,
251
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
252
     *
253
     * @see https://tools.ietf.org/html/rfc3986#section-2
254
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
255
     * @return string The URI query string.
256
     */
257 4
    public function getQuery()
258
    {
259 4
        return (string) $this->query;
260
    }
261
262
    /**
263
     * Retrieve the fragment component of the URI.
264
     *
265
     * If no fragment is present, this method MUST return an empty string.
266
     *
267
     * The leading "#" character is not part of the fragment and MUST NOT be
268
     * added.
269
     *
270
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
271
     * any characters. To determine what characters to encode, please refer to
272
     * RFC 3986, Sections 2 and 3.5.
273
     *
274
     * @see https://tools.ietf.org/html/rfc3986#section-2
275
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
276
     * @return string The URI fragment.
277
     */
278 4
    public function getFragment()
279
    {
280 4
        return (string) $this->fragment;
281
    }
282
283
    /**
284
     * Return an instance with the specified scheme.
285
     *
286
     * This method MUST retain the state of the current instance, and return
287
     * an instance that contains the specified scheme.
288
     *
289
     * Implementations MUST support the schemes "http" and "https" case
290
     * insensitively, and MAY accommodate other schemes if required.
291
     *
292
     * An empty scheme is equivalent to removing the scheme.
293
     *
294
     * @param string $scheme The scheme to use with the new instance.
295
     * @return static A new instance with the specified scheme.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
296
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
297
     */
298
    public function withScheme($scheme)
299
    {
300
        // TODO: Implement withScheme() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
301
    }
302
303
    /**
304
     * Return an instance with the specified authority.
305
     *
306
     * This method MUST retain the state of the current instance, and return
307
     * an instance that contains the specified authority.
308
     *
309
     * Replacing the authority is equivalent to replacing or removing all authority components depending upon the
310
     * composition of the authority.
311
     *
312
     * An empty authority is equivalent to removing the authority and all authority components.
313
     *
314
     * @param string $authority The scheme to use with the new instance.
315
     * @return static A new instance with the specified authority.
316
     * @throws \InvalidArgumentException for invalid authorities.
317
     */
318 21
    public function withAuthority($authority)
319
    {
320 21
        $with_authority = new Authority($authority);
321
322 15
        $new_authority_uri = clone $this;
323
324 15
        $new_authority_uri->authority = $with_authority;
325
326 15
        $new_authority_uri->uri_parts["authority"] = $authority;
327 15
        $new_authority_uri->uri_parts["user_info"] = (string) $with_authority->getUserInfo();
328 15
        $new_authority_uri->uri_parts["host"]      = (string) $with_authority->getHost();
329 15
        $new_authority_uri->uri_parts["port"]      = $with_authority->getPort()->getPort();
330
331 15
        return $new_authority_uri;
332
    }
333
334
    /**
335
     * Return an instance with the specified user information.
336
     *
337
     * This method MUST retain the state of the current instance, and return
338
     * an instance that contains the specified user information.
339
     *
340
     * Password is optional, but the user information MUST include the
341
     * user; an empty string for the user is equivalent to removing user
342
     * information.
343
     *
344
     * @param string $user The user name to use for authority.
345
     * @param null|string $password The password associated with $user.
346
     * @return static A new instance with the specified user information.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
347
     */
348
    public function withUserInfo($user, $password = null)
349
    {
350
        // TODO: Implement withUserInfo() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
351
    }
352
353
    /**
354
     * Return an instance with the specified host.
355
     *
356
     * This method MUST retain the state of the current instance, and return
357
     * an instance that contains the specified host.
358
     *
359
     * An empty host value is equivalent to removing the host.
360
     *
361
     * @param string $host The hostname to use with the new instance.
362
     * @return static A new instance with the specified host.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
363
     * @throws \InvalidArgumentException for invalid hostnames.
364
     */
365
    public function withHost($host)
366
    {
367
        // TODO: Implement withHost() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
368
    }
369
370
    /**
371
     * Return an instance with the specified port.
372
     *
373
     * This method MUST retain the state of the current instance, and return
374
     * an instance that contains the specified port.
375
     *
376
     * Implementations MUST raise an exception for ports outside the
377
     * established TCP and UDP port ranges.
378
     *
379
     * A null value provided for the port is equivalent to removing the port
380
     * information.
381
     *
382
     * @param null|int $port The port to use with the new instance; a null value
383
     *     removes the port information.
384
     * @return static A new instance with the specified port.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
385
     * @throws \InvalidArgumentException for invalid ports.
386
     */
387
    public function withPort($port)
388
    {
389
        // TODO: Implement withPort() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
390
    }
391
392
    /**
393
     * Return an instance with the specified path.
394
     *
395
     * This method MUST retain the state of the current instance, and return
396
     * an instance that contains the specified path.
397
     *
398
     * The path can either be empty or absolute (starting with a slash) or
399
     * rootless (not starting with a slash). Implementations MUST support all
400
     * three syntaxes.
401
     *
402
     * If the path is intended to be domain-relative rather than path relative then
403
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
404
     * are assumed to be relative to some base path known to the application or
405
     * consumer.
406
     *
407
     * Users can provide both encoded and decoded path characters.
408
     * Implementations ensure the correct encoding as outlined in getPath().
409
     *
410
     * @throws \InvalidArgumentException for invalid paths and invalid path/authority combinations.
411
     *
412
     * @param string $path The path to use with the new instance.
413
     * @return static A new instance with the specified path.
414
     */
415 18
    public function withPath($path)
416
    {
417 18
        if (!Path::validatePathAgainstAuthority($path, $this->authority)) {
418 1
            throw new \InvalidArgumentException("Cannot create a URI with an authority given a rootless path");
419
        }
420
421 11
        $with_path = new Path($path);
422
423 11
        $new_path_uri = clone $this;
424
425 11
        $new_path_uri->path = $with_path;
426
427 11
        $new_path_uri->uri_parts["path"] = $path;
428
429 11
        return $new_path_uri;
430
    }
431
432
    /**
433
     * Return an instance with the specified query string.
434
     *
435
     * This method MUST retain the state of the current instance, and return
436
     * an instance that contains the specified query string.
437
     *
438
     * Users can provide both encoded and decoded query characters.
439
     * Implementations ensure the correct encoding as outlined in getQuery().
440
     *
441
     * An empty query string value is equivalent to removing the query string.
442
     *
443
     * @param string $query The query string to use with the new instance.
444
     * @return static A new instance with the specified query string.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
445
     * @throws \InvalidArgumentException for invalid query strings.
446
     */
447
    public function withQuery($query)
448
    {
449
        // TODO: Implement withQuery() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
450
    }
451
452
    /**
453
     * Return an instance with the specified URI fragment.
454
     *
455
     * This method MUST retain the state of the current instance, and return
456
     * an instance that contains the specified URI fragment.
457
     *
458
     * Users can provide both encoded and decoded fragment characters.
459
     * Implementations ensure the correct encoding as outlined in getFragment().
460
     *
461
     * An empty fragment value is equivalent to removing the fragment.
462
     *
463
     * @param string $fragment The fragment to use with the new instance.
464
     * @return static A new instance with the specified fragment.
0 ignored issues
show
Documentation introduced by
Should the return type not be Uri|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
465
     */
466
    public function withFragment($fragment)
467
    {
468
        // TODO: Implement withFragment() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
469
    }
470
471
    /**
472
     * Return the string representation as a URI reference.
473
     *
474
     * Depending on which components of the URI are present, the resulting
475
     * string is either a full URI or relative reference according to RFC 3986,
476
     * Section 4.1. The method concatenates the various components of the URI,
477
     * using the appropriate delimiters:
478
     *
479
     * - If a scheme is present, it MUST be suffixed by ":".
480
     * - If an authority is present, it MUST be prefixed by "//".
481
     * - The path can be concatenated without delimiters. But there are two
482
     *   cases where the path has to be adjusted to make the URI reference
483
     *   valid as PHP does not allow to throw an exception in __toString():
484
     *     - If the path is rootless and an authority is present, the path MUST
485
     *       be prefixed by "/".
486
     *     - If the path is starting with more than one "/" and no authority is
487
     *       present, the starting slashes MUST be reduced to one.
488
     * - If a query is present, it MUST be prefixed by "?".
489
     * - If a fragment is present, it MUST be prefixed by "#".
490
     *
491
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
492
     * @return string
493
     */
494 8
    public function __toString()
495
    {
496 8
        return $this->toString($this->uri_parts);
497
    }
498
499
    /**
500
     * @todo Maybe make static?
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
501
     * Converts a given array of URI parts to a string according to the specification of the __toString magic method
502
     *
503
     * @see Uri::__toString
504
     *
505
     * @param array $uri_parts  The URI parts to be combined into a string
506
     * @return string           The string combined from the array of URI parts
507
     */
508 8
    private function toString(array $uri_parts)
509
    {
510 8
        $uri_string = "";
511
512 8
        $uri_string .= $this->scheme->toUriString();
513
514 8
        $uri_string .= $this->authority->toUriString($this->scheme);
515
516 8
        $uri_string .= $this->pathToString($uri_parts["path"], $uri_parts["authority"]);
517
518 8
        $uri_string .= $this->query->toUriString();
519
520 8
        $uri_string .= $this->fragment->toUriString();
521
522 8
        return $uri_string;
523
    }
524
525
    /**
526
     * Splits a string URI into its component parts, returning true if the URI string matches a valid URI's syntax
527
     * and false if the URI string does not
528
     *
529
     * @param string $uri   The URI string to be decomposed
530
     * @return bool         Returns true if the URI string matches a valid URI's syntax
531
     *                      Returns false otherwise
532
     */
533 72
    private function explodeUri($uri)
534
    {
535 72
        $reg_start     = '/^';
536 72
        $scheme_part   = '(?:(?\'scheme\'[A-Za-z0-9][^\/\?#:]+):)?';
537 72
        $hier_part     = '(?\'hier_part\'[^\?#]+)?';
538 72
        $query_part    = '(?:\?(?\'query\'[^#]*))?';
539 72
        $fragment_part = '(?:#(?\'fragment\'.*))?';
540 72
        $reg_end       = '/';
541
542 72
        $uri_syntax = $reg_start . $scheme_part . $hier_part . $query_part . $fragment_part . $reg_end;
543
544 72
        $uri_valid = preg_match($uri_syntax, $uri, $parts);
545
546 72
        $this->uri_parts = array_merge($this->uri_parts, $parts); //overwriting default values with matches
547
548 72
        $this->explodeHierParts($this->uri_parts["hier_part"]);
549
550 72
        $this->scheme   = new Scheme($this->uri_parts["scheme"]);
551 72
        $this->query    = new Query($this->uri_parts["query"]);
552 72
        $this->fragment = new Fragment($this->uri_parts["fragment"]);
553
554 72
        $this->sanitizeUriPartsArray();
555
556 72
        return (bool) $uri_valid;
557
    }
558
559
    /**
560
     * Splits URI hierarchy data into authority and path data.
561
     *
562
     * @param string $hier_part     The hierarchy part of a URI to be decomposed
563
     * @return void
564
     */
565 72
    private function explodeHierParts($hier_part)
566
    {
567
        $hier_parts = array(
568 72
            "authority" => "",
569 72
            "path"      => "",
570 72
        );
571
572 72
        $reg_start      = '/^';
573 72
        $authority_part = '(?:(?:\/\/)(?\'authority\'.[^\/]+))?';
574 72
        $path_part      = '(?\'path\'.+)?';
575 72
        $reg_end        = '/';
576
577 72
        $hier_part_syntax = $reg_start . $authority_part . $path_part . $reg_end;
578
579 72
        preg_match($hier_part_syntax, $hier_part, $matches);
580
581 72
        $hier_parts = array_merge($hier_parts, $matches);
582
583 72
        $this->authority = new Authority($hier_parts["authority"]);
584 72
        $this->user_info = $this->authority->getUserInfo();
585 72
        $this->host      = $this->authority->getHost();
586 72
        $this->port      = $this->authority->getPort();
587 72
        $this->path      = new Path($hier_parts["path"]);
588
589 72
        $this->uri_parts["user_info"] = (string) $this->user_info;
590 72
        $this->uri_parts["host"]      = (string) $this->host;
591 72
        $this->uri_parts["port"]      = $this->port->getPort();
592
593 72
        $this->uri_parts = array_merge($this->uri_parts, $hier_parts);
594 72
    }
595
596
    /**
597
     * Sanitizes the URI component array by removing redundant key/value pairs
598
     *
599
     * @return void
600
     */
601 72
    private function sanitizeUriPartsArray()
602
    {
603 72
        $uri_part_array = new ArrayHelper($this->uri_parts);
604
605 72
        $this->uri_parts = $uri_part_array->removeNumericKeys();
606 72
    }
607
608
    /**
609
     * Percent encodes a component string except for sub-delims and unencoded pchar characters as defined by RFC 3986
610
     * in addition to any component-specific unencoded characters
611
     *
612
     * @param string $component_string          The string representing a URI component
613
     * @param string[] $component_unencoded     [OPTIONAL] Any additional unencoded characters specific to the component
614
     *
615
     * @return string                           The string with appropriate characters percent-encoded
616
     */
617 4
    private function encodeComponent($component_string, array $component_unencoded = array())
618
    {
619 4
        $uri_unencoded = array_merge($component_unencoded, $this->sub_delims, $this->pchar_unencoded);
620
621 4
        $string_helper = new StringHelper($component_string);
622
623 4
        $encoded_string = $string_helper->affectChunks("rawurlencode", ...$uri_unencoded);
624
625 4
        return $encoded_string;
626
    }
627
628
    /**
629
     * Determines whether a string contains unallowed URI characters, provided a string of allowed characters for a
630
     * given component.
631
     *
632
     * Note that a percent-encoded character (e.g. %20 for space) automatically counts as an allowed character, whereas
633
     * a percent sign not followed by two hex digits (e.g. %2X) does not count as an allowed character.
634
     *
635
     * @param string $string    The string to be checked for unallowed characters
636
     * @param string $allowed   A string containing all allowed characters for a given component
637
     *
638
     * @return bool             Returns true if the string contains unallowed characters
639
     *                          Returns false if the string contains only allowed characters (including percent-encoded
640
     *                          characters)
641
     */
642 8
    private function containsUnallowedUriCharacters($string, $allowed)
643
    {
644 8
        $allowed = preg_quote($allowed, "/");
645
646 8
        $pattern = "/^([0-9a-zA-Z\\.\\-_~{$allowed}]|%[0-9a-fA-F]{2})*\$/";
647
648 8
        $matches_allowed = preg_match($pattern, $string);
649
650 8
        return (bool) !$matches_allowed;
651
    }
652
653
    /**
654
     * Returns the appropriate path string based upon __toString specification rules.
655
     *
656
     * @see Uri::__toString()
657
     *
658
     * @param string $path          The path to compile into a URI-friendly string
659
     * @param string $authority     [optional] The authority of the URI
660
     *
661
     * @return string               The URI-friendly path string
662
     */
663 8
    private function pathToString($path, $authority = "")
664
    {
665 8
        $path_string        = "";
666 8
        $path_string_helper = new StringHelper($path);
667
668 8
        if (empty($authority)) {
669 2
            $collapsed_slashes = $path_string_helper->collapseStartingRepetition("/");
670
671 2
            $path_string .= $collapsed_slashes;
672 8
        } elseif (!empty($path)) {
673 5
            if (!$path_string_helper->startsWith("/")) {
674 1
                $path_string .= "/" . $path;
675 1
            } else {
676 4
                $path_string .= $path;
677
            }
678 5
        }
679
680 8
        return $path_string;
681
    }
682
}
683