diff --git a/grype/version/fuzzy_constraint.go b/grype/version/fuzzy_constraint.go index abda758d501..77d788332cf 100644 --- a/grype/version/fuzzy_constraint.go +++ b/grype/version/fuzzy_constraint.go @@ -9,7 +9,7 @@ import ( ) // derived from https://semver.org/, but additionally matches partial versions (e.g. "2.0") -var pseudoSemverPattern = regexp.MustCompile(`^(0|[1-9]\d*)(\.(0|[1-9]\d*))?(\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) +var pseudoSemverPattern = regexp.MustCompile(`^(0|[1-9]\d*)(\.(0|[1-9]\d*))?(\.(0|[1-9]\d*))?(?:(-|alpha|beta|rc)((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) type fuzzyConstraint struct { rawPhrase string diff --git a/grype/version/fuzzy_constraint_test.go b/grype/version/fuzzy_constraint_test.go index 150a383e30e..3be6292d469 100644 --- a/grype/version/fuzzy_constraint_test.go +++ b/grype/version/fuzzy_constraint_test.go @@ -264,6 +264,84 @@ func TestFuzzyConstraintSatisfaction(t *testing.T) { constraint: "> v1.5", satisfied: true, }, + { + name: "rc candidates with no '-' can match semver pattern", + version: "1.20rc1", + constraint: " = 1.20.0-rc1", + satisfied: true, + }, + { + name: "candidates ahead of alpha", + version: "3.11.0", + constraint: "> 3.11.0-alpha1", + satisfied: true, + }, + { + name: "candidates ahead of beta", + version: "3.11.0", + constraint: "> 3.11.0-beta1", + satisfied: true, + }, + { + name: "candidates ahead of same alpha versions", + version: "3.11.0-alpha5", + constraint: "> 3.11.0-alpha1", + satisfied: true, + }, + { + name: "candidates are placed correctly between alpha and release", + version: "3.11.0-beta5", + constraint: "3.11.0 || = 3.11.0-alpha1", + satisfied: false, + }, + { + name: "candidates with letter suffix are alphabetically greater than their versions", + version: "1.0.2a", + constraint: " < 1.0.2w", + satisfied: true, + }, + { + name: "candidates with multiple letter suffix are alphabetically greater than their versions", + version: "1.0.2zg", + constraint: " < 1.0.2zh", + satisfied: true, + }, + { + name: "candidates with pre suffix are sorted numerically", + version: "1.0.2pre1", + constraint: " < 1.0.2pre2", + satisfied: true, + }, + { + name: "candidates with letter suffix and r0 are alphabetically greater than their versions", + version: "1.0.2k-r0", + constraint: " < 1.0.2l-r0", + satisfied: true, + }, + { + name: "openssl version with letter suffix and r0 are alphabetically greater than their versions", + version: "1.0.2k-r0", + constraint: ">= 1.0.2", + satisfied: true, + }, + { + name: "openssl versions with letter suffix and r0 are alphabetically greater than their versions and compared equally to other lettered versions", + version: "1.0.2k-r0", + constraint: ">= 1.0.2, < 1.0.2m", + satisfied: true, + }, + { + name: "openssl pre2 is still considered less than release", + version: "1.1.1-pre2", + constraint: "> 1.1.1-pre1, < 1.1.1", + satisfied: true, + }, + { + name: "major version releases are less than their subsequent patch releases with letter suffixes", + version: "1.1.1", + constraint: "> 1.1.1-a", + satisfied: true, + }, } for _, test := range tests { @@ -275,3 +353,20 @@ func TestFuzzyConstraintSatisfaction(t *testing.T) { }) } } + +func TestPseudoSemverPattern(t *testing.T) { + tests := []struct { + name string + version string + valid bool + }{ + {name: "rc candidates are valid semver", version: "1.2.3-rc1", valid: true}, + {name: "rc candidates with no dash are valid semver", version: "1.2.3rc1", valid: true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.valid, pseudoSemverPattern.MatchString(test.version)) + }) + } +} diff --git a/grype/version/semantic_constraint_test.go b/grype/version/semantic_constraint_test.go index 42f36711c80..43dae2f2c42 100644 --- a/grype/version/semantic_constraint_test.go +++ b/grype/version/semantic_constraint_test.go @@ -70,6 +70,12 @@ func TestVersionSemantic(t *testing.T) { {version: "1.0.0-beta.11", constraint: "< 1.0.0-rc.1", satisfied: true}, {version: "1.0.0-rc.1", constraint: "> 1.0.0", satisfied: false}, {version: "1.0.0-rc.1", constraint: "< 1.0.0", satisfied: true}, + {version: "1.20rc1", constraint: " = 1.20.0-rc1", satisfied: true}, + {version: "1.21rc2", constraint: " = 1.21.1", satisfied: false}, + {version: "1.21rc2", constraint: " = 1.21", satisfied: false}, + {version: "1.21rc2", constraint: " = 1.21-rc2", satisfied: true}, + {version: "1.21rc2", constraint: " = 1.21.0-rc2", satisfied: true}, + {version: "1.21rc2", constraint: " = 1.21.0rc2", satisfied: true}, {version: "1.0.0-alpha.1", constraint: "> 1.0.0-alpha.1", satisfied: false}, {version: "1.0.0-alpha.2", constraint: "> 1.0.0-alpha.1", satisfied: true}, {version: "1.2.0-beta", constraint: ">1.0, <2.0", satisfied: true},