webdav: permit redirects on PROPFIND for metadata

The WebDAV implementation already permits redirects on PROPFIND for
listing paths in the `listAll` method but does not permit this for
metadata in `readMetaDataForPath`.  This results in a strange experience
for endpoints that heavily use redirects -

```
rclone lsl endpoint:
```

functions and lists `hello_world.txt` in its output but

```
rclone lsl endpoint:hello_world.txt
```

Fails with a HTTP 307.

The git history for this setting indicates this was done to avoid
an issue where redirects cause a verb change to GET in the Go HTTP
client; it does not appear to be problematic with HTTP 307.

To fix, a new `CheckRedirect` function is added in the `rest` library
to force the client to use the same verb across redirects, forcing this
for the PROPFIND case.
This commit is contained in:
Brian Bockelman
2026-02-26 16:23:52 +00:00
committed by Nick Craig-Wood
parent 6159ea9cf5
commit cf240b6c0f
2 changed files with 17 additions and 1 deletions
+1 -1
View File
@@ -348,7 +348,7 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string, depth string)
ExtraHeaders: map[string]string{ ExtraHeaders: map[string]string{
"Depth": depth, "Depth": depth,
}, },
NoRedirect: true, CheckRedirect: rest.PreserveMethodRedirectFn,
} }
if f.hasOCMD5 || f.hasOCSHA1 { if f.hasOCMD5 || f.hasOCSHA1 {
opts.Body = bytes.NewBuffer(owncloudProps) opts.Body = bytes.NewBuffer(owncloudProps)
+16
View File
@@ -215,6 +215,22 @@ func ClientWithNoRedirects(c *http.Client) *http.Client {
return &clientCopy return &clientCopy
} }
// PreserveMethodRedirectFn is a CheckRedirect function that
// preserves the original HTTP method on redirects.
//
// By default Go's http.Client changes the method to GET on 301, 302,
// and 303 redirects. This function overrides that behaviour so the
// original method (e.g. PROPFIND being preserved across a 307) is kept.
func PreserveMethodRedirectFn(req *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
if len(via) > 0 {
req.Method = via[0].Method
}
return nil
}
// Do calls the internal http.Client.Do method // Do calls the internal http.Client.Do method
func (api *Client) Do(req *http.Request) (*http.Response, error) { func (api *Client) Do(req *http.Request) (*http.Response, error) {
return api.c.Do(req) return api.c.Do(req)