diff --git a/url/Cargo.toml b/url/Cargo.toml index 7ba83a15b..f634f6dad 100644 --- a/url/Cargo.toml +++ b/url/Cargo.toml @@ -35,6 +35,8 @@ serde = {version = "1.0", optional = true, features = ["derive"]} [features] default = [] +# Produce smaller binaries by omitting Unicode domain support +disable_idna = [] # UNSTABLE FEATURES (requires Rust nightly) # Enable to use the #[debugger_visualizer] attribute. debugger_visualizer = [] diff --git a/url/src/host.rs b/url/src/host.rs index 9931c2f87..10a93d459 100644 --- a/url/src/host.rs +++ b/url/src/host.rs @@ -162,9 +162,22 @@ impl Host { } /// convert domain with idna + #[cfg(not(feature = "disable_idna"))] fn domain_to_ascii(domain: &str) -> Result { idna::domain_to_ascii(domain).map_err(Into::into) } + + /// checks domain is ascii + #[cfg(feature = "disable_idna")] + fn domain_to_ascii(domain: &str) -> Result { + // without idna feature, we can't verify that xn-- domains correctness + let domain = domain.to_lowercase(); + if domain.is_ascii() && domain.split('.').all(|s| !s.starts_with("xn--")) { + Ok(domain) + } else { + Err(ParseError::InvalidDomainCharacter) + } + } } impl> fmt::Display for Host { diff --git a/url/src/lib.rs b/url/src/lib.rs index 023a89a28..c07d1fe37 100644 --- a/url/src/lib.rs +++ b/url/src/lib.rs @@ -899,8 +899,11 @@ impl Url { /// assert_eq!(url.authority(), "user:password@example.com"); /// let url = Url::parse("irc://àlex.рф.example.com:6667/foo")?; /// assert_eq!(url.authority(), "%C3%A0lex.%D1%80%D1%84.example.com:6667"); + /// # #[cfg(not(feature = "disable_idna"))] + /// # { /// let url = Url::parse("http://àlex.рф.example.com:80/foo")?; /// assert_eq!(url.authority(), "xn--lex-8ka.xn--p1ai.example.com"); + /// # } /// # Ok(()) /// # } /// # run().unwrap(); diff --git a/url/src/origin.rs b/url/src/origin.rs index 81193f510..4e221b04a 100644 --- a/url/src/origin.rs +++ b/url/src/origin.rs @@ -86,6 +86,7 @@ impl Origin { } /// + #[cfg(not(feature = "disable_idna"))] pub fn unicode_serialization(&self) -> String { match *self { Origin::Opaque(_) => "null".to_owned(), diff --git a/url/src/parser.rs b/url/src/parser.rs index c32090e20..eecce5b86 100644 --- a/url/src/parser.rs +++ b/url/src/parser.rs @@ -87,6 +87,7 @@ simple_enum_error! { Overflow => "URLs more than 4 GB are not supported", } +#[cfg(not(feature = "disable_idna"))] impl From<::idna::Errors> for ParseError { fn from(_: ::idna::Errors) -> ParseError { ParseError::IdnaError diff --git a/url/src/quirks.rs b/url/src/quirks.rs index 0674ebb62..045ec4ec6 100644 --- a/url/src/quirks.rs +++ b/url/src/quirks.rs @@ -58,6 +58,7 @@ pub fn internal_components(url: &Url) -> InternalComponents { } /// https://url.spec.whatwg.org/#dom-url-domaintoascii +#[cfg(not(feature = "disable_idna"))] pub fn domain_to_ascii(domain: &str) -> String { match Host::parse(domain) { Ok(Host::Domain(domain)) => domain, diff --git a/url/tests/data.rs b/url/tests/data.rs index dc4660b9b..c6a8221cc 100644 --- a/url/tests/data.rs +++ b/url/tests/data.rs @@ -39,7 +39,17 @@ fn urltestdata() { .expect("missing base key") .maybe_string(); let input = entry.take_string("input"); - let failure = entry.take_key("failure").is_some(); + let failure = { + #[cfg(not(feature = "disable_idna"))] + { + entry.take_key("failure").is_some() + } + + #[cfg(feature = "disable_idna")] + { + entry.take_key("is_idna").is_some() || entry.take_key("failure").is_some() + } + }; let res = if let Some(base) = maybe_base { let base = match Url::parse(&base) { diff --git a/url/tests/unit.rs b/url/tests/unit.rs index 8957aaf32..bdbcbef1b 100644 --- a/url/tests/unit.rs +++ b/url/tests/unit.rs @@ -351,6 +351,7 @@ fn host_serialization() { } #[test] +#[cfg(not(feature = "disable_idna"))] fn test_idna() { assert!("http://goșu.ro".parse::().is_ok()); assert_eq!( @@ -586,6 +587,7 @@ fn test_origin_opaque() { } #[test] +#[cfg(not(feature = "disable_idna"))] fn test_origin_unicode_serialization() { let data = [ ("http://😅.com", "http://😅.com"), @@ -758,6 +760,7 @@ fn test_set_href() { } #[test] +#[cfg(not(feature = "disable_idna"))] fn test_domain_encoding_quirks() { use url::quirks::{domain_to_ascii, domain_to_unicode}; @@ -811,8 +814,11 @@ fn test_windows_unc_path() { let url = Url::from_file_path(Path::new(r"\\host\share\path\file.txt")).unwrap(); assert_eq!(url.as_str(), "file://host/share/path/file.txt"); - let url = Url::from_file_path(Path::new(r"\\höst\share\path\file.txt")).unwrap(); - assert_eq!(url.as_str(), "file://xn--hst-sna/share/path/file.txt"); + #[cfg(not(feature = "disable_idna"))] + { + let url = Url::from_file_path(Path::new(r"\\höst\share\path\file.txt")).unwrap(); + assert_eq!(url.as_str(), "file://xn--hst-sna/share/path/file.txt"); + } let url = Url::from_file_path(Path::new(r"\\192.168.0.1\share\path\file.txt")).unwrap(); assert_eq!(url.host(), Some(Host::Ipv4(Ipv4Addr::new(192, 168, 0, 1)))); @@ -1256,9 +1262,13 @@ fn test_authority() { url.authority(), "%C3%A0lex:%C3%A0lex@%C3%A0lex.%D1%80%D1%84.example.com:6667" ); - let url = Url::parse("https://àlex:àlex@àlex.рф.example.com:443/foo").unwrap(); - assert_eq!( - url.authority(), - "%C3%A0lex:%C3%A0lex@xn--lex-8ka.xn--p1ai.example.com" - ); + + #[cfg(not(feature = "disable_idna"))] + { + let url = Url::parse("https://àlex:àlex@àlex.рф.example.com:443/foo").unwrap(); + assert_eq!( + url.authority(), + "%C3%A0lex:%C3%A0lex@xn--lex-8ka.xn--p1ai.example.com" + ); + } } diff --git a/url/tests/urltestdata.json b/url/tests/urltestdata.json index 53d036886..e38302607 100644 --- a/url/tests/urltestdata.json +++ b/url/tests/urltestdata.json @@ -3617,7 +3617,8 @@ "port": "", "pathname": "/", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, "Leading and trailing C0 control or space", { @@ -3649,7 +3650,8 @@ "port": "", "pathname": "/", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, "Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0", { @@ -3735,7 +3737,8 @@ "port": "", "pathname": "/", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, "URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257", { @@ -3773,7 +3776,8 @@ "port": "", "pathname": "/", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, { "input": "https://faß.ExAmPlE/", @@ -3788,7 +3792,8 @@ "port": "", "pathname": "/", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, { "input": "sc://faß.ExAmPlE/", @@ -3889,7 +3894,8 @@ "port": "", "pathname": "/", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, "Domains with empty labels", { @@ -5325,7 +5331,8 @@ "port": "", "pathname": "/", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, { "input": "https://%e2%98%83", @@ -5340,7 +5347,8 @@ "port": "", "pathname": "/", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, "# tests from jsdom/whatwg-url designed for code coverage", { @@ -8172,7 +8180,8 @@ "port": "", "pathname": "/p", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, { "input": "file://a%C2%ADb/p", @@ -8186,7 +8195,8 @@ "port": "", "pathname": "/p", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, "IDNA hostnames which get mapped to 'localhost'", { @@ -8201,7 +8211,8 @@ "port": "", "pathname": "/usr/bin", "search": "", - "hash": "" + "hash": "", + "is_idna": true }, "Empty host after the domain to ASCII", {