carddav: implement REPORT addressbook-multiget

This commit is contained in:
Simon Ser 2020-01-19 11:05:56 +01:00
parent 402593c5c6
commit 60e5d57cda
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
3 changed files with 103 additions and 6 deletions

View File

@ -2,6 +2,7 @@ package carddav
import ( import (
"encoding/xml" "encoding/xml"
"fmt"
"github.com/emersion/go-webdav/internal" "github.com/emersion/go-webdav/internal"
) )
@ -12,6 +13,8 @@ var (
addressBookName = xml.Name{namespace, "addressbook"} addressBookName = xml.Name{namespace, "addressbook"}
addressBookHomeSetName = xml.Name{namespace, "addressbook-home-set"} addressBookHomeSetName = xml.Name{namespace, "addressbook-home-set"}
addressBookDescriptionName = xml.Name{namespace, "addressbook-description"} addressBookDescriptionName = xml.Name{namespace, "addressbook-description"}
addressBookQueryName = xml.Name{namespace, "addressbook-query"}
addressBookMultigetName = xml.Name{namespace, "addressbook-multiget"}
) )
type addressbookHomeSet struct { type addressbookHomeSet struct {
@ -69,3 +72,24 @@ type addressDataResp struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav address-data"` XMLName xml.Name `xml:"urn:ietf:params:xml:ns:carddav address-data"`
Data []byte `xml:",chardata"` 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)
}

View File

@ -2,6 +2,7 @@ package carddav
import ( import (
"encoding/xml" "encoding/xml"
"mime"
"net/http" "net/http"
"github.com/emersion/go-vcard" "github.com/emersion/go-vcard"
@ -25,11 +26,73 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
var err error
switch r.Method {
case "REPORT":
err = h.handleReport(w, r)
default:
b := backend{h.Backend} b := backend{h.Backend}
hh := internal.Handler{&b} hh := internal.Handler{&b}
hh.ServeHTTP(w, r) 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 { type backend struct {
Backend Backend Backend Backend
} }
@ -124,7 +187,8 @@ func (b *backend) propfindAddressObject(propfind *internal.Propfind, ao *Address
internal.GetContentTypeName: func(*internal.RawXMLValue) (interface{}, error) { internal.GetContentTypeName: func(*internal.RawXMLValue) (interface{}, error) {
return &internal.GetContentType{Type: vcard.MIMEType}, nil return &internal.GetContentType{Type: vcard.MIMEType}, nil
}, },
// TODO getlastmodified, getetag // TODO: getlastmodified, getetag
// TODO: address-data
} }
return internal.NewPropfindResponse(ao.Href, propfind, props) return internal.NewPropfindResponse(ao.Href, propfind, props)

View File

@ -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 { type Backend interface {
Options(r *http.Request) ([]string, error) Options(r *http.Request) ([]string, error)
HeadGet(w http.ResponseWriter, r *http.Request) 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 { 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" { if t != "application/xml" && t != "text/xml" {
return HTTPErrorf(http.StatusBadRequest, "webdav: expected application/xml PROPFIND request") 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 depth := DepthInfinity
if s := r.Header.Get("Depth"); s != "" { if s := r.Header.Get("Depth"); s != "" {
var err error
depth, err = ParseDepth(s) depth, err = ParseDepth(s)
if err != nil { if err != nil {
return &HTTPError{http.StatusBadRequest, err} return &HTTPError{http.StatusBadRequest, err}
@ -180,7 +189,7 @@ func NewPropfindResponse(href string, propfind *Propfind, props map[xml.Name]Pro
} }
} }
} else { } 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 return resp, nil