[Solution included] This client isn't compatible with the Hetzner storage box WebDAV server
- Dominant language
- Go
- Stars
- 497
- Forks
- 108
- PR merge metrics
- No merged PRs in 30d
Description
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:
```go
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:
```go
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
```go
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
Contributor guide
Research direction
Start at Readdir and the webdav.NewClient HTTP path, then reproduce the redirect against the Hetzner WebDAV server described in the issue. Verify that a redirected PROPFIND request remains a WebDAV request and that Readdir returns directory data rather than HTML; the issue's webdavTransport example shows the reported behavior and expected result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100