uts46.rs 6.7 KB

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