diff --git a/docs/docs/coverage/language/index.md b/docs/docs/coverage/language/index.md index ef45c8d25b1e..18c3aa1f3909 100644 --- a/docs/docs/coverage/language/index.md +++ b/docs/docs/coverage/language/index.md @@ -17,7 +17,7 @@ If the target is a pre-build project, like a code repository, Trivy will analyze On the other hand, when the target is a post-build artifact, like a container image, Trivy will analyze installed package metadata like `.gemspec`, binary files, and so on. | Language | File | Image[^5] | Rootfs[^6] | Filesystem[^7] | Repository[^8] | -| -------------------- | ------------------------------------------------------------------------------------------ | :-------: | :--------: | :------------: | :------------: | +|----------------------|--------------------------------------------------------------------------------------------|:---------:|:----------:|:--------------:|:--------------:| | [Ruby](ruby.md) | Gemfile.lock | - | - | ✅ | ✅ | | | gemspec | ✅ | ✅ | - | - | | [Python](python.md) | Pipfile.lock | - | - | ✅ | ✅ | @@ -45,6 +45,7 @@ On the other hand, when the target is a post-build artifact, like a container im | [Elixir](elixir.md) | mix.lock[^10] | - | - | ✅ | ✅ | | [Dart](dart.md) | pubspec.lock | - | - | ✅ | ✅ | | [Swift](swift.md) | Podfile.lock | - | - | ✅ | ✅ | +| | Package.resolved | - | - | ✅ | ✅ | The path of these files does not matter. diff --git a/docs/docs/coverage/language/swift.md b/docs/docs/coverage/language/swift.md index 226dee08054c..0245a06b44a3 100644 --- a/docs/docs/coverage/language/swift.md +++ b/docs/docs/coverage/language/swift.md @@ -1,10 +1,44 @@ # Swift -Trivy supports [CocoaPods][cocoapods] for Swift packages. +Trivy supports [CocoaPods][cocoapods] and [Swift][swift] package managers. + The following scanners are supported. -| Package manager | SBOM | Vulnerability | License | -| --------------- | :---: | :-----------: | :-----: | -| CocoaPods | ✓ | - | - | +| Package manager | SBOM | Vulnerability | License | +|-----------------|:----:|:-------------:|:-------:| +| Swift | ✓ | ✓ | - | +| CocoaPods | ✓ | ✓ | - | + +The following table provides an outline of the features Trivy offers. + +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|:---------------:|------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:| +| Swift | Package.resolved | ✓ | Included | - | ✓ | +| CocoaPods | Podfile.lock | ✓ | Included | ✓ | - | + +These may be enabled or disabled depending on the target. +See [here](./index.md) for the detail. + +## Swift +Trivy parses [Package.resolved][package-resolved] file to find dependencies. +Don't forget to update (`swift package update` command) this file before scanning. + +## CocoaPods +CocoaPods uses package names in `PodFile.lock`, but [GitHub Advisory Database (GHSA)][ghsa] Trivy relies on uses Git URLs. +We parse [the CocoaPods Specs][cocoapods-specs] to match package names and links. + +!!! note "Limitation" + Since [GHSA][ghsa] holds only Git URLs, such as github.com/apple/swift-nio, + Trivy can't identify affected submodules, and detect all submodules maintained by the same URL. + For example, [SwiftNIOHTTP1][niohttp1] and [SwiftNIOWebSocket][niowebsocket] both are maintained under `github.com/apple/swift-nio`, + and Trivy detect CVE-2022-3215 for both of them, even though only [SwiftNIOHTTP1][niohttp1] is actually affected. + +[cocoapods]: https://cocoapods.org/ +[cocoapods-specs]: https://github.com/CocoaPods/Specs +[ghsa]: https://github.com/advisories?query=type%3Areviewed+ecosystem%3Aswift +[swift]: https://www.swift.org/package-manager/ +[package-resolved]: https://github.com/apple/swift-package-manager/blob/4a42f2519e3f7b8a731c5ed89b47ed577df8f86c/Documentation/Usage.md#resolving-versions-packageresolved-file +[dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies -[cocoapods]: https://cocoapods.org/ \ No newline at end of file +[niohttp1]: https://cocoapods.org/pods/SwiftNIOHTTP1 +[niowebsocket]: https://cocoapods.org/pods/SwiftNIOWebSocket \ No newline at end of file diff --git a/docs/docs/scanner/vulnerability.md b/docs/docs/scanner/vulnerability.md index 820be63d4759..b1d9681197e7 100644 --- a/docs/docs/scanner/vulnerability.md +++ b/docs/docs/scanner/vulnerability.md @@ -77,7 +77,7 @@ See [here](../coverage/language/index.md#supported-languages) for the supported ### Data Sources | Language | Source | Commercial Use | Delay[^1] | -| -------- | --------------------------------------------------- | :------------: | :-------: | +|----------|-----------------------------------------------------|:--------------:|:---------:| | PHP | [PHP Security Advisories Database][php] | ✅ | - | | | [GitHub Advisory Database (Composer)][php-ghsa] | ✅ | - | | Python | [GitHub Advisory Database (pip)][python-ghsa] | ✅ | - | @@ -93,7 +93,8 @@ See [here](../coverage/language/index.md#supported-languages) for the supported | .NET | [GitHub Advisory Database (NuGet)][dotnet-ghsa] | ✅ | - | | C/C++ | [GitLab Advisories Community][gitlab] | ✅ | 1 month | | Dart | [GitHub Advisory Database (Pub)][pub-ghsa] | ✅ | - | -| Elixir | [GitHub Advisory Database (Erlang)][erlang-ghsa] | ✅ | | +| Elixir | [GitHub Advisory Database (Erlang)][erlang-ghsa] | ✅ | - | +| Swift | [GitHub Advisory Database (Swift)][swift-ghsa] | ✅ | - | [^1]: Intentional delay between vulnerability disclosure and registration in the DB @@ -168,6 +169,7 @@ Currently, specifying a username and password is not supported. [pub-ghsa]: https://github.com/advisories?query=ecosystem%3Apub [erlang-ghsa]: https://github.com/advisories?query=ecosystem%3Aerlang [go-ghsa]: https://github.com/advisories?query=ecosystem%3Ago +[swift-ghsa]: https://github.com/advisories?query=ecosystem%3Aswift [php]: https://github.com/FriendsOfPHP/security-advisories [ruby]: https://github.com/rubysec/ruby-advisory-db diff --git a/go.mod b/go.mod index d9d8f493ae0f..dd3ddac53376 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/alicebob/miniredis/v2 v2.30.4 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 github.com/aquasecurity/defsec v0.91.1 - github.com/aquasecurity/go-dep-parser v0.0.0-20230823094455-40c1f85cc942 + github.com/aquasecurity/go-dep-parser v0.0.0-20230825043456-df72a286b673 github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 @@ -23,7 +23,7 @@ require ( github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da github.com/aquasecurity/tml v0.6.1 - github.com/aquasecurity/trivy-db v0.0.0-20230823084507-315928e846ff + github.com/aquasecurity/trivy-db v0.0.0-20230828105148-2c9c4da5a321 github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728 github.com/aquasecurity/trivy-kubernetes v0.5.7-0.20230814115812-7afa52705226 github.com/aws/aws-sdk-go v1.44.273 diff --git a/go.sum b/go.sum index eb4829bc3fd5..ec2e0e341896 100644 --- a/go.sum +++ b/go.sum @@ -325,8 +325,8 @@ github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30 github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8= github.com/aquasecurity/defsec v0.91.1 h1:dBIPm6Tva9I+ZTQv+6t9wob3ZlMSu8NFqMJr4mgJC5A= github.com/aquasecurity/defsec v0.91.1/go.mod h1:l/srzxtuuyb6c6FlqUvMp3xw2ZbvuZ0l9972MNJM7V8= -github.com/aquasecurity/go-dep-parser v0.0.0-20230823094455-40c1f85cc942 h1:VGfeUtZyya9Vsl8enDurZ7pb/NDp2aJlL2rx2g4pR6A= -github.com/aquasecurity/go-dep-parser v0.0.0-20230823094455-40c1f85cc942/go.mod h1:0+GvQF0gL4YEAAUPpNeLeGpFDxMvvIHLMd7vk9bpwko= +github.com/aquasecurity/go-dep-parser v0.0.0-20230825043456-df72a286b673 h1:RMhUzr2ZfQ8OAO26aUkqbwfxK7d3ieFtPqUhiwTxOe0= +github.com/aquasecurity/go-dep-parser v0.0.0-20230825043456-df72a286b673/go.mod h1:0+GvQF0gL4YEAAUPpNeLeGpFDxMvvIHLMd7vk9bpwko= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM= github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s= github.com/aquasecurity/go-mock-aws v0.0.0-20230328195059-5bf52338aec3 h1:Vt9y1gZS5JGY3tsL9zc++Cg4ofX51CG7PaMyC5SXWPg= @@ -345,8 +345,8 @@ github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da h1:pj/adfN github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da/go.mod h1:852lbQLpK2nCwlR4ZLYIccxYCfoQao6q9Nl6tjz54v8= github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo= github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY= -github.com/aquasecurity/trivy-db v0.0.0-20230823084507-315928e846ff h1:+MLnPm81Msu921N/lBrKd/NwwBrrzRoTgyMq0pIUhbs= -github.com/aquasecurity/trivy-db v0.0.0-20230823084507-315928e846ff/go.mod h1:iJSGMMclPEhkYeyiN9i+gzjV9jhEv+XfPzfVgFhfvTE= +github.com/aquasecurity/trivy-db v0.0.0-20230828105148-2c9c4da5a321 h1:oAXkM8x6jMal+6p2XB78+ntPs5LGjxZhtWHdOy4crlg= +github.com/aquasecurity/trivy-db v0.0.0-20230828105148-2c9c4da5a321/go.mod h1:WJ5Qnk5ZNGWvks07GOZe2IOsuXrPfSC5c8hYGOGfrsU= github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728 h1:0eS+V7SXHgqoT99tV1mtMW6HL4HdoB9qGLMCb1fZp8A= github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8= github.com/aquasecurity/trivy-kubernetes v0.5.7-0.20230814115812-7afa52705226 h1:fL4BpAfnLFruHqkomRDAB7Lv8yv3zuKdg71mZk9y61c= diff --git a/integration/testdata/cocoapods.json.golden b/integration/testdata/cocoapods.json.golden index 5d66b7a6786a..14685eac577a 100644 --- a/integration/testdata/cocoapods.json.golden +++ b/integration/testdata/cocoapods.json.golden @@ -21,44 +21,39 @@ "Type": "cocoapods", "Packages": [ { - "ID": "AppCenter/4.2.0", - "Name": "AppCenter", - "Version": "4.2.0", - "DependsOn": [ - "AppCenter/Analytics/4.2.0", - "AppCenter/Crashes/4.2.0" - ], - "Layer": {} - }, - { - "ID": "AppCenter/Analytics/4.2.0", - "Name": "AppCenter/Analytics", - "Version": "4.2.0", - "DependsOn": [ - "AppCenter/Core/4.2.0" - ], - "Layer": {} - }, - { - "ID": "AppCenter/Core/4.2.0", - "Name": "AppCenter/Core", - "Version": "4.2.0", + "ID": "_NIODataStructures@2.41.0", + "Name": "_NIODataStructures", + "Version": "2.41.0", "Layer": {} - }, + } + ], + "Vulnerabilities": [ { - "ID": "AppCenter/Crashes/4.2.0", - "Name": "AppCenter/Crashes", - "Version": "4.2.0", - "DependsOn": [ - "AppCenter/Core/4.2.0" + "VulnerabilityID": "CVE-2022-3215", + "PkgID": "_NIODataStructures@2.41.0", + "PkgName": "_NIODataStructures", + "InstalledVersion": "2.41.0", + "FixedVersion": "2.29.1, 2.39.1, 2.42.0", + "Status": "fixed", + "Layer": {}, + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2022-3215", + "Title": "SwiftNIO vulnerable to Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')", + "Description": "`NIOHTTP1` and projects using it for generating HTTP responses, including SwiftNIO, can be subject to a HTTP Response Injection attack...", + "Severity": "MEDIUM", + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "V3Score": 5.3 + } + }, + "References": [ + "https://github.com/apple/swift-nio/security/advisories/GHSA-7fj7-39wj-c64f", + "https://nvd.nist.gov/vuln/detail/CVE-2022-3215", + "https://github.com/apple/swift-nio/commit/a16e2f54a25b2af217044e5168997009a505930f", + "https://github.com/advisories/GHSA-7fj7-39wj-c64f" ], - "Layer": {} - }, - { - "ID": "KeychainAccess/4.2.1", - "Name": "KeychainAccess", - "Version": "4.2.1", - "Layer": {} + "PublishedDate": "2023-06-07T16:01:53Z", + "LastModifiedDate": "2023-06-19T16:45:07Z" } ] } diff --git a/integration/testdata/fixtures/db/cocoapods.yaml b/integration/testdata/fixtures/db/cocoapods.yaml new file mode 100644 index 000000000000..3b218912e36a --- /dev/null +++ b/integration/testdata/fixtures/db/cocoapods.yaml @@ -0,0 +1,14 @@ +- bucket: "cocoapods::GitHub Security Advisory Cocoapods" + pairs: + - bucket: _NIODataStructures + pairs: + - key: CVE-2022-3215 + value: + PatchedVersions: + - "2.29.1" + - "2.39.1" + - "2.42.0" + VulnerableVersions: + - "< 2.29.1" + - ">= 2.39.0, < 2.39.1" + - ">= 2.41.0, < 2.42.0" \ No newline at end of file diff --git a/integration/testdata/fixtures/repo/cocoapods/Podfile.lock b/integration/testdata/fixtures/repo/cocoapods/Podfile.lock index 65600c35476c..905fe5fc5119 100644 --- a/integration/testdata/fixtures/repo/cocoapods/Podfile.lock +++ b/integration/testdata/fixtures/repo/cocoapods/Podfile.lock @@ -1,12 +1,16 @@ PODS: - - AppCenter (4.2.0): - - AppCenter/Analytics (= 4.2.0) - - AppCenter/Crashes (= 4.2.0) - - AppCenter/Analytics (4.2.0): - - AppCenter/Core - - AppCenter/Core (4.2.0) - - AppCenter/Crashes (4.2.0): - - AppCenter/Core - - KeychainAccess (4.2.1) - -COCOAPODS: 1.11.2 \ No newline at end of file + - _NIODataStructures (2.41.0) + +DEPENDENCIES: + - _NIODataStructures (= 2.41.0) + +SPEC REPOS: + trunk: + - _NIODataStructures + +SPEC CHECKSUMS: + _NIODataStructures: 3d45d8e70a1d17a15b1dc59d102c63dbc0525ffd + +PODFILE CHECKSUM: 2acff18c7f9246879b6a1a2d04e5decbc9410ef4 + +COCOAPODS: 1.12.1 \ No newline at end of file diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index 395738d76e14..52d0196efbd2 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -66,8 +66,10 @@ func NewDriver(libType string) (Driver, bool) { ecosystem = vulnerability.Swift comparer = compare.GenericComparer{} case ftypes.Cocoapods: - log.Logger.Warn("CocoaPods is supported for SBOM, not for vulnerability scanning") - return Driver{}, false + // CocoaPods uses RubyGems version specifiers + // https://guides.cocoapods.org/making/making-a-cocoapod.html#cocoapods-versioning-specifics + ecosystem = vulnerability.Cocoapods + comparer = rubygems.Comparer{} case ftypes.CondaPkg: log.Logger.Warn("Conda package is supported for SBOM, not for vulnerability scanning") return Driver{}, false diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 5f2be4551df9..896b76202c01 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -181,6 +181,7 @@ var ( TypeRustBinary, TypeConanLock, TypeCocoaPods, + TypeSwift, TypePubSpecLock, TypeMixLock, } @@ -199,6 +200,7 @@ var ( TypeConanLock, TypeGradleLock, TypeCocoaPods, + TypeSwift, TypePubSpecLock, TypeMixLock, } diff --git a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go index 0a12f0230eb3..8d8ea5620a6e 100644 --- a/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go +++ b/pkg/fanal/analyzer/language/swift/cocoapods/cocoapods_test.go @@ -27,37 +27,37 @@ func Test_cocoaPodsLockAnalyzer_Analyze(t *testing.T) { FilePath: "testdata/happy.lock", Libraries: types.Packages{ { - ID: "AppCenter/4.2.0", + ID: "AppCenter@4.2.0", Name: "AppCenter", Version: "4.2.0", DependsOn: []string{ - "AppCenter/Analytics/4.2.0", - "AppCenter/Crashes/4.2.0", + "AppCenter/Analytics@4.2.0", + "AppCenter/Crashes@4.2.0", }, }, { - ID: "AppCenter/Analytics/4.2.0", + ID: "AppCenter/Analytics@4.2.0", Name: "AppCenter/Analytics", Version: "4.2.0", DependsOn: []string{ - "AppCenter/Core/4.2.0", + "AppCenter/Core@4.2.0", }, }, { - ID: "AppCenter/Core/4.2.0", + ID: "AppCenter/Core@4.2.0", Name: "AppCenter/Core", Version: "4.2.0", }, { - ID: "AppCenter/Crashes/4.2.0", + ID: "AppCenter/Crashes@4.2.0", Name: "AppCenter/Crashes", Version: "4.2.0", DependsOn: []string{ - "AppCenter/Core/4.2.0", + "AppCenter/Core@4.2.0", }, }, { - ID: "KeychainAccess/4.2.1", + ID: "KeychainAccess@4.2.1", Name: "KeychainAccess", Version: "4.2.1", }, diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 6278943a870a..fa7ea391129e 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -57,6 +57,17 @@ func (p *PackageURL) Package() *ftypes.Package { } } + // CocoaPods purl has no namespace, but has subpath + // https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#cocoapods + if p.Type == packageurl.TypeCocoapods && p.Subpath != "" { + // CocoaPods uses / format for package name + // e.g. `pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib` => `GoogleUtilities/NSData+zlib` + pkg.Name = strings.Join([]string{ + p.Name, + p.Subpath, + }, "/") + } + if p.Type == packageurl.TypeRPM { rpmVer := version.NewVersion(p.Version) pkg.Release = rpmVer.Release() @@ -109,6 +120,8 @@ func (p *PackageURL) PackageType() string { case packageurl.TypeNuget: return ftypes.NuGet case packageurl.TypeSwift: + return ftypes.Swift + case packageurl.TypeCocoapods: return ftypes.Cocoapods case packageurl.TypeHex: return ftypes.Hex @@ -154,6 +167,7 @@ func NewPackageURL(t string, metadata types.Metadata, pkg ftypes.Package) (Packa name := pkg.Name ver := utils.FormatVersion(pkg) namespace := "" + subpath := "" switch ptype { case packageurl.TypeRPM: @@ -180,6 +194,10 @@ func NewPackageURL(t string, metadata types.Metadata, pkg ftypes.Package) (Packa namespace, name = parseGolang(name) case packageurl.TypeNPM: namespace, name = parseNpm(name) + case packageurl.TypeSwift: + namespace, name = parseSwift(name) + case packageurl.TypeCocoapods: + name, subpath = parseCocoapods(name) case packageurl.TypeOCI: purl, err := parseOCI(metadata) if err != nil { @@ -189,7 +207,7 @@ func NewPackageURL(t string, metadata types.Metadata, pkg ftypes.Package) (Packa } return PackageURL{ - PackageURL: *packageurl.NewPackageURL(ptype, namespace, name, ver, qualifiers, ""), + PackageURL: *packageurl.NewPackageURL(ptype, namespace, name, ver, qualifiers, subpath), FilePath: pkg.FilePath, }, nil } @@ -306,6 +324,22 @@ func parseComposer(pkgName string) (string, string) { return parsePkgName(pkgName) } +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#swift +func parseSwift(pkgName string) (string, string) { + return parsePkgName(pkgName) +} + +// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#cocoapods +func parseCocoapods(pkgName string) (string, string) { + var subpath string + index := strings.Index(pkgName, "/") + if index != -1 { + subpath = pkgName[index+1:] + pkgName = pkgName[:index] + } + return pkgName, subpath +} + // ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#npm func parseNpm(pkgName string) (string, string) { // the name must be lowercased @@ -330,6 +364,8 @@ func purlType(t string) string { case ftypes.Npm, ftypes.NodePkg, ftypes.Yarn, ftypes.Pnpm: return packageurl.TypeNPM case ftypes.Cocoapods: + return packageurl.TypeCocoapods + case ftypes.Swift: return packageurl.TypeSwift case ftypes.Hex: return packageurl.TypeHex diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index 0de388ce2b29..defd24a309e8 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -213,6 +213,40 @@ func TestNewPackageURL(t *testing.T) { }, }, }, + { + name: "swift package", + typ: ftypes.Swift, + pkg: ftypes.Package{ + ID: "github.com/apple/swift-atomics@1.1.0", + Name: "github.com/apple/swift-atomics", + Version: "1.1.0", + }, + want: purl.PackageURL{ + PackageURL: packageurl.PackageURL{ + Type: packageurl.TypeSwift, + Namespace: "github.com/apple", + Name: "swift-atomics", + Version: "1.1.0", + }, + }, + }, + { + name: "cocoapods package", + typ: ftypes.Cocoapods, + pkg: ftypes.Package{ + ID: "GoogleUtilities/NSData+zlib@7.5.2", + Name: "GoogleUtilities/NSData+zlib", + Version: "7.5.2", + }, + want: purl.PackageURL{ + PackageURL: packageurl.PackageURL{ + Type: packageurl.TypeCocoapods, + Name: "GoogleUtilities", + Version: "7.5.2", + Subpath: "NSData+zlib", + }, + }, + }, { name: "os package", typ: os.RedHat, @@ -377,7 +411,6 @@ func TestNewPackageURL(t *testing.T) { } func TestFromString(t *testing.T) { - testCases := []struct { name string purl string @@ -415,6 +448,19 @@ func TestFromString(t *testing.T) { }, }, }, + { + name: "happy path for coocapods", + purl: "pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib", + want: purl.PackageURL{ + PackageURL: packageurl.PackageURL{ + Type: packageurl.TypeCocoapods, + Name: "GoogleUtilities", + Version: "7.5.2", + Subpath: "NSData+zlib", + Qualifiers: packageurl.Qualifiers{}, + }, + }, + }, { name: "happy path for hex", purl: "pkg:hex/plug@1.14.0", @@ -514,3 +560,125 @@ func TestFromString(t *testing.T) { }) } } + +func TestPackage(t *testing.T) { + tests := []struct { + name string + pkgURL *purl.PackageURL + wantPkg *ftypes.Package + }{ + { + name: "rpm + Qualifiers", + pkgURL: &purl.PackageURL{ + PackageURL: packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "nodejs-full-i18n", + Version: "10.21.0-3.module_el8.2.0+391+8da3adc6", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "x86_64", + }, + { + Key: "epoch", + Value: "1", + }, + { + Key: "modularitylabel", + Value: "nodejs:10:8020020200707141642:6a468ee4", + }, + { + Key: "distro", + Value: "redhat-8", + }, + }, + }, + }, + wantPkg: &ftypes.Package{ + Name: "nodejs-full-i18n", + Version: "10.21.0", + Release: "3.module_el8.2.0+391+8da3adc6", + Arch: "x86_64", + Epoch: 1, + Modularitylabel: "nodejs:10:8020020200707141642:6a468ee4", + }, + }, + { + name: "composer with namespace", + pkgURL: &purl.PackageURL{ + PackageURL: packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "contracts", + Version: "v1.0.2", + }, + }, + wantPkg: &ftypes.Package{ + Name: "symfony/contracts", + Version: "v1.0.2", + }, + }, + { + name: "maven with namespace", + pkgURL: &purl.PackageURL{ + PackageURL: packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.0.4.RELEASE", + Qualifiers: packageurl.Qualifiers{}, + }, + }, + wantPkg: &ftypes.Package{ + Name: "org.springframework:spring-core", + Version: "5.0.4.RELEASE", + }, + }, + { + name: "cocoapods with subpath", + pkgURL: &purl.PackageURL{ + PackageURL: packageurl.PackageURL{ + Type: packageurl.TypeCocoapods, + Version: "4.2.0", + Name: "AppCenter", + Subpath: "Analytics", + Qualifiers: packageurl.Qualifiers{}, + }, + }, + wantPkg: &ftypes.Package{ + Name: "AppCenter/Analytics", + Version: "4.2.0", + }, + }, + { + name: "wrong epoch", + pkgURL: &purl.PackageURL{ + PackageURL: packageurl.PackageURL{ + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "acl", + Version: "2.2.53-1.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "epoch", + Value: "wrong", + }, + }, + }, + }, + wantPkg: &ftypes.Package{ + Name: "acl", + Version: "2.2.53", + Release: "1.el8", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.pkgURL.Package() + assert.Equal(t, tt.wantPkg, got) + }) + } +}