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