diff --git a/tools/osv-linter/internal/pkgchecker/ecosystems.go b/tools/osv-linter/internal/pkgchecker/ecosystems.go index 7152dce..7e6f532 100644 --- a/tools/osv-linter/internal/pkgchecker/ecosystems.go +++ b/tools/osv-linter/internal/pkgchecker/ecosystems.go @@ -155,7 +155,7 @@ func VersionsExistInEcosystem(pkg string, versions []string, ecosystem string) e case "MinimOS": return nil case "npm": - return nil + return versionsExistInNpm(pkg, versions) case "NuGet": return nil case "openSUSE": diff --git a/tools/osv-linter/internal/pkgchecker/ecosystems_test.go b/tools/osv-linter/internal/pkgchecker/ecosystems_test.go index 4eba81a..f907a07 100644 --- a/tools/osv-linter/internal/pkgchecker/ecosystems_test.go +++ b/tools/osv-linter/internal/pkgchecker/ecosystems_test.go @@ -38,6 +38,70 @@ func Test_versionsExistInGo(t *testing.T) { } } +func Test_versionsExistInNpm(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: "semver", + versions: []string{"1.0.1", "2.0.0-beta", "5.7.1"}, + }, + wantErr: false, + }, + { + name: "multiple_versions_with_one_that_does_not_exist", + args: args{ + pkg: "semver", + versions: []string{"1.1", "2.0.0-beta1", "3.1.5", "5.1rc1"}, + }, + wantErr: true, + }, + { + name: "an_invalid_version", + args: args{ + pkg: "semver", + 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 := versionsExistInNpm(tt.args.pkg, tt.args.versions); (err != nil) != tt.wantErr { + t.Errorf("versionsExistInNpm() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + func Test_versionsExistInPackagist(t *testing.T) { t.Parallel() diff --git a/tools/osv-linter/internal/pkgchecker/version_check.go b/tools/osv-linter/internal/pkgchecker/version_check.go index 227ada2..5645c0f 100644 --- a/tools/osv-linter/internal/pkgchecker/version_check.go +++ b/tools/osv-linter/internal/pkgchecker/version_check.go @@ -129,6 +129,58 @@ func goVersionsExist(versions []string) error { return nil } +// Confirm that all specified versions of a package exist in npm. +func versionsExistInNpm(pkg string, versions []string) error { + packageInstanceURL := fmt.Sprintf("%s/%s", EcosystemBaseURLs["npm"], 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.@keys") + releases.ForEach(func(key, value gjson.Result) bool { + versionsInRepository = append(versionsInRepository, value.String()) + return true // keep iterating. + }) + // Determine which referenced versions are missing. + versionsMissing := []string{} + for _, versionToCheckFor := range versions { + versionFound := false + vc, err := semantic.Parse(versionToCheckFor, "npm") + 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: "npm", Missing: versionsMissing, Known: versionsInRepository} + } + + return nil +} + // Confirm that all specified versions of a package exist in Packagist. func versionsExistInPackagist(pkg string, versions []string) error { packageInstanceURL := fmt.Sprintf("%s/%s.json", EcosystemBaseURLs["Packagist"], pkg)