uts46.rs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. // Copyright 2013-2014 The rust-url developers.
  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 crate::test::TestFn;
  9. use std::char;
  10. pub fn collect_tests<F: FnMut(String, TestFn)>(add_test: &mut F) {
  11. // https://www.unicode.org/Public/idna/13.0.0/IdnaTestV2.txt
  12. for (i, line) in include_str!("IdnaTestV2.txt").lines().enumerate() {
  13. if line == "" || line.starts_with('#') {
  14. continue;
  15. }
  16. // Remove comments
  17. let line = match line.find('#') {
  18. Some(index) => &line[0..index],
  19. None => line,
  20. };
  21. let mut pieces = line.split(';').map(|x| x.trim()).collect::<Vec<&str>>();
  22. let source = unescape(&pieces.remove(0));
  23. // ToUnicode
  24. let mut to_unicode = unescape(&pieces.remove(0));
  25. if to_unicode.is_empty() {
  26. to_unicode = source.clone();
  27. }
  28. let to_unicode_status = status(pieces.remove(0));
  29. // ToAsciiN
  30. let to_ascii_n = pieces.remove(0);
  31. let to_ascii_n = if to_ascii_n.is_empty() {
  32. to_unicode.clone()
  33. } else {
  34. to_ascii_n.to_owned()
  35. };
  36. let to_ascii_n_status = pieces.remove(0);
  37. let to_ascii_n_status = if to_ascii_n_status.is_empty() {
  38. to_unicode_status.clone()
  39. } else {
  40. status(to_ascii_n_status)
  41. };
  42. // ToAsciiT
  43. let to_ascii_t = pieces.remove(0);
  44. let to_ascii_t = if to_ascii_t.is_empty() {
  45. to_ascii_n.clone()
  46. } else {
  47. to_ascii_t.to_owned()
  48. };
  49. let to_ascii_t_status = pieces.remove(0);
  50. let to_ascii_t_status = if to_ascii_t_status.is_empty() {
  51. to_ascii_n_status.clone()
  52. } else {
  53. status(to_ascii_t_status)
  54. };
  55. let test_name = format!("UTS #46 line {}", i + 1);
  56. add_test(
  57. test_name,
  58. TestFn::dyn_test_fn(move || {
  59. let config = idna::Config::default()
  60. .use_std3_ascii_rules(true)
  61. .verify_dns_length(true)
  62. .check_hyphens(true);
  63. // http://unicode.org/reports/tr46/#Deviations
  64. // applications that perform IDNA2008 lookup are not required to check
  65. // for these contexts, so we skip all tests annotated with C*
  66. // Everybody ignores V2
  67. // https://github.com/servo/rust-url/pull/240
  68. // https://github.com/whatwg/url/issues/53#issuecomment-181528158
  69. // http://www.unicode.org/review/pri317/
  70. // "The special error codes X3 and X4_2 are now returned where a toASCII error code
  71. // was formerly being generated in toUnicode due to an empty label."
  72. // This is not implemented yet, so we skip toUnicode X4_2 tests for now, too.
  73. let (to_unicode_value, to_unicode_result) =
  74. config.transitional_processing(false).to_unicode(&source);
  75. if !to_unicode_status.is_empty() {
  76. if !to_unicode_status.iter().any(|e| e.starts_with('C'))
  77. && !to_unicode_status.contains(&"V2")
  78. && !to_unicode_status.contains(&"X4_2")
  79. {
  80. let res = to_unicode_result.ok();
  81. assert!(
  82. res == None,
  83. "Expected error {:?}. result: {:?} | source: {}",
  84. to_unicode_status,
  85. to_unicode_value,
  86. source
  87. );
  88. }
  89. } else {
  90. assert!(
  91. to_unicode_result.is_ok(),
  92. "Couldn't parse {} | error: {:?}",
  93. source,
  94. to_unicode_result.err()
  95. );
  96. assert!(
  97. to_unicode_value == to_unicode,
  98. "result: {} | expected: {} | source: {}",
  99. to_unicode_value,
  100. to_unicode,
  101. source
  102. );
  103. }
  104. let to_ascii_n_result = config.transitional_processing(false).to_ascii(&source);
  105. if !to_ascii_n_status.is_empty() {
  106. if !to_ascii_n_status.iter().any(|e| e.starts_with('C'))
  107. && !to_ascii_n_status.contains(&"V2")
  108. {
  109. let res = to_ascii_n_result.ok();
  110. assert!(
  111. res == None,
  112. "Expected error {:?}. result: {} | source: {}",
  113. to_ascii_n_status,
  114. res.unwrap(),
  115. source
  116. );
  117. }
  118. } else {
  119. assert!(
  120. to_ascii_n_result.is_ok(),
  121. "Couldn't parse {} | error: {:?}",
  122. source,
  123. to_ascii_n_result.err()
  124. );
  125. let output = to_ascii_n_result.ok().unwrap();
  126. assert!(
  127. output == to_ascii_n,
  128. "result: {} | expected: {} | source: {}",
  129. output,
  130. to_ascii_n,
  131. source
  132. );
  133. }
  134. let to_ascii_t_result = config.transitional_processing(true).to_ascii(&source);
  135. if !to_ascii_t_status.is_empty() {
  136. if !to_ascii_t_status.iter().any(|e| e.starts_with('C'))
  137. && !to_ascii_t_status.contains(&"V2")
  138. {
  139. let res = to_ascii_t_result.ok();
  140. assert!(
  141. res == None,
  142. "Expected error {:?}. result: {} | source: {}",
  143. to_ascii_t_status,
  144. res.unwrap(),
  145. source
  146. );
  147. }
  148. } else {
  149. assert!(
  150. to_ascii_t_result.is_ok(),
  151. "Couldn't parse {} | error: {:?}",
  152. source,
  153. to_ascii_t_result.err()
  154. );
  155. let output = to_ascii_t_result.ok().unwrap();
  156. assert!(
  157. output == to_ascii_t,
  158. "result: {} | expected: {} | source: {}",
  159. output,
  160. to_ascii_t,
  161. source
  162. );
  163. }
  164. }),
  165. )
  166. }
  167. }
  168. fn unescape(input: &str) -> String {
  169. let mut output = String::new();
  170. let mut chars = input.chars();
  171. loop {
  172. match chars.next() {
  173. None => return output,
  174. Some(c) => {
  175. if c == '\\' {
  176. match chars.next().unwrap() {
  177. '\\' => output.push('\\'),
  178. 'u' => {
  179. let c1 = chars.next().unwrap().to_digit(16).unwrap();
  180. let c2 = chars.next().unwrap().to_digit(16).unwrap();
  181. let c3 = chars.next().unwrap().to_digit(16).unwrap();
  182. let c4 = chars.next().unwrap().to_digit(16).unwrap();
  183. match char::from_u32(((c1 * 16 + c2) * 16 + c3) * 16 + c4) {
  184. Some(c) => output.push(c),
  185. None => {
  186. output
  187. .push_str(&format!("\\u{:X}{:X}{:X}{:X}", c1, c2, c3, c4));
  188. }
  189. };
  190. }
  191. _ => panic!("Invalid test data input"),
  192. }
  193. } else {
  194. output.push(c);
  195. }
  196. }
  197. }
  198. }
  199. }
  200. fn status(status: &str) -> Vec<&str> {
  201. if status.is_empty() || status == "[]" {
  202. return Vec::new();
  203. }
  204. let mut result = status.split(", ").collect::<Vec<_>>();
  205. assert!(result[0].starts_with('['));
  206. result[0] = &result[0][1..];
  207. let idx = result.len() - 1;
  208. let last = &mut result[idx];
  209. assert!(last.ends_with(']'));
  210. *last = &last[..last.len() - 1];
  211. result
  212. }