Skip to content

Commit

Permalink
carddav: PROPPATCH support for address books
Browse files Browse the repository at this point in the history
  • Loading branch information
bitfehler committed Mar 15, 2024
1 parent 25f1014 commit 20d0ee3
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 25 deletions.
5 changes: 5 additions & 0 deletions carddav/carddav.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ type AddressBook struct {
SupportedAddressData []AddressDataType
}

type AddressBookUpdate struct {
Name *string
Description *string
}

func (ab *AddressBook) SupportsAddressData(contentType, version string) bool {
if len(ab.SupportedAddressData) == 0 {
return contentType == "text/vcard" && version == "3.0"
Expand Down
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, path string, update *AddressBookUpdate) error {
panic("TODO: implement")
}

func (*testBackend) DeleteAddressBook(ctx context.Context, path string) error {
panic("TODO: implement")
}
Expand Down
125 changes: 100 additions & 25 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, path string, update *AddressBookUpdate) 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 @@ -284,7 +285,7 @@ func (b *backend) Options(r *http.Request) (caps []string, allow []string, err e
if b.resourceTypeAtPath(r.URL.Path) != resourceTypeAddressObject {
// Note: some clients assume the address book is read-only when
// DELETE/MKCOL are missing
return caps, []string{http.MethodOptions, "PROPFIND", "REPORT", "DELETE", "MKCOL"}, nil
return caps, []string{http.MethodOptions, "PROPFIND", "PROPPATCH", "REPORT", "DELETE", "MKCOL"}, nil
}

var dataReq AddressDataRequest
Expand All @@ -302,6 +303,8 @@ func (b *backend) Options(r *http.Request) (caps []string, allow []string, err e
http.MethodPut,
http.MethodDelete,
"PROPFIND",
// TODO PROPPATCH support for address objects
//"PROPPATCH",
}, nil
}

Expand Down Expand Up @@ -614,43 +617,115 @@ 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:
abUpdate, err := b.propPatchAddressBook(r.Context(), update, resp)
if err != nil {
return nil, err
}
err = b.Backend.UpdateAddressBook(r.Context(), r.URL.Path, &abUpdate)
if err != nil {
return nil, err
}
case resourceTypeAddressObject:
// TODO: support PROPPATCH for address objects
return nil, internal.HTTPErrorf(http.StatusNotImplemented, "PROPPATCH for address objects not yet implemented")
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, resp *internal.Response) (AddressBookUpdate, error) {
// TODO handle all properties
var (
result AddressBookUpdate
name internal.DisplayName
desc addressbookDescription
)
for _, prop := range update.Remove {
for _, raw := range prop.Prop.Raw {
rxn, ok := raw.XMLName()
if !ok {
return result, fmt.Errorf("failed to parse properties")
}
switch rxn {
case internal.DisplayNameName:
result.Description = new(string)
if err := resp.EncodeProp(http.StatusOK, internal.DisplayName{}); err != nil {
return result, err
}
case addressBookDescriptionName:
result.Description = new(string)
if err := resp.EncodeProp(http.StatusOK, desc); err != nil {
return result, err
}
default:
emptyVal := internal.NewRawXMLElement(rxn, nil, nil)
if err := resp.EncodeProp(http.StatusNotImplemented, emptyVal); err != nil {
return result, 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 result, fmt.Errorf("failed to parse properties")
}
switch rxn {
case internal.DisplayNameName:
if err := raw.Decode(&name); err != nil {
return result, err
}
result.Name = &name.Name
if err := resp.EncodeProp(http.StatusOK, internal.DisplayName{}); err != nil {
return result, err
}
case addressBookDescriptionName:
if err := raw.Decode(&desc); err != nil {
return result, err
}
result.Description = &desc.Description
if err := resp.EncodeProp(http.StatusOK, desc); err != nil {
return result, err
}
default:
emptyVal := internal.NewRawXMLElement(rxn, nil, nil)
if err := resp.EncodeProp(http.StatusNotImplemented, emptyVal); err != nil {
return result, err
}
}
}
}

return resp, nil
return result, nil
}

func (b *backend) Put(r *http.Request) (*internal.Href, error) {
Expand Down

0 comments on commit 20d0ee3

Please sign in to comment.