@@ -475,11 +475,20 @@ func IsLocalIP(ipaddress string) bool {
475475func ZipDirectory (destination string , source string ) (err error ) {
476476 if _ , err = os .Stat (destination ); err == nil {
477477 log .Errorf ("%s file already exists!\n " , destination )
478+ return fmt .Errorf ("file already exists: %s" , destination )
478479 }
480+
481+ // Check if source directory exists
482+ if _ , err := os .Stat (source ); os .IsNotExist (err ) {
483+ log .Errorf ("Source directory does not exist: %s" , source )
484+ return fmt .Errorf ("source directory does not exist: %s" , source )
485+ }
486+
479487 fmt .Fprintf (os .Stderr , "Zipping %s to %s\n " , source , destination )
480488 file , err := os .Create (destination )
481489 if err != nil {
482490 log .Error (err )
491+ return fmt .Errorf ("failed to create zip file: %w" , err )
483492 }
484493 defer file .Close ()
485494 writer := zip .NewWriter (file )
@@ -488,32 +497,114 @@ func ZipDirectory(destination string, source string) (err error) {
488497 return flate .NewWriter (out , flate .NoCompression )
489498 })
490499 defer writer .Close ()
500+
501+ // Get base name for zip structure
502+ baseName := strings .TrimSuffix (filepath .Base (destination ), ".zip" )
503+
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
491526 err = filepath .Walk (source , func (path string , info os.FileInfo , err error ) error {
492527 if err != nil {
493528 log .Error (err )
529+ return nil
530+ }
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
494542 }
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 )
551+ if err != nil {
552+ log .Error (err )
553+ return nil
554+ }
555+ header .Name = zipPath + "/" // Trailing slash indicates directory
556+ header .Method = zip .Store
557+ // Preserve the original modification time
558+ header .Modified = info .ModTime ()
559+
560+ _ , err = writer .CreateHeader (header )
561+ if err != nil {
562+ log .Error (err )
563+ return nil
564+ }
565+
566+ fmt .Fprintf (os .Stderr , "\r \033 [2K" )
567+ fmt .Fprintf (os .Stderr , "\r Adding %s" , zipPath + "/" )
568+ return nil
569+ }
570+
495571 if info .Mode ().IsRegular () {
496572 f1 , err := os .Open (path )
497573 if err != nil {
498574 log .Error (err )
575+ return nil
499576 }
500577 defer f1 .Close ()
501- zipPath := strings .ReplaceAll (path , source , strings .TrimSuffix (destination , ".zip" ))
502- zipPath = filepath .ToSlash (zipPath )
503- w1 , err := writer .Create (zipPath )
578+
579+ // Create file header with modified time
580+ header , err := zip .FileInfoHeader (info )
581+ if err != nil {
582+ log .Error (err )
583+ return nil
584+ }
585+ header .Name = zipPath
586+ header .Method = zip .Deflate
587+
588+ w1 , err := writer .CreateHeader (header )
504589 if err != nil {
505590 log .Error (err )
591+ return nil
506592 }
593+
507594 if _ , err := io .Copy (w1 , f1 ); err != nil {
508595 log .Error (err )
596+ return nil
509597 }
598+
510599 fmt .Fprintf (os .Stderr , "\r \033 [2K" )
511600 fmt .Fprintf (os .Stderr , "\r Adding %s" , zipPath )
512601 }
513602 return nil
514603 })
604+
515605 if err != nil {
516606 log .Error (err )
607+ return fmt .Errorf ("error during directory walk: %w" , err )
517608 }
518609 fmt .Fprintf (os .Stderr , "\n " )
519610 return nil
@@ -523,26 +614,46 @@ func UnzipDirectory(destination string, source string) error {
523614 archive , err := zip .OpenReader (source )
524615 if err != nil {
525616 log .Error (err )
617+ return fmt .Errorf ("failed to open zip file: %w" , err )
526618 }
527619 defer archive .Close ()
528620
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
529625 for _ , f := range archive .File {
530626 filePath := filepath .Join (destination , f .Name )
531627 fmt .Fprintf (os .Stderr , "\r \033 [2K" )
532628 fmt .Fprintf (os .Stderr , "\r Unzipping file %s" , filePath )
629+
533630 // Issue #593 conceal path traversal vulnerability
534631 // make sure the filepath does not have ".."
535632 filePath = filepath .Clean (filePath )
536633 if strings .Contains (filePath , ".." ) {
537634 log .Errorf ("Invalid file path %s\n " , filePath )
635+ continue
538636 }
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
642+ }
643+ if ! modifiedTime .IsZero () {
644+ modTimes [filePath ] = modifiedTime
645+ }
646+
539647 if f .FileInfo ().IsDir () {
540- os .MkdirAll (filePath , os .ModePerm )
648+ if err := os .MkdirAll (filePath , os .ModePerm ); err != nil {
649+ log .Error (err )
650+ }
541651 continue
542652 }
543653
544654 if err := os .MkdirAll (filepath .Dir (filePath ), os .ModePerm ); err != nil {
545655 log .Error (err )
656+ continue
546657 }
547658
548659 // check if file exists
@@ -558,11 +669,14 @@ func UnzipDirectory(destination string, source string) error {
558669 dstFile , err := os .OpenFile (filePath , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , f .Mode ())
559670 if err != nil {
560671 log .Error (err )
672+ continue
561673 }
562674
563675 fileInArchive , err := f .Open ()
564676 if err != nil {
565677 log .Error (err )
678+ dstFile .Close ()
679+ continue
566680 }
567681
568682 if _ , err := io .Copy (dstFile , fileInArchive ); err != nil {
@@ -572,6 +686,21 @@ func UnzipDirectory(destination string, source string) error {
572686 dstFile .Close ()
573687 fileInArchive .Close ()
574688 }
689+
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 )
700+ }
701+ }
702+ }
703+
575704 fmt .Fprintf (os .Stderr , "\n " )
576705 return nil
577706}
@@ -627,7 +756,6 @@ func RemoveMarkedFiles() (err error) {
627756 if err != nil {
628757 return
629758 }
630- defer f .Close ()
631759 scanner := bufio .NewScanner (f )
632760 for scanner .Scan () {
633761 fname := scanner .Text ()
@@ -636,6 +764,7 @@ func RemoveMarkedFiles() (err error) {
636764 log .Tracef ("Removed %s" , fname )
637765 }
638766 }
767+ f .Close ()
639768 os .Remove (crocRemovalFile )
640769 return
641770}
0 commit comments