PluginProbe
Media Cloud Sync / 1.2.13
Media Cloud Sync v1.2.13
1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 All 35 releases
media-cloud-sync / includes / sdk / s3 / GuzzleHttp / Psr7 / Uri.php

Uri.php in Media Cloud Sync 1.2.13, at includes/sdk/s3/GuzzleHttp/Psr7/Uri.php

573 lines 20.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7;
5
6 use Dudlewebs\WPMCS\s3\GuzzleHttp\Psr7\Exception\MalformedUriException;
7 use Dudlewebs\WPMCS\s3\Psr\Http\Message\UriInterface;
8 /**
9 * PSR-7 URI implementation.
10 *
11 * @author Michael Dowling
12 * @author Tobias Schultze
13 * @author Matthew Weier O'Phinney
14 */
15 class Uri implements UriInterface, \JsonSerializable
16 {
17 /**
18 * Absolute http and https URIs require a host per RFC 7230 Section 2.7
19 * but in generic URIs the host can be empty. So for http(s) URIs
20 * we apply this default host when no host is given yet to form a
21 * valid URI.
22 */
23 private const HTTP_DEFAULT_HOST = 'localhost';
24 private const DEFAULT_PORTS = ['http' => 80, 'https' => 443, 'ftp' => 21, 'gopher' => 70, 'nntp' => 119, 'news' => 119, 'telnet' => 23, 'tn3270' => 23, 'imap' => 143, 'pop' => 110, 'ldap' => 389];
25 /**
26 * Unreserved characters for use in a regex.
27 *
28 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
29 */
30 private const CHAR_UNRESERVED = 'a-zA-Z0-9_\\-\\.~';
31 /**
32 * Sub-delims for use in a regex.
33 *
34 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
35 */
36 private const CHAR_SUB_DELIMS = '!\\$&\'\\(\\)\\*\\+,;=';
37 private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26'];
38 /** @var string Uri scheme. */
39 private $scheme = '';
40 /** @var string Uri user info. */
41 private $userInfo = '';
42 /** @var string Uri host. */
43 private $host = '';
44 /** @var int|null Uri port. */
45 private $port;
46 /** @var string Uri path. */
47 private $path = '';
48 /** @var string Uri query string. */
49 private $query = '';
50 /** @var string Uri fragment. */
51 private $fragment = '';
52 /** @var string|null String representation */
53 private $composedComponents;
54 public function __construct(string $uri = '')
55 {
56 if ($uri !== '') {
57 $parts = self::parse($uri);
58 if ($parts === \false) {
59 throw new MalformedUriException("Unable to parse URI: {$uri}");
60 }
61 $this->applyParts($parts);
62 }
63 }
64 /**
65 * UTF-8 aware \parse_url() replacement.
66 *
67 * The internal function produces broken output for non ASCII domain names
68 * (IDN) when used with locales other than "C".
69 *
70 * On the other hand, cURL understands IDN correctly only when UTF-8 locale
71 * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
72 *
73 * @see https://bugs.php.net/bug.php?id=52923
74 * @see https://www.php.net/manual/en/function.parse-url.php#114817
75 * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
76 *
77 * @return array|false
78 */
79 private static function parse(string $url)
80 {
81 // If IPv6
82 $prefix = '';
83 if (\preg_match('%^(.*://\\[[0-9:a-f]+\\])(.*?)$%', $url, $matches)) {
84 /** @var array{0:string, 1:string, 2:string} $matches */
85 $prefix = $matches[1];
86 $url = $matches[2];
87 }
88 /** @var string */
89 $encodedUrl = \preg_replace_callback('%[^:/@?&=#]+%usD', static function ($matches) {
90 return \urlencode($matches[0]);
91 }, $url);
92 $result = \parse_url($prefix . $encodedUrl);
93 if ($result === \false) {
94 return \false;
95 }
96 return \array_map('urldecode', $result);
97 }
98 public function __toString() : string
99 {
100 if ($this->composedComponents === null) {
101 $this->composedComponents = self::composeComponents($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
102 }
103 return $this->composedComponents;
104 }
105 /**
106 * Composes a URI reference string from its various components.
107 *
108 * Usually this method does not need to be called manually but instead is used indirectly via
109 * `Psr\Http\Message\UriInterface::__toString`.
110 *
111 * PSR-7 UriInterface treats an empty component the same as a missing component as
112 * getQuery(), getFragment() etc. always return a string. This explains the slight
113 * difference to RFC 3986 Section 5.3.
114 *
115 * Another adjustment is that the authority separator is added even when the authority is missing/empty
116 * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
117 * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
118 * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
119 * that format).
120 *
121 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
122 */
123 public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment) : string
124 {
125 $uri = '';
126 // weak type checks to also accept null until we can add scalar type hints
127 if ($scheme != '') {
128 $uri .= $scheme . ':';
129 }
130 if ($authority != '' || $scheme === 'file') {
131 $uri .= '//' . $authority;
132 }
133 if ($authority != '' && $path != '' && $path[0] != '/') {
134 $path = '/' . $path;
135 }
136 $uri .= $path;
137 if ($query != '') {
138 $uri .= '?' . $query;
139 }
140 if ($fragment != '') {
141 $uri .= '#' . $fragment;
142 }
143 return $uri;
144 }
145 /**
146 * Whether the URI has the default port of the current scheme.
147 *
148 * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
149 * independently of the implementation.
150 */
151 public static function isDefaultPort(UriInterface $uri) : bool
152 {
153 return $uri->getPort() === null || isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()];
154 }
155 /**
156 * Whether the URI is absolute, i.e. it has a scheme.
157 *
158 * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
159 * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
160 * to another URI, the base URI. Relative references can be divided into several forms:
161 * - network-path references, e.g. '//example.com/path'
162 * - absolute-path references, e.g. '/path'
163 * - relative-path references, e.g. 'subpath'
164 *
165 * @see Uri::isNetworkPathReference
166 * @see Uri::isAbsolutePathReference
167 * @see Uri::isRelativePathReference
168 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4
169 */
170 public static function isAbsolute(UriInterface $uri) : bool
171 {
172 return $uri->getScheme() !== '';
173 }
174 /**
175 * Whether the URI is a network-path reference.
176 *
177 * A relative reference that begins with two slash characters is termed an network-path reference.
178 *
179 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
180 */
181 public static function isNetworkPathReference(UriInterface $uri) : bool
182 {
183 return $uri->getScheme() === '' && $uri->getAuthority() !== '';
184 }
185 /**
186 * Whether the URI is a absolute-path reference.
187 *
188 * A relative reference that begins with a single slash character is termed an absolute-path reference.
189 *
190 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
191 */
192 public static function isAbsolutePathReference(UriInterface $uri) : bool
193 {
194 return $uri->getScheme() === '' && $uri->getAuthority() === '' && isset($uri->getPath()[0]) && $uri->getPath()[0] === '/';
195 }
196 /**
197 * Whether the URI is a relative-path reference.
198 *
199 * A relative reference that does not begin with a slash character is termed a relative-path reference.
200 *
201 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
202 */
203 public static function isRelativePathReference(UriInterface $uri) : bool
204 {
205 return $uri->getScheme() === '' && $uri->getAuthority() === '' && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
206 }
207 /**
208 * Whether the URI is a same-document reference.
209 *
210 * A same-document reference refers to a URI that is, aside from its fragment
211 * component, identical to the base URI. When no base URI is given, only an empty
212 * URI reference (apart from its fragment) is considered a same-document reference.
213 *
214 * @param UriInterface $uri The URI to check
215 * @param UriInterface|null $base An optional base URI to compare against
216 *
217 * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4
218 */
219 public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null) : bool
220 {
221 if ($base !== null) {
222 $uri = UriResolver::resolve($base, $uri);
223 return $uri->getScheme() === $base->getScheme() && $uri->getAuthority() === $base->getAuthority() && $uri->getPath() === $base->getPath() && $uri->getQuery() === $base->getQuery();
224 }
225 return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
226 }
227 /**
228 * Creates a new URI with a specific query string value removed.
229 *
230 * Any existing query string values that exactly match the provided key are
231 * removed.
232 *
233 * @param UriInterface $uri URI to use as a base.
234 * @param string $key Query string key to remove.
235 */
236 public static function withoutQueryValue(UriInterface $uri, string $key) : UriInterface
237 {
238 $result = self::getFilteredQueryString($uri, [$key]);
239 return $uri->withQuery(\implode('&', $result));
240 }
241 /**
242 * Creates a new URI with a specific query string value.
243 *
244 * Any existing query string values that exactly match the provided key are
245 * removed and replaced with the given key value pair.
246 *
247 * A value of null will set the query string key without a value, e.g. "key"
248 * instead of "key=value".
249 *
250 * @param UriInterface $uri URI to use as a base.
251 * @param string $key Key to set.
252 * @param string|null $value Value to set
253 */
254 public static function withQueryValue(UriInterface $uri, string $key, ?string $value) : UriInterface
255 {
256 $result = self::getFilteredQueryString($uri, [$key]);
257 $result[] = self::generateQueryString($key, $value);
258 return $uri->withQuery(\implode('&', $result));
259 }
260 /**
261 * Creates a new URI with multiple specific query string values.
262 *
263 * It has the same behavior as withQueryValue() but for an associative array of key => value.
264 *
265 * @param UriInterface $uri URI to use as a base.
266 * @param (string|null)[] $keyValueArray Associative array of key and values
267 */
268 public static function withQueryValues(UriInterface $uri, array $keyValueArray) : UriInterface
269 {
270 $result = self::getFilteredQueryString($uri, \array_keys($keyValueArray));
271 foreach ($keyValueArray as $key => $value) {
272 $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null);
273 }
274 return $uri->withQuery(\implode('&', $result));
275 }
276 /**
277 * Creates a URI from a hash of `parse_url` components.
278 *
279 * @see https://www.php.net/manual/en/function.parse-url.php
280 *
281 * @throws MalformedUriException If the components do not form a valid URI.
282 */
283 public static function fromParts(array $parts) : UriInterface
284 {
285 $uri = new self();
286 $uri->applyParts($parts);
287 $uri->validateState();
288 return $uri;
289 }
290 public function getScheme() : string
291 {
292 return $this->scheme;
293 }
294 public function getAuthority() : string
295 {
296 $authority = $this->host;
297 if ($this->userInfo !== '') {
298 $authority = $this->userInfo . '@' . $authority;
299 }
300 if ($this->port !== null) {
301 $authority .= ':' . $this->port;
302 }
303 return $authority;
304 }
305 public function getUserInfo() : string
306 {
307 return $this->userInfo;
308 }
309 public function getHost() : string
310 {
311 return $this->host;
312 }
313 public function getPort() : ?int
314 {
315 return $this->port;
316 }
317 public function getPath() : string
318 {
319 return $this->path;
320 }
321 public function getQuery() : string
322 {
323 return $this->query;
324 }
325 public function getFragment() : string
326 {
327 return $this->fragment;
328 }
329 public function withScheme($scheme) : UriInterface
330 {
331 $scheme = $this->filterScheme($scheme);
332 if ($this->scheme === $scheme) {
333 return $this;
334 }
335 $new = clone $this;
336 $new->scheme = $scheme;
337 $new->composedComponents = null;
338 $new->removeDefaultPort();
339 $new->validateState();
340 return $new;
341 }
342 public function withUserInfo($user, $password = null) : UriInterface
343 {
344 $info = $this->filterUserInfoComponent($user);
345 if ($password !== null) {
346 $info .= ':' . $this->filterUserInfoComponent($password);
347 }
348 if ($this->userInfo === $info) {
349 return $this;
350 }
351 $new = clone $this;
352 $new->userInfo = $info;
353 $new->composedComponents = null;
354 $new->validateState();
355 return $new;
356 }
357 public function withHost($host) : UriInterface
358 {
359 $host = $this->filterHost($host);
360 if ($this->host === $host) {
361 return $this;
362 }
363 $new = clone $this;
364 $new->host = $host;
365 $new->composedComponents = null;
366 $new->validateState();
367 return $new;
368 }
369 public function withPort($port) : UriInterface
370 {
371 $port = $this->filterPort($port);
372 if ($this->port === $port) {
373 return $this;
374 }
375 $new = clone $this;
376 $new->port = $port;
377 $new->composedComponents = null;
378 $new->removeDefaultPort();
379 $new->validateState();
380 return $new;
381 }
382 public function withPath($path) : UriInterface
383 {
384 $path = $this->filterPath($path);
385 if ($this->path === $path) {
386 return $this;
387 }
388 $new = clone $this;
389 $new->path = $path;
390 $new->composedComponents = null;
391 $new->validateState();
392 return $new;
393 }
394 public function withQuery($query) : UriInterface
395 {
396 $query = $this->filterQueryAndFragment($query);
397 if ($this->query === $query) {
398 return $this;
399 }
400 $new = clone $this;
401 $new->query = $query;
402 $new->composedComponents = null;
403 return $new;
404 }
405 public function withFragment($fragment) : UriInterface
406 {
407 $fragment = $this->filterQueryAndFragment($fragment);
408 if ($this->fragment === $fragment) {
409 return $this;
410 }
411 $new = clone $this;
412 $new->fragment = $fragment;
413 $new->composedComponents = null;
414 return $new;
415 }
416 public function jsonSerialize() : string
417 {
418 return $this->__toString();
419 }
420 /**
421 * Apply parse_url parts to a URI.
422 *
423 * @param array $parts Array of parse_url parts to apply.
424 */
425 private function applyParts(array $parts) : void
426 {
427 $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : '';
428 $this->userInfo = isset($parts['user']) ? $this->filterUserInfoComponent($parts['user']) : '';
429 $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : '';
430 $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
431 $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
432 $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
433 $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
434 if (isset($parts['pass'])) {
435 $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
436 }
437 $this->removeDefaultPort();
438 }
439 /**
440 * @param mixed $scheme
441 *
442 * @throws \InvalidArgumentException If the scheme is invalid.
443 */
444 private function filterScheme($scheme) : string
445 {
446 if (!\is_string($scheme)) {
447 throw new \InvalidArgumentException('Scheme must be a string');
448 }
449 return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
450 }
451 /**
452 * @param mixed $component
453 *
454 * @throws \InvalidArgumentException If the user info is invalid.
455 */
456 private function filterUserInfoComponent($component) : string
457 {
458 if (!\is_string($component)) {
459 throw new \InvalidArgumentException('User info must be a string');
460 }
461 return \preg_replace_callback('/(?:[^%' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $component);
462 }
463 /**
464 * @param mixed $host
465 *
466 * @throws \InvalidArgumentException If the host is invalid.
467 */
468 private function filterHost($host) : string
469 {
470 if (!\is_string($host)) {
471 throw new \InvalidArgumentException('Host must be a string');
472 }
473 return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
474 }
475 /**
476 * @param mixed $port
477 *
478 * @throws \InvalidArgumentException If the port is invalid.
479 */
480 private function filterPort($port) : ?int
481 {
482 if ($port === null) {
483 return null;
484 }
485 $port = (int) $port;
486 if (0 > $port || 0xffff < $port) {
487 throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port));
488 }
489 return $port;
490 }
491 /**
492 * @param (string|int)[] $keys
493 *
494 * @return string[]
495 */
496 private static function getFilteredQueryString(UriInterface $uri, array $keys) : array
497 {
498 $current = $uri->getQuery();
499 if ($current === '') {
500 return [];
501 }
502 $decodedKeys = \array_map(function ($k) : string {
503 return \rawurldecode((string) $k);
504 }, $keys);
505 return \array_filter(\explode('&', $current), function ($part) use($decodedKeys) {
506 return !\in_array(\rawurldecode(\explode('=', $part)[0]), $decodedKeys, \true);
507 });
508 }
509 private static function generateQueryString(string $key, ?string $value) : string
510 {
511 // Query string separators ("=", "&") within the key or value need to be encoded
512 // (while preventing double-encoding) before setting the query string. All other
513 // chars that need percent-encoding will be encoded by withQuery().
514 $queryString = \strtr($key, self::QUERY_SEPARATORS_REPLACEMENT);
515 if ($value !== null) {
516 $queryString .= '=' . \strtr($value, self::QUERY_SEPARATORS_REPLACEMENT);
517 }
518 return $queryString;
519 }
520 private function removeDefaultPort() : void
521 {
522 if ($this->port !== null && self::isDefaultPort($this)) {
523 $this->port = null;
524 }
525 }
526 /**
527 * Filters the path of a URI
528 *
529 * @param mixed $path
530 *
531 * @throws \InvalidArgumentException If the path is invalid.
532 */
533 private function filterPath($path) : string
534 {
535 if (!\is_string($path)) {
536 throw new \InvalidArgumentException('Path must be a string');
537 }
538 return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\\/]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $path);
539 }
540 /**
541 * Filters the query string or fragment of a URI.
542 *
543 * @param mixed $str
544 *
545 * @throws \InvalidArgumentException If the query or fragment is invalid.
546 */
547 private function filterQueryAndFragment($str) : string
548 {
549 if (!\is_string($str)) {
550 throw new \InvalidArgumentException('Query and fragment must be a string');
551 }
552 return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\\/\\?]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $str);
553 }
554 private function rawurlencodeMatchZero(array $match) : string
555 {
556 return \rawurlencode($match[0]);
557 }
558 private function validateState() : void
559 {
560 if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
561 $this->host = self::HTTP_DEFAULT_HOST;
562 }
563 if ($this->getAuthority() === '') {
564 if (0 === \strpos($this->path, '//')) {
565 throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"');
566 }
567 if ($this->scheme === '' && \false !== \strpos(\explode('/', $this->path, 2)[0], ':')) {
568 throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon');
569 }
570 }
571 }
572 }
573