internal/frontend: redirect searches for Go vulns

If a user searches for something in the form of a Go vuln
ID (e.g. GO-2021-0231), redirect to the page for that vuln.

Change-Id: I496b4c387fb97951312ca58ff61f3a5797c1b4d9
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/382095
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/frontend/search.go b/internal/frontend/search.go
index 34e98bc..5e6aeee 100644
--- a/internal/frontend/search.go
+++ b/internal/frontend/search.go
@@ -10,6 +10,7 @@
 	"fmt"
 	"net/http"
 	"path"
+	"regexp"
 	"sort"
 	"strings"
 	"sync"
@@ -303,17 +304,27 @@
 	return sr
 }
 
+// A regexp that matches Go vuln IDs.
+var goVulnIDRegexp = regexp.MustCompile("^GO-[0-9]{4}-[0-9]{4}$")
+
 // searchRequestRedirectPath returns the path that a search request should be
-// redirected to, or the empty string if there is no such path. If the user
-// types an existing package path into the search bar, we will redirect the
-// user to the details page. Standard library packages that only contain one
-// element (such as fmt, errors, etc.) will not redirect, to allow users to
-// search by those terms.
+// redirected to, or the empty string if there is no such path.
+//
+// If the user types an existing package path into the search bar, we will
+// redirect the user to the details page. Standard library packages that only
+// contain one element (such as fmt, errors, etc.) will not redirect, to allow
+// users to search by those terms.
+//
+// If the user types a name that is in the form of a Go vulnerability ID, we will
+// redirect to the page for that ID (whether or not it exists).
 func searchRequestRedirectPath(ctx context.Context, ds internal.DataSource, query string) string {
 	urlSchemeIdx := strings.Index(query, "://")
 	if urlSchemeIdx > -1 {
 		query = query[urlSchemeIdx+3:]
 	}
+	if goVulnIDRegexp.MatchString(query) {
+		return fmt.Sprintf("/vuln/%s", query)
+	}
 	requestedPath := path.Clean(query)
 	if !strings.Contains(requestedPath, "/") {
 		return ""
diff --git a/internal/frontend/search_test.go b/internal/frontend/search_test.go
index 44c3c3f..3552118 100644
--- a/internal/frontend/search_test.go
+++ b/internal/frontend/search_test.go
@@ -378,6 +378,8 @@
 		{"std does not redirect", "std", ""},
 		{"non-existent path does not redirect", "github.com/non-existent", ""},
 		{"trim URL scheme from query", "https://golang.org/x/tools", "/golang.org/x/tools"},
+		{"Go vuln redirects", "GO-1969-0720", "/vuln/GO-1969-0720"},
+		{"not a Go vuln", "somepkg/GO-1969-0720", ""},
 	} {
 		t.Run(test.name, func(t *testing.T) {
 			if got := searchRequestRedirectPath(ctx, testDB, test.query); got != test.want {