Fix WebDAV/CalDAV/CardDAV bugs, drop username from URLs, harden concurrency
- Root handler now only serves the welcome page for GET/HEAD; all other methods (e.g. OPTIONS, PROPFIND) return 405 with an Allow header instead of always returning 200, fixing client capability probes and PROPFIND misbehavior. - Mount /files/ properly and cache one xwebdav.Handler per authenticated user so its LockSystem persists across requests instead of being recreated per-request (which broke LOCK/UNLOCK). - Remove the username segment from all DAV URLs (/cal/, /card/, /files/ are now identical for every account; the acting user is always resolved via Basic Auth, never the path). - Reintroduce a fixed literal "home" path segment (/cal/home/, /card/home/) to preserve the URL segment depth that go-webdav's caldav/carddav server relies on to classify resources (principal vs. home-set vs. collection vs. object). Removing the username had collapsed this depth, silently misclassifying requests and returning empty <multistatus> responses (DAVx5 "no resources found"). - Replace the store's single global mutex with per-user sharded locks so different users' requests no longer serialize against each other. - Add auth.NewContext test helper, WebDAV handler tests (per-user isolation, lock persistence across requests), and a concurrent multi-user store test. - Update README and copilot-instructions to document the new URL scheme and the go-webdav path-depth classification quirk. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+18
-9
@@ -139,16 +139,25 @@ func buildMux(
|
||||
// CalDAV, CardDAV, and file WebDAV — all behind Basic Auth
|
||||
mux.Handle("/cal/", authMw.Wrap(calHandler))
|
||||
mux.Handle("/card/", authMw.Wrap(cardHandler))
|
||||
// mux.Handle("/files/", authMw.Wrap(fileHandler))
|
||||
mux.Handle("/files/", authMw.Wrap(fileHandler))
|
||||
|
||||
// Root — simple HTML welcome page
|
||||
// Root — simple HTML welcome page (unauthenticated)
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" && r.Method == "GET" {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
fmt.Fprintf(w, welcomePage, cfg.Server.BaseURL, cfg.Server.BaseURL, cfg.Server.BaseURL, cfg.Server.BaseURL, cfg.Server.BaseURL)
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
authMw.Wrap(fileHandler).ServeHTTP(w, r)
|
||||
// Only plain GET/HEAD get the welcome page. Any other method
|
||||
// (PROPFIND, LOCK, PUT, ...) hitting "/" means a client is pointed
|
||||
// at the wrong URL — reject it explicitly instead of returning a
|
||||
// misleading 200 OK, which breaks WebDAV clients expecting 207.
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
w.Header().Set("Allow", "GET, HEAD")
|
||||
http.Error(w, "not a WebDAV collection; use /cal/, /card/ or /files/", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
fmt.Fprintf(w, welcomePage, cfg.Server.BaseURL, cfg.Server.BaseURL, cfg.Server.BaseURL, cfg.Server.BaseURL, cfg.Server.BaseURL)
|
||||
})
|
||||
|
||||
return mux
|
||||
@@ -218,9 +227,9 @@ const welcomePage = `<!DOCTYPE html>
|
||||
<p>This server provides CalDAV, CardDAV, and WebDAV access.</p>
|
||||
<h2>Endpoints</h2>
|
||||
<ul>
|
||||
<li><strong>CalDAV</strong> — <code>%s/cal/<username>/</code></li>
|
||||
<li><strong>CardDAV</strong> — <code>%s/card/<username>/</code></li>
|
||||
<li><strong>WebDAV files</strong> — <code>%s/files/<username>/</code></li>
|
||||
<li><strong>CalDAV</strong> — <code>%s/cal/</code></li>
|
||||
<li><strong>CardDAV</strong> — <code>%s/card/</code></li>
|
||||
<li><strong>WebDAV files</strong> — <code>%s/files/</code></li>
|
||||
</ul>
|
||||
<h2>Auto-discovery</h2>
|
||||
<ul>
|
||||
|
||||
Reference in New Issue
Block a user