From e1aa19f1e8c4a68b43add79374d6dfeae8bd9921 Mon Sep 17 00:00:00 2001 From: tjpcc Date: Thu, 19 Jan 2023 16:03:52 -0700 Subject: New sharedhost contrib package. ReplaceTilde simply replaces a leading ~ in the URL. Fixes #9. --- README.gmi | 3 +- README.md | 3 +- contrib/sharedhost/replacement.go | 46 +++++++++++++++++++++++++++ contrib/sharedhost/replacement_test.go | 57 ++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 contrib/sharedhost/replacement.go create mode 100644 contrib/sharedhost/replacement_test.go diff --git a/README.gmi b/README.gmi index a4e9946..9eb12be 100644 --- a/README.gmi +++ b/README.gmi @@ -34,10 +34,11 @@ The gemtext package provides a parser and converters for gemtext documents. It e This is where useful building blocks themselves start to come in. Sub-packages of contrib include Handler and Middleware implementations which accomplish the things your servers actually need to do. -So far there are at least 3 packages: +So far there are at least 4 packages: * log contains a simple request-logging middleware * fs has handlers that make file servers possible: serve files, build directory listings, etc * cgi includes handlers which can execute CGI programs +* sharedhost which provides means of handling /~username URLs * ...with more to come ## Get it diff --git a/README.md b/README.md index 98a3861..2b4f53b 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,12 @@ The gemtext package provides a parser and converters for gemtext documents. It e This is where useful building blocks themselves start to come in. Sub-packages of contrib include Handler and Middleware implementations which accomplish the things your servers actually need to do. -So far there are at least 3 packages: +So far there are at least 4 packages: * log contains a simple request-logging middleware * fs has handlers that make file servers possible: serve files, build directory listings, etc * cgi includes handlers which can execute CGI programs +* sharedhost which provides means of handling /~username URLs * ...with more to come ## Get it diff --git a/contrib/sharedhost/replacement.go b/contrib/sharedhost/replacement.go new file mode 100644 index 0000000..1fb2a0d --- /dev/null +++ b/contrib/sharedhost/replacement.go @@ -0,0 +1,46 @@ +package sharedhost + +import ( + "context" + "crypto/tls" + "net/url" + + "tildegit.org/tjp/gus" +) + +// ReplaceTilde builds a middleware which substitutes a leading '~' in the request path. +// +// It makes the alteration to a copy of the request which is then passed into the +// wrapped Handler. This way middlewares outside of this one inspecting the request +// afterwards will see the original URL. +// +// Typically the replacement should end with a "/", so that the ~ ends up mapping to a +// particular directory on the filesystem. For instance with a replacement string of +// "users/", "domain.com/~jim/index.gmi" maps to "domain.com/users/jim/index.gmi". +func ReplaceTilde(replacement string) gus.Middleware { + return func(inner gus.Handler) gus.Handler { + return func(ctx context.Context, request *gus.Request) *gus.Response { + if len(request.Path) > 1 && request.Path[0] == '/' && request.Path[1] == '~' { + request = cloneRequest(request) + request.Path = "/" + replacement + request.Path[2:] + } + + return inner(ctx, request) + } + } +} + +func cloneRequest(start *gus.Request) *gus.Request { + next := &gus.Request{} + *next = *start + + next.URL = &url.URL{} + *next.URL = *start.URL + + if start.TLSState != nil { + next.TLSState = &tls.ConnectionState{} + *next.TLSState = *start.TLSState + } + + return next +} diff --git a/contrib/sharedhost/replacement_test.go b/contrib/sharedhost/replacement_test.go new file mode 100644 index 0000000..cab80bb --- /dev/null +++ b/contrib/sharedhost/replacement_test.go @@ -0,0 +1,57 @@ +package sharedhost_test + +import ( + "context" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + + "tildegit.org/tjp/gus" + "tildegit.org/tjp/gus/contrib/sharedhost" +) + +func TestReplaceTilde(t *testing.T) { + tests := []struct { + replacement string + inputURL string + replacedPath string + }{ + { + replacement: "users/", + inputURL: "gemini://domain.com/~username/foo/bar", + replacedPath: "/users/username/foo/bar", + }, + { + replacement: "people-", + inputURL: "gemini://domain.com/non/match", + replacedPath: "/non/match", + }, + { + replacement: "people-", + inputURL: "gemini://domain.com/~someone/dir/file", + replacedPath: "/people-someone/dir/file", + }, + } + + for _, test := range tests { + t.Run(test.inputURL, func(t *testing.T) { + u, err := url.Parse(test.inputURL) + assert.Nil(t, err) + + originalPath := u.Path + + replacer := sharedhost.ReplaceTilde(test.replacement) + request := &gus.Request{URL: u} + handler := replacer(func(_ context.Context, request *gus.Request) *gus.Response { + assert.Equal(t, test.replacedPath, request.Path) + return &gus.Response{} + }) + + handler(context.Background(), request) + + // original request was unmodified + assert.Equal(t, originalPath, request.Path) + }) + } +} -- cgit v1.2.3