Initial Commit
This commit is contained in:
177
dependencies/league/uri-interfaces/Idna/Converter.php
vendored
Normal file
177
dependencies/league/uri-interfaces/Idna/Converter.php
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
<?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 WP_Ultimo\Dependencies\League\Uri\Idna;
|
||||
|
||||
use WP_Ultimo\Dependencies\League\Uri\Exceptions\ConversionFailed;
|
||||
use WP_Ultimo\Dependencies\League\Uri\Exceptions\SyntaxError;
|
||||
use WP_Ultimo\Dependencies\League\Uri\FeatureDetection;
|
||||
use Stringable;
|
||||
use function idn_to_ascii;
|
||||
use function idn_to_utf8;
|
||||
use function rawurldecode;
|
||||
use const INTL_IDNA_VARIANT_UTS46;
|
||||
/**
|
||||
* @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
|
||||
*/
|
||||
final class Converter
|
||||
{
|
||||
private const REGEXP_IDNA_PATTERN = '/[^\\x20-\\x7f]/';
|
||||
private const MAX_DOMAIN_LENGTH = 253;
|
||||
private const MAX_LABEL_LENGTH = 63;
|
||||
/**
|
||||
* General registered name regular expression.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc3986#section-3.2.2
|
||||
* @see https://regex101.com/r/fptU8V/1
|
||||
*/
|
||||
private const REGEXP_REGISTERED_NAME = '/
|
||||
(?(DEFINE)
|
||||
(?<unreserved>[a-z0-9_~\\-]) # . is missing as it is used to separate labels
|
||||
(?<sub_delims>[!$&\'()*+,;=])
|
||||
(?<encoded>%[A-F0-9]{2})
|
||||
(?<reg_name>(?:(?&unreserved)|(?&sub_delims)|(?&encoded))*)
|
||||
)
|
||||
^(?:(?®_name)\\.)*(?®_name)\\.?$
|
||||
/ix';
|
||||
/**
|
||||
* Converts the input to its IDNA ASCII form or throw on failure.
|
||||
*
|
||||
* @see Converter::toAscii()
|
||||
*
|
||||
* @throws SyntaxError if the string cannot be converted to UNICODE using IDN UTS46 algorithm
|
||||
* @throws ConversionFailed if the conversion returns error
|
||||
*/
|
||||
public static function toAsciiOrFail(Stringable|string $domain, Option|int|null $options = null) : string
|
||||
{
|
||||
$result = self::toAscii($domain, $options);
|
||||
return match (\true) {
|
||||
$result->hasErrors() => throw ConversionFailed::dueToIdnError($domain, $result),
|
||||
default => $result->domain(),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Converts the input to its IDNA ASCII form.
|
||||
*
|
||||
* This method returns the string converted to IDN ASCII form
|
||||
*
|
||||
* @throws SyntaxError if the string cannot be converted to ASCII using IDN UTS46 algorithm
|
||||
*/
|
||||
public static function toAscii(Stringable|string $domain, Option|int|null $options = null) : Result
|
||||
{
|
||||
$domain = rawurldecode((string) $domain);
|
||||
if (1 === \preg_match(self::REGEXP_IDNA_PATTERN, $domain)) {
|
||||
FeatureDetection::supportsIdn();
|
||||
$flags = match (\true) {
|
||||
null === $options => Option::forIDNA2008Ascii(),
|
||||
$options instanceof Option => $options,
|
||||
default => Option::new($options),
|
||||
};
|
||||
idn_to_ascii($domain, $flags->toBytes(), INTL_IDNA_VARIANT_UTS46, $idnaInfo);
|
||||
if ([] === $idnaInfo) {
|
||||
return Result::fromIntl(['result' => \strtolower($domain), 'isTransitionalDifferent' => \false, 'errors' => self::validateDomainAndLabelLength($domain)]);
|
||||
}
|
||||
return Result::fromIntl($idnaInfo);
|
||||
}
|
||||
$error = Error::NONE->value;
|
||||
if (1 !== \preg_match(self::REGEXP_REGISTERED_NAME, $domain)) {
|
||||
$error |= Error::DISALLOWED->value;
|
||||
}
|
||||
return Result::fromIntl(['result' => \strtolower($domain), 'isTransitionalDifferent' => \false, 'errors' => self::validateDomainAndLabelLength($domain) | $error]);
|
||||
}
|
||||
/**
|
||||
* Converts the input to its IDNA UNICODE form or throw on failure.
|
||||
*
|
||||
* @see Converter::toUnicode()
|
||||
*
|
||||
* @throws ConversionFailed if the conversion returns error
|
||||
*/
|
||||
public static function toUnicodeOrFail(Stringable|string $domain, Option|int|null $options = null) : string
|
||||
{
|
||||
$result = self::toUnicode($domain, $options);
|
||||
return match (\true) {
|
||||
$result->hasErrors() => throw ConversionFailed::dueToIdnError($domain, $result),
|
||||
default => $result->domain(),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Converts the input to its IDNA UNICODE form.
|
||||
*
|
||||
* This method returns the string converted to IDN UNICODE form
|
||||
*
|
||||
* @throws SyntaxError if the string cannot be converted to UNICODE using IDN UTS46 algorithm
|
||||
*/
|
||||
public static function toUnicode(Stringable|string $domain, Option|int|null $options = null) : Result
|
||||
{
|
||||
$domain = rawurldecode((string) $domain);
|
||||
if (\false === \stripos($domain, 'xn--')) {
|
||||
return Result::fromIntl(['result' => $domain, 'isTransitionalDifferent' => \false, 'errors' => Error::NONE->value]);
|
||||
}
|
||||
FeatureDetection::supportsIdn();
|
||||
$flags = match (\true) {
|
||||
null === $options => Option::forIDNA2008Unicode(),
|
||||
$options instanceof Option => $options,
|
||||
default => Option::new($options),
|
||||
};
|
||||
idn_to_utf8($domain, $flags->toBytes(), INTL_IDNA_VARIANT_UTS46, $idnaInfo);
|
||||
if ([] === $idnaInfo) {
|
||||
return Result::fromIntl(['result' => $domain, 'isTransitionalDifferent' => \false, 'errors' => Error::NONE->value]);
|
||||
}
|
||||
return Result::fromIntl($idnaInfo);
|
||||
}
|
||||
/**
|
||||
* Tells whether the submitted host is a valid IDN regardless of its format.
|
||||
*
|
||||
* Returns false if the host is invalid or if its conversion yield the same result
|
||||
*/
|
||||
public static function isIdn(Stringable|string|null $domain) : bool
|
||||
{
|
||||
$domain = \strtolower(rawurldecode((string) $domain));
|
||||
$result = match (1) {
|
||||
\preg_match(self::REGEXP_IDNA_PATTERN, $domain) => self::toAscii($domain),
|
||||
default => self::toUnicode($domain),
|
||||
};
|
||||
return match (\true) {
|
||||
$result->hasErrors() => \false,
|
||||
default => $result->domain() !== $domain,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Adapted from https://github.com/TRowbotham/idna.
|
||||
*
|
||||
* @see https://github.com/TRowbotham/idna/blob/master/src/Idna.php#L236
|
||||
*/
|
||||
private static function validateDomainAndLabelLength(string $domain) : int
|
||||
{
|
||||
$error = Error::NONE->value;
|
||||
$labels = \explode('.', $domain);
|
||||
$maxDomainSize = self::MAX_DOMAIN_LENGTH;
|
||||
$length = \count($labels);
|
||||
// If the last label is empty, and it is not the first label, then it is the root label.
|
||||
// Increase the max size by 1, making it 254, to account for the root label's "."
|
||||
// delimiter. This also means we don't need to check the last label's length for being too
|
||||
// long.
|
||||
if ($length > 1 && '' === $labels[$length - 1]) {
|
||||
++$maxDomainSize;
|
||||
\array_pop($labels);
|
||||
}
|
||||
if (\strlen($domain) > $maxDomainSize) {
|
||||
$error |= Error::DOMAIN_NAME_TOO_LONG->value;
|
||||
}
|
||||
foreach ($labels as $label) {
|
||||
if (\strlen($label) > self::MAX_LABEL_LENGTH) {
|
||||
$error |= Error::LABEL_TOO_LONG->value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $error;
|
||||
}
|
||||
}
|
56
dependencies/league/uri-interfaces/Idna/Error.php
vendored
Normal file
56
dependencies/league/uri-interfaces/Idna/Error.php
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
<?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.
|
||||
*/
|
||||
namespace WP_Ultimo\Dependencies\League\Uri\Idna;
|
||||
|
||||
enum Error : int
|
||||
{
|
||||
case NONE = 0;
|
||||
case EMPTY_LABEL = 1;
|
||||
case LABEL_TOO_LONG = 2;
|
||||
case DOMAIN_NAME_TOO_LONG = 4;
|
||||
case LEADING_HYPHEN = 8;
|
||||
case TRAILING_HYPHEN = 0x10;
|
||||
case HYPHEN_3_4 = 0x20;
|
||||
case LEADING_COMBINING_MARK = 0x40;
|
||||
case DISALLOWED = 0x80;
|
||||
case PUNYCODE = 0x100;
|
||||
case LABEL_HAS_DOT = 0x200;
|
||||
case INVALID_ACE_LABEL = 0x400;
|
||||
case BIDI = 0x800;
|
||||
case CONTEXTJ = 0x1000;
|
||||
case CONTEXTO_PUNCTUATION = 0x2000;
|
||||
case CONTEXTO_DIGITS = 0x4000;
|
||||
public function description() : string
|
||||
{
|
||||
return match ($this) {
|
||||
self::NONE => 'No error has occurred',
|
||||
self::EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty',
|
||||
self::LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes',
|
||||
self::DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form',
|
||||
self::LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")',
|
||||
self::TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")',
|
||||
self::HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions',
|
||||
self::LEADING_COMBINING_MARK => 'a label starts with a combining mark',
|
||||
self::DISALLOWED => 'a label or domain name contains disallowed characters',
|
||||
self::PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode',
|
||||
self::LABEL_HAS_DOT => 'a label contains a dot=full stop',
|
||||
self::INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string',
|
||||
self::BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)',
|
||||
self::CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements',
|
||||
self::CONTEXTO_DIGITS => 'a label does not meet the IDNA CONTEXTO requirements for digits',
|
||||
self::CONTEXTO_PUNCTUATION => 'a label does not meet the IDNA CONTEXTO requirements for punctuation characters. Some punctuation characters "Would otherwise have been DISALLOWED" but are allowed in certain contexts',
|
||||
};
|
||||
}
|
||||
public static function filterByErrorBytes(int $errors) : array
|
||||
{
|
||||
return \array_values(\array_filter(self::cases(), fn(self $error): bool => 0 !== ($error->value & $errors)));
|
||||
}
|
||||
}
|
137
dependencies/league/uri-interfaces/Idna/Option.php
vendored
Normal file
137
dependencies/league/uri-interfaces/Idna/Option.php
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
<?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 WP_Ultimo\Dependencies\League\Uri\Idna;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionClassConstant;
|
||||
/**
|
||||
* @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
|
||||
*/
|
||||
final class Option
|
||||
{
|
||||
private const DEFAULT = 0;
|
||||
private const ALLOW_UNASSIGNED = 1;
|
||||
private const USE_STD3_RULES = 2;
|
||||
private const CHECK_BIDI = 4;
|
||||
private const CHECK_CONTEXTJ = 8;
|
||||
private const NONTRANSITIONAL_TO_ASCII = 0x10;
|
||||
private const NONTRANSITIONAL_TO_UNICODE = 0x20;
|
||||
private const CHECK_CONTEXTO = 0x40;
|
||||
private function __construct(private readonly int $value)
|
||||
{
|
||||
}
|
||||
private static function cases() : array
|
||||
{
|
||||
static $assoc;
|
||||
if (null === $assoc) {
|
||||
$assoc = [];
|
||||
$fooClass = new ReflectionClass(self::class);
|
||||
foreach ($fooClass->getConstants(ReflectionClassConstant::IS_PRIVATE) as $name => $value) {
|
||||
$assoc[$name] = $value;
|
||||
}
|
||||
}
|
||||
return $assoc;
|
||||
}
|
||||
public static function new(int $bytes = self::DEFAULT) : self
|
||||
{
|
||||
return new self(\array_reduce(self::cases(), fn(int $value, int $option) => 0 !== ($option & $bytes) ? $value | $option : $value, self::DEFAULT));
|
||||
}
|
||||
public static function forIDNA2008Ascii() : self
|
||||
{
|
||||
return self::new()->nonTransitionalToAscii()->checkBidi()->useSTD3Rules()->checkContextJ();
|
||||
}
|
||||
public static function forIDNA2008Unicode() : self
|
||||
{
|
||||
return self::new()->nonTransitionalToUnicode()->checkBidi()->useSTD3Rules()->checkContextJ();
|
||||
}
|
||||
public function toBytes() : int
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
/** array<string, int> */
|
||||
public function list() : array
|
||||
{
|
||||
return \array_keys(\array_filter(self::cases(), fn(int $value) => 0 !== ($value & $this->value)));
|
||||
}
|
||||
public function allowUnassigned() : self
|
||||
{
|
||||
return $this->add(self::ALLOW_UNASSIGNED);
|
||||
}
|
||||
public function disallowUnassigned() : self
|
||||
{
|
||||
return $this->remove(self::ALLOW_UNASSIGNED);
|
||||
}
|
||||
public function useSTD3Rules() : self
|
||||
{
|
||||
return $this->add(self::USE_STD3_RULES);
|
||||
}
|
||||
public function prohibitSTD3Rules() : self
|
||||
{
|
||||
return $this->remove(self::USE_STD3_RULES);
|
||||
}
|
||||
public function checkBidi() : self
|
||||
{
|
||||
return $this->add(self::CHECK_BIDI);
|
||||
}
|
||||
public function ignoreBidi() : self
|
||||
{
|
||||
return $this->remove(self::CHECK_BIDI);
|
||||
}
|
||||
public function checkContextJ() : self
|
||||
{
|
||||
return $this->add(self::CHECK_CONTEXTJ);
|
||||
}
|
||||
public function ignoreContextJ() : self
|
||||
{
|
||||
return $this->remove(self::CHECK_CONTEXTJ);
|
||||
}
|
||||
public function checkContextO() : self
|
||||
{
|
||||
return $this->add(self::CHECK_CONTEXTO);
|
||||
}
|
||||
public function ignoreContextO() : self
|
||||
{
|
||||
return $this->remove(self::CHECK_CONTEXTO);
|
||||
}
|
||||
public function nonTransitionalToAscii() : self
|
||||
{
|
||||
return $this->add(self::NONTRANSITIONAL_TO_ASCII);
|
||||
}
|
||||
public function transitionalToAscii() : self
|
||||
{
|
||||
return $this->remove(self::NONTRANSITIONAL_TO_ASCII);
|
||||
}
|
||||
public function nonTransitionalToUnicode() : self
|
||||
{
|
||||
return $this->add(self::NONTRANSITIONAL_TO_UNICODE);
|
||||
}
|
||||
public function transitionalToUnicode() : self
|
||||
{
|
||||
return $this->remove(self::NONTRANSITIONAL_TO_UNICODE);
|
||||
}
|
||||
public function add(Option|int|null $option = null) : self
|
||||
{
|
||||
return match (\true) {
|
||||
null === $option => $this,
|
||||
$option instanceof self => self::new($this->value | $option->value),
|
||||
default => self::new($this->value | $option),
|
||||
};
|
||||
}
|
||||
public function remove(Option|int|null $option = null) : self
|
||||
{
|
||||
return match (\true) {
|
||||
null === $option => $this,
|
||||
$option instanceof self => self::new($this->value & ~$option->value),
|
||||
default => self::new($this->value & ~$option),
|
||||
};
|
||||
}
|
||||
}
|
57
dependencies/league/uri-interfaces/Idna/Result.php
vendored
Normal file
57
dependencies/league/uri-interfaces/Idna/Result.php
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
<?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 WP_Ultimo\Dependencies\League\Uri\Idna;
|
||||
|
||||
/**
|
||||
* @see https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/uidna_8h.html
|
||||
*/
|
||||
final class Result
|
||||
{
|
||||
private function __construct(
|
||||
private readonly string $domain,
|
||||
private readonly bool $isTransitionalDifferent,
|
||||
/** @var array<Error> */
|
||||
private readonly array $errors
|
||||
)
|
||||
{
|
||||
}
|
||||
/**
|
||||
* @param array{result:string, isTransitionalDifferent:bool, errors:int} $infos
|
||||
*/
|
||||
public static function fromIntl(array $infos) : self
|
||||
{
|
||||
return new self($infos['result'], $infos['isTransitionalDifferent'], Error::filterByErrorBytes($infos['errors']));
|
||||
}
|
||||
public function domain() : string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
public function isTransitionalDifferent() : bool
|
||||
{
|
||||
return $this->isTransitionalDifferent;
|
||||
}
|
||||
/**
|
||||
* @return array<Error>
|
||||
*/
|
||||
public function errors() : array
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
public function hasErrors() : bool
|
||||
{
|
||||
return [] !== $this->errors;
|
||||
}
|
||||
public function hasError(Error $error) : bool
|
||||
{
|
||||
return \in_array($error, $this->errors, \true);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user