@@ -536,6 +536,33 @@ impl<'a> TryFrom<&'a CentralDirectoryEndInfo> for CentralDirectoryInfo {
536536 }
537537}
538538
539+ /// Store all entries which specify a numeric "mode" which is familiar to POSIX operating systems.
540+ #[ cfg( unix) ]
541+ #[ derive( Default , Debug ) ]
542+ struct FilesByUnixMode {
543+ map : std:: collections:: BTreeMap < PathBuf , u32 > ,
544+ }
545+
546+ #[ cfg( unix) ]
547+ impl FilesByUnixMode {
548+ pub fn add_mode ( & mut self , path : PathBuf , mode : u32 ) {
549+ // We don't print a warning or consider it remotely out of the ordinary to receive two
550+ // separate modes for the same path: just take the later one.
551+ let _ = self . map . insert ( path, mode) ;
552+ }
553+
554+ // Child nodes will be sorted later lexicographically, so reversing the order puts them first.
555+ pub fn all_perms_with_children_first (
556+ self ,
557+ ) -> impl IntoIterator < Item = ( PathBuf , std:: fs:: Permissions ) > {
558+ use std:: os:: unix:: fs:: PermissionsExt ;
559+ self . map
560+ . into_iter ( )
561+ . rev ( )
562+ . map ( |( p, m) | ( p, std:: fs:: Permissions :: from_mode ( m) ) )
563+ }
564+ }
565+
539566impl < R > ZipArchive < R > {
540567 pub ( crate ) fn from_finalized_writer (
541568 files : IndexMap < Box < str > , ZipFileData > ,
@@ -885,33 +912,30 @@ impl<R: Read + Seek> ZipArchive<R> {
885912 . transpose ( ) ?;
886913
887914 #[ cfg( unix) ]
888- let mut files_by_unix_mode = Vec :: new ( ) ;
915+ let mut files_by_unix_mode = FilesByUnixMode :: default ( ) ;
889916
890917 for i in 0 ..self . len ( ) {
891918 let mut file = self . by_index ( i) ?;
892919
893920 let mut outpath = directory. clone ( ) ;
921+ /* TODO: the control flow of this method call and subsequent expectations about the
922+ * values in this loop is extremely difficult to follow. It also appears to
923+ * perform a nested loop upon extracting every single file entry? Why does it
924+ * accept two arguments that point to the same directory path, one mutable? */
894925 file. safe_prepare_path ( directory. as_ref ( ) , & mut outpath, root_dir. as_ref ( ) ) ?;
895926
896927 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- }
928+ #[ cfg( any( unix, windows) ) ]
929+ if file. is_symlink ( ) {
930+ let mut target = Vec :: with_capacity ( file. size ( ) as usize ) ;
931+ file. read_to_end ( & mut target) ?;
932+ symlink_target = Some ( target) ;
909933 }
934+
910935 if symlink_target. is_none ( ) && file. is_dir ( ) {
911936 crate :: read:: make_writable_dir_all ( & outpath) ?;
912937 continue ;
913938 }
914-
915939 if let Some ( target) = symlink_target {
916940 drop ( file) ;
917941 make_symlink ( & outpath, & target, & self . shared . files ) ?;
@@ -920,36 +944,28 @@ impl<R: Read + Seek> ZipArchive<R> {
920944 let mut outfile = fs:: File :: create ( & outpath) ?;
921945
922946 io:: copy ( & mut file, & mut outfile) ?;
947+
948+ // Check for real permissions, which we'll set in a second pass.
923949 #[ 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- }
950+ if let Some ( mode) = file. unix_mode ( ) {
951+ files_by_unix_mode. add_mode ( outpath, mode) ;
929952 }
953+
954+ // Set original timestamp.
930955 #[ 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- }
956+ if let Some ( last_modified) = file. last_modified ( ) {
957+ if let Some ( t) = datetime_to_systemtime ( & last_modified) {
958+ outfile. set_modified ( t) ?;
937959 }
938960 }
939961 }
940- #[ cfg( unix) ]
941- {
942- use std:: cmp:: Reverse ;
943- use std:: os:: unix:: fs:: PermissionsExt ;
944962
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- }
963+ // Ensure we update children's permissions before making a parent unwritable.
964+ #[ cfg( unix) ]
965+ for ( path, perms) in files_by_unix_mode. all_perms_with_children_first ( ) {
966+ std:: fs:: set_permissions ( path, perms) ?;
952967 }
968+
953969 Ok ( ( ) )
954970 }
955971
0 commit comments