github.com/weiwenhao/getter@v1.30.1/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, umask os.FileMode) 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, mode(0755, umask)); 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, mode(0755, umask)); 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  		err = copyReader(path, tarR, hdr.FileInfo().Mode(), umask)
    86  		if err != nil {
    87  			return err
    88  		}
    89  
    90  		// Set the access and modification time if valid, otherwise default to current time
    91  		aTime := now
    92  		mTime := now
    93  		if hdr.AccessTime.Unix() > 0 {
    94  			aTime = hdr.AccessTime
    95  		}
    96  		if hdr.ModTime.Unix() > 0 {
    97  			mTime = hdr.ModTime
    98  		}
    99  		if err := os.Chtimes(path, aTime, mTime); err != nil {
   100  			return err
   101  		}
   102  	}
   103  
   104  	// Perform a final pass over extracted directories to update metadata
   105  	for _, dirHdr := range dirHdrs {
   106  		path := filepath.Join(dst, dirHdr.Name)
   107  		// Chmod the directory since they might be created before we know the mode flags
   108  		if err := os.Chmod(path, mode(dirHdr.FileInfo().Mode(), umask)); err != nil {
   109  			return err
   110  		}
   111  		// Set the mtime/atime attributes since they would have been changed during extraction
   112  		aTime := now
   113  		mTime := now
   114  		if dirHdr.AccessTime.Unix() > 0 {
   115  			aTime = dirHdr.AccessTime
   116  		}
   117  		if dirHdr.ModTime.Unix() > 0 {
   118  			mTime = dirHdr.ModTime
   119  		}
   120  		if err := os.Chtimes(path, aTime, mTime); err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  // TarDecompressor is an implementation of Decompressor that can
   129  // unpack tar files.
   130  type TarDecompressor struct{}
   131  
   132  func (d *TarDecompressor) Decompress(dst, src string, dir bool, umask os.FileMode) error {
   133  	// If we're going into a directory we should make that first
   134  	mkdir := dst
   135  	if !dir {
   136  		mkdir = filepath.Dir(dst)
   137  	}
   138  	if err := os.MkdirAll(mkdir, mode(0755, umask)); err != nil {
   139  		return err
   140  	}
   141  
   142  	// File first
   143  	f, err := os.Open(src)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	defer f.Close()
   148  
   149  	return untar(f, dst, src, dir, umask)
   150  }