Skip to content

Commit 738a80c

Browse files
authored
Save modified time of files to zip, restore modified time of files on… (#999)
* Save modified time of files to zip, restore modified time of files on unzip. Tests. Fix RemoveMarkedFiles. * Store to zip modification time of directories. Restore modification times on unzip. Tests.
1 parent 74e69e9 commit 738a80c

File tree

2 files changed

+514
-5
lines changed

2 files changed

+514
-5
lines changed

src/utils/utils.go

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,11 +475,20 @@ func IsLocalIP(ipaddress string) bool {
475475
func 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, "\rAdding %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, "\rAdding %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, "\rAdding %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, "\rUnzipping 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

Comments
 (0)