merge of '546031563b555a3095688a4ec17eeb5902aea9a1'

and 'dcb58071ad12a3e87d2a061647e9558e70bb9775'

Monotone-Parent: 546031563b555a3095688a4ec17eeb5902aea9a1
Monotone-Parent: dcb58071ad12a3e87d2a061647e9558e70bb9775
Monotone-Revision: 77ae5931198ed270924efe912f3a0be9fe7a7a62

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2008-07-09T21:09:59
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Wolfgang Sourdeau
2008-07-09 21:09:59 +00:00
13 changed files with 632 additions and 135 deletions
+42
View File
@@ -1,3 +1,45 @@
2008-07-09 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* 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.
* 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
POSTs.
* 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-09 Francis Lachapelle <flachapelle@inverse.ca>
* UI/MailerUI/UIxMailMainFrame.m ([WOActionResults
+173 -2
View File
@@ -1741,6 +1741,21 @@ Index: sope-core/NGExtensions/EOExt.subproj/EOGlobalID+Ext.m
#import <EOControl/EOGlobalID.h>
#import <Foundation/NSString.h>
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 */
@@ -2077,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)
@@ -2292,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)
@@ -2317,3 +2437,54 @@ 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)
+++ 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
};
+30 -32
View File
@@ -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];
@@ -1304,6 +1274,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 +1747,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;
@@ -21,8 +21,14 @@
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSEnumerator.h>
#import <Foundation/NSString.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WORequest+So.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <SOGo/WORequest+SOGo.h>
#import "SOGoAppointmentFolder.h"
#import "SOGoAppointmentFolders.h"
@@ -44,6 +50,106 @@
return [self labelForKey: @"Personal Calendar"];
}
- (NSString *) _fetchPropertyWithName: (NSString *) propertyName
inArray: (NSArray *) section
{
NSObject <DOMElement> *currentElement;
NSString *currentName, *property;
NSEnumerator *elements;
NSObject <DOMNodeList> *values;
property = nil;
elements = [section objectEnumerator];
while (!property && (currentElement = [elements nextObject]))
{
currentName = [NSString stringWithFormat: @"{%@}%@",
[currentElement namespaceURI],
[currentElement nodeName]];
if ([currentName isEqualToString: propertyName])
{
values = [currentElement childNodes];
if ([values length])
property = [[values objectAtIndex: 0] nodeValue];
}
}
return property;
}
#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 <DOMElement> *currentProperty;
NSObject <DOMNodeList> *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;
}
- (NSException *) davCreateCalendarCollection: (NSString *) newName
inContext: (id) createContext
{
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)
// - (NSArray *) davComplianceClassesInContext: (id)_ctx
// {
@@ -52,6 +52,7 @@
- (NSArray *) postCalDAVEventRequestTo: (NSArray *) recipients;
- (NSArray *) postCalDAVEventReplyTo: (NSArray *) recipients;
- (NSArray *) postCalDAVEventCancelTo: (NSArray *) recipients;
/* "iCal multifolder saves" */
+57 -20
View File
@@ -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;
+2 -2
View File
@@ -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;
+90 -79
View File
@@ -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,99 @@
- (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
{
if (!isNew)
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 */
+3
View File
@@ -62,6 +62,9 @@
#define $(class) NSClassFromString(class)
SEL SOGoSelectorForPropertyGetter (NSString *property);
SEL SOGoSelectorForPropertySetter (NSString *property);
@interface SOGoObject : NSObject
{
WOContext *context;
+58
View File
@@ -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
+1
View File
@@ -43,6 +43,7 @@
- (void) setBaseOCSPath: (NSString *) newOCSPath;
- (NSArray *) toManyRelationshipKeys;
- (NSArray *) subFolders;
- (NSException *) newFolderWithName: (NSString *) name
+1
View File
@@ -28,6 +28,7 @@
@interface WORequest (SOGoSOPEUtilities)
- (BOOL) handledByDefaultHandler;
- (NSDictionary *) davPatchedPropertiesWithTopTag: (NSString *) topTag;
@end
+68
View File
@@ -20,9 +20,13 @@
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSDictionary.h>
#import <NGObjWeb/SoObjectRequestHandler.h>
#import <NGObjWeb/WOApplication.h>
#import <DOM/DOMProtocols.h>
#import "WORequest+SOGo.h"
@implementation WORequest (SOGoSOPEUtilities)
@@ -33,4 +37,68 @@
return (![requestHandlerKey isEqualToString: @"dav"]);
}
- (NSArray *) _propertiesOfElement: (id <DOMElement>) startElement
underTag: (NSString *) tag
{
id <DOMNodeList> list;
id <DOMElement> tagElement;
NSObject <DOMNode> *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 <DOMDocument> element;
id <DOMElement> startElement;
NSObject <DOMNodeList> *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