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 {