internal/frontend: copy pkgsite redirect logic from gddo
Code in motion. Copies pkgsite redirect from github.com/golang/gddo
and adds it as a middleware to pkgsite requests. Moving this redirect
to the pkgsite service will allow us to turn down the legacy godoc-org
project. After this CL is merged, the next step is to transfer the
godoc.org domain to pkgsite GCP project.
Change-Id: I322e9e6332dc5ee6dbbdf1e640ab3fdde5f15caa
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/425000
Run-TryBot: Jamal Carvalho <jamal@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
Reviewed-by: Julie Qiu <julieqiu@google.com>
diff --git a/cmd/frontend/main.go b/cmd/frontend/main.go
index d197198..82db064 100644
--- a/cmd/frontend/main.go
+++ b/cmd/frontend/main.go
@@ -181,6 +181,7 @@
middleware.RequestLog(cmdconfig.Logger(ctx, cfg, "frontend-log")),
middleware.AcceptRequests(http.MethodGet, http.MethodPost, http.MethodHead), // accept only GETs, POSTs and HEADs
middleware.BetaPkgGoDevRedirect(),
+ middleware.GodocOrgRedirect(),
middleware.Quota(cfg.Quota, cacheClient),
middleware.SecureHeaders(!*disableCSP), // must come before any caching for nonces to work
middleware.Experiment(experimenter),
diff --git a/internal/middleware/godocredirect.go b/internal/middleware/godocredirect.go
new file mode 100644
index 0000000..d8fd23c
--- /dev/null
+++ b/internal/middleware/godocredirect.go
@@ -0,0 +1,103 @@
+// Copyright 2020 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 middleware
+
+import (
+ "net/http"
+ "net/url"
+ "strings"
+
+ "golang.org/x/mod/module"
+)
+
+const (
+ goGithubRepoURLPath = "/github.com/golang/go"
+ pkgGoDevHost = "pkg.go.dev"
+)
+
+// GodocOrgRedirect redirects requests from godoc.org to pkg.go.dev.
+func GodocOrgRedirect() Middleware {
+ return func(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if !strings.HasSuffix(r.Host, "godoc.org") {
+ h.ServeHTTP(w, r)
+ return
+ }
+ http.Redirect(w, r, pkgGoDevURL(r.URL).String(), http.StatusMovedPermanently)
+ })
+ }
+}
+
+func pkgGoDevURL(godocURL *url.URL) *url.URL {
+ u := &url.URL{Scheme: "https", Host: pkgGoDevHost}
+ q := url.Values{"utm_source": []string{"godoc"}}
+
+ if strings.Contains(godocURL.Path, "/vendor/") || strings.HasSuffix(godocURL.Path, "/vendor") {
+ u.Path = "/"
+ u.RawQuery = q.Encode()
+ return u
+ }
+
+ if strings.HasPrefix(godocURL.Path, goGithubRepoURLPath) ||
+ strings.HasPrefix(godocURL.Path, goGithubRepoURLPath+"/src") {
+ u.Path = strings.TrimPrefix(strings.TrimPrefix(godocURL.Path, goGithubRepoURLPath), "/src")
+ if u.Path == "" {
+ u.Path = "/std"
+ }
+ u.RawQuery = q.Encode()
+ return u
+ }
+
+ _, isSVG := godocURL.Query()["status.svg"]
+ _, isPNG := godocURL.Query()["status.png"]
+ if isSVG || isPNG {
+ u.Path = "/badge" + godocURL.Path
+ u.RawQuery = q.Encode()
+ return u
+ }
+
+ switch godocURL.Path {
+ case "/-/go":
+ u.Path = "/std"
+ case "/-/about":
+ u.Path = "/about"
+ case "/C":
+ u.Path = "/C"
+ case "/":
+ if qparam := godocURL.Query().Get("q"); qparam != "" {
+ u.Path = "/search"
+ q.Set("q", qparam)
+ } else {
+ u.Path = "/"
+ }
+ case "":
+ u.Path = ""
+ case "/-/subrepo":
+ u.Path = "/search"
+ q.Set("q", "golang.org/x")
+ default:
+ {
+ godocURL.Path = strings.TrimSuffix(godocURL.Path, "/")
+ // If the import path is invalid, redirect to
+ // https://golang.org/issue/43036, so that the users has more context
+ // on why this path does not work on pkg.go.dev.
+ if err := module.CheckImportPath(strings.TrimPrefix(godocURL.Path, "/")); err != nil && strings.Contains(err.Error(), "invalid char") {
+ u.Host = "golang.org"
+ u.Path = "/issue/43036"
+ return u
+ }
+
+ u.Path = godocURL.Path
+ if _, ok := godocURL.Query()["imports"]; ok {
+ q.Set("tab", "imports")
+ } else if _, ok := godocURL.Query()["importers"]; ok {
+ q.Set("tab", "importedby")
+ }
+ }
+ }
+
+ u.RawQuery = q.Encode()
+ return u
+}
diff --git a/internal/middleware/godocredirect_test.go b/internal/middleware/godocredirect_test.go
new file mode 100644
index 0000000..9653639
--- /dev/null
+++ b/internal/middleware/godocredirect_test.go
@@ -0,0 +1,107 @@
+// Copyright 2022 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 middleware
+
+import (
+ "net/url"
+ "strings"
+ "testing"
+)
+
+func TestPkgGoDevURL(t *testing.T) {
+ testCases := []struct {
+ from, to string
+ }{
+ {
+ from: "https://godoc.org",
+ to: "https://pkg.go.dev?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/-/about",
+ to: "https://pkg.go.dev/about?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/-/go",
+ to: "https://pkg.go.dev/std?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/-/subrepo",
+ to: "https://pkg.go.dev/search?q=golang.org%2Fx&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/C",
+ to: "https://pkg.go.dev/C?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/?q=foo",
+ to: "https://pkg.go.dev/search?q=foo&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cloud.google.com/go/storage",
+ to: "https://pkg.go.dev/cloud.google.com/go/storage?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cloud.google.com/go/storage?imports",
+ to: "https://pkg.go.dev/cloud.google.com/go/storage?tab=imports&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cloud.google.com/go/storage?importers",
+ to: "https://pkg.go.dev/cloud.google.com/go/storage?tab=importedby&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cloud.google.com/go/storage?status.svg",
+ to: "https://pkg.go.dev/badge/cloud.google.com/go/storage?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cloud.google.com/go/storage?status.png",
+ to: "https://pkg.go.dev/badge/cloud.google.com/go/storage?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/github.com/golang/go",
+ to: "https://pkg.go.dev/std?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/github.com/golang/go/src",
+ to: "https://pkg.go.dev/std?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/github.com/golang/go/src/cmd/vet",
+ to: "https://pkg.go.dev/cmd/vet?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/golang.org/x/vgo/vendor/cmd/go/internal/modfile",
+ to: "https://pkg.go.dev/?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/golang.org/x/vgo/vendor",
+ to: "https://pkg.go.dev/?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cryptoscope.co/go/specialκ",
+ to: "https://golang.org/issue/43036",
+ },
+ {
+ from: "https://godoc.org/github.com/badimportpath//doubleslash",
+ to: "https://pkg.go.dev/github.com/badimportpath//doubleslash?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/github.com/google/go-containerregistry/",
+ to: "https://pkg.go.dev/github.com/google/go-containerregistry?utm_source=godoc",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(strings.ReplaceAll(tc.from, "/", " "), func(t *testing.T) {
+ u, err := url.Parse(tc.from)
+ if err != nil {
+ t.Fatalf("url.Parse(%q): %v", tc.from, err)
+ }
+ to := pkgGoDevURL(u)
+ if got, want := to.String(), tc.to; got != want {
+ t.Errorf("pkgGoDevURL(%q) = %q; want %q", u, got, want)
+ }
+ })
+ }
+}