From c7ecf5b15f72e6b11f97d0dc648429a5ab729cd0 Mon Sep 17 00:00:00 2001 From: Katsuhiko YOSHIDA Date: Wed, 1 Nov 2023 21:32:23 +0900 Subject: [PATCH] Support cocoapods --- README.md | 11 +-- cmd/cocoapods.go | 98 +++++++++++++++++++++++ cmd/cocoapods_test.go | 50 ++++++++++++ cmd/diagnose.go | 15 ++-- cmd/swift/cocoapods/testdata/Podfile.lock | 11 +++ 5 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 cmd/cocoapods.go create mode 100644 cmd/cocoapods_test.go create mode 100644 cmd/swift/cocoapods/testdata/Podfile.lock diff --git a/README.md b/README.md index 741dfc2..3b16c94 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,16 @@ However, some packages have archived their source code repositories or have had | language | package manager | file (e.g.) | status | | -------- | ------------- | -- | :----: | -| Ruby | bundler | Gemfile.lock | :heavy_check_mark: | -| JavaScript | yarn | yarn.lock | :heavy_check_mark: | +| Go | golang | go.mod | :heavy_check_mark: | | JavaScript | npm | package-lock.json | :heavy_check_mark: | +| JavaScript | yarn | yarn.lock | :heavy_check_mark: | +| PHP | composer | composer.lock | :heavy_check_mark: | | Python | pip | requirements.txt | :heavy_check_mark: | -| Python | poetry | poetry.lock | (later) | | Python | pipenv | Pipfile.lock | (later) | -| PHP | composer | composer.lock | :heavy_check_mark: | -| Go | golang | go.mod | :heavy_check_mark: | +| Python | poetry | poetry.lock | (later) | +| Ruby | bundler | Gemfile.lock | :heavy_check_mark: | | Rust | cargo | Cargo.lock | :heavy_check_mark: | +| Swift | cocoapods | Podfile.lock | :heavy_check_mark: | ## Support repository hosting services diff --git a/cmd/cocoapods.go b/cmd/cocoapods.go new file mode 100644 index 0000000..2699fbb --- /dev/null +++ b/cmd/cocoapods.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "crypto/md5" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + + parser_io "github.com/aquasecurity/go-dep-parser/pkg/io" + "github.com/aquasecurity/go-dep-parser/pkg/swift/cocoapods" + "github.com/aquasecurity/go-dep-parser/pkg/types" +) + +// It will redirected from https://cdn.cocoapods.org/ +// Should be used origin URL? +const COCOA_PODS_REGISTRY_API = "https://cdn.jsdelivr.net/cocoa/Specs/%s" + +type CocoaPodsDoctor struct { + HTTPClient http.Client +} + +func NewCococaPodsDoctor() *CocoaPodsDoctor { + client := &http.Client{} + return &CocoaPodsDoctor{HTTPClient: *client} +} + +func (d *CocoaPodsDoctor) Libraries(r parser_io.ReadSeekerAt) []types.Library { + p := &cocoapods.Parser{} + libs, _, _ := p.Parse(r) + return libs +} + +func (d *CocoaPodsDoctor) SourceCodeURL(lib types.Library) (string, error) { + pod := CocoaPod{ + name: lib.Name, + version: lib.Version, + } + url, err := pod.fetchURLFromRegistry(d.HTTPClient) + return url, err +} + +type CocoaPodsRegistryResponse struct { + Homepage string `json:"homepage"` + Source struct { + Git string `json:"git"` + } `json:"source"` +} + +type CocoaPod struct { + name string + version string +} + +func (c *CocoaPod) BaseName() string { + splittedName := strings.Split(c.name, "/") + return splittedName[0] +} + +func (c *CocoaPod) PodspecPath() string { + md5 := md5.Sum([]byte(c.BaseName())) + hashString := fmt.Sprintf("%x", md5) + return fmt.Sprintf("%c/%c/%c/%s/%s/%s.podspec.json", hashString[0], hashString[1], hashString[2], c.BaseName(), c.version, c.BaseName()) +} + +func (c *CocoaPod) fetchURLFromRegistry(client http.Client) (string, error) { + url := fmt.Sprintf(COCOA_PODS_REGISTRY_API, c.PodspecPath()) + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return "", err + } + + resp, err := client.Do(req) + if err != nil { + return "", err + } + + defer resp.Body.Close() + if resp.StatusCode < 200 || 299 < resp.StatusCode { + m := fmt.Sprintf("Got status code: %d from %s", resp.StatusCode, url) + return "", errors.New(m) + } + + body, _ := io.ReadAll(resp.Body) + + var CocoaPodsRegistryResponse CocoaPodsRegistryResponse + err = json.Unmarshal(body, &CocoaPodsRegistryResponse) + if err != nil { + return "", err + } + + if CocoaPodsRegistryResponse.Source.Git != "" { + return CocoaPodsRegistryResponse.Source.Git, nil + } + return CocoaPodsRegistryResponse.Homepage, nil +} diff --git a/cmd/cocoapods_test.go b/cmd/cocoapods_test.go new file mode 100644 index 0000000..332ac36 --- /dev/null +++ b/cmd/cocoapods_test.go @@ -0,0 +1,50 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCocoaPod_PodspecPath(t *testing.T) { + tests := []struct { + name string + pod CocoaPod + }{ + { + name: "KeychanAccess", + pod: CocoaPod{ + name: "KeychainAccess", + version: "4.2.2", + }, + }, + { + name: "GoogleUtilities/Logger", + pod: CocoaPod{ + name: "GoogleUtilities/Logger", + version: "7.11.0", + }, + }, + } + expects := []struct { + name string + path string + }{ + { + name: "KeychainAccess", + path: "f/6/3/KeychainAccess/4.2.2/KeychainAccess.podspec.json", + }, + { + name: "GoogleUtilities/Logger", + path: "0/8/4/GoogleUtilities/7.11.0/GoogleUtilities.podspec.json", + }, + } + + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := tt.pod.PodspecPath() + expect := expects[i].path + assert.Equal(t, expect, actual) + }) + } +} diff --git a/cmd/diagnose.go b/cmd/diagnose.go index d31dfdb..807d389 100644 --- a/cmd/diagnose.go +++ b/cmd/diagnose.go @@ -187,13 +187,14 @@ var ( ) var doctors = map[string]MedicalTechnician{ - "bundler": NewBundlerDoctor(), - "yarn": NewYarnDoctor(), - "pip": NewPipDoctor(), - "npm": NewNPMDoctor(), - "composer": NewComposerDoctor(), - "golang": NewGolangDoctor(), - "cargo": NewCargoDoctor(), + "bundler": NewBundlerDoctor(), + "yarn": NewYarnDoctor(), + "pip": NewPipDoctor(), + "npm": NewNPMDoctor(), + "composer": NewComposerDoctor(), + "golang": NewGolangDoctor(), + "cargo": NewCargoDoctor(), + "cocoapods": NewCococaPodsDoctor(), } var diagnoseCmd = &cobra.Command{ diff --git a/cmd/swift/cocoapods/testdata/Podfile.lock b/cmd/swift/cocoapods/testdata/Podfile.lock new file mode 100644 index 0000000..cdf8974 --- /dev/null +++ b/cmd/swift/cocoapods/testdata/Podfile.lock @@ -0,0 +1,11 @@ +PODS: + - GoogleUtilities (7.10.0): + - GoogleUtilities/Logger (= 7.10.0) + - GoogleUtilities/Logger (7.10.0): + - GoogleUtilities/Environment + - AppCenter (4.2.0): + - AppCenter/Analytics (= 4.2.0) + - AppCenter/Crashes (= 4.2.0) + - KeychainAccess (4.2.1) + +COCOAPODS: 1.11.2 \ No newline at end of file