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 }