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