osv: canonicalize semvers

Instead of converting go1.16 to 1.16, convert it to 1.16.0, which is
required by tooling that uses strict SEMVER.

Change-Id: I6fa19b58edbe06a6ec42147bf3eb1be4e974ddb0
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/349930
Trust: Roland Shoemaker <roland@golang.org>
Trust: Katie Hockman <katie@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Katie Hockman <katie@golang.org>
Vulndb-Deploy: Roland Shoemaker <bracewell@google.com>
diff --git a/osv/json.go b/osv/json.go
index 5577937..24f0efb 100644
--- a/osv/json.go
+++ b/osv/json.go
@@ -73,7 +73,7 @@
 
 	// Strip and then add the semver prefix so we can support bare versions,
 	// versions prefixed with 'v', and versions prefixed with 'go'.
-	v = addSemverPrefix(removeSemverPrefix(v))
+	v = canonicalizeSemverPrefix(v)
 
 	var affected bool
 	for _, e := range ar.Events {
@@ -97,6 +97,14 @@
 	return s
 }
 
+// canonicalizeSemverPrefix turns a SEMVER string into the canonical
+// representation using the 'v' prefix, as used by the OSV format.
+// Input may be a bare SEMVER ("1.2.3"), Go prefixed SEMVER ("go1.2.3"),
+// or already canonical SEMVER ("v1.2.3").
+func canonicalizeSemverPrefix(s string) string {
+	return addSemverPrefix(removeSemverPrefix(s))
+}
+
 func generateAffectedRanges(versions []report.VersionRange) Affects {
 	a := AffectsRange{Type: TypeSemver}
 	if len(versions) == 0 || versions[0].Introduced == "" {
@@ -104,10 +112,12 @@
 	}
 	for _, v := range versions {
 		if v.Introduced != "" {
-			a.Events = append(a.Events, RangeEvent{Introduced: removeSemverPrefix(v.Introduced)})
+			v.Introduced = canonicalizeSemverPrefix(v.Introduced)
+			a.Events = append(a.Events, RangeEvent{Introduced: removeSemverPrefix(semver.Canonical(v.Introduced))})
 		}
 		if v.Fixed != "" {
-			a.Events = append(a.Events, RangeEvent{Fixed: removeSemverPrefix(v.Fixed)})
+			v.Fixed = canonicalizeSemverPrefix(v.Fixed)
+			a.Events = append(a.Events, RangeEvent{Fixed: removeSemverPrefix(semver.Canonical(v.Fixed))})
 		}
 	}
 	return Affects{a}
diff --git a/osv/json_test.go b/osv/json_test.go
index 9ef2a05..6ac9d31 100644
--- a/osv/json_test.go
+++ b/osv/json_test.go
@@ -284,3 +284,30 @@
 		}
 	}
 }
+
+func TestSemverCanonicalize(t *testing.T) {
+	in := []report.VersionRange{
+		{
+			Introduced: "go1.16",
+			Fixed:      "go1.17",
+		},
+	}
+	expected := Affects{
+		{
+			Type: TypeSemver,
+			Events: []RangeEvent{
+				{
+					Introduced: "1.16.0",
+				},
+				{
+					Fixed: "1.17.0",
+				},
+			},
+		},
+	}
+
+	out := generateAffectedRanges(in)
+	if !reflect.DeepEqual(out, expected) {
+		t.Fatalf("unexpected output: got %#v, want %#v", out, expected)
+	}
+}