summaryrefslogtreecommitdiff
path: root/pim/cmd/caldavfs/principal.go
diff options
context:
space:
mode:
Diffstat (limited to 'pim/cmd/caldavfs/principal.go')
-rw-r--r--pim/cmd/caldavfs/principal.go66
1 files changed, 66 insertions, 0 deletions
diff --git a/pim/cmd/caldavfs/principal.go b/pim/cmd/caldavfs/principal.go
new file mode 100644
index 0000000..b5d6558
--- /dev/null
+++ b/pim/cmd/caldavfs/principal.go
@@ -0,0 +1,66 @@
+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 := `<?xml version="1.0" encoding="utf-8"?>` +
+ `<d:propfind xmlns:d="DAV:"><d:prop><d:current-user-principal/></d:prop></d:propfind>`
+
+ 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)
+}