Skip to content

Commit

Permalink
carddav: PROPPATCH support for address books
Browse files Browse the repository at this point in the history
The groundwork for address objects is also there, but it's not fully
implemented.
  • Loading branch information
bitfehler committed Feb 8, 2024
1 parent 25f1014 commit 4de6415
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 23 deletions.
4 changes: 4 additions & 0 deletions carddav/carddav_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
164 changes: 141 additions & 23 deletions carddav/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 4de6415

Please sign in to comment.