internal: redirect relevant requests to symbolsearch mode
When a user makes a single word search query containing a dot, there is
a good chance that they are searching for a <package>.<symbol>.
In that case, redirect the request if the <symbol> component does not
match a TLD in our database.
For golang/go#44142
Change-Id: I70ab0a74154b6c292fa649be6b2112f8906e291e
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/343569
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/internal/domain.go b/internal/domain.go
new file mode 100644
index 0000000..8bdb07c
--- /dev/null
+++ b/internal/domain.go
@@ -0,0 +1,186 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+// TopLevelDomains contains all of the top level domains in the discovery
+// database.
+var TopLevelDomains = map[string]bool{
+ "af": true,
+ "africa": true,
+ "ag": true,
+ "agency": true,
+ "ai": true,
+ "app": true,
+ "ar": true,
+ "as": true,
+ "at": true,
+ "au": true,
+ "beer": true,
+ "berlin": true,
+ "biz": true,
+ "blue": true,
+ "br": true,
+ "build": true,
+ "by": true,
+ "ca": true,
+ "cafe": true,
+ "casa": true,
+ "cc": true,
+ "ch": true,
+ "ci": true,
+ "city": true,
+ "cl": true,
+ "click": true,
+ "cloud": true,
+ "club": true,
+ "cn": true,
+ "co": true,
+ "codes": true,
+ "coffee": true,
+ "com": true,
+ "computer": true,
+ "consulting": true,
+ "coop": true,
+ "cx": true,
+ "cz": true,
+ "de": true,
+ "design": true,
+ "dev": true,
+ "digital": true,
+ "direct": true,
+ "dk": true,
+ "dog": true,
+ "download": true,
+ "earth": true,
+ "edu": true,
+ "ee": true,
+ "engineer": true,
+ "engineering": true,
+ "es": true,
+ "eu": true,
+ "farm": true,
+ "fi": true,
+ "fm": true,
+ "fr": true,
+ "fun": true,
+ "fyi": true,
+ "ga": true,
+ "gay": true,
+ "gg": true,
+ "gmbh": true,
+ "gov": true,
+ "gq": true,
+ "gt": true,
+ "haus": true,
+ "host": true,
+ "ht": true,
+ "hu": true,
+ "icu": true,
+ "id": true,
+ "ie": true,
+ "im": true,
+ "in": true,
+ "info": true,
+ "ink": true,
+ "io": true,
+ "ir": true,
+ "is": true,
+ "it": true,
+ "jp": true,
+ "ke": true,
+ "kr": true,
+ "kz": true,
+ "la": true,
+ "land": true,
+ "lgbt": true,
+ "li": true,
+ "life": true,
+ "link": true,
+ "london": true,
+ "lt": true,
+ "lv": true,
+ "me": true,
+ "media": true,
+ "mil": true,
+ "ml": true,
+ "mn": true,
+ "moe": true,
+ "ms": true,
+ "name": true,
+ "nc": true,
+ "net": true,
+ "network": true,
+ "ninja": true,
+ "nl": true,
+ "no": true,
+ "nu": true,
+ "nz": true,
+ "one": true,
+ "online": true,
+ "org": true,
+ "pe": true,
+ "pl": true,
+ "pm": true,
+ "pro": true,
+ "pub": true,
+ "pw": true,
+ "re": true,
+ "red": true,
+ "ren": true,
+ "rip": true,
+ "ro": true,
+ "rocks": true,
+ "ru": true,
+ "run": true,
+ "school": true,
+ "science": true,
+ "se": true,
+ "services": true,
+ "sh": true,
+ "site": true,
+ "sm": true,
+ "software": true,
+ "solutions": true,
+ "space": true,
+ "st": true,
+ "std": true,
+ "studio": true,
+ "study": true,
+ "su": true,
+ "supply": true,
+ "systems": true,
+ "taxi": true,
+ "team": true,
+ "tech": true,
+ "technology": true,
+ "tf": true,
+ "th": true,
+ "tickets": true,
+ "tk": true,
+ "tm": true,
+ "to": true,
+ "today": true,
+ "tools": true,
+ "top": true,
+ "town": true,
+ "trade": true,
+ "tv": true,
+ "tw": true,
+ "ua": true,
+ "uk": true,
+ "us": true,
+ "uz": true,
+ "ve": true,
+ "vip": true,
+ "vn": true,
+ "wang": true,
+ "website": true,
+ "work": true,
+ "works": true,
+ "wtf": true,
+ "xyz": true,
+ "yandex": true,
+ "zone": true,
+}
diff --git a/internal/frontend/search.go b/internal/frontend/search.go
index 4bb1151..ca25d95 100644
--- a/internal/frontend/search.go
+++ b/internal/frontend/search.go
@@ -373,9 +373,23 @@
if prefix := searchModePackage + ":"; strings.HasPrefix(q, prefix) {
return strings.TrimPrefix(q, prefix), false
}
+ if isPackageDotSymbol(q) {
+ return q, true
+ }
return q, strings.TrimSpace(r.FormValue("m")) == searchModeSymbol
}
+// isPackageDotSymbol reports whether the search query is of the form
+// <package>.<symbol>. The <symbol> component should not be a top-level domain
+// that is in our database.
+func isPackageDotSymbol(q string) bool {
+ if len(strings.Fields(q)) != 1 || !strings.ContainsAny(q, ".") {
+ return false
+ }
+ parts := strings.Split(q, ".")
+ return !internal.TopLevelDomains[parts[len(parts)-1]]
+}
+
// elapsedTime takes a date and returns returns human-readable,
// relative timestamps based on the following rules:
// (1) 'X hours ago' when X < 6
diff --git a/internal/frontend/search_test.go b/internal/frontend/search_test.go
index 50aac8e..55580c5 100644
--- a/internal/frontend/search_test.go
+++ b/internal/frontend/search_test.go
@@ -394,3 +394,25 @@
})
}
}
+
+func TestIsPackageDotSymbol(t *testing.T) {
+ for _, test := range []struct {
+ q string
+ want bool
+ }{
+ {"barista.run", false},
+ {"github.com", false},
+ {"julie.io", false},
+ {"my.name", false},
+ {"sql", false},
+ {"sql.DB", true},
+ {"sql.DB.Begin", true},
+ } {
+ t.Run(test.q, func(t *testing.T) {
+ got := isPackageDotSymbol(test.q)
+ if diff := cmp.Diff(test.want, got); diff != "" {
+ t.Errorf("mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}