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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-08 13:27:19 -04:00
parent c5b710498f
commit 5464484d73
1 changed files with 48 additions and 17 deletions

65
main.go
View File

@ -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)