Idn.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. private static $encodeTable = array(
  44. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
  45. 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
  46. 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  47. );
  48. private static $decodeTable = array(
  49. 'a' => 0, 'b' => 1, 'c' => 2, 'd' => 3, 'e' => 4, 'f' => 5,
  50. 'g' => 6, 'h' => 7, 'i' => 8, 'j' => 9, 'k' => 10, 'l' => 11,
  51. 'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17,
  52. 's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23,
  53. 'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29,
  54. '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35,
  55. );
  56. public static function idn_to_ascii($domain, $options, $variant, &$idna_info = array())
  57. {
  58. if (\PHP_VERSION_ID >= 70200 && INTL_IDNA_VARIANT_2003 === $variant) {
  59. @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED);
  60. }
  61. if (INTL_IDNA_VARIANT_UTS46 === $variant) {
  62. $domain = mb_strtolower($domain, 'utf-8');
  63. }
  64. $parts = explode('.', $domain);
  65. foreach ($parts as &$part) {
  66. if ('' === $part) {
  67. return false;
  68. }
  69. if (false === $part = self::encodePart($part)) {
  70. return false;
  71. }
  72. }
  73. $output = implode('.', $parts);
  74. $idna_info = array(
  75. 'result' => \strlen($output) > 255 ? false : $output,
  76. 'isTransitionalDifferent' => false,
  77. 'errors' => 0,
  78. );
  79. return $idna_info['result'];
  80. }
  81. public static function idn_to_utf8($domain, $options, $variant, &$idna_info = array())
  82. {
  83. if (\PHP_VERSION_ID >= 70200 && INTL_IDNA_VARIANT_2003 === $variant) {
  84. @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', E_USER_DEPRECATED);
  85. }
  86. $parts = explode('.', $domain);
  87. foreach ($parts as &$part) {
  88. $length = \strlen($part);
  89. if ($length < 1 || 63 < $length) {
  90. continue;
  91. }
  92. if (0 !== strpos($part, 'xn--')) {
  93. continue;
  94. }
  95. $part = substr($part, 4);
  96. $part = self::decodePart($part);
  97. }
  98. $output = implode('.', $parts);
  99. $idna_info = array(
  100. 'result' => \strlen($output) > 255 ? false : $output,
  101. 'isTransitionalDifferent' => false,
  102. 'errors' => 0,
  103. );
  104. return $idna_info['result'];
  105. }
  106. private static function encodePart($input)
  107. {
  108. $codePoints = self::listCodePoints($input);
  109. $n = 128;
  110. $bias = 72;
  111. $delta = 0;
  112. $h = $b = \count($codePoints['basic']);
  113. $output = '';
  114. foreach ($codePoints['basic'] as $code) {
  115. $output .= mb_chr($code, 'utf-8');
  116. }
  117. if ($input === $output) {
  118. return $output;
  119. }
  120. if ($b > 0) {
  121. $output .= '-';
  122. }
  123. $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']);
  124. sort($codePoints['nonBasic']);
  125. $i = 0;
  126. $length = mb_strlen($input, 'utf-8');
  127. while ($h < $length) {
  128. $m = $codePoints['nonBasic'][$i++];
  129. $delta += ($m - $n) * ($h + 1);
  130. $n = $m;
  131. foreach ($codePoints['all'] as $c) {
  132. if ($c < $n || $c < 128) {
  133. ++$delta;
  134. }
  135. if ($c === $n) {
  136. $q = $delta;
  137. for ($k = 36;; $k += 36) {
  138. $t = self::calculateThreshold($k, $bias);
  139. if ($q < $t) {
  140. break;
  141. }
  142. $code = $t + (($q - $t) % (36 - $t));
  143. $output .= self::$encodeTable[$code];
  144. $q = ($q - $t) / (36 - $t);
  145. }
  146. $output .= self::$encodeTable[$q];
  147. $bias = self::adapt($delta, $h + 1, ($h === $b));
  148. $delta = 0;
  149. ++$h;
  150. }
  151. }
  152. ++$delta;
  153. ++$n;
  154. }
  155. $output = 'xn--'.$output;
  156. return \strlen($output) < 1 || 63 < \strlen($output) ? false : strtolower($output);
  157. }
  158. private static function listCodePoints($input)
  159. {
  160. $codePoints = array(
  161. 'all' => array(),
  162. 'basic' => array(),
  163. 'nonBasic' => array(),
  164. );
  165. $length = mb_strlen($input, 'utf-8');
  166. for ($i = 0; $i < $length; ++$i) {
  167. $char = mb_substr($input, $i, 1, 'utf-8');
  168. $code = mb_ord($char, 'utf-8');
  169. if ($code < 128) {
  170. $codePoints['all'][] = $codePoints['basic'][] = $code;
  171. } else {
  172. $codePoints['all'][] = $codePoints['nonBasic'][] = $code;
  173. }
  174. }
  175. return $codePoints;
  176. }
  177. private static function calculateThreshold($k, $bias)
  178. {
  179. if ($k <= $bias + 1) {
  180. return 1;
  181. }
  182. if ($k >= $bias + 26) {
  183. return 26;
  184. }
  185. return $k - $bias;
  186. }
  187. private static function adapt($delta, $numPoints, $firstTime)
  188. {
  189. $delta = (int) ($firstTime ? $delta / 700 : $delta / 2);
  190. $delta += (int) ($delta / $numPoints);
  191. $k = 0;
  192. while ($delta > 35 * 13) {
  193. $delta = (int) ($delta / 35);
  194. $k = $k + 36;
  195. }
  196. return $k + (int) (36 * $delta / ($delta + 38));
  197. }
  198. private static function decodePart($input)
  199. {
  200. $n = 128;
  201. $i = 0;
  202. $bias = 72;
  203. $output = '';
  204. $pos = strrpos($input, '-');
  205. if (false !== $pos) {
  206. $output = substr($input, 0, $pos++);
  207. } else {
  208. $pos = 0;
  209. }
  210. $outputLength = \strlen($output);
  211. $inputLength = \strlen($input);
  212. while ($pos < $inputLength) {
  213. $oldi = $i;
  214. $w = 1;
  215. for ($k = 36;; $k += 36) {
  216. $digit = self::$decodeTable[$input[$pos++]];
  217. $i += $digit * $w;
  218. $t = self::calculateThreshold($k, $bias);
  219. if ($digit < $t) {
  220. break;
  221. }
  222. $w *= 36 - $t;
  223. }
  224. $bias = self::adapt($i - $oldi, ++$outputLength, 0 === $oldi);
  225. $n = $n + (int) ($i / $outputLength);
  226. $i = $i % $outputLength;
  227. $output = mb_substr($output, 0, $i, 'utf-8').mb_chr($n, 'utf-8').mb_substr($output, $i, $outputLength - 1, 'utf-8');
  228. ++$i;
  229. }
  230. return $output;
  231. }
  232. }