github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/util/unzip.go (about)

     1  package util
     2  
     3  import (
     4  	"archive/zip"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  )
    11  
    12  // Unzips the archvie into the specified directory
    13  // returns an error if a general issue occurred unzipping the archive
    14  func Unzip(src, dest string) error {
    15  	r, err := zip.OpenReader(src)
    16  	if err != nil {
    17  		return err
    18  	}
    19  	defer r.Close()
    20  
    21  	for _, f := range r.File {
    22  		err = extractFile(dest, f)
    23  		if err != nil {
    24  			return err
    25  		}
    26  	}
    27  	return nil
    28  }
    29  
    30  // Unzips the specified files from the archive
    31  // returns an error if any of the specified files are not found or a general issue occurred unzipping the archive
    32  func UnzipSpecificFiles(src, dest string, onlyFiles ...string) error {
    33  	r, err := zip.OpenReader(src)
    34  	if err != nil {
    35  		return err
    36  	}
    37  	defer r.Close()
    38  
    39  	m := make(map[string]bool)
    40  	for _, f := range onlyFiles {
    41  		m[f] = false
    42  	}
    43  
    44  	for _, f := range r.File {
    45  		name := f.Name
    46  		if _, matched := m[name]; matched {
    47  			err = extractFile(dest, f)
    48  			if err != nil {
    49  				return err
    50  			}
    51  			m[name] = true
    52  		}
    53  	}
    54  
    55  	// check we unzip all the specified files
    56  	failed := false
    57  	errString := ""
    58  	for f, b := range m {
    59  		if !b {
    60  			if failed {
    61  				errString += ", " + f
    62  			} else {
    63  				errString += ", " + f
    64  				failed = true
    65  			}
    66  		}
    67  	}
    68  	if failed {
    69  		return fmt.Errorf("the specified files where not found within the zip [%s]", errString)
    70  	}
    71  
    72  	return nil
    73  }
    74  
    75  // extract the specific file into the destination directory.
    76  func extractFile(dest string, f *zip.File) error {
    77  	name := filepath.Join(dest, f.Name) // #nosec
    78  	// We need to be secure to prevent attacks like
    79  	// https://snyk.io/blog/zip-slip-vulnerability
    80  	// the result is already 'Clean'ed so we only need to check the string starts
    81  	if !strings.HasPrefix(name, dest) {
    82  		return fmt.Errorf("refusing to unzip %s due to escaping out of expected directory", f.Name)
    83  	}
    84  
    85  	rc, err := f.Open()
    86  	if err != nil {
    87  		return err
    88  	}
    89  	defer rc.Close()
    90  
    91  	if f.FileInfo().IsDir() {
    92  		err := os.MkdirAll(name, os.ModePerm)
    93  		if err != nil {
    94  			return err
    95  		}
    96  	} else {
    97  		var fdir string
    98  		if lastIndex := strings.LastIndex(name, string(os.PathSeparator)); lastIndex > -1 {
    99  			fdir = name[:lastIndex]
   100  		}
   101  
   102  		err = os.MkdirAll(fdir, os.ModePerm)
   103  		if err != nil {
   104  			return err
   105  		}
   106  		f, err := os.OpenFile(
   107  			name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
   108  		if err != nil {
   109  			return err
   110  		}
   111  		defer f.Close()
   112  
   113  		limited := io.LimitReader(rc, 100*1024*1024)
   114  		_, err = io.Copy(f, limited)
   115  		if err != nil {
   116  			return err
   117  		}
   118  	}
   119  	return nil
   120  }