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 }