Idn.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <?php
  2. /*
  3. * Copyright (c) 2014 TrueServer B.V.
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is furnished
  10. * to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in all
  13. * copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. * THE SOFTWARE.
  22. *
  23. * Originally forked from
  24. * https://github.com/true/php-punycode/blob/v2.1.1/src/Punycode.php
  25. */
  26. namespace Symfony\Polyfill\Intl\Idn;
  27. /**
  28. * Partial intl implementation in pure PHP.
  29. *
  30. * Implemented:
  31. * - idn_to_ascii - Convert domain name to IDNA ASCII form
  32. * - idn_to_utf8 - Convert domain name from IDNA ASCII to Unicode
  33. *
  34. * @author Renan Gonçalves <renan.saddam@gmail.com>
  35. * @author Sebastian Kroczek <sk@xbug.de>
  36. * @author Dmitry Lukashin <dmitry@lukashin.ru>
  37. * @author Laurent Bassin <laurent@bassin.info>
  38. *
  39. * @internal
  40. */
  41. final class Idn
  42. {
  43. const INTL_IDNA_VARIANT_2003 = 0;
  44. const INTL_IDNA_VARIANT_UTS46 = 1;
  45. private static $encodeTable = array(
  46. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
  47. 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
  48. 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  49. );
  50. private static $decodeTable = array(
  51. 'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5,
  52. 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11,
  53. 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17,
  54. 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23,
  55. 'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29,
  56. '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35,
  57. );
  58. public static function idn_to_ascii($domain, $options, $variant, &$idna_info = array())
  59. {
  60. if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) {
  61. @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED);
  62. }
  63. if (self::INTL_IDNA_VARIANT_UTS46 === $variant) {
  64. $domain = mb_strtolower($domain, 'utf-8');
  65. }
  66. $parts = explode('.', $domain);
  67. foreach ($parts as $i => &$part) {
  68. if ('' === $part && \count($parts) > 1 + $i) {
  69. return false;
  70. }
  71. if (false === $part = self::encodePart($part)) {
  72. return false;
  73. }
  74. }
  75. $output = implode('.', $parts);
  76. $idna_info = array(
  77. 'result' => \strlen($output) > 255 ? false : $output,
  78. 'isTransitionalDifferent' => false,
  79. 'errors' => 0,
  80. );
  81. return $idna_info['result'];
  82. }
  83. public static function idn_to_utf8($domain, $options, $variant, &$idna_info = array())
  84. {
  85. if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) {
  86. @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED);
  87. }
  88. $parts = explode('.', $domain);
  89. foreach ($parts as &$part) {
  90. $length = \strlen($part);
  91. if ($length < 1 || 63 < $length) {
  92. continue;
  93. }
  94. if (0 !== strpos($part, 'xn--')) {
  95. continue;
  96. }
  97. $part = substr($part, 4);
  98. $part = self::decodePart($part);
  99. }
  100. $output = implode('.', $parts);
  101. $idna_info = array(
  102. 'result' => \strlen($output) > 255 ? false : $output,
  103. 'isTransitionalDifferent' => false,
  104. 'errors' => 0,
  105. );
  106. return $idna_info['result'];
  107. }
  108. private static function encodePart($input)
  109. {
  110. $codePoints = self::listCodePoints($input);
  111. $n = 128;
  112. $bias = 72;
  113. $delta = 0;
  114. $h = $b = \count($codePoints['basic']);
  115. $output = '';
  116. foreach ($codePoints['basic'] as $code) {
  117. $output .= mb_chr($code, 'utf-8');
  118. }
  119. if ($input === $output) {
  120. return $output;
  121. }
  122. if ($b > 0) {
  123. $output .= '-';
  124. }
  125. $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']);
  126. sort($codePoints['nonBasic']);
  127. $i = 0;
  128. $length = mb_strlen($input, 'utf-8');
  129. while ($h < $length) {
  130. $m = $codePoints['nonBasic'][$i++];
  131. $delta += ($m - $n) * ($h + 1);
  132. $n = $m;
  133. foreach ($codePoints['all'] as $c) {
  134. if ($c < $n || $c < 128) {
  135. ++$delta;
  136. }
  137. if ($c === $n) {
  138. $q = $delta;
  139. for ($k = 36;; $k += 36) {
  140. $t = self::calculateThreshold($k, $bias);
  141. if ($q < $t) {
  142. break;
  143. }
  144. $code = $t + (($q - $t) % (36 - $t));
  145. $output .= self::$encodeTable[$code];
  146. $q = ($q - $t) / (36 - $t);
  147. }
  148. $output .= self::$encodeTable[$q];
  149. $bias = self::adapt($delta, $h + 1, ($h === $b));
  150. $delta = 0;
  151. ++$h;
  152. }
  153. }
  154. ++$delta;
  155. ++$n;
  156. }
  157. $output = 'xn--'.$output;
  158. return \strlen($output) < 1 || 63 < \strlen($output) ? false : strtolower($output);
  159. }
  160. private static function listCodePoints($input)
  161. {
  162. $codePoints = array(
  163. 'all' => array(),
  164. 'basic' => array(),
  165. 'nonBasic' => array(),
  166. );
  167. $length = mb_strlen($input, 'utf-8');
  168. for ($i = 0; $i < $length; ++$i) {
  169. $char = mb_substr($input, $i, 1, 'utf-8');
  170. $code = mb_ord($char, 'utf-8');
  171. if ($code < 128) {
  172. $codePoints['all'][] = $codePoints['basic'][] = $code;
  173. } else {
  174. $codePoints['all'][] = $codePoints['nonBasic'][] = $code;
  175. }
  176. }
  177. return $codePoints;
  178. }
  179. private static function calculateThreshold($k, $bias)
  180. {
  181. if ($k <= $bias + 1) {
  182. return 1;
  183. }
  184. if ($k >= $bias + 26) {
  185. return 26;
  186. }
  187. return $k - $bias;
  188. }
  189. private static function adapt($delta, $numPoints, $firstTime)
  190. {
  191. $delta = (int) ($firstTime ? $delta / 700 : $delta / 2);
  192. $delta += (int) ($delta / $numPoints);
  193. $k = 0;
  194. while ($delta > 35 * 13) {
  195. $delta = (int) ($delta / 35);
  196. $k = $k + 36;
  197. }
  198. return $k + (int) (36 * $delta / ($delta + 38));
  199. }
  200. private static function decodePart($input)
  201. {
  202. $n = 128;
  203. $i = 0;
  204. $bias = 72;
  205. $output = '';
  206. $pos = strrpos($input, '-');
  207. if (false !== $pos) {
  208. $output = substr($input, 0, $pos++);
  209. } else {
  210. $pos = 0;
  211. }
  212. $outputLength = \strlen($output);
  213. $inputLength = \strlen($input);
  214. while ($pos < $inputLength) {
  215. $oldi = $i;
  216. $w = 1;
  217. for ($k = 36;; $k += 36) {
  218. $digit = self::$decodeTable[$input[$pos++]];
  219. $i += $digit * $w;
  220. $t = self::calculateThreshold($k, $bias);
  221. if ($digit < $t) {
  222. break;
  223. }
  224. $w *= 36 - $t;
  225. }
  226. $bias = self::adapt($i - $oldi, ++$outputLength, 0 === $oldi);
  227. $n = $n + (int) ($i / $outputLength);
  228. $i = $i % $outputLength;
  229. $output = mb_substr($output, 0, $i, 'utf-8').mb_chr($n, 'utf-8').mb_substr($output, $i, $outputLength - 1, 'utf-8');
  230. ++$i;
  231. }
  232. return $output;
  233. }
  234. }