uts46.rs 8.8 KB

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