@@ -146,16 +146,67 @@ fn url_to_file_path_wasm(url: &Url) -> Result<PathBuf, ()> {
146146#[ inline]
147147pub fn normalize_path ( path : Cow < Path > ) -> Cow < Path > {
148148 fn should_normalize ( path : & Path ) -> bool {
149+ if path_has_trailing_separator ( path) {
150+ return true ;
151+ }
152+
149153 for component in path. components ( ) {
150154 match component {
151- Component :: Prefix ( .. ) | Component :: CurDir | Component :: ParentDir => {
152- return true
155+ Component :: CurDir | Component :: ParentDir => {
156+ return true ;
153157 }
154- Component :: RootDir | Component :: Normal ( _) => {
158+ Component :: Prefix ( .. ) | Component :: RootDir | Component :: Normal ( _) => {
155159 // ok
156160 }
157161 }
158162 }
163+
164+ path_has_cur_dir_separator ( path)
165+ }
166+
167+ fn path_has_trailing_separator ( path : & Path ) -> bool {
168+ #[ cfg( unix) ]
169+ let raw = std:: os:: unix:: ffi:: OsStrExt :: as_bytes ( path. as_os_str ( ) ) ;
170+ #[ cfg( windows) ]
171+ let raw = path. as_os_str ( ) . as_encoded_bytes ( ) ;
172+ #[ cfg( target_arch = "wasm32" ) ]
173+ let raw = path. to_string_lossy ( ) ;
174+ #[ cfg( target_arch = "wasm32" ) ]
175+ let raw = raw. as_bytes ( ) ;
176+
177+ if sys_traits:: impls:: is_windows ( ) {
178+ raw. contains ( & b'/' ) || raw. ends_with ( b"\\ " )
179+ } else {
180+ raw. ends_with ( b"/" )
181+ }
182+ }
183+
184+ // Rust normalizes away `Component::CurDir` most of the time
185+ // so we need to explicitly check for it in the bytes
186+ fn path_has_cur_dir_separator ( path : & Path ) -> bool {
187+ #[ cfg( unix) ]
188+ let raw = std:: os:: unix:: ffi:: OsStrExt :: as_bytes ( path. as_os_str ( ) ) ;
189+ #[ cfg( windows) ]
190+ let raw = path. as_os_str ( ) . as_encoded_bytes ( ) ;
191+ #[ cfg( target_arch = "wasm32" ) ]
192+ let raw = path. to_string_lossy ( ) ;
193+ #[ cfg( target_arch = "wasm32" ) ]
194+ let raw = raw. as_bytes ( ) ;
195+
196+ if sys_traits:: impls:: is_windows ( ) {
197+ for window in raw. windows ( 3 ) {
198+ if matches ! ( window, [ b'\\' , b'.' , b'\\' ] ) {
199+ return true ;
200+ }
201+ }
202+ } else {
203+ for window in raw. windows ( 3 ) {
204+ if matches ! ( window, [ b'/' , b'.' , b'/' ] ) {
205+ return true ;
206+ }
207+ }
208+ }
209+
159210 false
160211 }
161212
@@ -603,22 +654,39 @@ mod tests {
603654 }
604655 }
605656
657+ #[ test]
658+ fn test_normalize_path_basic ( ) {
659+ let run_test = run_normalize_path_test;
660+ run_test ( "a/../b" , "b" ) ;
661+ run_test ( "a/./b/" , & PathBuf :: from ( "a" ) . join ( "b" ) . to_string_lossy ( ) ) ;
662+ run_test (
663+ "a/./b/../c" ,
664+ & PathBuf :: from ( "a" ) . join ( "c" ) . to_string_lossy ( ) ,
665+ ) ;
666+ }
667+
606668 #[ cfg( windows) ]
607669 #[ test]
608- fn test_normalize_path ( ) {
609- use super :: * ;
670+ fn test_normalize_path_win ( ) {
671+ let run_test = run_normalize_path_test ;
610672
611673 run_test ( "C:\\ test\\ file.txt" , "C:\\ test\\ file.txt" ) ;
612674 run_test ( "C:\\ test\\ ./file.txt" , "C:\\ test\\ file.txt" ) ;
613675 run_test ( "C:\\ test\\ ../other/file.txt" , "C:\\ other\\ file.txt" ) ;
614676 run_test ( "C:\\ test\\ ../other\\ file.txt" , "C:\\ other\\ file.txt" ) ;
677+ run_test (
678+ "C:\\ test\\ removes_trailing_slash\\ " ,
679+ "C:\\ test\\ removes_trailing_slash" ,
680+ ) ;
681+ run_test ( "C:\\ a\\ .\\ b\\ ..\\ c" , "C:\\ a\\ c" ) ;
682+ }
615683
616- fn run_test ( input : & str , expected : & str ) {
617- assert_eq ! (
618- normalize_path ( Cow :: Owned ( PathBuf :: from ( input ) ) ) ,
619- PathBuf :: from( expected )
620- ) ;
621- }
684+ # [ track_caller ]
685+ fn run_normalize_path_test ( input : & str , expected : & str ) {
686+ assert_eq ! (
687+ normalize_path ( Cow :: Owned ( PathBuf :: from( input ) ) ) . to_string_lossy ( ) ,
688+ expected
689+ ) ;
622690 }
623691
624692 #[ test]
0 commit comments