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.
This commit is contained in:
Conrad Hoffmann 2024-02-07 11:47:56 +01:00 committed by Simon Ser
parent 80d77a977a
commit 71bd967b43
3 changed files with 54 additions and 2 deletions

View File

@ -68,6 +68,14 @@ func (b *testBackend) GetAddressBook(ctx context.Context, path string) (*Address
return nil, webdav.NewHTTPError(404, fmt.Errorf("Not found")) 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) { func (*testBackend) GetAddressObject(ctx context.Context, path string, req *AddressDataRequest) (*AddressObject, error) {
if path == alicePath { if path == alicePath {
card, err := vcard.NewDecoder(strings.NewReader(aliceData)).Decode() card, err := vcard.NewDecoder(strings.NewReader(aliceData)).Decode()

View File

@ -211,3 +211,11 @@ func (r *reportReq) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
return d.DecodeElement(v, &start) 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?
}

View File

@ -5,6 +5,7 @@ import (
"context" "context"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io"
"mime" "mime"
"net/http" "net/http"
"path" "path"
@ -30,6 +31,8 @@ type Backend interface {
AddressBookHomeSetPath(ctx context.Context) (string, error) AddressBookHomeSetPath(ctx context.Context) (string, error)
ListAddressBooks(ctx context.Context) ([]AddressBook, error) ListAddressBooks(ctx context.Context) ([]AddressBook, error)
GetAddressBook(ctx context.Context, path string) (*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) GetAddressObject(ctx context.Context, path string, req *AddressDataRequest) (*AddressObject, error)
ListAddressObjects(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) 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 { func (b *backend) Delete(r *http.Request) error {
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 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 { 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) { func (b *backend) Copy(r *http.Request, dest *internal.Href, recursive, overwrite bool) (created bool, err error) {