PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 1.18
Independent Analytics – WordPress Analytics Plugin v1.18
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 3 years ago UriTemplate 3 years ago Http.php 3 years ago HttpFactory.php 3 years ago Uri.php 3 years ago UriInfo.php 3 years ago UriResolver.php 3 years ago UriString.php 3 years ago UriTemplate.php 3 years ago
UriString.php
385 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 declare (strict_types=1);
12 namespace IAWP\League\Uri;
13
14 use IAWP\League\Uri\Exceptions\IdnaConversionFailed;
15 use IAWP\League\Uri\Exceptions\IdnSupportMissing;
16 use IAWP\League\Uri\Exceptions\SyntaxError;
17 use IAWP\League\Uri\Idna\Idna;
18 use function array_merge;
19 use function explode;
20 use function filter_var;
21 use function gettype;
22 use function inet_pton;
23 use function is_object;
24 use function is_scalar;
25 use function method_exists;
26 use function preg_match;
27 use function rawurldecode;
28 use function sprintf;
29 use function strpos;
30 use function substr;
31 use const FILTER_FLAG_IPV6;
32 use const FILTER_VALIDATE_IP;
33 /**
34 * A class to parse a URI string according to RFC3986.
35 *
36 * @link https://tools.ietf.org/html/rfc3986
37 * @package League\Uri
38 * @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
39 * @since 6.0.0
40 */
41 final class UriString
42 {
43 /**
44 * Default URI component values.
45 */
46 private const URI_COMPONENTS = ['scheme' => null, 'user' => null, 'pass' => null, 'host' => null, 'port' => null, 'path' => '', 'query' => null, 'fragment' => null];
47 /**
48 * Simple URI which do not need any parsing.
49 */
50 private const URI_SCHORTCUTS = ['' => [], '#' => ['fragment' => ''], '?' => ['query' => ''], '?#' => ['query' => '', 'fragment' => ''], '/' => ['path' => '/'], '//' => ['host' => '']];
51 /**
52 * Range of invalid characters in URI string.
53 */
54 private const REGEXP_INVALID_URI_CHARS = '/[\\x00-\\x1f\\x7f]/';
55 /**
56 * RFC3986 regular expression URI splitter.
57 *
58 * @link https://tools.ietf.org/html/rfc3986#appendix-B
59 */
60 private const REGEXP_URI_PARTS = ',^
61 (?<scheme>(?<scontent>[^:/?\\#]+):)? # URI scheme component
62 (?<authority>//(?<acontent>[^/?\\#]*))? # URI authority part
63 (?<path>[^?\\#]*) # URI path component
64 (?<query>\\?(?<qcontent>[^\\#]*))? # URI query component
65 (?<fragment>\\#(?<fcontent>.*))? # URI fragment component
66 ,x';
67 /**
68 * URI scheme regular expresssion.
69 *
70 * @link https://tools.ietf.org/html/rfc3986#section-3.1
71 */
72 private const REGEXP_URI_SCHEME = '/^([a-z][a-z\\d\\+\\.\\-]*)?$/i';
73 /**
74 * IPvFuture regular expression.
75 *
76 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
77 */
78 private const REGEXP_IP_FUTURE = '/^
79 v(?<version>[A-F0-9])+\\.
80 (?:
81 (?<unreserved>[a-z0-9_~\\-\\.])|
82 (?<sub_delims>[!$&\'()*+,;=:]) # also include the : character
83 )+
84 $/ix';
85 /**
86 * General registered name regular expression.
87 *
88 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
89 */
90 private const REGEXP_REGISTERED_NAME = '/(?(DEFINE)
91 (?<unreserved>[a-z0-9_~\\-]) # . is missing as it is used to separate labels
92 (?<sub_delims>[!$&\'()*+,;=])
93 (?<encoded>%[A-F0-9]{2})
94 (?<reg_name>(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*)
95 )
96 ^(?:(?&reg_name)\\.)*(?&reg_name)\\.?$/ix';
97 /**
98 * Invalid characters in host regular expression.
99 *
100 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
101 */
102 private const REGEXP_INVALID_HOST_CHARS = '/
103 [:\\/?#\\[\\]@ ] # gen-delims characters as well as the space character
104 /ix';
105 /**
106 * Invalid path for URI without scheme and authority regular expression.
107 *
108 * @link https://tools.ietf.org/html/rfc3986#section-3.3
109 */
110 private const REGEXP_INVALID_PATH = ',^(([^/]*):)(.*)?/,';
111 /**
112 * Host and Port splitter regular expression.
113 */
114 private const REGEXP_HOST_PORT = ',^(?<host>\\[.*\\]|[^:]*)(:(?<port>.*))?$,';
115 /**
116 * IDN Host detector regular expression.
117 */
118 private const REGEXP_IDN_PATTERN = '/[^\\x20-\\x7f]/';
119 /**
120 * Only the address block fe80::/10 can have a Zone ID attach to
121 * let's detect the link local significant 10 bits.
122 */
123 private const ZONE_ID_ADDRESS_BLOCK = "\xfe\x80";
124 /**
125 * Generate an URI string representation from its parsed representation
126 * returned by League\Uri\parse() or PHP's parse_url.
127 *
128 * If you supply your own array, you are responsible for providing
129 * valid components without their URI delimiters.
130 *
131 * @link https://tools.ietf.org/html/rfc3986#section-5.3
132 * @link https://tools.ietf.org/html/rfc3986#section-7.5
133 *
134 * @param array{
135 * scheme:?string,
136 * user:?string,
137 * pass:?string,
138 * host:?string,
139 * port:?int,
140 * path:string,
141 * query:?string,
142 * fragment:?string
143 * } $components
144 */
145 public static function build(array $components) : string
146 {
147 $result = $components['path'] ?? '';
148 if (isset($components['query'])) {
149 $result .= '?' . $components['query'];
150 }
151 if (isset($components['fragment'])) {
152 $result .= '#' . $components['fragment'];
153 }
154 $scheme = null;
155 if (isset($components['scheme'])) {
156 $scheme = $components['scheme'] . ':';
157 }
158 if (!isset($components['host'])) {
159 return $scheme . $result;
160 }
161 $scheme .= '//';
162 $authority = $components['host'];
163 if (isset($components['port'])) {
164 $authority .= ':' . $components['port'];
165 }
166 if (!isset($components['user'])) {
167 return $scheme . $authority . $result;
168 }
169 $authority = '@' . $authority;
170 if (!isset($components['pass'])) {
171 return $scheme . $components['user'] . $authority . $result;
172 }
173 return $scheme . $components['user'] . ':' . $components['pass'] . $authority . $result;
174 }
175 /**
176 * Parse an URI string into its components.
177 *
178 * This method parses a URI and returns an associative array containing any
179 * of the various components of the URI that are present.
180 *
181 * <code>
182 * $components = (new Parser())->parse('http://foo@test.example.com:42?query#');
183 * var_export($components);
184 * //will display
185 * array(
186 * 'scheme' => 'http', // the URI scheme component
187 * 'user' => 'foo', // the URI user component
188 * 'pass' => null, // the URI pass component
189 * 'host' => 'test.example.com', // the URI host component
190 * 'port' => 42, // the URI port component
191 * 'path' => '', // the URI path component
192 * 'query' => 'query', // the URI query component
193 * 'fragment' => '', // the URI fragment component
194 * );
195 * </code>
196 *
197 * The returned array is similar to PHP's parse_url return value with the following
198 * differences:
199 *
200 * <ul>
201 * <li>All components are always present in the returned array</li>
202 * <li>Empty and undefined component are treated differently. And empty component is
203 * set to the empty string while an undefined component is set to the `null` value.</li>
204 * <li>The path component is never undefined</li>
205 * <li>The method parses the URI following the RFC3986 rules but you are still
206 * required to validate the returned components against its related scheme specific rules.</li>
207 * </ul>
208 *
209 * @link https://tools.ietf.org/html/rfc3986
210 *
211 * @param mixed $uri any scalar or stringable object
212 *
213 * @throws SyntaxError if the URI contains invalid characters
214 * @throws SyntaxError if the URI contains an invalid scheme
215 * @throws SyntaxError if the URI contains an invalid path
216 *
217 * @return array{
218 * scheme:?string,
219 * user:?string,
220 * pass:?string,
221 * host:?string,
222 * port:?int,
223 * path:string,
224 * query:?string,
225 * fragment:?string
226 * }
227 */
228 public static function parse($uri) : array
229 {
230 if (is_object($uri) && method_exists($uri, '__toString')) {
231 $uri = (string) $uri;
232 }
233 if (!is_scalar($uri)) {
234 throw new \TypeError(sprintf('The uri must be a scalar or a stringable object `%s` given', gettype($uri)));
235 }
236 $uri = (string) $uri;
237 if (isset(self::URI_SCHORTCUTS[$uri])) {
238 /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */
239 $components = array_merge(self::URI_COMPONENTS, self::URI_SCHORTCUTS[$uri]);
240 return $components;
241 }
242 if (1 === preg_match(self::REGEXP_INVALID_URI_CHARS, $uri)) {
243 throw new SyntaxError(sprintf('The uri `%s` contains invalid characters', $uri));
244 }
245 //if the first character is a known URI delimiter parsing can be simplified
246 $first_char = $uri[0];
247 //The URI is made of the fragment only
248 if ('#' === $first_char) {
249 [, $fragment] = explode('#', $uri, 2);
250 $components = self::URI_COMPONENTS;
251 $components['fragment'] = $fragment;
252 return $components;
253 }
254 //The URI is made of the query and fragment
255 if ('?' === $first_char) {
256 [, $partial] = explode('?', $uri, 2);
257 [$query, $fragment] = explode('#', $partial, 2) + [1 => null];
258 $components = self::URI_COMPONENTS;
259 $components['query'] = $query;
260 $components['fragment'] = $fragment;
261 return $components;
262 }
263 //use RFC3986 URI regexp to split the URI
264 preg_match(self::REGEXP_URI_PARTS, $uri, $parts);
265 $parts += ['query' => '', 'fragment' => ''];
266 if (':' === $parts['scheme'] || 1 !== preg_match(self::REGEXP_URI_SCHEME, $parts['scontent'])) {
267 throw new SyntaxError(sprintf('The uri `%s` contains an invalid scheme', $uri));
268 }
269 if ('' === $parts['scheme'] . $parts['authority'] && 1 === preg_match(self::REGEXP_INVALID_PATH, $parts['path'])) {
270 throw new SyntaxError(sprintf('The uri `%s` contains an invalid path.', $uri));
271 }
272 /** @var array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} $components */
273 $components = array_merge(self::URI_COMPONENTS, '' === $parts['authority'] ? [] : self::parseAuthority($parts['acontent']), ['path' => $parts['path'], 'scheme' => '' === $parts['scheme'] ? null : $parts['scontent'], 'query' => '' === $parts['query'] ? null : $parts['qcontent'], 'fragment' => '' === $parts['fragment'] ? null : $parts['fcontent']]);
274 return $components;
275 }
276 /**
277 * Parses the URI authority part.
278 *
279 * @link https://tools.ietf.org/html/rfc3986#section-3.2
280 *
281 * @throws SyntaxError If the port component is invalid
282 *
283 * @return array{user:?string, pass:?string, host:?string, port:?int}
284 */
285 private static function parseAuthority(string $authority) : array
286 {
287 $components = ['user' => null, 'pass' => null, 'host' => '', 'port' => null];
288 if ('' === $authority) {
289 return $components;
290 }
291 $parts = explode('@', $authority, 2);
292 if (isset($parts[1])) {
293 [$components['user'], $components['pass']] = explode(':', $parts[0], 2) + [1 => null];
294 }
295 preg_match(self::REGEXP_HOST_PORT, $parts[1] ?? $parts[0], $matches);
296 $matches += ['port' => ''];
297 $components['port'] = self::filterPort($matches['port']);
298 $components['host'] = self::filterHost($matches['host']);
299 return $components;
300 }
301 /**
302 * Filter and format the port component.
303 *
304 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
305 *
306 * @throws SyntaxError if the registered name is invalid
307 */
308 private static function filterPort(string $port) : ?int
309 {
310 if ('' === $port) {
311 return null;
312 }
313 if (1 === preg_match('/^\\d*$/', $port)) {
314 return (int) $port;
315 }
316 throw new SyntaxError(sprintf('The port `%s` is invalid', $port));
317 }
318 /**
319 * Returns whether a hostname is valid.
320 *
321 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
322 *
323 * @throws SyntaxError if the registered name is invalid
324 */
325 private static function filterHost(string $host) : string
326 {
327 if ('' === $host) {
328 return $host;
329 }
330 if ('[' !== $host[0] || ']' !== substr($host, -1)) {
331 return self::filterRegisteredName($host);
332 }
333 if (!self::isIpHost(substr($host, 1, -1))) {
334 throw new SyntaxError(sprintf('Host `%s` is invalid : the IP host is malformed', $host));
335 }
336 return $host;
337 }
338 /**
339 * Returns whether the host is an IPv4 or a registered named.
340 *
341 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
342 *
343 * @throws SyntaxError if the registered name is invalid
344 * @throws IdnSupportMissing if IDN support or ICU requirement are not available or met.
345 */
346 private static function filterRegisteredName(string $host) : string
347 {
348 $formatted_host = rawurldecode($host);
349 if (1 === preg_match(self::REGEXP_REGISTERED_NAME, $formatted_host)) {
350 return $host;
351 }
352 //to test IDN host non-ascii characters must be present in the host
353 if (1 !== preg_match(self::REGEXP_IDN_PATTERN, $formatted_host)) {
354 throw new SyntaxError(sprintf('Host `%s` is invalid : the host is not a valid registered name', $host));
355 }
356 $info = Idna::toAscii($host, Idna::IDNA2008_ASCII);
357 if (0 !== $info->errors()) {
358 throw IdnaConversionFailed::dueToIDNAError($host, $info);
359 }
360 return $host;
361 }
362 /**
363 * Validates a IPv6/IPvfuture host.
364 *
365 * @link https://tools.ietf.org/html/rfc3986#section-3.2.2
366 * @link https://tools.ietf.org/html/rfc6874#section-2
367 * @link https://tools.ietf.org/html/rfc6874#section-4
368 */
369 private static function isIpHost(string $ip_host) : bool
370 {
371 if (\false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
372 return \true;
373 }
374 if (1 === preg_match(self::REGEXP_IP_FUTURE, $ip_host, $matches)) {
375 return !\in_array($matches['version'], ['4', '6'], \true);
376 }
377 $pos = strpos($ip_host, '%');
378 if (\false === $pos || 1 === preg_match(self::REGEXP_INVALID_HOST_CHARS, rawurldecode(substr($ip_host, $pos)))) {
379 return \false;
380 }
381 $ip_host = substr($ip_host, 0, $pos);
382 return \false !== filter_var($ip_host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && 0 === strpos((string) inet_pton($ip_host), self::ZONE_ID_ADDRESS_BLOCK);
383 }
384 }
385