]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/SOGoObject.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1173 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / SOGo / SOGoObject.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #if LIB_FOUNDATION_LIBRARY
23 #error SOGo will not work properly with libFoundation. \
24        Please use gnustep-base instead.
25 #endif
26
27 #import <unistd.h>
28
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>
35
36 #import <NGObjWeb/SoClassSecurityInfo.h>
37 #import <NGObjWeb/SoObject+SoDAV.h>
38 #import <NGObjWeb/WEClientCapabilities.h>
39 #import <NGObjWeb/WOApplication.h>
40 #import <NGObjWeb/WOContext.h>
41 #import <NGObjWeb/WOResourceManager.h>
42 #import <NGObjWeb/WOResponse.h>
43 #import <NGObjWeb/WORequest.h>
44 #import <NGObjWeb/WORequest+So.h>
45 #import <NGObjWeb/NSException+HTTP.h>
46 #import <NGExtensions/NSObject+Logs.h>
47 #import <NGExtensions/NSString+misc.h>
48 #import <NGCards/NSDictionary+NGCards.h>
49 #import <UI/SOGoUI/SOGoACLAdvisory.h>
50
51 #import "SOGoPermissions.h"
52 #import "SOGoUser.h"
53 #import "SOGoDAVAuthenticator.h"
54 #import "SOGoUserFolder.h"
55
56 #import "SOGoDAVRendererTypes.h"
57
58 #import "NSArray+Utilities.h"
59 #import "NSDictionary+Utilities.h"
60 #import "NSString+Utilities.h"
61
62 #import "SOGoObject.h"
63
64 @interface SOGoObject(Content)
65 - (NSString *)contentAsString;
66 @end
67
68 @interface SoClassSecurityInfo (SOGoAcls)
69
70 + (id) defaultWebDAVPermissionsMap;
71
72 - (NSArray *) allPermissions;
73 - (NSArray *) allDAVPermissions;
74 - (NSArray *) DAVPermissionsForRole: (NSString *) role;
75 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles;
76
77 @end
78
79 @implementation SoClassSecurityInfo (SOGoAcls)
80
81 + (id) defaultWebDAVPermissionsMap
82 {
83   return [NSDictionary dictionaryWithObjectsAndKeys:
84                          @"read", SoPerm_AccessContentsInformation,
85                        @"bind", SoPerm_AddDocumentsImagesAndFiles,
86                        @"unbind", SoPerm_DeleteObjects,
87                        @"write-acl", SoPerm_ChangePermissions,
88                        @"write-content", SoPerm_ChangeImagesAndFiles,
89                        @"read-free-busy", SOGoPerm_FreeBusyLookup,
90                        NULL];
91 }
92
93 - (NSArray *) allPermissions
94 {
95   return [defRoles allKeys];
96 }
97
98 - (NSArray *) allDAVPermissions
99 {
100   NSEnumerator *allPermissions;
101   NSMutableArray *davPermissions;
102   NSDictionary *davPermissionsMap;
103   NSString *sopePermission, *davPermission;
104
105   davPermissions = [NSMutableArray array];
106
107   davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
108   allPermissions = [[self allPermissions] objectEnumerator];
109   sopePermission = [allPermissions nextObject];
110   while (sopePermission)
111     {
112       davPermission = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
113       if (davPermission && ![davPermissions containsObject: davPermission])
114         [davPermissions addObject: davPermission];
115       sopePermission = [allPermissions nextObject];
116     }
117
118   return davPermissions;
119 }
120
121 - (NSArray *) DAVPermissionsForRole: (NSString *) role
122 {
123   return [self DAVPermissionsForRoles: [NSArray arrayWithObject: role]];
124 }
125
126 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles
127 {
128   NSEnumerator *allPermissions;
129   NSMutableArray *davPermissions;
130   NSDictionary *davPermissionsMap;
131   NSString *sopePermission, *davPermission;
132
133   davPermissions = [NSMutableArray array];
134
135   davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
136   allPermissions = [[self allPermissions] objectEnumerator];
137   sopePermission = [allPermissions nextObject];
138   while (sopePermission)
139     {
140       if ([[defRoles objectForCaseInsensitiveKey: sopePermission]
141             firstObjectCommonWithArray: roles])
142         {
143           davPermission
144             = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
145           if (davPermission
146               && ![davPermissions containsObject: davPermission])
147             [davPermissions addObject: davPermission];
148         }
149       sopePermission = [allPermissions nextObject];
150     }
151
152   return davPermissions;
153 }
154
155 @end
156
157 @implementation SOGoObject
158
159 static BOOL kontactGroupDAV = YES;
160
161 + (int)version {
162   return 0;
163 }
164
165 + (void) initialize
166 {
167   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
168
169   kontactGroupDAV = 
170     [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
171
172 //   SoClass security declarations
173   
174 //   require View permission to access the root (bound to authenticated ...)
175 //   [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View];
176
177 //   to allow public access to all contained objects (subkeys)
178 //   [[self soClassSecurityInfo] setDefaultAccess: @"allow"];
179   
180 //   /* require Authenticated role for View and WebDAV */
181 //   [[self soClassSecurityInfo] declareRole: SoRole_Owner
182 //                               asDefaultForPermission: SoPerm_View];
183 //   [[self soClassSecurityInfo] declareRole: SoRole_Owner
184 //                               asDefaultForPermission: SoPerm_WebDAVAccess];
185 }
186
187 + (NSString *) globallyUniqueObjectId
188 {
189   /*
190     4C08AE1A-A808-11D8-AC5A-000393BBAFF6
191     SOGo-Web-28273-18283-288182
192     printf( "%x", *(int *) &f);
193   */
194   static int pid = 0;
195   static int sequence = 0;
196   static float rndm = 0;
197   float f;
198
199   if (pid == 0)
200     { /* break if we fork ;-) */
201       pid = getpid();
202       rndm = random();
203     }
204   sequence++;
205   f = [[NSDate date] timeIntervalSince1970];
206
207   return [NSString stringWithFormat:@"%0X-%0X-%0X-%0X",
208                    pid, (int) f, sequence++, random];
209 }
210
211 - (NSString *) globallyUniqueObjectId
212 {
213   return [[self class] globallyUniqueObjectId];
214 }
215
216 + (void) _fillDictionary: (NSMutableDictionary *) dictionary
217           withDAVMethods: (NSString *) firstMethod, ...
218 {
219   va_list ap;
220   NSString *aclMethodName;
221   NSString *methodName;
222   SEL methodSel;
223
224   va_start (ap, firstMethod);
225   aclMethodName = firstMethod;
226   while (aclMethodName)
227     {
228       methodName = [aclMethodName davMethodToObjC];
229       methodSel = NSSelectorFromString (methodName);
230       if (methodSel && [self instancesRespondToSelector: methodSel])
231         [dictionary setObject: methodName
232                     forKey: [NSString stringWithFormat: @"{DAV:}%@",
233                                       aclMethodName]];
234       else
235         NSLog(@"************ method '%@' is still unimplemented!",
236               methodName);
237       aclMethodName = va_arg (ap, NSString *);
238     }
239
240   va_end (ap);
241 }
242
243 + (NSDictionary *) defaultWebDAVAttributeMap
244 {
245   static NSMutableDictionary *map = nil;
246
247   if (!map)
248     {
249       map = [NSMutableDictionary
250               dictionaryWithDictionary: [super defaultWebDAVAttributeMap]];
251       [map retain];
252       [self _fillDictionary: map
253             withDAVMethods: @"owner", @"group", @"supported-privilege-set",
254             @"current-user-privilege-set", @"acl", @"acl-restrictions",
255             @"inherited-acl-set", @"principal-collection-set", nil];
256     }
257
258   return map;
259 }
260
261 /* containment */
262
263 + (id) objectWithName: (NSString *)_name inContainer:(id)_container
264 {
265   SOGoObject *object;
266
267   object = [[self alloc] initWithName: _name inContainer: _container];
268   [object autorelease];
269
270   return object;
271 }
272
273 /* DAV ACL properties */
274 - (NSString *) davOwner
275 {
276   return [NSString stringWithFormat: @"%@users/%@",
277                    [self rootURLInContext: context],
278                    [self ownerInContext: nil]];
279 }
280
281 - (NSString *) davAclRestrictions
282 {
283   NSMutableString *restrictions;
284
285   restrictions = [NSMutableString string];
286   [restrictions appendString: @"<D:grant-only/>"];
287   [restrictions appendString: @"<D:no-invert/>"];
288
289   return restrictions;
290 }
291
292 - (SOGoDAVSet *) davPrincipalCollectionSet
293 {
294   NSString *usersUrl;
295
296   usersUrl = [NSString stringWithFormat: @"%@users",
297                        [self rootURLInContext: context]];
298
299   return [SOGoDAVSet davSetWithArray: [NSArray arrayWithObject: usersUrl]
300                      ofValuesTaggedAs: @"D:href"];
301 }
302
303 - (SOGoDAVSet *) davCurrentUserPrivilegeSet
304 {
305   SOGoDAVAuthenticator *sAuth;
306   SoUser *user;
307   NSArray *roles;
308   SoClassSecurityInfo *sInfo;
309   NSArray *davPermissions;
310
311   sAuth = [SOGoDAVAuthenticator sharedSOGoDAVAuthenticator];
312   user = [sAuth userInContext: context];
313   roles = [user rolesForObject: self inContext: context];
314   sInfo = [[self class] soClassSecurityInfo];
315
316   davPermissions
317     = [[sInfo DAVPermissionsForRoles: roles] stringsWithFormat: @"<D:%@/>"];
318
319   return [SOGoDAVSet davSetWithArray: davPermissions
320                      ofValuesTaggedAs: @"D:privilege"];
321 }
322
323 - (SOGoDAVSet *) davSupportedPrivilegeSet
324 {
325   SoClassSecurityInfo *sInfo;
326   NSArray *allPermissions;
327
328   sInfo = [[self class] soClassSecurityInfo];
329
330   allPermissions = [[sInfo allDAVPermissions] stringsWithFormat: @"<D:%@/>"];
331
332   return [SOGoDAVSet davSetWithArray: allPermissions
333                      ofValuesTaggedAs: @"D:privilege"];
334 }
335
336 - (NSArray *) _davAcesFromAclsDictionary: (NSDictionary *) aclsDictionary
337 {
338   NSEnumerator *keys;
339   NSArray *privileges;
340   NSMutableString *currentAce;
341   NSMutableArray *davAces;
342   NSString *currentKey, *principal;
343   SOGoDAVSet *privilegesDS;
344
345   davAces = [NSMutableArray array];
346   keys = [[aclsDictionary allKeys] objectEnumerator];
347   currentKey = [keys nextObject];
348   while (currentKey)
349     {
350       currentAce = [NSMutableString string];
351       if ([currentKey hasPrefix: @":"])
352         [currentAce
353           appendFormat: @"<D:principal><D:property><D:%@/></D:property></D:principal>",
354           [currentKey substringFromIndex: 1]];
355       else
356         {
357           principal = [NSString stringWithFormat: @"%@users/%@",
358                                 [self rootURLInContext: context],
359                                 currentKey];
360           [currentAce
361             appendFormat: @"<D:principal><D:href>%@</D:href></D:principal>",
362             principal];
363         }
364
365       privileges = [[aclsDictionary objectForKey: currentKey]
366                      stringsWithFormat: @"<D:%@/>"];
367       privilegesDS = [SOGoDAVSet davSetWithArray: privileges
368                                  ofValuesTaggedAs: @"privilege"];
369       [currentAce appendString: [privilegesDS stringForTag: @"{DAV:}grant"
370                                               rawName: @"grant"
371                                               inContext: nil prefixes: nil]];
372       [davAces addObject: currentAce];
373       currentKey = [keys nextObject];
374     }
375
376   return davAces;
377 }
378
379 - (void) _appendRolesForPseudoPrincipals: (NSMutableDictionary *) aclsDictionary
380                    withClassSecurityInfo: (SoClassSecurityInfo *) sInfo
381 {
382   NSArray *perms;
383
384   perms = [sInfo DAVPermissionsForRole: SoRole_Owner];
385   if ([perms count])
386     [aclsDictionary setObject: perms forKey: @":owner"];
387   perms = [sInfo DAVPermissionsForRole: SoRole_Authenticated];
388   if ([perms count])
389     [aclsDictionary setObject: perms forKey: @":authenticated"];
390   perms = [sInfo DAVPermissionsForRole: SoRole_Anonymous];
391   if ([perms count])
392     [aclsDictionary setObject: perms forKey: @":unauthenticated"];
393 }
394
395 - (SOGoDAVSet *) davAcl
396 {
397   NSArray *roles;
398   NSEnumerator *uids;
399   NSMutableDictionary *aclsDictionary;
400   NSString *currentUID;
401   SoClassSecurityInfo *sInfo;
402
403   aclsDictionary = [NSMutableDictionary dictionary];
404   uids = [[self aclUsers] objectEnumerator];
405   sInfo = [[self class] soClassSecurityInfo];
406
407   currentUID = [uids nextObject];
408   while (currentUID)
409     {
410       roles = [self aclsForUser: currentUID];
411       [aclsDictionary setObject: [sInfo DAVPermissionsForRoles: roles]
412                       forKey: currentUID];
413       currentUID = [uids nextObject];
414     }
415   [self _appendRolesForPseudoPrincipals: aclsDictionary
416         withClassSecurityInfo: sInfo];
417
418   return [SOGoDAVSet davSetWithArray:
419                        [self _davAcesFromAclsDictionary: aclsDictionary]
420                      ofValuesTaggedAs: @"D:ace"];
421 }
422
423 /* end of properties */
424
425 - (BOOL) doesRetainContainer
426 {
427   return YES;
428 }
429
430 - (id) init
431 {
432   if ((self = [super init]))
433     {
434       context = nil;
435       nameInContainer = nil;
436       container = nil;
437       owner = nil;
438     }
439
440   return self;
441 }
442
443 - (id) initWithName: (NSString *) _name
444         inContainer: (id) _container
445 {
446   if ((self = [self init]))
447     {
448       context = [[WOApplication application] context];
449       [context retain];
450       nameInContainer = [_name copy];
451       container = _container;
452       if ([self doesRetainContainer])
453         [_container retain];
454       owner = [self ownerInContext: context];
455       if (owner)
456         [owner retain];
457     }
458
459   return self;
460 }
461
462 - (void) dealloc
463 {
464   [context release];
465   [owner release];
466   if ([self doesRetainContainer])
467     [container release];
468   [nameInContainer release];
469   [super dealloc];
470 }
471
472 /* accessors */
473
474 - (NSString *) nameInContainer
475 {
476   return nameInContainer;
477 }
478
479 - (id) container
480 {
481   return container;
482 }
483
484 - (NSArray *) pathArrayToSOGoObject
485 {
486   NSMutableArray *realPathArray;
487   NSString *objectName;
488   NSArray *objectDescription;
489
490   realPathArray
491     = [NSMutableArray arrayWithArray: [self pathArrayToSoObject]];
492   if ([realPathArray count] > 2)
493     {
494       objectName = [realPathArray objectAtIndex: 2];
495       objectDescription = [objectName componentsSeparatedByString: @"_"];
496       if ([objectDescription count] > 1)
497         {
498           [realPathArray replaceObjectAtIndex: 0
499                          withObject: [objectDescription objectAtIndex: 0]];
500           [realPathArray replaceObjectAtIndex: 2
501                          withObject: [objectDescription objectAtIndex: 1]];
502         }
503     }
504
505   return realPathArray;
506 }
507
508 /* ownership */
509
510 - (void) setOwner: (NSString *) newOwner
511 {
512   ASSIGN (owner, newOwner);
513 }
514
515 - (NSString *) ownerInContext: (id) localContext
516 {
517   if (!owner)
518     owner = [container ownerInContext: context];
519
520   return owner;
521 }
522
523 /* hierarchy */
524
525 - (NSArray *) fetchSubfolders
526 {
527   NSMutableArray *ma;
528   NSArray  *names;
529   unsigned i, count;
530   
531   if ((names = [self toManyRelationshipKeys]) == nil)
532     return nil;
533   
534   count = [names count];
535   ma    = [NSMutableArray arrayWithCapacity:count + 1];
536   for (i = 0; i < count; i++) {
537     id folder;
538
539     folder = [self lookupName: [names objectAtIndex:i]
540                    inContext: nil 
541                    acquire: NO];
542     if (folder == nil)
543       continue;
544     if ([folder isKindOfClass:[NSException class]])
545       continue;
546     
547     [ma addObject:folder];
548   }
549   return ma;
550 }
551
552 /* looking up shared objects */
553
554 - (SOGoUserFolder *) lookupUserFolder
555 {
556   if (![container respondsToSelector:_cmd])
557     return nil;
558   
559   return [container lookupUserFolder];
560 }
561
562 - (SOGoGroupsFolder *) lookupGroupsFolder
563 {
564   return [[self lookupUserFolder] lookupGroupsFolder];
565 }
566
567 - (void) sleep
568 {
569   if ([self doesRetainContainer])
570     [container release];
571   container = nil;
572 }
573
574 /* operations */
575
576 - (NSException *) delete
577 {
578   return [NSException exceptionWithHTTPStatus: 501 /* not implemented */
579                       reason: @"delete not yet implemented, sorry ..."];
580 }
581
582 /* KVC hacks */
583
584 - (id) valueForUndefinedKey: (NSString *) _key
585 {
586   return nil;
587 }
588
589 /* WebDAV */
590
591 - (NSString *) davDisplayName
592 {
593   return [self nameInContainer];
594 }
595
596 /* actions */
597
598 - (id) DELETEAction: (id) _ctx
599 {
600   NSException *error;
601
602   if ((error = [self delete]) != nil)
603     return error;
604   
605   /* Note: returning 'nil' breaks in SoObjectRequestHandler */
606   return [NSNumber numberWithBool:YES]; /* delete worked out ... */
607 }
608
609 - (NSString *) davContentType
610 {
611   return @"text/plain";
612 }
613
614 - (WOResponse *) _webDAVResponse: (WOContext *) localContext
615 {
616   WOResponse *response;
617   NSString *contentType;
618   id etag;
619
620   response = [localContext response];
621   contentType = [NSString stringWithFormat: @"%@; charset=utf8",
622                           [self davContentType]];
623   [response setHeader: contentType forKey: @"content-type"];
624   [response appendContentString: [self contentAsString]];
625   etag = [self davEntityTag];
626   if (etag)
627     [response setHeader: etag forKey: @"etag"];
628
629   return response;
630 }
631
632 - (id) GETAction: (id) localContext
633 {
634   // TODO: I guess this should really be done by SOPE (redirect to
635   //       default method)
636   WORequest *request;
637   NSString *uri;
638   NSException *error;
639   id value;
640
641   request = [localContext request];  
642   if ([request isSoWebDAVRequest])
643     {
644       if ([self respondsToSelector: @selector (contentAsString)])
645         {
646           error = [self matchesRequestConditionInContext: localContext];
647           if (error)
648             value = error;
649           else
650             value = [self _webDAVResponse: localContext];
651         }
652       else
653         value = [NSException exceptionWithHTTPStatus: 501 /* not implemented */
654                              reason: @"no WebDAV GET support?!"];
655     }
656   else
657     {
658       value = [localContext response];
659       uri = [[request uri] composeURLWithAction: @"view"
660                            parameters: [request formValues]
661                            andHash: NO];
662       [value setStatus: 302 /* moved */];
663       [value setHeader: uri forKey: @"location"];
664     }
665
666   return value;
667 }
668
669 /* etag support */
670
671 - (NSArray *)parseETagList:(NSString *)_c {
672   NSMutableArray *ma;
673   NSArray  *etags;
674   unsigned i, count;
675   
676   if ([_c length] == 0)
677     return nil;
678   if ([_c isEqualToString:@"*"])
679     return nil;
680   
681   etags = [_c componentsSeparatedByString:@","];
682   count = [etags count];
683   ma    = [NSMutableArray arrayWithCapacity:count];
684   for (i = 0; i < count; i++) {
685     NSString *etag;
686     
687     etag = [[etags objectAtIndex:i] stringByTrimmingSpaces];
688 #if 0 /* this is non-sense, right? */
689     if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""])
690       etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)];
691 #endif
692     
693     if (etag != nil) [ma addObject:etag];
694   }
695   return ma;
696 }
697
698 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
699   /* 
700      Only run the request if one of the etags matches the resource etag,
701      usually used to ensure consistent PUTs.
702   */
703   NSArray  *etags;
704   NSString *etag;
705   
706   if ([_c isEqualToString:@"*"])
707     /* to ensure that the resource exists! */
708     return nil;
709   
710   if ((etags = [self parseETagList:_c]) == nil)
711     return nil;
712   if ([etags count] == 0) /* no etags to check for? */
713     return nil;
714   
715   etag = [self davEntityTag];
716   if ([etag length] == 0) /* has no etag, ignore */
717     return nil;
718   
719   if ([etags containsObject:etag]) {
720     [self debugWithFormat:@"etag '%@' matches: %@", etag, 
721           [etags componentsJoinedByString:@","]];
722     return nil; /* one etag matches, so continue with request */
723   }
724
725   /* hack for Kontact 3.4 */
726   
727   if (kontactGroupDAV) {
728     WEClientCapabilities *cc;
729     
730     cc = [[(WOContext *)_ctx request] clientCapabilities];
731     if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
732       if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
733         [self logWithFormat:
734                 @"WARNING: applying Kontact 3.4 GroupDAV hack"
735                 @" - etag check is disabled!"
736                 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
737         return nil;
738       }
739     }
740   }
741   
742   // TODO: we might want to return the davEntityTag in the response
743   [self debugWithFormat:@"etag '%@' does not match: %@", etag, 
744         [etags componentsJoinedByString:@","]];
745   return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
746                       reason:@"Precondition Failed"];
747 }
748
749 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
750   /*
751     If one of the etags is still the same, we can ignore the request.
752     
753     Can be used for PUT to ensure that the object does not exist in the store
754     and for GET to retrieve the content only if if the etag changed.
755   */
756   
757   if (![_c isEqualToString:@"*"] && 
758       [[[_ctx request] method] isEqualToString:@"GET"]) {
759     NSString *etag;
760     NSArray  *etags;
761     
762     if ((etags = [self parseETagList:_c]) == nil)
763       return nil;
764     if ([etags count] == 0) /* no etags to check for? */
765       return nil;
766     
767     etag = [self davEntityTag];
768     if ([etag length] == 0) /* has no etag, ignore */
769       return nil;
770     
771     if ([etags containsObject:etag]) {
772       [self debugWithFormat:@"etag '%@' matches: %@", etag, 
773               [etags componentsJoinedByString:@","]];
774       /* one etag matches, so stop the request */
775       return [NSException exceptionWithHTTPStatus:304 /* Not Modified */
776                           reason:@"object was not modified"];
777     }
778     
779     return nil;
780   }
781   
782 #if 0
783   if ([_c isEqualToString:@"*"])
784     return nil;
785   
786   if ((a = [self parseETagList:_c]) == nil)
787     return nil;
788 #else
789   [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
790 #endif
791   return nil;
792 }
793
794 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
795   NSException *error;
796   WORequest *rq;
797   NSString  *c;
798   
799   if ((rq = [(WOContext *)_ctx request]) == nil)
800     return nil; /* be tolerant - no request, no condition */
801   
802   if ((c = [rq headerForKey:@"if-match"]) != nil) {
803     if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
804       return error;
805   }
806   if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
807     if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
808       return error;
809   }
810   
811   return nil;
812 }
813
814 /* acls */
815
816 - (NSArray *) aclUsers
817 {
818   [self subclassResponsibility: _cmd];
819
820   return nil;
821 }
822
823 - (NSArray *) aclsForUser: (NSString *) uid
824 {
825   [self subclassResponsibility: _cmd];
826
827   return nil;
828 }
829
830 - (void) setRoles: (NSArray *) roles
831           forUser: (NSString *) uid
832 {
833   [self subclassResponsibility: _cmd];
834 }
835
836 - (void) removeAclsForUsers: (NSArray *) users
837 {
838   [self subclassResponsibility: _cmd];
839 }
840
841 - (NSString *) defaultUserID
842 {
843   [self subclassResponsibility: _cmd];
844
845   return nil;
846 }
847
848 - (void) sendACLAdvisoryTemplate: (NSString *) template
849                           toUser: (NSString *) uid
850 {
851   NSString *language, *pageName;
852   SOGoUser *user;
853   SOGoACLAdvisory *page;
854   WOApplication *app;
855
856   user = [SOGoUser userWithLogin: uid roles: nil];
857   language = [user language];
858   pageName = [NSString stringWithFormat: @"SOGoACL%@%@Advisory",
859                        language, template];
860
861   app = [WOApplication application];
862   page = [app pageWithName: pageName inContext: context];
863   [page setACLObject: self];
864   [page setRecipientUID: uid];
865   [page send];
866 }
867
868 - (void) sendACLAdditionAdvisoryToUser: (NSString *) uid
869 {
870   return [self sendACLAdvisoryTemplate: @"Addition"
871                toUser: uid];
872 }
873
874 - (void) sendACLRemovalAdvisoryToUser: (NSString *) uid
875 {
876   return [self sendACLAdvisoryTemplate: @"Removal"
877                toUser: uid];
878 }
879
880 - (NSURL *) _urlPreferringParticle: (NSString *) expected
881                        overThisOne: (NSString *) possible
882 {
883   NSURL *serverURL, *url;
884   NSMutableArray *path;
885   NSString *baseURL, *urlMethod;
886
887   serverURL = [context serverURL];
888   baseURL = [[self baseURLInContext: context] stringByUnescapingURL];
889   path = [NSMutableArray arrayWithArray: [baseURL componentsSeparatedByString:
890                                                     @"/"]];
891   if ([baseURL hasPrefix: @"http"])
892     {
893       [path removeObjectAtIndex: 1];
894       [path removeObjectAtIndex: 0];
895       [path replaceObjectAtIndex: 0 withObject: @""];
896     }
897   urlMethod = [path objectAtIndex: 2];
898   if (![urlMethod isEqualToString: expected])
899     {
900       if ([urlMethod isEqualToString: possible])
901         [path replaceObjectAtIndex: 2 withObject: expected];
902       else
903         [path insertObject: expected atIndex: 2];
904     }
905
906   url = [[NSURL alloc] initWithScheme: [serverURL scheme]
907                        host: [serverURL host]
908                        path: [path componentsJoinedByString: @"/"]];
909   [url autorelease];
910
911   return url;
912 }
913
914 - (NSURL *) davURL
915 {
916   return [self _urlPreferringParticle: @"dav" overThisOne: @"so"];
917 }
918
919 - (NSURL *) soURL
920 {
921   return [self _urlPreferringParticle: @"so" overThisOne: @"dav"];
922 }
923
924 - (NSURL *) soURLToBaseContainerForUser: (NSString *) uid
925 {
926   NSURL *soURL, *baseSoURL;
927   NSArray *basePath;
928   NSMutableArray *newPath;
929
930   soURL = [self soURL];
931   basePath = [[soURL path] componentsSeparatedByString: @"/"];
932   newPath
933     = [NSMutableArray arrayWithArray:
934                         [basePath subarrayWithRange: NSMakeRange (0, 5)]];
935   [newPath replaceObjectAtIndex: 3 withObject: uid];
936
937   baseSoURL = [[NSURL alloc] initWithScheme: [soURL scheme]
938                              host: [soURL host]
939                              path: [newPath componentsJoinedByString: @"/"]];
940   [baseSoURL autorelease];
941
942   return baseSoURL;
943 }
944
945 - (NSURL *) soURLToBaseContainerForCurrentUser
946 {
947   NSString *currentLogin;
948
949   currentLogin = [[context activeUser] login];
950
951   return [self soURLToBaseContainerForUser: currentLogin];
952 }
953
954 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid
955 {
956   [self subclassResponsibility: _cmd];
957
958   return nil;
959 }
960
961 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid
962 {
963   [self subclassResponsibility: _cmd];
964
965   return nil;
966 }
967
968 - (NSString *) labelForKey: (NSString *) key
969 {
970   NSString *userLanguage, *label;
971   NSArray *paths;
972   NSBundle *bundle;
973   NSDictionary *strings;
974
975   bundle = [NSBundle bundleForClass: [self class]];
976   if (!bundle)
977     bundle = [NSBundle mainBundle];
978
979   userLanguage = [[context activeUser] language];
980   paths = [bundle pathsForResourcesOfType: @"strings"
981                   inDirectory: [NSString stringWithFormat: @"%@.lproj", userLanguage]
982                   forLocalization: userLanguage];
983   if ([paths count] > 0)
984     {
985       strings = [NSDictionary dictionaryFromStringsFile: [paths objectAtIndex: 0]];
986       label = [strings objectForKey: key];
987       if (!label)
988         label = key;
989     }
990   else
991     label = key;
992   
993   return label;
994 }
995
996 /* description */
997
998 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
999   if (nameInContainer) 
1000     [_ms appendFormat:@" name=%@", nameInContainer];
1001   if (container)
1002     [_ms appendFormat:@" container=0x%08X/%@", 
1003          container, [container valueForKey:@"nameInContainer"]];
1004 }
1005
1006 - (NSString *)description {
1007   NSMutableString *ms;
1008
1009   ms = [NSMutableString stringWithCapacity:64];
1010   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
1011   [self appendAttributesToDescription:ms];
1012   [ms appendString:@">"];
1013
1014   return ms;
1015 }
1016
1017 @end /* SOGoObject */