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 #import <Foundation/NSUserDefaults.h>
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>
30 #import <NGImap4/NGImap4Connection.h>
31 #import <NGImap4/NGImap4Client.h>
33 #import <SoObjects/SOGo/SOGoPermissions.h>
34 #import <SoObjects/SOGo/SOGoUser.h>
35 #import <SoObjects/SOGo/NSArray+Utilities.h>
37 #import "SOGoMailObject.h"
38 #import "SOGoMailAccount.h"
39 #import "SOGoMailManager.h"
40 #import "SOGoMailFolderDataSource.h"
41 #import "SOGoMailFolder.h"
43 static NSString *defaultUserID = @"anyone";
45 @implementation SOGoMailFolder
47 static BOOL useAltNamespace = NO;
51 return [super version] + 0 /* v1 */;
56 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
58 NSAssert2([super version] == 1,
59 @"invalid superclass (%@) version %i !",
60 NSStringFromClass([self superclass]), [super version]);
62 useAltNamespace = [ud boolForKey:@"SOGoSpecialFoldersInRoot"];
67 SOGoMailAccount *mailAccount;
68 NSString *path, *folder;
71 mailAccount = [self mailAccountFolder];
72 path = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
74 folder = [mailAccount sharedFolderName];
75 if (folder && [path hasPrefix: folder])
76 [self setOwner: @"anyone"];
79 folder = [mailAccount otherUsersFolderName];
80 if (folder && [path hasPrefix: folder])
82 names = [path componentsSeparatedByString: @"/"];
83 if ([names count] > 1)
84 [self setOwner: [names objectAtIndex: 1]];
86 [self setOwner: @"anyone"];
91 - (id) initWithName: (NSString *) newName
92 inContainer: (id) newContainer
94 if ((self = [super initWithName: newName
95 inContainer: newContainer]))
106 [folderType release];
112 - (NSString *) relativeImap4Name
114 return [self nameInContainer];
117 /* listing the available folders */
119 - (NSArray *) toManyRelationshipKeys
121 return [self subfolders];
124 - (NSArray *) subfolders
126 return [[self imap4Connection] subfoldersForURL: [self imap4URL]];
129 - (NSArray *) subfoldersURL
131 NSURL *selfURL, *currentURL;
132 NSMutableArray *subfoldersURL;
133 NSEnumerator *subfolders;
134 NSString *selfPath, *currentFolder;
136 subfoldersURL = [NSMutableArray array];
137 selfURL = [self imap4URL];
138 selfPath = [selfURL path];
139 subfolders = [[self subfolders] objectEnumerator];
140 currentFolder = [subfolders nextObject];
141 while (currentFolder)
143 currentURL = [[NSURL alloc]
144 initWithScheme: [selfURL scheme]
146 path: [selfPath stringByAppendingPathComponent:
148 [currentURL autorelease];
149 [subfoldersURL addObject: currentURL];
150 currentFolder = [subfolders nextObject];
153 return subfoldersURL;
156 - (NSString *) davContentType
158 return @"httpd/unix-directory";
161 - (NSArray *) toOneRelationshipKeys
166 if (filenames != nil)
167 return [filenames isNotNull] ? filenames : nil;
169 uids = [self fetchUIDsMatchingQualifier:nil sortOrdering:@"DATE"];
170 if ([uids isKindOfClass:[NSException class]])
173 if ((count = [uids count]) == 0) {
174 filenames = [[NSArray alloc] init];
177 NSMutableArray *keys;
180 keys = [[NSMutableArray alloc] initWithCapacity:count];
181 for (i = 0; i < count; i++) {
184 k = [[uids objectAtIndex:i] stringValue];
185 k = [k stringByAppendingString:@".mail"];
188 filenames = [keys copy];
194 - (EODataSource *) contentDataSourceInContext: (id) _ctx
196 SOGoMailFolderDataSource *ds;
198 ds = [[SOGoMailFolderDataSource alloc] initWithImap4URL:[self imap4URL]
199 imap4Password:[self imap4Password]];
200 return [ds autorelease];
205 - (NSArray *) fetchUIDsMatchingQualifier: (id) _q
206 sortOrdering: (id) _so
208 /* seems to return an NSArray of NSNumber's */
209 return [[self imap4Connection] fetchUIDsInURL:[self imap4URL]
210 qualifier:_q sortOrdering:_so];
213 - (NSArray *) fetchUIDs: (NSArray *) _uids
214 parts: (NSArray *) _parts
216 return [[self imap4Connection] fetchUIDs:_uids inURL:[self imap4URL]
220 - (NSException *) postData: (NSData *) _data
223 return [[self imap4Connection] postData:_data flags:_flags
224 toFolderURL:[self imap4URL]];
227 - (NSException *) expunge
229 return [[self imap4Connection] expungeAtURL: [self imap4URL]];
234 - (NSException *) addFlagsToAllMessages: (id) _f
236 return [[self imap4Connection] addFlags:_f
237 toAllMessagesInURL: [self imap4URL]];
242 - (BOOL) isMessageKey: (NSString *) _key
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.
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
252 TODO: support this mode using a default.
254 if ([_key length] == 0)
257 if (isdigit([_key characterAtIndex:0]))
263 - (id) lookupImap4Folder: (NSString *) _key
266 // TODO: we might want to check for existence prior controller creation
269 /* check whether URL exists */
271 sf = [self imap4URL];
272 sf = [NSURL URLWithString:[[sf path] stringByAppendingPathComponent:_key]
275 if (![[self imap4Connection] doesMailboxExistAtURL:sf]) {
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).
286 return [[[SOGoMailFolder alloc] initWithName:_key
287 inContainer:self] autorelease];
290 - (id) lookupImap4Message: (NSString *) _key
293 // TODO: we might want to check for existence prior controller creation
294 return [[[SOGoMailObject alloc] initWithName:_key
295 inContainer:self] autorelease];
298 - (id) lookupName: (NSString *) _key
300 acquire: (BOOL) _acquire
304 if ([self isMessageKey:_key inContext:_ctx]) {
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.
310 return [self lookupImap4Message:_key inContext:_ctx];
313 obj = [self lookupImap4Folder:_key inContext:_ctx];
317 /* check attributes directly bound to the app */
318 if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
321 /* return 404 to stop acquisition */
323 ? [NSException exceptionWithHTTPStatus:404 /* Not Found */]
324 : nil; /* hack to work with WebDAV move */
329 - (BOOL) davIsCollection
334 - (NSException *) davCreateCollection: (NSString *) _name
337 return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
340 - (NSException *) delete
342 /* Note: overrides SOGoObject -delete */
343 return [[self imap4Connection] deleteMailboxAtURL:[self imap4URL]];
346 - (NSException *) davMoveToTargetObject: (id) _target
347 newName: (NSString *) _name
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"];
357 if (![_target respondsToSelector:@selector(imap4URL)]) {
358 return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
359 reason:@"target is not an IMAP4 folder"];
362 /* build IMAP4 URL for target */
364 destImapURL = [_target imap4URL];
365 destImapURL = [NSURL URLWithString:[[destImapURL path]
366 stringByAppendingPathComponent:_name]
367 relativeToURL:destImapURL];
369 [self logWithFormat:@"TODO: should move collection as '%@' to: %@",
370 [[self imap4URL] absoluteString],
371 [destImapURL absoluteString]];
373 return [[self imap4Connection] moveMailboxAtURL:[self imap4URL]
377 - (NSException *) davCopyToTargetObject: (id) _target
378 newName: (NSString *) _name
381 [self logWithFormat:@"TODO: should copy collection as '%@' to: %@",
383 return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
384 reason:@"not implemented"];
388 - (NSString *) folderType
393 - (NSString *) outlookFolderClass
395 // TODO: detect Trash/Sent/Drafts folders
396 SOGoMailAccount *account;
399 if (folderType != nil)
402 account = [self mailAccountFolder];
403 n = [self nameInContainer];
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";
412 folderType = @"IPF.Folder";
419 - (NSArray *) _imapAclsToSOGoAcls: (NSString *) imapAcls
421 unsigned int count, max;
422 NSMutableArray *SOGoAcls;
424 SOGoAcls = [NSMutableArray array];
425 max = [imapAcls length];
426 for (count = 0; count < max; count++)
428 switch ([imapAcls characterAtIndex: count])
431 [SOGoAcls addObjectUniquely: SOGoRole_ObjectViewer];
434 [SOGoAcls addObjectUniquely: SOGoRole_ObjectReader];
437 [SOGoAcls addObjectUniquely: SOGoMailRole_SeenKeeper];
440 [SOGoAcls addObjectUniquely: SOGoMailRole_Writer];
443 [SOGoAcls addObjectUniquely: SOGoRole_ObjectCreator];
446 [SOGoAcls addObjectUniquely: SOGoMailRole_Poster];
449 [SOGoAcls addObjectUniquely: SOGoRole_FolderCreator];
452 [SOGoAcls addObjectUniquely: SOGoRole_FolderEraser];
455 [SOGoAcls addObjectUniquely: SOGoRole_ObjectEraser];
458 [SOGoAcls addObjectUniquely: SOGoMailRole_Expunger];
461 [SOGoAcls addObjectUniquely: SOGoMailRole_Administrator];
469 - (NSString *) _sogoAclsToImapAcls: (NSArray *) sogoAcls
471 NSMutableString *imapAcls;
473 NSString *currentAcl;
476 imapAcls = [NSMutableString string];
477 acls = [sogoAcls objectEnumerator];
478 currentAcl = [acls nextObject];
481 if ([currentAcl isEqualToString: SOGoRole_ObjectViewer])
483 else if ([currentAcl isEqualToString: SOGoRole_ObjectReader])
485 else if ([currentAcl isEqualToString: SOGoMailRole_SeenKeeper])
487 else if ([currentAcl isEqualToString: SOGoMailRole_Writer])
489 else if ([currentAcl isEqualToString: SOGoRole_ObjectCreator])
491 else if ([currentAcl isEqualToString: SOGoMailRole_Poster])
493 else if ([currentAcl isEqualToString: SOGoRole_FolderCreator])
495 else if ([currentAcl isEqualToString: SOGoRole_FolderEraser])
497 else if ([currentAcl isEqualToString: SOGoRole_ObjectEraser])
499 else if ([currentAcl isEqualToString: SOGoMailRole_Expunger])
501 else if ([currentAcl isEqualToString: SOGoMailRole_Administrator])
507 [imapAcls appendFormat: @"%c", character];
509 currentAcl = [acls nextObject];
515 - (NSArray *) aclUsers
518 NSDictionary *imapAcls;
520 imapAcls = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]];
521 if ([imapAcls isKindOfClass: [NSDictionary class]])
522 users = [imapAcls allKeys];
529 - (NSMutableArray *) _sharesACLs
531 NSMutableArray *acls;
532 SOGoMailAccount *mailAccount;
533 NSString *path, *folder;
535 // unsigned int count;
537 acls = [NSMutableArray array];
539 mailAccount = [self mailAccountFolder];
540 path = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
541 // names = [path componentsSeparatedByString: @"/"];
542 // count = [names count];
544 folder = [mailAccount sharedFolderName];
545 if (folder && [path hasPrefix: folder])
546 [acls addObject: SOGoRole_ObjectViewer];
549 folder = [mailAccount otherUsersFolderName];
550 if (folder && [path hasPrefix: folder])
551 [acls addObject: SOGoRole_ObjectViewer];
553 [acls addObject: SoRole_Owner];
559 - (NSArray *) aclsForUser: (NSString *) uid
561 NSDictionary *imapAcls;
562 NSMutableArray *acls;
565 acls = [self _sharesACLs];
566 imapAcls = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]];
567 if ([imapAcls isKindOfClass: [NSDictionary class]])
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]];
579 - (void) removeAclsForUsers: (NSArray *) users
582 NSString *currentUID;
583 NSString *folderName;
584 NGImap4Client *client;
586 folderName = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
587 client = [imap4 client];
589 uids = [users objectEnumerator];
590 currentUID = [uids nextObject];
593 [client deleteACL: folderName uid: currentUID];
594 currentUID = [uids nextObject];
598 - (void) setRoles: (NSArray *) roles
599 forUser: (NSString *) uid
601 NSString *acls, *folderName;
603 acls = [self _sogoAclsToImapAcls: roles];
604 folderName = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
605 [[imap4 client] setACL: folderName rights: acls uid: uid];
608 - (NSString *) defaultUserID
610 return defaultUserID;
613 - (NSString *) otherUsersPathToFolder
615 NSString *userPath, *selfPath, *otherUsers, *sharedFolders;
616 SOGoMailAccount *account;
618 account = [self mailAccountFolder];
619 otherUsers = [account otherUsersFolderName];
620 sharedFolders = [account sharedFolderName];
622 selfPath = [[self imap4URL] path];
624 && [selfPath hasPrefix:
625 [NSString stringWithFormat: @"/%@", otherUsers]])
627 && [selfPath hasPrefix:
628 [NSString stringWithFormat: @"/%@", sharedFolders]]))
633 userPath = [NSString stringWithFormat: @"/%@/%@%@",
634 [otherUsers stringByEscapingURL],
643 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid;
646 NSString *otherUsersPath, *url;
648 user = [SOGoUser userWithLogin: uid roles: nil];
649 otherUsersPath = [self otherUsersPathToFolder];
651 url = [NSString stringWithFormat: @"%@/%@%@",
652 [self soURLToBaseContainerForUser: uid],
653 [user primaryIMAP4AccountString],
661 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid;
663 NSURL *selfURL, *userURL;
665 selfURL = [self imap4URL];
666 userURL = [[NSURL alloc] initWithScheme: [selfURL scheme]
668 path: [self otherUsersPathToFolder]];
669 [userURL autorelease];
671 return [userURL absoluteString];
674 @end /* SOGoMailFolder */