github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/helm/parser/parser_tar.go (about)

     1  package parser
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"io/fs"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"github.com/aquasecurity/trivy-iac/pkg/detection"
    15  	"github.com/liamg/memoryfs"
    16  )
    17  
    18  var errSkipFS = errors.New("skip parse FS")
    19  
    20  func (p *Parser) addTarToFS(path string) (fs.FS, error) {
    21  	tarFS := memoryfs.CloneFS(p.workingFS)
    22  
    23  	file, err := tarFS.Open(path)
    24  	if err != nil {
    25  		return nil, fmt.Errorf("failed to open tar: %w", err)
    26  	}
    27  	defer file.Close()
    28  
    29  	var tr *tar.Reader
    30  
    31  	if detection.IsZip(path) {
    32  		zipped, err := gzip.NewReader(file)
    33  		if err != nil {
    34  			return nil, fmt.Errorf("failed to create gzip reader: %w", err)
    35  		}
    36  		defer zipped.Close()
    37  		tr = tar.NewReader(zipped)
    38  	} else {
    39  		tr = tar.NewReader(file)
    40  	}
    41  
    42  	checkExistedChart := true
    43  
    44  	for {
    45  		header, err := tr.Next()
    46  		if err != nil {
    47  			if errors.Is(err, io.EOF) {
    48  				break
    49  			}
    50  			return nil, fmt.Errorf("failed to get next entry: %w", err)
    51  		}
    52  
    53  		if checkExistedChart {
    54  			// Do not add archive files to FS if the chart already exists
    55  			// This can happen when the source chart is located next to an archived chart (with the `helm package` command)
    56  			// The first level folder in the archive is equal to the Chart name
    57  			if _, err := tarFS.Stat(filepath.Dir(path) + "/" + filepath.Dir(header.Name)); err == nil {
    58  				return nil, errSkipFS
    59  			}
    60  			checkExistedChart = false
    61  		}
    62  
    63  		// get the individual path and extract to the current directory
    64  		entryPath := header.Name
    65  
    66  		switch header.Typeflag {
    67  		case tar.TypeDir:
    68  			if err := tarFS.MkdirAll(entryPath, os.FileMode(header.Mode)); err != nil && !errors.Is(err, fs.ErrExist) {
    69  				return nil, err
    70  			}
    71  		case tar.TypeReg:
    72  			writePath := filepath.Dir(path) + "/" + entryPath
    73  			p.debug.Log("Unpacking tar entry %s", writePath)
    74  
    75  			_ = tarFS.MkdirAll(filepath.Dir(writePath), fs.ModePerm)
    76  
    77  			buf, err := copyChunked(tr, 1024)
    78  			if err != nil {
    79  				return nil, err
    80  			}
    81  
    82  			p.debug.Log("writing file contents to %s", writePath)
    83  			if err := tarFS.WriteFile(writePath, buf.Bytes(), fs.ModePerm); err != nil {
    84  				return nil, fmt.Errorf("write file error: %w", err)
    85  			}
    86  		default:
    87  			return nil, fmt.Errorf("header type %q is not supported", header.Typeflag)
    88  		}
    89  	}
    90  
    91  	if err := tarFS.Remove(path); err != nil {
    92  		return nil, fmt.Errorf("failed to remove tar from FS: %w", err)
    93  	}
    94  
    95  	return tarFS, nil
    96  }
    97  
    98  func copyChunked(src io.Reader, chunkSize int64) (*bytes.Buffer, error) {
    99  	buf := new(bytes.Buffer)
   100  	for {
   101  		if _, err := io.CopyN(buf, src, chunkSize); err != nil {
   102  			if errors.Is(err, io.EOF) {
   103  				break
   104  			}
   105  			return nil, fmt.Errorf("failed to copy: %w", err)
   106  		}
   107  	}
   108  
   109  	return buf, nil
   110  }