@@ -6,7 +6,7 @@ use super::InvokeResponse;
66use crate :: { api:: path:: BaseDirectory , Config , PackageInfo } ;
77use serde:: Deserialize ;
88#[ cfg( path_all) ]
9- use std:: path:: { Path , PathBuf } ;
9+ use std:: path:: { Component , Path , PathBuf , MAIN_SEPARATOR } ;
1010use std:: sync:: Arc ;
1111/// The API descriptor.
1212#[ derive( Deserialize ) ]
@@ -77,48 +77,74 @@ pub fn resolve_path_handler(
7777
7878#[ cfg( path_all) ]
7979fn resolve ( paths : Vec < String > ) -> crate :: Result < String > {
80- // start with the current directory
81- let mut resolved_path = PathBuf :: new ( ) . join ( "." ) ;
82-
83- for path in paths {
84- let path_buf = PathBuf :: from ( path) ;
85-
86- // if we encounter an absolute path, we use it as the starting path for next iteration
87- if path_buf. is_absolute ( ) {
88- resolved_path = path_buf;
89- } else {
90- resolved_path = resolved_path. join ( & path_buf) ;
91- }
80+ // Start with current directory path because users might pass empty or vec!["."]
81+ // then start adding paths from the vector one by one using path.push()
82+ // so if an absolute path is encountered in the iteration, we use it as the current full path
83+ // examples:
84+ // 1. vec!["."] or vec![] will be equal to std::env::current_dir()
85+ // 2. vec!["/foo/bar", "/tmp/file", "baz"] will be equal to PathBuf::from("/tmp/file/baz")
86+ let mut path = std:: env:: current_dir ( ) ?;
87+ for p in paths {
88+ path. push ( p) ;
9289 }
93-
94- normalize ( resolved_path. to_string_lossy ( ) . to_string ( ) )
90+ Ok ( normalize_path ( & path) . to_string_lossy ( ) . to_string ( ) )
9591}
9692
9793#[ cfg( path_all) ]
98- fn normalize ( path : String ) -> crate :: Result < String > {
99- let path = std:: fs:: canonicalize ( path) ?;
100- let path = path. to_string_lossy ( ) . to_string ( ) ;
101-
102- // remove `\\\\?\\` on windows, UNC path
103- #[ cfg( target_os = "windows" ) ]
104- let path = path. replace ( "\\ \\ ?\\ " , "" ) ;
94+ fn join ( paths : Vec < String > ) -> crate :: Result < String > {
95+ let path = PathBuf :: from (
96+ paths
97+ . iter ( )
98+ . map ( |p| {
99+ // Add MAIN_SEPARATOR if this is not the first element in the vector
100+ // and if it doesn't already have a spearator.
101+ // Doing this to ensure that the vector elements are separated in
102+ // the resulting string so path.components() can work correctly when called
103+ // in normalize_path_no_absolute() later
104+ if !p. starts_with ( '/' ) && !p. starts_with ( '\\' ) && p != & paths[ 0 ] {
105+ let mut tmp = String :: from ( MAIN_SEPARATOR ) ;
106+ tmp. push_str ( p) ;
107+ tmp
108+ } else {
109+ p. to_string ( )
110+ }
111+ } )
112+ . collect :: < String > ( ) ,
113+ ) ;
105114
106- Ok ( path)
115+ let p = normalize_path_no_absolute ( & path)
116+ . to_string_lossy ( )
117+ . to_string ( ) ;
118+ Ok ( if p. is_empty ( ) { "." . into ( ) } else { p } )
107119}
108120
109121#[ cfg( path_all) ]
110- fn join ( paths : Vec < String > ) -> crate :: Result < String > {
111- let mut joined_path = PathBuf :: new ( ) ;
112- for path in paths {
113- joined_path = joined_path. join ( path) ;
114- }
115- normalize ( joined_path. to_string_lossy ( ) . to_string ( ) )
122+ fn normalize ( path : String ) -> crate :: Result < String > {
123+ let mut p = normalize_path_no_absolute ( Path :: new ( & path) )
124+ . to_string_lossy ( )
125+ . to_string ( ) ;
126+ Ok ( if p. is_empty ( ) {
127+ // Nodejs will return ".." if we used normalize("..")
128+ // and will return "." if we used normalize("") or normalize(".")
129+ if path == ".." {
130+ path
131+ } else {
132+ "." . into ( )
133+ }
134+ } else {
135+ // If the path passed to this function contains a trailing separator,
136+ // we make sure to perserve it. That's how NodeJS works
137+ if ( path. ends_with ( '/' ) || path. ends_with ( '\\' ) ) && ( !p. ends_with ( '/' ) || !p. ends_with ( '\\' ) ) {
138+ p. push ( MAIN_SEPARATOR ) ;
139+ }
140+ p
141+ } )
116142}
117143
118144#[ cfg( path_all) ]
119145fn dirname ( path : String ) -> crate :: Result < String > {
120146 match Path :: new ( & path) . parent ( ) {
121- Some ( path ) => Ok ( path . to_string_lossy ( ) . to_string ( ) ) ,
147+ Some ( p ) => Ok ( p . to_string_lossy ( ) . to_string ( ) ) ,
122148 None => Err ( crate :: Error :: FailedToExecuteApi ( crate :: api:: Error :: Path (
123149 "Couldn't get the parent directory" . into ( ) ,
124150 ) ) ) ,
@@ -131,7 +157,7 @@ fn extname(path: String) -> crate::Result<String> {
131157 . extension ( )
132158 . and_then ( std:: ffi:: OsStr :: to_str)
133159 {
134- Some ( path ) => Ok ( path . to_string ( ) ) ,
160+ Some ( p ) => Ok ( p . to_string ( ) ) ,
135161 None => Err ( crate :: Error :: FailedToExecuteApi ( crate :: api:: Error :: Path (
136162 "Couldn't get the extension of the file" . into ( ) ,
137163 ) ) ) ,
@@ -144,13 +170,87 @@ fn basename(path: String, ext: Option<String>) -> crate::Result<String> {
144170 . file_name ( )
145171 . and_then ( std:: ffi:: OsStr :: to_str)
146172 {
147- Some ( path ) => Ok ( if let Some ( ext) = ext {
148- path . replace ( ext. as_str ( ) , "" )
173+ Some ( p ) => Ok ( if let Some ( ext) = ext {
174+ p . replace ( ext. as_str ( ) , "" )
149175 } else {
150- path . to_string ( )
176+ p . to_string ( )
151177 } ) ,
152178 None => Err ( crate :: Error :: FailedToExecuteApi ( crate :: api:: Error :: Path (
153179 "Couldn't get the basename" . into ( ) ,
154180 ) ) ) ,
155181 }
156182}
183+
184+ /// Resolve ".." and "." if there is any , this snippet is taken from cargo's paths util
185+ /// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106
186+ #[ cfg( path_all) ]
187+ fn normalize_path ( path : & Path ) -> PathBuf {
188+ let mut components = path. components ( ) . peekable ( ) ;
189+ let mut ret = if let Some ( c @ Component :: Prefix ( ..) ) = components. peek ( ) . cloned ( ) {
190+ components. next ( ) ;
191+ PathBuf :: from ( c. as_os_str ( ) )
192+ } else {
193+ PathBuf :: new ( )
194+ } ;
195+
196+ for component in components {
197+ match component {
198+ Component :: Prefix ( ..) => unreachable ! ( ) ,
199+ Component :: RootDir => {
200+ ret. push ( component. as_os_str ( ) ) ;
201+ }
202+ Component :: CurDir => { }
203+ Component :: ParentDir => {
204+ ret. pop ( ) ;
205+ }
206+ Component :: Normal ( c) => {
207+ ret. push ( c) ;
208+ }
209+ }
210+ }
211+ ret
212+ }
213+
214+ /// Resolve ".." and "." if there is any , this snippet is taken from cargo's paths util but
215+ /// slightly modified to not resolve absolute paths
216+ /// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106
217+ #[ cfg( path_all) ]
218+ fn normalize_path_no_absolute ( path : & Path ) -> PathBuf {
219+ let mut components = path. components ( ) . peekable ( ) ;
220+ let mut ret = if let Some ( c @ Component :: Prefix ( ..) ) = components. peek ( ) . cloned ( ) {
221+ components. next ( ) ;
222+ PathBuf :: from ( c. as_os_str ( ) )
223+ } else {
224+ PathBuf :: new ( )
225+ } ;
226+
227+ for component in components {
228+ match component {
229+ Component :: Prefix ( ..) => unreachable ! ( ) ,
230+ Component :: RootDir => {
231+ ret. push ( component. as_os_str ( ) ) ;
232+ }
233+ Component :: CurDir => { }
234+ Component :: ParentDir => {
235+ ret. pop ( ) ;
236+ }
237+ Component :: Normal ( c) => {
238+ // Using PathBuf::push here will replace the whole path if an absolute path is encountered
239+ // which is not the intended behavior, so instead of that, convert the current resolved path
240+ // to a string and do simple string concatenation with the current component then convert it
241+ // back to a PathBuf
242+ let mut p = ret. to_string_lossy ( ) . to_string ( ) ;
243+ // Don't add the separator if the resolved path is empty,
244+ // otherwise we are gonna have unwanted leading separator
245+ if !p. is_empty ( ) && !p. ends_with ( '/' ) && !p. ends_with ( '\\' ) {
246+ p. push ( MAIN_SEPARATOR ) ;
247+ }
248+ if let Some ( c) = c. to_str ( ) {
249+ p. push_str ( c) ;
250+ }
251+ ret = PathBuf :: from ( p) ;
252+ }
253+ }
254+ }
255+ ret
256+ }
0 commit comments