y_punycode.inc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /**--------------------------------------------------------------------------**\
  2. ===================================
  3. y_punycode - Character encodings.
  4. ===================================
  5. Description:
  6. Functions for converting unicode strings to and from punycode, to be
  7. represented in just ASCII characters. Based on several public
  8. implementations and the RFC, adapted for PAWN. For more information see:
  9. https://en.wikipedia.org/wiki/Punycode
  10. Also includes a function that hooks the "HTTP" function to allow for
  11. internationalised domain names with that function.
  12. Legal:
  13. Version: MPL 1.1
  14. The contents of this file are subject to the Mozilla Public License Version
  15. 1.1 (the "License"); you may not use this file except in compliance with
  16. the License. You may obtain a copy of the License at
  17. http://www.mozilla.org/MPL/
  18. Software distributed under the License is distributed on an "AS IS" basis,
  19. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  20. for the specific language governing rights and limitations under the
  21. License.
  22. The Original Code is the YSI punycode include.
  23. The Initial Developer of the Original Code is Alex "Y_Less" Cole.
  24. Portions created by the Initial Developer are Copyright (C) 2011
  25. the Initial Developer. All Rights Reserved.
  26. Contributors:
  27. ZeeX, koolk, JoeBullet/Google63, g_aSlice/Slice
  28. Thanks:
  29. JoeBullet/Google63 - Handy arbitrary ASM jump code using SCTRL.
  30. ZeeX - Very productive conversations.
  31. koolk - IsPlayerinAreaEx code.
  32. TheAlpha - Danish translation.
  33. breadfish - German translation.
  34. Fireburn - Dutch translation.
  35. yom - French translation.
  36. 50p - Polish translation.
  37. Zamaroht - Spanish translation.
  38. Dracoblue, sintax, mabako, Xtreme, other coders - Producing other modes
  39. for me to strive to better.
  40. Pixels^ - Running XScripters where the idea was born.
  41. Matite - Pestering me to release it and using it.
  42. Very special thanks to:
  43. Thiadmer - PAWN, whose limits continue to amaze me!
  44. Kye/Kalcor - SA:MP.
  45. SA:MP Team past, present and future - SA:MP.
  46. Version:
  47. 0.1
  48. Changelog:
  49. 29/04/13:
  50. Added Puny_HTTP.
  51. 26/04/13:
  52. First version.
  53. Functions:
  54. Public
  55. -
  56. Core:
  57. -
  58. Stock:
  59. Puny_Encode - Convert a Unicode string to Punycode.
  60. Puny_Decode - Convert a Punycode string to Unicode.
  61. Puny_HTTP - Wrapper for "HTTP" to encode domain names.
  62. Static:
  63. -
  64. Inline:
  65. -
  66. API:
  67. -
  68. Callbacks:
  69. -
  70. Definitions:
  71. -
  72. Enums:
  73. -
  74. Macros:
  75. -
  76. Tags:
  77. -
  78. Variables:
  79. Global:
  80. -
  81. Static:
  82. -
  83. Commands:
  84. -
  85. Compile options:
  86. -
  87. Operators:
  88. -
  89. \**--------------------------------------------------------------------------**/
  90. #if defined _INC_y_punycode
  91. #endinput
  92. #endif
  93. #define _INC_y_punycode
  94. #include "..\YSI_Internal\y_version"
  95. #include "..\YSI_Internal\y_pp"
  96. #include "..\YSI_Core\y_debug"
  97. #if !defined HTTP
  98. #include <a_http>
  99. #endif
  100. #define string:
  101. #define PUNY_BASE (36)
  102. #define PUNY_CHAR ('-')
  103. // Some versions use "-1" or "cellmax", the RFC uses "PUNY_BASE".
  104. #define PUNY_INVL PUNY_BASE
  105. static stock const
  106. PUNY_TMIN = 1,
  107. PUNY_TMAX = 26,
  108. PUNY_SKEW = 38,
  109. PUNY_BIAS = 72,
  110. PUNY_INIT = 128,
  111. PUNY_DAMP = 700,
  112. YSI_gscDecoder[128] =
  113. {
  114. PP_LOOP<48>(PUNY_INVL)(,),
  115. // '0' - '9'.
  116. 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
  117. PP_LOOP<7>(PUNY_INVL)(,),
  118. // 'A' - 'Z'.
  119. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
  120. 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
  121. PP_LOOP<6>(PUNY_INVL)(,),
  122. // 'a' - 'z'.
  123. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
  124. 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
  125. PP_LOOP<5>(PUNY_INVL)(,)
  126. };
  127. /**--------------------------------------------------------------------------**\
  128. <summary>Puny_Decode</summary>
  129. <param name="dst">Where to store the converted string.</param>
  130. <param name="src">The string to convert.</param>
  131. <param name="wlen">The length of the destination.</param>
  132. <param name="delimiter">What character is between the parts.</param>
  133. <returns>
  134. -
  135. </returns>
  136. <remarks>
  137. Takes a punycode string and converts it to unicode.
  138. </remarks>
  139. \**--------------------------------------------------------------------------**/
  140. stock Puny_Decode(string:dst[], string:src[], wlen = sizeof (dst), const delimiter = PUNY_CHAR)
  141. {
  142. new
  143. rlen = strlen(src),
  144. basicEnd = rlen;
  145. while (basicEnd--)
  146. {
  147. if (src[basicEnd] == delimiter) break;
  148. }
  149. // Enough space for the string, and not empty.
  150. if (0 < ++basicEnd < wlen)
  151. {
  152. // Enough space to store the basic string (and no punycode string).
  153. dst[0] = '\0',
  154. strcat(dst, src, basicEnd);
  155. }
  156. else
  157. {
  158. return dst[0] = '\0', strcat(dst, src, wlen), 1;
  159. }
  160. --wlen;
  161. for (
  162. new
  163. n = PUNY_INIT,
  164. bias = PUNY_BIAS,
  165. delta = 0,
  166. codePointsWritten = basicEnd - 1,
  167. pointsRead = basicEnd;
  168. pointsRead != rlen && codePointsWritten != wlen;
  169. )
  170. {
  171. new
  172. oldDelta = delta;
  173. for (new w = 1, k = PUNY_BASE; pointsRead != rlen; k += PUNY_BASE)
  174. {
  175. new
  176. digit = YSI_gscDecoder[src[pointsRead++]];
  177. if (digit == PUNY_BASE || digit > (cellmax - delta) / w) return 0;
  178. delta += digit * w;
  179. new
  180. t = (k <= bias) ? (PUNY_TMIN) : ((k >= bias + PUNY_TMAX) ? (PUNY_TMAX) : (k - bias));
  181. // Find the end of the current code.
  182. if (digit < t) break;
  183. if (w > cellmax / (PUNY_BASE - t)) return 0;
  184. w *= PUNY_BASE - t;
  185. }
  186. bias = Puny_Adapt(delta - oldDelta, ++codePointsWritten, oldDelta == 0);
  187. if (delta / codePointsWritten > cellmax - n) return 0;
  188. static
  189. sTinyString[2];
  190. n += delta / codePointsWritten,
  191. delta %= codePointsWritten,
  192. sTinyString[0] = n,
  193. strins(dst, sTinyString, delta++, wlen + 1);
  194. }
  195. return 1;
  196. }
  197. /**--------------------------------------------------------------------------**\
  198. <summary>Puny_Encode</summary>
  199. <param name="dst">Where to store the converted string.</param>
  200. <param name="src">The string to convert.</param>
  201. <param name="wlen">The length of the destination.</param>
  202. <param name="delimiter">What character to place between the parts.</param>
  203. <returns>
  204. -
  205. </returns>
  206. <remarks>
  207. Takes a unicode string and converts it to punycode.
  208. </remarks>
  209. \**--------------------------------------------------------------------------**/
  210. stock Puny_Encode(string:dst[], string:src[], wlen = sizeof (dst), const delimiter = PUNY_CHAR)
  211. {
  212. new
  213. widx,
  214. rlen = strlen(src);
  215. --wlen;
  216. for (new ridx = 0; ridx != rlen; ++ridx)
  217. {
  218. if ('\0' < src[ridx] <= '~')
  219. {
  220. if (widx == wlen) return (dst[widx] = '\0');
  221. dst[widx++] = src[ridx];
  222. }
  223. }
  224. // Wrote out all the characters.
  225. if (widx == rlen) return (dst[widx] = '\0'), -1;
  226. if (widx < wlen) dst[widx++] = delimiter;
  227. else return (dst[widx] = '\0');
  228. // Set up punycode variables.
  229. for (
  230. new
  231. n = PUNY_INIT,
  232. bias = PUNY_BIAS,
  233. delta = 0,
  234. codePointsWritten = widx - 1,
  235. basicPointsWritten = widx;
  236. codePointsWritten < rlen;
  237. )
  238. {
  239. new
  240. m = cellmax;
  241. for (new ridx = 0; ridx != rlen; ++ridx)
  242. {
  243. if (n <= src[ridx] < m)
  244. {
  245. // Find the lowest Unicode character.
  246. m = src[ridx];
  247. }
  248. }
  249. // Make sure the number isn't too big to encode.
  250. if ((m - n) > (cellmax - delta) / (codePointsWritten + 1)) return (dst[widx] = '\0');
  251. // More punycode state machine.
  252. delta += (m - n) * (codePointsWritten + 1),
  253. n = m;
  254. for (new ridx = 0; ridx != rlen; ++ridx)
  255. {
  256. if (src[ridx] < n)
  257. {
  258. if (++delta == 0) return (dst[widx] = '\0');
  259. }
  260. else if (src[ridx] == n)
  261. {
  262. widx += Puny_EncodeVar(bias, delta, dst[widx], wlen - widx),
  263. ++codePointsWritten,
  264. bias = Puny_Adapt(delta, codePointsWritten, (codePointsWritten == basicPointsWritten)),
  265. delta = 0;
  266. }
  267. }
  268. ++n,
  269. ++delta;
  270. }
  271. return (dst[widx] = '\0'), 1;
  272. }
  273. /**--------------------------------------------------------------------------**\
  274. <summary>Puny_EncodeHash</summary>
  275. <param name="dst">Where to store the converted string.</param>
  276. <param name="src">The string to convert.</param>
  277. <param name="hash">Store the hash value.</param>
  278. <param name="wlen">The length of the destination.</param>
  279. <param name="delimiter">What character to place between the parts.</param>
  280. <returns>
  281. The length of string read.
  282. </returns>
  283. <remarks>
  284. Takes a unicode string and converts it to punycode, while at the same time
  285. generating a Bernstein hash of the string. CASE INSENSITIVE.
  286. </remarks>
  287. \**--------------------------------------------------------------------------**/
  288. stock Puny_EncodeHash(string:dst[], string:src[], &hash, wlen = sizeof (dst), const delimiter = PUNY_CHAR)
  289. {
  290. new
  291. ch,
  292. widx,
  293. rlen,
  294. sSrc[YSI_MAX_STRING],
  295. bPacked = ispacked(src);
  296. --wlen,
  297. hash = -1;
  298. if (bPacked) {
  299. strunpack(sSrc, src);
  300. } else {
  301. strcpy(sSrc, src);
  302. }
  303. for (new bool:bb = true; ; ++rlen)
  304. {
  305. if ((ch = sSrc[rlen]) <= '~')
  306. {
  307. if (ch <= ' ')
  308. {
  309. if (bb)
  310. {
  311. return
  312. dst[widx] = '\0',
  313. rlen;
  314. }
  315. break;
  316. }
  317. ch = tolower(ch),
  318. dst[widx++] = ch,
  319. hash = hash * 33 + ch;
  320. }
  321. else bb = false;
  322. }
  323. // Wrote out all the characters.
  324. if (widx >= wlen) return (bPacked ? strpack(dst, dst, wlen) : 0), (dst[widx] = '\0'), rlen;
  325. dst[widx++] = delimiter,
  326. hash = hash * 33 + delimiter;
  327. // Set up punycode variables.
  328. for (
  329. new
  330. n = PUNY_INIT,
  331. bias = PUNY_BIAS,
  332. delta = 0,
  333. codePointsWritten = widx - 1,
  334. basicPointsWritten = widx;
  335. codePointsWritten < rlen;
  336. )
  337. {
  338. new
  339. m = cellmax;
  340. for (new ridx = 0; ridx != rlen; ++ridx)
  341. {
  342. ch = tolower(sSrc[ridx]);
  343. if (n <= ch < m)
  344. {
  345. // Find the lowest Unicode character.
  346. m = ch;
  347. }
  348. }
  349. // Make sure the number isn't too big to encode.
  350. if ((m - n) > (cellmax - delta) / (codePointsWritten + 1)) return (bPacked ? strpack(dst, dst, wlen) : 0), (dst[widx] = '\0'), rlen;
  351. // More punycode state machine.
  352. delta += (m - n) * (codePointsWritten + 1),
  353. n = m;
  354. for (new ridx = 0; ridx != rlen; ++ridx)
  355. {
  356. ch = tolower(sSrc[ridx]);
  357. if (ch < n)
  358. {
  359. if (++delta == 0) return (bPacked ? strpack(dst, dst, wlen) : 0), (dst[widx] = '\0'), rlen;
  360. }
  361. else if (ch == n)
  362. {
  363. widx += Puny_EncodeVarHash(bias, delta, dst[widx], wlen - widx, hash),
  364. ++codePointsWritten,
  365. bias = Puny_Adapt(delta, codePointsWritten, (codePointsWritten == basicPointsWritten)),
  366. delta = 0;
  367. }
  368. }
  369. ++n,
  370. ++delta;
  371. }
  372. return (bPacked ? strpack(dst, dst, wlen) : 0), (dst[widx] = '\0'), rlen;
  373. }
  374. /**--------------------------------------------------------------------------**\
  375. <summary>_Puny_Basic</summary>
  376. <param name="num">The single number to encode.</param>
  377. <returns>
  378. -
  379. </returns>
  380. <remarks>
  381. Convert a single digit to base 36.
  382. </remarks>
  383. \**--------------------------------------------------------------------------**/
  384. #define _Puny_Basic(%0) (((%0) > 25) ? ((%0) + ('0' - 25)) : ((%0) + 'a'))
  385. /**--------------------------------------------------------------------------**\
  386. <summary>Puny_EncodeVarHash</summary>
  387. <param name="bias">Part of the state machine.</param>
  388. <param name="delta">Part of the state machine.</param>
  389. <param name="dst">Array to write to.</param>
  390. <param name="wlen">Size of the array.</param>
  391. <param name="hash">Hashed string.</param>
  392. <returns>
  393. -
  394. </returns>
  395. <remarks>
  396. This is part of how the punycode algorithm encodes numbers as very clever
  397. strings, but honestly I don't fully understand it!
  398. </remarks>
  399. \**--------------------------------------------------------------------------**/
  400. static stock Puny_EncodeVarHash(bias, delta, dst[], wlen, &hash)
  401. {
  402. new
  403. i = 0,
  404. k = PUNY_BASE,
  405. t;
  406. while (i < wlen)
  407. {
  408. if (k <= bias) t = PUNY_TMIN;
  409. else if (k >= bias + PUNY_TMAX) t = PUNY_TMAX;
  410. else t = k - bias;
  411. // Find the last digit below the threshold.
  412. if (delta < t) break;
  413. new
  414. c = t + (delta - t) % (PUNY_BASE - t);
  415. dst[i] = _Puny_Basic(c),
  416. hash = hash * 33 + dst[i++],
  417. delta = (delta - t) / (PUNY_BASE - t),
  418. k += PUNY_BASE;
  419. }
  420. if (i < wlen)
  421. {
  422. dst[i] = _Puny_Basic(delta),
  423. hash = hash * 33 + dst[i++];
  424. }
  425. return i;
  426. }
  427. /**--------------------------------------------------------------------------**\
  428. <summary>Puny_EncodeVar</summary>
  429. <param name="bias">Part of the state machine.</param>
  430. <param name="delta">Part of the state machine.</param>
  431. <param name="dst">Array to write to.</param>
  432. <param name="wlen">Size of the array.</param>
  433. <returns>
  434. -
  435. </returns>
  436. <remarks>
  437. This is part of how the punycode algorithm encodes numbers as very clever
  438. strings, but honestly I don't fully understand it!
  439. </remarks>
  440. \**--------------------------------------------------------------------------**/
  441. static stock Puny_EncodeVar(bias, delta, dst[], wlen)
  442. {
  443. new
  444. i = 0,
  445. k = PUNY_BASE,
  446. t;
  447. while (i < wlen)
  448. {
  449. if (k <= bias) t = PUNY_TMIN;
  450. else if (k >= bias + PUNY_TMAX) t = PUNY_TMAX;
  451. else t = k - bias;
  452. // Find the last digit below the threshold.
  453. if (delta < t) break;
  454. new
  455. c = t + (delta - t) % (PUNY_BASE - t);
  456. dst[i++] = _Puny_Basic(c),
  457. delta = (delta - t) / (PUNY_BASE - t),
  458. k += PUNY_BASE;
  459. }
  460. if (i < wlen) dst[i++] = _Puny_Basic(delta);
  461. return i;
  462. }
  463. /**--------------------------------------------------------------------------**\
  464. <summary>Puny_Adapt</summary>
  465. <param name="delta">Part of the state machine.</param>
  466. <param name="length">Written string size.</param>
  467. <param name="firstTime">Have special characters already been written?</param>
  468. <returns>
  469. -
  470. </returns>
  471. <remarks>
  472. This is part of how the punycode algorithm encodes numbers as very clever
  473. strings, but honestly I don't fully understand it!
  474. </remarks>
  475. \**--------------------------------------------------------------------------**/
  476. static stock Puny_Adapt(delta, length, bool:firstTime)
  477. {
  478. if (firstTime) delta /= PUNY_DAMP;
  479. else delta >>>= 1;
  480. delta += delta / length;
  481. new
  482. k = 0;
  483. while (delta > (PUNY_BASE - PUNY_TMIN) * PUNY_TMAX >> 1)
  484. {
  485. delta /= PUNY_BASE - PUNY_TMIN,
  486. k += PUNY_BASE;
  487. }
  488. return k + (PUNY_BASE - PUNY_TMIN + 1) * delta / (delta + PUNY_SKEW);
  489. }
  490. /**--------------------------------------------------------------------------**\
  491. <summary>Puny_HTTP</summary>
  492. <param name="index">The HTTP reference index.</param>
  493. <param name="type">How the request should be sent.</param>
  494. <param name="url[]">The (internationalised) URL address.</param>
  495. <param name="data[]">The GET/POST data.</param>
  496. <param name="callback[]">Which function to return the data to.</param>
  497. <returns>
  498. -
  499. </returns>
  500. <remarks>
  501. Hooks the "HTTP" function.
  502. </remarks>
  503. \**--------------------------------------------------------------------------**/
  504. #if defined PUNY_HTTP_HOOK
  505. stock Puny_HTTP(index, type, url[], data[], callback[])
  506. {
  507. static
  508. sPart[64], // Maximum legal domain part length.
  509. sEncoded[256]; // Maximum legal hostname length.
  510. new
  511. idx = strfind(url, !"://");
  512. // Skip any prefix.
  513. if (idx != -1) idx += 2;
  514. // Add the protocol.
  515. sEncoded[0] = '\0',
  516. strcat(sEncoded, url, idx + 2);
  517. // Encode all parts.
  518. new
  519. prev = idx + 1,
  520. end = strfind(url, !"/", false, prev);
  521. if (end == -1) end = strlen(url); // Nothing after the main domain.
  522. do
  523. {
  524. // Find the size of one part.
  525. idx = strfind(url, !".", false, prev);
  526. // Only encode the domain part.
  527. if (!(-1 < idx < end)) idx = end;
  528. static
  529. ch;
  530. // There's no length parameter for "Puny_Encode", so we need a limit.
  531. ch = url[idx],
  532. url[idx] = sPart[0] = '\0';
  533. switch (Puny_Encode(sPart, url[prev]))
  534. {
  535. // Encoding error.
  536. case 0: return 0;
  537. // Encoded something, add the prefix.
  538. case 1:
  539. {
  540. // The hyphen at the start is the only one - no latin chars.
  541. if (sPart[0] == '-' && strfind(sPart, !"-", false, 1) == -1) format(sEncoded, sizeof (sEncoded), "%sxn-%s%c", sEncoded, sPart, ch);
  542. else format(sEncoded, sizeof (sEncoded), "%sxn--%s%c", sEncoded, sPart, ch);
  543. #if defined _DEBUG
  544. #if _DEBUG >= 1
  545. static
  546. sDecoded[64];
  547. Puny_Decode(sDecoded, sPart);
  548. P:5("Puny_HTTP Original: \"%s\", Encoded: \"%s\", Decoded: \"%s\"", url[prev], sPart, sDecoded);
  549. if (strcmp(url[prev], sDecoded)) P:E("Puny_Decode did not match Puny_Encode");
  550. #endif
  551. #endif
  552. }
  553. // No special characters.
  554. case -1: format(sEncoded, sizeof (sEncoded), "%s%s%c", sEncoded, sPart, ch);
  555. }
  556. // Restore the data.
  557. url[idx] = ch,
  558. prev = idx + 1;
  559. }
  560. while (idx < end);
  561. // Add the remainder of the domain.
  562. if (url[end]) strcat(sEncoded, url[end + 1]);
  563. #if defined _DEBUG
  564. P:2("Puny_HTTP Domain: \"%s\" -> \"%s\"", url, sEncoded);
  565. #endif
  566. // Call the original "HTTP".
  567. return HTTP(index, type, sEncoded, data, callback);
  568. }
  569. #if defined _ALS_HTTP
  570. #undef HTTP
  571. #else
  572. native BAD_HTTP(index, type, url[], data[], callback[]) = HTTP;
  573. #define _ALS_HTTP
  574. #endif
  575. #define HTTP Puny_HTTP
  576. #endif
  577. #undef _Puny_Basic
  578. #undef PUNY_BASE
  579. #undef PUNY_CHAR
  580. #if defined YSI_TESTS
  581. #include "..\YSI_Core\y_testing"
  582. #include "y_punycode/tests"
  583. #endif