Completed
Push — master ( b48c57...3b20ab )
by Derek
02:12
created

Uri::authorityToString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 5
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\Utils\ArrayHelper;
6
use Subreality\Dilmun\Anshar\Utils\StringHelper;
7
8
class Uri implements UriInterface
9
{
10
    use SchemePortsTrait;
11
12
    protected $uri_parts = array(
13
        "scheme"    => "",
14
        "hier_part" => "",
15
        "authority" => "",
16
        "user_info" => "",
17
        "host"      => "",
18
        "port"      => null,
19
        "path"      => "",
20
        "query"     => "",
21
        "fragment"  => "",
22
    );
23
24
    protected $sub_delims = array(
25
        "!",
26
        "$",
27
        "&",
28
        "'",
29
        "(",
30
        ")",
31
        "*",
32
        "+",
33
        ",",
34
        ";",
35
        "=",
36
    );
37
38
    protected $pchar_unencoded = array(
39
        ":",
40
        "@",
41
    );
42
43
    /**
44
     * Uri constructor.  Accepts a string representing a URI and parses the string into the URI's component parts.
45
     *
46
     * @throws \InvalidArgumentException    Throws an \InvalidArgumentException when its parameter is not a string
47
     * @param string $uri
48
     */
49 81
    public function __construct($uri)
50
    {
51 81
        if (!is_string($uri)) {
52 6
            throw new \InvalidArgumentException("New Uri objects must be constructed with a string URI");
53
        }
54
55 75
        $this->explodeUri($uri);
56 75
    }
57
58
    /**
59
     * @todo Add a distinct test for this outside of constructor test
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...
60
     * Retrieve the parsed components of the URI string.
61
     *
62
     * If the class was provided an invalid URI string, URI components will be empty strings, except port, which will
63
     * be null
64
     *
65
     * @return mixed[]
66
     */
67 34
    public function getParsedUri()
68
    {
69 34
        return $this->uri_parts;
70
    }
71
72
    /**
73
     * Retrieve the scheme component of the URI.
74
     *
75
     * If no scheme is present, this method MUST return an empty string.
76
     *
77
     * The value returned MUST be normalized to lowercase, per RFC 3986
78
     * Section 3.1.
79
     *
80
     * The trailing ":" character is not part of the scheme and MUST NOT be
81
     * added.
82
     *
83
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
84
     * @return string The URI scheme.
85
     */
86 3
    public function getScheme()
87
    {
88 3
        return strtolower($this->uri_parts["scheme"]);
89
    }
90
91
    /**
92
     * Retrieve the authority component of the URI.
93
     *
94
     * If no authority information is present, this method MUST return an empty
95
     * string.
96
     *
97
     * The authority syntax of the URI is:
98
     *
99
     * <pre>
100
     * [user-info@]host[:port]
101
     * </pre>
102
     *
103
     * If the port component is not set or is the standard port for the current
104
     * scheme, it SHOULD NOT be included.
105
     *
106
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
107
     * @return string The URI authority, in "[user-info@]host[:port]" format.
108
     */
109 4
    public function getAuthority()
110
    {
111 4
        $normalized_authority = $this->uri_parts["host"];
112
113 4
        if (!empty($this->uri_parts["user_info"])) {
114 4
            $normalized_authority = $this->uri_parts["user_info"] . "@" . $normalized_authority;
115 4
        }
116
117 4
        $normalized_port = $this->normalizePort();
118
119 4
        if (!is_null($normalized_port)) {
120 2
            $normalized_authority = $normalized_authority . ":" . $normalized_port;
121 2
        }
122
123 4
        return $normalized_authority;
124
    }
125
126
    /**
127
     * Retrieve the user information component of the URI.
128
     *
129
     * If no user information is present, this method MUST return an empty
130
     * string.
131
     *
132
     * If a user is present in the URI, this will return that value;
133
     * additionally, if the password is also present, it will be appended to the
134
     * user value, with a colon (":") separating the values.
135
     *
136
     * The trailing "@" character is not part of the user information and MUST
137
     * NOT be added.
138
     *
139
     * @return string The URI user information, in "username[:password]" format.
140
     */
141 2
    public function getUserInfo()
142
    {
143 2
        return $this->uri_parts["user_info"];
144
    }
145
146
    /**
147
     * Retrieve the host component of the URI.
148
     *
149
     * If no host is present, this method MUST return an empty string.
150
     *
151
     * The value returned MUST be normalized to lowercase, per RFC 3986
152
     * Section 3.2.2.
153
     *
154
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
155
     * @return string The URI host.
156
     */
157 3
    public function getHost()
158
    {
159 3
        return strtolower($this->uri_parts["host"]);
160
    }
161
162
    /**
163
     * Retrieve the port component of the URI.
164
     *
165
     * If a port is present, and it is non-standard for the current scheme,
166
     * this method MUST return it as an integer. If the port is the standard port
167
     * used with the current scheme, this method SHOULD return null.
168
     *
169
     * If no port is present, and no scheme is present, this method MUST return
170
     * a null value.
171
     *
172
     * If no port is present, but a scheme is present, this method MAY return
173
     * the standard port for that scheme, but SHOULD return null.
174
     *
175
     * @return null|int The URI port.
176
     */
177 4
    public function getPort()
178
    {
179 4
        $normalized_port = $this->normalizePort();
180
181 4
        return $normalized_port;
182
    }
183
184
    /**
185
     * Retrieve the path component of the URI.
186
     *
187
     * The path can either be empty or absolute (starting with a slash) or
188
     * rootless (not starting with a slash). Implementations MUST support all
189
     * three syntaxes.
190
     *
191
     * Normally, the empty path "" and absolute path "/" are considered equal as
192
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
193
     * do this normalization because in contexts with a trimmed base path, e.g.
194
     * the front controller, this difference becomes significant. It's the task
195
     * of the user to handle both "" and "/".
196
     *
197
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
198
     * any characters. To determine what characters to encode, please refer to
199
     * RFC 3986, Sections 2 and 3.3.
200
     *
201
     * As an example, if the value should include a slash ("/") not intended as
202
     * delimiter between path segments, that value MUST be passed in encoded
203
     * form (e.g., "%2F") to the instance.
204
     *
205
     * @see https://tools.ietf.org/html/rfc3986#section-2
206
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
207
     * @return string The URI path.
208
     */
209 8
    public function getPath()
210
    {
211 8
        $path_unencoded = array("/");
212 8
        $allowed        = implode($this->pchar_unencoded) . implode($this->sub_delims) . implode($path_unencoded);
213
214 8
        if ($this->containsUnallowedUriCharacters($this->uri_parts["path"], $allowed)) {
215 4
            $encoded_string = $this->encodeComponent($this->uri_parts["path"], $path_unencoded);
216
217 4
            return $encoded_string;
218
        } else {
219 4
            return $this->uri_parts["path"];
220
        }
221
    }
222
223
    /**
224
     * Retrieve the query string of the URI.
225
     *
226
     * If no query string is present, this method MUST return an empty string.
227
     *
228
     * The leading "?" character is not part of the query and MUST NOT be
229
     * added.
230
     *
231
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
232
     * any characters. To determine what characters to encode, please refer to
233
     * RFC 3986, Sections 2 and 3.4.
234
     *
235
     * As an example, if a value in a key/value pair of the query string should
236
     * include an ampersand ("&") not intended as a delimiter between values,
237
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
238
     *
239
     * @see https://tools.ietf.org/html/rfc3986#section-2
240
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
241
     * @return string The URI query string.
242
     */
243 4
    public function getQuery()
244
    {
245 4
        $query_unencoded = array("/", "?");
246
247 4
        $encoded_string = $this->encodeComponent($this->uri_parts["query"], $query_unencoded);
248
249 4
        return $encoded_string;
250
    }
251
252
    /**
253
     * Retrieve the fragment component of the URI.
254
     *
255
     * If no fragment is present, this method MUST return an empty string.
256
     *
257
     * The leading "#" character is not part of the fragment and MUST NOT be
258
     * added.
259
     *
260
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
261
     * any characters. To determine what characters to encode, please refer to
262
     * RFC 3986, Sections 2 and 3.5.
263
     *
264
     * @see https://tools.ietf.org/html/rfc3986#section-2
265
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
266
     * @return string The URI fragment.
267
     */
268 4
    public function getFragment()
269
    {
270 4
        $fragment_unencoded = array("/", "?");
271
272 4
        $encoded_string = $this->encodeComponent($this->uri_parts["fragment"], $fragment_unencoded);
273
274 4
        return $encoded_string;
275
    }
276
277
    /**
278
     * Return an instance with the specified scheme.
279
     *
280
     * This method MUST retain the state of the current instance, and return
281
     * an instance that contains the specified scheme.
282
     *
283
     * Implementations MUST support the schemes "http" and "https" case
284
     * insensitively, and MAY accommodate other schemes if required.
285
     *
286
     * An empty scheme is equivalent to removing the scheme.
287
     *
288
     * @param string $scheme The scheme to use with the new instance.
289
     * @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...
290
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
291
     */
292
    public function withScheme($scheme)
293
    {
294
        // 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...
295
    }
296
297
    /**
298
     * Return an instance with the specified authority.
299
     *
300
     * This method MUST retain the state of the current instance, and return
301
     * an instance that contains the specified authority.
302
     *
303
     * Replacing the authority is equivalent to replacing or removing all authority components depending upon the
304
     * composition of the authority.
305
     *
306
     * An empty authority is equivalent to removing the authority and all authority components.
307
     *
308
     * @param string $authority The scheme to use with the new instance.
309
     * @return static A new instance with the specified authority.
310
     * @throws \InvalidArgumentException for invalid authorities.
311
     */
312 22
    public function withAuthority($authority)
313
    {
314 22
        if (!is_string($authority)) {
315 6
            throw new \InvalidArgumentException("Authority must be a string");
316 16
        } elseif (stristr($authority, "/")) {
317 1
            throw new \InvalidArgumentException("Authority must not contain a slash");
318
        }
319
320 15
        $uri_parts              = $this->uri_parts;
321 15
        $uri_parts["authority"] = $authority;
322
323 15
        $new_authority_string = $this->toString($uri_parts);
324
325 15
        $new_authority_uri = new Uri($new_authority_string);
326
327 15
        return $new_authority_uri;
328
    }
329
330
    /**
331
     * Return an instance with the specified user information.
332
     *
333
     * This method MUST retain the state of the current instance, and return
334
     * an instance that contains the specified user information.
335
     *
336
     * Password is optional, but the user information MUST include the
337
     * user; an empty string for the user is equivalent to removing user
338
     * information.
339
     *
340
     * @param string $user The user name to use for authority.
341
     * @param null|string $password The password associated with $user.
342
     * @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...
343
     */
344
    public function withUserInfo($user, $password = null)
345
    {
346
        // 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...
347
    }
348
349
    /**
350
     * Return an instance with the specified host.
351
     *
352
     * This method MUST retain the state of the current instance, and return
353
     * an instance that contains the specified host.
354
     *
355
     * An empty host value is equivalent to removing the host.
356
     *
357
     * @param string $host The hostname to use with the new instance.
358
     * @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...
359
     * @throws \InvalidArgumentException for invalid hostnames.
360
     */
361
    public function withHost($host)
362
    {
363
        // 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...
364
    }
365
366
    /**
367
     * Return an instance with the specified port.
368
     *
369
     * This method MUST retain the state of the current instance, and return
370
     * an instance that contains the specified port.
371
     *
372
     * Implementations MUST raise an exception for ports outside the
373
     * established TCP and UDP port ranges.
374
     *
375
     * A null value provided for the port is equivalent to removing the port
376
     * information.
377
     *
378
     * @param null|int $port The port to use with the new instance; a null value
379
     *     removes the port information.
380
     * @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...
381
     * @throws \InvalidArgumentException for invalid ports.
382
     */
383
    public function withPort($port)
384
    {
385
        // 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...
386
    }
387
388
    /**
389
     * Return an instance with the specified path.
390
     *
391
     * This method MUST retain the state of the current instance, and return
392
     * an instance that contains the specified path.
393
     *
394
     * The path can either be empty or absolute (starting with a slash) or
395
     * rootless (not starting with a slash). Implementations MUST support all
396
     * three syntaxes.
397
     *
398
     * If the path is intended to be domain-relative rather than path relative then
399
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
400
     * are assumed to be relative to some base path known to the application or
401
     * consumer.
402
     *
403
     * Users can provide both encoded and decoded path characters.
404
     * Implementations ensure the correct encoding as outlined in getPath().
405
     *
406
     * @param string $path The path to use with the new instance.
407
     * @return static A new instance with the specified path.
408
     * @throws \InvalidArgumentException for invalid paths.
409
     */
410 18
    public function withPath($path)
411
    {
412 18
        if (!is_string($path)) {
413 6
            throw new \InvalidArgumentException("Supplied path must be a string");
414
        }
415
416 12
        $uri_parts          = $this->uri_parts;
417 12
        $uri_parts["path"]  = $path;
418 12
        $path_string_helper = new StringHelper($path);
419
420 12
        if (!empty($uri_parts["authority"]) && !empty($path) && !$path_string_helper->startsWith("/")) {
421 1
            throw new \InvalidArgumentException("Cannot create a URI with an authority given a rootless path");
422
        }
423
424 11
        $new_path_string = $this->toString($uri_parts);
425
426 11
        $new_path_uri = new Uri($new_path_string);
427
428 11
        return $new_path_uri;
429
    }
430
431
    /**
432
     * Return an instance with the specified query string.
433
     *
434
     * This method MUST retain the state of the current instance, and return
435
     * an instance that contains the specified query string.
436
     *
437
     * Users can provide both encoded and decoded query characters.
438
     * Implementations ensure the correct encoding as outlined in getQuery().
439
     *
440
     * An empty query string value is equivalent to removing the query string.
441
     *
442
     * @param string $query The query string to use with the new instance.
443
     * @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...
444
     * @throws \InvalidArgumentException for invalid query strings.
445
     */
446
    public function withQuery($query)
447
    {
448
        // 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...
449
    }
450
451
    /**
452
     * Return an instance with the specified URI fragment.
453
     *
454
     * This method MUST retain the state of the current instance, and return
455
     * an instance that contains the specified URI fragment.
456
     *
457
     * Users can provide both encoded and decoded fragment characters.
458
     * Implementations ensure the correct encoding as outlined in getFragment().
459
     *
460
     * An empty fragment value is equivalent to removing the fragment.
461
     *
462
     * @param string $fragment The fragment to use with the new instance.
463
     * @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...
464
     */
465
    public function withFragment($fragment)
466
    {
467
        // 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...
468
    }
469
470
    /**
471
     * Return the string representation as a URI reference.
472
     *
473
     * Depending on which components of the URI are present, the resulting
474
     * string is either a full URI or relative reference according to RFC 3986,
475
     * Section 4.1. The method concatenates the various components of the URI,
476
     * using the appropriate delimiters:
477
     *
478
     * - If a scheme is present, it MUST be suffixed by ":".
479
     * - If an authority is present, it MUST be prefixed by "//".
480
     * - The path can be concatenated without delimiters. But there are two
481
     *   cases where the path has to be adjusted to make the URI reference
482
     *   valid as PHP does not allow to throw an exception in __toString():
483
     *     - If the path is rootless and an authority is present, the path MUST
484
     *       be prefixed by "/".
485
     *     - If the path is starting with more than one "/" and no authority is
486
     *       present, the starting slashes MUST be reduced to one.
487
     * - If a query is present, it MUST be prefixed by "?".
488
     * - If a fragment is present, it MUST be prefixed by "#".
489
     *
490
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
491
     * @return string
492
     */
493 8
    public function __toString()
494
    {
495 8
        return $this->toString($this->uri_parts);
496
    }
497
498
    /**
499
     * Converts a given array of URI parts to a string according to the specification of the __toString magic method
500
     *
501
     * @see Uri::__toString
502
     *
503
     * @param array $uri_parts  The URI parts to be combined into a string
504
     * @return string           The string combined from the array of URI parts
505
     */
506 31
    private function toString(array $uri_parts)
507
    {
508 31
        $uri_string = "";
509
510 31
        $uri_string .= $this->schemeToString($uri_parts["scheme"]);
511
512 31
        $uri_string .= $this->authorityToString($uri_parts["authority"]);
513
514 31
        $uri_string .= $this->pathToString($uri_parts["path"], $uri_parts["authority"]);
515
516 31
        $uri_string .= $this->queryToString($uri_parts["query"]);
517
518 31
        $uri_string .= $this->fragmentToString($uri_parts["fragment"]);
519
520 31
        return $uri_string;
521
    }
522
523
    /**
524
     * Splits a string URI into its component parts, returning true if the URI string matches a valid URI's syntax
525
     * and false if the URI string does not
526
     *
527
     * @param string $uri   The URI string to be decomposed
528
     * @return bool         Returns true if the URI string matches a valid URI's syntax
529
     *                      Returns false otherwise
530
     */
531 75
    private function explodeUri($uri)
532
    {
533 75
        $reg_start     = '/^';
534 75
        $scheme_part   = '(?:(?P<scheme>[A-Za-z0-9][^\/\?#:]+):)?';
535 75
        $hier_part     = '(?P<hier_part>[^\?#]+)?';
536 75
        $query_part    = '(?:\?(?P<query>[^#]*))?';
537 75
        $fragment_part = '(?:#(?P<fragment>.*))?';
538 75
        $reg_end       = '/';
539
540 75
        $uri_syntax = $reg_start . $scheme_part . $hier_part . $query_part . $fragment_part . $reg_end;
541
542 75
        $uri_valid = preg_match($uri_syntax, $uri, $parts);
543
544 75
        $this->uri_parts = array_merge($this->uri_parts, $parts); //overwriting default values with matches
545
546 75
        $this->explodeHierParts($this->uri_parts["hier_part"]);
547
548 75
        $this->sanitizeUriPartsArray();
549
550 75
        return (bool) $uri_valid;
551
    }
552
553
    /**
554
     * Splits URI hierarchy data into authority and path data.
555
     *
556
     * @param string $hier_part     The hierarchy part of a URI to be decomposed
557
     * @return void
558
     */
559 75
    private function explodeHierParts($hier_part)
560
    {
561 75
        $authority_parts = array();
562
563 75
        $reg_start      = '/^';
564 75
        $authority_part = '(?:(?:\/\/)(?P<authority>.[^\/]+))?';
565 75
        $path_part      = '(?P<path>.+)?';
566 75
        $reg_end        = '/';
567
568 75
        $hier_part_syntax = $reg_start . $authority_part . $path_part . $reg_end;
569
570 75
        preg_match($hier_part_syntax, $hier_part, $hier_parts);
571
572 75
        if (isset($hier_parts["authority"])) {
573 74
            $authority_parts = $this->explodeAuthority($hier_parts["authority"]);
574 74
        }
575
576 75
        $hier_parts = array_merge($hier_parts, $authority_parts);
577
578 75
        $this->uri_parts = array_merge($this->uri_parts, $hier_parts);
579 75
    }
580
581
    /**
582
     * Splits URI authority data into user info, host, and port data, returning an array with named keys.
583
     *
584
     * For the host component, it will capture everything within brackets to support ipv6 or match all characters until
585
     * it finds a colon indicating the start of the port component.
586
     *
587
     * @param string $authority     The authority part of a URI to be decomposed
588
     * @return mixed[]              An array with named keys containing the component parts of the supplied
0 ignored issues
show
Documentation introduced by
Should the return type not be array<*,string|integer|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...
589
     *                              authority
590
     */
591 74
    private function explodeAuthority($authority)
592
    {
593 74
        $reg_start      = '/^';
594 74
        $user_info_part = '(?:(?P<user_info>.+)@)?';
595 74
        $host_part      = '(?P<host>\[.+\]|.[^:]+)';
596 74
        $port_part      = '(?::(?P<port>[0-9]+))?';
597 74
        $reg_end        = '/';
598
599 74
        $authority_syntax = $reg_start . $user_info_part . $host_part . $port_part . $reg_end;
600
601 74
        preg_match($authority_syntax, $authority, $authority_parts);
602
603 74
        if (isset($authority_parts["port"])) {
604 33
            $authority_parts["port"] = (int) $authority_parts["port"];
605 33
        }
606
607 74
        return $authority_parts;
608
    }
609
610
    /**
611
     * Normalizes a port string based on whether the URI's port is standard for its scheme
612
     *
613
     * @return int|null     Returns null if the port is standard for the scheme
614
     *                      Returns the port prepended with a colon if the port is not standard for the scheme
615
     */
616 8
    private function normalizePort()
617
    {
618 8
        $scheme_port_array = new ArrayHelper($this->scheme_ports);
619
620 8
        $standard_port = $scheme_port_array->valueLookup($this->uri_parts["scheme"]);
621
622 8
        if ($this->uri_parts["port"] == $standard_port) {
623 5
            $normalized_port = null;
624 5
        } else {
625 3
            $normalized_port = $this->uri_parts["port"];
626
        }
627
628 8
        return $normalized_port;
629
    }
630
631
    /**
632
     * Sanitizes the URI component array by removing redundant key/value pairs
633
     *
634
     * @return void
635
     */
636 75
    private function sanitizeUriPartsArray()
637
    {
638 75
        $uri_part_array = new ArrayHelper($this->uri_parts);
639
640 75
        $this->uri_parts = $uri_part_array->removeNumericKeys();
641 75
    }
642
643
    /**
644
     * Percent encodes a component string except for sub-delims and unencoded pchar characters as defined by RFC 3986
645
     * in addition to any component-specific unencoded characters
646
     *
647
     * @param string $component_string          The string representing a URI component
648
     * @param string[] $component_unencoded     [OPTIONAL] Any additional unencoded characters specific to the component
649
     *
650
     * @return string                           The string with appropriate characters percent-encoded
651
     */
652 12
    private function encodeComponent($component_string, array $component_unencoded = array())
653
    {
654 12
        $uri_unencoded = array_merge($component_unencoded, $this->sub_delims, $this->pchar_unencoded);
655
656 12
        $string_helper = new StringHelper($component_string);
657
658 12
        $encoded_string = $string_helper->affectChunks("rawurlencode", ...$uri_unencoded);
659
660 12
        return $encoded_string;
661
    }
662
663
    /**
664
     * Determines whether a string contains unallowed URI characters, provided a string of allowed characters for a
665
     * given component.
666
     *
667
     * Note that a percent-encoded character (e.g. %20 for space) automatically counts as an allowed character, whereas
668
     * a percent sign not followed by two hex digits (e.g. %2X) does not count as an allowed character.
669
     *
670
     * @param string $string    The string to be checked for unallowed characters
671
     * @param string $allowed   A string containing all allowed characters for a given component
672
     *
673
     * @return bool             Returns true if the string contains unallowed characters
674
     *                          Returns false if the string contains only allowed characters (including percent-encoded
675
     *                          characters)
676
     */
677 8
    private function containsUnallowedUriCharacters($string, $allowed)
678
    {
679 8
        $allowed = preg_quote($allowed, "/");
680
681 8
        $pattern = "/^([0-9a-zA-Z\\.\\-_~{$allowed}]|%[0-9a-fA-F]{2})*\$/";
682
683 8
        $matches_allowed = preg_match($pattern, $string);
684
685 8
        return (bool) !$matches_allowed;
686
    }
687
688
    /**
689
     * Returns the appropriate scheme string based upon __toString specification rules.
690
     *
691
     * @see Uri::__toString()
692
     *
693
     * @param string $scheme    The scheme to compile into a URI-friendly string
694
     *
695
     * @return string           The URI-friendly scheme string
696
     */
697 31
    private function schemeToString($scheme)
698
    {
699 31
        $scheme_string = "";
700
701 31
        if (!empty($scheme)) {
702 28
            $scheme_string .= $scheme . ":";
703 28
        }
704
705 31
        return $scheme_string;
706
    }
707
708
    /**
709
     * Returns the appropriate authority string based upon __toString specification rules.
710
     *
711
     * @see Uri::__toString()
712
     *
713
     * @param string $authority     The authority to compile into a URI-friendly string
714
     *
715
     * @return string               The URI-friendly authority string
716
     */
717 31
    private function authorityToString($authority)
718
    {
719 31
        $authority_string = "";
720
721 31
        if (!empty($authority)) {
722 23
            $authority_string .= "//" . $authority;
723 23
        }
724
725 31
        return $authority_string;
726
    }
727
728
    /**
729
     * Returns the appropriate path string based upon __toString specification rules.
730
     *
731
     * @see Uri::__toString()
732
     *
733
     * @param string $path          The path to compile into a URI-friendly string
734
     * @param string $authority     [optional] The authority of the URI
735
     *
736
     * @return string               The URI-friendly path string
737
     */
738 31
    private function pathToString($path, $authority = "")
739
    {
740 31
        $path_string        = "";
741 31
        $path_string_helper = new StringHelper($path);
742
743 31
        if (empty($authority)) {
744 9
            $collapsed_slashes = $path_string_helper->collapseStartingRepetition("/");
745
746 9
            $path_string .= $collapsed_slashes;
747 31
        } elseif (!empty($path)) {
748 21
            if (!$path_string_helper->startsWith("/")) {
749 1
                $path_string .= "/" . $path;
750 1
            } else {
751 21
                $path_string .= $path;
752
            }
753 21
        }
754
755 31
        return $path_string;
756
    }
757
758
    /**
759
     * Returns the appropriate query string based upon __toString specification rules.
760
     *
761
     * @see Uri::__toString()
762
     *
763
     * @param string $query     The query to compile into a URI-friendly string
764
     *
765
     * @return string           The URI-friendly query string
766
     */
767 31
    private function queryToString($query)
768
    {
769 31
        $query_string = "";
770
771 31
        if (!empty($query)) {
772 26
            $query_string .= "?" . $query;
773 26
        }
774
775 31
        return $query_string;
776
    }
777
778
    /**
779
     * Returns the appropriate fragment string based upon __toString specification rules.
780
     *
781
     * @see Uri::__toString()
782
     *
783
     * @param string $fragment  The fragment to compile into a URI-friendly string
784
     *
785
     * @return string           The URI-friendly fragment string
786
     */
787 31
    private function fragmentToString($fragment)
788
    {
789 31
        $fragment_string = "";
790
791 31
        if (!empty($fragment)) {
792 27
            $fragment_string .= "#" . $fragment;
793 27
        }
794
795 31
        return $fragment_string;
796
    }
797
}
798