github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/updater/util/unzip.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package util
     5  
     6  import (
     7  	"archive/zip"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  )
    13  
    14  // UnzipOver safely unzips a file and copies it contents to a destination path.
    15  // If destination path exists, it will be removed first.
    16  // The filename must have a ".zip" extension.
    17  // You can specify a check function, which will run before moving the unzipped
    18  // directory into place.
    19  // If you specify a tmpDir and destination path exists, it will be moved there
    20  // instead of being removed.
    21  //
    22  // To unzip Keybase-1.2.3.zip and move the contents Keybase.app to /Applications/Keybase.app
    23  //
    24  //	UnzipOver("/tmp/Keybase-1.2.3.zip", "Keybase.app", "/Applications/Keybase.app", check, "", log)
    25  func UnzipOver(sourcePath string, path string, destinationPath string, check func(sourcePath, destinationPath string) error, tmpDir string, log Log) error {
    26  	unzipPath := fmt.Sprintf("%s.unzipped", sourcePath)
    27  	defer RemoveFileAtPath(unzipPath)
    28  	err := unzipOver(sourcePath, unzipPath, log)
    29  	if err != nil {
    30  		return err
    31  	}
    32  
    33  	contentPath := filepath.Join(unzipPath, path)
    34  
    35  	err = check(contentPath, destinationPath)
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	return MoveFile(contentPath, destinationPath, tmpDir, log)
    41  }
    42  
    43  // UnzipPath unzips and returns path to unzipped directory
    44  func UnzipPath(sourcePath string, log Log) (string, error) {
    45  	unzipPath := fmt.Sprintf("%s.unzipped", sourcePath)
    46  	err := unzipOver(sourcePath, unzipPath, log)
    47  	if err != nil {
    48  		return "", err
    49  	}
    50  	return unzipPath, nil
    51  }
    52  
    53  func unzipOver(sourcePath string, destinationPath string, log Log) error {
    54  	if destinationPath == "" {
    55  		return fmt.Errorf("Invalid destination %q", destinationPath)
    56  	}
    57  
    58  	if _, ferr := os.Stat(destinationPath); ferr == nil {
    59  		log.Infof("Removing existing unzip destination path: %s", destinationPath)
    60  		err := os.RemoveAll(destinationPath)
    61  		if err != nil {
    62  			return err
    63  		}
    64  	}
    65  
    66  	log.Infof("Unzipping %q to %q", sourcePath, destinationPath)
    67  	return Unzip(sourcePath, destinationPath, log)
    68  }
    69  
    70  // Unzip unpacks a zip file to a destination.
    71  // This unpacks files using the current user and time (it doesn't preserve).
    72  // This code was modified from https://stackoverflow.com/questions/20357223/easy-way-to-unzip-file-with-golang/20357902
    73  func Unzip(sourcePath, destinationPath string, log Log) error {
    74  	r, err := zip.OpenReader(sourcePath)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	defer func() {
    79  		if closeErr := r.Close(); closeErr != nil {
    80  			log.Warningf("Error in unzip closing zip file: %s", closeErr)
    81  		}
    82  	}()
    83  
    84  	err = os.MkdirAll(destinationPath, 0755)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	// Closure to address file descriptors issue with all the deferred .Close() methods
    90  	extractAndWriteFile := func(f *zip.File) error {
    91  		rc, err := f.Open()
    92  		if err != nil {
    93  			return err
    94  		}
    95  		defer func() {
    96  			if err := rc.Close(); err != nil {
    97  				log.Warningf("Error in unzip closing file: %s", err)
    98  			}
    99  		}()
   100  
   101  		filePath := filepath.Join(destinationPath, f.Name)
   102  		fileInfo := f.FileInfo()
   103  
   104  		if fileInfo.IsDir() {
   105  			err := os.MkdirAll(filePath, fileInfo.Mode())
   106  			if err != nil {
   107  				return err
   108  			}
   109  		} else {
   110  			err := os.MkdirAll(filepath.Dir(filePath), 0755)
   111  			if err != nil {
   112  				return err
   113  			}
   114  
   115  			if fileInfo.Mode()&os.ModeSymlink != 0 {
   116  				linkName, readErr := io.ReadAll(rc)
   117  				if readErr != nil {
   118  					return readErr
   119  				}
   120  				return os.Symlink(string(linkName), filePath)
   121  			}
   122  
   123  			fileCopy, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, fileInfo.Mode())
   124  			if err != nil {
   125  				return err
   126  			}
   127  			defer Close(fileCopy)
   128  
   129  			_, err = io.Copy(fileCopy, rc)
   130  			if err != nil {
   131  				return err
   132  			}
   133  		}
   134  
   135  		return nil
   136  	}
   137  
   138  	for _, f := range r.File {
   139  		err := extractAndWriteFile(f)
   140  		if err != nil {
   141  			return err
   142  		}
   143  	}
   144  
   145  	return nil
   146  }