diff --git a/README.md b/README.md index b9af89b74..2a7384cd3 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ A CLI tool and Go library for generating a Software Bill of Materials (SBOM) fro - Dart (pubs) - Debian (dpkg) - Dotnet (deps.json) +- Objective-C (cocoapods) - Go (go.mod, Go binaries) - Java (jar, ear, war, par, sar) - JavaScript (npm, yarn) @@ -44,6 +45,7 @@ A CLI tool and Go library for generating a Software Bill of Materials (SBOM) fro - Red Hat (rpm) - Ruby (gem) - Rust (cargo.lock) +- Swift (cocoapods) ## Installation diff --git a/go.mod b/go.mod index 4e93f4d90..1cddfed3e 100644 --- a/go.mod +++ b/go.mod @@ -63,6 +63,7 @@ require ( github.com/sigstore/rekor v0.4.1-0.20220114213500-23f583409af3 github.com/sigstore/sigstore v1.2.1-0.20220424143412-3d41663116d5 github.com/vbatts/go-mtree v0.5.0 + gopkg.in/yaml.v3 v3.0.0 ) require ( @@ -294,7 +295,6 @@ require ( gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect k8s.io/api v0.23.5 // indirect k8s.io/apimachinery v0.23.5 // indirect k8s.io/client-go v0.23.5 // indirect diff --git a/internal/formats/common/spdxhelpers/source_info.go b/internal/formats/common/spdxhelpers/source_info.go index 0b6614470..c330ea8c9 100644 --- a/internal/formats/common/spdxhelpers/source_info.go +++ b/internal/formats/common/spdxhelpers/source_info.go @@ -35,6 +35,8 @@ func SourceInfo(p pkg.Package) string { answer = "acquired package info from rust cargo manifest" case pkg.PhpComposerPkg: answer = "acquired package info from PHP composer manifest" + case pkg.CocoapodsPkg: + answer = "acquired package info from installed cocoapods manifest file" case pkg.ConanPkg: answer = "acquired package info from conan manifest" case pkg.PortagePkg: diff --git a/internal/formats/common/spdxhelpers/source_info_test.go b/internal/formats/common/spdxhelpers/source_info_test.go index b03c3897e..86a893181 100644 --- a/internal/formats/common/spdxhelpers/source_info_test.go +++ b/internal/formats/common/spdxhelpers/source_info_test.go @@ -150,6 +150,14 @@ func Test_SourceInfo(t *testing.T) { "from ALPM DB", }, }, + { + input: pkg.Package{ + Type: pkg.CocoapodsPkg, + }, + expected: []string{ + "installed cocoapods manifest file", + }, + }, { input: pkg.Package{ Type: pkg.ConanPkg, diff --git a/internal/formats/syftjson/model/package.go b/internal/formats/syftjson/model/package.go index eaf4a2e04..43a1d4520 100644 --- a/internal/formats/syftjson/model/package.go +++ b/internal/formats/syftjson/model/package.go @@ -145,6 +145,12 @@ func unpackMetadata(p *Package, unpacker packageMetadataUnpacker) error { return err } p.Metadata = payload + case pkg.CocoapodsMetadataType: + var payload pkg.CocoapodsMetadata + if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil { + return err + } + p.Metadata = payload case pkg.ConanaMetadataType: var payload pkg.ConanMetadata if err := json.Unmarshal(unpacker.Metadata, &payload); err != nil { diff --git a/syft/pkg/cataloger/cataloger.go b/syft/pkg/cataloger/cataloger.go index c0a5c0be6..06198b286 100644 --- a/syft/pkg/cataloger/cataloger.go +++ b/syft/pkg/cataloger/cataloger.go @@ -26,6 +26,7 @@ import ( "github.com/anchore/syft/syft/pkg/cataloger/rpmdb" "github.com/anchore/syft/syft/pkg/cataloger/ruby" "github.com/anchore/syft/syft/pkg/cataloger/rust" + "github.com/anchore/syft/syft/pkg/cataloger/swift" "github.com/anchore/syft/syft/source" ) @@ -78,6 +79,7 @@ func DirectoryCatalogers(cfg Config) []Cataloger { rust.NewCargoLockCataloger(), dart.NewPubspecLockCataloger(), dotnet.NewDotnetDepsCataloger(), + swift.NewCocoapodsCataloger(), cpp.NewConanfileCataloger(), portage.NewPortageCataloger(), }, cfg.Catalogers) @@ -105,6 +107,7 @@ func AllCatalogers(cfg Config) []Cataloger { dotnet.NewDotnetDepsCataloger(), php.NewPHPComposerInstalledCataloger(), php.NewPHPComposerLockCataloger(), + swift.NewCocoapodsCataloger(), cpp.NewConanfileCataloger(), portage.NewPortageCataloger(), }, cfg.Catalogers) diff --git a/syft/pkg/cataloger/swift/cataloger.go b/syft/pkg/cataloger/swift/cataloger.go new file mode 100644 index 000000000..d0c073408 --- /dev/null +++ b/syft/pkg/cataloger/swift/cataloger.go @@ -0,0 +1,17 @@ +/* +Package swift provides a concrete Cataloger implementation for Podfile.lock files. +*/ +package swift + +import ( + "github.com/anchore/syft/syft/pkg/cataloger/common" +) + +// NewCocoapodsCataloger returns a new Swift Cocoapods lock file cataloger object. +func NewCocoapodsCataloger() *common.GenericCataloger { + globParsers := map[string]common.ParserFn{ + "**/Podfile.lock": parsePodfileLock, + } + + return common.NewGenericCataloger(nil, globParsers, "cocoapods-cataloger") +} diff --git a/syft/pkg/cataloger/swift/parse_podfile_lock.go b/syft/pkg/cataloger/swift/parse_podfile_lock.go new file mode 100644 index 000000000..31819bef1 --- /dev/null +++ b/syft/pkg/cataloger/swift/parse_podfile_lock.go @@ -0,0 +1,76 @@ +package swift + +import ( + "fmt" + "io" + "io/ioutil" + "strings" + + "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/pkg/cataloger/common" + "gopkg.in/yaml.v3" +) + +// integrity check +var _ common.ParserFn = parsePodfileLock + +// parsePodfileLock is a parser function for Podfile.lock contents, returning all cocoapods pods discovered. +func parsePodfileLock(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) { + bytes, err := ioutil.ReadAll(reader) + if err != nil { + return nil, nil, fmt.Errorf("unable to read file: %w", err) + } + var podfile map[string]interface{} + if err = yaml.Unmarshal(bytes, &podfile); err != nil { + return nil, nil, fmt.Errorf("unable to parse yaml: %w", err) + } + + c, exists := podfile["SPEC CHECKSUMS"] + if !exists { + return nil, nil, fmt.Errorf("malformed podfile.lock: missing checksums") + } + checksums := c.(map[string]interface{}) + p, exists := podfile["PODS"] + if !exists { + return nil, nil, fmt.Errorf("malformed podfile.lock: missing checksums") + } + pods := p.([]interface{}) + + pkgs := []*pkg.Package{} + for _, podInterface := range pods { + var podBlob string + switch v := podInterface.(type) { + case map[string]interface{}: + for k := range v { + podBlob = k + } + case string: + podBlob = v + default: + return nil, nil, fmt.Errorf("malformed podfile.lock") + } + splits := strings.Split(podBlob, " ") + podName := splits[0] + podVersion := strings.TrimSuffix(strings.TrimPrefix(splits[1], "("), ")") + podRootPkg := strings.Split(podName, "/")[0] + pkgHash, exists := checksums[podRootPkg] + if !exists { + return nil, nil, fmt.Errorf("malformed podfile.lock: incomplete checksums") + } + pkgs = append(pkgs, &pkg.Package{ + Name: podName, + Version: podVersion, + Type: pkg.CocoapodsPkg, + Language: pkg.Swift, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: podName, + Version: podVersion, + PkgHash: pkgHash.(string), + }, + }) + } + + return pkgs, nil, nil +} diff --git a/syft/pkg/cataloger/swift/parse_podfile_lock_test.go b/syft/pkg/cataloger/swift/parse_podfile_lock_test.go new file mode 100644 index 000000000..8592eb4f8 --- /dev/null +++ b/syft/pkg/cataloger/swift/parse_podfile_lock_test.go @@ -0,0 +1,306 @@ +package swift + +import ( + "os" + "testing" + + "github.com/anchore/syft/syft/pkg" + "github.com/go-test/deep" +) + +func TestParsePodfileLock(t *testing.T) { + expected := []*pkg.Package{ + { + Name: "GlossButtonNode", + Version: "3.1.2", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "GlossButtonNode", + Version: "3.1.2", + PkgHash: "4ea1197a744f2fb5fb875fe31caf17ded4762e8f", + }, + }, + { + Name: "PINCache", + Version: "3.0.3", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "PINCache", + Version: "3.0.3", + PkgHash: "7a8fc1a691173d21dbddbf86cd515de6efa55086", + }, + }, + { + Name: "PINCache/Arc-exception-safe", + Version: "3.0.3", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "PINCache/Arc-exception-safe", + Version: "3.0.3", + PkgHash: "7a8fc1a691173d21dbddbf86cd515de6efa55086", + }, + }, + { + Name: "PINCache/Core", + Version: "3.0.3", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "PINCache/Core", + Version: "3.0.3", + PkgHash: "7a8fc1a691173d21dbddbf86cd515de6efa55086", + }, + }, + { + Name: "PINOperation", + Version: "1.2.1", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "PINOperation", + Version: "1.2.1", + PkgHash: "00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20", + }, + }, + { + Name: "PINRemoteImage/Core", + Version: "3.0.3", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "PINRemoteImage/Core", + Version: "3.0.3", + PkgHash: "f1295b29f8c5e640e25335a1b2bd9d805171bd01", + }, + }, + { + Name: "PINRemoteImage/iOS", + Version: "3.0.3", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "PINRemoteImage/iOS", + Version: "3.0.3", + PkgHash: "f1295b29f8c5e640e25335a1b2bd9d805171bd01", + }, + }, + { + Name: "PINRemoteImage/PINCache", + Version: "3.0.3", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "PINRemoteImage/PINCache", + Version: "3.0.3", + PkgHash: "f1295b29f8c5e640e25335a1b2bd9d805171bd01", + }, + }, + { + Name: "Reveal-SDK", + Version: "33", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "Reveal-SDK", + Version: "33", + PkgHash: "effba1c940b8337195563c425a6b5862ec875caa", + }, + }, + { + Name: "SwiftGen", + Version: "6.5.1", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "SwiftGen", + Version: "6.5.1", + PkgHash: "a6d22010845f08fe18fbdf3a07a8e380fd22e0ea", + }, + }, + { + Name: "Texture", + Version: "3.1.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "Texture", + Version: "3.1.0", + PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3", + }, + }, + { + Name: "Texture/AssetsLibrary", + Version: "3.1.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "Texture/AssetsLibrary", + Version: "3.1.0", + PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3", + }, + }, + { + Name: "Texture/Core", + Version: "3.1.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "Texture/Core", + Version: "3.1.0", + PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3", + }, + }, + { + Name: "Texture/MapKit", + Version: "3.1.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "Texture/MapKit", + Version: "3.1.0", + PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3", + }, + }, + { + Name: "Texture/Photos", + Version: "3.1.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "Texture/Photos", + Version: "3.1.0", + PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3", + }, + }, + { + Name: "Texture/PINRemoteImage", + Version: "3.1.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "Texture/PINRemoteImage", + Version: "3.1.0", + PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3", + }, + }, + { + Name: "Texture/Video", + Version: "3.1.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "Texture/Video", + Version: "3.1.0", + PkgHash: "2e8ab2519452515f7f5a520f5a8f7e0a413abfa3", + }, + }, + { + Name: "TextureSwiftSupport", + Version: "3.13.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "TextureSwiftSupport", + Version: "3.13.0", + PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb", + }, + }, + { + Name: "TextureSwiftSupport/Components", + Version: "3.13.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "TextureSwiftSupport/Components", + Version: "3.13.0", + PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb", + }, + }, + { + Name: "TextureSwiftSupport/Experiments", + Version: "3.13.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "TextureSwiftSupport/Experiments", + Version: "3.13.0", + PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb", + }, + }, + { + Name: "TextureSwiftSupport/Extensions", + Version: "3.13.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "TextureSwiftSupport/Extensions", + Version: "3.13.0", + PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb", + }, + }, + { + Name: "TextureSwiftSupport/LayoutSpecBuilders", + Version: "3.13.0", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "TextureSwiftSupport/LayoutSpecBuilders", + Version: "3.13.0", + PkgHash: "c515c7927fab92d0d9485f49b885b8c5de34fbfb", + }, + }, + { + Name: "TinyConstraints", + Version: "4.0.2", + Language: pkg.Swift, + Type: pkg.CocoapodsPkg, + MetadataType: pkg.CocoapodsMetadataType, + Metadata: pkg.CocoapodsMetadata{ + Name: "TinyConstraints", + Version: "4.0.2", + PkgHash: "7b7ccc0c485bb3bb47082138ff28bc33cd49897f", + }, + }, + } + + fixture, err := os.Open("test-fixtures/Podfile.lock") + if err != nil { + t.Fatalf("failed to open fixture: %+v", err) + } + + // TODO: no relationships are under test yet + actual, _, err := parsePodfileLock(fixture.Name(), fixture) + if err != nil { + t.Error(err) + } + + differences := deep.Equal(expected, actual) + if differences != nil { + t.Errorf("returned package list differed from expectation: %+v", differences) + } +} diff --git a/syft/pkg/cataloger/swift/test-fixtures/Podfile.lock b/syft/pkg/cataloger/swift/test-fixtures/Podfile.lock new file mode 100644 index 000000000..4d82d8ef2 --- /dev/null +++ b/syft/pkg/cataloger/swift/test-fixtures/Podfile.lock @@ -0,0 +1,92 @@ +PODS: + - GlossButtonNode (3.1.2): + - Texture/Core (~> 3) + - TextureSwiftSupport (>= 3.10.0) + - PINCache (3.0.3): + - PINCache/Arc-exception-safe (= 3.0.3) + - PINCache/Core (= 3.0.3) + - PINCache/Arc-exception-safe (3.0.3): + - PINCache/Core + - PINCache/Core (3.0.3): + - PINOperation (~> 1.2.1) + - PINOperation (1.2.1) + - PINRemoteImage/Core (3.0.3): + - PINOperation + - PINRemoteImage/iOS (3.0.3): + - PINRemoteImage/Core + - PINRemoteImage/PINCache (3.0.3): + - PINCache (~> 3.0.3) + - PINRemoteImage/Core + - Reveal-SDK (33) + - SwiftGen (6.5.1) + - Texture (3.1.0): + - Texture/AssetsLibrary (= 3.1.0) + - Texture/Core (= 3.1.0) + - Texture/MapKit (= 3.1.0) + - Texture/Photos (= 3.1.0) + - Texture/PINRemoteImage (= 3.1.0) + - Texture/Video (= 3.1.0) + - Texture/AssetsLibrary (3.1.0): + - Texture/Core + - Texture/Core (3.1.0) + - Texture/MapKit (3.1.0): + - Texture/Core + - Texture/Photos (3.1.0): + - Texture/Core + - Texture/PINRemoteImage (3.1.0): + - PINRemoteImage/iOS (~> 3.0.0) + - PINRemoteImage/PINCache + - Texture/Core + - Texture/Video (3.1.0): + - Texture/Core + - TextureSwiftSupport (3.13.0): + - Texture/Core (>= 3) + - TextureSwiftSupport/Components (= 3.13.0) + - TextureSwiftSupport/Experiments (= 3.13.0) + - TextureSwiftSupport/Extensions (= 3.13.0) + - TextureSwiftSupport/LayoutSpecBuilders (= 3.13.0) + - TextureSwiftSupport/Components (3.13.0): + - Texture/Core (>= 3) + - TextureSwiftSupport/LayoutSpecBuilders + - TextureSwiftSupport/Experiments (3.13.0): + - Texture/Core (>= 3) + - TextureSwiftSupport/Extensions (3.13.0): + - Texture/Core (>= 3) + - TextureSwiftSupport/LayoutSpecBuilders (3.13.0): + - Texture/Core (>= 3) + - TinyConstraints (4.0.2) + +DEPENDENCIES: + - GlossButtonNode + - Reveal-SDK + - SwiftGen + - Texture + - TextureSwiftSupport + - TinyConstraints + +SPEC REPOS: + trunk: + - GlossButtonNode + - PINCache + - PINOperation + - PINRemoteImage + - Reveal-SDK + - SwiftGen + - Texture + - TextureSwiftSupport + - TinyConstraints + +SPEC CHECKSUMS: + GlossButtonNode: 4ea1197a744f2fb5fb875fe31caf17ded4762e8f + PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 + PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 + PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01 + Reveal-SDK: effba1c940b8337195563c425a6b5862ec875caa + SwiftGen: a6d22010845f08fe18fbdf3a07a8e380fd22e0ea + Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3 + TextureSwiftSupport: c515c7927fab92d0d9485f49b885b8c5de34fbfb + TinyConstraints: 7b7ccc0c485bb3bb47082138ff28bc33cd49897f + +PODFILE CHECKSUM: 07aa55f54421f6e6d3a920c11716a89fc9243d1b + +COCOAPODS: 1.11.2 diff --git a/syft/pkg/cocoapods_metadata.go b/syft/pkg/cocoapods_metadata.go new file mode 100644 index 000000000..bad5708f7 --- /dev/null +++ b/syft/pkg/cocoapods_metadata.go @@ -0,0 +1,27 @@ +package pkg + +import ( + "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/linux" +) + +var _ urlIdentifier = (*CocoapodsMetadata)(nil) + +type CocoapodsMetadata struct { + Name string `mapstructure:"name" json:"name"` + Version string `mapstructure:"version" json:"version"` + PkgHash string `mapstructure:"pkgHash" json:"pkgHash"` +} + +func (m CocoapodsMetadata) PackageURL(_ *linux.Release) string { + var qualifiers packageurl.Qualifiers + + return packageurl.NewPackageURL( + packageurl.TypeCocoapods, + "", + m.Name, + m.Version, + qualifiers, + "", + ).ToString() +} diff --git a/syft/pkg/language.go b/syft/pkg/language.go index 297d09adc..e4d3a1713 100644 --- a/syft/pkg/language.go +++ b/syft/pkg/language.go @@ -21,6 +21,7 @@ const ( Rust Language = "rust" Dart Language = "dart" Dotnet Language = "dotnet" + Swift Language = "swift" CPP Language = "c++" ) @@ -35,6 +36,7 @@ var AllLanguages = []Language{ Rust, Dart, Dotnet, + Swift, CPP, } @@ -72,6 +74,8 @@ func LanguageByName(name string) Language { return Dart case packageurl.TypeDotnet: return Dotnet + case packageurl.TypeCocoapods, packageurl.TypeSwift, string(CocoapodsPkg): + return Swift case packageurl.TypeConan, string(CPP): return CPP default: diff --git a/syft/pkg/language_test.go b/syft/pkg/language_test.go index af9376a2e..69825928c 100644 --- a/syft/pkg/language_test.go +++ b/syft/pkg/language_test.go @@ -50,6 +50,10 @@ func TestLanguageFromPURL(t *testing.T) { purl: "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?type=zip&classifier=dist", want: Java, }, + { + purl: "pkg:cocoapods/GlossButtonNode@3.1.2", + want: Swift, + }, { purl: "pkg:conan/catch2@2.13.8", want: CPP, @@ -183,6 +187,18 @@ func TestLanguageByName(t *testing.T) { name: "dotnet", language: Dotnet, }, + { + name: "swift", + language: Swift, + }, + { + name: "pod", + language: Swift, + }, + { + name: "cocoapods", + language: Swift, + }, { name: "unknown", language: UnknownLanguage, diff --git a/syft/pkg/metadata.go b/syft/pkg/metadata.go index 8f73f3893..07591a16c 100644 --- a/syft/pkg/metadata.go +++ b/syft/pkg/metadata.go @@ -25,6 +25,7 @@ const ( KbPackageMetadataType MetadataType = "KbPackageMetadata" GolangBinMetadataType MetadataType = "GolangBinMetadata" PhpComposerJSONMetadataType MetadataType = "PhpComposerJsonMetadata" + CocoapodsMetadataType MetadataType = "CocoapodsMetadataType" ConanaMetadataType MetadataType = "ConanaMetadataType" PortageMetadataType MetadataType = "PortageMetadata" ) @@ -44,6 +45,7 @@ var AllMetadataTypes = []MetadataType{ KbPackageMetadataType, GolangBinMetadataType, PhpComposerJSONMetadataType, + CocoapodsMetadataType, ConanaMetadataType, PortageMetadataType, } @@ -63,6 +65,7 @@ var MetadataTypeByName = map[MetadataType]reflect.Type{ KbPackageMetadataType: reflect.TypeOf(KbPackageMetadata{}), GolangBinMetadataType: reflect.TypeOf(GolangBinMetadata{}), PhpComposerJSONMetadataType: reflect.TypeOf(PhpComposerJSONMetadata{}), + CocoapodsMetadataType: reflect.TypeOf(CocoapodsMetadata{}), ConanaMetadataType: reflect.TypeOf(ConanMetadata{}), PortageMetadataType: reflect.TypeOf(PortageMetadata{}), } diff --git a/syft/pkg/type.go b/syft/pkg/type.go index 7bbeb5a2d..8eddebf32 100644 --- a/syft/pkg/type.go +++ b/syft/pkg/type.go @@ -23,6 +23,7 @@ const ( KbPkg Type = "msrc-kb" DartPubPkg Type = "dart-pub" DotnetPkg Type = "dotnet" + CocoapodsPkg Type = "pod" ConanPkg Type = "conan" PortagePkg Type = "portage" ) @@ -44,6 +45,7 @@ var AllPkgs = []Type{ KbPkg, DartPubPkg, DotnetPkg, + CocoapodsPkg, ConanPkg, PortagePkg, } @@ -77,6 +79,8 @@ func (t Type) PackageURLType() string { return packageurl.TypePub case DotnetPkg: return packageurl.TypeDotnet + case CocoapodsPkg: + return packageurl.TypeCocoapods case ConanPkg: return packageurl.TypeConan case PortagePkg: @@ -124,6 +128,8 @@ func TypeByName(name string) Type { return DartPubPkg case packageurl.TypeDotnet: return DotnetPkg + case packageurl.TypeCocoapods: + return CocoapodsPkg case packageurl.TypeConan: return ConanPkg case "portage": diff --git a/syft/pkg/type_test.go b/syft/pkg/type_test.go index 23dc961ae..3c806f555 100644 --- a/syft/pkg/type_test.go +++ b/syft/pkg/type_test.go @@ -68,6 +68,10 @@ func TestTypeFromPURL(t *testing.T) { purl: "pkg:alpm/arch/linux@5.10.0?arch=x86_64&distro=arch", expected: AlpmPkg, }, + { + purl: "pkg:cocoapods/GlossButtonNode@3.1.2", + expected: CocoapodsPkg, + }, { purl: "pkg:conan/catch2@2.13.8", expected: ConanPkg, diff --git a/syft/pkg/url_test.go b/syft/pkg/url_test.go index 1e68ee5d8..7780db2ef 100644 --- a/syft/pkg/url_test.go +++ b/syft/pkg/url_test.go @@ -217,6 +217,20 @@ func TestPackageURL(t *testing.T) { expected: "pkg:alpm/arch/linux@5.10.0?distro=arch-rolling", }, + { + name: "cocoapods", + pkg: Package{ + Name: "GlossButtonNode", + Version: "3.1.2", + Language: Swift, + Type: CocoapodsPkg, + Metadata: CocoapodsMetadata{ + Name: "GlossButtonNode", + Version: "3.1.2", + }, + }, + expected: "pkg:cocoapods/GlossButtonNode@3.1.2", + }, { name: "conan", pkg: Package{ diff --git a/test/integration/catalog_packages_cases_test.go b/test/integration/catalog_packages_cases_test.go index 68bb30081..ada610c65 100644 --- a/test/integration/catalog_packages_cases_test.go +++ b/test/integration/catalog_packages_cases_test.go @@ -251,6 +251,36 @@ var dirOnlyTestCases = []testCase{ "junit": "4.12", }, }, + { + name: "find cocoapods packages", + pkgType: pkg.CocoapodsPkg, + pkgLanguage: pkg.Swift, + pkgInfo: map[string]string{ + "GlossButtonNode": "3.1.2", + "PINCache": "3.0.3", + "PINCache/Arc-exception-safe": "3.0.3", + "PINCache/Core": "3.0.3", + "PINOperation": "1.2.1", + "PINRemoteImage/Core": "3.0.3", + "PINRemoteImage/iOS": "3.0.3", + "PINRemoteImage/PINCache": "3.0.3", + "Reveal-SDK": "33", + "SwiftGen": "6.5.1", + "Texture": "3.1.0", + "Texture/AssetsLibrary": "3.1.0", + "Texture/Core": "3.1.0", + "Texture/MapKit": "3.1.0", + "Texture/Photos": "3.1.0", + "Texture/PINRemoteImage": "3.1.0", + "Texture/Video": "3.1.0", + "TextureSwiftSupport": "3.13.0", + "TextureSwiftSupport/Components": "3.13.0", + "TextureSwiftSupport/Experiments": "3.13.0", + "TextureSwiftSupport/Extensions": "3.13.0", + "TextureSwiftSupport/LayoutSpecBuilders": "3.13.0", + "TinyConstraints": "4.0.2", + }, + }, } var commonTestCases = []testCase{ diff --git a/test/integration/catalog_packages_test.go b/test/integration/catalog_packages_test.go index 8b02136b4..ce909a733 100644 --- a/test/integration/catalog_packages_test.go +++ b/test/integration/catalog_packages_test.go @@ -67,6 +67,7 @@ func TestPkgCoverageImage(t *testing.T) { definedLanguages.Remove(pkg.Rust.String()) definedLanguages.Remove(pkg.Dart.String()) definedLanguages.Remove(pkg.Dotnet.String()) + definedLanguages.Remove(string(pkg.Swift.String())) definedLanguages.Remove(pkg.CPP.String()) observedPkgs := internal.NewStringSet() @@ -81,6 +82,7 @@ func TestPkgCoverageImage(t *testing.T) { definedPkgs.Remove(string(pkg.RustPkg)) definedPkgs.Remove(string(pkg.DartPubPkg)) definedPkgs.Remove(string(pkg.DotnetPkg)) + definedPkgs.Remove(string(pkg.CocoapodsPkg)) definedPkgs.Remove(string(pkg.ConanPkg)) var cases []testCase diff --git a/test/integration/test-fixtures/image-pkg-coverage/cocoapods/Podfile.lock b/test/integration/test-fixtures/image-pkg-coverage/cocoapods/Podfile.lock new file mode 100644 index 000000000..4d82d8ef2 --- /dev/null +++ b/test/integration/test-fixtures/image-pkg-coverage/cocoapods/Podfile.lock @@ -0,0 +1,92 @@ +PODS: + - GlossButtonNode (3.1.2): + - Texture/Core (~> 3) + - TextureSwiftSupport (>= 3.10.0) + - PINCache (3.0.3): + - PINCache/Arc-exception-safe (= 3.0.3) + - PINCache/Core (= 3.0.3) + - PINCache/Arc-exception-safe (3.0.3): + - PINCache/Core + - PINCache/Core (3.0.3): + - PINOperation (~> 1.2.1) + - PINOperation (1.2.1) + - PINRemoteImage/Core (3.0.3): + - PINOperation + - PINRemoteImage/iOS (3.0.3): + - PINRemoteImage/Core + - PINRemoteImage/PINCache (3.0.3): + - PINCache (~> 3.0.3) + - PINRemoteImage/Core + - Reveal-SDK (33) + - SwiftGen (6.5.1) + - Texture (3.1.0): + - Texture/AssetsLibrary (= 3.1.0) + - Texture/Core (= 3.1.0) + - Texture/MapKit (= 3.1.0) + - Texture/Photos (= 3.1.0) + - Texture/PINRemoteImage (= 3.1.0) + - Texture/Video (= 3.1.0) + - Texture/AssetsLibrary (3.1.0): + - Texture/Core + - Texture/Core (3.1.0) + - Texture/MapKit (3.1.0): + - Texture/Core + - Texture/Photos (3.1.0): + - Texture/Core + - Texture/PINRemoteImage (3.1.0): + - PINRemoteImage/iOS (~> 3.0.0) + - PINRemoteImage/PINCache + - Texture/Core + - Texture/Video (3.1.0): + - Texture/Core + - TextureSwiftSupport (3.13.0): + - Texture/Core (>= 3) + - TextureSwiftSupport/Components (= 3.13.0) + - TextureSwiftSupport/Experiments (= 3.13.0) + - TextureSwiftSupport/Extensions (= 3.13.0) + - TextureSwiftSupport/LayoutSpecBuilders (= 3.13.0) + - TextureSwiftSupport/Components (3.13.0): + - Texture/Core (>= 3) + - TextureSwiftSupport/LayoutSpecBuilders + - TextureSwiftSupport/Experiments (3.13.0): + - Texture/Core (>= 3) + - TextureSwiftSupport/Extensions (3.13.0): + - Texture/Core (>= 3) + - TextureSwiftSupport/LayoutSpecBuilders (3.13.0): + - Texture/Core (>= 3) + - TinyConstraints (4.0.2) + +DEPENDENCIES: + - GlossButtonNode + - Reveal-SDK + - SwiftGen + - Texture + - TextureSwiftSupport + - TinyConstraints + +SPEC REPOS: + trunk: + - GlossButtonNode + - PINCache + - PINOperation + - PINRemoteImage + - Reveal-SDK + - SwiftGen + - Texture + - TextureSwiftSupport + - TinyConstraints + +SPEC CHECKSUMS: + GlossButtonNode: 4ea1197a744f2fb5fb875fe31caf17ded4762e8f + PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086 + PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20 + PINRemoteImage: f1295b29f8c5e640e25335a1b2bd9d805171bd01 + Reveal-SDK: effba1c940b8337195563c425a6b5862ec875caa + SwiftGen: a6d22010845f08fe18fbdf3a07a8e380fd22e0ea + Texture: 2e8ab2519452515f7f5a520f5a8f7e0a413abfa3 + TextureSwiftSupport: c515c7927fab92d0d9485f49b885b8c5de34fbfb + TinyConstraints: 7b7ccc0c485bb3bb47082138ff28bc33cd49897f + +PODFILE CHECKSUM: 07aa55f54421f6e6d3a920c11716a89fc9243d1b + +COCOAPODS: 1.11.2