postgres_exporter/vendor/github.com/mholt/archiver/zip.go
Will Rouesnel 989489096e Refactor repository layout and convert build system to Mage.
This commit implements a massive refactor of the repository, and
moves the build system over to use Mage (magefile.org) which should
allow seamless building across multiple platforms.
2018-03-06 07:29:35 +10:00

234 lines
5.2 KiB
Go

// Package archiver makes it super easy to create and open .zip,
// .tar.gz, and .tar.bz2 files.
package archiver
import (
"archive/zip"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
)
// Zip is for Zip format
var Zip zipFormat
func init() {
RegisterFormat("Zip", Zip)
}
type zipFormat struct{}
func (zipFormat) Match(filename string) bool {
return strings.HasSuffix(strings.ToLower(filename), ".zip") || isZip(filename)
}
// isZip checks the file has the Zip format signature by reading its beginning
// bytes and matching it against "PK\x03\x04"
func isZip(zipPath string) bool {
f, err := os.Open(zipPath)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, 4)
if n, err := f.Read(buf); err != nil || n < 4 {
return false
}
return bytes.Equal(buf, []byte("PK\x03\x04"))
}
// Write outputs a .zip file to the given writer with
// the contents of files listed in filePaths. File paths
// can be those of regular files or directories. Regular
// files are stored at the 'root' of the archive, and
// directories are recursively added.
//
// Files with an extension for formats that are already
// compressed will be stored only, not compressed.
func (zipFormat) Write(output io.Writer, filePaths []string) error {
w := zip.NewWriter(output)
for _, fpath := range filePaths {
if err := zipFile(w, fpath); err != nil {
w.Close()
return err
}
}
return w.Close()
}
// Make creates a .zip file in the location zipPath containing
// the contents of files listed in filePaths. File paths
// can be those of regular files or directories. Regular
// files are stored at the 'root' of the archive, and
// directories are recursively added.
//
// Files with an extension for formats that are already
// compressed will be stored only, not compressed.
func (zipFormat) Make(zipPath string, filePaths []string) error {
out, err := os.Create(zipPath)
if err != nil {
return fmt.Errorf("error creating %s: %v", zipPath, err)
}
defer out.Close()
return Zip.Write(out, filePaths)
}
func zipFile(w *zip.Writer, source string) error {
sourceInfo, err := os.Stat(source)
if err != nil {
return fmt.Errorf("%s: stat: %v", source, err)
}
var baseDir string
if sourceInfo.IsDir() {
baseDir = filepath.Base(source)
}
return filepath.Walk(source, func(fpath string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("walking to %s: %v", fpath, err)
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return fmt.Errorf("%s: getting header: %v", fpath, err)
}
if baseDir != "" {
name, err := filepath.Rel(source, fpath)
if err != nil {
return err
}
header.Name = path.Join(baseDir, filepath.ToSlash(name))
}
if info.IsDir() {
header.Name += "/"
header.Method = zip.Store
} else {
ext := strings.ToLower(path.Ext(header.Name))
if _, ok := compressedFormats[ext]; ok {
header.Method = zip.Store
} else {
header.Method = zip.Deflate
}
}
writer, err := w.CreateHeader(header)
if err != nil {
return fmt.Errorf("%s: making header: %v", fpath, err)
}
if info.IsDir() {
return nil
}
if header.Mode().IsRegular() {
file, err := os.Open(fpath)
if err != nil {
return fmt.Errorf("%s: opening: %v", fpath, err)
}
defer file.Close()
_, err = io.CopyN(writer, file, info.Size())
if err != nil && err != io.EOF {
return fmt.Errorf("%s: copying contents: %v", fpath, err)
}
}
return nil
})
}
// Read unzips the .zip file read from the input Reader into destination.
func (zipFormat) Read(input io.Reader, destination string) error {
buf, err := ioutil.ReadAll(input)
if err != nil {
return err
}
rdr := bytes.NewReader(buf)
r, err := zip.NewReader(rdr, rdr.Size())
if err != nil {
return err
}
return unzipAll(r, destination)
}
// Open unzips the .zip file at source into destination.
func (zipFormat) Open(source, destination string) error {
r, err := zip.OpenReader(source)
if err != nil {
return err
}
defer r.Close()
return unzipAll(&r.Reader, destination)
}
func unzipAll(r *zip.Reader, destination string) error {
for _, zf := range r.File {
if err := unzipFile(zf, destination); err != nil {
return err
}
}
return nil
}
func unzipFile(zf *zip.File, destination string) error {
if strings.HasSuffix(zf.Name, "/") {
return mkdir(filepath.Join(destination, zf.Name))
}
rc, err := zf.Open()
if err != nil {
return fmt.Errorf("%s: open compressed file: %v", zf.Name, err)
}
defer rc.Close()
return writeNewFile(filepath.Join(destination, zf.Name), rc, zf.FileInfo().Mode())
}
// compressedFormats is a (non-exhaustive) set of lowercased
// file extensions for formats that are typically already
// compressed. Compressing already-compressed files often
// results in a larger file, so when possible, we check this
// set to avoid that.
var compressedFormats = map[string]struct{}{
".7z": {},
".avi": {},
".bz2": {},
".cab": {},
".gif": {},
".gz": {},
".jar": {},
".jpeg": {},
".jpg": {},
".lz": {},
".lzma": {},
".mov": {},
".mp3": {},
".mp4": {},
".mpeg": {},
".mpg": {},
".png": {},
".rar": {},
".tbz2": {},
".tgz": {},
".txz": {},
".xz": {},
".zip": {},
".zipx": {},
}