1const BASE: i32 = 36;
2const TMIN: i32 = 1;
3const TMAX: i32 = 26;
4const SKEW: i32 = 38;
5const DAMP: i32 = 700;
6const INITIAL_BIAS: i32 = 72;
7const INITIAL_N: u32 = 128;
8
9fn char_to_digit_value(value: u8) -> i32 {
10 match value {
11 b'a'..=b'z' => (value - b'a') as i32,
12 b'0'..=b'9' => (value - b'0') as i32 + 26,
13 _ => -1,
14 }
15}
16
17fn digit_to_char(digit: i32) -> u8 {
18 if digit < 26 {
19 (digit + 97) as u8
20 } else {
21 (digit + 22) as u8
22 }
23}
24
25fn adapt(mut d: i32, n: i32, firsttime: bool) -> i32 {
26 d = if firsttime { d / DAMP } else { d / 2 };
27 d += d / n;
28 let mut k = 0;
29 while d > ((BASE - TMIN) * TMAX) / 2 {
30 d /= BASE - TMIN;
31 k += BASE;
32 }
33 k + (((BASE - TMIN + 1) * d) / (d + SKEW))
34}
35
36pub fn punycode_to_utf32(input: &str) -> Option<Vec<u32>> {
37 if input.starts_with("xn--") {
39 return None;
40 }
41
42 let mut written_out = 0i32;
43 let mut out = Vec::new();
44 let mut n = INITIAL_N;
45 let mut i = 0i32;
46 let mut bias = INITIAL_BIAS;
47
48 let mut input_bytes = input.as_bytes();
49
50 if let Some(end_of_ascii) = input_bytes.iter().rposition(|&b| b == b'-') {
52 for &c in &input_bytes[..end_of_ascii] {
53 if c >= 0x80 {
54 return None;
55 }
56 out.push(c as u32);
57 written_out += 1;
58 }
59 input_bytes = &input_bytes[end_of_ascii + 1..];
60 }
61
62 let mut pos = 0;
63 while pos < input_bytes.len() {
64 let oldi = i;
65 let mut w = 1i32;
66 let k = BASE;
67 loop {
68 if pos >= input_bytes.len() {
69 return None;
70 }
71 let code_point = input_bytes[pos];
72 pos += 1;
73 let digit = char_to_digit_value(code_point);
74 if digit < 0 {
75 return None;
76 }
77 if digit > (0x7fffffff - i) / w {
78 return None;
79 }
80 i += digit * w;
81 let t = if k <= bias {
82 TMIN
83 } else if k >= bias + TMAX {
84 TMAX
85 } else {
86 k - bias
87 };
88 if digit < t {
89 break;
90 }
91 if w > 0x7fffffff / (BASE - t) {
92 return None;
93 }
94 w *= BASE - t;
95 }
96 bias = adapt(i - oldi, written_out + 1, oldi == 0);
97 if i / (written_out + 1) > (0x7fffffff - n as i32) {
98 return None;
99 }
100 n += (i / (written_out + 1)) as u32;
101 i %= written_out + 1;
102 if n < 0x80 {
103 return None;
104 }
105 out.insert(i as usize, n);
106 written_out += 1;
107 i += 1;
108 }
109
110 Some(out)
111}
112
113pub fn verify_punycode(input: &str) -> bool {
114 if input.starts_with("xn--") {
115 return false;
116 }
117
118 let mut written_out = 0usize;
119 let mut n = INITIAL_N;
120 let mut i = 0i32;
121 let mut bias = INITIAL_BIAS;
122
123 let mut input_bytes = input.as_bytes();
124
125 if let Some(end_of_ascii) = input_bytes.iter().rposition(|&b| b == b'-') {
127 for &c in &input_bytes[..end_of_ascii] {
128 if c >= 0x80 {
129 return false;
130 }
131 written_out += 1;
132 }
133 input_bytes = &input_bytes[end_of_ascii + 1..];
134 }
135
136 let mut pos = 0;
137 while pos < input_bytes.len() {
138 let oldi = i;
139 let mut w = 1i32;
140 let k = BASE;
141 loop {
142 if pos >= input_bytes.len() {
143 return false;
144 }
145 let code_point = input_bytes[pos];
146 pos += 1;
147 let digit = char_to_digit_value(code_point);
148 if digit < 0 {
149 return false;
150 }
151 if digit > (0x7fffffff - i) / w {
152 return false;
153 }
154 i += digit * w;
155 let t = if k <= bias {
156 TMIN
157 } else if k >= bias + TMAX {
158 TMAX
159 } else {
160 k - bias
161 };
162 if digit < t {
163 break;
164 }
165 if w > 0x7fffffff / (BASE - t) {
166 return false;
167 }
168 w *= BASE - t;
169 }
170 bias = adapt(i - oldi, (written_out + 1) as i32, oldi == 0);
171 if i / (written_out + 1) as i32 > (0x7fffffff_u32 - n) as i32 {
172 return false;
173 }
174 n += (i / (written_out + 1) as i32) as u32;
175 i %= (written_out + 1) as i32;
176 if n < 0x80 {
177 return false;
178 }
179 written_out += 1;
180 i += 1;
181 }
182
183 true
184}
185
186pub fn utf32_to_punycode(input: &[u32]) -> Option<String> {
187 let mut out = Vec::new();
188 let mut n = INITIAL_N;
189 let mut d = 0i32;
190 let mut bias = INITIAL_BIAS;
191 let mut h = 0usize;
192
193 for &c in input {
195 if c < 0x80 {
196 h += 1;
197 out.push(c as u8);
198 }
199 if c > 0x10ffff || (0xd800..0xe000).contains(&c) {
200 return None;
201 }
202 }
203 let b = h;
204 if b > 0 {
205 out.push(b'-');
206 }
207
208 while h < input.len() {
209 let mut m = 0x10FFFF;
210 for &code_point in input {
211 if code_point >= n && code_point < m {
212 m = code_point;
213 }
214 }
215
216 if (m - n) > ((0x7fffffff_u32 - d as u32) / (h + 1) as u32) {
217 return None;
218 }
219 d += ((m - n) * (h + 1) as u32) as i32;
220 n = m;
221
222 for &c in input {
223 if c < n {
224 if d == 0x7fffffff {
225 return None;
226 }
227 d += 1;
228 }
229 if c == n {
230 let mut q = d;
231 let k = BASE;
232 loop {
233 let t = if k <= bias {
234 TMIN
235 } else if k >= bias + TMAX {
236 TMAX
237 } else {
238 k - bias
239 };
240
241 if q < t {
242 break;
243 }
244 out.push(digit_to_char(t + ((q - t) % (BASE - t))));
245 q = (q - t) / (BASE - t);
246 }
247 out.push(digit_to_char(q));
248 bias = adapt(d, (h + 1) as i32, h == b);
249 d = 0;
250 h += 1;
251 }
252 }
253 d += 1;
254 n += 1;
255 }
256
257 String::from_utf8(out).ok()
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_punycode_encoding() {
266 let input = vec![0x00E4];
267 let result = utf32_to_punycode(&input);
268 assert!(result.is_some());
269 let encoded = result.unwrap();
270 assert_eq!(encoded, "4ca");
271 }
272
273 #[test]
274 fn test_punycode_decoding() {
275 let input = vec![0x00E4];
276 let encoded = utf32_to_punycode(&input).unwrap();
277 let decoded = punycode_to_utf32(&encoded);
278 assert_eq!(decoded, Some(vec![0x00E4]));
279 }
280
281 #[test]
282 fn test_verify_punycode() {
283 assert!(verify_punycode("4ca"));
284 assert!(verify_punycode(""));
286 }
287
288 #[test]
289 fn test_xn_prefix_rejection() {
290 assert_eq!(punycode_to_utf32("xn--test"), None);
292 assert!(!verify_punycode("xn--test"));
293 }
294}