Completed
Push — master ( fada80...5c89c9 )
by Derek
02:45
created

Uri::normalizePort()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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