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 #include "SOGoMailManager.h"
23 #include "SOGoMailConnectionEntry.h"
24 #include "SOGoMailboxInfo.h"
28 Could check read-write state:
29 dict = [[self->context client] select:[self absoluteName]];
31 [[dict objectForKey:@"access"] isEqualToString:@"READ-WRITE"]
32 ? NoNumber : YesNumber;
34 TODO: to implement copy, use "uid copy" instead of "copy" as used by
38 @implementation SOGoMailManager
40 static BOOL debugOn = NO;
41 static BOOL debugCache = NO;
42 static BOOL debugKeys = NO;
43 static BOOL poolingOff = NO;
44 static BOOL alwaysSelect = NO;
45 static BOOL onlyFetchInbox = NO;
46 static NSTimeInterval PoolScanInterval = 5 * 60 /* every five minutes */;
47 static NSString *imap4Separator = nil;
50 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
52 debugOn = [ud boolForKey:@"SOGoEnableIMAP4Debug"];
53 debugCache = [ud boolForKey:@"SOGoEnableIMAP4CacheDebug"];
54 poolingOff = [ud boolForKey:@"SOGoDisableIMAP4Pooling"];
55 alwaysSelect = [ud boolForKey:@"SOGoAlwaysSelectIMAP4Folder"];
57 if (debugOn) NSLog(@"Note: SOGoEnableIMAP4Debug is enabled!");
58 if (poolingOff) NSLog(@"WARNING: IMAP4 connection pooling is disabled!");
61 NSLog(@"WARNING: 'SOGoAlwaysSelectIMAP4Folder' enabled (slow down)");
63 imap4Separator = [[ud stringForKey:@"SOGoIMAP4StringSeparator"] copy];
64 if ([imap4Separator length] == 0)
65 imap4Separator = @"/";
66 NSLog(@"Note(SOGoMailManager): using '%@' as the IMAP4 folder separator.",
70 + (id)defaultMailManager {
71 static SOGoMailManager *manager = nil; // THREAD
73 manager = [[self alloc] init];
78 if ((self = [super init])) {
80 self->urlToEntry = [[NSMutableDictionary alloc] initWithCapacity:256];
83 self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
85 target:self selector:@selector(_garbageCollect:)
86 userInfo:nil repeats:YES] retain];
92 if (self->gcTimer) [self->gcTimer invalidate];
93 [self->gcTimer release];
95 [self->urlToEntry release];
101 - (id)cacheKeyForURL:(NSURL *)_url {
102 // protocol, user, host, port
103 return [NSString stringWithFormat:@"%@://%@@%@:%@",
104 [_url scheme], [_url user], [_url host], [_url port]];
107 - (SOGoMailConnectionEntry *)entryForURL:(NSURL *)_url {
111 return [self->urlToEntry objectForKey:[self cacheKeyForURL:_url]];
113 - (void)cacheEntry:(SOGoMailConnectionEntry *)_entry forURL:(NSURL *)_url {
114 if (_entry == nil) _entry = (id)[NSNull null];
115 [self->urlToEntry setObject:_entry forKey:[self cacheKeyForURL:_url]];
118 - (void)_garbageCollect:(NSTimer *)_timer {
119 // TODO: scan for old IMAP4 channels
120 [self debugWithFormat:@"should collect IMAP4 channels (%d active)",
121 [self->urlToEntry count]];
124 - (id)entryForURL:(NSURL *)_url password:(NSString *)_pwd {
127 a) not yet connected => create new entry and connect
128 b) connected, correct password => return cached entry
129 c) connected, different password => try to recreate entry
131 SOGoMailConnectionEntry *entry;
132 NGImap4Client *client;
136 if ((entry = [self entryForURL:_url]) != nil) {
137 if ([entry isValidPassword:_pwd]) {
139 [self logWithFormat:@"valid password, reusing cache entry ..."];
143 /* different password, password could have changed! */
145 [self logWithFormat:@"different password than cached entry: %@", _url];
149 [self debugWithFormat:@"no connection cached yet for url: %@", _url];
153 client = [entry isValidPassword:_pwd]
155 : [self imap4ClientForURL:_url password:_pwd];
160 /* sideeffect of -imap4ClientForURL:password: is to create a cache entry */
161 return [self entryForURL:_url];
166 - (NGImap4Client *)imap4ClientForURL:(NSURL *)_url password:(NSString *)_pwd {
167 // TODO: move to some global IMAP4 connection pool manager
168 SOGoMailConnectionEntry *entry;
169 NGImap4Client *client;
170 NSDictionary *result;
175 /* check connection pool */
177 if ((entry = [self entryForURL:_url]) != nil) {
178 if ([entry isValidPassword:_pwd]) {
179 [self debugWithFormat:@"reused IMAP4 connection for URL: %@", _url];
180 return [entry client];
183 /* different password, password could have changed! */
187 /* setup connection and attempt login */
189 if ((client = [NGImap4Client clientWithURL:_url]) == nil)
192 result = [client login:[_url user] password:_pwd];
193 if (![[result valueForKey:@"result"] boolValue]) {
194 [self errorWithFormat:
195 @"IMAP4 login failed:\n"
196 @" host=%@, user=%@, pwd=%s\n"
197 @" url=%@\n base=%@\n base-class=%@)\n"
199 [_url host], [_url user], [_pwd length] > 0 ? "yes" : "no",
200 [_url absoluteString],
202 NSStringFromClass([[_url baseURL] class]),
207 [self debugWithFormat:@"created new IMAP4 connection for URL: %@", _url];
209 /* cache connection in pool */
211 entry = [[SOGoMailConnectionEntry alloc] initWithClient:client
213 [self cacheEntry:entry forURL:_url];
214 [entry release]; entry = nil;
219 - (void)flushCachesForURL:(NSURL *)_url {
220 SOGoMailConnectionEntry *entry;
222 if ((entry = [self entryForURL:_url]) == nil) /* nothing cached */
225 [entry flushFolderHierarchyCache];
226 [entry flushMailCaches];
229 - (BOOL)selectFolder:(id)_url inClient:(NGImap4Client *)_client {
230 NSDictionary *result;
233 newFolder = [_url isKindOfClass:[NSURL class]]
234 ? [self imap4FolderNameForURL:_url]
238 if ([[_client selectedFolderName] isEqualToString:newFolder])
242 result = [_client select:newFolder];
243 if (![[result valueForKey:@"result"] boolValue]) {
244 [self errorWithFormat:@"could not select URL: %@: %@", _url, result];
251 /* folder hierarchy */
253 - (NSArray *)_getDirectChildren:(NSArray *)_array folderName:(NSString *)_fn {
255 Scans string '_array' for strings which start with the string in '_fn'.
259 unsigned i, count, prefixlen;
261 if ((count = [_array count]) < 2)
262 /* one entry is the folder itself, so we need at least two */
263 return [NSArray array];
266 // TODO: somehow results are different on OSX
267 prefixlen = [_fn isEqualToString:@""] ? 0 : [_fn length] + 1;
269 prefixlen = [_fn isEqualToString:@"/"] ? 1 : [_fn length] + 1;
271 ma = [NSMutableArray arrayWithCapacity:count];
272 for (i = 0; i < count; i++) {
275 p = [_array objectAtIndex:i];
276 if ([p length] <= prefixlen)
278 if (prefixlen != 0 && ![p hasPrefix:_fn])
281 /* cut of common part */
282 p = [p substringFromIndex:prefixlen];
284 /* check whether the path is a sub-subfolder path */
285 if ([p rangeOfString:@"/"].length > 0)
291 [ma sortUsingSelector:@selector(compare:)];
295 - (NSString *)imap4Separator {
296 return imap4Separator;
299 - (NSString *)imap4FolderNameForURL:(NSURL *)_url removeFileName:(BOOL)_delfn {
300 /* a bit hackish, but should be OK */
301 NSString *folderName;
307 folderName = [_url path];
308 if ([folderName length] == 0)
310 if ([folderName characterAtIndex:0] == '/')
311 folderName = [folderName substringFromIndex:1];
313 if (_delfn) folderName = [folderName stringByDeletingLastPathComponent];
315 if ([[self imap4Separator] isEqualToString:@"/"])
318 names = [folderName pathComponents];
319 return [names componentsJoinedByString:[self imap4Separator]];
321 - (NSString *)imap4FolderNameForURL:(NSURL *)_url {
322 return [self imap4FolderNameForURL:_url removeFileName:NO];
325 - (NSArray *)extractSubfoldersForURL:(NSURL *)_url
326 fromResultSet:(NSDictionary *)_result
328 NSString *folderName;
329 NSDictionary *result;
333 /* Note: the result is normalized, that is, it contains / as the separator */
334 folderName = [_url path];
336 /* normalized results already have the / in front on libFoundation?! */
337 if ([folderName hasPrefix:@"/"])
338 folderName = [folderName substringFromIndex:1];
341 result = [_result valueForKey:@"list"];
343 /* Cyrus already tells us whether we need to check for children */
344 flags = [result objectForKey:folderName];
345 if ([flags containsObject:@"hasnochildren"]) {
347 [self logWithFormat:@"folder %@ has no children.", folderName];
352 [self logWithFormat:@"all keys %@: %@", folderName,
353 [[result allKeys] componentsJoinedByString:@", "]];
356 names = [self _getDirectChildren:[result allKeys] folderName:folderName];
358 [self logWithFormat:@"subfolders of '%@': %@", folderName,
359 [names componentsJoinedByString:@","]];
364 - (NSArray *)extractFoldersFromResultSet:(NSDictionary *)_result {
365 /* Note: the result is normalized, that is, it contains / as the separator */
366 return [[_result valueForKey:@"list"] allKeys];
369 - (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
370 SOGoMailConnectionEntry *entry;
371 NSDictionary *result;
374 [self debugWithFormat:@"subfolders for URL: %@ ...",[_url absoluteString]];
376 /* check connection cache */
378 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
381 /* check hierarchy cache */
383 if ((result = [entry cachedHierarchyResults]) != nil)
384 return [self extractSubfoldersForURL:_url fromResultSet:result];
386 [self debugWithFormat:@" no folders cached yet .."];
388 /* fetch _all_ folders */
390 result = [[entry client] list:(onlyFetchInbox ? @"INBOX" : @"*")
392 if (![[result valueForKey:@"result"] boolValue]) {
393 [self errorWithFormat:@"listing of folder failed!"];
399 if ([result isNotNull]) {
400 if (entry == nil) /* required in case the entry was not setup */
401 entry = [self entryForURL:_url];
403 [entry cacheHierarchyResults:result];
405 [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)",
406 entry, result, [result count]];
412 return [self extractSubfoldersForURL:_url fromResultSet:result];
415 - (NSArray *)allFoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
416 SOGoMailConnectionEntry *entry;
417 NSDictionary *result;
420 [self debugWithFormat:@"folders for URL: %@ ...",[_url absoluteString]];
422 /* check connection cache */
424 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
427 /* check hierarchy cache */
429 if ((result = [entry cachedHierarchyResults]) != nil)
430 return [self extractFoldersFromResultSet:result];
432 [self debugWithFormat:@" no folders cached yet .."];
434 /* fetch _all_ folders */
436 result = [[entry client] list:@"INBOX" pattern:@"*"];
437 if (![[result valueForKey:@"result"] boolValue]) {
438 [self logWithFormat:@"ERROR: listing of folder failed!"];
444 if ([result isNotNull]) {
445 if (entry == nil) /* required in case the entry was not setup */
446 entry = [self entryForURL:_url];
448 [entry cacheHierarchyResults:result];
450 [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)",
451 entry, result, [result count]];
456 return [self extractFoldersFromResultSet:result];
461 - (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_qualifier
462 sortOrdering:(id)_so password:(NSString *)_pwd
465 sortOrdering can be an NSString, an EOSortOrdering or an array of EOS.
467 SOGoMailConnectionEntry *entry;
468 NSDictionary *result;
471 /* check connection cache */
473 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
478 uids = [entry cachedUIDsForURL:_url qualifier:_qualifier sortOrdering:_so];
480 if (debugCache) [self logWithFormat:@"reusing uid cache!"];
481 return [uids isNotNull] ? uids : nil;
484 /* select folder and fetch */
486 if (![self selectFolder:_url inClient:[entry client]])
489 result = [[entry client] sort:_so qualifier:_qualifier encoding:@"UTF-8"];
490 if (![[result valueForKey:@"result"] boolValue]) {
491 [self errorWithFormat:@"could not sort contents of URL: %@", _url];
495 uids = [result valueForKey:@"sort"];
496 if (![uids isNotNull]) {
497 [self errorWithFormat:@"got no UIDs for URL: %@: %@", _url, result];
503 [entry cacheUIDs:uids forURL:_url qualifier:_qualifier sortOrdering:_so];
507 - (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url
508 parts:(NSArray *)_parts password:(NSString *)_pwd
510 // currently returns a dict?!
514 BODY.PEEK[<section>]<<partial>>
515 BODY [this is the bodystructure, supported]
516 BODYSTRUCTURE [not supported yet!]
517 ENVELOPE [this is a parsed header, but does not include type]
525 NGImap4Client *client;
526 NSDictionary *result;
530 if ([_uids count] == 0)
531 return nil; // TODO: might break empty folders?! return a dict!
533 if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil)
538 if (![self selectFolder:_url inClient:client])
543 // TODO: split uids into batches, otherwise Cyrus will complain
544 // => not really important because we batch before (in the sort)
545 // if the list is too long, we get a:
546 // "* BYE Fatal error: word too long"
548 result = [client fetchUids:_uids parts:_parts];
549 if (![[result valueForKey:@"result"] boolValue]) {
550 [self errorWithFormat:@"could not fetch %d uids for url: %@",
555 //[self logWithFormat:@"RESULT: %@", result];
559 - (NSException *)expungeAtURL:(NSURL *)_url password:(NSString *)_pwd {
560 NGImap4Client *client;
564 if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil)
565 return nil; // TODO: return error?
569 p = [self imap4FolderNameForURL:_url removeFileName:NO];
570 if (![self selectFolder:p inClient:client])
575 result = [client expunge];
577 if (![[result valueForKey:@"result"] boolValue]) {
578 [self errorWithFormat:@"could not expunge url: %@", _url];
581 //[self logWithFormat:@"RESULT: %@", result];
585 - (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts password:(NSString *)_pwd{
586 // currently returns a dict
587 NGImap4Client *client;
588 NSDictionary *result;
591 if (![_url isNotNull]) return nil;
593 if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil)
598 if (![self selectFolder:[self imap4FolderNameForURL:_url removeFileName:YES]
604 uid = [[_url path] lastPathComponent];
606 result = [client fetchUids:[NSArray arrayWithObject:uid] parts:_parts];
607 if (![[result valueForKey:@"result"] boolValue]) {
608 [self errorWithFormat:@"could not fetch url: %@", _url];
611 //[self logWithFormat:@"RESULT: %@", result];
615 - (NSData *)fetchContentOfBodyPart:(NSString *)_partId
616 atURL:(NSURL *)_url password:(NSString *)_pwd
620 id result, fetch, body;
622 if (_partId == nil) return nil;
624 key = [@"body[" stringByAppendingString:_partId];
625 key = [key stringByAppendingString:@"]"];
626 parts = [NSArray arrayWithObjects:&key count:1];
630 result = [self fetchURL:_url parts:parts password:_pwd];
632 /* process results */
634 result = [result objectForKey:@"fetch"];
635 if ([result count] == 0) { /* did not find part */
636 [self errorWithFormat:@"did not find part: %@", _partId];
640 fetch = [result objectAtIndex:0];
641 if ((body = [fetch objectForKey:@"body"]) == nil) {
642 [self errorWithFormat:@"did not find body in response: %@", result];
646 if ((result = [body objectForKey:@"data"]) == nil) {
647 [self errorWithFormat:@"did not find data in body: %@", fetch];
653 - (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f
654 toURL:(NSURL *)_url password:(NSString *)_p
656 NGImap4Client *client;
659 if (![_url isNotNull]) return nil;
660 if (![_f isNotNull]) return nil;
662 if (![_f isKindOfClass:[NSArray class]])
663 _f = [NSArray arrayWithObjects:&_f count:1];
667 if ((client = [self imap4ClientForURL:_url password:_p]) == nil)
673 if (![self selectFolder:[self imap4FolderNameForURL:_url removeFileName:YES]
675 return [NSException exceptionWithHTTPStatus:404 /* server error */
676 reason:@"could not select IMAP4 folder"];
682 result = [client storeUid:[[[_url path] lastPathComponent] intValue]
683 add:[NSNumber numberWithBool:_flag]
685 if (![[result valueForKey:@"result"] boolValue]) {
689 r = [result valueForKey:@"reason"];
690 if ([r isEqualToString:@"Permission denied"]) {
691 /* different for each server?, no error codes in IMAP4 ... */
692 status = 403 /* Forbidden */;
695 status = 500 /* internal server error */;
697 [self logWithFormat:@"DEBUG: fail result %@", result];
699 r = [@"Failed to add flag to IMAP4 message: " stringByAppendingString:r];
701 return [NSException exceptionWithHTTPStatus:status /* server error */
704 /* result contains 'fetch' key with the current flags */
707 - (NSException *)addFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p {
708 return [self addOrRemove:YES flags:_f toURL:_u password:_p];
710 - (NSException *)removeFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p {
711 return [self addOrRemove:NO flags:_f toURL:_u password:_p];
714 - (NSException *)markURLDeleted:(NSURL *)_url password:(NSString *)_p {
715 return [self addOrRemove:YES flags:@"Deleted" toURL:_url password:_p];
718 - (NSException *)postData:(NSData *)_data flags:(id)_f
719 toFolderURL:(NSURL *)_url password:(NSString *)_p
721 NGImap4Client *client;
724 if (![_url isNotNull]) return nil;
725 if (![_f isNotNull]) _f = [NSArray array];
727 if ((client = [self imap4ClientForURL:_url password:_p]) == nil)
730 if (![_f isKindOfClass:[NSArray class]])
731 _f = [NSArray arrayWithObjects:&_f count:1];
733 result = [client append:_data
734 toFolder:[self imap4FolderNameForURL:_url]
736 if (![[result valueForKey:@"result"] boolValue]) {
737 [self logWithFormat:@"DEBUG: fail result %@", result];
738 return [NSException exceptionWithHTTPStatus:500 /* server error */
739 reason:@"failed to store message to IMAP4 message"];
741 /* result contains 'fetch' key with the current flags */
743 // TODO: need to flush any caches?
747 - (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl
748 password:(NSString *)_pwd
750 SOGoMailConnectionEntry *entry;
751 NSString *srcname, *destname;
755 /* check connection cache */
757 if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil) {
758 // TODO: better to use an auth exception?
759 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
760 reason:@"did not find IMAP4 folder (no entry)"];
763 /* check whether URLs are on different servers */
765 if ([self entryForURL:_desturl password:_pwd] != entry) {
766 // TODO: find a better error code
767 return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
768 reason:@"source and destination on different servers"];
773 srcname = [self imap4FolderNameForURL:_srcurl removeFileName:YES];
774 uid = [[[_srcurl path] lastPathComponent] unsignedIntValue];
775 destname = [self imap4FolderNameForURL:_desturl];
777 /* select source folder */
779 if (![self selectFolder:srcname inClient:[entry client]]) {
780 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
781 reason:@"did not find source folder"];
786 result = [[entry client] copyUid:uid toFolder:destname];
787 if (![[result valueForKey:@"result"] boolValue]) {
788 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
789 reason:@"copy operation failed"];
792 // TODO: need to flush some caches?
797 /* managing folders */
799 - (BOOL)isPermissionDeniedResult:(id)_result {
800 if ([[_result valueForKey:@"result"] intValue] != 0)
803 return [[_result valueForKey:@"reason"]
804 isEqualToString:@"Permission denied"];
807 - (BOOL)doesMailboxExistAtURL:(NSURL *)_url password:(NSString *)_pwd {
808 SOGoMailConnectionEntry *entry;
809 NSString *folderName;
812 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
815 /* check in hierarchy cache */
817 if ((result = [entry cachedHierarchyResults]) != nil) {
818 result = [result objectForKey:@"list"];
819 return ([result objectForKey:[_url path]] != nil) ? YES : NO;
822 /* check using IMAP4 select */
823 // TODO: we should probably just fetch the whole hierarchy?
825 folderName = [self imap4FolderNameForURL:_url];
826 result = [[entry client] select:folderName];
827 if (![[result valueForKey:@"result"] boolValue])
833 - (id)infoForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
834 SOGoMailConnectionEntry *entry;
835 SOGoMailboxInfo *info;
836 NSString *folderName;
839 if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
840 // TODO: better to use an auth exception?
841 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
842 reason:@"did not find IMAP4 folder (no entry)"];
845 folderName = [self imap4FolderNameForURL:_url];
846 result = [[entry client] select:folderName];
847 if (![[result valueForKey:@"result"] boolValue]) {
848 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
849 reason:@"did not find IMAP4 folder (select failed)"];
852 info = [[SOGoMailboxInfo alloc] initWithURL:_url folderName:folderName
853 selectDictionary:result];
854 return [info autorelease];
857 - (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url
858 password:(NSString *)_pwd
860 SOGoMailConnectionEntry *entry;
864 /* check connection cache */
866 if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
867 // TODO: better to use an auth exception?
868 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
869 reason:@"did not find IMAP4 folder (no entry)"];
874 newPath = [self imap4FolderNameForURL:_url];
875 newPath = [newPath stringByAppendingString:[self imap4Separator]];
876 newPath = [newPath stringByAppendingString:_mailbox];
880 result = [[entry client] create:newPath];
881 if ([self isPermissionDeniedResult:result]) {
882 return [NSException exceptionWithHTTPStatus:403 /* forbidden */
883 reason:@"creation of folders not allowed"];
885 else if ([[result valueForKey:@"result"] intValue] == 0) {
886 return [NSException exceptionWithHTTPStatus:500 /* server error */
887 reason:[result valueForKey:@"reason"]];
890 [entry flushFolderHierarchyCache];
891 // [self debugWithFormat:@"created mailbox: %@: %@", newPath, result];
895 - (NSException *)deleteMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
896 SOGoMailConnectionEntry *entry;
900 /* check connection cache */
902 if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
903 // TODO: better to use an auth exception?
904 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
905 reason:@"did not find IMAP4 folder (no entry)"];
910 path = [self imap4FolderNameForURL:_url];
911 result = [[entry client] delete:path];
913 if ([self isPermissionDeniedResult:result]) {
914 return [NSException exceptionWithHTTPStatus:403 /* forbidden */
915 reason:@"creation of folders not allowed"];
917 else if ([[result valueForKey:@"result"] intValue] == 0) {
918 return [NSException exceptionWithHTTPStatus:500 /* server error */
919 reason:[result valueForKey:@"reason"]];
922 [entry flushFolderHierarchyCache];
924 [self debugWithFormat:@"delete mailbox %@: %@", _url, result];
929 - (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl
930 password:(NSString *)_pwd
932 SOGoMailConnectionEntry *entry;
933 NSString *srcname, *destname;
936 /* check connection cache */
938 if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil) {
939 // TODO: better to use an auth exception?
940 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
941 reason:@"did not find IMAP4 folder (no entry)"];
944 /* check whether URLs are on different servers */
946 if ([self entryForURL:_desturl password:_pwd] != entry) {
947 // TODO: find a better error code
948 return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
949 reason:@"source and destination on different servers"];
954 srcname = [self imap4FolderNameForURL:_srcurl];
955 destname = [self imap4FolderNameForURL:_desturl];
957 result = [[entry client] rename:srcname to:destname];
959 if ([self isPermissionDeniedResult:result]) {
960 return [NSException exceptionWithHTTPStatus:403 /* forbidden */
961 reason:@"creation of folders not allowed"];
963 else if ([[result valueForKey:@"result"] intValue] == 0) {
964 return [NSException exceptionWithHTTPStatus:500 /* server error */
965 reason:[result valueForKey:@"reason"]];
968 [entry flushFolderHierarchyCache];
970 [self debugWithFormat:@"renamed mailbox %@: %@", _srcurl, result];
975 - (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
977 Returns a mapping of uid => permission strings, eg:
981 SOGoMailConnectionEntry *entry;
982 NSString *folderName;
985 if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
986 // TODO: better to use an auth exception?
987 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
988 reason:@"did not find IMAP4 folder (no entry)"];
991 folderName = [self imap4FolderNameForURL:_url];
992 result = [[entry client] getACL:folderName];
993 if (![[result valueForKey:@"result"] boolValue]) {
994 [self logWithFormat:@"ERROR: getacl failed: %@", result];
995 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
996 reason:@"did not find ACL for IMAP4 folder"];
999 return [result valueForKey:@"acl"];
1002 - (NSString *)myRightsForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
1003 SOGoMailConnectionEntry *entry;
1004 NSString *folderName;
1007 if ((entry = [self entryForURL:_url password:_pwd]) == nil) {
1008 // TODO: better to use an auth exception?
1009 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
1010 reason:@"did not find IMAP4 folder (no entry)"];
1015 if ((result = [entry cachedMyRightsForURL:_url]) != nil)
1020 folderName = [self imap4FolderNameForURL:_url];
1021 result = [[entry client] myRights:folderName];
1022 if (![[result valueForKey:@"result"] boolValue]) {
1023 [self logWithFormat:@"ERROR: myrights failed: %@", result];
1024 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
1025 reason:@"did not find myrights for IMAP4 folder"];
1030 if ((result = [result valueForKey:@"myrights"]) != nil)
1031 [entry cacheMyRights:result forURL:_url];
1035 /* bulk flag adding (eg used for empty/trash) */
1037 - (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url
1038 password:(NSString *)_p
1040 NGImap4Client *client;
1043 if (![_url isNotNull]) return nil;
1044 if (![_f isNotNull]) return nil;
1046 if (![_f isKindOfClass:[NSArray class]])
1047 _f = [NSArray arrayWithObjects:&_f count:1];
1051 if ((client = [self imap4ClientForURL:_url password:_p]) == nil)
1052 // TODO: return 401?
1057 if (![self selectFolder:[self imap4FolderNameForURL:_url] inClient:client]) {
1058 return [NSException exceptionWithHTTPStatus:404 /* not found */
1059 reason:@"could not select IMAP4 folder"];
1062 /* fetch all sequence numbers */
1064 result = [client searchWithQualifier:nil /* means: ALL */];
1065 if (![[result valueForKey:@"result"] boolValue]) {
1066 return [NSException exceptionWithHTTPStatus:500 /* server error */
1067 reason:@"could not search in IMAP4 folder"];
1070 result = [result valueForKey:@"search"];
1071 if ([result count] == 0) /* no messages in there, nothin' to be done */
1076 result = [client storeFlags:_f forMSNs:result addOrRemove:YES];
1077 if (![[result valueForKey:@"result"] boolValue]) {
1078 return [NSException exceptionWithHTTPStatus:500 /* server error */
1079 reason:@"could not change flags in IMAP4 folder"];
1087 - (BOOL)isDebuggingEnabled {
1091 @end /* SOGoMailManager */