Skip to content

Commit 46ed42f

Browse files
committed
golink: don't modify URL path when resolving link
Both http.ServeMux as well as the http.Redirect method pass the request URL through `cleanPath` which, among other things, collapses double slashes `//` to a single slash `/`. Most of the time this is fine, since most servers treat those as identical anyway. But some destination servers need the original path unmodified. Since we're just redirecting, we don't need to be concerned with the additional benefits of `cleanPath` such as eliminating `../` path components, since that is the responsibility of the destination server to clean if needed. This change adds a separate root http.Handler for golink requests. It still uses http.ServeMux for internal endpoints, but serves golinks directly without passing the request through ServeMux. Additionally, this sets the redirect status and Location header directly rather than calling http.Redirect, since that also modifies the URL it is given. Fixes #89 Signed-off-by: Will Norris <[email protected]>
1 parent 2ff7d04 commit 46ed42f

File tree

2 files changed

+36
-12
lines changed

2 files changed

+36
-12
lines changed

golink.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,6 @@ func Run() error {
139139
// flush stats periodically
140140
go flushStatsLoop()
141141

142-
http.HandleFunc("/", serveGo)
143-
http.HandleFunc("/.detail/", serveDetail)
144-
http.HandleFunc("/.export", serveExport)
145-
http.HandleFunc("/.help", serveHelp)
146-
http.HandleFunc("/.opensearch", serveOpenSearch)
147-
http.HandleFunc("/.all", serveAll)
148-
http.HandleFunc("/.delete/", serveDelete)
149-
http.Handle("/.static/", http.StripPrefix("/.", http.FileServer(http.FS(embeddedFS))))
150-
151142
if *dev != "" {
152143
// override default hostname for dev mode
153144
if *hostname == defaultHostname {
@@ -160,7 +151,7 @@ func Run() error {
160151
}
161152

162153
log.Printf("Running in dev mode on %s ...", *dev)
163-
log.Fatal(http.ListenAndServe(*dev, nil))
154+
log.Fatal(http.ListenAndServe(*dev, serveHandler()))
164155
}
165156

166157
if *hostname == "" {
@@ -295,6 +286,29 @@ func deleteLinkStats(link *Link) {
295286
db.DeleteStats(link.Short)
296287
}
297288

289+
// serverHandler returns the main http.Handler for serving all requests.
290+
func serveHandler() http.Handler {
291+
mux := http.NewServeMux()
292+
mux.HandleFunc("/.detail/", serveDetail)
293+
mux.HandleFunc("/.export", serveExport)
294+
mux.HandleFunc("/.help", serveHelp)
295+
mux.HandleFunc("/.opensearch", serveOpenSearch)
296+
mux.HandleFunc("/.all", serveAll)
297+
mux.HandleFunc("/.delete/", serveDelete)
298+
mux.Handle("/.static/", http.StripPrefix("/.", http.FileServer(http.FS(embeddedFS))))
299+
300+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
301+
// all internal URLs begin with a leading "."; any other URL is treated as a go link.
302+
// Serve go links directly without passing through the ServeMux,
303+
// which sometimes modifies the request URL path, which we don't want.
304+
if !strings.HasPrefix(r.URL.Path, "/.") {
305+
serveGo(w, r)
306+
return
307+
}
308+
mux.ServeHTTP(w, r)
309+
})
310+
}
311+
298312
func serveHome(w http.ResponseWriter, short string) {
299313
var clicks []visitData
300314

@@ -408,7 +422,11 @@ func serveGo(w http.ResponseWriter, r *http.Request) {
408422
http.Error(w, err.Error(), http.StatusInternalServerError)
409423
return
410424
}
411-
http.Redirect(w, r, target.String(), http.StatusFound)
425+
426+
// http.Redirect always cleans the redirect URL, which we don't always want.
427+
// Instead, manually set status and Location header.
428+
w.WriteHeader(http.StatusFound)
429+
w.Header().Set("Location", target.String())
412430
}
413431

414432
// acceptHTML returns whether the request can accept a text/html response.

golink_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ func TestServeGo(t *testing.T) {
6969
wantStatus: http.StatusFound,
7070
wantLink: "http://who/p?q=1",
7171
},
72+
{
73+
name: "simple link with double slash in path",
74+
link: "/who/http://host",
75+
wantStatus: http.StatusFound,
76+
wantLink: "http://who/http://host",
77+
},
7278
{
7379
name: "user link",
7480
link: "/me",
@@ -105,7 +111,7 @@ func TestServeGo(t *testing.T) {
105111

106112
r := httptest.NewRequest("GET", tt.link, nil)
107113
w := httptest.NewRecorder()
108-
serveGo(w, r)
114+
serveHandler().ServeHTTP(w, r)
109115

110116
if w.Code != tt.wantStatus {
111117
t.Errorf("serveGo(%q) = %d; want %d", tt.link, w.Code, tt.wantStatus)

0 commit comments

Comments
 (0)