From 5464484d73624d4e5ac57bc098f7203932a26837 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 8 Apr 2026 13:27:19 -0400 Subject: [PATCH] fix: Use catch-all route to avoid /{slug}/dl vs /static/ conflict Go 1.22+ panics when two patterns overlap without one being strictly more specific. GET /{slug}/dl and GET /static/ both match /static/dl. Replaced individual wildcard routes with GET /{path...} catch-all that manually dispatches to batch handlers. Co-Authored-By: Claude Opus 4.6 --- main.go | 65 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/main.go b/main.go index 7aae621..9a8c1f9 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "os/signal" + "strings" "syscall" "time" @@ -67,24 +68,54 @@ func main() { mux.HandleFunc("GET /f/{id}/auth", h.AuthPage) mux.HandleFunc("POST /f/{id}/auth", h.AuthSubmit) mux.HandleFunc("DELETE /f/{id}", h.Delete) - // Top-level batch routes (batches use their own slug) - mux.HandleFunc("GET /{slug}", h.Batch) - mux.HandleFunc("GET /{slug}/dl", h.BatchDownload) - mux.HandleFunc("GET /{slug}/auth", h.BatchAuthPage) - mux.HandleFunc("POST /{slug}/auth", h.BatchAuthSubmit) + // Catch-all for batch routes: /{slug}, /{slug}/dl, /{slug}/auth + // Uses {path...} to avoid conflicts with /static/ subtree pattern + mux.HandleFunc("GET /{path...}", func(w http.ResponseWriter, r *http.Request) { + p := r.PathValue("path") + parts := strings.SplitN(p, "/", 3) + if len(parts) == 0 || parts[0] == "" { + http.NotFound(w, r) + return + } + slug := parts[0] + // Backward compat: /b/{id} -> /{id} + if slug == "b" && len(parts) >= 2 { + target := "/" + strings.Join(parts[1:], "/") + http.Redirect(w, r, target, http.StatusMovedPermanently) + return + } + r.SetPathValue("slug", slug) + if len(parts) == 1 { + h.Batch(w, r) + } else if len(parts) == 2 { + switch parts[1] { + case "dl": + h.BatchDownload(w, r) + case "auth": + h.BatchAuthPage(w, r) + default: + http.NotFound(w, r) + } + } else { + http.NotFound(w, r) + } + }) - // Backward compat: /b/{id} redirects to /{id} - mux.HandleFunc("GET /b/{id}", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/"+r.PathValue("id"), http.StatusMovedPermanently) - }) - mux.HandleFunc("GET /b/{id}/dl", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/"+r.PathValue("id")+"/dl", http.StatusMovedPermanently) - }) - mux.HandleFunc("GET /b/{id}/auth", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/"+r.PathValue("id")+"/auth", http.StatusMovedPermanently) - }) - mux.HandleFunc("POST /b/{id}/auth", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/"+r.PathValue("id")+"/auth", http.StatusTemporaryRedirect) + mux.HandleFunc("POST /{path...}", func(w http.ResponseWriter, r *http.Request) { + p := r.PathValue("path") + parts := strings.SplitN(p, "/", 3) + // Backward compat: /b/{id}/auth -> /{id}/auth + if len(parts) >= 2 && parts[0] == "b" { + target := "/" + strings.Join(parts[1:], "/") + http.Redirect(w, r, target, http.StatusTemporaryRedirect) + return + } + if len(parts) == 2 && parts[1] == "auth" { + r.SetPathValue("slug", parts[0]) + h.BatchAuthSubmit(w, r) + return + } + http.NotFound(w, r) }) // Favicon (prevent 404)