@@ -30,7 +30,7 @@ pub struct FileManager {
30
30
impl FileManager {
31
31
pub fn new ( root : & Path ) -> Self {
32
32
Self {
33
- root : root. to_path_buf ( ) ,
33
+ root : root. normalize ( ) ,
34
34
file_map : Default :: default ( ) ,
35
35
id_to_path : Default :: default ( ) ,
36
36
path_to_id : Default :: default ( ) ,
@@ -44,7 +44,7 @@ impl FileManager {
44
44
// TODO: The stdlib path should probably be an absolute path rooted in something people would never create
45
45
file_name. to_path_buf ( )
46
46
} else {
47
- self . resolve_path ( file_name)
47
+ self . root . join ( file_name) . normalize ( )
48
48
} ;
49
49
50
50
// Check that the resolved path already exists in the file map, if it is, we return it.
@@ -99,41 +99,82 @@ impl FileManager {
99
99
100
100
Err ( candidate_files. remove ( 0 ) . as_os_str ( ) . to_str ( ) . unwrap ( ) . to_owned ( ) )
101
101
}
102
+ }
102
103
103
- /// Resolve a path within the FileManager, removing all `.` and `..` segments.
104
- /// Additionally, relative paths will be resolved against the FileManager's root.
105
- pub fn resolve_path ( & self , path : & Path ) -> PathBuf {
106
- // This is a replacement for `std::fs::canonicalize` that doesn't verify the path exists.
107
- //
108
- // Plucked from https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
109
- // Advice from https://www.reddit.com/r/rust/comments/hkkquy/comment/fwtw53s/
110
- let mut components = path. components ( ) . peekable ( ) ;
111
- let mut ret = match components. peek ( ) . cloned ( ) {
112
- Some ( c @ Component :: Prefix ( ..) ) => {
113
- components. next ( ) ;
114
- PathBuf :: from ( c. as_os_str ( ) )
115
- }
116
- Some ( Component :: RootDir ) => PathBuf :: new ( ) ,
117
- // If the first component isn't a RootDir or a Prefix, we know it is relative and needs to be joined to root
118
- _ => self . root . clone ( ) ,
119
- } ;
104
+ pub trait NormalizePath {
105
+ /// Replacement for `std::fs::canonicalize` that doesn't verify the path exists.
106
+ ///
107
+ /// Plucked from https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
108
+ /// Advice from https://www.reddit.com/r/rust/comments/hkkquy/comment/fwtw53s/
109
+ fn normalize ( & self ) -> PathBuf ;
110
+ }
111
+
112
+ impl NormalizePath for PathBuf {
113
+ fn normalize ( & self ) -> PathBuf {
114
+ let components = self . components ( ) ;
115
+ resolve_components ( components)
116
+ }
117
+ }
118
+
119
+ impl NormalizePath for & Path {
120
+ fn normalize ( & self ) -> PathBuf {
121
+ let components = self . components ( ) ;
122
+ resolve_components ( components)
123
+ }
124
+ }
120
125
121
- for component in components {
122
- match component {
123
- Component :: Prefix ( ..) => unreachable ! ( ) ,
124
- Component :: RootDir => {
125
- ret. push ( component. as_os_str ( ) ) ;
126
- }
127
- Component :: CurDir => { }
128
- Component :: ParentDir => {
129
- ret. pop ( ) ;
130
- }
131
- Component :: Normal ( c) => {
132
- ret. push ( c) ;
133
- }
126
+ fn resolve_components < ' a > ( components : impl Iterator < Item = Component < ' a > > ) -> PathBuf {
127
+ let mut components = components. peekable ( ) ;
128
+
129
+ // Preserve path prefix if one exists.
130
+ let mut normalized_path = if let Some ( c @ Component :: Prefix ( ..) ) = components. peek ( ) . cloned ( ) {
131
+ components. next ( ) ;
132
+ PathBuf :: from ( c. as_os_str ( ) )
133
+ } else {
134
+ PathBuf :: new ( )
135
+ } ;
136
+
137
+ for component in components {
138
+ match component {
139
+ Component :: Prefix ( ..) => unreachable ! ( "Path cannot contain multiple prefixes" ) ,
140
+ Component :: RootDir => {
141
+ normalized_path. push ( component. as_os_str ( ) ) ;
142
+ }
143
+ Component :: CurDir => { }
144
+ Component :: ParentDir => {
145
+ normalized_path. pop ( ) ;
146
+ }
147
+ Component :: Normal ( c) => {
148
+ normalized_path. push ( c) ;
134
149
}
135
150
}
136
- ret
151
+ }
152
+
153
+ normalized_path
154
+ }
155
+
156
+ #[ cfg( test) ]
157
+ mod path_normalization {
158
+ use iter_extended:: vecmap;
159
+ use std:: path:: PathBuf ;
160
+
161
+ use crate :: NormalizePath ;
162
+
163
+ #[ test]
164
+ fn normalizes_paths_correctly ( ) {
165
+ // Note that tests are run on unix so prefix handling can't be tested (as these only exist on Windows)
166
+ let test_cases = vecmap (
167
+ [
168
+ ( "/" , "/" ) , // Handles root
169
+ ( "/foo/bar/../baz/../bar" , "/foo/bar" ) , // Handles backtracking
170
+ ( "/././././././././baz" , "/baz" ) , // Removes no-ops
171
+ ] ,
172
+ |( unnormalized, normalized) | ( PathBuf :: from ( unnormalized) , PathBuf :: from ( normalized) ) ,
173
+ ) ;
174
+
175
+ for ( path, expected_result) in test_cases {
176
+ assert_eq ! ( path. normalize( ) , expected_result) ;
177
+ }
137
178
}
138
179
}
139
180
0 commit comments