punycode.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. // Copyright 2013 Simon Sapin.
  2. //
  3. // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
  4. // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
  5. // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
  6. // option. This file may not be copied, modified, or distributed
  7. // except according to those terms.
  8. use std::u32;
  9. use std::char;
  10. use std::ascii::Ascii;
  11. use std::ascii::AsciiStr;
  12. static BASE: u32 = 36;
  13. static T_MIN: u32 = 1;
  14. static T_MAX: u32 = 26;
  15. static SKEW: u32 = 38;
  16. static DAMP: u32 = 700;
  17. static INITIAL_BIAS: u32 = 72;
  18. static INITIAL_N: u32 = 0x80;
  19. static DELIMITER: char = '-';
  20. #[inline]
  21. fn adapt(mut delta: u32, num_points: u32, first_time: bool) -> u32 {
  22. delta /= if first_time { DAMP } else { 2 };
  23. delta += delta / num_points;
  24. let mut k = 0;
  25. while delta > ((BASE - T_MIN) * T_MAX) / 2 {
  26. delta /= BASE - T_MIN;
  27. k += BASE;
  28. }
  29. k + (((BASE - T_MIN + 1) * delta) / (delta + SKEW))
  30. }
  31. pub fn decode(input: &[Ascii]) -> Option<~[char]> {
  32. let (mut output, input) = match input.to_str_ascii().rfind(DELIMITER) {
  33. None => (~[], input),
  34. Some(position) => (
  35. input.slice_to(position).map(|a| a.to_char()),
  36. if position > 0 { input.slice_from(position + 1) } else { input }
  37. )
  38. };
  39. let mut n = INITIAL_N;
  40. let mut bias = INITIAL_BIAS;
  41. let mut i = 0;
  42. let mut iter = input.iter();
  43. loop {
  44. let previous_i = i;
  45. let mut weight = 1;
  46. let mut k = BASE;
  47. let mut ascii = match iter.next() {
  48. None => break,
  49. Some(ascii) => ascii,
  50. };
  51. loop {
  52. let digit = match ascii.to_byte() {
  53. byte @ 0x30 .. 0x39 => byte - 0x30 + 26, // 0..9
  54. byte @ 0x41 .. 0x5A => byte - 0x41, // A..Z
  55. byte @ 0x61 .. 0x7A => byte - 0x61, // a..z
  56. _ => return None
  57. } as u32;
  58. if digit > (u32::max_value - i) / weight {
  59. return None // Malformed input would cause integer overflow
  60. }
  61. i += digit * weight;
  62. let t = if k <= bias { T_MIN }
  63. else if k >= bias + T_MAX { T_MAX }
  64. else { k - bias };
  65. if digit < t {
  66. break
  67. }
  68. if weight > u32::max_value / (BASE - t) {
  69. return None // Malformed input would cause integer overflow
  70. }
  71. weight *= BASE - t;
  72. k += BASE;
  73. ascii = match iter.next() {
  74. None => return None, // End of input before the end of this delta
  75. Some(ascii) => ascii,
  76. };
  77. }
  78. let length = output.len() as u32;
  79. bias = adapt(i - previous_i, length + 1, previous_i == 0);
  80. if i / (length + 1) > u32::max_value - n {
  81. return None // Malformed input would cause integer overflow
  82. }
  83. n += i / (length + 1);
  84. i %= length + 1;
  85. let c = match char::from_u32(n) {
  86. Some(c) => c,
  87. None => return None
  88. };
  89. output.insert(i as uint, c);
  90. i += 1;
  91. }
  92. Some(output)
  93. }
  94. #[cfg(test)]
  95. mod tests {
  96. use super::decode;
  97. use std::ascii::AsciiCast;
  98. use std::str::from_chars;
  99. use extra::json::{from_str, List, Object, String};
  100. fn one_test(description: &str, decoded: &str, encoded: &str) {
  101. let result = decode(encoded.to_ascii()).map(|s| from_chars(s));
  102. assert!(result == Some(decoded.to_owned()),
  103. format!("Decoding {:?} failed:\n {:?}\n!= {:?}\n{}",
  104. encoded, result.unwrap_or(~"<Failed>"), decoded, description));
  105. }
  106. fn get_string<'a>(map: &'a ~Object, key: &~str) -> &'a str {
  107. match map.find(key) {
  108. Some(&String(ref s)) => s.as_slice(),
  109. None => "",
  110. _ => fail!(),
  111. }
  112. }
  113. #[test]
  114. fn test_punycode() {
  115. match from_str(include_str!("punycode_tests.json")) {
  116. Ok(List(tests)) => for test in tests.iter() {
  117. match test {
  118. &Object(ref o) => one_test(
  119. get_string(o, &~"description"),
  120. get_string(o, &~"decoded"),
  121. get_string(o, &~"encoded")
  122. ),
  123. _ => fail!(),
  124. }
  125. },
  126. other => fail!("{:?}", other)
  127. }
  128. }
  129. }