| 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) |
| } |