Files
gocryptotrader/common/file/archive/zip.go
Andrew 12159e36fa (COMMON/FILE) Add Zip create/extract support (#411)
* Added Zip archive support

* extended test coverage

* extended test coverage for unzip and zip

* cleaned up Zip() and moved walk to non-exported method increased test coverage

* underscore unused params

* remove unneeded var

* check against correct error and return after

* Errorf

* z -> f

* fixed wording on tests

* handle fine handler close error
2020-01-13 10:55:10 +11:00

182 lines
3.6 KiB
Go

package archive
import (
"archive/zip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
log "github.com/thrasher-corp/gocryptotrader/logger"
)
const (
// ErrUnableToCloseFile message to display when file handler is unable to be closed normally
ErrUnableToCloseFile string = "Unable to close file %v %v"
)
var (
addFilesToZip func(z *zip.Writer, src string, isDir bool) error
)
func init() {
addFilesToZip = addFilesToZipWrapper
}
// UnZip extracts input zip into dest path
func UnZip(src, dest string) (fileList []string, err error) {
z, err := zip.OpenReader(src)
if err != nil {
return
}
for x := range z.File {
fPath := filepath.Join(dest, z.File[x].Name) // nolint:gosec
// We ignore gosec linter above because the code below files the file traversal bug when extracting archives
if !strings.HasPrefix(fPath, filepath.Clean(dest)+string(os.PathSeparator)) {
err = z.Close()
if err != nil {
log.Errorf(log.Global, ErrUnableToCloseFile, z, err)
}
err = fmt.Errorf("%s: illegal file path", fPath)
return
}
if z.File[x].FileInfo().IsDir() {
err = os.MkdirAll(fPath, os.ModePerm)
if err != nil {
return
}
continue
}
err = os.MkdirAll(filepath.Dir(fPath), 0770)
if err != nil {
return
}
var outFile *os.File
outFile, err = os.OpenFile(fPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, z.File[x].Mode())
if err != nil {
return
}
var eFile io.ReadCloser
eFile, err = z.File[x].Open()
if err != nil {
errCls := outFile.Close()
if errCls != nil {
log.Errorf(log.Global, ErrUnableToCloseFile, outFile, errCls)
}
return
}
_, errIOCopy := io.Copy(outFile, eFile)
if errIOCopy != nil {
err = z.Close()
if err != nil {
log.Errorf(log.Global, ErrUnableToCloseFile, z, err)
}
err = outFile.Close()
if err != nil {
log.Errorf(log.Global, ErrUnableToCloseFile, outFile, err)
}
err = eFile.Close()
if err != nil {
log.Errorf(log.Global, ErrUnableToCloseFile, eFile, err)
}
return fileList, errIOCopy
}
err = outFile.Close()
if err != nil {
log.Errorf(log.Global, ErrUnableToCloseFile, outFile, err)
}
err = eFile.Close()
if err != nil {
log.Errorf(log.Global, ErrUnableToCloseFile, eFile, err)
}
if err != nil {
return
}
fileList = append(fileList, fPath)
}
return fileList, z.Close()
}
// Zip archives requested file or folder
func Zip(src, dest string) error {
i, err := os.Stat(src)
if err != nil {
return err
}
f, err := os.Create(dest)
if err != nil {
return err
}
defer f.Close()
z := zip.NewWriter(f)
defer z.Close()
err = addFilesToZip(z, src, i.IsDir())
if err != nil {
errCls := f.Close()
if errCls != nil {
log.Errorf(log.Global, "Failed to close file handle, manual deletion required: %v", errCls)
return err
}
errRemove := os.Remove(dest)
if errRemove != nil {
log.Errorf(log.Global, "Failed to remove archive, manual deletion required: %v", errRemove)
}
return err
}
return nil
}
func addFilesToZipWrapper(z *zip.Writer, src string, isDir bool) error {
return filepath.Walk(src, func(path string, i os.FileInfo, err error) error {
if err != nil {
return err
}
h, err := zip.FileInfoHeader(i)
if err != nil {
return err
}
if isDir {
h.Name = filepath.Join(filepath.Base(src), strings.TrimPrefix(path, src))
}
if i.IsDir() {
h.Name += "/"
} else {
h.Method = zip.Deflate
}
w, err := z.CreateHeader(h)
if err != nil {
return err
}
if i.IsDir() {
return nil
}
f, err := os.Open(path)
if err != nil {
return err
}
_, err = io.Copy(w, f)
if err != nil {
log.Errorf(log.Global, "Failed to Copy data: %v", err)
}
return f.Close()
})
}