|
4 | 4 |
|
5 | 5 | namespace Pdp;
|
6 | 6 |
|
7 |
| -use UnexpectedValueException; |
8 | 7 | use function idn_to_ascii;
|
9 | 8 | use function idn_to_utf8;
|
10 |
| -use function implode; |
11 | 9 | use function preg_match;
|
12 | 10 | use function rawurldecode;
|
13 | 11 | use function strpos;
|
14 | 12 | use function strtolower;
|
15 | 13 | use const IDNA_CHECK_BIDI;
|
16 | 14 | use const IDNA_CHECK_CONTEXTJ;
|
17 | 15 | use const IDNA_DEFAULT;
|
18 |
| -use const IDNA_ERROR_BIDI; |
19 |
| -use const IDNA_ERROR_CONTEXTJ; |
20 |
| -use const IDNA_ERROR_DISALLOWED; |
21 |
| -use const IDNA_ERROR_DOMAIN_NAME_TOO_LONG; |
22 |
| -use const IDNA_ERROR_EMPTY_LABEL; |
23 |
| -use const IDNA_ERROR_HYPHEN_3_4; |
24 |
| -use const IDNA_ERROR_INVALID_ACE_LABEL; |
25 |
| -use const IDNA_ERROR_LABEL_HAS_DOT; |
26 |
| -use const IDNA_ERROR_LABEL_TOO_LONG; |
27 |
| -use const IDNA_ERROR_LEADING_COMBINING_MARK; |
28 |
| -use const IDNA_ERROR_LEADING_HYPHEN; |
29 |
| -use const IDNA_ERROR_PUNYCODE; |
30 |
| -use const IDNA_ERROR_TRAILING_HYPHEN; |
31 | 16 | use const IDNA_NONTRANSITIONAL_TO_ASCII;
|
32 | 17 | use const IDNA_NONTRANSITIONAL_TO_UNICODE;
|
33 | 18 | use const IDNA_USE_STD3_RULES;
|
34 | 19 | use const INTL_IDNA_VARIANT_UTS46;
|
35 | 20 |
|
36 | 21 | final class IntlIdna
|
37 | 22 | {
|
38 |
| - public const IDNA2008_ASCII_OPTIONS = IDNA_NONTRANSITIONAL_TO_ASCII |
| 23 | + public const IDNA2008_ASCII = IDNA_NONTRANSITIONAL_TO_ASCII |
39 | 24 | | IDNA_CHECK_BIDI
|
40 | 25 | | IDNA_USE_STD3_RULES
|
41 | 26 | | IDNA_CHECK_CONTEXTJ;
|
42 | 27 |
|
43 |
| - public const IDNA2008_UNICODE_OPTIONS = IDNA_NONTRANSITIONAL_TO_UNICODE |
| 28 | + public const IDNA2008_UNICODE = IDNA_NONTRANSITIONAL_TO_UNICODE |
44 | 29 | | IDNA_CHECK_BIDI
|
45 | 30 | | IDNA_USE_STD3_RULES
|
46 | 31 | | IDNA_CHECK_CONTEXTJ;
|
47 | 32 |
|
48 |
| - public const IDNA2003_ASCII_OPTIONS = IDNA_DEFAULT; |
49 |
| - public const IDNA2003_UNICODE_OPTIONS = IDNA_DEFAULT; |
50 |
| - |
51 |
| - /** |
52 |
| - * IDNA errors. |
53 |
| - * |
54 |
| - * @see http://icu-project.org/apiref/icu4j/com/ibm/icu/text/IDNA.Error.html |
55 |
| - */ |
56 |
| - private const IDNA_ERRORS = [ |
57 |
| - IDNA_ERROR_EMPTY_LABEL => 'a non-final domain name label (or the whole domain name) is empty', |
58 |
| - IDNA_ERROR_LABEL_TOO_LONG => 'a domain name label is longer than 63 bytes', |
59 |
| - IDNA_ERROR_DOMAIN_NAME_TOO_LONG => 'a domain name is longer than 255 bytes in its storage form', |
60 |
| - IDNA_ERROR_LEADING_HYPHEN => 'a label starts with a hyphen-minus ("-")', |
61 |
| - IDNA_ERROR_TRAILING_HYPHEN => 'a label ends with a hyphen-minus ("-")', |
62 |
| - IDNA_ERROR_HYPHEN_3_4 => 'a label contains hyphen-minus ("-") in the third and fourth positions', |
63 |
| - IDNA_ERROR_LEADING_COMBINING_MARK => 'a label starts with a combining mark', |
64 |
| - IDNA_ERROR_DISALLOWED => 'a label or domain name contains disallowed characters', |
65 |
| - IDNA_ERROR_PUNYCODE => 'a label starts with "xn--" but does not contain valid Punycode', |
66 |
| - IDNA_ERROR_LABEL_HAS_DOT => 'a label contains a dot=full stop', |
67 |
| - IDNA_ERROR_INVALID_ACE_LABEL => 'An ACE label does not contain a valid label string', |
68 |
| - IDNA_ERROR_BIDI => 'a label does not meet the IDNA BiDi requirements (for right-to-left characters)', |
69 |
| - IDNA_ERROR_CONTEXTJ => 'a label does not meet the IDNA CONTEXTJ requirements', |
70 |
| - ]; |
| 33 | + public const IDNA2003_ASCII = IDNA_DEFAULT; |
| 34 | + public const IDNA2003_UNICODE = IDNA_DEFAULT; |
71 | 35 |
|
72 | 36 | private const REGEXP_IDNA_PATTERN = '/[^\x20-\x7f]/';
|
73 | 37 |
|
74 |
| - /** |
75 |
| - * Get and format IDN conversion error message. |
76 |
| - */ |
77 |
| - private static function getIDNAErrors(int $errorByte): string |
78 |
| - { |
79 |
| - $res = []; |
80 |
| - foreach (self::IDNA_ERRORS as $error => $reason) { |
81 |
| - if ($error === ($errorByte & $error)) { |
82 |
| - $res[] = $reason; |
83 |
| - } |
84 |
| - } |
85 |
| - |
86 |
| - return [] === $res ? 'Unknown IDNA conversion error.' : implode(', ', $res).'.'; |
87 |
| - } |
88 |
| - |
89 | 38 | /**
|
90 | 39 | * Converts the input to its IDNA ASCII form.
|
91 | 40 | *
|
92 | 41 | * This method returns the string converted to IDN ASCII form
|
93 | 42 | *
|
94 | 43 | * @throws SyntaxError if the string can not be converted to ASCII using IDN UTS46 algorithm
|
95 | 44 | */
|
96 |
| - public static function toAscii(string $domain, int $option): string |
| 45 | + public static function toAscii(string $domain, int $option): IdnaResult |
97 | 46 | {
|
98 | 47 | $domain = rawurldecode($domain);
|
99 | 48 | if (1 !== preg_match(self::REGEXP_IDNA_PATTERN, $domain)) {
|
100 |
| - return strtolower($domain); |
101 |
| - } |
102 |
| - |
103 |
| - $output = idn_to_ascii($domain, $option, INTL_IDNA_VARIANT_UTS46, $infos); |
104 |
| - if ([] === $infos) { |
105 |
| - throw SyntaxError::dueToIDNAError($domain); |
106 |
| - } |
107 |
| - |
108 |
| - if (0 !== $infos['errors']) { |
109 |
| - throw SyntaxError::dueToIDNAError($domain, self::getIDNAErrors($infos['errors'])); |
| 49 | + return IdnaResult::fromIntl([ |
| 50 | + 'result' => strtolower($domain), |
| 51 | + 'isTransitionalDifferent' => false, |
| 52 | + 'errors' => 0, |
| 53 | + ]); |
110 | 54 | }
|
111 | 55 |
|
112 |
| - // @codeCoverageIgnoreStart |
113 |
| - if (false === $output) { |
114 |
| - throw new UnexpectedValueException('The Intl extension is misconfigured for '.PHP_OS.', please correct this issue before proceeding.'); |
115 |
| - } |
116 |
| - // @codeCoverageIgnoreEnd |
117 |
| - |
118 |
| - if (false === strpos($output, '%')) { |
119 |
| - return $output; |
120 |
| - } |
| 56 | + idn_to_ascii($domain, $option, INTL_IDNA_VARIANT_UTS46, $infos); |
121 | 57 |
|
122 |
| - throw SyntaxError::dueToInvalidCharacters($domain); |
| 58 | + return self::createIdnaResult($domain, $infos); |
123 | 59 | }
|
124 | 60 |
|
125 | 61 | /**
|
126 | 62 | * Converts the input to its IDNA UNICODE form.
|
127 | 63 | *
|
128 | 64 | * This method returns the string converted to IDN UNICODE form
|
129 | 65 | *
|
130 |
| - * @throws SyntaxError if the string can not be converted to UNICODE using IDN UTS46 algorithm |
131 |
| - * @throws UnexpectedValueException if the intl extension is misconfigured |
| 66 | + * @throws SyntaxError if the string can not be converted to UNICODE using IDN UTS46 algorithm |
132 | 67 | */
|
133 |
| - public static function toUnicode(string $domain, int $option): string |
| 68 | + public static function toUnicode(string $domain, int $option): IdnaResult |
134 | 69 | {
|
135 | 70 | if (false === strpos($domain, 'xn--')) {
|
136 |
| - return $domain; |
| 71 | + return IdnaResult::fromIntl([ |
| 72 | + 'result' => $domain, |
| 73 | + 'isTransitionalDifferent' => false, |
| 74 | + 'errors' => 0, |
| 75 | + ]); |
137 | 76 | }
|
138 | 77 |
|
139 |
| - $output = idn_to_utf8($domain, $option, INTL_IDNA_VARIANT_UTS46, $info); |
140 |
| - if ([] === $info) { |
141 |
| - throw SyntaxError::dueToIDNAError($domain); |
142 |
| - } |
| 78 | + idn_to_utf8($domain, $option, INTL_IDNA_VARIANT_UTS46, $infos); |
143 | 79 |
|
144 |
| - if (0 !== $info['errors']) { |
145 |
| - throw SyntaxError::dueToIDNAError($domain, self::getIDNAErrors($info['errors'])); |
146 |
| - } |
| 80 | + return self::createIdnaResult($domain, $infos); |
| 81 | + } |
147 | 82 |
|
148 |
| - // @codeCoverageIgnoreStart |
149 |
| - if (false === $output) { |
150 |
| - throw new UnexpectedValueException('The Intl extension for '.PHP_OS.' is misconfigured. Please correct this issue before proceeding.'); |
| 83 | + /** |
| 84 | + * @param array{result:string, isTransitionalDifferent:bool, errors:int} $infos |
| 85 | + */ |
| 86 | + private static function createIdnaResult(string $domain, array $infos): IdnaResult |
| 87 | + { |
| 88 | + $result = IdnaResult::fromIntl($infos); |
| 89 | + if ([] !== $result->errors()) { |
| 90 | + throw SyntaxError::dueToIDNAError($domain, $result); |
151 | 91 | }
|
152 |
| - // @codeCoverageIgnoreEnd |
153 | 92 |
|
154 |
| - return $output; |
| 93 | + return $result; |
155 | 94 | }
|
156 | 95 | }
|
0 commit comments