github.com/hashicorp/go-getter/v2@v2.2.2/decompress_zip.go (about) 1 package getter 2 3 import ( 4 "archive/zip" 5 "fmt" 6 "os" 7 "path/filepath" 8 ) 9 10 // ZipDecompressor is an implementation of Decompressor that can 11 // decompress zip files. 12 type ZipDecompressor struct { 13 // FileSizeLimit limits the total size of all 14 // decompressed files. 15 // 16 // The zero value means no limit. 17 FileSizeLimit int64 18 19 // FilesLimit limits the number of files that are 20 // allowed to be decompressed. 21 // 22 // The zero value means no limit. 23 FilesLimit int 24 } 25 26 func (d *ZipDecompressor) Decompress(dst, src string, dir bool, umask os.FileMode) error { 27 // If we're going into a directory we should make that first 28 mkdir := dst 29 if !dir { 30 mkdir = filepath.Dir(dst) 31 } 32 if err := os.MkdirAll(mkdir, mode(0755, umask)); err != nil { 33 return err 34 } 35 36 // Open the zip 37 zipR, err := zip.OpenReader(src) 38 if err != nil { 39 return err 40 } 41 defer zipR.Close() 42 43 // Check the zip integrity 44 if len(zipR.File) == 0 { 45 // Empty archive 46 return fmt.Errorf("empty archive: %s", src) 47 } 48 if !dir && len(zipR.File) > 1 { 49 return fmt.Errorf("expected a single file: %s", src) 50 } 51 52 if d.FilesLimit > 0 && len(zipR.File) > d.FilesLimit { 53 return fmt.Errorf("zip archive contains too many files: %d > %d", len(zipR.File), d.FilesLimit) 54 } 55 56 var fileSizeTotal int64 57 58 // Go through and unarchive 59 for _, f := range zipR.File { 60 path := dst 61 if dir { 62 // Disallow parent traversal 63 if containsDotDot(f.Name) { 64 return fmt.Errorf("entry contains '..': %s", f.Name) 65 } 66 67 path = filepath.Join(path, f.Name) 68 } 69 70 fileInfo := f.FileInfo() 71 72 fileSizeTotal += fileInfo.Size() 73 74 if d.FileSizeLimit > 0 && fileSizeTotal > d.FileSizeLimit { 75 return fmt.Errorf("zip archive larger than limit: %d", d.FileSizeLimit) 76 } 77 78 if fileInfo.IsDir() { 79 if !dir { 80 return fmt.Errorf("expected a single file: %s", src) 81 } 82 83 // A directory, just make the directory and continue unarchiving... 84 if err := os.MkdirAll(path, mode(0755, umask)); err != nil { 85 return err 86 } 87 88 continue 89 } 90 91 // Create the enclosing directories if we must. ZIP files aren't 92 // required to contain entries for just the directories so this 93 // can happen. 94 if dir { 95 if err := os.MkdirAll(filepath.Dir(path), mode(0755, umask)); err != nil { 96 return err 97 } 98 } 99 100 // Open the file for reading 101 srcF, err := f.Open() 102 if err != nil { 103 srcF.Close() 104 return err 105 } 106 107 // Size limit is tracked using the returned file info. 108 err = copyReader(path, srcF, f.Mode(), umask, 0) 109 srcF.Close() 110 if err != nil { 111 return err 112 } 113 } 114 115 return nil 116 }