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 }