2006-06-15 21:34:10 +02:00
|
|
|
/*
|
|
|
|
Copyright (C) 2004-2005 SKYRIX Software AG
|
|
|
|
|
|
|
|
This file is part of OpenGroupware.org.
|
|
|
|
|
|
|
|
OGo is free software; you can redistribute it and/or modify it under
|
|
|
|
the terms of the GNU Lesser General Public License as published by the
|
|
|
|
Free Software Foundation; either version 2, or (at your option) any
|
|
|
|
later version.
|
|
|
|
|
|
|
|
OGo is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
|
|
License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with OGo; see the file COPYING. If not, write to the
|
|
|
|
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
|
|
|
02111-1307, USA.
|
|
|
|
*/
|
|
|
|
|
2007-06-18 17:37:37 +02:00
|
|
|
#import <Foundation/NSArray.h>
|
|
|
|
#import <Foundation/NSString.h>
|
|
|
|
#import <Foundation/NSValue.h>
|
|
|
|
|
|
|
|
#import <NGObjWeb/NSException+HTTP.h>
|
|
|
|
#import <NGObjWeb/WOContext.h>
|
|
|
|
#import <NGObjWeb/WOResponse.h>
|
|
|
|
#import <NGExtensions/NSObject+Logs.h>
|
2007-02-21 22:12:41 +01:00
|
|
|
#import <GDLContentStore/GCSFolder.h>
|
|
|
|
|
2007-12-12 16:57:40 +01:00
|
|
|
#import "NSCalendarDate+SOGo.h"
|
2007-11-07 16:59:01 +01:00
|
|
|
#import "SOGoGCSFolder.h"
|
2007-04-26 03:16:19 +02:00
|
|
|
#import "SOGoUser.h"
|
|
|
|
#import "SOGoPermissions.h"
|
2007-02-21 22:12:41 +01:00
|
|
|
#import "SOGoContentObject.h"
|
2006-06-15 21:34:10 +02:00
|
|
|
|
|
|
|
@interface SOGoContentObject(ETag)
|
|
|
|
- (NSArray *)parseETagList:(NSString *)_c;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation SOGoContentObject
|
|
|
|
|
|
|
|
// TODO: check superclass version
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (id) initWithName: (NSString *) newName
|
|
|
|
inContainer: (id) newContainer
|
|
|
|
{
|
|
|
|
if ((self = [super initWithName: newName inContainer: newContainer]))
|
|
|
|
{
|
|
|
|
ocsPath = nil;
|
2008-01-16 19:59:58 +01:00
|
|
|
record = [[self ocsFolder] recordOfEntryWithName: newName];
|
|
|
|
[record retain];
|
|
|
|
isNew = (!record);
|
2007-06-01 22:59:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
{
|
2008-01-16 19:59:58 +01:00
|
|
|
[record release];
|
2007-04-17 16:32:47 +02:00
|
|
|
[ocsPath release];
|
2006-06-15 21:34:10 +02:00
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* accessors */
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (BOOL) isFolderish
|
|
|
|
{
|
2006-06-15 21:34:10 +02:00
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (void) setOCSPath: (NSString *) newOCSPath
|
|
|
|
{
|
|
|
|
if (![ocsPath isEqualToString: newOCSPath])
|
|
|
|
{
|
|
|
|
if (ocsPath)
|
|
|
|
[self warnWithFormat:@"GCS path is already set! '%@'", newOCSPath];
|
2006-06-15 21:34:10 +02:00
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
ASSIGNCOPY (ocsPath, newOCSPath);
|
|
|
|
}
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
|
|
|
|
2007-04-27 20:58:48 +02:00
|
|
|
- (NSString *) ocsPath
|
|
|
|
{
|
2007-06-01 22:59:47 +02:00
|
|
|
NSMutableString *newOCSPath;
|
|
|
|
|
2007-04-27 20:58:48 +02:00
|
|
|
if (!ocsPath)
|
|
|
|
{
|
2007-06-01 22:59:47 +02:00
|
|
|
newOCSPath = [NSMutableString new];
|
|
|
|
[newOCSPath appendString: [self ocsPathOfContainer]];
|
|
|
|
if ([newOCSPath length] > 0)
|
2007-04-27 20:58:48 +02:00
|
|
|
{
|
2007-06-01 22:59:47 +02:00
|
|
|
if (![newOCSPath hasSuffix:@"/"])
|
|
|
|
[newOCSPath appendString: @"/"];
|
|
|
|
[newOCSPath appendString: nameInContainer];
|
|
|
|
ocsPath = newOCSPath;
|
2007-04-27 20:58:48 +02:00
|
|
|
}
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
2007-04-27 20:58:48 +02:00
|
|
|
|
2007-04-17 16:32:47 +02:00
|
|
|
return ocsPath;
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (NSString *) ocsPathOfContainer
|
|
|
|
{
|
|
|
|
NSString *ocsPathOfContainer;
|
2006-06-15 21:34:10 +02:00
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
if ([container respondsToSelector: @selector (ocsPath)])
|
|
|
|
ocsPathOfContainer = [container ocsPath];
|
|
|
|
else
|
|
|
|
ocsPathOfContainer = nil;
|
|
|
|
|
2007-11-29 20:45:20 +01:00
|
|
|
return ocsPathOfContainer;
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
|
|
|
|
2007-04-17 16:32:47 +02:00
|
|
|
- (GCSFolder *) ocsFolder
|
|
|
|
{
|
|
|
|
return [container ocsFolder];
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* content */
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (BOOL) isNew
|
2007-04-17 16:32:47 +02:00
|
|
|
{
|
2007-06-01 22:59:47 +02:00
|
|
|
return isNew;
|
|
|
|
}
|
2006-06-15 21:34:10 +02:00
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (NSString *) contentAsString
|
|
|
|
{
|
2008-01-16 19:59:58 +01:00
|
|
|
return [record objectForKey: @"c_content"];
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (NSException *) saveContentString: (NSString *) newContent
|
|
|
|
baseVersion: (unsigned int) newBaseVersion
|
2006-06-15 21:34:10 +02:00
|
|
|
{
|
|
|
|
/* Note: "iCal multifolder saves" are implemented in the apt subclass! */
|
2008-01-16 19:59:58 +01:00
|
|
|
GCSFolder *folder;
|
2006-06-15 21:34:10 +02:00
|
|
|
NSException *ex;
|
2008-01-16 19:59:58 +01:00
|
|
|
NSMutableDictionary *newRecord;
|
2007-06-01 22:59:47 +02:00
|
|
|
|
|
|
|
ex = nil;
|
|
|
|
|
2008-01-16 19:59:58 +01:00
|
|
|
if (record)
|
|
|
|
newRecord = [NSMutableDictionary dictionaryWithDictionary: record];
|
|
|
|
else
|
|
|
|
newRecord = [NSMutableDictionary dictionary];
|
|
|
|
[newRecord setObject: newContent forKey: @"c_content"];
|
|
|
|
ASSIGN (record, newRecord);
|
2007-11-18 11:16:25 +01:00
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
folder = [container ocsFolder];
|
|
|
|
if (folder)
|
|
|
|
{
|
2007-11-18 11:16:25 +01:00
|
|
|
ex = [folder writeContent: newContent toName: nameInContainer
|
2007-06-01 22:59:47 +02:00
|
|
|
baseVersion: newBaseVersion];
|
|
|
|
if (ex)
|
|
|
|
[self errorWithFormat:@"write failed: %@", ex];
|
|
|
|
}
|
|
|
|
else
|
2006-06-15 21:34:10 +02:00
|
|
|
[self errorWithFormat:@"Did not find folder of content object."];
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
return ex;
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
2007-06-01 22:59:47 +02:00
|
|
|
|
|
|
|
- (NSException *) saveContentString: (NSString *) newContent
|
|
|
|
{
|
|
|
|
return [self saveContentString: newContent baseVersion: 0];
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (NSException *) delete
|
|
|
|
{
|
2006-06-15 21:34:10 +02:00
|
|
|
/* Note: "iCal multifolder saves" are implemented in the apt subclass! */
|
|
|
|
GCSFolder *folder;
|
|
|
|
NSException *ex;
|
|
|
|
|
|
|
|
// TODO: add precondition check? (or add DELETEAction?)
|
|
|
|
|
|
|
|
if ((folder = [self ocsFolder]) == nil) {
|
|
|
|
[self errorWithFormat:@"Did not find folder of content object."];
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((ex = [folder deleteContentWithName:[self nameInContainer]])) {
|
|
|
|
[self errorWithFormat:@"delete failed: %@", ex];
|
|
|
|
return ex;
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* actions */
|
|
|
|
|
2007-06-05 00:38:43 +02:00
|
|
|
// - (id) lookupName:
|
|
|
|
// {
|
|
|
|
// SoSelectorInvocation *invocation;
|
|
|
|
// NSString *name;
|
|
|
|
|
|
|
|
// name = [NSString stringWithFormat: @"%@:", [_key davMethodToObjC]];
|
|
|
|
|
|
|
|
// invocation = [[SoSelectorInvocation alloc]
|
|
|
|
// initWithSelectorNamed: name
|
|
|
|
// addContextParameter: YES];
|
|
|
|
// [invocation autorelease];
|
|
|
|
|
|
|
|
// return invocation;
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
2007-06-01 22:59:47 +02:00
|
|
|
- (id) PUTAction: (WOContext *) _ctx
|
|
|
|
{
|
2006-06-15 21:34:10 +02:00
|
|
|
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"]) {
|
2007-09-15 00:01:02 +02:00
|
|
|
tmp = [self globallyUniqueObjectId];
|
2006-06-15 21:34:10 +02:00
|
|
|
needsLocation = YES;
|
|
|
|
|
|
|
|
[self debugWithFormat:
|
|
|
|
@"reassigned a new location for special new-location: %@", tmp];
|
|
|
|
|
|
|
|
/* kinda dangerous */
|
2007-04-17 16:32:47 +02:00
|
|
|
ASSIGNCOPY(nameInContainer, tmp);
|
|
|
|
ASSIGN(ocsPath, nil);
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 */
|
2007-05-19 02:42:51 +02:00
|
|
|
|
|
|
|
if ((error = [self saveContentString: [rq contentAsString]
|
|
|
|
baseVersion: baseVersion]) != nil)
|
2006-06-15 21:34:10 +02:00
|
|
|
return error;
|
|
|
|
|
|
|
|
/* setup response */
|
|
|
|
|
|
|
|
// TODO: this should be automatic in the SoDispatcher if we return nil?
|
2007-06-01 22:59:47 +02:00
|
|
|
[[_ctx response] setStatus: 201 /* Created */];
|
2006-06-15 21:34:10 +02:00
|
|
|
|
|
|
|
if ((etag = [self davEntityTag]) != nil)
|
|
|
|
[[_ctx response] setHeader:etag forKey:@"etag"];
|
|
|
|
|
|
|
|
if (needsLocation) {
|
|
|
|
[[_ctx response] setHeader:[self baseURLInContext:_ctx]
|
|
|
|
forKey:@"location"];
|
|
|
|
}
|
|
|
|
|
|
|
|
return [_ctx response];
|
|
|
|
}
|
|
|
|
|
|
|
|
/* E-Tags */
|
|
|
|
|
2007-04-27 20:58:48 +02:00
|
|
|
- (id) davEntityTag
|
|
|
|
{
|
2006-06-15 21:34:10 +02:00
|
|
|
// TODO: cache tag in ivar? => if you do, remember to flush after PUT
|
|
|
|
GCSFolder *folder;
|
|
|
|
char buf[64];
|
2007-04-27 20:58:48 +02:00
|
|
|
NSString *entityTag;
|
|
|
|
NSNumber *versionValue;
|
2006-06-15 21:34:10 +02:00
|
|
|
|
2007-04-27 20:58:48 +02:00
|
|
|
folder = [self ocsFolder];
|
|
|
|
if (folder)
|
|
|
|
{
|
2008-01-16 19:59:58 +01:00
|
|
|
versionValue = [record objectForKey: @"c_version"];
|
2007-04-27 20:58:48 +02:00
|
|
|
sprintf (buf, "\"gcs%08d\"", [versionValue unsignedIntValue]);
|
|
|
|
entityTag = [NSString stringWithCString: buf];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[self errorWithFormat:@"Did not find folder of content object."];
|
|
|
|
entityTag = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
return entityTag;
|
2006-06-15 21:34:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* WebDAV */
|
2007-12-12 16:57:40 +01:00
|
|
|
- (NSString *) davCreationDate
|
|
|
|
{
|
|
|
|
NSCalendarDate *date;
|
|
|
|
|
2008-01-16 19:59:58 +01:00
|
|
|
date = [record objectForKey: @"c_creationdate"];
|
2007-12-12 16:57:40 +01:00
|
|
|
|
|
|
|
return [date rfc822DateString];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) davLastModified
|
|
|
|
{
|
|
|
|
NSCalendarDate *date;
|
|
|
|
|
2008-01-16 19:59:58 +01:00
|
|
|
date = [record objectForKey: @"c_lastmodified"];
|
2007-12-12 16:57:40 +01:00
|
|
|
|
|
|
|
return [date rfc822DateString];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) davContentLength
|
|
|
|
{
|
2008-01-16 19:59:58 +01:00
|
|
|
NSString *content;
|
|
|
|
|
|
|
|
content = [record objectForKey: @"c_content"];
|
|
|
|
|
2007-12-12 16:57:40 +01:00
|
|
|
return [NSString stringWithFormat: @"%u",
|
2008-01-16 19:59:58 +01:00
|
|
|
[content lengthOfBytesUsingEncoding: NSISOLatin1StringEncoding]];
|
2007-12-12 16:57:40 +01:00
|
|
|
}
|
2006-06-15 21:34:10 +02:00
|
|
|
|
2007-04-27 20:58:48 +02:00
|
|
|
- (NSException *) davMoveToTargetObject: (id) _target
|
|
|
|
newName: (NSString *) _name
|
|
|
|
inContext: (id) _ctx
|
2006-06-15 21:34:10 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
Note: even for new objects we won't get a new name but a preinstantiated
|
|
|
|
object representing the new one.
|
|
|
|
*/
|
|
|
|
[self logWithFormat:
|
|
|
|
@"TODO: move not implemented:\n target: %@\n new name: %@",
|
|
|
|
_target, _name];
|
|
|
|
return [NSException exceptionWithHTTPStatus:405 /* not allowed */
|
|
|
|
reason:@"this object cannot be copied via WebDAV"];
|
|
|
|
}
|
|
|
|
|
2007-04-27 20:58:48 +02:00
|
|
|
- (NSException *) davCopyToTargetObject: (id)_target
|
|
|
|
newName: (NSString *) _name
|
|
|
|
inContext: (id) _ctx
|
2006-06-15 21:34:10 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
Note: even for new objects we won't get a new name but a preinstantiated
|
|
|
|
object representing the new one.
|
|
|
|
*/
|
|
|
|
[self logWithFormat:
|
|
|
|
@"TODO: copy not implemented:\n target: %@\n new name: %@",
|
|
|
|
_target, _name];
|
|
|
|
return [NSException exceptionWithHTTPStatus:405 /* not allowed */
|
|
|
|
reason:@"this object cannot be copied via WebDAV"];
|
|
|
|
}
|
|
|
|
|
2007-04-17 16:32:47 +02:00
|
|
|
/* acls */
|
|
|
|
|
2007-05-19 02:42:51 +02:00
|
|
|
- (NSArray *) aclUsers
|
2007-04-17 16:32:47 +02:00
|
|
|
{
|
2007-09-15 00:01:02 +02:00
|
|
|
return [container aclUsersForObjectAtPath: [self pathArrayToSOGoObject]];
|
2007-04-17 16:32:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *) aclsForUser: (NSString *) uid
|
|
|
|
{
|
2007-04-26 03:16:19 +02:00
|
|
|
NSMutableArray *acls;
|
2008-02-07 17:51:41 +01:00
|
|
|
NSArray *containerAcls;
|
2007-04-26 03:16:19 +02:00
|
|
|
|
|
|
|
acls = [NSMutableArray array];
|
2007-12-20 22:56:37 +01:00
|
|
|
/* this is unused... */
|
|
|
|
// ownAcls = [container aclsForUser: uid
|
|
|
|
// forObjectAtPath: [self pathArrayToSOGoObject]];
|
|
|
|
// [acls addObjectsFromArray: ownAcls];
|
2007-04-26 03:16:19 +02:00
|
|
|
containerAcls = [container aclsForUser: uid];
|
|
|
|
if ([containerAcls count] > 0)
|
|
|
|
{
|
2007-11-27 22:06:11 +01:00
|
|
|
[acls addObjectsFromArray: containerAcls];
|
|
|
|
if (isNew)
|
2007-06-01 22:59:47 +02:00
|
|
|
{
|
2007-11-27 22:06:11 +01:00
|
|
|
if ([containerAcls containsObject: SOGoRole_ObjectCreator])
|
2007-06-01 22:59:47 +02:00
|
|
|
[acls addObject: SOGoRole_ObjectEditor];
|
2007-11-27 22:06:11 +01:00
|
|
|
else
|
|
|
|
[acls removeObject: SOGoRole_ObjectEditor];
|
2007-06-01 22:59:47 +02:00
|
|
|
}
|
2007-04-26 03:16:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return acls;
|
2007-04-17 16:32:47 +02:00
|
|
|
}
|
|
|
|
|
2007-04-25 00:45:38 +02:00
|
|
|
- (void) setRoles: (NSArray *) roles
|
2007-04-17 16:32:47 +02:00
|
|
|
forUser: (NSString *) uid
|
|
|
|
{
|
|
|
|
return [container setRoles: roles
|
|
|
|
forUser: uid
|
|
|
|
forObjectAtPath: [self pathArrayToSoObject]];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) removeAclsForUsers: (NSArray *) users
|
|
|
|
{
|
|
|
|
return [container removeAclsForUsers: users
|
|
|
|
forObjectAtPath: [self pathArrayToSoObject]];
|
|
|
|
}
|
|
|
|
|
2007-05-22 20:35:17 +02:00
|
|
|
- (NSString *) defaultUserID
|
|
|
|
{
|
|
|
|
return @"<default>";
|
|
|
|
}
|
|
|
|
|
2006-06-15 21:34:10 +02:00
|
|
|
/* message type */
|
|
|
|
|
2007-04-27 20:58:48 +02:00
|
|
|
- (NSString *) outlookMessageClass
|
|
|
|
{
|
2006-06-15 21:34:10 +02:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* description */
|
|
|
|
|
2007-04-27 20:58:48 +02:00
|
|
|
- (void) appendAttributesToDescription: (NSMutableString *) _ms
|
|
|
|
{
|
2006-06-15 21:34:10 +02:00
|
|
|
[super appendAttributesToDescription:_ms];
|
|
|
|
|
|
|
|
[_ms appendFormat:@" ocs=%@", [self ocsPath]];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end /* SOGoContentObject */
|