Skip to content

Commit 04c4e7d

Browse files
committed
handle IDN conversion better, and log expected failures
1 parent 69bc9ae commit 04c4e7d

File tree

6 files changed

+53
-5
lines changed

6 files changed

+53
-5
lines changed

url/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
3333
idna = { version = "0.5.0", path = "../idna" }
3434

3535
[target.'cfg(target_arch = "wasm32")'.dependencies]
36-
web-sys = { version = "0.3.65", features = ["Url"] }
36+
web-sys = { version = "0.3.65", features = ["Navigator", "Url", "Window"] }
3737

3838
[features]
3939
default = []

url/src/host.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,20 +145,36 @@ impl Host<String> {
145145
idna::domain_to_ascii(domain).map_err(Into::into)
146146
}
147147

148+
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
149+
const SENTINEL_HOSTNAME: &'static str = "url-host-web-sys-sentinel";
150+
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
151+
const SENTINEL_URL: &'static str = "http://url-host-web-sys-sentinel";
152+
148153
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
149154
/// Convert IDN domain to ASCII form with [web_sys::Url]
150155
fn domain_to_ascii(domain: &str) -> Result<String, ParseError> {
156+
debug_assert!(Self::SENTINEL_URL.ends_with(Self::SENTINEL_HOSTNAME));
151157
// Url throws an error on empty hostnames
152158
if domain.is_empty() {
153-
return Err(ParseError::EmptyHost);
159+
return Ok(domain.to_string());
154160
}
155161
// Url returns strange results for invalid domain chars
156162
if domain.contains(Self::is_invalid_domain_char) {
157163
return Err(ParseError::InvalidDomainCharacter);
158164
}
159-
let u =
160-
web_sys::Url::new(&format!("http://{domain}")).map_err(|_| ParseError::IdnaError)?;
161-
Ok(u.host())
165+
166+
// Create a new Url with a sentinel value.
167+
let u = web_sys::Url::new(Self::SENTINEL_URL).map_err(|_| ParseError::IdnaError)?;
168+
debug_assert_eq!(u.hostname(), Self::SENTINEL_HOSTNAME);
169+
// Whenever set_hostname fails, it doesn't update the Url.
170+
u.set_hostname(domain);
171+
let h = u.hostname();
172+
if h.eq_ignore_ascii_case(Self::SENTINEL_HOSTNAME) || h.is_empty() {
173+
// It's probably invalid
174+
Err(ParseError::IdnaError)
175+
} else {
176+
Ok(h)
177+
}
162178
}
163179

164180
fn is_invalid_domain_char(c: char) -> bool {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<http://\u{ff05}\u{ff14}\u{ff11}.com> against <http://other.com/>
2+
<http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/>
3+
<http://!\"$&\'()*+,-.;=_`{}~/>
4+
<wss://!\"$&\'()*+,-.;=_`{}~/>
5+
<https://example.com/> set host to <xn-->
6+
<https://example.com/> set hostname to <xn-->
7+
<http://a.b.c.xn--pokxncvks>
8+
<http://10.0.0.xn--pokxncvks>
9+
<http://a.b.c.XN--pokxncvks>
10+
<http://a.b.c.Xn--pokxncvks>
11+
<http://10.0.0.XN--pokxncvks>
12+
<http://10.0.0.xN--pokxncvks>
13+
<file://xn--/p>
14+
<https://xn--/>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<http://!\"$&\'()*+,-.;=_`{}~/>
2+
<wss://!\"$&\'()*+,-.;=_`{}~/>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<http://\u{ff05}\u{ff14}\u{ff11}.com> against <http://other.com/>
2+
<http://%ef%bc%85%ef%bc%94%ef%bc%91.com> against <http://other.com/>

url/tests/wpt.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ fn main() {
139139
let mut expected_failures = include_str!("expected_failures.txt")
140140
.lines()
141141
.collect::<Vec<_>>();
142+
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
143+
{
144+
// Every browser has its quirks.
145+
let user_agent = web_sys::window().unwrap().navigator().user_agent().unwrap().to_ascii_lowercase();
146+
if user_agent.contains("chrom") {
147+
expected_failures.extend(include_str!("expected_failures_chromium.txt").lines());
148+
}
149+
if user_agent.contains("gecko/20") {
150+
expected_failures.extend(include_str!("expected_failures_firefox.txt").lines());
151+
}
152+
if user_agent.contains("safari") && !user_agent.contains("chrom") {
153+
expected_failures.extend(include_str!("expected_failures_safari.txt").lines());
154+
}
155+
}
142156

143157
let mut errors = vec![];
144158

0 commit comments

Comments
 (0)