diff --git a/internal/files/web.go b/internal/files/web.go index 580fde9..0c57ed7 100644 --- a/internal/files/web.go +++ b/internal/files/web.go @@ -231,6 +231,9 @@ func (h *webSrv) handleDownload(w http.ResponseWriter, r *http.Request) { } func (h *webSrv) handleUpload(w http.ResponseWriter, r *http.Request) { + if !requirePost(w, r) { + return + } sess, ok := h.session(w, r) if !ok { return @@ -268,6 +271,9 @@ func (h *webSrv) handleUpload(w http.ResponseWriter, r *http.Request) { } func (h *webSrv) handleMkdir(w http.ResponseWriter, r *http.Request) { + if !requirePost(w, r) { + return + } sess, ok := h.session(w, r) if !ok { return @@ -286,6 +292,9 @@ func (h *webSrv) handleMkdir(w http.ResponseWriter, r *http.Request) { } func (h *webSrv) handleDelete(w http.ResponseWriter, r *http.Request) { + if !requirePost(w, r) { + return + } sess, ok := h.session(w, r) if !ok { return @@ -299,6 +308,15 @@ func (h *webSrv) handleDelete(w http.ResponseWriter, r *http.Request) { h.redirectMsg(w, r, parent, "deleted "+path.Base(target)) } +func requirePost(w http.ResponseWriter, r *http.Request) bool { + if r.Method == http.MethodPost { + return true + } + w.Header().Set("Allow", http.MethodPost) + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return false +} + // session resolves the logged-in member into a filesystem session, writing an // auth error to w when there is none. func (h *webSrv) session(w http.ResponseWriter, r *http.Request) (*session, bool) { diff --git a/internal/files/web_test.go b/internal/files/web_test.go index e4ec5c9..342168d 100644 --- a/internal/files/web_test.go +++ b/internal/files/web_test.go @@ -124,6 +124,38 @@ func TestWebRoundTrip(t *testing.T) { } } +func TestWebMutationsRejectGet(t *testing.T) { + h, _ := webTestHandler(t) + cookie := loginCookie(t, h) + uploadTo(t, h, cookie, "/me", "keep.txt", "keep me") + + for _, target := range []string{ + "/upload?dir=/me", + "/mkdir?dir=/me&name=from-get", + "/delete?path=/me/keep.txt", + } { + rr := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, target, nil) + req.AddCookie(cookie) + h.ServeHTTP(rr, req) + if rr.Code != http.StatusMethodNotAllowed { + t.Fatalf("GET %s: want 405, got %d", target, rr.Code) + } + } + + rr := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/?path=/me", nil) + req.AddCookie(cookie) + h.ServeHTTP(rr, req) + body := rr.Body.String() + if !strings.Contains(body, "keep.txt") { + t.Fatal("GET /delete removed the file") + } + if strings.Contains(body, "from-get") { + t.Fatal("GET /mkdir created a directory") + } +} + // loginCookie logs alice in and returns her session cookie. func loginCookie(t *testing.T, h http.Handler) *http.Cookie { t.Helper()