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"];
74 - (NSString *) relativeImap4Name
76 return [self nameInContainer];
79 /* listing the available folders */
81 - (NSArray *) toManyRelationshipKeys
83 return [self subfolders];
86 - (NSArray *) subfolders
88 return [[self imap4Connection] subfoldersForURL: [self imap4URL]];
91 - (NSArray *) subfoldersURL
93 NSURL *selfURL, *currentURL;
94 NSMutableArray *subfoldersURL;
95 NSEnumerator *subfolders;
96 NSString *selfPath, *currentFolder;
98 subfoldersURL = [NSMutableArray array];
99 selfURL = [self imap4URL];
100 selfPath = [selfURL path];
101 subfolders = [[self subfolders] objectEnumerator];
102 currentFolder = [subfolders nextObject];
103 while (currentFolder)
105 currentURL = [[NSURL alloc]
106 initWithScheme: [selfURL scheme]
108 path: [selfPath stringByAppendingPathComponent:
110 [currentURL autorelease];
111 [subfoldersURL addObject: currentURL];
112 currentFolder = [subfolders nextObject];
115 return subfoldersURL;
118 - (NSArray *) toOneRelationshipKeys
123 if (filenames != nil)
124 return [filenames isNotNull] ? filenames : nil;
126 uids = [self fetchUIDsMatchingQualifier:nil sortOrdering:@"DATE"];
127 if ([uids isKindOfClass:[NSException class]])
130 if ((count = [uids count]) == 0) {
131 filenames = [[NSArray alloc] init];
134 NSMutableArray *keys;
137 keys = [[NSMutableArray alloc] initWithCapacity:count];
138 for (i = 0; i < count; i++) {
141 k = [[uids objectAtIndex:i] stringValue];
142 k = [k stringByAppendingString:@".mail"];
145 filenames = [keys copy];
151 - (EODataSource *) contentDataSourceInContext: (id) _ctx
153 SOGoMailFolderDataSource *ds;
155 ds = [[SOGoMailFolderDataSource alloc] initWithImap4URL:[self imap4URL]
156 imap4Password:[self imap4Password]];
157 return [ds autorelease];
162 - (NSArray *) fetchUIDsMatchingQualifier: (id) _q
163 sortOrdering: (id) _so
165 /* seems to return an NSArray of NSNumber's */
166 return [[self imap4Connection] fetchUIDsInURL:[self imap4URL]
167 qualifier:_q sortOrdering:_so];
170 - (NSArray *) fetchUIDs: (NSArray *) _uids
171 parts: (NSArray *) _parts
173 return [[self imap4Connection] fetchUIDs:_uids inURL:[self imap4URL]
177 - (NSException *) postData: (NSData *) _data
180 return [[self imap4Connection] postData:_data flags:_flags
181 toFolderURL:[self imap4URL]];
184 - (NSException *) expunge
186 return [[self imap4Connection] expungeAtURL: [self imap4URL]];
191 - (NSException *) addFlagsToAllMessages: (id) _f
193 return [[self imap4Connection] addFlags:_f
194 toAllMessagesInURL: [self imap4URL]];
199 - (BOOL) isMessageKey: (NSString *) _key
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.
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
209 TODO: support this mode using a default.
211 if ([_key length] == 0)
214 if (isdigit([_key characterAtIndex:0]))
220 - (id) lookupImap4Folder: (NSString *) _key
223 // TODO: we might want to check for existence prior controller creation
226 /* check whether URL exists */
228 sf = [self imap4URL];
229 sf = [NSURL URLWithString:[[sf path] stringByAppendingPathComponent:_key]
232 if (![[self imap4Connection] doesMailboxExistAtURL:sf]) {
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).
243 return [[[SOGoMailFolder alloc] initWithName:_key
244 inContainer:self] autorelease];
247 - (id) lookupImap4Message: (NSString *) _key
250 // TODO: we might want to check for existence prior controller creation
251 return [[[SOGoMailObject alloc] initWithName:_key
252 inContainer:self] autorelease];
255 - (id) lookupName: (NSString *) _key
257 acquire: (BOOL) _acquire
261 if ([self isMessageKey:_key inContext:_ctx]) {
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.
267 return [self lookupImap4Message:_key inContext:_ctx];
270 obj = [self lookupImap4Folder:_key inContext:_ctx];
274 /* check attributes directly bound to the app */
275 if ((obj = [super lookupName:_key inContext:_ctx acquire:NO]))
278 /* return 404 to stop acquisition */
280 ? [NSException exceptionWithHTTPStatus:404 /* Not Found */]
281 : nil; /* hack to work with WebDAV move */
286 - (BOOL) davIsCollection
291 - (NSException *) davCreateCollection: (NSString *) _name
294 return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
297 - (NSException *) delete
299 /* Note: overrides SOGoObject -delete */
300 return [[self imap4Connection] deleteMailboxAtURL:[self imap4URL]];
303 - (NSException *) davMoveToTargetObject: (id) _target
304 newName: (NSString *) _name
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"];
314 if (![_target respondsToSelector:@selector(imap4URL)]) {
315 return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
316 reason:@"target is not an IMAP4 folder"];
319 /* build IMAP4 URL for target */
321 destImapURL = [_target imap4URL];
322 destImapURL = [NSURL URLWithString:[[destImapURL path]
323 stringByAppendingPathComponent:_name]
324 relativeToURL:destImapURL];
326 [self logWithFormat:@"TODO: should move collection as '%@' to: %@",
327 [[self imap4URL] absoluteString],
328 [destImapURL absoluteString]];
330 return [[self imap4Connection] moveMailboxAtURL:[self imap4URL]
334 - (NSException *) davCopyToTargetObject: (id) _target
335 newName: (NSString *) _name
338 [self logWithFormat:@"TODO: should copy collection as '%@' to: %@",
340 return [NSException exceptionWithHTTPStatus:501 /* Not Implemented */
341 reason:@"not implemented"];
345 - (NSString *) folderType
350 - (NSString *) outlookFolderClass
352 // TODO: detect Trash/Sent/Drafts folders
353 SOGoMailAccount *account;
356 if (folderType != nil)
359 account = [self mailAccountFolder];
360 n = [self nameInContainer];
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";
369 folderType = @"IPF.Folder";
376 - (NSArray *) _imapAclsToSOGoAcls: (NSString *) imapAcls
378 unsigned int count, max;
379 NSMutableArray *SOGoAcls;
381 SOGoAcls = [NSMutableArray array];
382 max = [imapAcls length];
383 for (count = 0; count < max; count++)
385 switch ([imapAcls characterAtIndex: count])
388 [SOGoAcls addObjectUniquely: SOGoRole_ObjectViewer];
391 [SOGoAcls addObjectUniquely: SOGoRole_ObjectReader];
394 [SOGoAcls addObjectUniquely: SOGoMailRole_SeenKeeper];
397 [SOGoAcls addObjectUniquely: SOGoMailRole_Writer];
400 [SOGoAcls addObjectUniquely: SOGoRole_ObjectCreator];
403 [SOGoAcls addObjectUniquely: SOGoMailRole_Poster];
406 [SOGoAcls addObjectUniquely: SOGoRole_FolderCreator];
409 [SOGoAcls addObjectUniquely: SOGoRole_ObjectEraser];
412 [SOGoAcls addObjectUniquely: SOGoMailRole_MessageEraser];
415 [SOGoAcls addObjectUniquely: SOGoMailRole_Expunger];
418 [SOGoAcls addObjectUniquely: SOGoMailRole_Administrator];
426 - (NSString *) _sogoAclsToImapAcls: (NSArray *) sogoAcls
428 NSMutableString *imapAcls;
430 NSString *currentAcl;
433 imapAcls = [NSMutableString string];
434 acls = [sogoAcls objectEnumerator];
435 currentAcl = [acls nextObject];
438 if ([currentAcl isEqualToString: SOGoRole_ObjectViewer])
440 else if ([currentAcl isEqualToString: SOGoRole_ObjectReader])
442 else if ([currentAcl isEqualToString: SOGoMailRole_SeenKeeper])
444 else if ([currentAcl isEqualToString: SOGoMailRole_Writer])
446 else if ([currentAcl isEqualToString: SOGoRole_ObjectCreator])
448 else if ([currentAcl isEqualToString: SOGoMailRole_Poster])
450 else if ([currentAcl isEqualToString: SOGoRole_FolderCreator])
452 else if ([currentAcl isEqualToString: SOGoRole_ObjectEraser])
454 else if ([currentAcl isEqualToString: SOGoMailRole_MessageEraser])
456 else if ([currentAcl isEqualToString: SOGoMailRole_Expunger])
458 else if ([currentAcl isEqualToString: SOGoMailRole_Administrator])
464 [imapAcls appendFormat: @"%c", character];
466 currentAcl = [acls nextObject];
472 - (NSArray *) aclUsers
475 NSDictionary *imapAcls;
477 imapAcls = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]];
478 if ([imapAcls isKindOfClass: [NSDictionary class]])
479 users = [imapAcls allKeys];
486 - (NSMutableArray *) _sharesACLs
488 NSMutableArray *acls;
489 SOGoMailAccount *mailAccount;
494 acls = [NSMutableArray array];
496 mailAccount = [self mailAccountFolder];
497 path = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
498 names = [path componentsSeparatedByString: @"/"];
499 count = [names count];
501 if ([path hasPrefix: [mailAccount sharedFolderName]])
502 [acls addObject: SOGoRole_ObjectViewer];
503 else if ([path hasPrefix: [mailAccount otherUsersFolderName]])
504 [acls addObject: SOGoRole_ObjectViewer];
506 [acls addObject: SoRole_Owner];
511 - (NSArray *) aclsForUser: (NSString *) uid
513 NSDictionary *imapAcls;
514 NSMutableArray *acls;
517 acls = [self _sharesACLs];
518 imapAcls = [[self imap4Connection] aclForMailboxAtURL: [self imap4URL]];
519 if ([imapAcls isKindOfClass: [NSDictionary class]])
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]];
531 - (void) removeAclsForUsers: (NSArray *) users
534 NSString *currentUID;
535 NSString *folderName;
536 NGImap4Client *client;
538 folderName = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
539 client = [imap4 client];
541 uids = [users objectEnumerator];
542 currentUID = [uids nextObject];
545 [client deleteACL: folderName uid: currentUID];
546 currentUID = [uids nextObject];
550 - (void) setRoles: (NSArray *) roles
551 forUser: (NSString *) uid
553 NSString *acls, *folderName;
555 acls = [self _sogoAclsToImapAcls: roles];
556 folderName = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
557 [[imap4 client] setACL: folderName rights: acls uid: uid];
560 - (NSString *) defaultUserID
562 return defaultUserID;
565 - (NSString *) ownerInContext: (WOContext *) localContext
567 SOGoMailAccount *mailAccount;
568 NSString *path, *owner;
571 mailAccount = [self mailAccountFolder];
572 path = [[self imap4Connection] imap4FolderNameForURL: [self imap4URL]];
574 if ([path hasPrefix: [mailAccount sharedFolderName]])
576 else if ([path hasPrefix: [mailAccount otherUsersFolderName]])
578 names = [path componentsSeparatedByString: @"/"];
579 if ([names count] > 1)
580 owner = [names objectAtIndex: 1];
585 owner = [super ownerInContext: localContext];
590 - (NSString *) otherUsersPathToFolder
592 NSString *userPath, *selfPath, *otherUsers, *sharedFolders;
593 SOGoMailAccount *account;
595 account = [self mailAccountFolder];
596 otherUsers = [account otherUsersFolderName];
597 sharedFolders = [account sharedFolderName];
599 selfPath = [[self imap4URL] path];
600 if ([selfPath hasPrefix: [NSString stringWithFormat: @"/%@", otherUsers]]
601 || [selfPath hasPrefix:
602 [NSString stringWithFormat: @"/%@", sharedFolders]])
605 userPath = [NSString stringWithFormat: @"/%@/%@%@",
606 [otherUsers stringByEscapingURL],
607 [self ownerInContext: context],
613 - (NSString *) httpURLForAdvisoryToUser: (NSString *) uid;
617 user = [SOGoUser userWithLogin: uid roles: nil];
619 return [NSString stringWithFormat: @"%@/%@%@",
620 [self soURLToBaseContainerForUser: uid],
621 [user primaryIMAP4AccountString],
622 [self otherUsersPathToFolder]];
625 - (NSString *) resourceURLForAdvisoryToUser: (NSString *) uid;
627 NSURL *selfURL, *userURL;
629 selfURL = [self imap4URL];
630 userURL = [[NSURL alloc] initWithScheme: [selfURL scheme]
632 path: [self otherUsersPathToFolder]];
633 [userURL autorelease];
635 return [userURL absoluteString];
638 @end /* SOGoMailFolder */