@@ -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 ) ]
31963234pub struct UrlQuery < ' a > {
0 commit comments