From 7ff69cfbde04f7c3d8276f4b60152c8d5ecf1b74 Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 10 Nov 2026 05:52:05 +0000 Subject: [PATCH] rproxy: pass WebDAV methods on (closes #278) rproxy now passes PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, REPORT, SEARCH, LOCK and UNLOCK to discriminator instead of answering 405, so Nextcloud's Files app and sync client work. The Core Rule Set's 911100 still refuses them wherever no app's exclusions allow them. Model: opus-5-5 --- SPEC.md | 13 +++++++++++++ internal/rproxy/question_test.go | 7 +++++-- internal/rproxy/rproxy.go | 4 +++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/SPEC.md b/SPEC.md index 6592a45..84f5044 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1881,6 +1881,19 @@ image runs it by default (see "Deployment"). - `DELETE` - `PATCH` - `QUERY` +- `PROPFIND` +- `PROPPATCH` +- `MKCOL` +- `COPY` +- `MOVE` +- `REPORT` +- `SEARCH` +- `LOCK` +- `UNLOCK` + +Nextcloud's Files app and sync client use the last nine, which are WebDAV +methods. The Core Rule Set refuses them except where an app's exclusions allow +them. It answers any other method `405`, with no question and no report. The answer's `Allow` header names these methods. diff --git a/internal/rproxy/question_test.go b/internal/rproxy/question_test.go index 5940ab9..02c0cac 100644 --- a/internal/rproxy/question_test.go +++ b/internal/rproxy/question_test.go @@ -331,7 +331,7 @@ func TestAQuestionCarriesItsBodyWhateverTheMethod(t *testing.T) { func TestAMethodNotPassedOnIsAnswered405(t *testing.T) { t.Parallel() - for _, method := range []string{http.MethodTrace, "PROPFIND", "LOCK", "BREW"} { + for _, method := range []string{http.MethodTrace, "MKCALENDAR", "BREW"} { t.Run(method, func(t *testing.T) { t.Parallel() @@ -343,7 +343,8 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) { got := visit(t, newRequest(t, method, addr, "/", http.NoBody)) if got.status != http.StatusMethodNotAllowed || - got.header.Get("Allow") != "GET, HEAD, POST, OPTIONS, PUT, DELETE, PATCH, QUERY" { + got.header.Get("Allow") != "GET, HEAD, POST, OPTIONS, PUT, DELETE, PATCH, QUERY, "+ + "PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, REPORT, SEARCH, LOCK, UNLOCK" { t.Errorf("answered %+v, want 405 naming the methods passed on", got) } @@ -368,6 +369,8 @@ func TestEachMethodPassedOnReachesDiscriminatorAndTheApp(t *testing.T) { for _, method := range []string{ http.MethodGet, http.MethodHead, http.MethodPost, http.MethodOptions, http.MethodPut, http.MethodDelete, http.MethodPatch, "QUERY", + "PROPFIND", "PROPPATCH", "MKCOL", "COPY", "MOVE", "REPORT", "SEARCH", + "LOCK", "UNLOCK", } { t.Run(method, func(t *testing.T) { t.Parallel() diff --git a/internal/rproxy/rproxy.go b/internal/rproxy/rproxy.go index 0a4c9f0..94bf459 100644 --- a/internal/rproxy/rproxy.go +++ b/internal/rproxy/rproxy.go @@ -51,7 +51,9 @@ const ownEndpointPrefix = "/_sww/" // allowedMethods lists the methods rproxy passes on, as an Allow header gives // them. It answers any other method 405 itself. -const allowedMethods = "GET, HEAD, POST, OPTIONS, PUT, DELETE, PATCH, QUERY" +// The WebDAV ones are for Nextcloud. +const allowedMethods = "GET, HEAD, POST, OPTIONS, PUT, DELETE, PATCH, QUERY, " + + "PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, REPORT, SEARCH, LOCK, UNLOCK" // Params are what New needs. type Params struct {