package main import ( "encoding/xml" "fmt" "net/http" "strings" ) // findPrincipal asks the endpoint who we are. // // go-webdav does this too, but it resolves the request path with // path.Join, which drops a trailing slash: a client pointed at /dav/ // ends up asking about /dav, and Cyrus answers 405 for that exact // spelling while answering 207 for /dav/. Deeper paths tolerate either, // so only this first request needs doing by hand. func findPrincipal(endpoint, user, pass string) (string, error) { body := `` + `` req, err := http.NewRequest("PROPFIND", endpoint, strings.NewReader(body)) if err != nil { return "", err } req.Header.Set("Depth", "0") req.Header.Set("Content-Type", "application/xml; charset=utf-8") req.SetBasicAuth(user, pass) resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusMultiStatus { return "", fmt.Errorf("%s: %s", endpoint, resp.Status) } // Spelled out rather than using encoding/xml's "a>b" path // shorthand, which does not carry namespaces through. type href struct { Href string `xml:"DAV: href"` } type prop struct { Principal href `xml:"DAV: current-user-principal"` } type propstat struct { Prop prop `xml:"DAV: prop"` } type response struct { Propstats []propstat `xml:"DAV: propstat"` } var ms struct { Responses []response `xml:"DAV: response"` } if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil { return "", err } for _, r := range ms.Responses { for _, ps := range r.Propstats { if h := strings.TrimSpace(ps.Prop.Principal.Href); h != "" { return h, nil } } } return "", fmt.Errorf("%s: no current-user-principal", endpoint) }