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 }