diff --git a/tools/osv-linter/internal/pkgchecker/ecosystems.go b/tools/osv-linter/internal/pkgchecker/ecosystems.go index 7152dce..52b62c3 100644 --- a/tools/osv-linter/internal/pkgchecker/ecosystems.go +++ b/tools/osv-linter/internal/pkgchecker/ecosystems.go @@ -133,7 +133,7 @@ func VersionsExistInEcosystem(pkg string, versions []string, ecosystem string) e case "CRAN": return nil case "crates.io": - return nil + return versionsExistInCrates(pkg, versions) case "Debian": return nil case "GIT": diff --git a/tools/osv-linter/internal/pkgchecker/ecosystems_test.go b/tools/osv-linter/internal/pkgchecker/ecosystems_test.go index 4eba81a..4875c49 100644 --- a/tools/osv-linter/internal/pkgchecker/ecosystems_test.go +++ b/tools/osv-linter/internal/pkgchecker/ecosystems_test.go @@ -2,6 +2,70 @@ package pkgchecker import "testing" +func Test_versionsExistInCrates(t *testing.T) { + t.Parallel() + + type args struct { + pkg string + versions []string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "multiple_versions_which_all_exist", + args: args{ + pkg: "defmt", + versions: []string{"0.0.0", "0.3.0", "0.3.100-rc.1", "1.0.0-rc.1", "1.0.1"}, + }, + wantErr: false, + }, + { + name: "multiple_versions_with_one_that_does_not_exist", + args: args{ + pkg: "defmt", + versions: []string{"1.1", "0.3.6-beta", "1.1.2"}, + }, + wantErr: true, + }, + { + name: "an_invalid_version", + args: args{ + pkg: "defmt", + versions: []string{"!"}, + }, + wantErr: true, + }, + { + name: "an_invalid_package", + args: args{ + pkg: "!", + versions: []string{"1.0.0"}, + }, + wantErr: true, + }, + { + name: "a_package_that_does_not_exit", + args: args{ + pkg: "not-a-real-package-hopefully", + versions: []string{"1.0.0"}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if err := versionsExistInCrates(tt.args.pkg, tt.args.versions); (err != nil) != tt.wantErr { + t.Errorf("versionsExistInCrates() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + func Test_versionsExistInGo(t *testing.T) { type args struct { pkg string diff --git a/tools/osv-linter/internal/pkgchecker/version_check.go b/tools/osv-linter/internal/pkgchecker/version_check.go index 227ada2..ec3fb33 100644 --- a/tools/osv-linter/internal/pkgchecker/version_check.go +++ b/tools/osv-linter/internal/pkgchecker/version_check.go @@ -15,6 +15,58 @@ import ( "golang.org/x/mod/semver" ) +// Confirm that all specified versions of a package exist in crates.io. +func versionsExistInCrates(pkg string, versions []string) error { + packageInstanceURL := fmt.Sprintf("%s/%s", EcosystemBaseURLs["crates.io"], pkg) + + resp, err := faulttolerant.Get(packageInstanceURL) + if err != nil { + return fmt.Errorf("unable to validate package: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL) + } + + // Parse the known versions from the JSON. + respJSON, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("unable to retrieve JSON for %q: %v", pkg, err) + } + // Fetch all known versions of package. + versionsInRepository := []string{} + releases := gjson.GetBytes(respJSON, "versions") + releases.ForEach(func(key, value gjson.Result) bool { + versionsInRepository = append(versionsInRepository, value.Get("num").String()) + return true // keep iterating. + }) + // Determine which referenced versions are missing. + versionsMissing := []string{} + for _, versionToCheckFor := range versions { + versionFound := false + vc, err := semantic.Parse(versionToCheckFor, "RubyGems") + if err != nil { + versionsMissing = append(versionsMissing, versionToCheckFor) + continue + } + for _, pkgversion := range versionsInRepository { + if r, err := vc.CompareStr(pkgversion); r == 0 && err == nil { + versionFound = true + break + } + } + if versionFound { + continue + } + versionsMissing = append(versionsMissing, versionToCheckFor) + } + if len(versionsMissing) > 0 { + return &MissingVersionsError{Package: pkg, Ecosystem: "RubyGems", Missing: versionsMissing, Known: versionsInRepository} + } + + return nil +} + // Confirm that all specified versions of a package exist in Go. func versionsExistInGo(pkg string, versions []string) error { if pkg == "stdlib" || pkg == "toolchain" {