From 323238818e4df8e217c9ccc2f3b240b7103b1801 Mon Sep 17 00:00:00 2001 From: "jiapeng.han" Date: Thu, 28 Dec 2023 19:00:17 +0800 Subject: [PATCH] fix: fixture for version match --- cvefeed/nvd/match_cpe.go | 39 ++++++++++++----- cvefeed/nvd/smartvercmp.go | 1 + cvefeed/nvd/version.go | 86 +++++++++++++++++++++++++++++++++++++ cvefeed/nvd/version_test.go | 59 +++++++++++++++++++++++++ wfn/wfn.go | 2 +- 5 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 cvefeed/nvd/version.go create mode 100644 cvefeed/nvd/version_test.go diff --git a/cvefeed/nvd/match_cpe.go b/cvefeed/nvd/match_cpe.go index 0e0e2b2..17908ab 100644 --- a/cvefeed/nvd/match_cpe.go +++ b/cvefeed/nvd/match_cpe.go @@ -93,7 +93,7 @@ func (cm *cpeMatch) match(attr *wfn.Attributes, requireVersion bool) bool { return false } - if cm.Attributes.Version == wfn.Any { + if cm.Attributes.Version == wfn.Any || cm.Attributes.Version == wfn.NA { if !cm.hasVersionRanges { // if version is any and doesn't have version ranges, then it matches any return !requireVersion @@ -121,21 +121,38 @@ func (cm *cpeMatch) match(attr *wfn.Attributes, requireVersion bool) bool { // match version to ranges ver := wfn.StripSlashes(attr.Version) + return cm.compareVersions(ver) +} - matches := true +func (cm *cpeMatch) compareVersions(targetVersion string) bool { + result := cm.versionEndExcluding != "" || cm.versionEndIncluding != "" || cm.versionStartExcluding != "" || cm.versionStartIncluding != "" - if cm.versionStartIncluding != "" { - matches = matches && smartVerCmp(ver, cm.versionStartIncluding) >= 0 + if !result && cm.MatchOnlyVersion(&wfn.Attributes{ + Version: targetVersion, + }) { + return true } - if cm.versionStartExcluding != "" { - matches = matches && smartVerCmp(ver, cm.versionStartExcluding) > 0 + + target := ParseVersion(targetVersion) + if target == nil { + return false } - if cm.versionEndIncluding != "" { - matches = matches && smartVerCmp(ver, cm.versionEndIncluding) <= 0 + if result && cm.versionEndExcluding != "" { + endExcluding := ParseVersion(cm.versionEndExcluding) + result = endExcluding.CompareTo(target) > 0 } - if cm.versionEndExcluding != "" { - matches = matches && smartVerCmp(ver, cm.versionEndExcluding) < 0 + if result && cm.versionStartExcluding != "" { + startExcluding := ParseVersion(cm.versionStartExcluding) + result = startExcluding.CompareTo(target) < 0 + } + if result && cm.versionEndIncluding != "" { + endIncluding := ParseVersion(cm.versionEndIncluding) + result = result && endIncluding.CompareTo(target) >= 0 + } + if result && cm.versionStartIncluding != "" { + startIncluding := ParseVersion(cm.versionStartIncluding) + result = result && startIncluding.CompareTo(target) <= 0 } - return matches + return result } diff --git a/cvefeed/nvd/smartvercmp.go b/cvefeed/nvd/smartvercmp.go index c6a0927..b76efd5 100644 --- a/cvefeed/nvd/smartvercmp.go +++ b/cvefeed/nvd/smartvercmp.go @@ -96,3 +96,4 @@ func lpad(s string, n int) string { sb.WriteString(s) return sb.String() } + diff --git a/cvefeed/nvd/version.go b/cvefeed/nvd/version.go new file mode 100644 index 0000000..17d3418 --- /dev/null +++ b/cvefeed/nvd/version.go @@ -0,0 +1,86 @@ +package nvd + +import ( + "regexp" + "strconv" + "strings" +) + +type ComponentVersion struct { + VersionParts []string +} + +var versionPattern = regexp.MustCompile(`(\d+[a-z]{1,3}$|[a-z]{1,3}[_-]?\d+|\d+|(rc|release|snapshot|beta|alpha)$)`) + +func ParseVersion(version string) *ComponentVersion { + versionParts := make([]string, 0) + if version == "" { + return nil + } + + lcVersion := strings.ToLower(version) + versionSubmatch := versionPattern.FindAllStringSubmatch(lcVersion, -1) + for _, vs := range versionSubmatch { + versionParts = append(versionParts, vs...) + } + if len(versionParts) == 0 { + versionParts = append(versionParts, version) + } + + return &ComponentVersion{ + VersionParts: versionParts, + } +} + +func (cv *ComponentVersion) CompareTo(v *ComponentVersion) int { + if v == nil { + return 1 + } + left := cv.VersionParts + right := v.VersionParts + + var max int + if len(left) < len(right) { + max = len(left) + } else { + max = len(right) + } + for i := 0; i < max; i++ { + lStr := left[i] + rStr := right[i] + if lStr == rStr { + continue + } + l, lerr := strconv.Atoi(lStr) + r, rerr := strconv.Atoi(rStr) + if lerr != nil || rerr != nil { + comp := strings.Compare(lStr, rStr) + if comp < 0 { + return -1 + } else if comp > 0 { + return 1 + } + } + if l < r { + return -1 + } else if l > r { + return 1 + } + } + if len(left) == max && len(right) == len(left) + 1 && right[len(right) - 1] == "0" { + return 0 + } else if len(right) == max && len(left) == len(right) + 1 && left[len(left) - 1] == "0" { + return 0 + } else { + if len(left) > len(right) { + return 1 + } + if len(right) > len(left) { + return -1 + } + if len(right) == len(left) { + return 0 + } + } + return 0 +} diff --git a/cvefeed/nvd/version_test.go b/cvefeed/nvd/version_test.go new file mode 100644 index 0000000..a4f15ee --- /dev/null +++ b/cvefeed/nvd/version_test.go @@ -0,0 +1,59 @@ +package nvd + +import "testing" + +func TestComponentVersion_CompareTo(t *testing.T) { + type fields struct { + VersionParts []string + } + type args struct { + v *ComponentVersion + } + tests := []struct { + name string + fields fields + args args + want int + }{ + { + name: "test-version-compare", + fields: fields{ + VersionParts: ParseVersion("2.6.3").VersionParts, + }, + args: args{ + v: ParseVersion("2.6.4"), + }, + want: -1, + }, + { + name: "test-version-compare", + fields: fields{ + VersionParts: ParseVersion("7.0.0").VersionParts, + }, + args: args{ + v: ParseVersion("7.0.0."), + }, + want: 0, + }, + { + name: "test-version-compare", + fields: fields{ + VersionParts: ParseVersion("x.y.z").VersionParts, + }, + args: args{ + v: ParseVersion("7.0.0"), + }, + want: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cv := &ComponentVersion{ + VersionParts: tt.fields.VersionParts, + } + if got := cv.CompareTo(tt.args.v); got != tt.want { + t.Errorf("ComponentVersion.CompareTo() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/wfn/wfn.go b/wfn/wfn.go index 0cce686..2f658d5 100644 --- a/wfn/wfn.go +++ b/wfn/wfn.go @@ -29,7 +29,7 @@ var KnownParts = map[string]string{ // Possible logical value of Attributes // empty string considered ANY when parsing and unquoted "-" is illegal in WFN attribute-value const ( - Any = "" + Any = "*" NA = "-" )