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) {