Require POST for file web mutations (#82)
Some checks failed
CI / build (push) Has been cancelled
deploy / deploy (push) Has been cancelled
test / test (push) Has been cancelled

This commit is contained in:
Luna Ops 2026-07-07 20:41:16 +08:00 committed by GitHub
parent ee06082ce1
commit ff9eef907d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 0 deletions

View file

@ -231,6 +231,9 @@ func (h *webSrv) handleDownload(w http.ResponseWriter, r *http.Request) {
} }
func (h *webSrv) handleUpload(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) sess, ok := h.session(w, r)
if !ok { if !ok {
return 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) { func (h *webSrv) handleMkdir(w http.ResponseWriter, r *http.Request) {
if !requirePost(w, r) {
return
}
sess, ok := h.session(w, r) sess, ok := h.session(w, r)
if !ok { if !ok {
return 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) { func (h *webSrv) handleDelete(w http.ResponseWriter, r *http.Request) {
if !requirePost(w, r) {
return
}
sess, ok := h.session(w, r) sess, ok := h.session(w, r)
if !ok { if !ok {
return return
@ -299,6 +308,15 @@ func (h *webSrv) handleDelete(w http.ResponseWriter, r *http.Request) {
h.redirectMsg(w, r, parent, "deleted "+path.Base(target)) 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 // session resolves the logged-in member into a filesystem session, writing an
// auth error to w when there is none. // auth error to w when there is none.
func (h *webSrv) session(w http.ResponseWriter, r *http.Request) (*session, bool) { func (h *webSrv) session(w http.ResponseWriter, r *http.Request) (*session, bool) {

View file

@ -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. // loginCookie logs alice in and returns her session cookie.
func loginCookie(t *testing.T, h http.Handler) *http.Cookie { func loginCookie(t *testing.T, h http.Handler) *http.Cookie {
t.Helper() t.Helper()