]> err.no Git - scalable-opengroupware.org/blob - SoObjects/Mailer/SOGoMailFolder.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1077 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / Mailer / SOGoMailFolder.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 #import <Foundation/NSUserDefaults.h>
23
24 #import <NGObjWeb/NSException+HTTP.h>
25 #import <NGExtensions/NSNull+misc.h>
26 #import <NGExtensions/NSURL+misc.h>
27 #import <NGExtensions/NSObject+Logs.h>
28 #import <NGExtensions/NSString+misc.h>
29
30 #import <NGImap4/NGImap4Connection.h>
31 #import <NGImap4/NGImap4Client.h>
32
33 #import <SoObjects/SOGo/SOGoPermissions.h>
34 #import <SoObjects/SOGo/SOGoUser.h>
35 #import <SoObjects/SOGo/NSArray+Utilities.h>
36
37 #import "SOGoMailObject.h"
38 #import "SOGoMailAccount.h"
39 #import "SOGoMailManager.h"
40 #import "SOGoMailFolderDataSource.h"
41 #import "SOGoMailFolder.h"
42
43 static NSString *defaultUserID =  @"anyone";
44
45 @implementation SOGoMailFolder
46
47 static BOOL useAltNamespace = NO;
48
49 + (int) version
50 {
51   return [super version] + 0 /* v1 */;
52 }
53
54 + (void) initialize
55 {
56   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
57
58   NSAssert2([super version] == 1,
59             @"invalid superclass (%@) version %i !",
60             NSStringFromClass([self superclass]), [super version]);
61   
62   useAltNamespace = [ud boolForKey:@"SOGoSpecialFoldersInRoot"];
63 }
64
65 - (void) _adjustOwner
66 {
67   SOGoMailAccount *mailAccount;
68   NSString *path, *folder;
69   NSArray *names;
70
71   mailAccount = [self mailAccountFolder];
72   path = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
73
74   folder = [mailAccount sharedFolderName];
75   if (folder && [path hasPrefix: folder])
76     [self setOwner: @"anyone"];
77   else
78     {
79       folder = [mailAccount otherUsersFolderName];
80       if (folder && [path hasPrefix: folder])
81         {
82           names = [path componentsSeparatedByString: @"/"];
83           if ([names count] > 1)
84             [self setOwner: [names objectAtIndex: 1]];
85           else
86             [self setOwner: @"anyone"];
87         }
88     }
89 }
90
91 - (id) initWithName: (NSString *) newName
92         inContainer: (id) newContainer
93 {
94   if ((self = [super initWithName: newName
95                      inContainer: newContainer]))
96     {
97       [self _adjustOwner];
98     }
99
100   return self;
101 }
102
103 - (void) dealloc
104 {
105   [filenames  release];
106   [folderType release];
107   [super dealloc];
108 }
109
110 /* IMAP4 */
111
112 - (NSString *) relativeImap4Name
113 {
114   return [self nameInContainer];
115 }
116
117 /* listing the available folders */
118
119 - (NSArray *) toManyRelationshipKeys
120 {
121   return [self subfolders];
122 }
123
124 - (NSArray *) subfolders
125 {
126   return [[self imap4Connection] subfoldersForURL: [self imap4URL]];
127 }
128
129 - (NSArray *) subfoldersURL
130 {
131   NSURL *selfURL, *currentURL;
132   NSMutableArray *subfoldersURL;
133   NSEnumerator *subfolders;
134   NSString *selfPath, *currentFolder;
135
136   subfoldersURL = [NSMutableArray array];
137   selfURL = [self imap4URL];
138   selfPath = [selfURL path];
139   subfolders = [[self subfolders] objectEnumerator];
140   currentFolder = [subfolders nextObject];
141   while (currentFolder)
142     {
143       currentURL = [[NSURL alloc]
144                      initWithScheme: [selfURL scheme]
145                      host: [selfURL host]
146                      path: [selfPath stringByAppendingPathComponent:
147                                        currentFolder]];
148       [currentURL autorelease];
149       [subfoldersURL addObject: currentURL];
150       currentFolder = [subfolders nextObject];
151     }
152
153   return subfoldersURL;
154 }
155
156 - (NSString *) davContentType
157 {
158   return @"httpd/unix-directory";
159 }
160
161 - (NSArray *) toOneRelationshipKeys
162 {
163   NSArray  *uids;
164   unsigned count;
165   
166   if (filenames != nil)
167     return [filenames isNotNull] ? filenames : nil;
168
169   uids = [self fetchUIDsMatchingQualifier:nil sortOrdering:@"DATE"];
170   if ([uids isKindOfClass:[NSException class]])
171     return nil;
172   
173   if ((count = [uids count]) == 0) {
174     filenames = [[NSArray alloc] init];
175   }
176   else {
177     NSMutableArray *keys;
178     unsigned i;
179     
180     keys = [[NSMutableArray alloc] initWithCapacity:count];
181     for (i = 0; i < count; i++) {
182       NSString *k;
183       
184       k = [[uids objectAtIndex:i] stringValue];
185       k = [k stringByAppendingString:@".mail"];
186       [keys addObject:k];
187     }
188     filenames = [keys copy];
189     [keys release];
190   }
191   return filenames;
192 }
193
194 - (EODataSource *) contentDataSourceInContext: (id) _ctx
195 {
196   SOGoMailFolderDataSource *ds;
197   
198   ds = [[SOGoMailFolderDataSource alloc] initWithImap4URL:[self imap4URL]
199                                          imap4Password:[self imap4Password]];
200   return [ds autorelease];
201 }
202
203 /* messages */
204
205 - (NSArray *) fetchUIDsMatchingQualifier: (id) _q
206                             sortOrdering: (id) _so
207 {
208   /* seems to return an NSArray of NSNumber's */
209   return [[self imap4Connection] fetchUIDsInURL:[self imap4URL]
210                                  qualifier:_q sortOrdering:_so];
211 }
212
213 - (NSArray *) fetchUIDs: (NSArray *) _uids
214                   parts: (NSArray *) _parts
215 {
216   return [[self imap4Connection] fetchUIDs:_uids inURL:[self imap4URL]
217                                  parts:_parts];
218 }
219
220 - (NSException *) postData: (NSData *) _data
221                      flags: (id) _flags
222 {
223   return [[self imap4Connection] postData:_data flags:_flags
224                                  toFolderURL:[self imap4URL]];
225 }
226
227 - (NSException *) expunge
228 {
229   return [[self imap4Connection] expungeAtURL: [self imap4URL]];
230 }
231
232 /* flags */
233
234 - (NSException *) addFlagsToAllMessages: (id) _f
235 {
236   return [[self imap4Connection] addFlags:_f 
237                                  toAllMessagesInURL: [self imap4URL]];
238 }
239
240 /* name lookup */
241
242 - (BOOL) isMessageKey: (NSString *) _key
243             inContext: (id) _ctx
244 {
245   /*
246     Every key starting with a digit is consider an IMAP4 message key. This is
247     not entirely correct since folders could also start with a number.
248     
249     If we want to support folders beginning with numbers, we would need to
250     scan the folder list for the _key, which would make everything quite a bit
251     slower.
252     TODO: support this mode using a default.
253   */
254   if ([_key length] == 0)
255     return NO;
256   
257   if (isdigit([_key characterAtIndex:0]))
258     return YES;
259   
260   return NO;
261 }
262
263 - (id) lookupImap4Folder: (NSString *) _key
264                inContext: (id) _ctx
265 {
266   // TODO: we might want to check for existence prior controller creation
267   NSURL *sf;
268
269   /* check whether URL exists */
270   
271   sf = [self imap4URL];
272   sf = [NSURL URLWithString:[[sf path] stringByAppendingPathComponent:_key]
273               relativeToURL:sf];
274   
275   if (![[self imap4Connection] doesMailboxExistAtURL:sf]) {
276     /* 
277        We may not return 404, confuses path traversal - but we still do in the
278        calling method. Probably the traversal process should be fixed to
279        support 404 exceptions (as stop traversal _and_ acquisition).
280     */
281     return nil;
282   }
283   
284   /* create object */
285   
286   return [[[SOGoMailFolder alloc] initWithName:_key 
287                                   inContainer:self] autorelease];
288 }
289
290 - (id) lookupImap4Message: (NSString *) _key
291                 inContext: (id) _ctx
292 {
293   // TODO: we might want to check for existence prior controller creation
294   return [[[SOGoMailObject alloc] initWithName:_key 
295                                   inContainer:self] autorelease];
296 }
297
298 - (id) lookupName: (NSString *) _key
299         inContext: (id)_ctx
300           acquire: (BOOL) _acquire
301 {
302   id obj;
303   
304   if ([self isMessageKey:_key inContext:_ctx]) {
305     /* 
306        We assume here that _key is a number and methods are not and this is
307        moved above the super lookup since the super checks the
308        -toOneRelationshipKeys which in turn loads the message ids.
309     */
310     return [self lookupImap4Message:_key inContext:_ctx];
311   }
312
313   obj = [self lookupImap4Folder:_key  inContext:_ctx];
314   if (obj != nil)
315     return obj;
316   
317   /* check attributes directly bound to the app */
318   if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
319     return obj;
320   
321   /* return 404 to stop acquisition */
322   return _acquire
323     ? [NSException exceptionWithHTTPStatus:404 /* Not Found */]
324     : nil; /* hack to work with WebDAV move */
325 }
326
327 /* WebDAV */
328
329 - (BOOL) davIsCollection
330 {
331   return YES;
332 }
333
334 - (NSException *) davCreateCollection: (NSString *) _name
335                             inContext: (id) _ctx
336 {
337   return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
338 }
339
340 - (NSException *) delete
341 {
342   /* Note: overrides SOGoObject -delete */
343   return [[self imap4Connection] deleteMailboxAtURL:[self imap4URL]];
344 }
345
346 - (NSException *) davMoveToTargetObject: (id) _target
347                                 newName: (NSString *) _name
348                               inContext: (id)_ctx
349 {
350   NSURL *destImapURL;
351   
352   if ([_name length] == 0) { /* target already exists! */
353     // TODO: check the overwrite request field (should be done by dispatcher)
354     return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
355                         reason:@"target already exists"];
356   }
357   if (![_target respondsToSelector:@selector(imap4URL)]) {
358     return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
359                         reason:@"target is not an IMAP4 folder"];
360   }
361   
362   /* build IMAP4 URL for target */
363   
364   destImapURL = [_target imap4URL];
365   destImapURL = [NSURL URLWithString:[[destImapURL path] 
366                                        stringByAppendingPathComponent:_name]
367                        relativeToURL:destImapURL];
368   
369   [self logWithFormat:@"TODO: should move collection as '%@' to: %@",
370         [[self imap4URL] absoluteString], 
371         [destImapURL absoluteString]];
372   
373   return [[self imap4Connection] moveMailboxAtURL:[self imap4URL] 
374                                  toURL:destImapURL];
375 }
376
377 - (NSException *) davCopyToTargetObject: (id) _target
378                                 newName: (NSString *) _name
379                               inContext: (id) _ctx
380 {
381   [self logWithFormat:@"TODO: should copy collection as '%@' to: %@",
382         _name, _target];
383   return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
384                       reason:@"not implemented"];
385 }
386
387 /* folder type */
388 - (NSString *) folderType
389 {
390   return @"Mail";
391 }
392
393 - (NSString *) outlookFolderClass
394 {
395   // TODO: detect Trash/Sent/Drafts folders
396   SOGoMailAccount *account;
397   NSString *n;
398
399   if (folderType != nil)
400     return folderType;
401   
402   account = [self mailAccountFolder];
403   n       = [self nameInContainer];
404   
405   if ([n isEqualToString:[account trashFolderNameInContext:nil]])
406     folderType = @"IPF.Trash";
407   else if ([n isEqualToString:[account inboxFolderNameInContext:nil]])
408     folderType = @"IPF.Inbox";
409   else if ([n isEqualToString:[account sentFolderNameInContext:nil]])
410     folderType = @"IPF.Sent";
411   else
412     folderType = @"IPF.Folder";
413   
414   return folderType;
415 }
416
417 /* acls */
418
419 - (NSArray *) _imapAclsToSOGoAcls: (NSString *) imapAcls
420 {
421   unsigned int count, max;
422   NSMutableArray *SOGoAcls;
423
424   SOGoAcls = [NSMutableArray array];
425   max = [imapAcls length];
426   for (count = 0; count < max; count++)
427     {
428       switch ([imapAcls characterAtIndex: count])
429         {
430         case 'l':
431           [SOGoAcls addObjectUniquely: SOGoRole_ObjectViewer];
432           break;
433         case 'r':
434           [SOGoAcls addObjectUniquely: SOGoRole_ObjectReader];
435           break;
436         case 's':
437           [SOGoAcls addObjectUniquely: SOGoMailRole_SeenKeeper];
438           break;
439         case 'w':
440           [SOGoAcls addObjectUniquely: SOGoMailRole_Writer];
441           break;
442         case 'i':
443           [SOGoAcls addObjectUniquely: SOGoRole_ObjectCreator];
444           break;
445         case 'p':
446           [SOGoAcls addObjectUniquely: SOGoMailRole_Poster];
447           break;
448         case 'k':
449           [SOGoAcls addObjectUniquely: SOGoRole_FolderCreator];
450           break;
451         case 'x':
452           [SOGoAcls addObjectUniquely: SOGoRole_FolderEraser];
453           break;
454         case 't':
455           [SOGoAcls addObjectUniquely: SOGoRole_ObjectEraser];
456           break;
457         case 'e':
458           [SOGoAcls addObjectUniquely: SOGoMailRole_Expunger];
459           break;
460         case 'a':
461           [SOGoAcls addObjectUniquely: SOGoMailRole_Administrator];
462           break;
463         }
464     }
465
466   return SOGoAcls;
467 }
468
469 - (NSString *) _sogoAclsToImapAcls: (NSArray *) sogoAcls
470 {
471   NSMutableString *imapAcls;
472   NSEnumerator *acls;
473   NSString *currentAcl;
474   char character;
475
476   imapAcls = [NSMutableString string];
477   acls = [sogoAcls objectEnumerator];
478   currentAcl = [acls nextObject];
479   while (currentAcl)
480     {
481       if ([currentAcl isEqualToString: SOGoRole_ObjectViewer])
482         character = 'l';
483       else if ([currentAcl isEqualToString: SOGoRole_ObjectReader])
484         character = 'r';
485       else if ([currentAcl isEqualToString: SOGoMailRole_SeenKeeper])
486         character = 's';
487       else if ([currentAcl isEqualToString: SOGoMailRole_Writer])
488         character = 'w';
489       else if ([currentAcl isEqualToString: SOGoRole_ObjectCreator])
490         character = 'i';
491       else if ([currentAcl isEqualToString: SOGoMailRole_Poster])
492         character = 'p';
493       else if ([currentAcl isEqualToString: SOGoRole_FolderCreator])
494         character = 'k';
495       else if ([currentAcl isEqualToString: SOGoRole_FolderEraser])
496         character = 'x';
497       else if ([currentAcl isEqualToString: SOGoRole_ObjectEraser])
498         character = 't';
499       else if ([currentAcl isEqualToString: SOGoMailRole_Expunger])
500         character = 'e';
501       else if ([currentAcl isEqualToString: SOGoMailRole_Administrator])
502         character = 'a';
503       else
504         character = 0;
505
506       if (character)
507         [imapAcls appendFormat: @"%c", character];
508
509       currentAcl = [acls nextObject];
510     }
511
512   return imapAcls;
513 }
514
515 - (NSArray *) aclUsers
516 {
517   NSArray *users;
518   NSDictionary *imapAcls;
519
520   imapAcls = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]];
521   if ([imapAcls isKindOfClass: [NSDictionary class]])
522     users = [imapAcls allKeys];
523   else
524     users = nil;
525
526   return users;
527 }
528
529 - (NSMutableArray *) _sharesACLs
530 {
531   NSMutableArray *acls;
532   SOGoMailAccount *mailAccount;
533   NSString *path, *folder;
534 //   NSArray *names;
535 //   unsigned int count;
536
537   acls = [NSMutableArray array];
538
539   mailAccount = [self mailAccountFolder];
540   path = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
541 //   names = [path componentsSeparatedByString: @"/"];
542 //   count = [names count];
543
544   folder = [mailAccount sharedFolderName];
545   if (folder && [path hasPrefix: folder])
546     [acls addObject: SOGoRole_ObjectViewer];
547   else
548     {
549       folder = [mailAccount otherUsersFolderName];
550       if (folder && [path hasPrefix: folder])
551         [acls addObject: SOGoRole_ObjectViewer];
552       else
553         [acls addObject: SoRole_Owner];
554     }
555
556   return acls;
557 }
558
559 - (NSArray *) aclsForUser: (NSString *) uid
560 {
561   NSDictionary *imapAcls;
562   NSMutableArray *acls;
563   NSString *userAcls;
564
565   acls = [self _sharesACLs];
566   imapAcls = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]];
567   if ([imapAcls isKindOfClass: [NSDictionary class]])
568     {
569       userAcls = [imapAcls objectForKey: uid];
570       if (!([userAcls length] || [uid isEqualToString: defaultUserID]))
571         userAcls = [imapAcls objectForKey: defaultUserID];
572       if ([userAcls length])
573         [acls addObjectsFromArray: [self _imapAclsToSOGoAcls: userAcls]];
574     }
575
576   return acls;
577 }
578
579 - (void) removeAclsForUsers: (NSArray *) users
580 {
581   NSEnumerator *uids;
582   NSString *currentUID;
583   NSString *folderName;
584   NGImap4Client *client;
585
586   folderName = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
587   client = [imap4 client];
588
589   uids = [users objectEnumerator];
590   currentUID = [uids nextObject];
591   while (currentUID)
592     {
593       [client deleteACL: folderName uid: currentUID];
594       currentUID = [uids nextObject];
595     }
596 }
597
598 - (void) setRoles: (NSArray *) roles
599           forUser: (NSString *) uid
600 {
601   NSString *acls, *folderName;
602
603   acls = [self _sogoAclsToImapAcls: roles];
604   folderName = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
605   [[imap4 client] setACL: folderName rights: acls uid: uid];
606 }
607
608 - (NSString *) defaultUserID
609 {
610   return defaultUserID;
611 }
612
613 - (NSString *) otherUsersPathToFolder
614 {
615   NSString *userPath, *selfPath, *otherUsers, *sharedFolders;
616   SOGoMailAccount *account;
617
618   account = [self mailAccountFolder];
619   otherUsers = [account otherUsersFolderName];
620   sharedFolders = [account sharedFolderName];
621
622   selfPath = [[self imap4URL] path];
623   if ((otherUsers
624        && [selfPath hasPrefix:
625                       [NSString stringWithFormat: @"/%@", otherUsers]])
626       || (sharedFolders
627           && [selfPath hasPrefix:
628                          [NSString stringWithFormat: @"/%@", sharedFolders]]))
629     userPath = selfPath;
630   else
631     {
632       if (otherUsers)
633         userPath = [NSString stringWithFormat: @"/%@/%@%@",
634                              [otherUsers stringByEscapingURL],
635                              owner, selfPath];
636       else
637         userPath = nil;
638     }
639
640   return userPath;
641 }
642
643 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid;
644 {
645   SOGoUser *user;
646   NSString *otherUsersPath, *url;
647
648   user = [SOGoUser userWithLogin: uid roles: nil];
649   otherUsersPath = [self otherUsersPathToFolder];
650   if (otherUsersPath)
651     url = [NSString stringWithFormat: @"%@/%@%@",
652                     [self soURLToBaseContainerForUser: uid],
653                     [user primaryIMAP4AccountString],
654                     otherUsersPath];
655   else
656     url = nil;
657
658   return url;
659 }
660
661 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid;
662 {
663   NSURL *selfURL, *userURL;
664
665   selfURL = [self imap4URL];
666   userURL = [[NSURL alloc] initWithScheme: [selfURL scheme]
667                            host: [selfURL host]
668                            path: [self otherUsersPathToFolder]];
669   [userURL autorelease];
670
671   return [userURL absoluteString];
672 }
673
674 @end /* SOGoMailFolder */