Line data Source code
1 : // Copyright (C) 2020-2026 Free Software Foundation, Inc.
2 :
3 : // This file is part of GCC.
4 :
5 : // GCC is free software; you can redistribute it and/or modify it under
6 : // the terms of the GNU General Public License as published by the Free
7 : // Software Foundation; either version 3, or (at your option) any later
8 : // version.
9 :
10 : // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11 : // WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 : // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 : // for more details.
14 :
15 : // You should have received a copy of the GNU General Public License
16 : // along with GCC; see the file COPYING3. If not see
17 : // <http://www.gnu.org/licenses/>.
18 :
19 : // This file provides functions for punycode conversion
20 : // See https://datatracker.ietf.org/doc/html/rfc3492
21 :
22 : #include "rust-system.h"
23 : #include "rust-unicode.h"
24 : #include "optional.h"
25 : #include "selftest.h"
26 :
27 : namespace Rust {
28 :
29 : // https://tools.ietf.org/html/rfc3492#section-4.
30 : constexpr uint32_t BASE = 36;
31 : constexpr uint32_t TMIN = 1;
32 : constexpr uint32_t TMAX = 26;
33 : constexpr uint32_t SKEW = 38;
34 : constexpr uint32_t DAMP = 700;
35 : constexpr uint32_t INITIAL_BIAS = 72;
36 : constexpr uint32_t INITIAL_N = 128;
37 : constexpr char DELIMITER = '-';
38 :
39 : std::string
40 63 : extract_basic_string (const std::vector<Codepoint> &src)
41 : {
42 63 : std::string basic_string;
43 534 : for (auto c : src)
44 : {
45 471 : if (c.is_ascii ())
46 824 : basic_string += c.as_string ();
47 : }
48 63 : return basic_string;
49 : }
50 :
51 : uint32_t
52 59 : adapt_bias (uint32_t delta, const uint32_t n_points, const bool is_first)
53 : {
54 59 : delta /= is_first ? DAMP : 2;
55 59 : delta += delta / n_points;
56 59 : uint32_t k = 0;
57 :
58 81 : while (delta > (BASE - TMIN) * TMAX / 2)
59 : {
60 22 : delta /= BASE - TMIN;
61 22 : k += BASE;
62 : }
63 59 : return k + (BASE - TMIN + 1) * delta / (delta + SKEW);
64 : }
65 :
66 : uint32_t
67 139 : clamped_sub (const uint32_t min, const uint32_t lhs, const uint32_t rhs,
68 : const uint32_t max)
69 : {
70 139 : if (min + rhs >= lhs)
71 : return min;
72 105 : else if (max + rhs <= lhs)
73 : return max;
74 : else
75 26 : return lhs - rhs;
76 : }
77 :
78 : uint32_t
79 54 : min_gt_or_eq (const std::vector<Codepoint> &l, const uint32_t threshold)
80 : {
81 54 : uint32_t min = UINT32_MAX;
82 575 : for (auto c : l)
83 521 : if (c.value >= threshold && c.value < min)
84 521 : min = c.value;
85 54 : return min;
86 : }
87 :
88 : char
89 139 : encode_digit (const uint32_t d)
90 : {
91 139 : return d + 22 + (d < 26 ? 75 : 0);
92 : }
93 :
94 : tl::optional<std::string>
95 63 : encode_punycode (const Utf8String &input)
96 : {
97 63 : std::vector<Codepoint> input_chars = input.get_chars ();
98 :
99 63 : uint32_t n = INITIAL_N;
100 63 : uint32_t delta = 0;
101 63 : uint32_t bias = INITIAL_BIAS;
102 :
103 63 : std::string output = extract_basic_string (input_chars);
104 63 : uint32_t h = output.size ();
105 63 : const uint32_t b = h;
106 63 : if (b > 0)
107 55 : output += DELIMITER;
108 :
109 117 : while (h < input_chars.size ())
110 : {
111 54 : const uint32_t m = min_gt_or_eq (input_chars, n);
112 :
113 54 : if (m - n > ((UINT32_MAX - delta) / (h + 1)))
114 0 : return tl::nullopt;
115 :
116 54 : delta += (m - n) * (h + 1);
117 54 : n = m;
118 :
119 575 : for (const auto c : input_chars)
120 : {
121 521 : if (c.value < n)
122 252 : delta++;
123 269 : else if (c.value == n)
124 : {
125 : uint32_t q = delta;
126 : // encode as a variable length integer
127 80 : for (uint32_t k = 1;; k++)
128 : {
129 139 : const uint32_t kb = k * BASE;
130 139 : const uint32_t t = clamped_sub (TMIN, kb, bias, TMAX);
131 139 : if (q < t)
132 : break;
133 :
134 80 : output += encode_digit (t + (q - t) % (BASE - t));
135 80 : q = (q - t) / (BASE - t);
136 80 : }
137 59 : output += encode_digit (q);
138 :
139 59 : bias = adapt_bias (delta, h + 1, h == b);
140 59 : delta = 0;
141 59 : h++;
142 : }
143 : }
144 54 : delta++;
145 54 : n++;
146 : }
147 :
148 126 : return {output};
149 63 : }
150 :
151 : } // namespace Rust
152 :
153 : #if CHECKING_P
154 :
155 : namespace selftest {
156 :
157 : void
158 7 : encode_assert (const std::string &input, const std::string &expected)
159 : {
160 7 : Rust::Utf8String input_utf8
161 7 : = Rust::Utf8String::make_utf8_string (input).value ();
162 7 : std::string actual = Rust::encode_punycode (input_utf8).value ();
163 7 : ASSERT_EQ (actual, expected);
164 7 : }
165 :
166 : void
167 1 : rust_punycode_encode_test ()
168 : {
169 1 : encode_assert ("abc", "abc-");
170 1 : encode_assert ("12345", "12345-");
171 1 : encode_assert ("香港", "j6w193g");
172 :
173 : // Examples from https://datatracker.ietf.org/doc/html/rfc3492#section-7.1
174 1 : encode_assert ("ليهمابتكلموشعربي؟", "egbpdaj6bu4bxfgehfvwxn");
175 1 : encode_assert ("他们为什么不说中文", "ihqwcrb4cv8a8dqg056pqjye");
176 1 : encode_assert ("他們爲什麽不說中文", "ihqwctvzc91f659drss3x8bo0yb");
177 1 : encode_assert ("Pročprostěnemluvíčesky", "Proprostnemluvesky-uyb24dma41a");
178 1 : }
179 :
180 : } // namespace selftest
181 :
182 : #endif // CHECKING_P
|