From 2154dd9c50a6ca36e164fc34de07608d4736a276 Mon Sep 17 00:00:00 2001 From: rissrice2105-agent Date: Sun, 12 Jul 2026 15:22:52 -0600 Subject: [PATCH] fix(files): encode download filenames --- internal/files/web.go | 5 ++++- internal/files/web_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/internal/files/web.go b/internal/files/web.go index 0c57ed7..01f1609 100644 --- a/internal/files/web.go +++ b/internal/files/web.go @@ -7,6 +7,7 @@ import ( "fmt" "html/template" "io" + "mime" "net/http" "os" "path" @@ -224,7 +225,9 @@ func (h *webSrv) handleDownload(w http.ResponseWriter, r *http.Request) { return } defer f.Close() - w.Header().Set("Content-Disposition", "attachment; filename=\""+path.Base(vpath)+"\"") + w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{ + "filename": path.Base(vpath), + })) w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Length", strconv.FormatInt(fi.Size(), 10)) _, _ = io.Copy(w, f) diff --git a/internal/files/web_test.go b/internal/files/web_test.go index 342168d..3dd6677 100644 --- a/internal/files/web_test.go +++ b/internal/files/web_test.go @@ -3,10 +3,13 @@ package files import ( "bytes" "io" + "mime" "mime/multipart" "net/http" "net/http/httptest" "net/url" + "os" + "path/filepath" "strings" "testing" @@ -124,6 +127,41 @@ func TestWebRoundTrip(t *testing.T) { } } +func TestWebDownloadEncodesUnicodeFilename(t *testing.T) { + svc, _, u := newTestService(t) + h := svc.WebHandler(WebConfig{ + Title: "files.test", + Authenticate: func(user, pass string) (store.User, bool, error) { + return u, user == "alice" && pass == "secret", nil + }, + }) + cookie := loginCookie(t, h) + name := "résumé.txt" + if _, _, err := svc.OpenFor(u.Name); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(svc.privRoot(u.Name), name), []byte("complete"), 0o644); err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/download?path="+url.QueryEscape("/me/"+name), nil) + req.AddCookie(cookie) + h.ServeHTTP(rr, req) + + disposition := rr.Header().Get("Content-Disposition") + if !strings.Contains(disposition, "filename*=") { + t.Fatalf("Content-Disposition should encode a Unicode filename: %q", disposition) + } + mediaType, params, err := mime.ParseMediaType(disposition) + if err != nil { + t.Fatalf("invalid Content-Disposition: %v", err) + } + if mediaType != "attachment" || params["filename"] != name { + t.Fatalf("Content-Disposition filename: got %q, want %q", params["filename"], name) + } +} + func TestWebMutationsRejectGet(t *testing.T) { h, _ := webTestHandler(t) cookie := loginCookie(t, h)