From 4de64157f3450aa399e735e916a70e0c9cd2bf66 Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Thu, 8 Feb 2024 16:54:13 +0100 Subject: [PATCH] carddav: PROPPATCH support for address books The groundwork for address objects is also there, but it's not fully implemented. --- carddav/carddav_test.go | 4 + carddav/server.go | 164 ++++++++++++++++++++++++++++++++++------ 2 files changed, 145 insertions(+), 23 deletions(-) diff --git a/carddav/carddav_test.go b/carddav/carddav_test.go index 337ac83..9ba976e 100644 --- a/carddav/carddav_test.go +++ b/carddav/carddav_test.go @@ -72,6 +72,10 @@ func (*testBackend) CreateAddressBook(ctx context.Context, ab *AddressBook) erro panic("TODO: implement") } +func (*testBackend) UpdateAddressBook(ctx context.Context, ab *AddressBook) error { + panic("TODO: implement") +} + func (*testBackend) DeleteAddressBook(ctx context.Context, path string) error { panic("TODO: implement") } diff --git a/carddav/server.go b/carddav/server.go index 8ea7680..b36bb13 100644 --- a/carddav/server.go +++ b/carddav/server.go @@ -31,6 +31,7 @@ type Backend interface { ListAddressBooks(ctx context.Context) ([]AddressBook, error) GetAddressBook(ctx context.Context, path string) (*AddressBook, error) CreateAddressBook(ctx context.Context, addressBook *AddressBook) error + UpdateAddressBook(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) @@ -614,43 +615,160 @@ func (b *backend) propFindAllAddressObjects(ctx context.Context, propfind *inter } func (b *backend) PropPatch(r *http.Request, update *internal.PropertyUpdate) (*internal.Response, error) { - homeSetPath, err := b.Backend.AddressBookHomeSetPath(r.Context()) - if err != nil { - return nil, err - } - + resType := b.resourceTypeAtPath(r.URL.Path) resp := internal.NewOKResponse(r.URL.Path) - if r.URL.Path == homeSetPath { - // TODO: support PROPPATCH for address books + switch resType { + case resourceTypeAddressBook: + ab, err := b.Backend.GetAddressBook(r.Context(), r.URL.Path) + if err != nil { + return nil, err + } + err = b.propPatchAddressBook(r.Context(), update, ab, resp) + if err != nil { + return nil, err + } + err = b.Backend.UpdateAddressBook(r.Context(), ab) + if err != nil { + return nil, err + } + case resourceTypeAddressObject: + dataReq := AddressDataRequest{AllProp: true} + ao, err := b.Backend.GetAddressObject(r.Context(), r.URL.Path, &dataReq) + if err != nil { + return nil, err + } + // TODO: support PROPPATCH for address objects + err = b.propPatchAddressObject(r.Context(), update, ao, resp) + if err != nil { + return nil, err + } + // TODO: interface for updating contacts? + //err = b.Backend.UpdateAddressObject(r.Context(), ab) + //if err != nil { + // return nil, err + //} + default: for _, prop := range update.Remove { - emptyVal := internal.NewRawXMLElement(prop.Prop.XMLName, nil, nil) - if err := resp.EncodeProp(http.StatusNotImplemented, emptyVal); err != nil { - return nil, err + for _, raw := range prop.Prop.Raw { + rxn, ok := raw.XMLName() + if !ok { + return nil, fmt.Errorf("failed to parse properties") + } + emptyVal := internal.NewRawXMLElement(rxn, nil, nil) + if err := resp.EncodeProp(http.StatusMethodNotAllowed, emptyVal); err != nil { + return nil, err + } } } for _, prop := range update.Set { - emptyVal := internal.NewRawXMLElement(prop.Prop.XMLName, nil, nil) - if err := resp.EncodeProp(http.StatusNotImplemented, emptyVal); err != nil { - return nil, err + for _, raw := range prop.Prop.Raw { + rxn, ok := raw.XMLName() + if !ok { + return nil, fmt.Errorf("failed to parse properties") + } + emptyVal := internal.NewRawXMLElement(rxn, nil, nil) + if err := resp.EncodeProp(http.StatusMethodNotAllowed, emptyVal); err != nil { + return nil, err + } } } - } else { - for _, prop := range update.Remove { - emptyVal := internal.NewRawXMLElement(prop.Prop.XMLName, nil, nil) - if err := resp.EncodeProp(http.StatusMethodNotAllowed, emptyVal); err != nil { - return nil, err + } + return resp, nil +} + +func (b *backend) propPatchAddressBook(ctx context.Context, update *internal.PropertyUpdate, ab *AddressBook, resp *internal.Response) error { + // TODO handle all properties + var ( + name internal.DisplayName + desc addressbookDescription + ) + for _, prop := range update.Remove { + for _, raw := range prop.Prop.Raw { + rxn, ok := raw.XMLName() + if !ok { + return fmt.Errorf("failed to parse properties") + } + switch rxn { + case internal.DisplayNameName: + ab.Name = "" + if err := resp.EncodeProp(http.StatusOK, internal.DisplayName{}); err != nil { + return err + } + case addressBookDescriptionName: + ab.Description = "" + if err := resp.EncodeProp(http.StatusOK, desc); err != nil { + return err + } + default: + emptyVal := internal.NewRawXMLElement(rxn, nil, nil) + if err := resp.EncodeProp(http.StatusNotImplemented, emptyVal); err != nil { + return err + } } } - for _, prop := range update.Set { - emptyVal := internal.NewRawXMLElement(prop.Prop.XMLName, nil, nil) - if err := resp.EncodeProp(http.StatusMethodNotAllowed, emptyVal); err != nil { - return nil, err + } + for _, prop := range update.Set { + for _, raw := range prop.Prop.Raw { + rxn, ok := raw.XMLName() + if !ok { + return fmt.Errorf("failed to parse properties") + } + switch rxn { + case internal.DisplayNameName: + if err := raw.Decode(&name); err != nil { + return err + } + ab.Name = name.Name + if err := resp.EncodeProp(http.StatusOK, internal.DisplayName{}); err != nil { + return err + } + case addressBookDescriptionName: + if err := raw.Decode(&desc); err != nil { + return err + } + ab.Description = desc.Description + if err := resp.EncodeProp(http.StatusOK, desc); err != nil { + return err + } + default: + emptyVal := internal.NewRawXMLElement(rxn, nil, nil) + if err := resp.EncodeProp(http.StatusNotImplemented, emptyVal); err != nil { + return err + } } } } + return nil +} - return resp, nil +func (b *backend) propPatchAddressObject(ctx context.Context, update *internal.PropertyUpdate, ao *AddressObject, resp *internal.Response) error { + // TODO: support PROPPATCH for address objects + for _, prop := range update.Remove { + for _, raw := range prop.Prop.Raw { + rxn, ok := raw.XMLName() + if !ok { + return fmt.Errorf("failed to parse properties") + } + emptyVal := internal.NewRawXMLElement(rxn, nil, nil) + if err := resp.EncodeProp(http.StatusNotImplemented, emptyVal); err != nil { + return err + } + } + } + for _, prop := range update.Set { + for _, raw := range prop.Prop.Raw { + rxn, ok := raw.XMLName() + if !ok { + return fmt.Errorf("failed to parse properties") + } + emptyVal := internal.NewRawXMLElement(rxn, nil, nil) + if err := resp.EncodeProp(http.StatusNotImplemented, emptyVal); err != nil { + return err + } + } + } + return nil } func (b *backend) Put(r *http.Request) (*internal.Href, error) {