diff --git a/idna/src/uts46.rs b/idna/src/uts46.rs index eef531d55..11385f78d 100644 --- a/idna/src/uts46.rs +++ b/idna/src/uts46.rs @@ -237,10 +237,12 @@ fn processing(domain: &str, flags: Flags, errors: &mut Vec) -> String { } let normalized: String = mapped.nfc().collect(); let mut validated = String::new(); + let mut first = true; for label in normalized.split('.') { - if validated.len() > 0 { + if !first { validated.push('.'); } + first = false; if label.starts_with("xn--") { match punycode::decode_to_string(&label["xn--".len()..]) { Some(decoded_label) => { @@ -266,13 +268,14 @@ pub struct Flags { } #[derive(PartialEq, Eq, Clone, Copy, Debug)] -enum Error { +pub enum Error { PunycodeError, ValidityCriteria, DissallowedByStd3AsciiRules, DissallowedMappedInStd3, DissallowedCharacter, TooLongForDns, + TooShortForDns, } /// Errors recorded during UTS #46 processing. @@ -282,14 +285,22 @@ enum Error { #[derive(Debug)] pub struct Errors(Vec); +impl PartialEq> for Errors { + fn eq(&self, other: &Vec) -> bool { + self.0 == *other + } +} + /// http://www.unicode.org/reports/tr46/#ToASCII pub fn to_ascii(domain: &str, flags: Flags) -> Result { let mut errors = Vec::new(); let mut result = String::new(); + let mut first = true; for label in processing(domain, flags, &mut errors).split('.') { - if result.len() > 0 { + if !first { result.push('.'); } + first = false; if label.is_ascii() { result.push_str(label); } else { @@ -305,8 +316,10 @@ pub fn to_ascii(domain: &str, flags: Flags) -> Result { if flags.verify_dns_length { let domain = if result.ends_with(".") { &result[..result.len()-1] } else { &*result }; - if domain.len() < 1 || domain.len() > 253 || - domain.split('.').any(|label| label.len() < 1 || label.len() > 63) { + if domain.len() < 1 || domain.split('.').any(|label| label.len() < 1) { + errors.push(Error::TooShortForDns) + } + if domain.len() > 253 || domain.split('.').any(|label| label.len() > 63) { errors.push(Error::TooLongForDns) } } diff --git a/idna/tests/uts46.rs b/idna/tests/uts46.rs index ddc8af989..ec20e1948 100644 --- a/idna/tests/uts46.rs +++ b/idna/tests/uts46.rs @@ -84,6 +84,16 @@ pub fn collect_tests(add_test: &mut F) { return; } + if let Err(ref errors) = result { + if errors.eq(&vec![uts46::Error::TooShortForDns]) { + // Conformant implementations may produce error where test file has strings. + // Test file drop all leading dots (leading empty labels), in contrast to spec. + // So, we allo TooShortForDns failers on test cases. + // https://github.com/servo/rust-url/issues/166 + return; + } + } + assert!(result.is_ok(), "Couldn't parse {} | original: {} | error: {:?}", source, original, result.err()); let output = result.ok().unwrap(); diff --git a/tests/unit.rs b/tests/unit.rs index c2aa93abb..a8d01fca6 100644 --- a/tests/unit.rs +++ b/tests/unit.rs @@ -344,6 +344,13 @@ fn test_set_host() { assert_eq!(url.as_str(), "foobar:/hello"); } +#[test] +// https://github.com/servo/rust-url/issues/166 +fn test_leading_dots() { + assert_eq!(Host::parse(".org").unwrap(), Host::Domain(".org".to_owned())); + assert_eq!(Url::parse("file://./foo").unwrap().domain(), Some(".")); +} + // This is testing that the macro produces buildable code when invoked // inside both a module and a function #[test]