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