9
9
//
10
10
11
11
use std:: collections:: BTreeMap ;
12
+ use std:: path:: Component ;
12
13
use std:: path:: Path ;
13
14
use std:: path:: PathBuf ;
14
15
@@ -38,6 +39,62 @@ impl From<BTreeMap<(PackageName, PackageVersion), Package>> for Repository {
38
39
}
39
40
}
40
41
42
+ // A helper function to normalize relative Unix paths (ensures that one cannot escape using `..`):
43
+ fn normalize_relative_path ( path : PathBuf ) -> Result < PathBuf > {
44
+ let mut normalized_path = PathBuf :: new ( ) ;
45
+ for component in path. components ( ) {
46
+ match component {
47
+ Component :: Prefix ( _) => {
48
+ // "A Windows path prefix, e.g., C: or \\server\share."
49
+ // "Does not occur on Unix."
50
+ return Err ( anyhow ! (
51
+ "The relative path \" {}\" starts with a Windows path prefix" ,
52
+ path. display( )
53
+ ) ) ;
54
+ }
55
+ Component :: RootDir => {
56
+ // "The root directory component, appears after any prefix and before anything else.
57
+ // It represents a separator that designates that a path starts from root."
58
+ return Err ( anyhow ! (
59
+ "The relative path \" {}\" starts from the root directory" ,
60
+ path. display( )
61
+ ) ) ;
62
+ }
63
+ Component :: CurDir => {
64
+ // "A reference to the current directory, i.e., `.`."
65
+ // Also (from `Path.components()`): "Occurrences of . are normalized away, except
66
+ // if they are at the beginning of the path. For example, a/./b, a/b/, a/b/. and
67
+ // a/b all have a and b as components, but ./a/b starts with an additional CurDir
68
+ // component."
69
+ // -> May only occur as the first path component and we can ignore it / normalize
70
+ // it away (we should just ensure that it's not the only path component in which
71
+ // case the path would be empty).
72
+ }
73
+ Component :: ParentDir => {
74
+ // "A reference to the parent directory, i.e., `..`."
75
+ if !normalized_path. pop ( ) {
76
+ return Err ( anyhow ! (
77
+ "The relative path \" {}\" uses `..` to escape the base directory" ,
78
+ path. display( )
79
+ ) ) ;
80
+ }
81
+ }
82
+ Component :: Normal ( component) => {
83
+ // "A normal component, e.g., a and b in a/b. This variant is the most common one,
84
+ // it represents references to files or directories."
85
+ normalized_path. push ( component) ;
86
+ }
87
+ }
88
+ }
89
+
90
+ if normalized_path. parent ( ) . is_none ( ) {
91
+ // Optional: Convert "" to ".":
92
+ normalized_path. push ( Component :: CurDir ) ;
93
+ }
94
+
95
+ Ok ( normalized_path)
96
+ }
97
+
41
98
impl Repository {
42
99
fn new ( inner : BTreeMap < ( PackageName , PackageVersion ) , Package > ) -> Self {
43
100
Repository { inner }
@@ -76,6 +133,7 @@ impl Repository {
76
133
}
77
134
}
78
135
136
+ let cwd = std:: env:: current_dir ( ) ?;
79
137
let leaf_files = fsr
80
138
. files ( )
81
139
. par_iter ( )
@@ -113,9 +171,27 @@ impl Repository {
113
171
// `path` is only available in this "iteration".
114
172
patches_after_merge
115
173
. into_iter ( )
116
- // Prepend the path of the directory of the `pkg.toml` file to the name of the patch:
117
174
. map ( |p| if let Some ( current_dir) = path. parent ( ) {
118
- Ok ( current_dir. join ( p) )
175
+ // Prepend the path of the directory of the `pkg.toml` file to
176
+ // the name of the patch file:
177
+ let mut path = current_dir. join ( p) ;
178
+ // Ensure that we use relative paths for the patches (the rest
179
+ // of the code that uses the patches doesn't work correctly
180
+ // with absolute paths):
181
+ if path. is_absolute ( ) {
182
+ // We assume that cwd is part of the prefix (currently, the
183
+ // path comes from `git2::Repository::workdir()` and should
184
+ // always be absolute and start from cwd so this is fine):
185
+ path = path
186
+ . strip_prefix ( & cwd)
187
+ . map ( |p| p. to_owned ( ) )
188
+ . with_context ( || anyhow ! ( "Cannot strip the prefix {} form path {}" , cwd. display( ) , current_dir. display( ) ) ) ?;
189
+ }
190
+ if path. is_absolute ( ) {
191
+ Err ( anyhow ! ( "The path {} is absolute but it should be a relative path." , path. display( ) ) )
192
+ } else {
193
+ normalize_relative_path ( path)
194
+ }
119
195
} else {
120
196
Err ( anyhow ! ( "Path should point to path with parent, but doesn't: {}" , path. display( ) ) )
121
197
} )
@@ -125,11 +201,11 @@ impl Repository {
125
201
. and_then_ok ( |patch| if patch. exists ( ) {
126
202
Ok ( Some ( patch) )
127
203
} else {
128
- Err ( anyhow ! ( "Patch does not exist: {}" , patch. display( ) ) )
129
- . with_context ( || anyhow ! ( "The patch is declared here: {}" , path. display( ) ) )
204
+ Err ( anyhow ! ( "The following patch does not exist: {}" , patch. display( ) ) )
130
205
} )
131
206
. filter_map_ok ( |o| o)
132
- . collect :: < Result < Vec < _ > > > ( ) ?
207
+ . collect :: < Result < Vec < _ > > > ( )
208
+ . with_context ( || anyhow ! ( "Could not process the patches declared here: {}" , path. display( ) ) ) ?
133
209
} ;
134
210
135
211
trace ! ( "Patches after postprocessing merge: {:?}" , patches) ;
@@ -319,19 +395,19 @@ pub mod tests {
319
395
assert_pkg ( & repo, "s" , "19.1" ) ;
320
396
assert_pkg ( & repo, "z" , "26" ) ;
321
397
322
- // Verify the paths of the patches (and "merging"):
398
+ // Verify the paths of the patches (and the base directory "merging"/joining logic plus the
399
+ // normalization of relative paths):
323
400
// The patches are defined as follows:
324
401
// s/pkg.toml: patches = [ "./foo.patch" ]
325
402
// s/19.0/pkg.toml: patches = ["./foo.patch","s190.patch"]
326
403
// s/19.1/pkg.toml: - (no `patches` entry)
327
404
// s/19.2/pkg.toml: patches = ["../foo.patch"]
328
405
// s/19.3/pkg.toml: patches = ["s190.patch"]
329
406
let p = get_pkg ( & repo, "s" , "19.0" ) ;
330
- // Ideally we'd normalize the `./` away:
331
407
assert_eq ! (
332
408
p. patches( ) ,
333
409
& vec![
334
- PathBuf :: from( "examples/packages/repo/s/19.0/./ foo.patch" ) ,
410
+ PathBuf :: from( "examples/packages/repo/s/19.0/foo.patch" ) ,
335
411
PathBuf :: from( "examples/packages/repo/s/19.0/s190.patch" )
336
412
]
337
413
) ;
@@ -341,10 +417,9 @@ pub mod tests {
341
417
& vec![ PathBuf :: from( "examples/packages/repo/s/foo.patch" ) ]
342
418
) ;
343
419
let p = get_pkg ( & repo, "s" , "19.2" ) ;
344
- // We might want to normalize the `19.2/../` away:
345
420
assert_eq ! (
346
421
p. patches( ) ,
347
- & vec![ PathBuf :: from( "examples/packages/repo/s/19.2/../ foo.patch" ) ]
422
+ & vec![ PathBuf :: from( "examples/packages/repo/s/foo.patch" ) ]
348
423
) ;
349
424
let p = get_pkg ( & repo, "s" , "19.3" ) ;
350
425
assert_eq ! (
@@ -354,4 +429,28 @@ pub mod tests {
354
429
355
430
Ok ( ( ) )
356
431
}
432
+
433
+ #[ test]
434
+ fn test_relative_path_normalization ( ) -> Result < ( ) > {
435
+ assert ! ( normalize_relative_path( PathBuf :: from( "/root" ) ) . is_err( ) ) ;
436
+ assert ! ( normalize_relative_path( PathBuf :: from( "a/../../root" ) ) . is_err( ) ) ;
437
+ assert_eq ! (
438
+ normalize_relative_path( PathBuf :: from( "" ) ) ?,
439
+ PathBuf :: from( "." )
440
+ ) ;
441
+ assert_eq ! (
442
+ normalize_relative_path( PathBuf :: from( "." ) ) ?,
443
+ PathBuf :: from( "." )
444
+ ) ;
445
+ assert_eq ! (
446
+ normalize_relative_path( PathBuf :: from( "./a//b/../b/./c/." ) ) ?,
447
+ PathBuf :: from( "a/b/c" )
448
+ ) ;
449
+ assert_eq ! (
450
+ normalize_relative_path( PathBuf :: from( "./a//../b/" ) ) ?,
451
+ PathBuf :: from( "b" )
452
+ ) ;
453
+
454
+ Ok ( ( ) )
455
+ }
357
456
}
0 commit comments