@@ -2,6 +2,7 @@ use crate::{
2
2
error:: TarError , header:: bytes2path, other, pax:: pax_extensions, Archive , Header , PaxExtensions ,
3
3
} ;
4
4
use filetime:: { self , FileTime } ;
5
+ use rustc_hash:: FxHashSet ;
5
6
use std:: {
6
7
borrow:: Cow ,
7
8
cmp,
@@ -26,7 +27,7 @@ use tokio::{
26
27
/// be inspected. It acts as a file handle by implementing the Reader trait. An
27
28
/// entry cannot be rewritten once inserted into an archive.
28
29
pub struct Entry < R : Read + Unpin > {
29
- fields : EntryFields < R > ,
30
+ pub ( crate ) fields : EntryFields < R > ,
30
31
_ignored : marker:: PhantomData < Archive < R > > ,
31
32
}
32
33
@@ -274,7 +275,9 @@ impl<R: Read + Unpin> Entry<R> {
274
275
/// # Ok(()) }) }
275
276
/// ```
276
277
pub async fn unpack_in < P : AsRef < Path > > ( & mut self , dst : P ) -> io:: Result < Option < PathBuf > > {
277
- self . fields . unpack_in ( dst. as_ref ( ) ) . await
278
+ self . fields
279
+ . unpack_in ( dst. as_ref ( ) , & mut FxHashSet :: default ( ) )
280
+ . await
278
281
}
279
282
280
283
/// Indicate whether extended file attributes (xattrs on Unix) are preserved
@@ -426,7 +429,20 @@ impl<R: Read + Unpin> EntryFields<R> {
426
429
Ok ( Some ( pax_extensions ( self . pax_extensions . as_ref ( ) . unwrap ( ) ) ) )
427
430
}
428
431
429
- async fn unpack_in ( & mut self , dst : & Path ) -> io:: Result < Option < PathBuf > > {
432
+ /// Unpack the [`Entry`] into the specified destination.
433
+ ///
434
+ /// It's assumed that `dst` is already canonicalized, and that the memoized set of validated
435
+ /// paths are tied to `dst`.
436
+ pub ( crate ) async fn unpack_in (
437
+ & mut self ,
438
+ dst : & Path ,
439
+ memo : & mut FxHashSet < PathBuf > ,
440
+ ) -> io:: Result < Option < PathBuf > > {
441
+ // It's assumed that `dst` is already canonicalized.
442
+ debug_assert ! ( dst
443
+ . canonicalize( )
444
+ . is_ok_and( |canon_target| canon_target == dst) ) ;
445
+
430
446
// Notes regarding bsdtar 2.8.3 / libarchive 2.8.3:
431
447
// * Leading '/'s are trimmed. For example, `///test` is treated as
432
448
// `test`.
@@ -478,13 +494,16 @@ impl<R: Read + Unpin> EntryFields<R> {
478
494
None => return Ok ( None ) ,
479
495
} ;
480
496
481
- self . ensure_dir_created ( dst, parent)
482
- . await
483
- . map_err ( |e| TarError :: new ( & format ! ( "failed to create `{}`" , parent. display( ) ) , e) ) ?;
484
-
485
- let canon_target = self . validate_inside_dst ( dst, parent) . await ?;
497
+ // Validate the parent, if we haven't seen it yet.
498
+ if !memo. contains ( parent) {
499
+ self . ensure_dir_created ( dst, parent) . await . map_err ( |e| {
500
+ TarError :: new ( & format ! ( "failed to create `{}`" , parent. display( ) ) , e)
501
+ } ) ?;
502
+ self . validate_inside_dst ( dst, parent) . await ?;
503
+ memo. insert ( parent. to_path_buf ( ) ) ;
504
+ }
486
505
487
- self . unpack ( Some ( & canon_target ) , & file_dst)
506
+ self . unpack ( Some ( & parent ) , & file_dst)
488
507
. await
489
508
. map_err ( |e| TarError :: new ( & format ! ( "failed to unpack `{}`" , file_dst. display( ) ) , e) ) ?;
490
509
@@ -890,32 +909,26 @@ impl<R: Read + Unpin> EntryFields<R> {
890
909
Ok ( ( ) )
891
910
}
892
911
893
- async fn validate_inside_dst ( & self , dst : & Path , file_dst : & Path ) -> io:: Result < PathBuf > {
912
+ async fn validate_inside_dst ( & self , dst : & Path , file_dst : & Path ) -> io:: Result < ( ) > {
894
913
// Abort if target (canonical) parent is outside of `dst`
895
914
let canon_parent = file_dst. canonicalize ( ) . map_err ( |err| {
896
915
Error :: new (
897
916
err. kind ( ) ,
898
917
format ! ( "{} while canonicalizing {}" , err, file_dst. display( ) ) ,
899
918
)
900
919
} ) ?;
901
- let canon_target = dst. canonicalize ( ) . map_err ( |err| {
902
- Error :: new (
903
- err. kind ( ) ,
904
- format ! ( "{} while canonicalizing {}" , err, dst. display( ) ) ,
905
- )
906
- } ) ?;
907
- if !canon_parent. starts_with ( & canon_target) {
920
+ if !canon_parent. starts_with ( & dst) {
908
921
let err = TarError :: new (
909
922
& format ! (
910
923
"trying to unpack outside of destination path: {}" ,
911
- canon_target . display( )
924
+ dst . display( )
912
925
) ,
913
926
// TODO: use ErrorKind::InvalidInput here? (minor breaking change)
914
927
Error :: new ( ErrorKind :: Other , "Invalid argument" ) ,
915
928
) ;
916
929
return Err ( err. into ( ) ) ;
917
930
}
918
- Ok ( canon_target )
931
+ Ok ( ( ) )
919
932
}
920
933
}
921
934
0 commit comments