carddav: support multiple address books

This is the equivalent of #127 (and #140) for CardDAV and finally allows
backends to serve different address books to different users.

While I'm breaking the interface, correct one last instance of
"Addressbook" to "AddressBook" (in `AddressBookHomeSetPath`).
This commit is contained in:
Conrad Hoffmann 2024-02-02 16:09:49 +01:00 committed by Simon Ser
parent e3ba95cd77
commit eaac65215b
3 changed files with 56 additions and 43 deletions

View File

@ -37,22 +37,37 @@ func (*testBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
return r, nil return r, nil
} }
func (*testBackend) AddressbookHomeSetPath(ctx context.Context) (string, error) { func (*testBackend) AddressBookHomeSetPath(ctx context.Context) (string, error) {
r := ctx.Value(homeSetPathKey).(string) r := ctx.Value(homeSetPathKey).(string)
return r, nil return r, nil
} }
func (*testBackend) AddressBook(ctx context.Context) (*AddressBook, error) { func (*testBackend) ListAddressBooks(ctx context.Context) ([]AddressBook, error) {
p := ctx.Value(addressBookPathKey).(string) p := ctx.Value(addressBookPathKey).(string)
return &AddressBook{ return []AddressBook{
Path: p, AddressBook{
Name: "My contacts", Path: p,
Description: "Default address book", Name: "My contacts",
MaxResourceSize: 1024, Description: "Default address book",
SupportedAddressData: nil, MaxResourceSize: 1024,
SupportedAddressData: nil,
},
}, nil }, nil
} }
func (b *testBackend) GetAddressBook(ctx context.Context, path string) (*AddressBook, error) {
abs, err := b.ListAddressBooks(ctx)
if err != nil {
panic(err)
}
for _, ab := range abs {
if ab.Path == path {
return &ab, nil
}
}
return nil, webdav.NewHTTPError(404, fmt.Errorf("Not found"))
}
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()
@ -68,7 +83,11 @@ func (*testBackend) GetAddressObject(ctx context.Context, path string, req *Addr
} }
} }
func (b *testBackend) ListAddressObjects(ctx context.Context, req *AddressDataRequest) ([]AddressObject, error) { func (b *testBackend) ListAddressObjects(ctx context.Context, path string, req *AddressDataRequest) ([]AddressObject, error) {
p := ctx.Value(addressBookPathKey).(string)
if !strings.HasPrefix(path, p) {
return nil, webdav.NewHTTPError(404, fmt.Errorf("Not found"))
}
alice, err := b.GetAddressObject(ctx, alicePath, req) alice, err := b.GetAddressObject(ctx, alicePath, req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -77,7 +96,7 @@ func (b *testBackend) ListAddressObjects(ctx context.Context, req *AddressDataRe
return []AddressObject{*alice}, nil return []AddressObject{*alice}, nil
} }
func (*testBackend) QueryAddressObjects(ctx context.Context, query *AddressBookQuery) ([]AddressObject, error) { func (*testBackend) QueryAddressObjects(ctx context.Context, path string, query *AddressBookQuery) ([]AddressObject, error) {
panic("TODO: implement") panic("TODO: implement")
} }

View File

@ -16,8 +16,6 @@ import (
"github.com/emersion/go-webdav/internal" "github.com/emersion/go-webdav/internal"
) )
// TODO: add support for multiple address books
type PutAddressObjectOptions struct { type PutAddressObjectOptions struct {
// IfNoneMatch indicates that the client does not want to overwrite // IfNoneMatch indicates that the client does not want to overwrite
// an existing resource. // an existing resource.
@ -29,11 +27,12 @@ type PutAddressObjectOptions struct {
// Backend is a CardDAV server backend. // Backend is a CardDAV server backend.
type Backend interface { type Backend interface {
AddressbookHomeSetPath(ctx context.Context) (string, error) AddressBookHomeSetPath(ctx context.Context) (string, error)
AddressBook(ctx context.Context) (*AddressBook, error) ListAddressBooks(ctx context.Context) ([]AddressBook, error)
GetAddressBook(ctx context.Context, path string) (*AddressBook, 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, req *AddressDataRequest) ([]AddressObject, error) ListAddressObjects(ctx context.Context, path string, req *AddressDataRequest) ([]AddressObject, error)
QueryAddressObjects(ctx context.Context, query *AddressBookQuery) ([]AddressObject, error) QueryAddressObjects(ctx context.Context, path string, query *AddressBookQuery) ([]AddressObject, error)
PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *PutAddressObjectOptions) (loc string, err error) PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *PutAddressObjectOptions) (loc string, err error)
DeleteAddressObject(ctx context.Context, path string) error DeleteAddressObject(ctx context.Context, path string) error
@ -90,7 +89,7 @@ func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) error {
} }
if report.Query != nil { if report.Query != nil {
return h.handleQuery(r.Context(), w, report.Query) return h.handleQuery(r, w, report.Query)
} else if report.Multiget != nil { } else if report.Multiget != nil {
return h.handleMultiget(r.Context(), w, report.Multiget) return h.handleMultiget(r.Context(), w, report.Multiget)
} }
@ -152,7 +151,7 @@ func decodeAddressDataReq(addressData *addressDataReq) (*AddressDataRequest, err
return req, nil return req, nil
} }
func (h *Handler) handleQuery(ctx context.Context, w http.ResponseWriter, query *addressbookQuery) error { func (h *Handler) handleQuery(r *http.Request, w http.ResponseWriter, query *addressbookQuery) error {
var q AddressBookQuery var q AddressBookQuery
if query.Prop != nil { if query.Prop != nil {
var addressData addressDataReq var addressData addressDataReq
@ -180,7 +179,7 @@ func (h *Handler) handleQuery(ctx context.Context, w http.ResponseWriter, query
} }
} }
aos, err := h.Backend.QueryAddressObjects(ctx, &q) aos, err := h.Backend.QueryAddressObjects(r.Context(), r.URL.Path, &q)
if err != nil { if err != nil {
return err return err
} }
@ -196,7 +195,7 @@ func (h *Handler) handleQuery(ctx context.Context, w http.ResponseWriter, query
AllProp: query.AllProp, AllProp: query.AllProp,
PropName: query.PropName, PropName: query.PropName,
} }
resp, err := b.propFindAddressObject(ctx, &propfind, &ao) resp, err := b.propFindAddressObject(r.Context(), &propfind, &ao)
if err != nil { if err != nil {
return err return err
} }
@ -371,7 +370,7 @@ func (b *backend) PropFind(r *http.Request, propfind *internal.PropFind, depth i
} }
} }
case resourceTypeAddressBookHomeSet: case resourceTypeAddressBookHomeSet:
homeSetPath, err := b.Backend.AddressbookHomeSetPath(r.Context()) homeSetPath, err := b.Backend.AddressBookHomeSetPath(r.Context())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -391,24 +390,21 @@ func (b *backend) PropFind(r *http.Request, propfind *internal.PropFind, depth i
} }
} }
case resourceTypeAddressBook: case resourceTypeAddressBook:
// TODO for multiple address books, look through all of them ab, err := b.Backend.GetAddressBook(r.Context(), r.URL.Path)
ab, err := b.Backend.AddressBook(r.Context())
if err != nil { if err != nil {
return nil, err return nil, err
} }
if r.URL.Path == ab.Path { resp, err := b.propFindAddressBook(r.Context(), propfind, ab)
resp, err := b.propFindAddressBook(r.Context(), propfind, ab) if err != nil {
return nil, err
}
resps = append(resps, *resp)
if depth != internal.DepthZero {
resps_, err := b.propFindAllAddressObjects(r.Context(), propfind, ab)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resps = append(resps, *resp) resps = append(resps, resps_...)
if depth != internal.DepthZero {
resps_, err := b.propFindAllAddressObjects(r.Context(), propfind, ab)
if err != nil {
return nil, err
}
resps = append(resps, resps_...)
}
} }
case resourceTypeAddressObject: case resourceTypeAddressObject:
ao, err := b.Backend.GetAddressObject(r.Context(), r.URL.Path, &dataReq) ao, err := b.Backend.GetAddressObject(r.Context(), r.URL.Path, &dataReq)
@ -448,7 +444,7 @@ func (b *backend) propFindUserPrincipal(ctx context.Context, propfind *internal.
if err != nil { if err != nil {
return nil, err return nil, err
} }
homeSetPath, err := b.Backend.AddressbookHomeSetPath(ctx) homeSetPath, err := b.Backend.AddressBookHomeSetPath(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -472,7 +468,7 @@ func (b *backend) propFindHomeSet(ctx context.Context, propfind *internal.PropFi
if err != nil { if err != nil {
return nil, err return nil, err
} }
homeSetPath, err := b.Backend.AddressbookHomeSetPath(ctx) homeSetPath, err := b.Backend.AddressBookHomeSetPath(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -527,22 +523,20 @@ func (b *backend) propFindAddressBook(ctx context.Context, propfind *internal.Pr
} }
func (b *backend) propFindAllAddressBooks(ctx context.Context, propfind *internal.PropFind, recurse bool) ([]internal.Response, error) { func (b *backend) propFindAllAddressBooks(ctx context.Context, propfind *internal.PropFind, recurse bool) ([]internal.Response, error) {
// TODO iterate over all address books once having multiple is supported abs, err := b.Backend.ListAddressBooks(ctx)
ab, err := b.Backend.AddressBook(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
abs := []*AddressBook{ab}
var resps []internal.Response var resps []internal.Response
for _, ab := range abs { for _, ab := range abs {
resp, err := b.propFindAddressBook(ctx, propfind, ab) resp, err := b.propFindAddressBook(ctx, propfind, &ab)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resps = append(resps, *resp) resps = append(resps, *resp)
if recurse { if recurse {
resps_, err := b.propFindAllAddressObjects(ctx, propfind, ab) resps_, err := b.propFindAllAddressObjects(ctx, propfind, &ab)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -597,7 +591,7 @@ func (b *backend) propFindAddressObject(ctx context.Context, propfind *internal.
func (b *backend) propFindAllAddressObjects(ctx context.Context, propfind *internal.PropFind, ab *AddressBook) ([]internal.Response, error) { func (b *backend) propFindAllAddressObjects(ctx context.Context, propfind *internal.PropFind, ab *AddressBook) ([]internal.Response, error) {
var dataReq AddressDataRequest var dataReq AddressDataRequest
aos, err := b.Backend.ListAddressObjects(ctx, &dataReq) aos, err := b.Backend.ListAddressObjects(ctx, ab.Path, &dataReq)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -614,7 +608,7 @@ func (b *backend) propFindAllAddressObjects(ctx context.Context, propfind *inter
} }
func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (*internal.Response, error) { func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (*internal.Response, error) {
homeSetPath, err := b.Backend.AddressbookHomeSetPath(r.Context()) homeSetPath, err := b.Backend.AddressBookHomeSetPath(r.Context())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -255,7 +255,7 @@ func (b *backend) Move(r *http.Request, dest *internal.Href, overwrite bool) (cr
// BackendSuppliedHomeSet represents either a CalDAV calendar-home-set or a // BackendSuppliedHomeSet represents either a CalDAV calendar-home-set or a
// CardDAV addressbook-home-set. It should only be created via // CardDAV addressbook-home-set. It should only be created via
// caldav.NewCalendarHomeSet or carddav.NewAddressbookHomeSet. Only to // caldav.NewCalendarHomeSet or carddav.NewAddressBookHomeSet. Only to
// be used server-side, for listing a user's home sets as determined by the // be used server-side, for listing a user's home sets as determined by the
// (external) backend. // (external) backend.
type BackendSuppliedHomeSet interface { type BackendSuppliedHomeSet interface {