diff --git a/syft/pkg/cataloger/cpp/package.go b/syft/pkg/cataloger/cpp/package.go index dbbdd0b90..b093c928d 100644 --- a/syft/pkg/cataloger/cpp/package.go +++ b/syft/pkg/cataloger/cpp/package.go @@ -8,23 +8,67 @@ import ( "github.com/anchore/syft/syft/pkg" ) -func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package { - fields := strings.Split(strings.TrimSpace(m.Ref), "/") - if len(fields) < 2 { - return nil +type conanRef struct { + Name string + Version string + User string + Channel string + Revision string + Timestamp string +} + +func splitConanRef(ref string) *conanRef { + // Conan ref format is: + // pkg/0.1@user/channel#rrev%timestamp + // This method is based on conan's ref.loads method: + // https://github.com/conan-io/conan/blob/release/2.0/conans/model/recipe_ref.py#L93C21-L93C21 + + var cref conanRef + + // timestamp + tokens := strings.Split(ref, "%") + text := tokens[0] + if len(tokens) == 2 { + cref.Timestamp = tokens[1] } - pkgName, pkgVersion := fields[0], fields[1] + // revision + tokens = strings.Split(text, "#") + ref = tokens[0] + if len(tokens) == 2 { + cref.Revision = tokens[1] + } - if pkgName == "" || pkgVersion == "" { + // name and version are always given + tokens = strings.Split(ref, "@") + nameAndVersion := strings.Split(tokens[0], "/") + if len(nameAndVersion) < 2 || nameAndVersion[0] == "" || nameAndVersion[1] == "" { + return nil + } + cref.Name = nameAndVersion[0] + cref.Version = nameAndVersion[1] + // user and channel + if len(tokens) == 2 && tokens[1] != "" { + tokens = strings.Split(tokens[1], "/") + if len(tokens) == 2 { + cref.User = tokens[0] + cref.Channel = tokens[1] + } + } + return &cref +} + +func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package { + ref := splitConanRef(m.Ref) + if ref == nil { return nil } p := pkg.Package{ - Name: pkgName, - Version: pkgVersion, + Name: ref.Name, + Version: ref.Version, Locations: file.NewLocationSet(locations...), - PURL: packageURL(pkgName, pkgVersion), + PURL: packageURL(ref), Language: pkg.CPP, Type: pkg.ConanPkg, MetadataType: pkg.ConanMetadataType, @@ -37,22 +81,16 @@ func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.P } func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *pkg.Package { - fields := strings.Split(strings.Split(m.Ref, "@")[0], "/") - if len(fields) < 2 { - return nil - } - - pkgName, pkgVersion := fields[0], fields[1] - - if pkgName == "" || pkgVersion == "" { + ref := splitConanRef(m.Ref) + if ref == nil { return nil } p := pkg.Package{ - Name: pkgName, - Version: pkgVersion, + Name: ref.Name, + Version: ref.Version, Locations: file.NewLocationSet(locations...), - PURL: packageURL(pkgName, pkgVersion), + PURL: packageURL(ref), Language: pkg.CPP, Type: pkg.ConanPkg, MetadataType: pkg.ConanLockMetadataType, @@ -64,13 +102,20 @@ func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *p return &p } -func packageURL(name, version string) string { +func packageURL(ref *conanRef) string { + qualifiers := packageurl.Qualifiers{} + if ref.Channel != "" { + qualifiers = append(qualifiers, packageurl.Qualifier{ + Key: "channel", + Value: ref.Channel, + }) + } return packageurl.NewPackageURL( packageurl.TypeConan, - "", - name, - version, - nil, // TODO: no qualifiers (...yet) + ref.User, + ref.Name, + ref.Version, + qualifiers, "", ).ToString() } diff --git a/syft/pkg/cataloger/cpp/parse_conanfile.go b/syft/pkg/cataloger/cpp/parse_conanfile.go index f9ae172f3..bf60706db 100644 --- a/syft/pkg/cataloger/cpp/parse_conanfile.go +++ b/syft/pkg/cataloger/cpp/parse_conanfile.go @@ -36,7 +36,7 @@ func parseConanfile(_ file.Resolver, _ *generic.Environment, reader file.Locatio switch { case strings.Contains(line, "[requires]"): inRequirements = true - case strings.ContainsAny(line, "[]#"): + case strings.ContainsAny(line, "[]") || strings.HasPrefix(strings.TrimSpace(line), "#"): inRequirements = false } diff --git a/syft/pkg/cataloger/cpp/parse_conanfile_test.go b/syft/pkg/cataloger/cpp/parse_conanfile_test.go index bca49223a..93ba846a2 100644 --- a/syft/pkg/cataloger/cpp/parse_conanfile_test.go +++ b/syft/pkg/cataloger/cpp/parse_conanfile_test.go @@ -52,13 +52,13 @@ func TestParseConanfile(t *testing.T) { { Name: "spdlog", Version: "1.9.2", - PURL: "pkg:conan/spdlog@1.9.2", + PURL: "pkg:conan/my_user/spdlog@1.9.2?channel=my_channel", Locations: fixtureLocationSet, Language: pkg.CPP, Type: pkg.ConanPkg, MetadataType: pkg.ConanMetadataType, Metadata: pkg.ConanMetadata{ - Ref: "spdlog/1.9.2", + Ref: "spdlog/1.9.2@my_user/my_channel#1234567%%987654", }, }, { @@ -70,19 +70,19 @@ func TestParseConanfile(t *testing.T) { Type: pkg.ConanPkg, MetadataType: pkg.ConanMetadataType, Metadata: pkg.ConanMetadata{ - Ref: "sdl/2.0.20", + Ref: "sdl/2.0.20#1234567%%987654", }, }, { Name: "fltk", Version: "1.3.8", - PURL: "pkg:conan/fltk@1.3.8", + PURL: "pkg:conan/my_user/fltk@1.3.8?channel=my_channel", Locations: fixtureLocationSet, Language: pkg.CPP, Type: pkg.ConanPkg, MetadataType: pkg.ConanMetadataType, Metadata: pkg.ConanMetadata{ - Ref: "fltk/1.3.8", + Ref: "fltk/1.3.8@my_user/my_channel", }, }, } diff --git a/syft/pkg/cataloger/cpp/parse_conanlock_test.go b/syft/pkg/cataloger/cpp/parse_conanlock_test.go index b699081de..66dc9ca92 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock_test.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock_test.go @@ -13,20 +13,49 @@ func TestParseConanlock(t *testing.T) { fixture := "test-fixtures/conan.lock" expected := []pkg.Package{ { - Name: "zlib", - Version: "1.2.12", - PURL: "pkg:conan/zlib@1.2.12", + Name: "spdlog", + Version: "1.11.0", + PURL: "pkg:conan/spdlog@1.11.0", Locations: file.NewLocationSet(file.NewLocation(fixture)), Language: pkg.CPP, Type: pkg.ConanPkg, MetadataType: pkg.ConanLockMetadataType, Metadata: pkg.ConanLockMetadata{ - Ref: "zlib/1.2.12", + Ref: "spdlog/1.11.0", Options: map[string]string{ - "fPIC": "True", - "shared": "False", + "fPIC": "True", + "header_only": "False", + "no_exceptions": "False", + "shared": "False", + "wchar_filenames": "False", + "wchar_support": "False", + "fmt:fPIC": "True", + "fmt:header_only": "False", + "fmt:shared": "False", + "fmt:with_fmt_alias": "False", + "fmt:with_os_api": "True", + }, + Path: "conanfile.py", + Context: "host", + }, + }, + { + Name: "fmt", + Version: "9.1.0", + PURL: "pkg:conan/my_user/fmt@9.1.0?channel=my_channel", + Locations: file.NewLocationSet(file.NewLocation(fixture)), + Language: pkg.CPP, + Type: pkg.ConanPkg, + MetadataType: pkg.ConanLockMetadataType, + Metadata: pkg.ConanLockMetadata{ + Ref: "fmt/9.1.0@my_user/my_channel#6708c9d84f98d56a6d9f2e6c2d5639ba", + Options: map[string]string{ + "fPIC": "True", + "header_only": "False", + "shared": "False", + "with_fmt_alias": "False", + "with_os_api": "True", }, - Path: "all/conanfile.py", Context: "host", }, }, diff --git a/syft/pkg/cataloger/cpp/test-fixtures/conan.lock b/syft/pkg/cataloger/cpp/test-fixtures/conan.lock index cbae840d9..2632386b9 100644 --- a/syft/pkg/cataloger/cpp/test-fixtures/conan.lock +++ b/syft/pkg/cataloger/cpp/test-fixtures/conan.lock @@ -2,15 +2,24 @@ "graph_lock": { "nodes": { "0": { - "ref": "zlib/1.2.12", - "options": "fPIC=True\nshared=False", - "requires": [], - "path": "all/conanfile.py", + "ref": "spdlog/1.11.0", + "options": "fPIC=True\nheader_only=False\nno_exceptions=False\nshared=False\nwchar_filenames=False\nwchar_support=False\nfmt:fPIC=True\nfmt:header_only=False\nfmt:shared=False\nfmt:with_fmt_alias=False\nfmt:with_os_api=True", + "requires": [ + "1" + ], + "path": "conanfile.py", + "context": "host" + }, + "1": { + "ref": "fmt/9.1.0@my_user/my_channel#6708c9d84f98d56a6d9f2e6c2d5639ba", + "options": "fPIC=True\nheader_only=False\nshared=False\nwith_fmt_alias=False\nwith_os_api=True", + "package_id": "2c09c8f84c016041549fcee94e4caae5d89424b6", + "prev": "9f5ab13fc7c73e4a9f87e4e213f2cfa4", "context": "host" } }, - "revisions_enabled": false + "revisions_enabled": true }, "version": "0.4", - "profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=9\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" -} + "profile_host": "[settings]\narch=x86_64\narch.march=None\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++11\ncompiler.version=11\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" +} \ No newline at end of file diff --git a/syft/pkg/cataloger/cpp/test-fixtures/conanfile.txt b/syft/pkg/cataloger/cpp/test-fixtures/conanfile.txt index c265b1328..21db293a9 100644 --- a/syft/pkg/cataloger/cpp/test-fixtures/conanfile.txt +++ b/syft/pkg/cataloger/cpp/test-fixtures/conanfile.txt @@ -4,9 +4,9 @@ catch2/2.13.8 docopt.cpp/0.6.3 fmt/8.1.1 -spdlog/1.9.2 -sdl/2.0.20 -fltk/1.3.8 +spdlog/1.9.2@my_user/my_channel#1234567%%987654 +sdl/2.0.20#1234567%%987654 +fltk/1.3.8@my_user/my_channel [generators] cmake_find_package_multi