From 60e5d57cda4f7849f8b753d1a1088119d3684142 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 19 Jan 2020 11:05:56 +0100 Subject: [PATCH] carddav: implement REPORT addressbook-multiget --- carddav/elements.go | 24 +++++++++++++++ carddav/server.go | 72 ++++++++++++++++++++++++++++++++++++++++++--- internal/server.go | 13 ++++++-- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/carddav/elements.go b/carddav/elements.go index 47ff275..5a0ca71 100644 --- a/carddav/elements.go +++ b/carddav/elements.go @@ -2,6 +2,7 @@ package carddav import ( "encoding/xml" + "fmt" "github.com/emersion/go-webdav/internal" ) @@ -12,6 +13,8 @@ var ( addressBookName = xml.Name{namespace, "addressbook"} addressBookHomeSetName = xml.Name{namespace, "addressbook-home-set"} addressBookDescriptionName = xml.Name{namespace, "addressbook-description"} + addressBookQueryName = xml.Name{namespace, "addressbook-query"} + addressBookMultigetName = xml.Name{namespace, "addressbook-multiget"} ) type addressbookHomeSet struct { @@ -69,3 +72,24 @@ type addressDataResp struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav address-data"` Data []byte `xml:",chardata"` } + +type reportReq struct { + Query *addressbookQuery + Multiget *addressbookMultiget +} + +func (r *reportReq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var v interface{} + switch start.Name { + case addressBookQueryName: + r.Query = &addressbookQuery{} + v = r.Query + case addressBookMultigetName: + r.Multiget = &addressbookMultiget{} + v = r.Multiget + default: + return fmt.Errorf("carddav: unsupported REPORT root %q %q", start.Name.Space, start.Name.Local) + } + + return d.DecodeElement(v, &start) +} diff --git a/carddav/server.go b/carddav/server.go index 2c2d9a8..5bb49e9 100644 --- a/carddav/server.go +++ b/carddav/server.go @@ -2,6 +2,7 @@ package carddav import ( "encoding/xml" + "mime" "net/http" "github.com/emersion/go-vcard" @@ -25,9 +26,71 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - b := backend{h.Backend} - hh := internal.Handler{&b} - hh.ServeHTTP(w, r) + var err error + switch r.Method { + case "REPORT": + err = h.handleReport(w, r) + default: + b := backend{h.Backend} + hh := internal.Handler{&b} + hh.ServeHTTP(w, r) + } + + if err != nil { + internal.ServeError(w, err) + } +} + +func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) error { + t, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) + if t != "application/xml" && t != "text/xml" { + return internal.HTTPErrorf(http.StatusBadRequest, "webdav: expected application/xml REPORT request") + } + + var report reportReq + if err := xml.NewDecoder(r.Body).Decode(&report); err != nil { + return &internal.HTTPError{http.StatusBadRequest, err} + } + + if report.Query != nil { + return h.handleQuery(w, report.Query) + } else if report.Multiget != nil { + return h.handleMultiget(w, report.Multiget) + } + return internal.HTTPErrorf(http.StatusBadRequest, "webdav: expected addressbook-query or addressbook-multiget element in REPORT request") +} + +func (h *Handler) handleQuery(w http.ResponseWriter, query *addressbookQuery) error { + return nil // TODO +} + +func (h *Handler) handleMultiget(w http.ResponseWriter, multiget *addressbookMultiget) error { + var resps []internal.Response + for _, href := range multiget.Hrefs { + ao, err := h.Backend.GetAddressObject(href) + if err != nil { + return err // TODO: create internal.Response with error + } + + b := backend{h.Backend} + propfind := internal.Propfind{ + Prop: multiget.Prop, + // TODO: Allprop, Propnames + } + resp, err := b.propfindAddressObject(&propfind, ao) + if err != nil { + return err + } + + resps = append(resps, *resp) + } + + ms := internal.NewMultistatus(resps...) + + w.Header().Add("Content-Type", "text/xml; charset=\"utf-8\"") + w.WriteHeader(http.StatusMultiStatus) + w.Write([]byte(xml.Header)) + return xml.NewEncoder(w).Encode(&ms) } type backend struct { @@ -124,7 +187,8 @@ func (b *backend) propfindAddressObject(propfind *internal.Propfind, ao *Address internal.GetContentTypeName: func(*internal.RawXMLValue) (interface{}, error) { return &internal.GetContentType{Type: vcard.MIMEType}, nil }, - // TODO getlastmodified, getetag + // TODO: getlastmodified, getetag + // TODO: address-data } return internal.NewPropfindResponse(ao.Href, propfind, props) diff --git a/internal/server.go b/internal/server.go index 7c19c16..55e4f18 100644 --- a/internal/server.go +++ b/internal/server.go @@ -37,6 +37,14 @@ func (err *HTTPError) Error() string { } } +func ServeError(w http.ResponseWriter, err error) { + code := http.StatusInternalServerError + if httpErr, ok := err.(*HTTPError); ok { + code = httpErr.Code + } + http.Error(w, err.Error(), code) +} + type Backend interface { Options(r *http.Request) ([]string, error) HeadGet(w http.ResponseWriter, r *http.Request) error @@ -86,7 +94,7 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) error { } func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) error { - t, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + t, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) if t != "application/xml" && t != "text/xml" { return HTTPErrorf(http.StatusBadRequest, "webdav: expected application/xml PROPFIND request") } @@ -98,6 +106,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) error { depth := DepthInfinity if s := r.Header.Get("Depth"); s != "" { + var err error depth, err = ParseDepth(s) if err != nil { return &HTTPError{http.StatusBadRequest, err} @@ -180,7 +189,7 @@ func NewPropfindResponse(href string, propfind *Propfind, props map[xml.Name]Pro } } } else { - return nil, HTTPErrorf(http.StatusBadRequest, "webdav: propfind request missing propname, allprop or prop element") + return nil, HTTPErrorf(http.StatusBadRequest, "webdav: request missing propname, allprop or prop element") } return resp, nil