{"record":{"id":"9a8e78513a55a21f","repo":"juicedata/juicefs","slug":"forbidden","errorCode":null,"errorMessage":"Forbidden","messagePattern":"Forbidden","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"pkg/fs/http.go","lineNumber":293,"sourceCode":"\t\t}\n\t\tif userName != h.Username || pwd != h.Password {\n\t\t\thttp.Error(w, \"WebDAV: need authorized!\", http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Excerpt from RFC4918, section 9.4:\n\t//\n\t// \t\tGET, when applied to a collection, may return the contents of an\n\t//\t\t\"index.html\" resource, a human-readable view of the contents of\n\t//\t\tthe collection, or something else altogether.\n\t//\n\t// Get, when applied to collection, will return the same as PROPFIND method.\n\tif r.Method == \"GET\" && strings.HasPrefix(r.URL.Path, h.Handler.Prefix) {\n\t\tinfo, err := h.Handler.FileSystem.Stat(context.TODO(), strings.TrimPrefix(r.URL.Path, h.Handler.Prefix))\n\t\tif err == nil && info.IsDir() {\n\t\t\tif h.DisallowList {\n\t\t\t\thttp.Error(w, \"Forbidden\", http.StatusForbidden)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tr.Method = \"PROPFIND\"\n\t\t\tif r.Header.Get(\"Depth\") == \"\" {\n\t\t\t\tr.Header.Add(\"Depth\", \"1\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// The next line would normally be:\n\t//\thttp.Handle(\"/\", h)\n\t// but we wrap that HTTP handler h to cater for a special case.\n\t//\n\t// The propfind_invalid2 litmus test case expects an empty namespace prefix\n\t// declaration to be an error. The FAQ in the webdav litmus test says:\n\t//\n\t// \"What does the \"propfind_invalid2\" test check for?...\n\t//","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/fs/http.go#L275-L311","documentation":"The JuiceFS WebDAV handler returns HTTP 403 'Forbidden' when a GET request targets a directory while DisallowList is enabled. This prevents clients from browsing directory contents through WebDAV GET requests (which the handler would otherwise translate to PROPFIND). The check happens in ServeHTTP in pkg/fs/http.go before delegating to the underlying WebDAV handler.","triggerScenarios":"Sending an HTTP GET to a path that resolves to a directory on the WebDAV server while the server was started with the --disallow-list option; the handler stats the path, sees info.IsDir()==true and h.DisallowList==true, and writes 'Forbidden' with status 403.","commonSituations":"Deployments that expose WebDAV only for direct file access (sharing individual file URLs) but disable directory listing for privacy or performance; users pasting a directory URL into a browser or running a recursive downloader against a disallow-list server.","solutions":["If directory browsing is intended, restart the WebDAV server without the --disallow-list flag","Access individual files directly instead of directory URLs (GET on a file is still allowed)","Guard client code: issue PROPFIND/GET only on known file paths, or handle 403 as 'listing disabled' and skip directory traversal"],"exampleFix":"// before (client recursing directories)\nfor _, name := range listDir(url) { download(url + \"/\" + name) }\n// after (handle 403 as listing disabled)\nresp, _ := http.Get(dirURL)\nif resp.StatusCode == http.StatusForbidden { log skip; return }","handlingStrategy":"fallback","validationCode":"// WebDAV client: skip directories when listing is disallowed\nfunc canGet(path string, isDir bool, disallowList bool) bool {\n\treturn !isDir || !disallowList\n}","typeGuard":"func isForbidden(resp *http.Response) bool { return resp.StatusCode == http.StatusForbidden }","tryCatchPattern":"resp, err := http.Get(dirURL)\nif err != nil { return err }\nif resp.StatusCode == http.StatusForbidden { /* listing disabled: skip */ return nil }","preventionTips":["Know whether the WebDAV server was started with --disallow-list","Only generate file URLs (not directory URLs) for sharing","Treat 403 on directories as expected behavior in recursive downloaders"],"tags":["http","webdav","permission-denied"],"backgroundTag":"permission-denied","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}