blob: b6986a25aec57510571dacfd87217a6276893a18 [file] [log] [blame]
package getter
import (
"archive/tar"
"fmt"
"io"
"os"
"path/filepath"
"time"
)
// untar is a shared helper for untarring an archive. The reader should provide
// an uncompressed view of the tar archive.
func untar(input io.Reader, dst, src string, dir bool) error {
tarR := tar.NewReader(input)
done := false
dirHdrs := []*tar.Header{}
now := time.Now()
for {
hdr, err := tarR.Next()
if err == io.EOF {
if !done {
// Empty archive
return fmt.Errorf("empty archive: %s", src)
}
break
}
if err != nil {
return err
}
if hdr.Typeflag == tar.TypeXGlobalHeader || hdr.Typeflag == tar.TypeXHeader {
// don't unpack extended headers as files
continue
}
path := dst
if dir {
// Disallow parent traversal
if containsDotDot(hdr.Name) {
return fmt.Errorf("entry contains '..': %s", hdr.Name)
}
path = filepath.Join(path, hdr.Name)
}
if hdr.FileInfo().IsDir() {
if !dir {
return fmt.Errorf("expected a single file: %s", src)
}
// A directory, just make the directory and continue unarchiving...
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
// Record the directory information so that we may set its attributes
// after all files have been extracted
dirHdrs = append(dirHdrs, hdr)
continue
} else {
// There is no ordering guarantee that a file in a directory is
// listed before the directory
dstPath := filepath.Dir(path)
// Check that the directory exists, otherwise create it
if _, err := os.Stat(dstPath); os.IsNotExist(err) {
if err := os.MkdirAll(dstPath, 0755); err != nil {
return err
}
}
}
// We have a file. If we already decoded, then it is an error
if !dir && done {
return fmt.Errorf("expected a single file, got multiple: %s", src)
}
// Mark that we're done so future in single file mode errors
done = true
// Open the file for writing
dstF, err := os.Create(path)
if err != nil {
return err
}
_, err = io.Copy(dstF, tarR)
dstF.Close()
if err != nil {
return err
}
// Chmod the file
if err := os.Chmod(path, hdr.FileInfo().Mode()); err != nil {
return err
}
// Set the access and modification time if valid, otherwise default to current time
aTime := now
mTime := now
if hdr.AccessTime.Unix() > 0 {
aTime = hdr.AccessTime
}
if hdr.ModTime.Unix() > 0 {
mTime = hdr.ModTime
}
if err := os.Chtimes(path, aTime, mTime); err != nil {
return err
}
}
// Perform a final pass over extracted directories to update metadata
for _, dirHdr := range dirHdrs {
path := filepath.Join(dst, dirHdr.Name)
// Chmod the directory since they might be created before we know the mode flags
if err := os.Chmod(path, dirHdr.FileInfo().Mode()); err != nil {
return err
}
// Set the mtime/atime attributes since they would have been changed during extraction
aTime := now
mTime := now
if dirHdr.AccessTime.Unix() > 0 {
aTime = dirHdr.AccessTime
}
if dirHdr.ModTime.Unix() > 0 {
mTime = dirHdr.ModTime
}
if err := os.Chtimes(path, aTime, mTime); err != nil {
return err
}
}
return nil
}
// tarDecompressor is an implementation of Decompressor that can
// unpack tar files.
type tarDecompressor struct{}
func (d *tarDecompressor) Decompress(dst, src string, dir bool) error {
// If we're going into a directory we should make that first
mkdir := dst
if !dir {
mkdir = filepath.Dir(dst)
}
if err := os.MkdirAll(mkdir, 0755); err != nil {
return err
}
// File first
f, err := os.Open(src)
if err != nil {
return err
}
defer f.Close()
return untar(f, dst, src, dir)
}