@@ -20,6 +20,7 @@ use crate::zipcrypto::{ZipCryptoReader, ZipCryptoReaderValid, ZipCryptoValidator
2020use crate :: ZIP64_BYTES_THR ;
2121use indexmap:: IndexMap ;
2222use std:: borrow:: Cow ;
23+ use std:: collections:: BTreeMap ;
2324use std:: ffi:: OsStr ;
2425use std:: fs:: create_dir_all;
2526use std:: io:: { self , copy, prelude:: * , sink, SeekFrom } ;
@@ -536,6 +537,33 @@ impl<'a> TryFrom<&'a CentralDirectoryEndInfo> for CentralDirectoryInfo {
536537 }
537538}
538539
540+ /// Store all entries which specify a numeric "mode" which is familiar to POSIX operating systems.
541+ #[ cfg( unix) ]
542+ #[ derive( Default , Debug ) ]
543+ struct FilesByUnixMode {
544+ map : BTreeMap < PathBuf , u32 > ,
545+ }
546+
547+ #[ cfg( unix) ]
548+ impl FilesByUnixMode {
549+ pub fn add_mode ( & mut self , path : PathBuf , mode : u32 ) {
550+ // We don't print a warning or consider it remotely out of the ordinary to receive two
551+ // separate modes for the same path: just take the later one.
552+ let _ = self . map . insert ( path, mode) ;
553+ }
554+
555+ // Child nodes will be sorted later lexicographically, so reversing the order puts them first.
556+ pub fn all_perms_with_children_first (
557+ self ,
558+ ) -> impl IntoIterator < Item = ( PathBuf , std:: fs:: Permissions ) > {
559+ use std:: os:: unix:: fs:: PermissionsExt ;
560+ self . map
561+ . into_iter ( )
562+ . rev ( )
563+ . map ( |( p, m) | ( p, std:: fs:: Permissions :: from_mode ( m) ) )
564+ }
565+ }
566+
539567impl < R > ZipArchive < R > {
540568 pub ( crate ) fn from_finalized_writer (
541569 files : IndexMap < Box < str > , ZipFileData > ,
@@ -885,33 +913,30 @@ impl<R: Read + Seek> ZipArchive<R> {
885913 . transpose ( ) ?;
886914
887915 #[ cfg( unix) ]
888- let mut files_by_unix_mode = Vec :: new ( ) ;
916+ let mut files_by_unix_mode = FilesByUnixMode :: default ( ) ;
889917
890918 for i in 0 ..self . len ( ) {
891919 let mut file = self . by_index ( i) ?;
892920
893921 let mut outpath = directory. clone ( ) ;
922+ /* TODO: the control flow of this method call and subsequent expectations about the
923+ * values in this loop is extremely difficult to follow. It also appears to
924+ * perform a nested loop upon extracting every single file entry? Why does it
925+ * accept two arguments that point to the same directory path, one mutable? */
894926 file. safe_prepare_path ( directory. as_ref ( ) , & mut outpath, root_dir. as_ref ( ) ) ?;
895927
896928 let mut symlink_target: Option < Vec < u8 > > = None ;
897- cfg_if ! {
898- if #[ cfg( any( unix, windows) ) ] {
899- if file. is_symlink( ) {
900- let mut target = Vec :: with_capacity( file. size( ) as usize ) ;
901- file. read_to_end( & mut target) ?;
902- symlink_target = Some ( target) ;
903- }
904- } else {
905- if file. is_symlink( ) {
906- todo!( "no support for symlinks outside of unix or windows platforms" ) ;
907- }
908- }
929+ #[ cfg( any( unix, windows) ) ]
930+ if file. is_symlink ( ) {
931+ let mut target = Vec :: with_capacity ( file. size ( ) as usize ) ;
932+ file. read_to_end ( & mut target) ?;
933+ symlink_target = Some ( target) ;
909934 }
935+
910936 if symlink_target. is_none ( ) && file. is_dir ( ) {
911937 crate :: read:: make_writable_dir_all ( & outpath) ?;
912938 continue ;
913939 }
914-
915940 if let Some ( target) = symlink_target {
916941 drop ( file) ;
917942 make_symlink ( & outpath, & target, & self . shared . files ) ?;
@@ -920,36 +945,28 @@ impl<R: Read + Seek> ZipArchive<R> {
920945 let mut outfile = fs:: File :: create ( & outpath) ?;
921946
922947 io:: copy ( & mut file, & mut outfile) ?;
948+
949+ // Check for real permissions, which we'll set in a second pass.
923950 #[ cfg( unix) ]
924- {
925- // Check for real permissions, which we'll set in a second pass
926- if let Some ( mode) = file. unix_mode ( ) {
927- files_by_unix_mode. push ( ( outpath. clone ( ) , mode) ) ;
928- }
951+ if let Some ( mode) = file. unix_mode ( ) {
952+ files_by_unix_mode. add_mode ( outpath, mode) ;
929953 }
954+
955+ // Set original timestamp.
930956 #[ cfg( feature = "chrono" ) ]
931- {
932- // Set original timestamp.
933- if let Some ( last_modified) = file. last_modified ( ) {
934- if let Some ( t) = datetime_to_systemtime ( & last_modified) {
935- outfile. set_modified ( t) ?;
936- }
957+ if let Some ( last_modified) = file. last_modified ( ) {
958+ if let Some ( t) = datetime_to_systemtime ( & last_modified) {
959+ outfile. set_modified ( t) ?;
937960 }
938961 }
939962 }
940- #[ cfg( unix) ]
941- {
942- use std:: cmp:: Reverse ;
943- use std:: os:: unix:: fs:: PermissionsExt ;
944963
945- if files_by_unix_mode. len ( ) > 1 {
946- // Ensure we update children's permissions before making a parent unwritable
947- files_by_unix_mode. sort_by_key ( |( path, _) | Reverse ( path. clone ( ) ) ) ;
948- }
949- for ( path, mode) in files_by_unix_mode. into_iter ( ) {
950- fs:: set_permissions ( & path, fs:: Permissions :: from_mode ( mode) ) ?;
951- }
964+ // Ensure we update children's permissions before making a parent unwritable.
965+ #[ cfg( unix) ]
966+ for ( path, perms) in files_by_unix_mode. all_perms_with_children_first ( ) {
967+ std:: fs:: set_permissions ( path, perms) ?;
952968 }
969+
953970 Ok ( ( ) )
954971 }
955972
0 commit comments