2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #if LIB_FOUNDATION_LIBRARY
23 #error SOGo will not work properly with libFoundation. \
24 Please use gnustep-base instead.
29 #import <Foundation/NSBundle.h>
30 #import <Foundation/NSClassDescription.h>
31 #import <Foundation/NSString.h>
32 #import <Foundation/NSUserDefaults.h>
33 #import <Foundation/NSURL.h>
34 #import <Foundation/NSValue.h>
36 #import <NGObjWeb/SoClass.h>
37 #import <NGObjWeb/SoClassSecurityInfo.h>
38 #import <NGObjWeb/SoObject+SoDAV.h>
39 #import <NGObjWeb/WEClientCapabilities.h>
40 #import <NGObjWeb/WOApplication.h>
41 #import <NGObjWeb/WOContext.h>
42 #import <NGObjWeb/WOResourceManager.h>
43 #import <NGObjWeb/WOResponse.h>
44 #import <NGObjWeb/WORequest.h>
45 #import <NGObjWeb/WORequest+So.h>
46 #import <NGObjWeb/NSException+HTTP.h>
47 #import <NGExtensions/NSObject+Logs.h>
48 #import <NGExtensions/NSString+misc.h>
49 #import <DOM/DOMProtocols.h>
50 #import <NGCards/NSDictionary+NGCards.h>
51 #import <UI/SOGoUI/SOGoACLAdvisory.h>
53 #import "SOGoPermissions.h"
55 #import "SOGoDAVAuthenticator.h"
56 #import "SOGoUserFolder.h"
58 #import "SOGoDAVRendererTypes.h"
60 #import "NSArray+Utilities.h"
61 #import "NSCalendarDate+SOGo.h"
62 #import "NSDictionary+Utilities.h"
63 #import "NSString+Utilities.h"
65 #import "SOGoObject.h"
67 @interface SOGoObject(Content)
68 - (NSString *)contentAsString;
71 @interface SoClassSecurityInfo (SOGoAcls)
73 + (id) defaultWebDAVPermissionsMap;
75 - (NSArray *) allPermissions;
76 - (NSArray *) allDAVPermissions;
77 - (NSArray *) DAVPermissionsForRole: (NSString *) role;
78 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles;
82 @implementation SoClassSecurityInfo (SOGoAcls)
84 + (id) defaultWebDAVPermissionsMap
86 return [NSDictionary dictionaryWithObjectsAndKeys:
87 @"read", SoPerm_AccessContentsInformation,
88 @"bind", SoPerm_AddDocumentsImagesAndFiles,
89 @"unbind", SoPerm_DeleteObjects,
90 @"write-acl", SoPerm_ChangePermissions,
91 @"write-content", SoPerm_ChangeImagesAndFiles,
92 @"read-free-busy", SOGoPerm_FreeBusyLookup,
96 - (NSArray *) allPermissions
98 return [defRoles allKeys];
101 - (NSArray *) allDAVPermissions
103 NSEnumerator *allPermissions;
104 NSMutableArray *davPermissions;
105 NSDictionary *davPermissionsMap;
106 NSString *sopePermission, *davPermission;
108 davPermissions = [NSMutableArray array];
110 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
111 allPermissions = [[self allPermissions] objectEnumerator];
112 sopePermission = [allPermissions nextObject];
113 while (sopePermission)
115 davPermission = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
116 if (davPermission && ![davPermissions containsObject: davPermission])
117 [davPermissions addObject: davPermission];
118 sopePermission = [allPermissions nextObject];
121 return davPermissions;
124 - (NSArray *) DAVPermissionsForRole: (NSString *) role
126 return [self DAVPermissionsForRoles: [NSArray arrayWithObject: role]];
129 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles
131 NSEnumerator *allPermissions;
132 NSMutableArray *davPermissions;
133 NSDictionary *davPermissionsMap;
134 NSString *sopePermission, *davPermission;
136 davPermissions = [NSMutableArray array];
138 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
139 allPermissions = [[self allPermissions] objectEnumerator];
140 sopePermission = [allPermissions nextObject];
141 while (sopePermission)
143 if ([[defRoles objectForCaseInsensitiveKey: sopePermission]
144 firstObjectCommonWithArray: roles])
147 = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
149 && ![davPermissions containsObject: davPermission])
150 [davPermissions addObject: davPermission];
152 sopePermission = [allPermissions nextObject];
155 return davPermissions;
160 @implementation SOGoObject
162 static BOOL kontactGroupDAV = YES;
170 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
173 [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
175 // SoClass security declarations
177 // require View permission to access the root (bound to authenticated ...)
178 // [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View];
180 // to allow public access to all contained objects (subkeys)
181 // [[self soClassSecurityInfo] setDefaultAccess: @"allow"];
183 // /* require Authenticated role for View and WebDAV */
184 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
185 // asDefaultForPermission: SoPerm_View];
186 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
187 // asDefaultForPermission: SoPerm_WebDAVAccess];
190 + (NSString *) globallyUniqueObjectId
193 4C08AE1A-A808-11D8-AC5A-000393BBAFF6
194 SOGo-Web-28273-18283-288182
195 printf( "%x", *(int *) &f);
198 static int sequence = 0;
199 static float rndm = 0;
203 { /* break if we fork ;-) */
208 f = [[NSDate date] timeIntervalSince1970];
210 return [NSString stringWithFormat:@"%0X-%0X-%0X-%0X",
211 pid, (int) f, sequence++, random];
214 - (NSString *) globallyUniqueObjectId
216 return [[self class] globallyUniqueObjectId];
219 + (void) _fillDictionary: (NSMutableDictionary *) dictionary
220 withDAVMethods: (NSString *) firstMethod, ...
223 NSString *aclMethodName;
224 NSString *methodName;
227 va_start (ap, firstMethod);
228 aclMethodName = firstMethod;
229 while (aclMethodName)
231 methodName = [aclMethodName davMethodToObjC];
232 methodSel = NSSelectorFromString (methodName);
233 if (methodSel && [self instancesRespondToSelector: methodSel])
234 [dictionary setObject: methodName
235 forKey: [NSString stringWithFormat: @"{DAV:}%@",
238 NSLog(@"************ method '%@' is still unimplemented!",
240 aclMethodName = va_arg (ap, NSString *);
246 + (NSDictionary *) defaultWebDAVAttributeMap
248 static NSMutableDictionary *map = nil;
252 map = [NSMutableDictionary
253 dictionaryWithDictionary: [super defaultWebDAVAttributeMap]];
255 [self _fillDictionary: map
256 withDAVMethods: @"owner", @"group", @"supported-privilege-set",
257 @"current-user-privilege-set", @"acl", @"acl-restrictions",
258 @"inherited-acl-set", @"principal-collection-set", nil];
266 + (id) objectWithName: (NSString *)_name inContainer:(id)_container
270 object = [[self alloc] initWithName: _name inContainer: _container];
271 [object autorelease];
276 /* DAV ACL properties */
277 - (NSString *) davOwner
279 return [NSString stringWithFormat: @"%@users/%@",
280 [self rootURLInContext: context],
281 [self ownerInContext: nil]];
284 - (NSString *) davAclRestrictions
286 NSMutableString *restrictions;
288 restrictions = [NSMutableString string];
289 [restrictions appendString: @"<D:grant-only/>"];
290 [restrictions appendString: @"<D:no-invert/>"];
295 - (SOGoDAVSet *) davPrincipalCollectionSet
299 usersUrl = [NSString stringWithFormat: @"%@users",
300 [self rootURLInContext: context]];
302 return [SOGoDAVSet davSetWithArray: [NSArray arrayWithObject: usersUrl]
303 ofValuesTaggedAs: @"D:href"];
306 - (SOGoDAVSet *) davCurrentUserPrivilegeSet
308 SOGoDAVAuthenticator *sAuth;
311 SoClassSecurityInfo *sInfo;
312 NSArray *davPermissions;
314 sAuth = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
315 user = [sAuth userInContext: context];
316 roles = [user rolesForObject: self inContext: context];
317 sInfo = [[self class] soClassSecurityInfo];
320 = [[sInfo DAVPermissionsForRoles: roles] stringsWithFormat: @"<D:%@/>"];
322 return [SOGoDAVSet davSetWithArray: davPermissions
323 ofValuesTaggedAs: @"D:privilege"];
326 - (SOGoDAVSet *) davSupportedPrivilegeSet
328 SoClassSecurityInfo *sInfo;
329 NSArray *allPermissions;
331 sInfo = [[self class] soClassSecurityInfo];
333 allPermissions = [[sInfo allDAVPermissions] stringsWithFormat: @"<D:%@/>"];
335 return [SOGoDAVSet davSetWithArray: allPermissions
336 ofValuesTaggedAs: @"D:privilege"];
339 - (NSArray *) _davAcesFromAclsDictionary: (NSDictionary *) aclsDictionary
343 NSMutableString *currentAce;
344 NSMutableArray *davAces;
345 NSString *currentKey, *principal;
346 SOGoDAVSet *privilegesDS;
348 davAces = [NSMutableArray array];
349 keys = [[aclsDictionary allKeys] objectEnumerator];
350 currentKey = [keys nextObject];
353 currentAce = [NSMutableString string];
354 if ([currentKey hasPrefix: @":"])
356 appendFormat: @"<D:principal><D:property><D:%@/></D:property></D:principal>",
357 [currentKey substringFromIndex: 1]];
360 principal = [NSString stringWithFormat: @"%@users/%@",
361 [self rootURLInContext: context],
364 appendFormat: @"<D:principal><D:href>%@</D:href></D:principal>",
368 privileges = [[aclsDictionary objectForKey: currentKey]
369 stringsWithFormat: @"<D:%@/>"];
370 privilegesDS = [SOGoDAVSet davSetWithArray: privileges
371 ofValuesTaggedAs: @"privilege"];
372 [currentAce appendString: [privilegesDS stringForTag: @"{DAV:}grant"
374 inContext: nil prefixes: nil]];
375 [davAces addObject: currentAce];
376 currentKey = [keys nextObject];
382 - (void) _appendRolesForPseudoPrincipals: (NSMutableDictionary *) aclsDictionary
383 withClassSecurityInfo: (SoClassSecurityInfo *) sInfo
387 perms = [sInfo DAVPermissionsForRole: SoRole_Owner];
389 [aclsDictionary setObject: perms forKey: @":owner"];
390 perms = [sInfo DAVPermissionsForRole: SoRole_Authenticated];
392 [aclsDictionary setObject: perms forKey: @":authenticated"];
393 perms = [sInfo DAVPermissionsForRole: SoRole_Anonymous];
395 [aclsDictionary setObject: perms forKey: @":unauthenticated"];
398 - (SOGoDAVSet *) davAcl
402 NSMutableDictionary *aclsDictionary;
403 NSString *currentUID;
404 SoClassSecurityInfo *sInfo;
406 aclsDictionary = [NSMutableDictionary dictionary];
407 uids = [[self aclUsers] objectEnumerator];
408 sInfo = [[self class] soClassSecurityInfo];
410 currentUID = [uids nextObject];
413 roles = [self aclsForUser: currentUID];
414 [aclsDictionary setObject: [sInfo DAVPermissionsForRoles: roles]
416 currentUID = [uids nextObject];
418 [self _appendRolesForPseudoPrincipals: aclsDictionary
419 withClassSecurityInfo: sInfo];
421 return [SOGoDAVSet davSetWithArray:
422 [self _davAcesFromAclsDictionary: aclsDictionary]
423 ofValuesTaggedAs: @"D:ace"];
426 /* end of properties */
428 - (BOOL) doesRetainContainer
435 if ((self = [super init]))
438 nameInContainer = nil;
446 - (id) initWithName: (NSString *) _name
447 inContainer: (id) _container
449 if ((self = [self init]))
451 context = [[WOApplication application] context];
453 nameInContainer = [_name copy];
454 container = _container;
455 if ([self doesRetainContainer])
457 owner = [self ownerInContext: context];
469 if ([self doesRetainContainer])
471 [nameInContainer release];
477 - (NSString *) nameInContainer
479 return nameInContainer;
487 - (NSArray *) pathArrayToSOGoObject
489 NSMutableArray *realPathArray;
490 NSString *objectName;
491 NSArray *objectDescription;
494 = [NSMutableArray arrayWithArray: [self pathArrayToSoObject]];
495 if ([realPathArray count] > 2)
497 objectName = [realPathArray objectAtIndex: 2];
498 if ([objectName isKindOfClass: [NSString class]])
500 objectDescription = [objectName componentsSeparatedByString: @"_"];
501 if ([objectDescription count] > 1)
503 [realPathArray replaceObjectAtIndex: 0
504 withObject: [objectDescription objectAtIndex: 0]];
505 [realPathArray replaceObjectAtIndex: 2
506 withObject: [objectDescription objectAtIndex: 1]];
511 return realPathArray;
516 - (void) setOwner: (NSString *) newOwner
518 ASSIGN (owner, newOwner);
521 - (NSString *) ownerInContext: (id) localContext
524 owner = [container ownerInContext: context];
531 - (NSArray *) fetchSubfolders
537 if ((names = [self toManyRelationshipKeys]) == nil)
540 count = [names count];
541 ma = [NSMutableArray arrayWithCapacity:count + 1];
542 for (i = 0; i < count; i++) {
545 folder = [self lookupName: [names objectAtIndex:i]
550 if ([folder isKindOfClass:[NSException class]])
553 [ma addObject:folder];
558 - (id) lookupName: (NSString *) lookupName
559 inContext: (id) localContext
560 acquire: (BOOL) acquire
564 obj = [[self soClass] lookupKey: lookupName inContext: localContext];
566 [obj bindToObject: self inContext: localContext];
571 /* looking up shared objects */
573 - (SOGoUserFolder *) lookupUserFolder
575 if (![container respondsToSelector:_cmd])
578 return [container lookupUserFolder];
581 - (SOGoGroupsFolder *) lookupGroupsFolder
583 return [[self lookupUserFolder] lookupGroupsFolder];
588 if ([self doesRetainContainer])
595 - (NSException *) delete
597 return [NSException exceptionWithHTTPStatus: 501 /* not implemented */
598 reason: @"delete not yet implemented, sorry ..."];
603 - (id) valueForUndefinedKey: (NSString *) _key
610 - (NSString *) davDisplayName
612 return [self nameInContainer];
617 - (id) DELETEAction: (id) _ctx
621 if ((error = [self delete]) != nil)
624 /* Note: returning 'nil' breaks in SoObjectRequestHandler */
625 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
628 - (NSString *) davContentType
630 return @"text/plain";
633 - (NSString *) davLastModified
635 return [[NSCalendarDate date] rfc822DateString];
638 - (WOResponse *) _webDAVResponse: (WOContext *) localContext
640 WOResponse *response;
641 NSString *contentType;
644 response = [localContext response];
645 contentType = [NSString stringWithFormat: @"%@; charset=utf8",
646 [self davContentType]];
647 [response setHeader: contentType forKey: @"content-type"];
648 [response appendContentString: [self contentAsString]];
649 etag = [self davEntityTag];
651 [response setHeader: etag forKey: @"etag"];
656 - (NSString *) _parseXMLCommand: (id <DOMDocument>) document
660 command = [[document firstChild] nodeName];
662 return [NSString stringWithFormat: @"%@:", command];
665 - (id) POSTAction: (id) localContext
668 NSString *cType, *command;
669 id <DOMDocument> document;
675 rq = [localContext request];
676 if ([rq isSoWebDAVRequest])
678 cType = [rq headerForKey: @"content-type"];
679 if ([cType isEqualToString: @"application/xml"])
681 document = [rq contentAsDOMDocument];
682 command = [[self _parseXMLCommand: document] davMethodToObjC];
683 commandSel = NSSelectorFromString (command);
684 if ([self respondsToSelector: commandSel])
685 obj = [self performSelector: commandSel withObject: localContext];
692 - (id) GETAction: (id) localContext
694 // TODO: I guess this should really be done by SOPE (redirect to
701 request = [localContext request];
702 if ([request isSoWebDAVRequest])
704 if ([self respondsToSelector: @selector (contentAsString)])
706 error = [self matchesRequestConditionInContext: localContext];
710 value = [self _webDAVResponse: localContext];
713 value = [NSException exceptionWithHTTPStatus: 501 /* not implemented */
714 reason: @"no WebDAV GET support?!"];
718 value = [localContext response];
719 uri = [[request uri] composeURLWithAction: @"view"
720 parameters: [request formValues]
722 [value setStatus: 302 /* moved */];
723 [value setHeader: uri forKey: @"location"];
731 - (NSArray *)parseETagList:(NSString *)_c {
736 if ([_c length] == 0)
738 if ([_c isEqualToString:@"*"])
741 etags = [_c componentsSeparatedByString:@","];
742 count = [etags count];
743 ma = [NSMutableArray arrayWithCapacity:count];
744 for (i = 0; i < count; i++) {
747 etag = [[etags objectAtIndex:i] stringByTrimmingSpaces];
748 #if 0 /* this is non-sense, right? */
749 if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""])
750 etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)];
753 if (etag != nil) [ma addObject:etag];
758 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
760 Only run the request if one of the etags matches the resource etag,
761 usually used to ensure consistent PUTs.
766 if ([_c isEqualToString:@"*"])
767 /* to ensure that the resource exists! */
770 if ((etags = [self parseETagList:_c]) == nil)
772 if ([etags count] == 0) /* no etags to check for? */
775 etag = [self davEntityTag];
776 if ([etag length] == 0) /* has no etag, ignore */
779 if ([etags containsObject:etag]) {
780 [self debugWithFormat:@"etag '%@' matches: %@", etag,
781 [etags componentsJoinedByString:@","]];
782 return nil; /* one etag matches, so continue with request */
785 /* hack for Kontact 3.4 */
787 if (kontactGroupDAV) {
788 WEClientCapabilities *cc;
790 cc = [[(WOContext *)_ctx request] clientCapabilities];
791 if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
792 if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
794 @"WARNING: applying Kontact 3.4 GroupDAV hack"
795 @" - etag check is disabled!"
796 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
802 // TODO: we might want to return the davEntityTag in the response
803 [self debugWithFormat:@"etag '%@' does not match: %@", etag,
804 [etags componentsJoinedByString:@","]];
805 return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
806 reason:@"Precondition Failed"];
809 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
811 If one of the etags is still the same, we can ignore the request.
813 Can be used for PUT to ensure that the object does not exist in the store
814 and for GET to retrieve the content only if if the etag changed.
817 if (![_c isEqualToString:@"*"] &&
818 [[[_ctx request] method] isEqualToString:@"GET"]) {
822 if ((etags = [self parseETagList:_c]) == nil)
824 if ([etags count] == 0) /* no etags to check for? */
827 etag = [self davEntityTag];
828 if ([etag length] == 0) /* has no etag, ignore */
831 if ([etags containsObject:etag]) {
832 [self debugWithFormat:@"etag '%@' matches: %@", etag,
833 [etags componentsJoinedByString:@","]];
834 /* one etag matches, so stop the request */
835 return [NSException exceptionWithHTTPStatus:304 /* Not Modified */
836 reason:@"object was not modified"];
843 if ([_c isEqualToString:@"*"])
846 if ((a = [self parseETagList:_c]) == nil)
849 [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
854 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
859 if ((rq = [(WOContext *)_ctx request]) == nil)
860 return nil; /* be tolerant - no request, no condition */
862 if ((c = [rq headerForKey:@"if-match"]) != nil) {
863 if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
866 if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
867 if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
876 /* roles required to obtain the "authorized subscriber" role */
877 - (NSArray *) subscriptionRoles
879 return [container subscriptionRoles];
882 - (NSArray *) aclUsers
884 [self subclassResponsibility: _cmd];
889 - (NSArray *) aclsForUser: (NSString *) uid
891 [self subclassResponsibility: _cmd];
896 - (void) setRoles: (NSArray *) roles
897 forUser: (NSString *) uid
899 [self subclassResponsibility: _cmd];
902 - (void) removeAclsForUsers: (NSArray *) users
904 [self subclassResponsibility: _cmd];
907 - (NSString *) defaultUserID
909 [self subclassResponsibility: _cmd];
914 - (void) sendACLAdvisoryTemplate: (NSString *) template
915 toUser: (NSString *) uid
917 NSString *language, *pageName;
919 SOGoACLAdvisory *page;
922 user = [SOGoUser userWithLogin: uid roles: nil];
923 language = [user language];
924 pageName = [NSString stringWithFormat: @"SOGoACL%@%@Advisory",
927 app = [WOApplication application];
928 page = [app pageWithName: pageName inContext: context];
929 [page setACLObject: self];
930 [page setRecipientUID: uid];
934 - (void) sendACLAdditionAdvisoryToUser: (NSString *) uid
936 return [self sendACLAdvisoryTemplate: @"Addition"
940 - (void) sendACLRemovalAdvisoryToUser: (NSString *) uid
942 return [self sendACLAdvisoryTemplate: @"Removal"
946 - (NSURL *) _urlPreferringParticle: (NSString *) expected
947 overThisOne: (NSString *) possible
949 NSURL *serverURL, *url;
950 NSMutableArray *path;
951 NSString *baseURL, *urlMethod;
953 serverURL = [context serverURL];
954 baseURL = [[self baseURLInContext: context] stringByUnescapingURL];
955 path = [NSMutableArray arrayWithArray: [baseURL componentsSeparatedByString:
957 if ([baseURL hasPrefix: @"http"])
959 [path removeObjectAtIndex: 1];
960 [path removeObjectAtIndex: 0];
961 [path replaceObjectAtIndex: 0 withObject: @""];
963 urlMethod = [path objectAtIndex: 2];
964 if (![urlMethod isEqualToString: expected])
966 if ([urlMethod isEqualToString: possible])
967 [path replaceObjectAtIndex: 2 withObject: expected];
969 [path insertObject: expected atIndex: 2];
972 url = [[NSURL alloc] initWithScheme: [serverURL scheme]
973 host: [serverURL host]
974 path: [path componentsJoinedByString: @"/"]];
982 return [self _urlPreferringParticle: @"dav" overThisOne: @"so"];
987 return [self _urlPreferringParticle: @"so" overThisOne: @"dav"];
990 - (NSURL *) soURLToBaseContainerForUser: (NSString *) uid
992 NSURL *soURL, *baseSoURL;
994 NSMutableArray *newPath;
996 soURL = [self soURL];
997 basePath = [[soURL path] componentsSeparatedByString: @"/"];
999 = [NSMutableArray arrayWithArray:
1000 [basePath subarrayWithRange: NSMakeRange (0, 5)]];
1001 [newPath replaceObjectAtIndex: 3 withObject: uid];
1003 baseSoURL = [[NSURL alloc] initWithScheme: [soURL scheme]
1005 path: [newPath componentsJoinedByString: @"/"]];
1006 [baseSoURL autorelease];
1011 - (NSURL *) soURLToBaseContainerForCurrentUser
1013 NSString *currentLogin;
1015 currentLogin = [[context activeUser] login];
1017 return [self soURLToBaseContainerForUser: currentLogin];
1020 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
1022 [self subclassResponsibility: _cmd];
1027 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
1029 [self subclassResponsibility: _cmd];
1034 - (NSString *) labelForKey: (NSString *) key
1036 NSString *userLanguage, *label;
1039 NSDictionary *strings;
1041 bundle = [NSBundle bundleForClass: [self class]];
1043 bundle = [NSBundle mainBundle];
1045 userLanguage = [[context activeUser] language];
1046 paths = [bundle pathsForResourcesOfType: @"strings"
1047 inDirectory: [NSString stringWithFormat: @"%@.lproj",
1049 forLocalization: userLanguage];
1050 if ([paths count] > 0)
1052 strings = [NSDictionary
1053 dictionaryFromStringsFile: [paths objectAtIndex: 0]];
1054 label = [strings objectForKey: key];
1066 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
1067 if (nameInContainer)
1068 [_ms appendFormat:@" name=%@", nameInContainer];
1070 [_ms appendFormat:@" container=0x%08X/%@",
1071 container, [container valueForKey:@"nameInContainer"]];
1074 - (NSString *)description {
1075 NSMutableString *ms;
1077 ms = [NSMutableString stringWithCapacity:64];
1078 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
1079 [self appendAttributesToDescription:ms];
1080 [ms appendString:@">"];
1085 - (NSString *) loggingPrefix
1087 return [NSString stringWithFormat:@"<0x%08X[%@]:%@>",
1088 self, NSStringFromClass([self class]),
1089 [self nameInContainer]];
1092 @end /* SOGoObject */