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 }