go-webdav
go-webdav copied to clipboard
[Solution included] This client isn't compatible with the Hetzner storage box WebDAV server
Hello,
I tried using your WebDAV client to access my Hetzner storage box through their WebDAV server (pretty sure they use Apache as their WebDAV server)
First off, I noticed how their WebDAV differs than most of the WebDAV servers I have used before:
- Most of the WebDAV servers always respond in XML no matter what's the request method used
- Hetzner's WebDAV server (Apache I think?) responds in HTML unless the request method is a proper WebDAV verb (PROPFIND, etc).
So what's the issue?
If we perform a Readdir (for example) query on the Hetzner WebDAV server like that:
data, err := webdav.Readdir("/asset1", false)
if err != nil {
fmt.Println(err)
}
the internal HTTP client of go-webdav will send the request with the URL https://uXXXX.your-storagebox.de/asset1 and the method PROPFIND, but then the server will respond with HTTP status code 302 and a Location: https://uXXXX.your-storagebox.de/asset1/ header. This really shouldn't be an issue since the Golang HTTP client follows any redirects by default (I actually couldn't debug what I have just stated without disabling the automatic redirections following first). However the request made after the redirect loses its request method (PROPFIND) and always gets changed to a GET request, and here all the issues start (since as I said in the start, the Hetzner WebDAV server will respond with HTML if the request method isn't one of the WebDAV request verbs)
I don't really know why the HTTP client doesn't follow the redirections with the same request method of the original request, but I managed to solve it by creating my own Transport like that:
type webdavTransport struct {
username string
password string
}
func (t *webdavTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(t.username, t.password)
var resp *http.Response
var err error
resp, err = http.DefaultTransport.RoundTrip(req)
if err == nil {
if resp.StatusCode == 301 || resp.StatusCode == 302 {
newURL, _ := resp.Location()
reqNew, _ := http.NewRequest(req.Method, newURL.String(), req.Body)
reqNew.Header = req.Header
respNew, errNew := http.DefaultTransport.RoundTrip(reqNew)
resp = respNew
err = errNew
}
}
return resp, err
}
func FixedWebdavClientWithAuth(url, username, password string) (*webdav.Client, error) {
client := &http.Client{
Transport: &webdavTransport{
username: username,
password: password,
},
// Disable any redirections following since we'll manually handle this in the transport's RoundTripper
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
return webdav.NewClient(client, url)
}
now using
webdav, err := FixedWebdavClientWithAuth("https://uXXXX.your-storagebox.de", "[redacted]", "[redacted]")
if err != nil {
fmt.Println("unable to access")
}
data, err := webdav.Readdir("/asset1", false)
if err != nil {
fmt.Println(err)
fmt.Println("unable to access")
}
fmt.Println(data)
solves the issue and data gets printed successfully with no issues
See https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/net/http/client.go;l=518;drc=97df9c0b051b6c06bff7ec7e61522e7bbef40c91
In other words, the server should be replying with 307 instead of 302.