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  }