github.com/remind101/go-getter@v0.0.0-20180809191950-4bda8fa99001/decompress_tar.go (about)

     1  package getter
     2  
     3  import (
     4  	"archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  )
    11  
    12  // untar is a shared helper for untarring an archive. The reader should provide
    13  // an uncompressed view of the tar archive.
    14  func untar(input io.Reader, dst, src string, dir bool) error {
    15  	tarR := tar.NewReader(input)
    16  	done := false
    17  	dirHdrs := []*tar.Header{}
    18  	now := time.Now()
    19  	for {
    20  		hdr, err := tarR.Next()
    21  		if err == io.EOF {
    22  			if !done {
    23  				// Empty archive
    24  				return fmt.Errorf("empty archive: %s", src)
    25  			}
    26  
    27  			break
    28  		}
    29  		if err != nil {
    30  			return err
    31  		}
    32  
    33  		if hdr.Typeflag == tar.TypeXGlobalHeader || hdr.Typeflag == tar.TypeXHeader {
    34  			// don't unpack extended headers as files
    35  			continue
    36  		}
    37  
    38  		path := dst
    39  		if dir {
    40  			// Disallow parent traversal
    41  			if containsDotDot(hdr.Name) {
    42  				return fmt.Errorf("entry contains '..': %s", hdr.Name)
    43  			}
    44  
    45  			path = filepath.Join(path, hdr.Name)
    46  		}
    47  
    48  		if hdr.FileInfo().IsDir() {
    49  			if !dir {
    50  				return fmt.Errorf("expected a single file: %s", src)
    51  			}
    52  
    53  			// A directory, just make the directory and continue unarchiving...
    54  			if err := os.MkdirAll(path, 0755); err != nil {
    55  				return err
    56  			}
    57  
    58  			// Record the directory information so that we may set its attributes
    59  			// after all files have been extracted
    60  			dirHdrs = append(dirHdrs, hdr)
    61  
    62  			continue
    63  		} else {
    64  			// There is no ordering guarantee that a file in a directory is
    65  			// listed before the directory
    66  			dstPath := filepath.Dir(path)
    67  
    68  			// Check that the directory exists, otherwise create it
    69  			if _, err := os.Stat(dstPath); os.IsNotExist(err) {
    70  				if err := os.MkdirAll(dstPath, 0755); err != nil {
    71  					return err
    72  				}
    73  			}
    74  		}
    75  
    76  		// We have a file. If we already decoded, then it is an error
    77  		if !dir && done {
    78  			return fmt.Errorf("expected a single file, got multiple: %s", src)
    79  		}
    80  
    81  		// Mark that we're done so future in single file mode errors
    82  		done = true
    83  
    84  		// Open the file for writing
    85  		dstF, err := os.Create(path)
    86  		if err != nil {
    87  			return err
    88  		}
    89  		_, err = io.Copy(dstF, tarR)
    90  		dstF.Close()
    91  		if err != nil {
    92  			return err
    93  		}
    94  
    95  		// Chmod the file
    96  		if err := os.Chmod(path, hdr.FileInfo().Mode()); err != nil {
    97  			return err
    98  		}
    99  
   100  		// Set the access and modification time if valid, otherwise default to current time
   101  		aTime := now
   102  		mTime := now
   103  		if hdr.AccessTime.Unix() > 0 {
   104  			aTime = hdr.AccessTime
   105  		}
   106  		if hdr.ModTime.Unix() > 0 {
   107  			mTime = hdr.ModTime
   108  		}
   109  		if err := os.Chtimes(path, aTime, mTime); err != nil {
   110  			return err
   111  		}
   112  	}
   113  
   114  	// Perform a final pass over extracted directories to update metadata
   115  	for _, dirHdr := range dirHdrs {
   116  		path := filepath.Join(dst, dirHdr.Name)
   117  		// Chmod the directory since they might be created before we know the mode flags
   118  		if err := os.Chmod(path, dirHdr.FileInfo().Mode()); err != nil {
   119  			return err
   120  		}
   121  		// Set the mtime/atime attributes since they would have been changed during extraction
   122  		aTime := now
   123  		mTime := now
   124  		if dirHdr.AccessTime.Unix() > 0 {
   125  			aTime = dirHdr.AccessTime
   126  		}
   127  		if dirHdr.ModTime.Unix() > 0 {
   128  			mTime = dirHdr.ModTime
   129  		}
   130  		if err := os.Chtimes(path, aTime, mTime); err != nil {
   131  			return err
   132  		}
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  // tarDecompressor is an implementation of Decompressor that can
   139  // unpack tar files.
   140  type tarDecompressor struct{}
   141  
   142  func (d *tarDecompressor) Decompress(dst, src string, dir bool) error {
   143  	// If we're going into a directory we should make that first
   144  	mkdir := dst
   145  	if !dir {
   146  		mkdir = filepath.Dir(dst)
   147  	}
   148  	if err := os.MkdirAll(mkdir, 0755); err != nil {
   149  		return err
   150  	}
   151  
   152  	// File first
   153  	f, err := os.Open(src)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	defer f.Close()
   158  
   159  	return untar(f, dst, src, dir)
   160  }