github.com/ahlemtn/fabric@v2.1.1+incompatible/core/container/externalbuilder/tar.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package externalbuilder
     8  
     9  import (
    10  	"archive/tar"
    11  	"compress/gzip"
    12  	"io"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // Untar takes a gzip-ed tar archive, and extracts it to dst.
    21  // It returns an error if the tar contains any files which would escape to a
    22  // parent of dst, or if the archive contains any files whose type is not
    23  // a regular file or directory.
    24  func Untar(buffer io.Reader, dst string) error {
    25  	gzr, err := gzip.NewReader(buffer)
    26  	if err != nil {
    27  		return err
    28  	}
    29  	defer gzr.Close()
    30  
    31  	tr := tar.NewReader(gzr)
    32  
    33  	for {
    34  		header, err := tr.Next()
    35  
    36  		if err == io.EOF {
    37  			return nil
    38  		}
    39  
    40  		if err != nil {
    41  			return errors.WithMessage(err, "could not get next tar element")
    42  		}
    43  
    44  		if !ValidPath(header.Name) {
    45  			return errors.Errorf("tar contains the absolute or escaping path '%s'", header.Name)
    46  		}
    47  
    48  		target := filepath.Join(dst, header.Name)
    49  		switch header.Typeflag {
    50  		case tar.TypeDir:
    51  			if err := os.MkdirAll(target, 0700); err != nil {
    52  				return errors.WithMessagef(err, "could not create directory '%s'", header.Name)
    53  			}
    54  		case tar.TypeReg:
    55  			if err := os.MkdirAll(filepath.Dir(target), 0700); err != nil {
    56  				return errors.WithMessagef(err, "could not create directory '%s'", filepath.Dir(header.Name))
    57  			}
    58  
    59  			f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
    60  			if err != nil {
    61  				return errors.WithMessagef(err, "could not create file '%s'", header.Name)
    62  			}
    63  
    64  			// copy over contents
    65  			if _, err := io.Copy(f, tr); err != nil {
    66  				return err
    67  			}
    68  
    69  			f.Close()
    70  		default:
    71  			return errors.Errorf("invalid file type '%v' contained in archive for file '%s'", header.Typeflag, header.Name)
    72  		}
    73  	}
    74  }
    75  
    76  // ValidPath checks to see if the path is absolute, or if it is a
    77  // relative path higher in the tree.  In these cases it returns false.
    78  func ValidPath(uncleanPath string) bool {
    79  	// sanitizedPath will eliminate non-prefix instances of '..', as well
    80  	// as strip './'
    81  	sanitizedPath := filepath.Clean(uncleanPath)
    82  
    83  	switch {
    84  	case filepath.IsAbs(sanitizedPath):
    85  		return false
    86  	case strings.HasPrefix(sanitizedPath, ".."+string(filepath.Separator)) || sanitizedPath == "..":
    87  		// Path refers either to the parent, or a directory relative to the parent (but allows ..foo or ... for instance)
    88  		return false
    89  	default:
    90  		// Path appears to be relative without escaping higher
    91  		return true
    92  	}
    93  }