aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/league/uri/BaseUri.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/league/uri/BaseUri.php')
-rw-r--r--vendor/league/uri/BaseUri.php658
1 files changed, 658 insertions, 0 deletions
diff --git a/vendor/league/uri/BaseUri.php b/vendor/league/uri/BaseUri.php
new file mode 100644
index 000000000..c29dc2799
--- /dev/null
+++ b/vendor/league/uri/BaseUri.php
@@ -0,0 +1,658 @@
+<?php
+
+/**
+ * League.Uri (https://uri.thephpleague.com)
+ *
+ * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace League\Uri;
+
+use JsonSerializable;
+use League\Uri\Contracts\UriAccess;
+use League\Uri\Contracts\UriInterface;
+use League\Uri\Exceptions\MissingFeature;
+use League\Uri\Idna\Converter as IdnaConverter;
+use League\Uri\IPv4\Converter as IPv4Converter;
+use League\Uri\IPv6\Converter as IPv6Converter;
+use Psr\Http\Message\UriFactoryInterface;
+use Psr\Http\Message\UriInterface as Psr7UriInterface;
+use Stringable;
+
+use function array_pop;
+use function array_reduce;
+use function count;
+use function end;
+use function explode;
+use function implode;
+use function in_array;
+use function preg_match;
+use function rawurldecode;
+use function str_repeat;
+use function str_replace;
+use function strpos;
+use function substr;
+
+/**
+ * @phpstan-import-type ComponentMap from UriInterface
+ */
+class BaseUri implements Stringable, JsonSerializable, UriAccess
+{
+ /** @var array<string,int> */
+ final protected const WHATWG_SPECIAL_SCHEMES = ['ftp' => 1, 'http' => 1, 'https' => 1, 'ws' => 1, 'wss' => 1];
+
+ /** @var array<string,int> */
+ final protected const DOT_SEGMENTS = ['.' => 1, '..' => 1];
+
+ protected readonly Psr7UriInterface|UriInterface|null $origin;
+ protected readonly ?string $nullValue;
+
+ /**
+ * @param UriFactoryInterface|null $uriFactory Deprecated, will be removed in the next major release
+ */
+ final protected function __construct(
+ protected readonly Psr7UriInterface|UriInterface $uri,
+ protected readonly ?UriFactoryInterface $uriFactory
+ ) {
+ $this->nullValue = $this->uri instanceof Psr7UriInterface ? '' : null;
+ $this->origin = $this->computeOrigin($this->uri, $this->nullValue);
+ }
+
+ public static function from(Stringable|string $uri, ?UriFactoryInterface $uriFactory = null): static
+ {
+ return new static(static::formatHost(static::filterUri($uri, $uriFactory)), $uriFactory);
+ }
+
+ public function withUriFactory(UriFactoryInterface $uriFactory): static
+ {
+ return new static($this->uri, $uriFactory);
+ }
+
+ public function withoutUriFactory(): static
+ {
+ return new static($this->uri, null);
+ }
+
+ public function getUri(): Psr7UriInterface|UriInterface
+ {
+ return $this->uri;
+ }
+
+ public function getUriString(): string
+ {
+ return $this->uri->__toString();
+ }
+
+ public function jsonSerialize(): string
+ {
+ return $this->uri->__toString();
+ }
+
+ public function __toString(): string
+ {
+ return $this->uri->__toString();
+ }
+
+ public function origin(): ?self
+ {
+ return match (null) {
+ $this->origin => null,
+ default => new self($this->origin, $this->uriFactory),
+ };
+ }
+
+ /**
+ * Returns the Unix filesystem path.
+ *
+ * The method will return null if a scheme is present and is not the `file` scheme
+ */
+ public function unixPath(): ?string
+ {
+ return match ($this->uri->getScheme()) {
+ 'file', $this->nullValue => rawurldecode($this->uri->getPath()),
+ default => null,
+ };
+ }
+
+ /**
+ * Returns the Windows filesystem path.
+ *
+ * The method will return null if a scheme is present and is not the `file` scheme
+ */
+ public function windowsPath(): ?string
+ {
+ static $regexpWindowsPath = ',^(?<root>[a-zA-Z]:),';
+
+ if (!in_array($this->uri->getScheme(), ['file', $this->nullValue], true)) {
+ return null;
+ }
+
+ $originalPath = $this->uri->getPath();
+ $path = $originalPath;
+ if ('/' === ($path[0] ?? '')) {
+ $path = substr($path, 1);
+ }
+
+ if (1 === preg_match($regexpWindowsPath, $path, $matches)) {
+ $root = $matches['root'];
+ $path = substr($path, strlen($root));
+
+ return $root.str_replace('/', '\\', rawurldecode($path));
+ }
+
+ $host = $this->uri->getHost();
+
+ return match ($this->nullValue) {
+ $host => str_replace('/', '\\', rawurldecode($originalPath)),
+ default => '\\\\'.$host.'\\'.str_replace('/', '\\', rawurldecode($path)),
+ };
+ }
+
+ /**
+ * Returns a string representation of a File URI according to RFC8089.
+ *
+ * The method will return null if the URI scheme is not the `file` scheme
+ */
+ public function toRfc8089(): ?string
+ {
+ $path = $this->uri->getPath();
+
+ return match (true) {
+ 'file' !== $this->uri->getScheme() => null,
+ in_array($this->uri->getAuthority(), ['', null, 'localhost'], true) => 'file:'.match (true) {
+ '' === $path,
+ '/' === $path[0] => $path,
+ default => '/'.$path,
+ },
+ default => (string) $this->uri,
+ };
+ }
+
+ /**
+ * Tells whether the `file` scheme base URI represents a local file.
+ */
+ public function isLocalFile(): bool
+ {
+ return match (true) {
+ 'file' !== $this->uri->getScheme() => false,
+ in_array($this->uri->getAuthority(), ['', null, 'localhost'], true) => true,
+ default => false,
+ };
+ }
+
+ /**
+ * Tells whether the URI is opaque or not.
+ *
+ * A URI is opaque if and only if it is absolute
+ * and does not has an authority path.
+ */
+ public function isOpaque(): bool
+ {
+ return $this->nullValue === $this->uri->getAuthority()
+ && $this->isAbsolute();
+ }
+
+ /**
+ * Tells whether two URI do not share the same origin.
+ */
+ public function isCrossOrigin(Stringable|string $uri): bool
+ {
+ if (null === $this->origin) {
+ return true;
+ }
+
+ $uri = static::filterUri($uri);
+ $uriOrigin = $this->computeOrigin($uri, $uri instanceof Psr7UriInterface ? '' : null);
+
+ return match(true) {
+ null === $uriOrigin,
+ $uriOrigin->__toString() !== $this->origin->__toString() => true,
+ default => false,
+ };
+ }
+
+ /**
+ * Tells whether the URI is absolute.
+ */
+ public function isAbsolute(): bool
+ {
+ return $this->nullValue !== $this->uri->getScheme();
+ }
+
+ /**
+ * Tells whether the URI is a network path.
+ */
+ public function isNetworkPath(): bool
+ {
+ return $this->nullValue === $this->uri->getScheme()
+ && $this->nullValue !== $this->uri->getAuthority();
+ }
+
+ /**
+ * Tells whether the URI is an absolute path.
+ */
+ public function isAbsolutePath(): bool
+ {
+ return $this->nullValue === $this->uri->getScheme()
+ && $this->nullValue === $this->uri->getAuthority()
+ && '/' === ($this->uri->getPath()[0] ?? '');
+ }
+
+ /**
+ * Tells whether the URI is a relative path.
+ */
+ public function isRelativePath(): bool
+ {
+ return $this->nullValue === $this->uri->getScheme()
+ && $this->nullValue === $this->uri->getAuthority()
+ && '/' !== ($this->uri->getPath()[0] ?? '');
+ }
+
+ /**
+ * Tells whether both URI refers to the same document.
+ */
+ public function isSameDocument(Stringable|string $uri): bool
+ {
+ return $this->normalize(static::filterUri($uri)) === $this->normalize($this->uri);
+ }
+
+ /**
+ * Tells whether the URI contains an Internationalized Domain Name (IDN).
+ */
+ public function hasIdn(): bool
+ {
+ return IdnaConverter::isIdn($this->uri->getHost());
+ }
+
+ /**
+ * Tells whether the URI contains an IPv4 regardless if it is mapped or native.
+ */
+ public function hasIPv4(): bool
+ {
+ return IPv4Converter::fromEnvironment()->isIpv4($this->uri->getHost());
+ }
+
+ /**
+ * Resolves a URI against a base URI using RFC3986 rules.
+ *
+ * This method MUST retain the state of the submitted URI instance, and return
+ * a URI instance of the same type that contains the applied modifications.
+ *
+ * This method MUST be transparent when dealing with error and exceptions.
+ * It MUST not alter or silence them apart from validating its own parameters.
+ */
+ public function resolve(Stringable|string $uri): static
+ {
+ $uri = static::formatHost(static::filterUri($uri, $this->uriFactory));
+ $null = $uri instanceof Psr7UriInterface ? '' : null;
+
+ if ($null !== $uri->getScheme()) {
+ return new static(
+ $uri->withPath(static::removeDotSegments($uri->getPath())),
+ $this->uriFactory
+ );
+ }
+
+ if ($null !== $uri->getAuthority()) {
+ return new static(
+ $uri
+ ->withScheme($this->uri->getScheme())
+ ->withPath(static::removeDotSegments($uri->getPath())),
+ $this->uriFactory
+ );
+ }
+
+ $user = $null;
+ $pass = null;
+ $userInfo = $this->uri->getUserInfo();
+ if (null !== $userInfo) {
+ [$user, $pass] = explode(':', $userInfo, 2) + [1 => null];
+ }
+
+ [$path, $query] = $this->resolvePathAndQuery($uri);
+
+ return new static(
+ $uri
+ ->withPath($this->removeDotSegments($path))
+ ->withQuery($query)
+ ->withHost($this->uri->getHost())
+ ->withPort($this->uri->getPort())
+ ->withUserInfo($user, $pass)
+ ->withScheme($this->uri->getScheme()),
+ $this->uriFactory
+ );
+ }
+
+ /**
+ * Relativize a URI according to a base URI.
+ *
+ * This method MUST retain the state of the submitted URI instance, and return
+ * a URI instance of the same type that contains the applied modifications.
+ *
+ * This method MUST be transparent when dealing with error and exceptions.
+ * It MUST not alter of silence them apart from validating its own parameters.
+ */
+ public function relativize(Stringable|string $uri): static
+ {
+ $uri = static::formatHost(static::filterUri($uri, $this->uriFactory));
+ if ($this->canNotBeRelativize($uri)) {
+ return new static($uri, $this->uriFactory);
+ }
+
+ $null = $uri instanceof Psr7UriInterface ? '' : null;
+ $uri = $uri->withScheme($null)->withPort(null)->withUserInfo($null)->withHost($null);
+ $targetPath = $uri->getPath();
+ $basePath = $this->uri->getPath();
+
+ return new static(
+ match (true) {
+ $targetPath !== $basePath => $uri->withPath(static::relativizePath($targetPath, $basePath)),
+ static::componentEquals('query', $uri) => $uri->withPath('')->withQuery($null),
+ $null === $uri->getQuery() => $uri->withPath(static::formatPathWithEmptyBaseQuery($targetPath)),
+ default => $uri->withPath(''),
+ },
+ $this->uriFactory
+ );
+ }
+
+ final protected function computeOrigin(Psr7UriInterface|UriInterface $uri, ?string $nullValue): Psr7UriInterface|UriInterface|null
+ {
+ $scheme = $uri->getScheme();
+ if ('blob' !== $scheme) {
+ return match (true) {
+ isset(static::WHATWG_SPECIAL_SCHEMES[$scheme]) => $uri
+ ->withFragment($nullValue)
+ ->withQuery($nullValue)
+ ->withPath('')
+ ->withUserInfo($nullValue),
+ default => null,
+ };
+ }
+
+ $components = UriString::parse($uri->getPath());
+ if ($uri instanceof Psr7UriInterface) {
+ /** @var ComponentMap $components */
+ $components = array_map(fn ($component) => null === $component ? '' : $component, $components);
+ }
+
+ return match (true) {
+ null !== $components['scheme'] && isset(static::WHATWG_SPECIAL_SCHEMES[strtolower($components['scheme'])]) => $uri
+ ->withFragment($nullValue)
+ ->withQuery($nullValue)
+ ->withPath('')
+ ->withHost($components['host'])
+ ->withPort($components['port'])
+ ->withScheme($components['scheme'])
+ ->withUserInfo($nullValue),
+ default => null,
+ };
+ }
+
+ /**
+ * Normalizes a URI for comparison; this URI string representation is not suitable for usage as per RFC guidelines.
+ */
+ final protected function normalize(Psr7UriInterface|UriInterface $uri): string
+ {
+ $null = $uri instanceof Psr7UriInterface ? '' : null;
+
+ $path = $uri->getPath();
+ if ('/' === ($path[0] ?? '') || '' !== $uri->getScheme().$uri->getAuthority()) {
+ $path = $this->removeDotSegments($path);
+ }
+
+ $query = $uri->getQuery();
+ $pairs = null === $query ? [] : explode('&', $query);
+ sort($pairs);
+
+ static $regexpEncodedChars = ',%(2[D|E]|3\d|4[1-9|A-F]|5[\d|AF]|6[1-9|A-F]|7[\d|E]),i';
+ $value = preg_replace_callback(
+ $regexpEncodedChars,
+ static fn (array $matches): string => rawurldecode($matches[0]),
+ [$path, implode('&', $pairs)]
+ ) ?? ['', $null];
+
+ [$path, $query] = $value + ['', $null];
+ if ($null !== $uri->getAuthority() && '' === $path) {
+ $path = '/';
+ }
+
+ return $uri
+ ->withHost(Uri::fromComponents(['host' => $uri->getHost()])->getHost())
+ ->withPath($path)
+ ->withQuery([] === $pairs ? $null : $query)
+ ->withFragment($null)
+ ->__toString();
+ }
+
+ /**
+ * Input URI normalization to allow Stringable and string URI.
+ */
+ final protected static function filterUri(Stringable|string $uri, UriFactoryInterface|null $uriFactory = null): Psr7UriInterface|UriInterface
+ {
+ return match (true) {
+ $uri instanceof UriAccess => $uri->getUri(),
+ $uri instanceof Psr7UriInterface,
+ $uri instanceof UriInterface => $uri,
+ $uriFactory instanceof UriFactoryInterface => $uriFactory->createUri((string) $uri),
+ default => Uri::new($uri),
+ };
+ }
+
+ /**
+ * Remove dot segments from the URI path as per RFC specification.
+ */
+ final protected function removeDotSegments(string $path): string
+ {
+ if (!str_contains($path, '.')) {
+ return $path;
+ }
+
+ $reducer = function (array $carry, string $segment): array {
+ if ('..' === $segment) {
+ array_pop($carry);
+
+ return $carry;
+ }
+
+ if (!isset(static::DOT_SEGMENTS[$segment])) {
+ $carry[] = $segment;
+ }
+
+ return $carry;
+ };
+
+ $oldSegments = explode('/', $path);
+ $newPath = implode('/', array_reduce($oldSegments, $reducer(...), []));
+ if (isset(static::DOT_SEGMENTS[end($oldSegments)])) {
+ $newPath .= '/';
+ }
+
+ // @codeCoverageIgnoreStart
+ // added because some PSR-7 implementations do not respect RFC3986
+ if (str_starts_with($path, '/') && !str_starts_with($newPath, '/')) {
+ return '/'.$newPath;
+ }
+ // @codeCoverageIgnoreEnd
+
+ return $newPath;
+ }
+
+ /**
+ * Resolves an URI path and query component.
+ *
+ * @return array{0:string, 1:string|null}
+ */
+ final protected function resolvePathAndQuery(Psr7UriInterface|UriInterface $uri): array
+ {
+ $targetPath = $uri->getPath();
+ $null = $uri instanceof Psr7UriInterface ? '' : null;
+
+ if (str_starts_with($targetPath, '/')) {
+ return [$targetPath, $uri->getQuery()];
+ }
+
+ if ('' === $targetPath) {
+ $targetQuery = $uri->getQuery();
+ if ($null === $targetQuery) {
+ $targetQuery = $this->uri->getQuery();
+ }
+
+ $targetPath = $this->uri->getPath();
+ //@codeCoverageIgnoreStart
+ //because some PSR-7 Uri implementations allow this RFC3986 forbidden construction
+ if (null !== $this->uri->getAuthority() && !str_starts_with($targetPath, '/')) {
+ $targetPath = '/'.$targetPath;
+ }
+ //@codeCoverageIgnoreEnd
+
+ return [$targetPath, $targetQuery];
+ }
+
+ $basePath = $this->uri->getPath();
+ if (null !== $this->uri->getAuthority() && '' === $basePath) {
+ $targetPath = '/'.$targetPath;
+ }
+
+ if ('' !== $basePath) {
+ $segments = explode('/', $basePath);
+ array_pop($segments);
+ if ([] !== $segments) {
+ $targetPath = implode('/', $segments).'/'.$targetPath;
+ }
+ }
+
+ return [$targetPath, $uri->getQuery()];
+ }
+
+ /**
+ * Tells whether the component value from both URI object equals.
+ *
+ * @pqram 'query'|'authority'|'scheme' $property
+ */
+ final protected function componentEquals(string $property, Psr7UriInterface|UriInterface $uri): bool
+ {
+ $getComponent = function (string $property, Psr7UriInterface|UriInterface $uri): ?string {
+ $component = match ($property) {
+ 'query' => $uri->getQuery(),
+ 'authority' => $uri->getAuthority(),
+ default => $uri->getScheme(),
+ };
+
+ return match (true) {
+ $uri instanceof UriInterface, '' !== $component => $component,
+ default => null,
+ };
+ };
+
+ return $getComponent($property, $uri) === $getComponent($property, $this->uri);
+ }
+
+ /**
+ * Filter the URI object.
+ */
+ final protected static function formatHost(Psr7UriInterface|UriInterface $uri): Psr7UriInterface|UriInterface
+ {
+ $host = $uri->getHost();
+ try {
+ $converted = IPv4Converter::fromEnvironment()->toDecimal($host);
+ } catch (MissingFeature) {
+ $converted = null;
+ }
+
+ if (false === filter_var($converted, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ $converted = IPv6Converter::compress($host);
+ }
+
+ return match (true) {
+ null !== $converted => $uri->withHost($converted),
+ '' === $host,
+ $uri instanceof UriInterface => $uri,
+ default => $uri->withHost((string) Uri::fromComponents(['host' => $host])->getHost()),
+ };
+ }
+
+ /**
+ * Tells whether the submitted URI object can be relativized.
+ */
+ final protected function canNotBeRelativize(Psr7UriInterface|UriInterface $uri): bool
+ {
+ return !static::componentEquals('scheme', $uri)
+ || !static::componentEquals('authority', $uri)
+ || static::from($uri)->isRelativePath();
+ }
+
+ /**
+ * Relatives the URI for an authority-less target URI.
+ */
+ final protected static function relativizePath(string $path, string $basePath): string
+ {
+ $baseSegments = static::getSegments($basePath);
+ $targetSegments = static::getSegments($path);
+ $targetBasename = array_pop($targetSegments);
+ array_pop($baseSegments);
+ foreach ($baseSegments as $offset => $segment) {
+ if (!isset($targetSegments[$offset]) || $segment !== $targetSegments[$offset]) {
+ break;
+ }
+ unset($baseSegments[$offset], $targetSegments[$offset]);
+ }
+ $targetSegments[] = $targetBasename;
+
+ return static::formatPath(
+ str_repeat('../', count($baseSegments)).implode('/', $targetSegments),
+ $basePath
+ );
+ }
+
+ /**
+ * returns the path segments.
+ *
+ * @return string[]
+ */
+ final protected static function getSegments(string $path): array
+ {
+ return explode('/', match (true) {
+ '' === $path,
+ '/' !== $path[0] => $path,
+ default => substr($path, 1),
+ });
+ }
+
+ /**
+ * Formatting the path to keep a valid URI.
+ */
+ final protected static function formatPath(string $path, string $basePath): string
+ {
+ $colonPosition = strpos($path, ':');
+ $slashPosition = strpos($path, '/');
+
+ return match (true) {
+ '' === $path => match (true) {
+ '' === $basePath,
+ '/' === $basePath => $basePath,
+ default => './',
+ },
+ false === $colonPosition => $path,
+ false === $slashPosition,
+ $colonPosition < $slashPosition => "./$path",
+ default => $path,
+ };
+ }
+
+ /**
+ * Formatting the path to keep a resolvable URI.
+ */
+ final protected static function formatPathWithEmptyBaseQuery(string $path): string
+ {
+ $targetSegments = static::getSegments($path);
+ /** @var string $basename */
+ $basename = end($targetSegments);
+
+ return '' === $basename ? './' : $basename;
+ }
+}