diff --git a/url/src/parser.rs b/url/src/parser.rs index c32090e20..765cc027c 100644 --- a/url/src/parser.rs +++ b/url/src/parser.rs @@ -178,7 +178,7 @@ pub fn default_port(scheme: &str) -> Option { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Input<'i> { chars: str::Chars<'i>, } @@ -1173,7 +1173,7 @@ impl<'a> Parser<'a> { ) -> Input<'i> { // Relative path state loop { - let segment_start = self.serialization.len(); + let mut segment_start = self.serialization.len(); let mut ends_with_slash = false; loop { let input_before_c = input.clone(); @@ -1202,6 +1202,14 @@ impl<'a> Parser<'a> { } _ => { self.check_url_code_point(c, &input); + if scheme_type.is_file() + && is_normalized_windows_drive_letter( + &self.serialization[path_start + 1..], + ) + { + self.serialization.push('/'); + segment_start += 1; + } if self.context == Context::PathSegmentSetter { if scheme_type.is_special() { self.serialization @@ -1249,7 +1257,10 @@ impl<'a> Parser<'a> { } _ => { // If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then - if scheme_type.is_file() && is_windows_drive_letter(segment_before_slash) { + if scheme_type.is_file() + && segment_start == path_start + 1 + && is_windows_drive_letter(segment_before_slash) + { // Replace the second code point in buffer with U+003A (:). if let Some(c) = segment_before_slash.chars().next() { self.serialization.truncate(segment_start); diff --git a/url/tests/unit.rs b/url/tests/unit.rs index 8957aaf32..6cb0f37fe 100644 --- a/url/tests/unit.rs +++ b/url/tests/unit.rs @@ -1262,3 +1262,39 @@ fn test_authority() { "%C3%A0lex:%C3%A0lex@xn--lex-8ka.xn--p1ai.example.com" ); } + +#[test] +/// https://github.com/servo/rust-url/issues/838 +fn test_file_with_drive() { + let s1 = "fIlE:p:?../"; + let url = url::Url::parse(s1).unwrap(); + assert_eq!(url.to_string(), "file:///p:?../"); + assert_eq!(url.path(), "/p:"); + + let testcases = [ + ("a", "file:///p:/a"), + ("", "file:///p:?../"), + ("?x", "file:///p:?x"), + (".", "file:///p:/"), + ("..", "file:///p:/"), + ("../", "file:///p:/"), + ]; + + for case in &testcases { + let url2 = url::Url::join(&url, case.0).unwrap(); + assert_eq!(url2.to_string(), case.1); + } +} + +#[test] +/// Similar to test_file_with_drive, but with a path +/// that could be confused for a drive. +fn test_file_with_drive_and_path() { + let s1 = "fIlE:p:/x|?../"; + let url = url::Url::parse(s1).unwrap(); + assert_eq!(url.to_string(), "file:///p:/x|?../"); + assert_eq!(url.path(), "/p:/x|"); + let s2 = "a"; + let url2 = url::Url::join(&url, s2).unwrap(); + assert_eq!(url2.to_string(), "file:///p:/a"); +}