aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorGrégoire Duchêne <gduchene@awhk.org>2022-06-19 13:31:49 +0100
committerGrégoire Duchêne <gduchene@awhk.org>2022-06-19 13:40:39 +0100
commit41d23c22df853b0bdf35e2d0988c8d4c4281d42f (patch)
tree48ddc49039d6ceded97914374171157515b004be /pkg
parent21456154ef3172490cef72a3b69bf59bb9795e43 (diff)
Move redirection logic to a separate packagev0.4.0
Also, add an optional ‘-c’ flag to pass the path to a configuration file that can be used to specify several matching patterns and replacements.
Diffstat (limited to 'pkg')
-rw-r--r--pkg/redirector/redirector.go75
-rw-r--r--pkg/redirector/redirector_test.go130
-rw-r--r--pkg/redirector/template.tpl3
3 files changed, 208 insertions, 0 deletions
diff --git a/pkg/redirector/redirector.go b/pkg/redirector/redirector.go
new file mode 100644
index 0000000..96becb2
--- /dev/null
+++ b/pkg/redirector/redirector.go
@@ -0,0 +1,75 @@
+package redirector
+
+import (
+ "embed"
+ "encoding/json"
+ "log"
+ "net/http"
+ "path"
+ "regexp"
+ "strings"
+ "text/template"
+
+ "go.awhk.org/core"
+)
+
+var DefaultTemplate = template.Must(template.ParseFS(fs, "*.tpl"))
+
+var (
+ filter = core.FilterHTTPMethod(http.MethodGet)
+
+ //go:embed *.tpl
+ fs embed.FS
+)
+
+type Redirector struct {
+ Template *template.Template
+ Transformers []Transformer
+}
+
+func (h *Redirector) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ if filter(w, req) {
+ return
+ }
+
+ pkg := path.Join(req.Host, req.URL.Path)
+ for _, t := range h.Transformers {
+ if !t.Pattern.MatchString(pkg) {
+ continue
+ }
+
+ if req.URL.Query().Get("go-get") != "1" {
+ w.Header().Set("Location", "https://pkg.go.dev/"+pkg)
+ w.WriteHeader(http.StatusFound)
+ return
+ }
+
+ repo := t.Pattern.ReplaceAllString(pkg, t.Replacement)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusOK)
+ if err := h.Template.Execute(w, TemplateData{pkg, repo, t.VCS}); err != nil {
+ log.Println("Failed to execute template:", err)
+ }
+ return
+ }
+ w.WriteHeader(http.StatusNotFound)
+}
+
+type Pattern struct{ *regexp.Regexp }
+
+func (pp *Pattern) UnmarshalJSON(data []byte) (err error) {
+ var s string
+ if err = json.Unmarshal(data, &s); err != nil {
+ return
+ }
+ pp.Regexp, err = regexp.Compile(strings.ReplaceAll(s, `\\`, `\`))
+ return
+}
+
+type TemplateData struct{ Package, Repository, VCS string }
+
+type Transformer struct {
+ Pattern *Pattern `json:"pattern"`
+ Replacement string `json:"replacement"`
+ VCS string `json:"vcs"`
+}
diff --git a/pkg/redirector/redirector_test.go b/pkg/redirector/redirector_test.go
new file mode 100644
index 0000000..8761185
--- /dev/null
+++ b/pkg/redirector/redirector_test.go
@@ -0,0 +1,130 @@
+package redirector_test
+
+import (
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "regexp"
+ "testing"
+
+ "go.awhk.org/core"
+ "go.awhk.org/go-import-redirect/pkg/redirector"
+)
+
+func TestRedirector_ServeHTTP(s *testing.T) {
+ t := core.T{T: s}
+
+ meta := regexp.MustCompile(`<meta name="go-import" content="(.+?)">`)
+ redir := &redirector.Redirector{
+ Template: redirector.DefaultTemplate,
+ Transformers: []redirector.Transformer{
+ {
+ Pattern: &redirector.Pattern{regexp.MustCompile("go.example.com/x/baz(?:/.+)?")},
+ Replacement: "https://git.example.net/elsewhere/baz",
+ VCS: "git",
+ },
+ {
+ Pattern: &redirector.Pattern{regexp.MustCompile("go.example.com/x/([^/]+)(?:/.+)?")},
+ Replacement: "https://git.example.net/y/$1",
+ VCS: "git",
+ },
+ {
+ Pattern: &redirector.Pattern{regexp.MustCompile("go.example.com/x/qux(?:/.+)?")},
+ Replacement: "https://git.example.net/elsewhere/qux",
+ VCS: "git",
+ },
+ },
+ }
+
+ for _, tc := range []struct {
+ name string
+ method string
+ url string
+
+ expGetStatusCode int
+ expGetGoImport string
+ expVisitStatusCode int
+ expVisitLocation string
+ }{
+ {
+ name: "Match",
+ method: http.MethodGet,
+ url: "https://go.example.com/x/foo",
+
+ expGetStatusCode: http.StatusOK,
+ expGetGoImport: "go.example.com/x/foo git https://git.example.net/y/foo",
+ expVisitStatusCode: http.StatusFound,
+ expVisitLocation: "https://pkg.go.dev/go.example.com/x/foo",
+ },
+ {
+ name: "MatchDirectory",
+ method: http.MethodGet,
+ url: "https://go.example.com/x/foo/bar",
+
+ expGetStatusCode: http.StatusOK,
+ expGetGoImport: "go.example.com/x/foo/bar git https://git.example.net/y/foo",
+ expVisitStatusCode: http.StatusFound,
+ expVisitLocation: "https://pkg.go.dev/go.example.com/x/foo/bar",
+ },
+ {
+ name: "MatchIgnored",
+ method: http.MethodGet,
+ url: "https://go.example.com/x/qux",
+
+ expGetStatusCode: http.StatusOK,
+ expGetGoImport: "go.example.com/x/qux git https://git.example.net/y/qux",
+ expVisitStatusCode: http.StatusFound,
+ expVisitLocation: "https://pkg.go.dev/go.example.com/x/qux",
+ },
+ {
+ name: "MatchSpecific",
+ method: http.MethodGet,
+ url: "https://go.example.com/x/baz",
+
+ expGetStatusCode: http.StatusOK,
+ expGetGoImport: "go.example.com/x/baz git https://git.example.net/elsewhere/baz",
+ expVisitStatusCode: http.StatusFound,
+ expVisitLocation: "https://pkg.go.dev/go.example.com/x/baz",
+ },
+ {
+ name: "BadMethod",
+ method: http.MethodPost,
+ url: "https://go.example.com/x/baz",
+
+ expGetStatusCode: http.StatusMethodNotAllowed,
+ expVisitStatusCode: http.StatusMethodNotAllowed,
+ },
+ } {
+ t.Run("Get"+tc.name, func(t *core.T) {
+ var (
+ req = httptest.NewRequest(tc.method, tc.url+"?go-get=1", nil)
+ w = httptest.NewRecorder()
+ )
+ redir.ServeHTTP(w, req)
+
+ resp := w.Result()
+ t.AssertEqual(tc.expGetStatusCode, resp.StatusCode)
+ t.AssertEqual("", resp.Header.Get("Location"))
+
+ match := meta.FindSubmatch(core.Must(io.ReadAll(resp.Body)))
+ if tc.expGetGoImport == "" {
+ t.AssertEqual(0, len(match))
+ return
+ }
+ if t.AssertEqual(2, len(match)) {
+ t.AssertEqual(tc.expGetGoImport, string(match[1]))
+ }
+ })
+ t.Run("Visit"+tc.name, func(t *core.T) {
+ var (
+ req = httptest.NewRequest(tc.method, tc.url, nil)
+ w = httptest.NewRecorder()
+ )
+ redir.ServeHTTP(w, req)
+
+ resp := w.Result()
+ t.AssertEqual(tc.expVisitStatusCode, resp.StatusCode)
+ t.AssertEqual(tc.expVisitLocation, resp.Header.Get("Location"))
+ })
+ }
+}
diff --git a/pkg/redirector/template.tpl b/pkg/redirector/template.tpl
new file mode 100644
index 0000000..422024a
--- /dev/null
+++ b/pkg/redirector/template.tpl
@@ -0,0 +1,3 @@
+<!doctype html>
+<meta name="go-import" content="{{.Package}} {{.VCS}} {{.Repository}}">
+<title>go-import-redirect</title>