diff --git a/src/lib.rs b/src/lib.rs index 53348c396..1e8f0551d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1035,12 +1035,21 @@ impl Url { /// # run().unwrap(); /// ``` pub fn path(&self) -> &str { - match (self.query_start, self.fragment_start) { + let path = match (self.query_start, self.fragment_start) { (None, None) => self.slice(self.path_start..), (Some(next_component_start), _) | (None, Some(next_component_start)) => { self.slice(self.path_start..next_component_start) } + }; + // Disambiguating a path that starts with "//" from a URL with an authority may require + // the serializer to insert a disambiguating marker to the start of the path. + // When deserializing, "/./" is supposed to be reduced to "/", so avoid exposing it to + // the application. + if path.len() >= 3 && &path[..3] == "/./" { + &path[2..] + } else { + path } } diff --git a/src/parser.rs b/src/parser.rs index 4f9cc524b..5867d3714 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1057,6 +1057,17 @@ impl<'a> Parser<'a> { } } if ends_with_slash { + // This is a willfull violation of the URL specification for serialization. + // + // It aligns with the behaviour of Microsoft Edge, + // it does not affect the result of parsing (that's still compliant), + // and it's necessary to make URL reparsing idempotent. + // + // See the specification bug at https://github.com/whatwg/url/issues/415 + if !*has_host && &self.serialization[path_start..] == "/" { + self.serialization.push('.'); + self.serialization.push('/'); + } self.serialization.push('/') } } diff --git a/tests/urltestdata.json b/tests/urltestdata.json index 5565c938f..667a3ba53 100644 --- a/tests/urltestdata.json +++ b/tests/urltestdata.json @@ -6144,5 +6144,20 @@ "pathname": "/test", "search": "?a", "hash": "#bc" + }, + "Found by fuzzing", + { + "input": "a:/a/..//a", + "base": "about:blank", + "href": "a:/.//a", + "protocol": "a:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//a", + "search": "", + "hash": "" } ]