PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 1.3
Independent Analytics – WordPress Analytics Plugin v1.3
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / vendor / league / uri / src / UriString.php
independent-analytics / vendor / league / uri / src Last commit date
Exceptions 4 years ago UriTemplate 4 years ago Http.php 4 years ago HttpFactory.php 4 years ago Uri.php 4 years ago UriInfo.php 4 years ago UriResolver.php 4 years ago UriString.php 4 years ago UriTemplate.php 4 years ago
UriString.php
467 lines
1 <?php
2
3 /**
4 * League.Uri (https://uri.thephpleague.com)
5 *
6 * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 declare(strict_types=1);
13
14 namespace League\Uri;
15
16 use League\Uri\Exceptions\IdnaConversionFailed;
17 use League\Uri\Exceptions\IdnSupportMissing;
18 use League\Uri\Exceptions\SyntaxError;
19 use League\Uri\Idna\Idna;
20 use function array_merge;
21 use function explode;
22 use function filter_var;
23 use function gettype;
24 use function inet_pton;
25 use function is_object;
26 use function is_scalar;
27 use function method_exists;
28 use function preg_match;
29 use function rawurldecode;
30 use function sprintf;
31 use function strpos;
32 use function substr;
33 use const FILTER_FLAG_IPV6;
34 use const FILTER_VALIDATE_IP;
35
36 /**
37 * A class to parse a URI string according to RFC3986.
38 *
39 * @link https://tools.ietf.org/html/rfc3986
40 * @package League\Uri
41 * @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
42 * @since 6.0.0
43 */
44 final class UriString
45 {
46 /**
47 * Default URI component values.
48 */
49 private const URI_COMPONENTS = [
50 'scheme' => null, 'user' => null, 'pass' => null, 'host' => null,
51 'port' => null, 'path' => '', 'query' => null, 'fragment' => null,
52 ];
53
54 /**
55 * Simple URI which do not need any parsing.
56 */
57 private const URI_SCHORTCUTS = [
58 '' => [],
59 '#' => ['fragment' => ''],
60 '?' => ['query' => ''],
61 '?#' => ['query' => '', 'fragment' => ''],
62 '/' => ['path' => '/'],
63 '//' => ['host' => ''],
64 ];
65
66 /**
67 * Range of invalid characters in URI string.
68 */
69 private const REGEXP_INVALID_URI_CHARS = '/[\x00-\x1f\x7f]/';
70
71 /**
72 * RFC3986 regular expression URI splitter.
73 *
74 * @link https://tools.ietf.org/html/rfc3986#appendix-B
75 */
76 private const REGEXP_URI_PARTS = ',^
77 (?<scheme>(?<scontent>[^:/?\#]+):)? # URI scheme component
78 (?<authority>//(?<acontent>[^/?\#]*))? # URI authority part
79 (?<path>[^?\#]*) # URI path component
80 (?<query>\?(?<qcontent>[^\#]*))? # URI query component
81 (?<fragment>\#(?<fcontent>.*))? # URI fragment component
82 ,x';
83
84 /**
85 * URI scheme regular expresssion.
86 *
87 * @link https://tools.ietf.org/html/rfc3986#section-3.1
88 */
89 private const REGEXP_URI_SCHEME = '/^([a-z][a-z\d\+\.\-]*)?$/i';
90
91 /**
92 * IPvFuture regular expression.
93 *
94 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
95 */
96 private const REGEXP_IP_FUTURE = '/^
97 v(?<version>[A-F0-9])+\.
98 (?:
99 (?<unreserved>[a-z0-9_~\-\.])|
100 (?<sub_delims>[!$&\'()*+,;=:]) # also include the : character
101 )+
102 $/ix';
103
104 /**
105 * General registered name regular expression.
106 *
107 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
108 */
109 private const REGEXP_REGISTERED_NAME = '/(?(DEFINE)
110 (?<unreserved>[a-z0-9_~\-]) # . is missing as it is used to separate labels
111 (?<sub_delims>[!$&\'()*+,;=])
112 (?<encoded>%[A-F0-9]{2})
113 (?<reg_name>(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*)
114 )
115 ^(?:(?&reg_name)\.)*(?&reg_name)\.?$/ix';
116
117 /**
118 * Invalid characters in host regular expression.
119 *
120 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
121 */
122 private const REGEXP_INVALID_HOST_CHARS = '/
123 [:\/?#\[\]@ ] # gen-delims characters as well as the space character
124 /ix';
125
126 /**
127 * Invalid path for URI without scheme and authority regular expression.
128 *
129 * @link https://tools.ietf.org/html/rfc3986#section-3.3
130 */
131 private const REGEXP_INVALID_PATH = ',^(([^/]*):)(.*)?/,';
132
133 /**
134 * Host and Port splitter regular expression.
135 */
136 private const REGEXP_HOST_PORT = ',^(?<host>\[.*\]|[^:]*)(:(?<port>.*))?$,';
137
138 /**
139 * IDN Host detector regular expression.
140 */
141 private const REGEXP_IDN_PATTERN = '/[^\x20-\x7f]/';
142
143 /**
144 * Only the address block fe80::/10 can have a Zone ID attach to
145 * let's detect the link local significant 10 bits.
146 */
147 private const ZONE_ID_ADDRESS_BLOCK = "\xfe\x80";
148
149 /**
150 * Generate an URI string representation from its parsed representation
151 * returned by League\Uri\parse() or PHP's parse_url.
152 *
153 * If you supply your own array, you are responsible for providing
154 * valid components without their URI delimiters.
155 *
156 * @link https://tools.ietf.org/html/rfc3986#section-5.3
157 * @link https://tools.ietf.org/html/rfc3986#section-7.5
158 *
159 * @param array{
160 * scheme:?string,
161 * user:?string,
162 * pass:?string,
163 * host:?string,
164 * port:?int,
165 * path:string,
166 * query:?string,
167 * fragment:?string
168 * } $components
169 */
170 public static function build(array $components): string
171 {
172 $result = $components['path'] ?? '';
173 if (isset($components['query'])) {
174 $result .= '?'.$components['query'];
175 }
176
177 if (isset($components['fragment'])) {
178 $result .= '#'.$components['fragment'];
179 }
180
181 $scheme = null;
182 if (isset($components['scheme'])) {
183 $scheme = $components['scheme'].':';
184 }
185
186 if (!isset($components['host'])) {
187 return $scheme.$result;
188 }
189
190 $scheme .= '//';
191 $authority = $components['host'];
192 if (isset($components['port'])) {
193 $authority .= ':'.$components['port'];
194 }
195
196 if (!isset($components['user'])) {
197 return $scheme.$authority.$result;
198 }
199
200 $authority = '@'.$authority;
201 if (!isset($components['pass'])) {
202 return $scheme.$components['user'].$authority.$result;
203 }
204
205 return $scheme.$components['user'].':'.$components['pass'].$authority.$result;
206 }
207
208 /**
209 * Parse an URI string into its components.
210 *
211 * This method parses a URI and returns an associative array containing any
212 * of the various components of the URI that are present.
213 *
214 * <code>
215 * $components = (new Parser())->parse('http://foo@test.example.com:42?query#');
216 * var_export($components);
217 * //will display
218 * array(
219 * 'scheme' => 'http', // the URI scheme component
220 * 'user' => 'foo', // the URI user component
221 * 'pass' => null, // the URI pass component
222 * 'host' => 'test.example.com', // the URI host component
223 * 'port' => 42, // the URI port component
224 * 'path' => '', // the URI path component
225 * 'query' => 'query', // the URI query component
226 * 'fragment' => '', // the URI fragment component
227 * );
228 * </code>
229 *
230 * The returned array is similar to PHP's parse_url return value with the following
231 * differences:
232 *
233 * <ul>
234 * <li>All components are always present in the returned array</li>
235 * <li>Empty and undefined component are treated differently. And empty component is
236 * set to the empty string while an undefined component is set to the `null` value.</li>
237 * <li>The path component is never undefined</li>
238 * <li>The method parses the URI following the RFC3986 rules but you are still
239 * required to validate the returned components against its related scheme specific rules.</li>
240 * </ul>
241 *
242 * @link https://tools.ietf.org/html/rfc3986
243 *
244 * @param mixed $uri any scalar or stringable object
245 *
246 * @throws SyntaxError if the URI contains invalid characters
247 * @throws SyntaxError if the URI contains an invalid scheme
248 * @throws SyntaxError if the URI contains an invalid path
249 *
250 * @return array{
251 * scheme:?string,
252 * user:?string,
253 * pass:?string,
254 * host:?string,
255 * port:?int,
256 * path:string,
257 * query:?string,
258 * fragment:?string
259 * }
260 */
261 public static function parse($uri): array
262 {
263 if (is_object($uri) && method_exists($uri, '__toString')) {
264 $uri = (string) $uri;
265 }
266
267 if (!is_scalar($uri)) {
268 throw new \TypeError(sprintf('The uri must be a scalar or a stringable object `%s` given', gettype($uri)));
269 }
270
271 $uri = (string) $uri;
272
273 if (isset(self::URI_SCHORTCUTS[$uri])) {
274 /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */
275 $components = array_merge(self::URI_COMPONENTS, self::URI_SCHORTCUTS[$uri]);
276
277 return $components;
278 }
279
280 if (1 === preg_match(self::REGEXP_INVALID_URI_CHARS, $uri)) {
281 throw new SyntaxError(sprintf('The uri `%s` contains invalid characters', $uri));
282 }
283
284 //if the first character is a known URI delimiter parsing can be simplified
285 $first_char = $uri[0];
286
287 //The URI is made of the fragment only
288 if ('#' === $first_char) {
289 [, $fragment] = explode('#', $uri, 2);
290 $components = self::URI_COMPONENTS;
291 $components['fragment'] = $fragment;
292
293 return $components;
294 }
295
296 //The URI is made of the query and fragment
297 if ('?' === $first_char) {
298 [, $partial] = explode('?', $uri, 2);
299 [$query, $fragment] = explode('#', $partial, 2) + [1 => null];
300 $components = self::URI_COMPONENTS;
301 $components['query'] = $query;
302 $components['fragment'] = $fragment;
303
304 return $components;
305 }
306
307 //use RFC3986 URI regexp to split the URI
308 preg_match(self::REGEXP_URI_PARTS, $uri, $parts);
309 $parts += ['query' => '', 'fragment' => ''];
310
311 if (':' === $parts['scheme'] || 1 !== preg_match(self::REGEXP_URI_SCHEME, $parts['scontent'])) {
312 throw new SyntaxError(sprintf('The uri `%s` contains an invalid scheme', $uri));
313 }
314
315 if ('' === $parts['scheme'].$parts['authority'] && 1 === preg_match(self::REGEXP_INVALID_PATH, $parts['path'])) {
316 throw new SyntaxError(sprintf('The uri `%s` contains an invalid path.', $uri));
317 }
318
319 /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */
320 $components = array_merge(
321 self::URI_COMPONENTS,
322 '' === $parts['authority'] ? [] : self::parseAuthority($parts['acontent']),
323 [
324 'path' => $parts['path'],
325 'scheme' => '' === $parts['scheme'] ? null : $parts['scontent'],
326 'query' => '' === $parts['query'] ? null : $parts['qcontent'],
327 'fragment' => '' === $parts['fragment'] ? null : $parts['fcontent'],
328 ]
329 );
330
331 return $components;
332 }
333
334 /**
335 * Parses the URI authority part.
336 *
337 * @link https://tools.ietf.org/html/rfc3986#section-3.2
338 *
339 * @throws SyntaxError If the port component is invalid
340 *
341 * @return array{user:?string, pass:?string, host:?string, port:?int}
342 */
343 private static function parseAuthority(string $authority): array
344 {
345 $components = ['user' => null, 'pass' => null, 'host' => '', 'port' => null];
346 if ('' === $authority) {
347 return $components;
348 }
349
350 $parts = explode('@', $authority, 2);
351 if (isset($parts[1])) {
352 [$components['user'], $components['pass']] = explode(':', $parts[0], 2) + [1 => null];
353 }
354
355 preg_match(self::REGEXP_HOST_PORT, $parts[1] ?? $parts[0], $matches);
356 $matches += ['port' => ''];
357
358 $components['port'] = self::filterPort($matches['port']);
359 $components['host'] = self::filterHost($matches['host']);
360
361 return $components;
362 }
363
364 /**
365 * Filter and format the port component.
366 *
367 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
368 *
369 * @throws SyntaxError if the registered name is invalid
370 */
371 private static function filterPort(string $port): ?int
372 {
373 if ('' === $port) {
374 return null;
375 }
376
377 if (1 === preg_match('/^\d*$/', $port)) {
378 return (int) $port;
379 }
380
381 throw new SyntaxError(sprintf('The port `%s` is invalid', $port));
382 }
383
384 /**
385 * Returns whether a hostname is valid.
386 *
387 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
388 *
389 * @throws SyntaxError if the registered name is invalid
390 */
391 private static function filterHost(string $host): string
392 {
393 if ('' === $host) {
394 return $host;
395 }
396
397 if ('[' !== $host[0] || ']' !== substr($host, -1)) {
398 return self::filterRegisteredName($host);
399 }
400
401 if (!self::isIpHost(substr($host, 1, -1))) {
402 throw new SyntaxError(sprintf('Host `%s` is invalid : the IP host is malformed', $host));
403 }
404
405 return $host;
406 }
407
408 /**
409 * Returns whether the host is an IPv4 or a registered named.
410 *
411 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
412 *
413 * @throws SyntaxError if the registered name is invalid
414 * @throws IdnSupportMissing if IDN support or ICU requirement are not available or met.
415 */
416 private static function filterRegisteredName(string $host): string
417 {
418 $formatted_host = rawurldecode($host);
419 if (1 === preg_match(self::REGEXP_REGISTERED_NAME, $formatted_host)) {
420 return $host;
421 }
422
423 //to test IDN host non-ascii characters must be present in the host
424 if (1 !== preg_match(self::REGEXP_IDN_PATTERN, $formatted_host)) {
425 throw new SyntaxError(sprintf('Host `%s` is invalid : the host is not a valid registered name', $host));
426 }
427
428 $info = Idna::toAscii($host, Idna::IDNA2008_ASCII);
429 if (0 !== $info->errors()) {
430 throw IdnaConversionFailed::dueToIDNAError($host, $info);
431 }
432
433 return $host;
434 }
435
436 /**
437 * Validates a IPv6/IPvfuture host.
438 *
439 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
440 * @link https://tools.ietf.org/html/rfc6874#section-2
441 * @link https://tools.ietf.org/html/rfc6874#section-4
442 */
443 private static function isIpHost(string $ip_host): bool
444 {
445 if (false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
446 return true;
447 }
448
449 if (1 === preg_match(self::REGEXP_IP_FUTURE, $ip_host, $matches)) {
450 return !in_array($matches['version'], ['4', '6'], true);
451 }
452
453 $pos = strpos($ip_host, '%');
454 if (false === $pos || 1 === preg_match(
455 self::REGEXP_INVALID_HOST_CHARS,
456 rawurldecode(substr($ip_host, $pos))
457 )) {
458 return false;
459 }
460
461 $ip_host = substr($ip_host, 0, $pos);
462
463 return false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)
464 && 0 === strpos((string) inet_pton($ip_host), self::ZONE_ID_ADDRESS_BLOCK);
465 }
466 }
467