github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/cpp/parse_conaninfo.go (about)

     1  package cpp
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/anchore/syft/syft/artifact"
    12  	"github.com/anchore/syft/syft/file"
    13  	"github.com/anchore/syft/syft/pkg"
    14  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    15  )
    16  
    17  var _ generic.Parser = parseConaninfo
    18  
    19  func parseConanMetadataFromFilePath(path string) (pkg.ConaninfoEntry, error) {
    20  	//	fullFilePath = str(reader.Location.AccessPath)
    21  	// Split the full patch into the folders we expect. I.e.:
    22  	// $HOME/.conan/data/<pkg-name>/<pkg-version>/<user>/<channel>/package/<package_id>/conaninfo.txt
    23  	re := regexp.MustCompile(`.*[/\\](?P<name>[^/\\]+)[/\\](?P<version>[^/\\]+)[/\\](?P<user>[^/\\]+)[/\\](?P<channel>[^/\\]+)[/\\]package[/\\](?P<id>[^/\\]+)[/\\]conaninfo\.txt`)
    24  	matches := re.FindStringSubmatch(path)
    25  	if len(matches) != 6 {
    26  		return pkg.ConaninfoEntry{}, fmt.Errorf("failed to get parent package info from conaninfo file path")
    27  	}
    28  	mainPackageRef := fmt.Sprintf("%s/%s@%s/%s", matches[1], matches[2], matches[3], matches[4])
    29  	return pkg.ConaninfoEntry{
    30  		Ref:       mainPackageRef,
    31  		PackageID: matches[5],
    32  	}, nil
    33  }
    34  
    35  func getRelationships(pkgs []pkg.Package, mainPackageRef pkg.Package) []artifact.Relationship {
    36  	var relationships []artifact.Relationship
    37  	for _, p := range pkgs {
    38  		// this is a pkg that package "main_package" depends on... make a relationship
    39  		relationships = append(relationships, artifact.Relationship{
    40  			From: p,
    41  			To:   mainPackageRef,
    42  			Type: artifact.DependencyOfRelationship,
    43  		})
    44  	}
    45  	return relationships
    46  }
    47  
    48  func parseFullRequiresLine(line string, reader file.LocationReadCloser, pkgs *[]pkg.Package) {
    49  	if len(line) == 0 {
    50  		return
    51  	}
    52  
    53  	cref := splitConanRef(line)
    54  
    55  	meta := pkg.ConaninfoEntry{
    56  		Ref:       line,
    57  		PackageID: cref.PackageID,
    58  	}
    59  
    60  	p := newConaninfoPackage(
    61  		meta,
    62  		reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
    63  	)
    64  	if p != nil {
    65  		*pkgs = append(*pkgs, *p)
    66  	}
    67  }
    68  
    69  // parseConaninfo is a parser function for conaninfo.txt contents, returning all packages discovered.
    70  // The conaninfo.txt file is typically present for an installed conan package under:
    71  // $HOME/.conan/data/<pkg-name>/<pkg-version>/<user>/<channel>/package/<package_id>/conaninfo.txt
    72  // Based on the relative path we can get:
    73  // - package name
    74  // - package version
    75  // - package id
    76  // - user
    77  // - channel
    78  // The conaninfo.txt gives:
    79  // - package requires (full_requires)
    80  // - recipe revision (recipe_hash)
    81  func parseConaninfo(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    82  	// First set the base package info by checking the relative path
    83  	fullFilePath := string(reader.Location.LocationData.Reference().RealPath)
    84  	if len(fullFilePath) == 0 {
    85  		fullFilePath = reader.Location.LocationData.RealPath
    86  	}
    87  
    88  	mainMetadata, err := parseConanMetadataFromFilePath(fullFilePath)
    89  	if err != nil {
    90  		return nil, nil, err
    91  	}
    92  
    93  	r := bufio.NewReader(reader)
    94  	inRequirements := false
    95  	inRecipeHash := false
    96  	var pkgs []pkg.Package
    97  
    98  	for {
    99  		line, err := r.ReadString('\n')
   100  		switch {
   101  		case errors.Is(io.EOF, err):
   102  			mainPackage := newConaninfoPackage(
   103  				mainMetadata,
   104  				reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
   105  			)
   106  
   107  			mainPackageRef := *mainPackage
   108  			relationships := getRelationships(pkgs, mainPackageRef)
   109  
   110  			pkgs = append(pkgs, mainPackageRef)
   111  
   112  			return pkgs, relationships, nil
   113  		case err != nil:
   114  			return nil, nil, fmt.Errorf("failed to parse conaninfo.txt file: %w", err)
   115  		}
   116  
   117  		switch {
   118  		case strings.Contains(line, "[full_requires]"):
   119  			inRequirements = true
   120  			inRecipeHash = false
   121  			continue
   122  		case strings.Contains(line, "[recipe_hash]"):
   123  			inRequirements = false
   124  			inRecipeHash = true
   125  			continue
   126  		case strings.ContainsAny(line, "[]") || strings.HasPrefix(strings.TrimSpace(line), "#"):
   127  			inRequirements = false
   128  			inRecipeHash = false
   129  			continue
   130  		}
   131  
   132  		if inRequirements {
   133  			parseFullRequiresLine(strings.Trim(line, "\n "), reader, &pkgs)
   134  		}
   135  		if inRecipeHash {
   136  			// add recipe hash to the metadata ref
   137  			mainMetadata.Ref = mainMetadata.Ref + "#" + strings.Trim(line, "\n ")
   138  			inRecipeHash = false
   139  		}
   140  	}
   141  }