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 "NSDictionary+Utilities.h"
62 #import "NSString+Utilities.h"
64 #import "SOGoObject.h"
66 @interface SOGoObject(Content)
67 - (NSString *)contentAsString;
70 @interface SoClassSecurityInfo (SOGoAcls)
72 + (id) defaultWebDAVPermissionsMap;
74 - (NSArray *) allPermissions;
75 - (NSArray *) allDAVPermissions;
76 - (NSArray *) DAVPermissionsForRole: (NSString *) role;
77 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles;
81 @implementation SoClassSecurityInfo (SOGoAcls)
83 + (id) defaultWebDAVPermissionsMap
85 return [NSDictionary dictionaryWithObjectsAndKeys:
86 @"read", SoPerm_AccessContentsInformation,
87 @"bind", SoPerm_AddDocumentsImagesAndFiles,
88 @"unbind", SoPerm_DeleteObjects,
89 @"write-acl", SoPerm_ChangePermissions,
90 @"write-content", SoPerm_ChangeImagesAndFiles,
91 @"read-free-busy", SOGoPerm_FreeBusyLookup,
95 - (NSArray *) allPermissions
97 return [defRoles allKeys];
100 - (NSArray *) allDAVPermissions
102 NSEnumerator *allPermissions;
103 NSMutableArray *davPermissions;
104 NSDictionary *davPermissionsMap;
105 NSString *sopePermission, *davPermission;
107 davPermissions = [NSMutableArray array];
109 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
110 allPermissions = [[self allPermissions] objectEnumerator];
111 sopePermission = [allPermissions nextObject];
112 while (sopePermission)
114 davPermission = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
115 if (davPermission && ![davPermissions containsObject: davPermission])
116 [davPermissions addObject: davPermission];
117 sopePermission = [allPermissions nextObject];
120 return davPermissions;
123 - (NSArray *) DAVPermissionsForRole: (NSString *) role
125 return [self DAVPermissionsForRoles: [NSArray arrayWithObject: role]];
128 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles
130 NSEnumerator *allPermissions;
131 NSMutableArray *davPermissions;
132 NSDictionary *davPermissionsMap;
133 NSString *sopePermission, *davPermission;
135 davPermissions = [NSMutableArray array];
137 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
138 allPermissions = [[self allPermissions] objectEnumerator];
139 sopePermission = [allPermissions nextObject];
140 while (sopePermission)
142 if ([[defRoles objectForCaseInsensitiveKey: sopePermission]
143 firstObjectCommonWithArray: roles])
146 = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
148 && ![davPermissions containsObject: davPermission])
149 [davPermissions addObject: davPermission];
151 sopePermission = [allPermissions nextObject];
154 return davPermissions;
159 @implementation SOGoObject
161 static BOOL kontactGroupDAV = YES;
169 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
172 [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
174 // SoClass security declarations
176 // require View permission to access the root (bound to authenticated ...)
177 // [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View];
179 // to allow public access to all contained objects (subkeys)
180 // [[self soClassSecurityInfo] setDefaultAccess: @"allow"];
182 // /* require Authenticated role for View and WebDAV */
183 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
184 // asDefaultForPermission: SoPerm_View];
185 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
186 // asDefaultForPermission: SoPerm_WebDAVAccess];
189 + (NSString *) globallyUniqueObjectId
192 4C08AE1A-A808-11D8-AC5A-000393BBAFF6
193 SOGo-Web-28273-18283-288182
194 printf( "%x", *(int *) &f);
197 static int sequence = 0;
198 static float rndm = 0;
202 { /* break if we fork ;-) */
207 f = [[NSDate date] timeIntervalSince1970];
209 return [NSString stringWithFormat:@"%0X-%0X-%0X-%0X",
210 pid, (int) f, sequence++, random];
213 - (NSString *) globallyUniqueObjectId
215 return [[self class] globallyUniqueObjectId];
218 + (void) _fillDictionary: (NSMutableDictionary *) dictionary
219 withDAVMethods: (NSString *) firstMethod, ...
222 NSString *aclMethodName;
223 NSString *methodName;
226 va_start (ap, firstMethod);
227 aclMethodName = firstMethod;
228 while (aclMethodName)
230 methodName = [aclMethodName davMethodToObjC];
231 methodSel = NSSelectorFromString (methodName);
232 if (methodSel && [self instancesRespondToSelector: methodSel])
233 [dictionary setObject: methodName
234 forKey: [NSString stringWithFormat: @"{DAV:}%@",
237 NSLog(@"************ method '%@' is still unimplemented!",
239 aclMethodName = va_arg (ap, NSString *);
245 + (NSDictionary *) defaultWebDAVAttributeMap
247 static NSMutableDictionary *map = nil;
251 map = [NSMutableDictionary
252 dictionaryWithDictionary: [super defaultWebDAVAttributeMap]];
254 [self _fillDictionary: map
255 withDAVMethods: @"owner", @"group", @"supported-privilege-set",
256 @"current-user-privilege-set", @"acl", @"acl-restrictions",
257 @"inherited-acl-set", @"principal-collection-set", nil];
265 + (id) objectWithName: (NSString *)_name inContainer:(id)_container
269 object = [[self alloc] initWithName: _name inContainer: _container];
270 [object autorelease];
275 /* DAV ACL properties */
276 - (NSString *) davOwner
278 return [NSString stringWithFormat: @"%@users/%@",
279 [self rootURLInContext: context],
280 [self ownerInContext: nil]];
283 - (NSString *) davAclRestrictions
285 NSMutableString *restrictions;
287 restrictions = [NSMutableString string];
288 [restrictions appendString: @"<D:grant-only/>"];
289 [restrictions appendString: @"<D:no-invert/>"];
294 - (SOGoDAVSet *) davPrincipalCollectionSet
298 usersUrl = [NSString stringWithFormat: @"%@users",
299 [self rootURLInContext: context]];
301 return [SOGoDAVSet davSetWithArray: [NSArray arrayWithObject: usersUrl]
302 ofValuesTaggedAs: @"D:href"];
305 - (SOGoDAVSet *) davCurrentUserPrivilegeSet
307 SOGoDAVAuthenticator *sAuth;
310 SoClassSecurityInfo *sInfo;
311 NSArray *davPermissions;
313 sAuth = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
314 user = [sAuth userInContext: context];
315 roles = [user rolesForObject: self inContext: context];
316 sInfo = [[self class] soClassSecurityInfo];
319 = [[sInfo DAVPermissionsForRoles: roles] stringsWithFormat: @"<D:%@/>"];
321 return [SOGoDAVSet davSetWithArray: davPermissions
322 ofValuesTaggedAs: @"D:privilege"];
325 - (SOGoDAVSet *) davSupportedPrivilegeSet
327 SoClassSecurityInfo *sInfo;
328 NSArray *allPermissions;
330 sInfo = [[self class] soClassSecurityInfo];
332 allPermissions = [[sInfo allDAVPermissions] stringsWithFormat: @"<D:%@/>"];
334 return [SOGoDAVSet davSetWithArray: allPermissions
335 ofValuesTaggedAs: @"D:privilege"];
338 - (NSArray *) _davAcesFromAclsDictionary: (NSDictionary *) aclsDictionary
342 NSMutableString *currentAce;
343 NSMutableArray *davAces;
344 NSString *currentKey, *principal;
345 SOGoDAVSet *privilegesDS;
347 davAces = [NSMutableArray array];
348 keys = [[aclsDictionary allKeys] objectEnumerator];
349 currentKey = [keys nextObject];
352 currentAce = [NSMutableString string];
353 if ([currentKey hasPrefix: @":"])
355 appendFormat: @"<D:principal><D:property><D:%@/></D:property></D:principal>",
356 [currentKey substringFromIndex: 1]];
359 principal = [NSString stringWithFormat: @"%@users/%@",
360 [self rootURLInContext: context],
363 appendFormat: @"<D:principal><D:href>%@</D:href></D:principal>",
367 privileges = [[aclsDictionary objectForKey: currentKey]
368 stringsWithFormat: @"<D:%@/>"];
369 privilegesDS = [SOGoDAVSet davSetWithArray: privileges
370 ofValuesTaggedAs: @"privilege"];
371 [currentAce appendString: [privilegesDS stringForTag: @"{DAV:}grant"
373 inContext: nil prefixes: nil]];
374 [davAces addObject: currentAce];
375 currentKey = [keys nextObject];
381 - (void) _appendRolesForPseudoPrincipals: (NSMutableDictionary *) aclsDictionary
382 withClassSecurityInfo: (SoClassSecurityInfo *) sInfo
386 perms = [sInfo DAVPermissionsForRole: SoRole_Owner];
388 [aclsDictionary setObject: perms forKey: @":owner"];
389 perms = [sInfo DAVPermissionsForRole: SoRole_Authenticated];
391 [aclsDictionary setObject: perms forKey: @":authenticated"];
392 perms = [sInfo DAVPermissionsForRole: SoRole_Anonymous];
394 [aclsDictionary setObject: perms forKey: @":unauthenticated"];
397 - (SOGoDAVSet *) davAcl
401 NSMutableDictionary *aclsDictionary;
402 NSString *currentUID;
403 SoClassSecurityInfo *sInfo;
405 aclsDictionary = [NSMutableDictionary dictionary];
406 uids = [[self aclUsers] objectEnumerator];
407 sInfo = [[self class] soClassSecurityInfo];
409 currentUID = [uids nextObject];
412 roles = [self aclsForUser: currentUID];
413 [aclsDictionary setObject: [sInfo DAVPermissionsForRoles: roles]
415 currentUID = [uids nextObject];
417 [self _appendRolesForPseudoPrincipals: aclsDictionary
418 withClassSecurityInfo: sInfo];
420 return [SOGoDAVSet davSetWithArray:
421 [self _davAcesFromAclsDictionary: aclsDictionary]
422 ofValuesTaggedAs: @"D:ace"];
425 /* end of properties */
427 - (BOOL) doesRetainContainer
434 if ((self = [super init]))
437 nameInContainer = nil;
445 - (id) initWithName: (NSString *) _name
446 inContainer: (id) _container
448 if ((self = [self init]))
450 context = [[WOApplication application] context];
452 nameInContainer = [_name copy];
453 container = _container;
454 if ([self doesRetainContainer])
456 owner = [self ownerInContext: context];
468 if ([self doesRetainContainer])
470 [nameInContainer release];
476 - (NSString *) nameInContainer
478 return nameInContainer;
486 - (NSArray *) pathArrayToSOGoObject
488 NSMutableArray *realPathArray;
489 NSString *objectName;
490 NSArray *objectDescription;
493 = [NSMutableArray arrayWithArray: [self pathArrayToSoObject]];
494 if ([realPathArray count] > 2)
496 objectName = [realPathArray objectAtIndex: 2];
497 if ([objectName isKindOfClass: [NSString class]])
499 objectDescription = [objectName componentsSeparatedByString: @"_"];
500 if ([objectDescription count] > 1)
502 [realPathArray replaceObjectAtIndex: 0
503 withObject: [objectDescription objectAtIndex: 0]];
504 [realPathArray replaceObjectAtIndex: 2
505 withObject: [objectDescription objectAtIndex: 1]];
510 return realPathArray;
515 - (void) setOwner: (NSString *) newOwner
517 ASSIGN (owner, newOwner);
520 - (NSString *) ownerInContext: (id) localContext
523 owner = [container ownerInContext: context];
530 - (NSArray *) fetchSubfolders
536 if ((names = [self toManyRelationshipKeys]) == nil)
539 count = [names count];
540 ma = [NSMutableArray arrayWithCapacity:count + 1];
541 for (i = 0; i < count; i++) {
544 folder = [self lookupName: [names objectAtIndex:i]
549 if ([folder isKindOfClass:[NSException class]])
552 [ma addObject:folder];
557 - (id) lookupName: (NSString *) lookupName
558 inContext: (id) localContext
559 acquire: (BOOL) acquire
563 obj = [[self soClass] lookupKey: lookupName inContext: localContext];
565 [obj bindToObject: self inContext: localContext];
570 /* looking up shared objects */
572 - (SOGoUserFolder *) lookupUserFolder
574 if (![container respondsToSelector:_cmd])
577 return [container lookupUserFolder];
580 - (SOGoGroupsFolder *) lookupGroupsFolder
582 return [[self lookupUserFolder] lookupGroupsFolder];
587 if ([self doesRetainContainer])
594 - (NSException *) delete
596 return [NSException exceptionWithHTTPStatus: 501 /* not implemented */
597 reason: @"delete not yet implemented, sorry ..."];
602 - (id) valueForUndefinedKey: (NSString *) _key
609 - (NSString *) davDisplayName
611 return [self nameInContainer];
616 - (id) DELETEAction: (id) _ctx
620 if ((error = [self delete]) != nil)
623 /* Note: returning 'nil' breaks in SoObjectRequestHandler */
624 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
627 - (NSString *) davContentType
629 return @"text/plain";
632 - (WOResponse *) _webDAVResponse: (WOContext *) localContext
634 WOResponse *response;
635 NSString *contentType;
638 response = [localContext response];
639 contentType = [NSString stringWithFormat: @"%@; charset=utf8",
640 [self davContentType]];
641 [response setHeader: contentType forKey: @"content-type"];
642 [response appendContentString: [self contentAsString]];
643 etag = [self davEntityTag];
645 [response setHeader: etag forKey: @"etag"];
650 - (NSString *) _parseXMLCommand: (id <DOMDocument>) document
654 command = [[document firstChild] nodeName];
656 return [NSString stringWithFormat: @"%@:", command];
659 - (id) POSTAction: (id) localContext
662 NSString *cType, *command;
663 id <DOMDocument> document;
669 rq = [localContext request];
670 if ([rq isSoWebDAVRequest])
672 cType = [rq headerForKey: @"content-type"];
673 if ([cType isEqualToString: @"application/xml"])
675 document = [rq contentAsDOMDocument];
676 command = [[self _parseXMLCommand: document] davMethodToObjC];
677 commandSel = NSSelectorFromString (command);
678 if ([self respondsToSelector: commandSel])
679 obj = [self performSelector: commandSel withObject: localContext];
686 - (id) GETAction: (id) localContext
688 // TODO: I guess this should really be done by SOPE (redirect to
695 request = [localContext request];
696 if ([request isSoWebDAVRequest])
698 if ([self respondsToSelector: @selector (contentAsString)])
700 error = [self matchesRequestConditionInContext: localContext];
704 value = [self _webDAVResponse: localContext];
707 value = [NSException exceptionWithHTTPStatus: 501 /* not implemented */
708 reason: @"no WebDAV GET support?!"];
712 value = [localContext response];
713 uri = [[request uri] composeURLWithAction: @"view"
714 parameters: [request formValues]
716 [value setStatus: 302 /* moved */];
717 [value setHeader: uri forKey: @"location"];
725 - (NSArray *)parseETagList:(NSString *)_c {
730 if ([_c length] == 0)
732 if ([_c isEqualToString:@"*"])
735 etags = [_c componentsSeparatedByString:@","];
736 count = [etags count];
737 ma = [NSMutableArray arrayWithCapacity:count];
738 for (i = 0; i < count; i++) {
741 etag = [[etags objectAtIndex:i] stringByTrimmingSpaces];
742 #if 0 /* this is non-sense, right? */
743 if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""])
744 etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)];
747 if (etag != nil) [ma addObject:etag];
752 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
754 Only run the request if one of the etags matches the resource etag,
755 usually used to ensure consistent PUTs.
760 if ([_c isEqualToString:@"*"])
761 /* to ensure that the resource exists! */
764 if ((etags = [self parseETagList:_c]) == nil)
766 if ([etags count] == 0) /* no etags to check for? */
769 etag = [self davEntityTag];
770 if ([etag length] == 0) /* has no etag, ignore */
773 if ([etags containsObject:etag]) {
774 [self debugWithFormat:@"etag '%@' matches: %@", etag,
775 [etags componentsJoinedByString:@","]];
776 return nil; /* one etag matches, so continue with request */
779 /* hack for Kontact 3.4 */
781 if (kontactGroupDAV) {
782 WEClientCapabilities *cc;
784 cc = [[(WOContext *)_ctx request] clientCapabilities];
785 if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
786 if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
788 @"WARNING: applying Kontact 3.4 GroupDAV hack"
789 @" - etag check is disabled!"
790 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
796 // TODO: we might want to return the davEntityTag in the response
797 [self debugWithFormat:@"etag '%@' does not match: %@", etag,
798 [etags componentsJoinedByString:@","]];
799 return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
800 reason:@"Precondition Failed"];
803 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
805 If one of the etags is still the same, we can ignore the request.
807 Can be used for PUT to ensure that the object does not exist in the store
808 and for GET to retrieve the content only if if the etag changed.
811 if (![_c isEqualToString:@"*"] &&
812 [[[_ctx request] method] isEqualToString:@"GET"]) {
816 if ((etags = [self parseETagList:_c]) == nil)
818 if ([etags count] == 0) /* no etags to check for? */
821 etag = [self davEntityTag];
822 if ([etag length] == 0) /* has no etag, ignore */
825 if ([etags containsObject:etag]) {
826 [self debugWithFormat:@"etag '%@' matches: %@", etag,
827 [etags componentsJoinedByString:@","]];
828 /* one etag matches, so stop the request */
829 return [NSException exceptionWithHTTPStatus:304 /* Not Modified */
830 reason:@"object was not modified"];
837 if ([_c isEqualToString:@"*"])
840 if ((a = [self parseETagList:_c]) == nil)
843 [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
848 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
853 if ((rq = [(WOContext *)_ctx request]) == nil)
854 return nil; /* be tolerant - no request, no condition */
856 if ((c = [rq headerForKey:@"if-match"]) != nil) {
857 if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
860 if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
861 if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
870 /* roles required to obtain the "authorized subscriber" role */
871 - (NSArray *) subscriptionRoles
873 return [container subscriptionRoles];
876 - (NSArray *) aclUsers
878 [self subclassResponsibility: _cmd];
883 - (NSArray *) aclsForUser: (NSString *) uid
885 [self subclassResponsibility: _cmd];
890 - (void) setRoles: (NSArray *) roles
891 forUser: (NSString *) uid
893 [self subclassResponsibility: _cmd];
896 - (void) removeAclsForUsers: (NSArray *) users
898 [self subclassResponsibility: _cmd];
901 - (NSString *) defaultUserID
903 [self subclassResponsibility: _cmd];
908 - (void) sendACLAdvisoryTemplate: (NSString *) template
909 toUser: (NSString *) uid
911 NSString *language, *pageName;
913 SOGoACLAdvisory *page;
916 user = [SOGoUser userWithLogin: uid roles: nil];
917 language = [user language];
918 pageName = [NSString stringWithFormat: @"SOGoACL%@%@Advisory",
921 app = [WOApplication application];
922 page = [app pageWithName: pageName inContext: context];
923 [page setACLObject: self];
924 [page setRecipientUID: uid];
928 - (void) sendACLAdditionAdvisoryToUser: (NSString *) uid
930 return [self sendACLAdvisoryTemplate: @"Addition"
934 - (void) sendACLRemovalAdvisoryToUser: (NSString *) uid
936 return [self sendACLAdvisoryTemplate: @"Removal"
940 - (NSURL *) _urlPreferringParticle: (NSString *) expected
941 overThisOne: (NSString *) possible
943 NSURL *serverURL, *url;
944 NSMutableArray *path;
945 NSString *baseURL, *urlMethod;
947 serverURL = [context serverURL];
948 baseURL = [[self baseURLInContext: context] stringByUnescapingURL];
949 path = [NSMutableArray arrayWithArray: [baseURL componentsSeparatedByString:
951 if ([baseURL hasPrefix: @"http"])
953 [path removeObjectAtIndex: 1];
954 [path removeObjectAtIndex: 0];
955 [path replaceObjectAtIndex: 0 withObject: @""];
957 urlMethod = [path objectAtIndex: 2];
958 if (![urlMethod isEqualToString: expected])
960 if ([urlMethod isEqualToString: possible])
961 [path replaceObjectAtIndex: 2 withObject: expected];
963 [path insertObject: expected atIndex: 2];
966 url = [[NSURL alloc] initWithScheme: [serverURL scheme]
967 host: [serverURL host]
968 path: [path componentsJoinedByString: @"/"]];
976 return [self _urlPreferringParticle: @"dav" overThisOne: @"so"];
981 return [self _urlPreferringParticle: @"so" overThisOne: @"dav"];
984 - (NSURL *) soURLToBaseContainerForUser: (NSString *) uid
986 NSURL *soURL, *baseSoURL;
988 NSMutableArray *newPath;
990 soURL = [self soURL];
991 basePath = [[soURL path] componentsSeparatedByString: @"/"];
993 = [NSMutableArray arrayWithArray:
994 [basePath subarrayWithRange: NSMakeRange (0, 5)]];
995 [newPath replaceObjectAtIndex: 3 withObject: uid];
997 baseSoURL = [[NSURL alloc] initWithScheme: [soURL scheme]
999 path: [newPath componentsJoinedByString: @"/"]];
1000 [baseSoURL autorelease];
1005 - (NSURL *) soURLToBaseContainerForCurrentUser
1007 NSString *currentLogin;
1009 currentLogin = [[context activeUser] login];
1011 return [self soURLToBaseContainerForUser: currentLogin];
1014 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
1016 [self subclassResponsibility: _cmd];
1021 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
1023 [self subclassResponsibility: _cmd];
1028 - (NSString *) labelForKey: (NSString *) key
1030 NSString *userLanguage, *label;
1033 NSDictionary *strings;
1035 bundle = [NSBundle bundleForClass: [self class]];
1037 bundle = [NSBundle mainBundle];
1039 userLanguage = [[context activeUser] language];
1040 paths = [bundle pathsForResourcesOfType: @"strings"
1041 inDirectory: [NSString stringWithFormat: @"%@.lproj",
1043 forLocalization: userLanguage];
1044 if ([paths count] > 0)
1046 strings = [NSDictionary
1047 dictionaryFromStringsFile: [paths objectAtIndex: 0]];
1048 label = [strings objectForKey: key];
1060 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
1061 if (nameInContainer)
1062 [_ms appendFormat:@" name=%@", nameInContainer];
1064 [_ms appendFormat:@" container=0x%08X/%@",
1065 container, [container valueForKey:@"nameInContainer"]];
1068 - (NSString *)description {
1069 NSMutableString *ms;
1071 ms = [NSMutableString stringWithCapacity:64];
1072 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
1073 [self appendAttributesToDescription:ms];
1074 [ms appendString:@">"];
1079 - (NSString *) loggingPrefix
1081 return [NSString stringWithFormat:@"<0x%08X[%@]:%@>",
1082 self, NSStringFromClass([self class]),
1083 [self nameInContainer]];
1086 @end /* SOGoObject */