Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Solution included] This client isn't compatible with the Hetzner storage box WebDAV server #123

Open
oddmario opened this issue Jan 9, 2023 · 2 comments

Comments

@oddmario
Copy link

oddmario commented Jan 9, 2023

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

@jech
Copy link

jech commented Nov 25, 2023

In other words, the server should be replying with 307 instead of 302.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants