%PDF- %PDF-
| Direktori : /www/varak.net/nextcloud.varak.net/apps_old/apps/bookmarks/vendor/rowbot/idna/src/ |
| Current File : /www/varak.net/nextcloud.varak.net/apps_old/apps/bookmarks/vendor/rowbot/idna/src/Idna.php |
<?php
declare(strict_types=1);
namespace Rowbot\Idna;
use Normalizer;
use Rowbot\Punycode\Exception\PunycodeException;
use Rowbot\Punycode\Punycode;
use function array_merge;
use function count;
use function explode;
use function implode;
use function preg_match;
use function strncmp;
use function strlen;
use function substr;
/**
* @see https://www.unicode.org/reports/tr46/
*/
final class Idna
{
public const UNICODE_VERSION = '14.0.0';
private const DEFAULT_UNICODE_OPTIONS = [
'CheckHyphens' => true,
'CheckBidi' => true,
'CheckJoiners' => true,
'UseSTD3ASCIIRules' => true,
'Transitional_Processing' => false,
];
private const DEFAULT_ASCII_OPTIONS = self::DEFAULT_UNICODE_OPTIONS + [
'VerifyDnsLength' => true,
];
public const ERROR_EMPTY_LABEL = 1;
public const ERROR_LABEL_TOO_LONG = 2;
public const ERROR_DOMAIN_NAME_TOO_LONG = 4;
public const ERROR_LEADING_HYPHEN = 8;
public const ERROR_TRAILING_HYPHEN = 0x10;
public const ERROR_HYPHEN_3_4 = 0x20;
public const ERROR_LEADING_COMBINING_MARK = 0x40;
public const ERROR_DISALLOWED = 0x80;
public const ERROR_PUNYCODE = 0x100;
public const ERROR_LABEL_HAS_DOT = 0x200;
public const ERROR_INVALID_ACE_LABEL = 0x400;
public const ERROR_BIDI = 0x800;
public const ERROR_CONTEXTJ = 0x1000;
public const ERROR_CONTEXTO_PUNCTUATION = 0x2000;
public const ERROR_CONTEXTO_DIGITS = 0x4000;
private const MAX_DOMAIN_SIZE = 253;
private const MAX_LABEL_SIZE = 63;
/**
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* @see https://www.unicode.org/reports/tr46/#ProcessingStepMap
*
* @param array<string, bool> $options
*/
private static function mapCodePoints(string $domain, array $options, DomainInfo $info): string
{
$str = '';
$useSTD3ASCIIRules = $options['UseSTD3ASCIIRules'];
$transitional = $options['Transitional_Processing'];
$errors = 0;
$transitionalDifferent = false;
foreach (CodePoint::utf8Decode($domain) as $codePoint) {
$data = CodePointStatus::lookup($codePoint, $useSTD3ASCIIRules);
switch ($data['status']) {
case 'disallowed':
$errors |= Idna::ERROR_DISALLOWED;
// no break.
case 'valid':
$str .= CodePoint::encode($codePoint);
break;
case 'ignored':
// Do nothing.
break;
case 'mapped':
$str .= $data['mapping'];
break;
case 'deviation':
$transitionalDifferent = true;
$str .= ($transitional ? $data['mapping'] : CodePoint::encode($codePoint));
break;
}
}
$info->addError($errors);
if ($transitionalDifferent) {
$info->setTransitionalDifferent();
}
return $str;
}
/**
* @see https://www.unicode.org/reports/tr46/#Processing
*
* @param array<string, bool> $options
*
* @return list<string>
*/
private static function process(string $domain, array $options, DomainInfo $info): array
{
// If VerifyDnsLength is not set, we are doing ToUnicode otherwise we are doing ToASCII and
// we need to respect the VerifyDnsLength option.
$checkForEmptyLabels = !isset($options['VerifyDnsLength']) || $options['VerifyDnsLength'];
if ($checkForEmptyLabels && $domain === '') {
$info->addError(self::ERROR_EMPTY_LABEL);
return [''];
}
// Step 1. Map each code point in the domain name string
$domain = self::mapCodePoints($domain, $options, $info);
// Step 2. Normalize the domain name string to Unicode Normalization Form C.
if (!Normalizer::isNormalized($domain, Normalizer::FORM_C)) {
$originalDomain = $domain;
$domain = Normalizer::normalize($domain, Normalizer::FORM_C);
// This shouldn't be possible since the input string is run through the UTF-8 decoder
// when mapping the code points above, but lets account for it anyway.
if ($domain === false) {
$info->addError(self::ERROR_INVALID_ACE_LABEL);
$domain = $originalDomain;
}
unset($originalDomain);
}
// Step 3. Break the string into labels at U+002E (.) FULL STOP.
$labels = explode('.', $domain);
$lastLabelIndex = count($labels) - 1;
$validator = new LabelValidator($info);
// Step 4. Convert and validate each label in the domain name string.
foreach ($labels as $i => $label) {
$validationOptions = $options;
if (strncmp($label, 'xn--', 4) === 0) {
try {
$label = Punycode::decode(substr($label, 4));
} catch (PunycodeException $e) {
$info->addError(self::ERROR_PUNYCODE);
continue;
}
$validationOptions['Transitional_Processing'] = false;
$labels[$i] = $label;
}
$validator->validate($label, $validationOptions, $i > 0 && $i === $lastLabelIndex);
}
if ($info->isBidiDomain() && !$info->isValidBidiDomain()) {
$info->addError(self::ERROR_BIDI);
}
// Any input domain name string that does not record an error has been successfully
// processed according to this specification. Conversely, if an input domain_name string
// causes an error, then the processing of the input domain_name string fails. Determining
// what to do with error input is up to the caller, and not in the scope of this document.
return $labels;
}
/**
* @see https://www.unicode.org/reports/tr46/#ToASCII
*
* @param array<string, bool> $options
*/
public static function toAscii(string $domain, array $options = []): IdnaResult
{
$options = array_merge(self::DEFAULT_ASCII_OPTIONS, $options);
$info = new DomainInfo();
$labels = self::process($domain, $options, $info);
foreach ($labels as $i => $label) {
// Only convert labels to punycode that contain non-ASCII code points
if (preg_match('/[^\x00-\x7F]/', $label) === 1) {
try {
$label = 'xn--' . Punycode::encode($label);
} catch (PunycodeException $e) {
$info->addError(self::ERROR_PUNYCODE);
}
$labels[$i] = $label;
}
}
if ($options['VerifyDnsLength']) {
self::validateDomainAndLabelLength($labels, $info);
}
return new IdnaResult(implode('.', $labels), $info);
}
/**
* @see https://www.unicode.org/reports/tr46/#ToUnicode
*
* @param array<string, bool> $options
*/
public static function toUnicode(string $domain, array $options = []): IdnaResult
{
// VerifyDnsLength is not a valid option for toUnicode, so remove it if it exists.
unset($options['VerifyDnsLength']);
$options = array_merge(self::DEFAULT_UNICODE_OPTIONS, $options);
$info = new DomainInfo();
$labels = self::process($domain, $options, $info);
return new IdnaResult(implode('.', $labels), $info);
}
/**
* @param list<string> $labels
*/
private static function validateDomainAndLabelLength(array $labels, DomainInfo $info): void
{
$maxDomainSize = self::MAX_DOMAIN_SIZE;
$length = count($labels);
$totalLength = $length - 1;
// 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;
--$length;
}
for ($i = 0; $i < $length; ++$i) {
$bytes = strlen($labels[$i]);
$totalLength += $bytes;
if ($bytes > self::MAX_LABEL_SIZE) {
$info->addError(self::ERROR_LABEL_TOO_LONG);
}
}
if ($totalLength > $maxDomainSize) {
$info->addError(self::ERROR_DOMAIN_NAME_TOO_LONG);
}
}
}