github.com/sleungcy-sap/cli@v7.1.0+incompatible/cf/appfiles/zipper.go (about)

     1  package appfiles
     2  
     3  import (
     4  	"archive/zip"
     5  	"bufio"
     6  	"bytes"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  
    12  	"code.cloudfoundry.org/cli/cf/errors"
    13  	"code.cloudfoundry.org/gofileutils/fileutils"
    14  	securejoin "github.com/cyphar/filepath-securejoin"
    15  )
    16  
    17  //go:generate counterfeiter . Zipper
    18  
    19  type Zipper interface {
    20  	Zip(dirToZip string, targetFile *os.File) (err error)
    21  	IsZipFile(path string) bool
    22  	Unzip(appDir string, destDir string) (err error)
    23  	GetZipSize(zipFile *os.File) (int64, error)
    24  }
    25  
    26  type ApplicationZipper struct{}
    27  
    28  func (zipper ApplicationZipper) Zip(dirOrZipFilePath string, targetFile *os.File) error {
    29  	if zipper.IsZipFile(dirOrZipFilePath) {
    30  		zipFile, err := os.Open(dirOrZipFilePath)
    31  		if err != nil {
    32  			return err
    33  		}
    34  		defer zipFile.Close()
    35  
    36  		_, err = io.Copy(targetFile, zipFile)
    37  		if err != nil {
    38  			return err
    39  		}
    40  	} else {
    41  		err := writeZipFile(dirOrZipFilePath, targetFile)
    42  		if err != nil {
    43  			return err
    44  		}
    45  	}
    46  
    47  	_, err := targetFile.Seek(0, os.SEEK_SET)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	return nil
    53  }
    54  
    55  func (zipper ApplicationZipper) IsZipFile(name string) bool {
    56  	f, err := os.Open(name)
    57  	if err != nil {
    58  		return false
    59  	}
    60  	defer f.Close()
    61  
    62  	fi, err := f.Stat()
    63  	if err != nil {
    64  		return false
    65  	}
    66  
    67  	if fi.IsDir() {
    68  		return false
    69  	}
    70  
    71  	z, err := zip.OpenReader(name)
    72  	if err != nil && err == zip.ErrFormat {
    73  		return zipper.isZipWithOffsetFileHeaderLocation(name)
    74  	}
    75  	defer z.Close()
    76  
    77  	return err == nil
    78  }
    79  
    80  func (zipper ApplicationZipper) Unzip(name string, destDir string) error {
    81  	rc, err := zip.OpenReader(name)
    82  
    83  	if err == nil {
    84  		defer rc.Close()
    85  		for _, f := range rc.File {
    86  			err = zipper.extractFile(f, destDir)
    87  			if err != nil {
    88  				return err
    89  			}
    90  		}
    91  	}
    92  
    93  	if err == zip.ErrFormat {
    94  		loc, err := zipper.zipFileHeaderLocation(name)
    95  		if err != nil {
    96  			return err
    97  		}
    98  
    99  		if loc > int64(-1) {
   100  			f, err := os.Open(name)
   101  			if err != nil {
   102  				return err
   103  			}
   104  
   105  			defer f.Close()
   106  
   107  			fi, err := f.Stat()
   108  			if err != nil {
   109  				return err
   110  			}
   111  
   112  			readerAt := io.NewSectionReader(f, loc, fi.Size())
   113  			r, err := zip.NewReader(readerAt, fi.Size())
   114  			if err != nil {
   115  				return err
   116  			}
   117  			for _, f := range r.File {
   118  				err := zipper.extractFile(f, destDir)
   119  				if err != nil {
   120  					return err
   121  				}
   122  			}
   123  		}
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  func (zipper ApplicationZipper) GetZipSize(zipFile *os.File) (int64, error) {
   130  	zipFileSize := int64(0)
   131  
   132  	stat, err := zipFile.Stat()
   133  	if err != nil {
   134  		return 0, err
   135  	}
   136  
   137  	zipFileSize = int64(stat.Size())
   138  
   139  	return zipFileSize, nil
   140  }
   141  
   142  func writeZipFile(dir string, targetFile *os.File) error {
   143  	isEmpty, err := fileutils.IsDirEmpty(dir)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	if isEmpty {
   149  		return errors.NewEmptyDirError(dir)
   150  	}
   151  
   152  	writer := zip.NewWriter(targetFile)
   153  	defer writer.Close()
   154  
   155  	appfiles := ApplicationFiles{}
   156  	return appfiles.WalkAppFiles(dir, func(fileName string, fullPath string) error {
   157  		fileInfo, err := os.Stat(fullPath)
   158  		if err != nil {
   159  			return err
   160  		}
   161  
   162  		header, err := zip.FileInfoHeader(fileInfo)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  		if runtime.GOOS == "windows" {
   168  			header.SetMode(header.Mode() | 0700)
   169  		}
   170  
   171  		header.Name = filepath.ToSlash(fileName)
   172  		header.Method = zip.Deflate
   173  
   174  		if fileInfo.IsDir() {
   175  			header.Name += "/"
   176  		}
   177  
   178  		zipFilePart, err := writer.CreateHeader(header)
   179  		if err != nil {
   180  			return err
   181  		}
   182  
   183  		if fileInfo.IsDir() {
   184  			return nil
   185  		}
   186  
   187  		file, err := os.Open(fullPath)
   188  		if err != nil {
   189  			return err
   190  		}
   191  		defer file.Close()
   192  
   193  		_, err = io.Copy(zipFilePart, file)
   194  		if err != nil {
   195  			return err
   196  		}
   197  
   198  		return nil
   199  	})
   200  }
   201  
   202  func (zipper ApplicationZipper) zipFileHeaderLocation(name string) (int64, error) {
   203  	f, err := os.Open(name)
   204  	if err != nil {
   205  		return -1, err
   206  	}
   207  
   208  	defer f.Close()
   209  
   210  	// zip file header signature, 0x04034b50, reversed due to little-endian byte order
   211  	firstByte := byte(0x50)
   212  	restBytes := []byte{0x4b, 0x03, 0x04}
   213  	count := int64(-1)
   214  	foundAt := int64(-1)
   215  
   216  	reader := bufio.NewReader(f)
   217  
   218  	keepGoing := true
   219  	for keepGoing {
   220  		count++
   221  
   222  		b, err := reader.ReadByte()
   223  		if err != nil {
   224  			keepGoing = false
   225  			break
   226  		}
   227  
   228  		if b == firstByte {
   229  			nextBytes, err := reader.Peek(3)
   230  			if err != nil {
   231  				keepGoing = false
   232  			}
   233  			if bytes.Compare(nextBytes, restBytes) == 0 {
   234  				foundAt = count
   235  				keepGoing = false
   236  				break
   237  			}
   238  		}
   239  	}
   240  
   241  	return foundAt, nil
   242  }
   243  
   244  func (zipper ApplicationZipper) isZipWithOffsetFileHeaderLocation(name string) bool {
   245  	loc, err := zipper.zipFileHeaderLocation(name)
   246  	if err != nil {
   247  		return false
   248  	}
   249  
   250  	if loc > int64(-1) {
   251  		f, err := os.Open(name)
   252  		if err != nil {
   253  			return false
   254  		}
   255  
   256  		defer f.Close()
   257  
   258  		fi, err := f.Stat()
   259  		if err != nil {
   260  			return false
   261  		}
   262  
   263  		readerAt := io.NewSectionReader(f, loc, fi.Size())
   264  		_, err = zip.NewReader(readerAt, fi.Size())
   265  		if err == nil {
   266  			return true
   267  		}
   268  	}
   269  
   270  	return false
   271  }
   272  
   273  func (zipper ApplicationZipper) extractFile(f *zip.File, destDir string) error {
   274  	if f.FileInfo().IsDir() {
   275  		path, err := securejoin.SecureJoin(destDir, f.Name)
   276  		if err != nil {
   277  			return err
   278  		}
   279  
   280  		err = os.MkdirAll(path, os.ModeDir|os.ModePerm)
   281  		if err != nil {
   282  			return err
   283  		}
   284  		return nil
   285  	}
   286  
   287  	src, err := f.Open()
   288  	if err != nil {
   289  		return err
   290  	}
   291  	defer src.Close()
   292  
   293  	destFilePath, err := securejoin.SecureJoin(destDir, f.Name)
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	err = os.MkdirAll(filepath.Dir(destFilePath), os.ModeDir|os.ModePerm)
   299  	if err != nil {
   300  		return err
   301  	}
   302  
   303  	destFile, err := os.Create(destFilePath)
   304  	if err != nil {
   305  		return err
   306  	}
   307  	defer destFile.Close()
   308  
   309  	_, err = io.Copy(destFile, src)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	err = os.Chmod(destFilePath, f.FileInfo().Mode())
   315  	if err != nil {
   316  		return err
   317  	}
   318  
   319  	return nil
   320  }