Skip to content

Commit 5bf53e6

Browse files
committed
Try and fix bug
Signed-off-by: Adam Gutglick <adam@spiraldb.com>
1 parent a66f422 commit 5bf53e6

2 files changed

Lines changed: 86 additions & 25 deletions

File tree

url/src/lib.rs

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3122,6 +3122,37 @@ fn file_url_segments_to_pathbuf(
31223122
file_url_segments_to_pathbuf_windows(estimated_capacity, host, segments)
31233123
}
31243124

3125+
// Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
3126+
#[cfg(feature = "std")]
3127+
#[cfg_attr(not(windows), allow(dead_code))]
3128+
fn decode_windows_drive_path_segment(segment: &str) -> Result<String, ()> {
3129+
use percent_encoding::percent_decode_str;
3130+
3131+
// `path_segments_mut()` encodes separators inside a segment, but Windows
3132+
// file paths still need to round-trip through `to_file_path()`.
3133+
let decoded = percent_decode_str(segment).decode_utf8().map_err(|_| ())?;
3134+
let bytes = decoded.as_bytes();
3135+
if bytes.len() < 2 || !parser::ascii_alpha(bytes[0] as char) || bytes[1] != b':' {
3136+
return Err(());
3137+
}
3138+
3139+
if bytes.len() == 2 {
3140+
return Ok(decoded.into_owned());
3141+
}
3142+
3143+
if !matches!(bytes[2], b'\\' | b'/') {
3144+
return Err(());
3145+
}
3146+
3147+
let mut normalized = String::with_capacity(decoded.len());
3148+
normalized.push(bytes[0] as char);
3149+
normalized.push(':');
3150+
for c in decoded[2..].chars() {
3151+
normalized.push(if c == '/' { '\\' } else { c });
3152+
}
3153+
Ok(normalized)
3154+
}
3155+
31253156
// Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102
31263157
#[cfg(feature = "std")]
31273158
#[cfg_attr(not(windows), allow(dead_code))]
@@ -3138,31 +3169,7 @@ fn file_url_segments_to_pathbuf_windows(
31383169
string.push_str(host);
31393170
} else {
31403171
let first = segments.next().ok_or(())?;
3141-
3142-
match first.len() {
3143-
2 => {
3144-
if !first.starts_with(parser::ascii_alpha) || first.as_bytes()[1] != b':' {
3145-
return Err(());
3146-
}
3147-
3148-
string.push_str(first);
3149-
}
3150-
3151-
4 => {
3152-
if !first.starts_with(parser::ascii_alpha) {
3153-
return Err(());
3154-
}
3155-
let bytes = first.as_bytes();
3156-
if bytes[1] != b'%' || bytes[2] != b'3' || (bytes[3] != b'a' && bytes[3] != b'A') {
3157-
return Err(());
3158-
}
3159-
3160-
string.push_str(&first[0..1]);
3161-
string.push(':');
3162-
}
3163-
3164-
_ => return Err(()),
3165-
}
3172+
string.push_str(&decode_windows_drive_path_segment(first)?);
31663173
};
31673174

31683175
for segment in segments {
@@ -3191,6 +3198,37 @@ fn file_url_segments_to_pathbuf_windows(
31913198
Ok(path)
31923199
}
31933200

3201+
#[cfg(all(test, feature = "std"))]
3202+
mod tests {
3203+
use super::decode_windows_drive_path_segment;
3204+
use alloc::string::String;
3205+
3206+
#[test]
3207+
fn decode_windows_drive_path_segment_accepts_drive_letter_only() {
3208+
assert_eq!(
3209+
decode_windows_drive_path_segment("C%3A"),
3210+
Ok(String::from("C:"))
3211+
);
3212+
}
3213+
3214+
#[test]
3215+
fn decode_windows_drive_path_segment_accepts_encoded_separators() {
3216+
assert_eq!(
3217+
decode_windows_drive_path_segment("C:%5CUsers%5Cme"),
3218+
Ok(String::from(r"C:\Users\me"))
3219+
);
3220+
assert_eq!(
3221+
decode_windows_drive_path_segment("C:%2FUsers%2Fme"),
3222+
Ok(String::from(r"C:\Users\me"))
3223+
);
3224+
}
3225+
3226+
#[test]
3227+
fn decode_windows_drive_path_segment_rejects_drive_relative_paths() {
3228+
assert_eq!(decode_windows_drive_path_segment("C:Users"), Err(()));
3229+
}
3230+
}
3231+
31943232
/// Implementation detail of `Url::query_pairs_mut`. Typically not used directly.
31953233
#[derive(Debug)]
31963234
pub struct UrlQuery<'a> {

url/tests/unit.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,29 @@ fn new_path_windows_fun() {
219219
assert_eq!(url.to_file_path(), Ok(PathBuf::from(r"C:\foo\bar")));
220220
}
221221

222+
#[test]
223+
#[cfg(all(feature = "std", windows))]
224+
fn windows_to_file_path_accepts_encoded_separators_in_first_segment() {
225+
let url = Url::parse("file:///C:%5Cfoo%5Cbar").unwrap();
226+
assert_eq!(url.to_file_path(), Ok(PathBuf::from(r"C:\foo\bar")));
227+
228+
let url = Url::parse("file:///C:%2Ffoo%2Fbar").unwrap();
229+
assert_eq!(url.to_file_path(), Ok(PathBuf::from(r"C:\foo\bar")));
230+
}
231+
232+
#[test]
233+
#[cfg(all(feature = "std", windows))]
234+
fn windows_to_file_path_accepts_drive_path_from_path_segments_mut() {
235+
let mut url = Url::parse("file://").unwrap();
236+
url.path_segments_mut()
237+
.unwrap()
238+
.pop_if_empty()
239+
.push(r"C:\foo\bar");
240+
241+
assert_eq!(url.as_str(), "file:///C:%5Cfoo%5Cbar");
242+
assert_eq!(url.to_file_path(), Ok(PathBuf::from(r"C:\foo\bar")));
243+
}
244+
222245
#[test]
223246
#[cfg(all(
224247
feature = "std",

0 commit comments

Comments
 (0)