From 71bd967b43576dd5300d03bfd79dc72c573626c1 Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Wed, 7 Feb 2024 11:47:56 +0100 Subject: [PATCH] carddav: support address book creation/deletion Now that the handling for multiple address books is in place, this commit adds initial support for creation and deletion of address books. These operations obviously require support from the backend, so the interface gains two new methods. All properties of the address book passed to `CreateAddressBook()` may be unset (e.g. when a client sends a MKCOL request without a body), except for the path, which is always set. It is up to the backend to put any desired default values in place. --- carddav/carddav_test.go | 8 ++++++++ carddav/elements.go | 8 ++++++++ carddav/server.go | 40 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/carddav/carddav_test.go b/carddav/carddav_test.go index 5aed00b..b4914c3 100644 --- a/carddav/carddav_test.go +++ b/carddav/carddav_test.go @@ -68,6 +68,14 @@ func (b *testBackend) GetAddressBook(ctx context.Context, path string) (*Address return nil, webdav.NewHTTPError(404, fmt.Errorf("Not found")) } +func (*testBackend) CreateAddressBook(ctx context.Context, ab AddressBook) error { + panic("TODO: implement") +} + +func (*testBackend) DeleteAddressBook(ctx context.Context, path string) error { + panic("TODO: implement") +} + func (*testBackend) GetAddressObject(ctx context.Context, path string, req *AddressDataRequest) (*AddressObject, error) { if path == alicePath { card, err := vcard.NewDecoder(strings.NewReader(aliceData)).Decode() diff --git a/carddav/elements.go b/carddav/elements.go index 8b18714..6781d30 100644 --- a/carddav/elements.go +++ b/carddav/elements.go @@ -211,3 +211,11 @@ func (r *reportReq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return d.DecodeElement(v, &start) } + +type mkcolReq struct { + XMLName xml.Name `xml:"DAV: mkcol"` + ResourceType internal.ResourceType `xml:"set>prop>resourcetype"` + DisplayName string `xml:"set>prop>displayname"` + Description addressbookDescription `xml:"set>prop>addressbook-description"` + // TODO this could theoretically contain all addressbook properties? +} diff --git a/carddav/server.go b/carddav/server.go index 8dc0d30..52e3523 100644 --- a/carddav/server.go +++ b/carddav/server.go @@ -5,6 +5,7 @@ import ( "context" "encoding/xml" "fmt" + "io" "mime" "net/http" "path" @@ -30,6 +31,8 @@ type Backend interface { AddressBookHomeSetPath(ctx context.Context) (string, error) ListAddressBooks(ctx context.Context) ([]AddressBook, error) GetAddressBook(ctx context.Context, path string) (*AddressBook, error) + CreateAddressBook(ctx context.Context, addressBook AddressBook) error + DeleteAddressBook(ctx context.Context, path string) error GetAddressObject(ctx context.Context, path string, req *AddressDataRequest) (*AddressObject, error) ListAddressObjects(ctx context.Context, path string, req *AddressDataRequest) ([]AddressObject, error) QueryAddressObjects(ctx context.Context, path string, query *AddressBookQuery) ([]AddressObject, error) @@ -682,11 +685,44 @@ func (b *backend) Put(r *http.Request) (*internal.Href, error) { } func (b *backend) Delete(r *http.Request) error { - return b.Backend.DeleteAddressObject(r.Context(), r.URL.Path) + switch b.resourceTypeAtPath(r.URL.Path) { + case resourceTypeAddressBook: + return b.Backend.DeleteAddressBook(r.Context(), r.URL.Path) + case resourceTypeAddressObject: + return b.Backend.DeleteAddressObject(r.Context(), r.URL.Path) + } + return internal.HTTPErrorf(http.StatusForbidden, "carddav: cannot delete resource at given location") } func (b *backend) Mkcol(r *http.Request) error { - return internal.HTTPErrorf(http.StatusForbidden, "carddav: address book creation unsupported") + if b.resourceTypeAtPath(r.URL.Path) != resourceTypeAddressBook { + return internal.HTTPErrorf(http.StatusForbidden, "carddav: address book creation not allowed at given location") + } + + ab := AddressBook{ + Path: r.URL.Path, + } + + // Check if a request body was sent + _, err := r.Body.Read(nil) + if err != nil && err != io.EOF { + return err + } + if err == nil { + // Not EOF, body is present + var m mkcolReq + if err := internal.DecodeXMLRequest(r, &m); err != nil { + return internal.HTTPErrorf(http.StatusBadRequest, "carddav: error parsing mkcol request: %s", err.Error()) + } + + if !m.ResourceType.Is(internal.CollectionName) || !m.ResourceType.Is(addressBookName) { + return internal.HTTPErrorf(http.StatusBadRequest, "carddav: unexpected resource type") + } + ab.Name = m.DisplayName + ab.Description = m.Description.Description + // TODO ... + } + return b.Backend.CreateAddressBook(r.Context(), ab) } func (b *backend) Copy(r *http.Request, dest *internal.Href, recursive, overwrite bool) (created bool, err error) {