From 5886e82c794e118e402aff260e4eba309872867e Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 9 Jul 2008 15:42:22 +0000 Subject: [PATCH 1/8] Monotone-Parent: 1ffb41682368b46b7d5dfbe50eb430e0859f6837 Monotone-Revision: 9402acf99e73ef8bdcad7719c88dc1d6a3ee27d4 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-07-09T15:42:22 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 9 ++ SoObjects/SOGo/SOGoContentObject.h | 4 +- SoObjects/SOGo/SOGoContentObject.m | 168 +++++++++++++++-------------- 3 files changed, 100 insertions(+), 81 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8bd2072ff..da0efac1d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2008-07-09 Wolfgang Sourdeau + + * SoObjects/SOGo/SOGoContentObject.m ([SOGoContentObject + -PUTAction:_ctx]): the version passed to + saveContentString:baseVersion: is the current value of "version". + If the operation is succesful, the version is incremented from + here since no refresh is done from GCS. The returned etag has thus + now the correct value. + 2008-07-08 Wolfgang Sourdeau * SoObjects/Appointments/SOGoAppointmentFolder.m diff --git a/SoObjects/SOGo/SOGoContentObject.h b/SoObjects/SOGo/SOGoContentObject.h index 9aa5ccbbb..0961ae2a4 100644 --- a/SoObjects/SOGo/SOGoContentObject.h +++ b/SoObjects/SOGo/SOGoContentObject.h @@ -45,8 +45,8 @@ andContent: (NSString *) newContent inContainer: (SOGoGCSFolder *) newContainer; - (id) initWithRecord: (NSDictionary *) objectRecord - inContainer: (id) newContainer; -+ (id) initWithName: (NSString *) newName + inContainer: (SOGoGCSFolder *) newContainer; +- (id) initWithName: (NSString *) newName andContent: (NSString *) newContent inContainer: (SOGoGCSFolder *) newContainer; diff --git a/SoObjects/SOGo/SOGoContentObject.m b/SoObjects/SOGo/SOGoContentObject.m index b68ce50e6..d4c6e82db 100644 --- a/SoObjects/SOGo/SOGoContentObject.m +++ b/SoObjects/SOGo/SOGoContentObject.m @@ -53,13 +53,13 @@ return newObject; } -+ (id) objectWithName: (NSDictionary *) objectRecord ++ (id) objectWithName: (NSString *) newName andContent: (NSString *) newContent inContainer: (SOGoGCSFolder *) newContainer { SOGoContentObject *newObject; - newObject = [[self alloc] initWithName: objectRecord + newObject = [[self alloc] initWithName: newName andContent: newContent inContainer: newContainer]; [newObject autorelease]; @@ -104,7 +104,7 @@ } - (id) initWithRecord: (NSDictionary *) objectRecord - inContainer: (id) newContainer + inContainer: (SOGoGCSFolder *) newContainer { NSString *newName; @@ -119,7 +119,7 @@ - (id) initWithName: (NSString *) newName andContent: (NSString *) newContent - inContainer: (id) newContainer + inContainer: (SOGoGCSFolder *) newContainer { if ((self = [self initWithName: newName inContainer: newContainer])) { @@ -233,88 +233,98 @@ - (id) PUTAction: (WOContext *) _ctx { - WORequest *rq; - NSException *error; + WORequest *rq; + NSException *error; unsigned int baseVersion; - id etag, tmp; - BOOL needsLocation; - - if ((error = [self matchesRequestConditionInContext:_ctx]) != nil) - return error; - - rq = [_ctx request]; - - /* check whether its a request to the 'special' 'new' location */ - /* - Note: this is kinda hack. The OGo ZideStore detects writes to 'new' as - object creations and will assign a server side identifier. Most - current GroupDAV clients rely on this behaviour, so we reproduce it - here. - A correct client would loop until it has a name which doesn't not - yet exist (by using if-none-match). - */ - needsLocation = NO; - tmp = [[self nameInContainer] stringByDeletingPathExtension]; - if ([tmp isEqualToString:@"new"]) { - tmp = [self globallyUniqueObjectId]; - needsLocation = YES; - - [self debugWithFormat: - @"reassigned a new location for special new-location: %@", tmp]; - - /* kinda dangerous */ - ASSIGNCOPY(nameInContainer, tmp); - } - - /* determine base version from etag in if-match header */ - /* - Note: The -matchesRequestConditionInContext: already checks whether the - etag matches and returns an HTTP exception in case it doesn't. - We retrieve the etag again here to _ensure_ a transactionally save - commit. - (between the check and the update a change could have been done) - */ - tmp = [rq headerForKey:@"if-match"]; - tmp = [self parseETagList:tmp]; - etag = nil; - if ([tmp count] > 0) { - if ([tmp count] > 1) { - /* - Note: we would have to attempt a save for _each_ of the etags being - passed in! In practice most WebDAV clients submit exactly one - etag. - */ - [self warnWithFormat: - @"Got multiple if-match etags from client, only attempting to " - @"save with the first: %@", tmp]; - } - - etag = [tmp objectAtIndex:0]; - } - baseVersion = ([etag length] > 0) - ? [etag unsignedIntValue] - : 0 /* 0 means 'do not check' */; - - /* attempt a save */ + id etag, tmp; + BOOL needsLocation; + WOResponse *response; - if ((error = [self saveContentString: [rq contentAsString] - baseVersion: baseVersion]) != nil) - return error; + error = [self matchesRequestConditionInContext: _ctx]; + if (error) + response = (WOResponse *) error; + else + { + rq = [_ctx request]; - /* setup response */ + /* check whether its a request to the 'special' 'new' location */ + /* + Note: this is kinda hack. The OGo ZideStore detects writes to 'new' as + object creations and will assign a server side identifier. Most + current GroupDAV clients rely on this behaviour, so we reproduce it + here. + A correct client would loop until it has a name which doesn't not + yet exist (by using if-none-match). + */ + needsLocation = NO; + + tmp = [[self nameInContainer] stringByDeletingPathExtension]; + if ([tmp isEqualToString: @"new"]) + { + tmp = [self globallyUniqueObjectId]; + needsLocation = YES; + + [self debugWithFormat: + @"reassigned a new location for special new-location: %@", tmp]; + + /* kinda dangerous */ + ASSIGNCOPY (nameInContainer, tmp); + } - // TODO: this should be automatic in the SoDispatcher if we return nil? - [[_ctx response] setStatus: 201 /* Created */]; + /* determine base version from etag in if-match header */ + /* + Note: The -matchesRequestConditionInContext: already checks whether the + etag matches and returns an HTTP exception in case it doesn't. + We retrieve the etag again here to _ensure_ a transactionally save + commit. + (between the check and the update a change could have been done) + */ + tmp = [rq headerForKey: @"if-match"]; + tmp = [self parseETagList: tmp]; + etag = nil; + if ([tmp count] > 0) { + if ([tmp count] > 1) { + /* + Note: we would have to attempt a save for _each_ of the etags being + passed in! In practice most WebDAV clients submit exactly one + etag. + */ + [self warnWithFormat: + @"Got multiple if-match etags from client, only attempting to " + @"save with the first: %@", tmp]; + } + + etag = [tmp objectAtIndex: 0]; + } + baseVersion = (isNew ? 0 : version); - if ((etag = [self davEntityTag]) != nil) - [[_ctx response] setHeader:etag forKey:@"etag"]; + /* attempt a save */ + + error = [self saveContentString: [rq contentAsString] + baseVersion: baseVersion]; + if (error) + response = (WOResponse *) error; + else + { + version++; + response = [_ctx response]; + /* setup response */ - if (needsLocation) { - [[_ctx response] setHeader:[self baseURLInContext:_ctx] - forKey:@"location"]; - } + // TODO: this should be automatic in the SoDispatcher if we return nil? + [response setStatus: 201 /* Created */]; - return [_ctx response]; + etag = [self davEntityTag]; + if (etag) + { + [response setHeader: etag forKey: @"etag"]; + if (needsLocation) + [response setHeader: [self baseURLInContext:_ctx] + forKey: @"location"]; + } + } + } + + return response; } /* E-Tags */ From 325156489121fafbfe59884180c96eebe1be492f Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 9 Jul 2008 15:43:48 +0000 Subject: [PATCH 2/8] Monotone-Parent: 9402acf99e73ef8bdcad7719c88dc1d6a3ee27d4 Monotone-Revision: 4f866f7ebe374b79f2e28ceb3dca60ca3cc709bc Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-07-09T15:43:48 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 5 ++ .../Appointments/SOGoAppointmentObject.m | 77 ++++++++++++++----- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/ChangeLog b/ChangeLog index da0efac1d..0d7a27879 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2008-07-09 Wolfgang Sourdeau + * SoObjects/Appointments/SOGoAppointmentObject.m + ([SOGoAppointmentObject -postCalDAVEventCancelTo:recipients]): new + method that implements support for the CalDAV "CANCEL" method for + POSTs. + * SoObjects/SOGo/SOGoContentObject.m ([SOGoContentObject -PUTAction:_ctx]): the version passed to saveContentString:baseVersion: is the current value of "version". diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index e106ceb06..e93edd831 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -403,15 +403,34 @@ return ex; } +- (NSDictionary *) _caldavSuccessCodeWithRecipient: (NSString *) recipient +{ + NSMutableArray *element; + NSDictionary *code; + + element = [NSMutableArray new]; + [element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, + recipient)]; + [element addObject: davElementWithContent (@"request-status", + XMLNS_CALDAV, + @"2.0;Success")]; + code = davElementWithContent (@"response", XMLNS_CALDAV, + element); + [element release]; + + return code; +} + - (NSArray *) postCalDAVEventRequestTo: (NSArray *) recipients { - NSMutableArray *elements, *element; + NSMutableArray *elements; NSEnumerator *recipientsEnum; NSString *recipient, *uid; iCalEvent *event; iCalPerson *person; elements = [NSMutableArray array]; + event = [self component: NO secure: NO]; recipientsEnum = [recipients objectEnumerator]; while ((recipient = [recipientsEnum nextObject])) @@ -427,15 +446,40 @@ forOldObject: nil andNewObject: event toAttendees: [NSArray arrayWithObject: person]]; [person release]; - element = [NSMutableArray new]; - [element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, - recipient)]; - [element addObject: davElementWithContent (@"request-status", - XMLNS_CALDAV, - @"2.0;Success")]; - [elements addObject: davElementWithContent (@"response", XMLNS_CALDAV, - element)]; - [element release]; + [elements + addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; + } + + return elements; +} + +- (NSArray *) postCalDAVEventCancelTo: (NSArray *) recipients +{ + NSMutableArray *elements; + NSEnumerator *recipientsEnum; + NSString *recipient, *uid; + iCalEvent *event; + iCalPerson *person; + + elements = [NSMutableArray array]; + + event = [self component: NO secure: NO]; + recipientsEnum = [recipients objectEnumerator]; + while ((recipient = [recipientsEnum nextObject])) + if ([[recipient lowercaseString] hasPrefix: @"mailto:"]) + { + person = [iCalPerson new]; + [person setValue: 0 to: recipient]; + uid = [person uid]; + if (uid) + [self _removeEventFromUID: uid]; +#warning fix this when sendEmailUsing blabla has been cleaned up + [self sendEMailUsingTemplateNamed: @"Deletion" + forOldObject: nil andNewObject: event + toAttendees: [NSArray arrayWithObject: person]]; + [person release]; + [elements + addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; } return elements; @@ -459,7 +503,7 @@ - (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients { - NSMutableArray *elements, *element; + NSMutableArray *elements; NSEnumerator *recipientsEnum; NSString *recipient, *uid, *eventUID; iCalEvent *event; @@ -493,15 +537,8 @@ forOldObject: nil andNewObject: event toAttendees: [NSArray arrayWithObject: person]]; [person release]; - element = [NSMutableArray new]; - [element addObject: davElementWithContent (@"recipient", XMLNS_CALDAV, - recipient)]; - [element addObject: davElementWithContent (@"request-status", - XMLNS_CALDAV, - @"2.0;Success")]; - [elements addObject: davElementWithContent (@"response", XMLNS_CALDAV, - element)]; - [element release]; + [elements + addObject: [self _caldavSuccessCodeWithRecipient: recipient]]; } return elements; From 7a85810909b4e8719c45cb12c0d5b116d3f78577 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 9 Jul 2008 15:43:55 +0000 Subject: [PATCH 3/8] Monotone-Parent: 4f866f7ebe374b79f2e28ceb3dca60ca3cc709bc Monotone-Revision: 3cad3ace0562bec9fda3d3fbacaeea2559bdda1b Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-07-09T15:43:55 Monotone-Branch: ca.inverse.sogo --- SoObjects/Appointments/SOGoAppointmentObject.h | 1 + 1 file changed, 1 insertion(+) diff --git a/SoObjects/Appointments/SOGoAppointmentObject.h b/SoObjects/Appointments/SOGoAppointmentObject.h index 90f720f98..37f63201b 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SoObjects/Appointments/SOGoAppointmentObject.h @@ -52,6 +52,7 @@ - (NSArray *) postCalDAVEventRequestTo: (NSArray *) recipients; - (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients; +- (NSArray *) postCalDAVEventCancelTo: (NSArray *) recipients; /* "iCal multifolder saves" */ From 726a54d7dca57eb95c7b52765e28d9a98757e558 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 9 Jul 2008 15:54:39 +0000 Subject: [PATCH 4/8] Monotone-Parent: 3cad3ace0562bec9fda3d3fbacaeea2559bdda1b Monotone-Revision: 135e7483e9580e1a8e72447e05a5d342c957b54a Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-07-09T15:54:39 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 10 +++++++ .../Appointments/SOGoAppointmentFolder.m | 30 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 0d7a27879..faec68d32 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,15 @@ 2008-07-09 Wolfgang Sourdeau + * SoObjects/Appointments/SOGoAppointmentFolder.m + ([SOGoAppointmentFolder -davCalendarFreeBusySet]): new method that + returns the HREF to the calendars involved in the user's freebusy. + ([SOGoAppointmentFolder + -setDavCalendarFreeBusySet:newFreeBusySet]): new method that + PRETENDS to modify the list of calendars mentionned above. + ([SOGoAppointmentFolder + -caldavEventRequest:eventwithContent:iCalStringfrom:originatorto:recipients]): + add support for the "CANCEL" method. + * SoObjects/Appointments/SOGoAppointmentObject.m ([SOGoAppointmentObject -postCalDAVEventCancelTo:recipients]): new method that implements support for the CalDAV "CANCEL" method for diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 658cd6a52..f0fffbcba 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -1304,6 +1304,32 @@ _selectorForProperty (NSString *property) return error; } +- (SOGoWebDAVValue *) davCalendarFreeBusySet +{ + NSEnumerator *subFolders; + SOGoAppointmentFolder *currentFolder; + NSMutableArray *response; + SOGoWebDAVValue *responseValue; + + response = [NSMutableArray new]; + subFolders = [[container subFolders] objectEnumerator]; + while ((currentFolder = [subFolders nextObject])) + [response addObject: davElementWithContent (@"href", XMLNS_WEBDAV, + [currentFolder davURL])]; + responseValue = [davElementWithContent (@"calendar-free-busy-set", XMLNS_CALDAV, response) + asWebDAVValue]; + [response release]; + + return responseValue; +} + +/* This method is ignored but we must return a success value. */ +- (NSException *) setDavCalendarFreeBusySet: (NSString *) newFreeBusySet +{ + return nil; +} + + - (void) _appendComponentProperties: (NSString **) properties matchingFilters: (NSArray *) filters toResponse: (WOResponse *) response @@ -1751,7 +1777,9 @@ _selectorForProperty (NSString *property) if ([method isEqualToString: @"REQUEST"]) elements = [apt postCalDAVEventRequestTo: recipients]; else if ([method isEqualToString: @"REPLY"]) - elements = [apt postCalDAVEventReplyTo: recipients]; + elements = [apt postCalDAVEventReplyTo: recipients]; + else if ([method isEqualToString: @"CANCEL"]) + elements = [apt postCalDAVEventCancelTo: recipients]; else elements = nil; From 8cebb1931d10648b48dc7003d51d125315788ff7 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 9 Jul 2008 15:59:56 +0000 Subject: [PATCH 5/8] Monotone-Parent: 135e7483e9580e1a8e72447e05a5d342c957b54a Monotone-Revision: 337924e6726c6500e1c6fb527d7aa68336314c02 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-07-09T15:59:56 Monotone-Branch: ca.inverse.sogo --- SOPE/sope-patchset-r1623.diff | 51 +++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/SOPE/sope-patchset-r1623.diff b/SOPE/sope-patchset-r1623.diff index 3e43cfde8..02e87976b 100644 --- a/SOPE/sope-patchset-r1623.diff +++ b/SOPE/sope-patchset-r1623.diff @@ -1741,6 +1741,21 @@ Index: sope-core/NGExtensions/EOExt.subproj/EOGlobalID+Ext.m #import #import +Index: sope-core/NGStreams/GNUmakefile.preamble +=================================================================== +--- sope-core/NGStreams/GNUmakefile.preamble (révision 1623) ++++ sope-core/NGStreams/GNUmakefile.preamble (copie de travail) +@@ -1,7 +1,10 @@ + # compilation settings + ++MACHCPU = $(shell echo $$MACHTYPE | cut -f 1 -d '-') ++ + libNGStreams_INCLUDE_DIRS += \ + -I$(GNUSTEP_TARGET_CPU)/$(GNUSTEP_TARGET_OS) \ ++ -I./$(MACHCPU)/$(GNUSTEP_TARGET_OS) \ + -INGStreams \ + -I../NGExtensions \ + -I.. Index: sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.h =================================================================== --- sope-xml/libxmlSAXDriver/libxmlHTMLSAXDriver.h (révision 1623) @@ -1990,7 +2005,7 @@ Index: sope-appserver/NGObjWeb/DAVPropMap.plist "{DAV:}locktoken" = davLockToken; "{DAV:}activelock" = davActiveLock; // TODO: non-standard?, also used by WebDrive -@@ -120,12 +126,29 @@ +@@ -120,12 +126,31 @@ "{http://ucb.openoffice.org/dav/props/}IsRemoveable" = isOOoRemoveable; "{http://ucb.openoffice.org/dav/props/}IsVolume" = isOOoVolume; "{http://ucb.openoffice.org/dav/props/}TargetURL" = davOOoTargetURL; @@ -2016,12 +2031,14 @@ Index: sope-appserver/NGObjWeb/DAVPropMap.plist "{urn:ietf:params:xml:ns:caldav}calendar-home-set" = davCalendarHomeSet; + "{urn:ietf:params:xml:ns:caldav}calendar-user-address-set" = + davCalendarUserAddressSet; ++ "{urn:ietf:params:xml:ns:caldav}calendar-free-busy-set" = ++ davCalendarFreeBusySet; + "{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL" = davCalendarScheduleInboxURL; + "{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL" = davCalendarScheduleOutboxURL; "{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set" = davCalendarComponentSet; "{urn:ietf:params:xml:ns:caldav}supported-calendar-data" = -@@ -138,13 +161,13 @@ +@@ -138,13 +163,13 @@ "{urn:ietf:params:xml:ns:carddav}addressbook-description" = davDescription; /* Apple CalServer */ @@ -2317,3 +2334,33 @@ Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m @end /* WOHttpAdaptor */ @implementation WOCoreApplication(SimpleParserSelection) +Index: sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.h +=================================================================== +--- sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.h (révision 1623) ++++ sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.h (copie de travail) +@@ -62,6 +62,10 @@ + /* RFC 3253 (DeltaV) */ + NGHttpMethod_REPORT, + NGHttpMethod_VERSION_CONTROL, ++ /* RFC 4791 (CalDAV) */ ++ NGHttpMethod_MKCALENDAR, ++ /* http://ietfreport.isoc.org/idref/draft-daboo-carddav/ (CardDAV) */ ++ NGHttpMethod_MKADDRESSBOOK, + NGHttpMethod_last + } NGHttpMethod; + +Index: sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m +=================================================================== +--- sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m (révision 1623) ++++ sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.m (copie de travail) +@@ -59,6 +59,10 @@ + /* RFC 3253 (DeltaV) */ + @"REPORT", + @"VERSION-CONTROL", ++ /* RFC 4791 (CalDAV) */ ++ @"MKCALENDAR", ++ /* http://ietfreport.isoc.org/idref/draft-daboo-carddav/ (CardDAV) */ ++ @"MKADDRESSBOOK", + nil + }; + From 24f460e9b07fb6f8fe107037dd352e6ad3dabeb6 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 9 Jul 2008 16:02:42 +0000 Subject: [PATCH 6/8] Monotone-Parent: 337924e6726c6500e1c6fb527d7aa68336314c02 Monotone-Revision: 5e7e2018cb3fa2625cceaaa04d46251c7d1bbd09 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-07-09T16:02:42 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 4 ++ .../Appointments/SOGoAppointmentFolders.m | 40 +++++++++++++++++++ SoObjects/SOGo/SOGoContentObject.m | 3 +- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index faec68d32..7481b84e1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2008-07-09 Wolfgang Sourdeau + * SoObjects/SOGo/SOGoContentObject.m ([SOGoContentObject + -PUTAction:_ctx]): increment the version number only if the record + is not new. + * SoObjects/Appointments/SOGoAppointmentFolder.m ([SOGoAppointmentFolder -davCalendarFreeBusySet]): new method that returns the HREF to the calendars involved in the user's freebusy. diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.m b/SoObjects/Appointments/SOGoAppointmentFolders.m index 5490f021e..d0dd127b9 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.m +++ b/SoObjects/Appointments/SOGoAppointmentFolders.m @@ -23,6 +23,10 @@ #import #import +#import +#import +#import + #import "SOGoAppointmentFolder.h" #import "SOGoAppointmentFolders.h" @@ -44,6 +48,42 @@ return [self labelForKey: @"Personal Calendar"]; } +- (id) lookupName: (NSString *) name + inContext: (WOContext *) lookupContext + acquire: (BOOL) acquire +{ + id obj; + WORequest *rq; + + obj = [super lookupName: name inContext: lookupContext acquire: NO]; + + rq = [context request]; + if ([rq isSoWebDAVRequest] + && [[rq method] isEqualToString: @"MKCALENDAR"]) + { + if (obj) + obj = [NSException exceptionWithHTTPStatus: 403]; + else + { + obj = [self newFolderWithName: name andNameInContainer: name]; + if (!obj) + obj = [super lookupName: name inContext: lookupContext acquire: NO]; + } + } + + return obj; +} + +- (id) doMKCALENDAR: (id) test +{ + return nil; +} + +- (id) MKCALENDARAction: (id) localContext +{ + return nil; +} + #warning THIS CAUSES LIGHTNING TO FAIL (that is why its commented out) // - (NSArray *) davComplianceClassesInContext: (id)_ctx // { diff --git a/SoObjects/SOGo/SOGoContentObject.m b/SoObjects/SOGo/SOGoContentObject.m index d4c6e82db..e3e36605c 100644 --- a/SoObjects/SOGo/SOGoContentObject.m +++ b/SoObjects/SOGo/SOGoContentObject.m @@ -306,7 +306,8 @@ response = (WOResponse *) error; else { - version++; + if (!isNew) + version++; response = [_ctx response]; /* setup response */ From 13daab061a614876f58e1d58bca599da96ec96b2 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 9 Jul 2008 21:05:47 +0000 Subject: [PATCH 7/8] Monotone-Parent: 5e7e2018cb3fa2625cceaaa04d46251c7d1bbd09 Monotone-Revision: 10cd99d7bb60f6b8cd1fe6b59a1c9a647dd0ba3b Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-07-09T21:05:47 Monotone-Branch: ca.inverse.sogo --- SOPE/sope-patchset-r1623.diff | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/SOPE/sope-patchset-r1623.diff b/SOPE/sope-patchset-r1623.diff index 02e87976b..3ba49a124 100644 --- a/SOPE/sope-patchset-r1623.diff +++ b/SOPE/sope-patchset-r1623.diff @@ -2094,6 +2094,95 @@ Index: sope-appserver/NGObjWeb/WebDAV/SoObjectWebDAVDispatcher.m return [self httpException:400 /* invalid request */ reason:@"XML entity expected for WebDAV REPORT."]; } +@@ -1601,8 +1601,60 @@ + /* CalDAV */ + + - (id)doMKCALENDAR:(WOContext *)_ctx { +- return [self httpException:405 /* method not allowed */ +- reason:@"CalDAV calendar creation not yet implemented."]; ++ SoSecurityManager *sm; ++ NSException *e; ++ NSString *pathInfo; ++ ++ pathInfo = [_ctx pathInfo]; ++ if (![pathInfo isNotEmpty]) { ++ /* MKCALENDAR target already exists ... */ ++ WOResponse *r; ++ ++ [self logWithFormat:@"MKCALENDAR target exists !"]; ++ ++ r = [_ctx response]; ++ [r setStatus:405 /* method not allowed */]; ++ [r appendContentString:@"calendar collection already exists !"]; ++ return r; ++ } ++ ++ /* check permissions */ ++ ++ sm = [_ctx soSecurityManager]; ++ e = [sm validatePermission:SoPerm_AddFolders ++ onObject:self->object ++ inContext:_ctx]; ++ if (e != nil) return e; ++ ++ /* check whether all the parent collections are available */ ++ if ([pathInfo rangeOfString:@"/"].length > 0) { ++ return [self httpException:409 /* Conflict */ ++ reason: ++ @"invalid WebDAV MKCALENDAR request, first create all " ++ @"parent collections !"]; ++ } ++ ++ /* check whether the object supports creating collections */ ++ ++ if (![self->object respondsToSelector: ++ @selector(davCreateCalendarCollection:inContext:)]) { ++ /* Note: this should never happen, as this is implemented on NSObject */ ++ ++ [self logWithFormat:@"MKCALENDAR: object '%@' path-info '%@'", ++ self->object, pathInfo]; ++ return [self httpException:405 /* not allowed */ ++ reason: ++ @"this object cannot create a new calendar collection with MKCALENDAR"]; ++ } ++ ++ if ((e = [self->object davCreateCalendarCollection:pathInfo inContext:_ctx])) { ++ [self debugWithFormat:@"creation of calendar collection '%@' failed: %@", ++ pathInfo, e]; ++ return e; ++ } ++ ++ [self debugWithFormat:@"created calendar collection."]; ++ return [NSNumber numberWithBool:YES]; + } + + /* DAV access control lists */ +Index: sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m +=================================================================== +--- sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m (révision 1623) ++++ sope-appserver/NGObjWeb/WebDAV/SoWebDAVRenderer.m (copie de travail) +@@ -277,7 +277,8 @@ + ok = [self renderLockToken:_object inContext:_ctx]; + break; + case 'M': +- if ([m isEqualToString:@"MKCOL"]) ++ if ([m isEqualToString:@"MKCOL"] ++ || [m isEqualToString:@"MKCALENDAR"]) + ok = [self renderMkColResult:_object inContext:_ctx]; + else if ([m isEqualToString:@"MOVE"]) { + ok = [self renderStatusResult:_object +Index: sope-appserver/NGObjWeb/WebDAV/SoObject+SoDAV.h +=================================================================== +--- sope-appserver/NGObjWeb/WebDAV/SoObject+SoDAV.h (révision 1623) ++++ sope-appserver/NGObjWeb/WebDAV/SoObject+SoDAV.h (copie de travail) +@@ -62,6 +62,7 @@ + properties:(NSDictionary *)_props + inContext:(id)_ctx; + - (NSException *)davCreateCollection:(NSString *)_name inContext:(id)_ctx; ++- (NSException *)davCreateCalendarCollection:(NSString *)_name inContext:(id)_ctx; + + - (NSException *)davMoveToTargetObject:(id)_target newName:(NSString *)_name + inContext:(id)_ctx; Index: sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m =================================================================== --- sope-appserver/NGObjWeb/DynamicElements/WOHyperlinkInfo.m (révision 1623) @@ -2309,6 +2398,20 @@ Index: sope-appserver/NGObjWeb/SoObjects/SoObject.m } } +Index: sope-appserver/NGObjWeb/SoObjects/SoObject+Traversal.m +=================================================================== +--- sope-appserver/NGObjWeb/SoObjects/SoObject+Traversal.m (révision 1623) ++++ sope-appserver/NGObjWeb/SoObjects/SoObject+Traversal.m (copie de travail) +@@ -195,7 +195,8 @@ + isCreateIfMissingMethod = YES; + else if ([m isEqualToString:@"PROPPATCH"]) + isCreateIfMissingMethod = YES; +- else if ([m isEqualToString:@"MKCOL"]) ++ else if ([m isEqualToString:@"MKCOL"] ++ || [m isEqualToString:@"MKCALENDAR"]) + /* this one is strictly creating */ + isCreateMethod = YES; + // TODO: the following are only create-if-missing on the target! Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m =================================================================== --- sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m (révision 1623) @@ -2334,6 +2437,27 @@ Index: sope-appserver/NGObjWeb/WOHttpAdaptor/WOHttpTransaction.m @end /* WOHttpAdaptor */ @implementation WOCoreApplication(SimpleParserSelection) +Index: sope-appserver/NGObjWeb/Defaults.plist +=================================================================== +--- sope-appserver/NGObjWeb/Defaults.plist (révision 1623) ++++ sope-appserver/NGObjWeb/Defaults.plist (copie de travail) +@@ -216,7 +216,7 @@ + SoWebDAVDisableCrossHostMoveCheck = NO; + + SoWebDAVDefaultAllowMethods = ( +- GET, HEAD, POST, OPTIONS, MKCOL, DELETE, PUT, ++ GET, HEAD, POST, OPTIONS, MKCOL, MKCALENDAR, DELETE, PUT, + LOCK, UNLOCK, COPY, MOVE + /* , NOTIFY, POLL, SUBSCRIBE, UNSUBSCRIBE */ + ); +@@ -224,6 +224,7 @@ + SoWebDAVDetectionMethods = ( + OPTIONS, + MKCOL, ++ MKCALENDAR, + PROPFIND, + PROPPATCH, + DELETE, Index: sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.h =================================================================== --- sope-appserver/NGObjWeb/NGHttp/NGHttpRequest.h (révision 1623) From 5e1fb58ab9707c43c04733f21ffff67a2e001b6c Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 9 Jul 2008 21:09:08 +0000 Subject: [PATCH 8/8] Monotone-Parent: 10cd99d7bb60f6b8cd1fe6b59a1c9a647dd0ba3b Monotone-Revision: dcb58071ad12a3e87d2a061647e9558e70bb9775 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2008-07-09T21:09:08 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 14 +++ .../Appointments/SOGoAppointmentFolder.m | 32 +----- .../Appointments/SOGoAppointmentFolders.m | 104 ++++++++++++++---- SoObjects/SOGo/SOGoObject.h | 3 + SoObjects/SOGo/SOGoObject.m | 58 ++++++++++ SoObjects/SOGo/SOGoParentFolder.h | 1 + SoObjects/SOGo/WORequest+SOGo.h | 1 + SoObjects/SOGo/WORequest+SOGo.m | 68 ++++++++++++ 8 files changed, 231 insertions(+), 50 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7481b84e1..e4d4b05e0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,19 @@ 2008-07-09 Wolfgang Sourdeau + * SoObjects/Appointments/SOGoAppointmentFolders.m ([SOGoAppointmentFolders -davCreateCalendarCollection:newNameinContext:createContext]): + new method that execute a "MKCALENDAR" request. + + * SoObjects/SOGo/SOGoObject.m (SOGoSelectorForPropertyGetter): new + function that returns the selector for the getter method for the + specified property. + (SOGoSelectorForPropertySetter): same as above for the setter + method. + + * SoObjects/SOGo/WORequest+SOGo.m ([WORequest + -davPatchedPropertiesWithTopTag:topTag]): new category method to + help with parsing properties modified or removed from PROPPATCH + methods and alike. + * SoObjects/SOGo/SOGoContentObject.m ([SOGoContentObject -PUTAction:_ctx]): increment the version number only if the record is not new. diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index f0fffbcba..d2e6754f1 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -891,36 +891,6 @@ static Class sogoAppointmentFolderKlass = Nil; #warning we should use the EOFetchSpecification for that!!! (see doPROPFIND:) #warning components in calendar-data query are ignored -static inline SEL -_selectorForProperty (NSString *property) -{ - static NSMutableDictionary *methodMap = nil; - SEL propSel; - NSValue *propPtr; - NSDictionary *map; - NSString *methodName; - - if (!methodMap) - methodMap = [NSMutableDictionary new]; - propPtr = [methodMap objectForKey: property]; - if (propPtr) - propSel = [propPtr pointerValue]; - else - { - map = [sogoAppointmentFolderKlass defaultWebDAVAttributeMap]; - methodName = [map objectForKey: property]; - if (methodName) - { - propSel = NSSelectorFromString (methodName); - if (propSel) - [methodMap setObject: [NSValue valueWithPointer: propSel] - forKey: property]; - } - } - - return propSel; -} - - (NSString *) _nodeTagForProperty: (NSString *) property { NSString *namespace, *nodeName, *nsRep; @@ -984,7 +954,7 @@ _selectorForProperty (NSString *property) currentValue = values; while (*currentProperty) { - methodSel = _selectorForProperty (*currentProperty); + methodSel = SOGoSelectorForPropertyGetter (*currentProperty); if (methodSel && [sogoObject respondsToSelector: methodSel]) *currentValue = [[sogoObject performSelector: methodSel] stringByEscapingXMLString]; diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.m b/SoObjects/Appointments/SOGoAppointmentFolders.m index d0dd127b9..c12ab6135 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.m +++ b/SoObjects/Appointments/SOGoAppointmentFolders.m @@ -21,12 +21,14 @@ */ #import +#import #import #import #import #import +#import #import "SOGoAppointmentFolder.h" #import "SOGoAppointmentFolders.h" @@ -48,40 +50,104 @@ return [self labelForKey: @"Personal Calendar"]; } -- (id) lookupName: (NSString *) name - inContext: (WOContext *) lookupContext - acquire: (BOOL) acquire +- (NSString *) _fetchPropertyWithName: (NSString *) propertyName + inArray: (NSArray *) section { - id obj; - WORequest *rq; + NSObject *currentElement; + NSString *currentName, *property; + NSEnumerator *elements; + NSObject *values; - obj = [super lookupName: name inContext: lookupContext acquire: NO]; + property = nil; - rq = [context request]; - if ([rq isSoWebDAVRequest] - && [[rq method] isEqualToString: @"MKCALENDAR"]) + elements = [section objectEnumerator]; + while (!property && (currentElement = [elements nextObject])) { - if (obj) - obj = [NSException exceptionWithHTTPStatus: 403]; - else + currentName = [NSString stringWithFormat: @"{%@}%@", + [currentElement namespaceURI], + [currentElement nodeName]]; + if ([currentName isEqualToString: propertyName]) { - obj = [self newFolderWithName: name andNameInContainer: name]; - if (!obj) - obj = [super lookupName: name inContext: lookupContext acquire: NO]; + values = [currentElement childNodes]; + if ([values length]) + property = [[values objectAtIndex: 0] nodeValue]; } } - return obj; + return property; } -- (id) doMKCALENDAR: (id) test +#warning this method may be useful at a higher level +#warning not all values are simple strings... +- (NSException *) _applyMkCalendarProperties: (NSArray *) properties + toObject: (SOGoObject *) newFolder { + NSEnumerator *allProperties; + NSObject *currentProperty; + NSObject *values; + NSString *value, *currentName; + SEL methodSel; + + allProperties = [properties objectEnumerator]; + while ((currentProperty = [allProperties nextObject])) + { + values = [currentProperty childNodes]; + if ([values length]) + { + value = [[values objectAtIndex: 0] nodeValue]; + currentName = [NSString stringWithFormat: @"{%@}%@", + [currentProperty namespaceURI], + [currentProperty nodeName]]; + methodSel = SOGoSelectorForPropertySetter (currentName); + if ([newFolder respondsToSelector: methodSel]) + [newFolder performSelector: methodSel + withObject: value]; + } + } + return nil; } -- (id) MKCALENDARAction: (id) localContext +- (NSException *) davCreateCalendarCollection: (NSString *) newName + inContext: (id) createContext { - return nil; + NSArray *subfolderNames, *setProperties; + NSString *content, *newDisplayName; + NSDictionary *properties; + NSException *error; + SOGoAppointmentFolder *newFolder; + + subfolderNames = [self toManyRelationshipKeys]; + if ([subfolderNames containsObject: newName]) + { + content = [NSString stringWithFormat: + @"A collection named '%@' already exists.", + newName]; + error = [NSException exceptionWithHTTPStatus: 403 + reason: content]; + } + else + { + properties = [[createContext request] + davPatchedPropertiesWithTopTag: @"mkcalendar"]; + setProperties = [properties objectForKey: @"set"]; + newDisplayName = [self _fetchPropertyWithName: @"{DAV:}displayname" + inArray: setProperties]; + if (![newDisplayName length]) + newDisplayName = newName; + error + = [self newFolderWithName: newDisplayName andNameInContainer: newName]; + if (!error) + { + newFolder = [self lookupName: newName + inContext: createContext + acquire: NO]; + error = [self _applyMkCalendarProperties: setProperties + toObject: newFolder]; + } + } + + return error; } #warning THIS CAUSES LIGHTNING TO FAIL (that is why its commented out) diff --git a/SoObjects/SOGo/SOGoObject.h b/SoObjects/SOGo/SOGoObject.h index 4dc554da9..afaf8ad00 100644 --- a/SoObjects/SOGo/SOGoObject.h +++ b/SoObjects/SOGo/SOGoObject.h @@ -62,6 +62,9 @@ #define $(class) NSClassFromString(class) +SEL SOGoSelectorForPropertyGetter (NSString *property); +SEL SOGoSelectorForPropertySetter (NSString *property); + @interface SOGoObject : NSObject { WOContext *context; diff --git a/SoObjects/SOGo/SOGoObject.m b/SoObjects/SOGo/SOGoObject.m index 5d611b5b7..b8bb2af17 100644 --- a/SoObjects/SOGo/SOGoObject.m +++ b/SoObjects/SOGo/SOGoObject.m @@ -71,6 +71,64 @@ static BOOL kontactGroupDAV = YES; static BOOL sendACLAdvisories = NO; static NSDictionary *reportMap = nil; +static NSMutableDictionary *setterMap = nil; +static NSMutableDictionary *getterMap = nil; + +SEL SOGoSelectorForPropertyGetter (NSString *property) +{ + SEL propSel; + NSValue *propPtr; + NSDictionary *map; + NSString *methodName; + + if (!getterMap) + getterMap = [NSMutableDictionary new]; + propPtr = [getterMap objectForKey: property]; + if (propPtr) + propSel = [propPtr pointerValue]; + else + { + map = [SOGoObject defaultWebDAVAttributeMap]; + methodName = [map objectForKey: property]; + if (methodName) + { + propSel = NSSelectorFromString (methodName); + if (propSel) + [getterMap setObject: [NSValue valueWithPointer: propSel] + forKey: property]; + } + } + + return propSel; +} + +SEL SOGoSelectorForPropertySetter (NSString *property) +{ + SEL propSel; + NSValue *propPtr; + NSDictionary *map; + NSString *methodName; + + if (!setterMap) + setterMap = [NSMutableDictionary new]; + propPtr = [setterMap objectForKey: property]; + if (propPtr) + propSel = [propPtr pointerValue]; + else + { + map = [SOGoObject defaultWebDAVAttributeMap]; + methodName = [map objectForKey: property]; + if (methodName) + { + propSel = NSSelectorFromString ([methodName davSetterName]); + if (propSel) + [setterMap setObject: [NSValue valueWithPointer: propSel] + forKey: property]; + } + } + + return propSel; +} @implementation SOGoObject diff --git a/SoObjects/SOGo/SOGoParentFolder.h b/SoObjects/SOGo/SOGoParentFolder.h index 3001e6b3b..4271862e3 100644 --- a/SoObjects/SOGo/SOGoParentFolder.h +++ b/SoObjects/SOGo/SOGoParentFolder.h @@ -43,6 +43,7 @@ - (void) setBaseOCSPath: (NSString *) newOCSPath; +- (NSArray *) toManyRelationshipKeys; - (NSArray *) subFolders; - (NSException *) newFolderWithName: (NSString *) name diff --git a/SoObjects/SOGo/WORequest+SOGo.h b/SoObjects/SOGo/WORequest+SOGo.h index 647f0de34..ac6cc1065 100644 --- a/SoObjects/SOGo/WORequest+SOGo.h +++ b/SoObjects/SOGo/WORequest+SOGo.h @@ -28,6 +28,7 @@ @interface WORequest (SOGoSOPEUtilities) - (BOOL) handledByDefaultHandler; +- (NSDictionary *) davPatchedPropertiesWithTopTag: (NSString *) topTag; @end diff --git a/SoObjects/SOGo/WORequest+SOGo.m b/SoObjects/SOGo/WORequest+SOGo.m index 776cacf9d..eea2fae5e 100644 --- a/SoObjects/SOGo/WORequest+SOGo.m +++ b/SoObjects/SOGo/WORequest+SOGo.m @@ -20,9 +20,13 @@ * Boston, MA 02111-1307, USA. */ +#import + #import #import +#import + #import "WORequest+SOGo.h" @implementation WORequest (SOGoSOPEUtilities) @@ -33,4 +37,68 @@ return (![requestHandlerKey isEqualToString: @"dav"]); } +- (NSArray *) _propertiesOfElement: (id ) startElement + underTag: (NSString *) tag +{ + id list; + id tagElement; + NSObject *currentNode; + NSMutableArray *properties; + unsigned int count, max; + + properties = nil; + + list = [startElement getElementsByTagName: tag]; + if ([list length]) + { + tagElement = [list objectAtIndex: 0]; + list = [tagElement getElementsByTagName: @"prop"]; + if ([list length]) + { + tagElement = [list objectAtIndex: 0]; + properties = [NSMutableArray array]; + list = [tagElement childNodes]; + max = [list length]; + for (count = 0; count < max; count++) + { + currentNode = [list objectAtIndex: count]; + if ([currentNode conformsToProtocol: @protocol (DOMElement)]) + [properties addObject: currentNode]; + } + } + } + + return properties; +} + +- (NSDictionary *) davPatchedPropertiesWithTopTag: (NSString *) topTag +{ + NSMutableDictionary *patchedProperties; + NSArray *props; + id element; + id startElement; + NSObject *list; + + patchedProperties = nil; + if (!topTag) + topTag = @"propertyupdate"; + element = [self contentAsDOMDocument]; + list = [element getElementsByTagName: topTag]; + if ([list length]) + { + startElement = [list objectAtIndex: 0]; + patchedProperties = [NSMutableDictionary dictionary]; + props = [self _propertiesOfElement: startElement + underTag: @"set"]; + if (props) + [patchedProperties setObject: props forKey: @"set"]; + props = [self _propertiesOfElement: startElement + underTag: @"remove"]; + if (props) + [patchedProperties setObject: props forKey: @"remove"]; + } + + return patchedProperties; +} + @end