github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/cpp/parse_conaninfo.go (about)

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