github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/internal/file/zip_file_manifest.go (about)

     1  package file
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/scylladb/go-set/strset"
    10  
    11  	"github.com/lineaje-labs/syft/internal/log"
    12  )
    13  
    14  // ZipFileManifest is a collection of paths and their file metadata.
    15  type ZipFileManifest map[string]os.FileInfo
    16  
    17  // NewZipFileManifest creates and returns a new ZipFileManifest populated with path and metadata from the given zip archive path.
    18  func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
    19  	zipReader, err := OpenZip(archivePath)
    20  	manifest := make(ZipFileManifest)
    21  	if err != nil {
    22  		return manifest, fmt.Errorf("unable to open zip archive (%s): %w", archivePath, err)
    23  	}
    24  	defer func() {
    25  		err = zipReader.Close()
    26  		if err != nil {
    27  			log.Warnf("unable to close zip archive (%s): %+v", archivePath, err)
    28  		}
    29  	}()
    30  
    31  	for _, file := range zipReader.Reader.File {
    32  		manifest.Add(file.Name, file.FileInfo())
    33  	}
    34  	return manifest, nil
    35  }
    36  
    37  // Add a new path and it's file metadata to the collection.
    38  func (z ZipFileManifest) Add(entry string, info os.FileInfo) {
    39  	z[entry] = info
    40  }
    41  
    42  // GlobMatch returns the path keys that match the given value(s).
    43  func (z ZipFileManifest) GlobMatch(caseInsensitive bool, patterns ...string) []string {
    44  	uniqueMatches := strset.New()
    45  
    46  	for _, pattern := range patterns {
    47  		for entry := range z {
    48  			// We want to match globs as if entries begin with a leading slash (akin to an absolute path)
    49  			// so that glob logic is consistent inside and outside of ZIP archives
    50  			normalizedEntry := normalizeZipEntryName(caseInsensitive, entry)
    51  
    52  			if caseInsensitive {
    53  				pattern = strings.ToLower(pattern)
    54  			}
    55  			if GlobMatch(pattern, normalizedEntry) {
    56  				uniqueMatches.Add(entry)
    57  			}
    58  		}
    59  	}
    60  
    61  	results := uniqueMatches.List()
    62  	sort.Strings(results)
    63  
    64  	return results
    65  }
    66  
    67  // normalizeZipEntryName takes the given path entry and ensures it is prefixed with "/".
    68  func normalizeZipEntryName(caseInsensitive bool, entry string) string {
    69  	if caseInsensitive {
    70  		entry = strings.ToLower(entry)
    71  	}
    72  	if !strings.HasPrefix(entry, "/") {
    73  		return "/" + entry
    74  	}
    75  
    76  	return entry
    77  }