From 252dd61dd30c2bcf2bcfc30be1428c68d8f25934 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 19 Nov 2024 14:11:06 -0500 Subject: [PATCH] Fix for allowing some version that were invalid The NewVersion function, which uses the loose parser, had the regex for detection updated based on the official one. A change was made to allow for versions like 1.2 and other "loose" ones (to use the node semver term). StrictNewVersion had some internal validation updated to catch issues. For NewVersion, the benchmarking is now faster than the previous regex. Ref #211 Signed-off-by: Matt Farina --- version.go | 24 +++++++++++++++--------- version_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/version.go b/version.go index ff499fb..304edc3 100644 --- a/version.go +++ b/version.go @@ -39,9 +39,11 @@ var ( ) // semVerRegex is the regular expression used to parse a semantic version. -const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + - `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + - `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +// This is not the official regex from the semver spec. It has been modified to allow for loose handling +// where versions like 2.1 are detected. +const semVerRegex string = `v?(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-]+)*))?` // Version represents a single semantic version. type Version struct { @@ -146,8 +148,8 @@ func NewVersion(v string) (*Version, error) { } sv := &Version{ - metadata: m[8], - pre: m[5], + metadata: m[5], + pre: m[4], original: v, } @@ -158,7 +160,7 @@ func NewVersion(v string) (*Version, error) { } if m[2] != "" { - sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64) + sv.minor, err = strconv.ParseUint(m[2], 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } @@ -167,7 +169,7 @@ func NewVersion(v string) (*Version, error) { } if m[3] != "" { - sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64) + sv.patch, err = strconv.ParseUint(m[3], 10, 64) if err != nil { return nil, fmt.Errorf("Error parsing version segment: %s", err) } @@ -612,7 +614,9 @@ func containsOnly(s string, comp string) bool { func validatePrerelease(p string) error { eparts := strings.Split(p, ".") for _, p := range eparts { - if containsOnly(p, num) { + if p == "" { + return ErrInvalidMetadata + } else if containsOnly(p, num) { if len(p) > 1 && p[0] == '0' { return ErrSegmentStartsZero } @@ -631,7 +635,9 @@ func validatePrerelease(p string) error { func validateMetadata(m string) error { eparts := strings.Split(m, ".") for _, p := range eparts { - if !containsOnly(p, allowed) { + if p == "" { + return ErrInvalidMetadata + } else if !containsOnly(p, allowed) { return ErrInvalidMetadata } } diff --git a/version_test.go b/version_test.go index 74a1e91..2899598 100644 --- a/version_test.go +++ b/version_test.go @@ -21,6 +21,7 @@ func TestStrictNewVersion(t *testing.T) { {"v1.0", true}, {"1", true}, {"v1", true}, + {"1.2", true}, {"1.2.beta", true}, {"v1.2.beta", true}, {"foo", true}, @@ -45,6 +46,25 @@ func TestStrictNewVersion(t *testing.T) { // The SemVer spec in a pre-release expects to allow [0-9A-Za-z-]. But, // the lack of all 3 parts in this version should produce an error. {"20221209-update-renovatejson-v4", true}, + + // Various cases that are invalid semver + {"1.1.2+.123", true}, // A leading . in build metadata. This would signify that the first segment is empty + {"1.0.0-alpha_beta", true}, // An underscore in the pre-release is an invalid character + {"1.0.0-alpha..", true}, // Multiple empty segments + {"1.0.0-alpha..1", true}, // Multiple empty segments but one with a value + {"01.1.1", true}, // A leading 0 on a number segment + {"1.01.1", true}, // A leading 0 on a number segment + {"1.1.01", true}, // A leading 0 on a number segment + {"9.8.7+meta+meta", true}, // Multiple metadata parts + {"1.2.31----RC-SNAPSHOT.12.09.1--.12+788", true}, // Leading 0 in a number part of a pre-release segment + {"1.2.3-0123", true}, + {"1.2.3-0123.0123", true}, + {"+invalid", true}, + {"-invalid", true}, + {"-invalid.01", true}, + {"alpha+beta", true}, + {"1.2.3-alpha_beta+foo", true}, + {"1.0.0-alpha..1", true}, } for _, tc := range tests { @@ -101,6 +121,17 @@ func TestNewVersion(t *testing.T) { // The SemVer spec in a pre-release expects to allow [0-9A-Za-z-]. {"20221209-update-renovatejson-v4", false}, + + // Various cases that are invalid semver + {"1.1.2+.123", true}, // A leading . in build metadata. This would signify that the first segment is empty + {"1.0.0-alpha_beta", true}, // An underscore in the pre-release is an invalid character + {"1.0.0-alpha..", true}, // Multiple empty segments + {"1.0.0-alpha..1", true}, // Multiple empty segments but one with a value + {"01.1.1", true}, // A leading 0 on a number segment + {"1.01.1", true}, // A leading 0 on a number segment + {"1.1.01", true}, // A leading 0 on a number segment + {"9.8.7+meta+meta", true}, // Multiple metadata parts + {"1.2.31----RC-SNAPSHOT.12.09.1--.12+788", true}, // Leading 0 in a number part of a pre-release segment } for _, tc := range tests {