nodejs/stdlib/
punycode.rs1use crate::host::{with_host, JsObj};
25use fusevm::Value;
26use indexmap::IndexMap;
27
28const BASE: u32 = 36;
30const TMIN: u32 = 1;
31const TMAX: u32 = 26;
32const SKEW: u32 = 38;
33const DAMP: u32 = 700;
34const INITIAL_BIAS: u32 = 72;
35const INITIAL_N: u32 = 128;
36
37const DOTS: &[char] = &['\u{2E}', '\u{3002}', '\u{FF0E}', '\u{FF61}'];
40
41pub const METHODS: &[&str] = &[
42 "encode",
43 "decode",
44 "toASCII",
45 "toUnicode",
46 "ucs2Decode",
47 "ucs2Encode",
48];
49
50pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
51 let input = super::arg_str(args, 0);
52 Some(match method {
53 "encode" => encode_js(&input),
54 "decode" => decode_js(&input),
55 "toASCII" => Ok(with_host(|h| h.new_str(to_ascii(&input)))),
56 "toUnicode" => Ok(with_host(|h| h.new_str(to_unicode(&input)))),
57 "ucs2Decode" => Ok(ucs2_decode_val(&input)),
58 "ucs2Encode" => Ok(ucs2_encode_val(args.first())),
59 _ => return None,
60 })
61}
62
63pub fn constant(name: &str) -> Option<Value> {
67 match name {
68 "ucs2" => Some(with_host(|h| {
69 let mut m = IndexMap::new();
70 m.insert(
71 "decode".into(),
72 h.alloc(JsObj::Builtin("punycode.ucs2Decode".into())),
73 );
74 m.insert(
75 "encode".into(),
76 h.alloc(JsObj::Builtin("punycode.ucs2Encode".into())),
77 );
78 h.new_object(m)
79 })),
80 "version" => Some(with_host(|h| h.new_str("2.3.1"))),
81 _ => None,
82 }
83}
84
85fn encode_js(s: &str) -> Result<Value, String> {
88 let cps: Vec<u32> = s.chars().map(|c| c as u32).collect();
89 match encode(&cps) {
90 Ok(out) => Ok(with_host(|h| h.new_str(out))),
91 Err(e) => Err(crate::host::range_error(&e)),
92 }
93}
94
95fn decode_js(s: &str) -> Result<Value, String> {
96 match decode(s) {
97 Ok(cps) => {
98 let out: String = cps.iter().filter_map(|&c| char::from_u32(c)).collect();
99 Ok(with_host(|h| h.new_str(out)))
100 }
101 Err(e) => Err(crate::host::range_error(&e)),
102 }
103}
104
105fn ucs2_decode_val(s: &str) -> Value {
108 with_host(|h| {
109 let items: Vec<Value> = s.chars().map(|c| Value::Float(c as u32 as f64)).collect();
110 h.new_array(items)
111 })
112}
113
114fn ucs2_encode_val(arg: Option<&Value>) -> Value {
116 let cps: Vec<u32> = match arg {
117 Some(v) => with_host(|h| match h.get(v) {
118 Some(JsObj::Array(items)) => items.iter().map(|x| h.to_number(x) as u32).collect(),
119 _ => Vec::new(),
120 }),
121 None => Vec::new(),
122 };
123 let out: String = cps.iter().filter_map(|&c| char::from_u32(c)).collect();
124 with_host(|h| h.new_str(out))
125}
126
127fn map_labels(domain: &str, f: impl Fn(&str) -> String) -> String {
130 domain
131 .split(|c| DOTS.contains(&c))
132 .map(f)
133 .collect::<Vec<_>>()
134 .join(".")
135}
136
137fn to_ascii(domain: &str) -> String {
138 map_labels(domain, |label| {
139 if label.chars().any(|c| (c as u32) >= 0x80) {
141 let cps: Vec<u32> = label.chars().map(|c| c as u32).collect();
142 match encode(&cps) {
143 Ok(enc) => format!("xn--{enc}"),
144 Err(_) => label.to_string(),
145 }
146 } else {
147 label.to_string()
148 }
149 })
150}
151
152fn to_unicode(domain: &str) -> String {
153 map_labels(domain, |label| {
154 let lower = label.to_lowercase();
156 match lower.strip_prefix("xn--") {
157 Some(rest) => match decode(rest) {
158 Ok(cps) => cps.iter().filter_map(|&c| char::from_u32(c)).collect(),
159 Err(_) => label.to_string(),
160 },
161 None => label.to_string(),
162 }
163 })
164}
165
166fn adapt(mut delta: u32, num_points: u32, first_time: bool) -> u32 {
170 delta = if first_time { delta / DAMP } else { delta / 2 };
171 delta += delta / num_points;
172 let mut k = 0;
173 while delta > ((BASE - TMIN) * TMAX) / 2 {
174 delta /= BASE - TMIN;
175 k += BASE;
176 }
177 k + (((BASE - TMIN + 1) * delta) / (delta + SKEW))
178}
179
180fn digit_to_basic(d: u32) -> char {
183 if d < 26 {
184 (b'a' + d as u8) as char
185 } else {
186 (b'0' + (d - 26) as u8) as char
187 }
188}
189
190fn basic_to_digit(c: char) -> Result<u32, String> {
192 match c {
193 'a'..='z' => Ok(c as u32 - 'a' as u32),
194 'A'..='Z' => Ok(c as u32 - 'A' as u32),
195 '0'..='9' => Ok(c as u32 - '0' as u32 + 26),
196 _ => Err(format!("Invalid input: {c}")),
197 }
198}
199
200fn encode(input: &[u32]) -> Result<String, String> {
202 let mut output = String::new();
203
204 let mut b: u32 = 0;
206 for &c in input {
207 if c < 0x80 {
208 output.push(c as u8 as char);
209 b += 1;
210 }
211 }
212 let mut h = b;
214 if b > 0 {
215 output.push('-');
216 }
217
218 let input_len = input.len() as u32;
219 let mut n = INITIAL_N;
220 let mut delta: u32 = 0;
221 let mut bias = INITIAL_BIAS;
222
223 while h < input_len {
224 let mut m = u32::MAX;
226 for &c in input {
227 if c >= n && c < m {
228 m = c;
229 }
230 }
231 delta = delta
233 .checked_add((m - n).checked_mul(h + 1).ok_or("overflow")?)
234 .ok_or("overflow")?;
235 n = m;
236
237 for &c in input {
238 if c < n {
239 delta = delta.checked_add(1).ok_or("overflow")?;
240 }
241 if c == n {
242 let mut q = delta;
244 let mut k = BASE;
245 loop {
246 let t = threshold(k, bias);
247 if q < t {
248 break;
249 }
250 let digit = t + ((q - t) % (BASE - t));
251 output.push(digit_to_basic(digit));
252 q = (q - t) / (BASE - t);
253 k += BASE;
254 }
255 output.push(digit_to_basic(q));
256 bias = adapt(delta, h + 1, h == b);
257 delta = 0;
258 h += 1;
259 }
260 }
261 delta += 1;
262 n += 1;
263 }
264 Ok(output)
265}
266
267fn decode(input: &str) -> Result<Vec<u32>, String> {
269 let chars: Vec<char> = input.chars().collect();
270 let mut output: Vec<u32> = Vec::new();
271
272 let mut idx = match input.rfind('-') {
274 Some(pos) => {
275 for &c in &chars[..pos] {
278 if (c as u32) >= 0x80 {
279 return Err("Illegal basic code point".into());
280 }
281 output.push(c as u32);
282 }
283 pos + 1
284 }
285 None => 0,
286 };
287
288 let mut n = INITIAL_N;
289 let mut i: u32 = 0;
290 let mut bias = INITIAL_BIAS;
291 let len = chars.len();
292
293 while idx < len {
294 let oldi = i;
295 let mut w: u32 = 1;
296 let mut k = BASE;
297 loop {
298 if idx >= len {
299 return Err("Invalid input".into());
300 }
301 let digit = basic_to_digit(chars[idx])?;
302 idx += 1;
303 i = i
304 .checked_add(digit.checked_mul(w).ok_or("overflow")?)
305 .ok_or("overflow")?;
306 let t = threshold(k, bias);
307 if digit < t {
308 break;
309 }
310 w = w.checked_mul(BASE - t).ok_or("overflow")?;
311 k += BASE;
312 }
313 let out_len = output.len() as u32 + 1;
314 bias = adapt(i - oldi, out_len, oldi == 0);
315 n = n.checked_add(i / out_len).ok_or("overflow")?;
316 i %= out_len;
317 output.insert(i as usize, n);
319 i += 1;
320 }
321 Ok(output)
322}
323
324fn threshold(k: u32, bias: u32) -> u32 {
327 if k <= bias {
328 TMIN
329 } else if k >= bias + TMAX {
330 TMAX
331 } else {
332 k - bias
333 }
334}