github.com/anchore/syft@v1.38.2/internal/file/zip_file_manifest.go (about) 1 package file 2 3 import ( 4 "context" 5 "os" 6 "sort" 7 "strings" 8 9 "github.com/bmatcuk/doublestar/v4" 10 "github.com/mholt/archives" 11 "github.com/scylladb/go-set/strset" 12 13 "github.com/anchore/syft/internal/log" 14 ) 15 16 // ZipFileManifest is a collection of paths and their file metadata. 17 type ZipFileManifest map[string]os.FileInfo 18 19 // NewZipFileManifest creates and returns a new ZipFileManifest populated with path and metadata from the given zip archive path. 20 func NewZipFileManifest(ctx context.Context, archivePath string) (ZipFileManifest, error) { 21 zipReader, err := os.Open(archivePath) 22 manifest := make(ZipFileManifest) 23 if err != nil { 24 log.Debugf("unable to open zip archive (%s): %v", archivePath, err) 25 return manifest, err 26 } 27 defer func() { 28 if err = zipReader.Close(); err != nil { 29 log.Debugf("unable to close zip archive (%s): %+v", archivePath, err) 30 } 31 }() 32 33 err = archives.Zip{}.Extract(ctx, zipReader, func(_ context.Context, file archives.FileInfo) error { 34 manifest.Add(file.NameInArchive, file.FileInfo) 35 return nil 36 }) 37 if err != nil { 38 return manifest, err 39 } 40 return manifest, nil 41 } 42 43 // Add a new path and it's file metadata to the collection. 44 func (z ZipFileManifest) Add(entry string, info os.FileInfo) { 45 z[entry] = info 46 } 47 48 // GlobMatch returns the path keys to files (not directories) that match the given value(s). 49 func (z ZipFileManifest) GlobMatch(caseInsensitive bool, patterns ...string) []string { 50 uniqueMatches := strset.New() 51 52 for _, pattern := range patterns { 53 for entry := range z { 54 fileInfo := z[entry] 55 if fileInfo != nil && fileInfo.IsDir() { 56 continue 57 } 58 59 // We want to match globs as if entries begin with a leading slash (akin to an absolute path) 60 // so that glob logic is consistent inside and outside of ZIP archives 61 normalizedEntry := normalizeZipEntryName(caseInsensitive, entry) 62 63 if caseInsensitive { 64 pattern = strings.ToLower(pattern) 65 } 66 67 matches, err := doublestar.Match(pattern, normalizedEntry) 68 if err != nil { 69 log.Debugf("error with match pattern '%s', including by default: %v", pattern, err) 70 matches = true 71 } 72 if matches { 73 uniqueMatches.Add(entry) 74 } 75 } 76 } 77 78 results := uniqueMatches.List() 79 sort.Strings(results) 80 81 return results 82 } 83 84 // normalizeZipEntryName takes the given path entry and ensures it is prefixed with "/". 85 func normalizeZipEntryName(caseInsensitive bool, entry string) string { 86 if caseInsensitive { 87 entry = strings.ToLower(entry) 88 } 89 if !strings.HasPrefix(entry, "/") { 90 return "/" + entry 91 } 92 93 return entry 94 }