github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/walker/tar.go (about)

     1  package walker
     2  
     3  import (
     4  	"archive/tar"
     5  	"io"
     6  	"io/fs"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"golang.org/x/xerrors"
    12  
    13  	"github.com/devseccon/trivy/pkg/fanal/utils"
    14  )
    15  
    16  const (
    17  	opq string = ".wh..wh..opq"
    18  	wh  string = ".wh."
    19  )
    20  
    21  var parentDir = ".." + utils.PathSeparator
    22  
    23  type LayerTar struct {
    24  	walker
    25  	threshold int64
    26  }
    27  
    28  func NewLayerTar(skipFiles, skipDirs []string) LayerTar {
    29  	threshold := defaultSizeThreshold
    30  	return LayerTar{
    31  		walker:    newWalker(skipFiles, skipDirs),
    32  		threshold: threshold,
    33  	}
    34  }
    35  
    36  func (w LayerTar) Walk(layer io.Reader, analyzeFn WalkFunc) ([]string, []string, error) {
    37  	var opqDirs, whFiles, skipDirs []string
    38  	tr := tar.NewReader(layer)
    39  	for {
    40  		hdr, err := tr.Next()
    41  		if err == io.EOF {
    42  			break
    43  		} else if err != nil {
    44  			return nil, nil, xerrors.Errorf("failed to extract the archive: %w", err)
    45  		}
    46  
    47  		// filepath.Clean cannot be used since tar file paths should be OS-agnostic.
    48  		filePath := path.Clean(hdr.Name)
    49  		filePath = strings.TrimLeft(filePath, "/")
    50  		fileDir, fileName := path.Split(filePath)
    51  
    52  		// e.g. etc/.wh..wh..opq
    53  		if opq == fileName {
    54  			opqDirs = append(opqDirs, fileDir)
    55  			continue
    56  		}
    57  		// etc/.wh.hostname
    58  		if strings.HasPrefix(fileName, wh) {
    59  			name := strings.TrimPrefix(fileName, wh)
    60  			fpath := path.Join(fileDir, name)
    61  			whFiles = append(whFiles, fpath)
    62  			continue
    63  		}
    64  
    65  		switch hdr.Typeflag {
    66  		case tar.TypeDir:
    67  			if w.shouldSkipDir(filePath) {
    68  				skipDirs = append(skipDirs, filePath)
    69  				continue
    70  			}
    71  		case tar.TypeReg:
    72  			if w.shouldSkipFile(filePath) {
    73  				continue
    74  			}
    75  		// symlinks and hardlinks have no content in reader, skip them
    76  		default:
    77  			continue
    78  		}
    79  
    80  		if underSkippedDir(filePath, skipDirs) {
    81  			continue
    82  		}
    83  
    84  		// A regular file will reach here.
    85  		if err = w.processFile(filePath, tr, hdr.FileInfo(), analyzeFn); err != nil {
    86  			return nil, nil, xerrors.Errorf("failed to process the file: %w", err)
    87  		}
    88  	}
    89  	return opqDirs, whFiles, nil
    90  }
    91  
    92  func (w LayerTar) processFile(filePath string, tr *tar.Reader, fi fs.FileInfo, analyzeFn WalkFunc) error {
    93  	cf := newCachedFile(fi.Size(), tr, w.threshold)
    94  	defer func() {
    95  		// nolint
    96  		_ = cf.Clean()
    97  	}()
    98  
    99  	if err := analyzeFn(filePath, fi, cf.Open); err != nil {
   100  		return xerrors.Errorf("failed to analyze file: %w", err)
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  func underSkippedDir(filePath string, skipDirs []string) bool {
   107  	for _, skipDir := range skipDirs {
   108  		rel, err := filepath.Rel(skipDir, filePath)
   109  		if err != nil {
   110  			return false
   111  		}
   112  		if !strings.HasPrefix(rel, parentDir) {
   113  			return true
   114  		}
   115  	}
   116  	return false
   117  }