@@ -501,35 +501,85 @@ func ZipDirectory(destination string, source string) (err error) {
501501 // Get base name for zip structure
502502 baseName := strings .TrimSuffix (filepath .Base (destination ), ".zip" )
503503
504+ // First pass: add the root directory with its modification time
505+ rootInfo , err := os .Stat (source )
506+ if err == nil && rootInfo .IsDir () {
507+ header , err := zip .FileInfoHeader (rootInfo )
508+ if err != nil {
509+ log .Error (err )
510+ } else {
511+ header .Name = baseName + "/" // Trailing slash indicates directory
512+ header .Method = zip .Store
513+ header .Modified = rootInfo .ModTime ()
514+
515+ _ , err = writer .CreateHeader (header )
516+ if err != nil {
517+ log .Error (err )
518+ } else {
519+ fmt .Fprintf (os .Stderr , "\r \033 [2K" )
520+ fmt .Fprintf (os .Stderr , "\r Adding %s" , baseName + "/" )
521+ }
522+ }
523+ }
524+
525+ // Second pass: add all other directories and files
504526 err = filepath .Walk (source , func (path string , info os.FileInfo , err error ) error {
505527 if err != nil {
506528 log .Error (err )
507529 return nil
508530 }
509- if info .Mode ().IsRegular () {
510- f1 , err := os .Open (path )
531+
532+ // Skip root directory (we already added it)
533+ if path == source {
534+ return nil
535+ }
536+
537+ // Calculate relative path from source directory
538+ relPath , err := filepath .Rel (source , path )
539+ if err != nil {
540+ log .Error (err )
541+ return nil
542+ }
543+
544+ // Create zip path with base name structure
545+ zipPath := filepath .Join (baseName , relPath )
546+ zipPath = filepath .ToSlash (zipPath )
547+
548+ if info .IsDir () {
549+ // Add directory entry to zip with original modification time
550+ header , err := zip .FileInfoHeader (info )
511551 if err != nil {
512552 log .Error (err )
513553 return nil
514554 }
555+ header .Name = zipPath + "/" // Trailing slash indicates directory
556+ header .Method = zip .Store
557+ // Preserve the original modification time
558+ header .Modified = info .ModTime ()
515559
516- // Calculate relative path from source directory
517- relPath , err := filepath .Rel (source , path )
560+ _ , err = writer .CreateHeader (header )
518561 if err != nil {
519562 log .Error (err )
520- f1 .Close ()
521563 return nil
522564 }
523565
524- // Create zip path with base name structure
525- zipPath := filepath .Join (baseName , relPath )
526- zipPath = filepath .ToSlash (zipPath )
566+ fmt .Fprintf (os .Stderr , "\r \033 [2K" )
567+ fmt .Fprintf (os .Stderr , "\r Adding %s" , zipPath + "/" )
568+ return nil
569+ }
570+
571+ if info .Mode ().IsRegular () {
572+ f1 , err := os .Open (path )
573+ if err != nil {
574+ log .Error (err )
575+ return nil
576+ }
577+ defer f1 .Close ()
527578
528579 // Create file header with modified time
529580 header , err := zip .FileInfoHeader (info )
530581 if err != nil {
531582 log .Error (err )
532- f1 .Close ()
533583 return nil
534584 }
535585 header .Name = zipPath
@@ -538,23 +588,20 @@ func ZipDirectory(destination string, source string) (err error) {
538588 w1 , err := writer .CreateHeader (header )
539589 if err != nil {
540590 log .Error (err )
541- f1 .Close ()
542591 return nil
543592 }
544593
545594 if _ , err := io .Copy (w1 , f1 ); err != nil {
546595 log .Error (err )
547- f1 .Close ()
548596 return nil
549597 }
550598
551- f1 .Close ()
552-
553599 fmt .Fprintf (os .Stderr , "\r \033 [2K" )
554600 fmt .Fprintf (os .Stderr , "\r Adding %s" , zipPath )
555601 }
556602 return nil
557603 })
604+
558605 if err != nil {
559606 log .Error (err )
560607 return fmt .Errorf ("error during directory walk: %w" , err )
@@ -571,17 +618,32 @@ func UnzipDirectory(destination string, source string) error {
571618 }
572619 defer archive .Close ()
573620
621+ // Store modification times for all files and directories
622+ modTimes := make (map [string ]time.Time )
623+
624+ // First pass: extract all files and directories, store modification times
574625 for _ , f := range archive .File {
575626 filePath := filepath .Join (destination , f .Name )
576627 fmt .Fprintf (os .Stderr , "\r \033 [2K" )
577628 fmt .Fprintf (os .Stderr , "\r Unzipping file %s" , filePath )
629+
578630 // Issue #593 conceal path traversal vulnerability
579631 // make sure the filepath does not have ".."
580632 filePath = filepath .Clean (filePath )
581633 if strings .Contains (filePath , ".." ) {
582634 log .Errorf ("Invalid file path %s\n " , filePath )
583- continue // Skip file but continue extraction
635+ continue
636+ }
637+
638+ // Store modification time for this entry (BOTH files and directories)
639+ modifiedTime := f .Modified
640+ if modifiedTime .IsZero () {
641+ modifiedTime = f .FileHeader .Modified
584642 }
643+ if ! modifiedTime .IsZero () {
644+ modTimes [filePath ] = modifiedTime
645+ }
646+
585647 if f .FileInfo ().IsDir () {
586648 if err := os .MkdirAll (filePath , os .ModePerm ); err != nil {
587649 log .Error (err )
@@ -623,15 +685,18 @@ func UnzipDirectory(destination string, source string) error {
623685
624686 dstFile .Close ()
625687 fileInArchive .Close ()
688+ }
626689
627- // Set modified time from zip file header
628- modifiedTime := f .Modified
629- if modifiedTime .IsZero () {
630- modifiedTime = f .FileHeader .Modified
631- }
632- if ! modifiedTime .IsZero () {
633- if err := os .Chtimes (filePath , modifiedTime , modifiedTime ); err != nil {
634- log .Error (err )
690+ // Second pass: restore modification times for ALL files and directories
691+ for path , modTime := range modTimes {
692+ if err := os .Chtimes (path , modTime , modTime ); err != nil {
693+ log .Errorf ("Failed to set modification time for %s: %v" , path , err )
694+ } else {
695+ fi , err := os .Lstat (path )
696+ if err != nil ||
697+ ! modTime .UTC ().Equal (fi .ModTime ().UTC ()) {
698+ log .Errorf ("Failed to set modification time for %s: %v" , path , err )
699+ fmt .Fprintf (os .Stderr , "Failed to set modification time %s %v: %v\n " , path , modTime , err )
635700 }
636701 }
637702 }
0 commit comments