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 <NGCards/NSDictionary+NGCards.h>
50 #import <UI/SOGoUI/SOGoACLAdvisory.h>
52 #import "SOGoPermissions.h"
54 #import "SOGoDAVAuthenticator.h"
55 #import "SOGoUserFolder.h"
57 #import "SOGoDAVRendererTypes.h"
59 #import "NSArray+Utilities.h"
60 #import "NSDictionary+Utilities.h"
61 #import "NSString+Utilities.h"
63 #import "SOGoObject.h"
65 @interface SOGoObject(Content)
66 - (NSString *)contentAsString;
69 @interface SoClassSecurityInfo (SOGoAcls)
71 + (id) defaultWebDAVPermissionsMap;
73 - (NSArray *) allPermissions;
74 - (NSArray *) allDAVPermissions;
75 - (NSArray *) DAVPermissionsForRole: (NSString *) role;
76 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles;
80 @implementation SoClassSecurityInfo (SOGoAcls)
82 + (id) defaultWebDAVPermissionsMap
84 return [NSDictionary dictionaryWithObjectsAndKeys:
85 @"read", SoPerm_AccessContentsInformation,
86 @"bind", SoPerm_AddDocumentsImagesAndFiles,
87 @"unbind", SoPerm_DeleteObjects,
88 @"write-acl", SoPerm_ChangePermissions,
89 @"write-content", SoPerm_ChangeImagesAndFiles,
90 @"read-free-busy", SOGoPerm_FreeBusyLookup,
94 - (NSArray *) allPermissions
96 return [defRoles allKeys];
99 - (NSArray *) allDAVPermissions
101 NSEnumerator *allPermissions;
102 NSMutableArray *davPermissions;
103 NSDictionary *davPermissionsMap;
104 NSString *sopePermission, *davPermission;
106 davPermissions = [NSMutableArray array];
108 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
109 allPermissions = [[self allPermissions] objectEnumerator];
110 sopePermission = [allPermissions nextObject];
111 while (sopePermission)
113 davPermission = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
114 if (davPermission && ![davPermissions containsObject: davPermission])
115 [davPermissions addObject: davPermission];
116 sopePermission = [allPermissions nextObject];
119 return davPermissions;
122 - (NSArray *) DAVPermissionsForRole: (NSString *) role
124 return [self DAVPermissionsForRoles: [NSArray arrayWithObject: role]];
127 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles
129 NSEnumerator *allPermissions;
130 NSMutableArray *davPermissions;
131 NSDictionary *davPermissionsMap;
132 NSString *sopePermission, *davPermission;
134 davPermissions = [NSMutableArray array];
136 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
137 allPermissions = [[self allPermissions] objectEnumerator];
138 sopePermission = [allPermissions nextObject];
139 while (sopePermission)
141 if ([[defRoles objectForCaseInsensitiveKey: sopePermission]
142 firstObjectCommonWithArray: roles])
145 = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
147 && ![davPermissions containsObject: davPermission])
148 [davPermissions addObject: davPermission];
150 sopePermission = [allPermissions nextObject];
153 return davPermissions;
158 @implementation SOGoObject
160 static BOOL kontactGroupDAV = YES;
168 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
171 [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
173 // SoClass security declarations
175 // require View permission to access the root (bound to authenticated ...)
176 // [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View];
178 // to allow public access to all contained objects (subkeys)
179 // [[self soClassSecurityInfo] setDefaultAccess: @"allow"];
181 // /* require Authenticated role for View and WebDAV */
182 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
183 // asDefaultForPermission: SoPerm_View];
184 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
185 // asDefaultForPermission: SoPerm_WebDAVAccess];
188 + (NSString *) globallyUniqueObjectId
191 4C08AE1A-A808-11D8-AC5A-000393BBAFF6
192 SOGo-Web-28273-18283-288182
193 printf( "%x", *(int *) &f);
196 static int sequence = 0;
197 static float rndm = 0;
201 { /* break if we fork ;-) */
206 f = [[NSDate date] timeIntervalSince1970];
208 return [NSString stringWithFormat:@"%0X-%0X-%0X-%0X",
209 pid, (int) f, sequence++, random];
212 - (NSString *) globallyUniqueObjectId
214 return [[self class] globallyUniqueObjectId];
217 + (void) _fillDictionary: (NSMutableDictionary *) dictionary
218 withDAVMethods: (NSString *) firstMethod, ...
221 NSString *aclMethodName;
222 NSString *methodName;
225 va_start (ap, firstMethod);
226 aclMethodName = firstMethod;
227 while (aclMethodName)
229 methodName = [aclMethodName davMethodToObjC];
230 methodSel = NSSelectorFromString (methodName);
231 if (methodSel && [self instancesRespondToSelector: methodSel])
232 [dictionary setObject: methodName
233 forKey: [NSString stringWithFormat: @"{DAV:}%@",
236 NSLog(@"************ method '%@' is still unimplemented!",
238 aclMethodName = va_arg (ap, NSString *);
244 + (NSDictionary *) defaultWebDAVAttributeMap
246 static NSMutableDictionary *map = nil;
250 map = [NSMutableDictionary
251 dictionaryWithDictionary: [super defaultWebDAVAttributeMap]];
253 [self _fillDictionary: map
254 withDAVMethods: @"owner", @"group", @"supported-privilege-set",
255 @"current-user-privilege-set", @"acl", @"acl-restrictions",
256 @"inherited-acl-set", @"principal-collection-set", nil];
264 + (id) objectWithName: (NSString *)_name inContainer:(id)_container
268 object = [[self alloc] initWithName: _name inContainer: _container];
269 [object autorelease];
274 /* DAV ACL properties */
275 - (NSString *) davOwner
277 return [NSString stringWithFormat: @"%@users/%@",
278 [self rootURLInContext: context],
279 [self ownerInContext: nil]];
282 - (NSString *) davAclRestrictions
284 NSMutableString *restrictions;
286 restrictions = [NSMutableString string];
287 [restrictions appendString: @"<D:grant-only/>"];
288 [restrictions appendString: @"<D:no-invert/>"];
293 - (SOGoDAVSet *) davPrincipalCollectionSet
297 usersUrl = [NSString stringWithFormat: @"%@users",
298 [self rootURLInContext: context]];
300 return [SOGoDAVSet davSetWithArray: [NSArray arrayWithObject: usersUrl]
301 ofValuesTaggedAs: @"D:href"];
304 - (SOGoDAVSet *) davCurrentUserPrivilegeSet
306 SOGoDAVAuthenticator *sAuth;
309 SoClassSecurityInfo *sInfo;
310 NSArray *davPermissions;
312 sAuth = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
313 user = [sAuth userInContext: context];
314 roles = [user rolesForObject: self inContext: context];
315 sInfo = [[self class] soClassSecurityInfo];
318 = [[sInfo DAVPermissionsForRoles: roles] stringsWithFormat: @"<D:%@/>"];
320 return [SOGoDAVSet davSetWithArray: davPermissions
321 ofValuesTaggedAs: @"D:privilege"];
324 - (SOGoDAVSet *) davSupportedPrivilegeSet
326 SoClassSecurityInfo *sInfo;
327 NSArray *allPermissions;
329 sInfo = [[self class] soClassSecurityInfo];
331 allPermissions = [[sInfo allDAVPermissions] stringsWithFormat: @"<D:%@/>"];
333 return [SOGoDAVSet davSetWithArray: allPermissions
334 ofValuesTaggedAs: @"D:privilege"];
337 - (NSArray *) _davAcesFromAclsDictionary: (NSDictionary *) aclsDictionary
341 NSMutableString *currentAce;
342 NSMutableArray *davAces;
343 NSString *currentKey, *principal;
344 SOGoDAVSet *privilegesDS;
346 davAces = [NSMutableArray array];
347 keys = [[aclsDictionary allKeys] objectEnumerator];
348 currentKey = [keys nextObject];
351 currentAce = [NSMutableString string];
352 if ([currentKey hasPrefix: @":"])
354 appendFormat: @"<D:principal><D:property><D:%@/></D:property></D:principal>",
355 [currentKey substringFromIndex: 1]];
358 principal = [NSString stringWithFormat: @"%@users/%@",
359 [self rootURLInContext: context],
362 appendFormat: @"<D:principal><D:href>%@</D:href></D:principal>",
366 privileges = [[aclsDictionary objectForKey: currentKey]
367 stringsWithFormat: @"<D:%@/>"];
368 privilegesDS = [SOGoDAVSet davSetWithArray: privileges
369 ofValuesTaggedAs: @"privilege"];
370 [currentAce appendString: [privilegesDS stringForTag: @"{DAV:}grant"
372 inContext: nil prefixes: nil]];
373 [davAces addObject: currentAce];
374 currentKey = [keys nextObject];
380 - (void) _appendRolesForPseudoPrincipals: (NSMutableDictionary *) aclsDictionary
381 withClassSecurityInfo: (SoClassSecurityInfo *) sInfo
385 perms = [sInfo DAVPermissionsForRole: SoRole_Owner];
387 [aclsDictionary setObject: perms forKey: @":owner"];
388 perms = [sInfo DAVPermissionsForRole: SoRole_Authenticated];
390 [aclsDictionary setObject: perms forKey: @":authenticated"];
391 perms = [sInfo DAVPermissionsForRole: SoRole_Anonymous];
393 [aclsDictionary setObject: perms forKey: @":unauthenticated"];
396 - (SOGoDAVSet *) davAcl
400 NSMutableDictionary *aclsDictionary;
401 NSString *currentUID;
402 SoClassSecurityInfo *sInfo;
404 aclsDictionary = [NSMutableDictionary dictionary];
405 uids = [[self aclUsers] objectEnumerator];
406 sInfo = [[self class] soClassSecurityInfo];
408 currentUID = [uids nextObject];
411 roles = [self aclsForUser: currentUID];
412 [aclsDictionary setObject: [sInfo DAVPermissionsForRoles: roles]
414 currentUID = [uids nextObject];
416 [self _appendRolesForPseudoPrincipals: aclsDictionary
417 withClassSecurityInfo: sInfo];
419 return [SOGoDAVSet davSetWithArray:
420 [self _davAcesFromAclsDictionary: aclsDictionary]
421 ofValuesTaggedAs: @"D:ace"];
424 /* end of properties */
426 - (BOOL) doesRetainContainer
433 if ((self = [super init]))
436 nameInContainer = nil;
444 - (id) initWithName: (NSString *) _name
445 inContainer: (id) _container
447 if ((self = [self init]))
449 context = [[WOApplication application] context];
451 nameInContainer = [_name copy];
452 container = _container;
453 if ([self doesRetainContainer])
455 owner = [self ownerInContext: context];
467 if ([self doesRetainContainer])
469 [nameInContainer release];
475 - (NSString *) nameInContainer
477 return nameInContainer;
485 - (NSArray *) pathArrayToSOGoObject
487 NSMutableArray *realPathArray;
488 NSString *objectName;
489 NSArray *objectDescription;
492 = [NSMutableArray arrayWithArray: [self pathArrayToSoObject]];
493 if ([realPathArray count] > 2)
495 objectName = [realPathArray objectAtIndex: 2];
496 if ([objectName isKindOfClass: [NSString class]])
498 objectDescription = [objectName componentsSeparatedByString: @"_"];
499 if ([objectDescription count] > 1)
501 [realPathArray replaceObjectAtIndex: 0
502 withObject: [objectDescription objectAtIndex: 0]];
503 [realPathArray replaceObjectAtIndex: 2
504 withObject: [objectDescription objectAtIndex: 1]];
509 return realPathArray;
514 - (void) setOwner: (NSString *) newOwner
516 ASSIGN (owner, newOwner);
519 - (NSString *) ownerInContext: (id) localContext
522 owner = [container ownerInContext: context];
529 - (NSArray *) fetchSubfolders
535 if ((names = [self toManyRelationshipKeys]) == nil)
538 count = [names count];
539 ma = [NSMutableArray arrayWithCapacity:count + 1];
540 for (i = 0; i < count; i++) {
543 folder = [self lookupName: [names objectAtIndex:i]
548 if ([folder isKindOfClass:[NSException class]])
551 [ma addObject:folder];
556 - (id) lookupName: (NSString *) lookupName
557 inContext: (id) localContext
558 acquire: (BOOL) acquire
562 obj = [[self soClass] lookupKey: lookupName inContext: localContext];
564 [obj bindToObject: self inContext: localContext];
569 /* looking up shared objects */
571 - (SOGoUserFolder *) lookupUserFolder
573 if (![container respondsToSelector:_cmd])
576 return [container lookupUserFolder];
579 - (SOGoGroupsFolder *) lookupGroupsFolder
581 return [[self lookupUserFolder] lookupGroupsFolder];
586 if ([self doesRetainContainer])
593 - (NSException *) delete
595 return [NSException exceptionWithHTTPStatus: 501 /* not implemented */
596 reason: @"delete not yet implemented, sorry ..."];
601 - (id) valueForUndefinedKey: (NSString *) _key
608 - (NSString *) davDisplayName
610 return [self nameInContainer];
615 - (id) DELETEAction: (id) _ctx
619 if ((error = [self delete]) != nil)
622 /* Note: returning 'nil' breaks in SoObjectRequestHandler */
623 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
626 - (NSString *) davContentType
628 return @"text/plain";
631 - (WOResponse *) _webDAVResponse: (WOContext *) localContext
633 WOResponse *response;
634 NSString *contentType;
637 response = [localContext response];
638 contentType = [NSString stringWithFormat: @"%@; charset=utf8",
639 [self davContentType]];
640 [response setHeader: contentType forKey: @"content-type"];
641 [response appendContentString: [self contentAsString]];
642 etag = [self davEntityTag];
644 [response setHeader: etag forKey: @"etag"];
649 - (id) GETAction: (id) localContext
651 // TODO: I guess this should really be done by SOPE (redirect to
658 request = [localContext request];
659 if ([request isSoWebDAVRequest])
661 if ([self respondsToSelector: @selector (contentAsString)])
663 error = [self matchesRequestConditionInContext: localContext];
667 value = [self _webDAVResponse: localContext];
670 value = [NSException exceptionWithHTTPStatus: 501 /* not implemented */
671 reason: @"no WebDAV GET support?!"];
675 value = [localContext response];
676 uri = [[request uri] composeURLWithAction: @"view"
677 parameters: [request formValues]
679 [value setStatus: 302 /* moved */];
680 [value setHeader: uri forKey: @"location"];
688 - (NSArray *)parseETagList:(NSString *)_c {
693 if ([_c length] == 0)
695 if ([_c isEqualToString:@"*"])
698 etags = [_c componentsSeparatedByString:@","];
699 count = [etags count];
700 ma = [NSMutableArray arrayWithCapacity:count];
701 for (i = 0; i < count; i++) {
704 etag = [[etags objectAtIndex:i] stringByTrimmingSpaces];
705 #if 0 /* this is non-sense, right? */
706 if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""])
707 etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)];
710 if (etag != nil) [ma addObject:etag];
715 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
717 Only run the request if one of the etags matches the resource etag,
718 usually used to ensure consistent PUTs.
723 if ([_c isEqualToString:@"*"])
724 /* to ensure that the resource exists! */
727 if ((etags = [self parseETagList:_c]) == nil)
729 if ([etags count] == 0) /* no etags to check for? */
732 etag = [self davEntityTag];
733 if ([etag length] == 0) /* has no etag, ignore */
736 if ([etags containsObject:etag]) {
737 [self debugWithFormat:@"etag '%@' matches: %@", etag,
738 [etags componentsJoinedByString:@","]];
739 return nil; /* one etag matches, so continue with request */
742 /* hack for Kontact 3.4 */
744 if (kontactGroupDAV) {
745 WEClientCapabilities *cc;
747 cc = [[(WOContext *)_ctx request] clientCapabilities];
748 if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
749 if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
751 @"WARNING: applying Kontact 3.4 GroupDAV hack"
752 @" - etag check is disabled!"
753 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
759 // TODO: we might want to return the davEntityTag in the response
760 [self debugWithFormat:@"etag '%@' does not match: %@", etag,
761 [etags componentsJoinedByString:@","]];
762 return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
763 reason:@"Precondition Failed"];
766 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
768 If one of the etags is still the same, we can ignore the request.
770 Can be used for PUT to ensure that the object does not exist in the store
771 and for GET to retrieve the content only if if the etag changed.
774 if (![_c isEqualToString:@"*"] &&
775 [[[_ctx request] method] isEqualToString:@"GET"]) {
779 if ((etags = [self parseETagList:_c]) == nil)
781 if ([etags count] == 0) /* no etags to check for? */
784 etag = [self davEntityTag];
785 if ([etag length] == 0) /* has no etag, ignore */
788 if ([etags containsObject:etag]) {
789 [self debugWithFormat:@"etag '%@' matches: %@", etag,
790 [etags componentsJoinedByString:@","]];
791 /* one etag matches, so stop the request */
792 return [NSException exceptionWithHTTPStatus:304 /* Not Modified */
793 reason:@"object was not modified"];
800 if ([_c isEqualToString:@"*"])
803 if ((a = [self parseETagList:_c]) == nil)
806 [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
811 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
816 if ((rq = [(WOContext *)_ctx request]) == nil)
817 return nil; /* be tolerant - no request, no condition */
819 if ((c = [rq headerForKey:@"if-match"]) != nil) {
820 if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
823 if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
824 if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
833 - (NSArray *) aclUsers
835 [self subclassResponsibility: _cmd];
840 - (NSArray *) aclsForUser: (NSString *) uid
842 [self subclassResponsibility: _cmd];
847 - (void) setRoles: (NSArray *) roles
848 forUser: (NSString *) uid
850 [self subclassResponsibility: _cmd];
853 - (void) removeAclsForUsers: (NSArray *) users
855 [self subclassResponsibility: _cmd];
858 - (NSString *) defaultUserID
860 [self subclassResponsibility: _cmd];
865 - (void) sendACLAdvisoryTemplate: (NSString *) template
866 toUser: (NSString *) uid
868 NSString *language, *pageName;
870 SOGoACLAdvisory *page;
873 user = [SOGoUser userWithLogin: uid roles: nil];
874 language = [user language];
875 pageName = [NSString stringWithFormat: @"SOGoACL%@%@Advisory",
878 app = [WOApplication application];
879 page = [app pageWithName: pageName inContext: context];
880 [page setACLObject: self];
881 [page setRecipientUID: uid];
885 - (void) sendACLAdditionAdvisoryToUser: (NSString *) uid
887 return [self sendACLAdvisoryTemplate: @"Addition"
891 - (void) sendACLRemovalAdvisoryToUser: (NSString *) uid
893 return [self sendACLAdvisoryTemplate: @"Removal"
897 - (NSURL *) _urlPreferringParticle: (NSString *) expected
898 overThisOne: (NSString *) possible
900 NSURL *serverURL, *url;
901 NSMutableArray *path;
902 NSString *baseURL, *urlMethod;
904 serverURL = [context serverURL];
905 baseURL = [[self baseURLInContext: context] stringByUnescapingURL];
906 path = [NSMutableArray arrayWithArray: [baseURL componentsSeparatedByString:
908 if ([baseURL hasPrefix: @"http"])
910 [path removeObjectAtIndex: 1];
911 [path removeObjectAtIndex: 0];
912 [path replaceObjectAtIndex: 0 withObject: @""];
914 urlMethod = [path objectAtIndex: 2];
915 if (![urlMethod isEqualToString: expected])
917 if ([urlMethod isEqualToString: possible])
918 [path replaceObjectAtIndex: 2 withObject: expected];
920 [path insertObject: expected atIndex: 2];
923 url = [[NSURL alloc] initWithScheme: [serverURL scheme]
924 host: [serverURL host]
925 path: [path componentsJoinedByString: @"/"]];
933 return [self _urlPreferringParticle: @"dav" overThisOne: @"so"];
938 return [self _urlPreferringParticle: @"so" overThisOne: @"dav"];
941 - (NSURL *) soURLToBaseContainerForUser: (NSString *) uid
943 NSURL *soURL, *baseSoURL;
945 NSMutableArray *newPath;
947 soURL = [self soURL];
948 basePath = [[soURL path] componentsSeparatedByString: @"/"];
950 = [NSMutableArray arrayWithArray:
951 [basePath subarrayWithRange: NSMakeRange (0, 5)]];
952 [newPath replaceObjectAtIndex: 3 withObject: uid];
954 baseSoURL = [[NSURL alloc] initWithScheme: [soURL scheme]
956 path: [newPath componentsJoinedByString: @"/"]];
957 [baseSoURL autorelease];
962 - (NSURL *) soURLToBaseContainerForCurrentUser
964 NSString *currentLogin;
966 currentLogin = [[context activeUser] login];
968 return [self soURLToBaseContainerForUser: currentLogin];
971 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
973 [self subclassResponsibility: _cmd];
978 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
980 [self subclassResponsibility: _cmd];
985 - (NSString *) labelForKey: (NSString *) key
987 NSString *userLanguage, *label;
990 NSDictionary *strings;
992 bundle = [NSBundle bundleForClass: [self class]];
994 bundle = [NSBundle mainBundle];
996 userLanguage = [[context activeUser] language];
997 paths = [bundle pathsForResourcesOfType: @"strings"
998 inDirectory: [NSString stringWithFormat: @"%@.lproj",
1000 forLocalization: userLanguage];
1001 if ([paths count] > 0)
1003 strings = [NSDictionary
1004 dictionaryFromStringsFile: [paths objectAtIndex: 0]];
1005 label = [strings objectForKey: key];
1017 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
1018 if (nameInContainer)
1019 [_ms appendFormat:@" name=%@", nameInContainer];
1021 [_ms appendFormat:@" container=0x%08X/%@",
1022 container, [container valueForKey:@"nameInContainer"]];
1025 - (NSString *)description {
1026 NSMutableString *ms;
1028 ms = [NSMutableString stringWithCapacity:64];
1029 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
1030 [self appendAttributesToDescription:ms];
1031 [ms appendString:@">"];
1036 - (NSString *) loggingPrefix
1038 return [NSString stringWithFormat:@"<0x%08X[%@]:%@>",
1039 self, NSStringFromClass([self class]),
1040 [self nameInContainer]];
1043 @end /* SOGoObject */