github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/internal/file/tar_file_traversal.go (about)

     1  package file
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  
     8  	"github.com/bmatcuk/doublestar/v4"
     9  	"github.com/mholt/archiver/v3"
    10  )
    11  
    12  // ExtractGlobsFromTarToUniqueTempFile extracts paths matching the given globs within the given archive to a temporary directory, returning file openers for each file extracted.
    13  func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...string) (map[string]Opener, error) {
    14  	results := make(map[string]Opener)
    15  
    16  	// don't allow for full traversal, only select traversal from given paths
    17  	if len(globs) == 0 {
    18  		return results, nil
    19  	}
    20  
    21  	visitor := func(file archiver.File) error {
    22  		defer file.Close()
    23  
    24  		// ignore directories
    25  		if file.FileInfo.IsDir() {
    26  			return nil
    27  		}
    28  
    29  		// ignore any filename that doesn't match the given globs...
    30  		if !matchesAnyGlob(file.Name(), globs...) {
    31  			return nil
    32  		}
    33  
    34  		// we have a file we want to extract....
    35  		tempFilePrefix := filepath.Base(filepath.Clean(file.Name())) + "-"
    36  		tempFile, err := os.CreateTemp(dir, tempFilePrefix)
    37  		if err != nil {
    38  			return fmt.Errorf("unable to create temp file: %w", err)
    39  		}
    40  		// we shouldn't try and keep the tempFile open as the returned result may have several files, which takes up
    41  		// resources (leading to "too many open files"). Instead we'll return a file opener to the caller which
    42  		// provides a ReadCloser. It is up to the caller to handle closing the file explicitly.
    43  		defer tempFile.Close()
    44  
    45  		if err := safeCopy(tempFile, file.ReadCloser); err != nil {
    46  			return fmt.Errorf("unable to copy source=%q for tar=%q: %w", file.Name(), archivePath, err)
    47  		}
    48  
    49  		results[file.Name()] = Opener{path: tempFile.Name()}
    50  
    51  		return nil
    52  	}
    53  
    54  	return results, archiver.Walk(archivePath, visitor)
    55  }
    56  
    57  func matchesAnyGlob(name string, globs ...string) bool {
    58  	for _, glob := range globs {
    59  		if matches, err := doublestar.PathMatch(glob, name); err == nil && matches {
    60  			return true
    61  		}
    62  	}
    63  	return false
    64  }