From 984585e74603ab6acec3f06affb51606b3ba0dd2 Mon Sep 17 00:00:00 2001 From: helge Date: Mon, 11 Jul 2005 10:32:44 +0000 Subject: [PATCH] refactoring in mailer git-svn-id: http://svn.opengroupware.org/SOGo/trunk@708 d1b88da0-ebda-0310-925b-ed51d893ca5b --- SOGo/SoObjects/Mailer/ChangeLog | 9 + SOGo/SoObjects/Mailer/SOGoMailBaseObject.h | 1 - SOGo/SoObjects/Mailer/SOGoMailBaseObject.m | 4 - .../Mailer/SOGoMailConnectionEntry.h | 53 +- .../Mailer/SOGoMailConnectionEntry.m | 755 +++++++++++++++++- SOGo/SoObjects/Mailer/SOGoMailManager.h | 2 - SOGo/SoObjects/Mailer/SOGoMailManager.m | 721 ++--------------- SOGo/SoObjects/Mailer/Version | 2 +- 8 files changed, 882 insertions(+), 665 deletions(-) diff --git a/SOGo/SoObjects/Mailer/ChangeLog b/SOGo/SoObjects/Mailer/ChangeLog index a3eb41a0..a26ab25b 100644 --- a/SOGo/SoObjects/Mailer/ChangeLog +++ b/SOGo/SoObjects/Mailer/ChangeLog @@ -1,3 +1,12 @@ +2005-07-11 Helge Hess + + * v0.9.95 + + * SOGoMailBaseObject.m: removed -imapFolderName method + + * SOGoMailManager.m, SOGoMailConnectionEntry.m: moved implementations + of operations to SOGoMailConnectionEntry + 2005-07-08 Helge Hess * SOGoMailAccounts.m: use WOContext method from libSOGo to detect diff --git a/SOGo/SoObjects/Mailer/SOGoMailBaseObject.h b/SOGo/SoObjects/Mailer/SOGoMailBaseObject.h index 77951f9c..da6b83c9 100644 --- a/SOGo/SoObjects/Mailer/SOGoMailBaseObject.h +++ b/SOGo/SoObjects/Mailer/SOGoMailBaseObject.h @@ -60,7 +60,6 @@ - (NSURL *)imap4URL; - (NSString *)imap4Login; - (NSString *)imap4Password; -- (NSString *)imap4FolderName; - (NGImap4Client *)imap4Client; - (void)flushMailCaches; diff --git a/SOGo/SoObjects/Mailer/SOGoMailBaseObject.m b/SOGo/SoObjects/Mailer/SOGoMailBaseObject.m index 07c45371..d366f40e 100644 --- a/SOGo/SoObjects/Mailer/SOGoMailBaseObject.m +++ b/SOGo/SoObjects/Mailer/SOGoMailBaseObject.m @@ -101,10 +101,6 @@ static BOOL debugOn = YES; return self->imap4URL; } -- (NSString *)imap4FolderName { - return [[self mailManager] imap4FolderNameForURL:[self imap4URL]]; -} - - (NSString *)imap4Login { if (![[self container] respondsToSelector:_cmd]) return nil; diff --git a/SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.h b/SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.h index 0e3fcfc2..962df8b3 100644 --- a/SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.h +++ b/SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2004 SKYRIX Software AG + Copyright (C) 2004-2005 SKYRIX Software AG This file is part of OpenGroupware.org. @@ -37,6 +37,7 @@ */ @class NSString, NSDate, NSArray, NSDictionary, NSURL, NSMutableDictionary; +@class NSException, NSData; @class NGImap4Client; @interface SOGoMailConnectionEntry : NSObject @@ -80,6 +81,56 @@ - (void)flushMailCaches; +/* folder operations */ + +- (NSArray *)subfoldersForURL:(NSURL *)_url; +- (NSArray *)allFoldersForURL:(NSURL *)_url; + +/* message operations */ + +- (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_qualifier + sortOrdering:(id)_so; +- (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url + parts:(NSArray *)_parts; +- (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts; +- (NSData *)fetchContentOfBodyPart:(NSString *)_partId atURL:(NSURL *)_url; + +/* message flags */ + +- (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f toURL:(NSURL *)_url; +- (NSException *)addFlags:(id)_f toURL:(NSURL *)_u; +- (NSException *)removeFlags:(id)_f toURL:(NSURL *)_u; +- (NSException *)markURLDeleted:(NSURL *)_url; +- (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url; + +/* posting new data */ + +- (NSException *)postData:(NSData *)_data flags:(id)_f toFolderURL:(NSURL *)_u; + +/* operations */ + +- (NSException *)expungeAtURL:(NSURL *)_url; + +/* copying and moving */ + +- (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl; + +/* managing folders */ + +- (BOOL)doesMailboxExistAtURL:(NSURL *)_url; +- (id)infoForMailboxAtURL:(NSURL *)_url; +- (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url; +- (NSException *)deleteMailboxAtURL:(NSURL *)_url; +- (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl; + +/* ACLs */ + +- (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url; +- (NSString *)myRightsForMailboxAtURL:(NSURL *)_url; + @end +extern NSArray *SOGoMailGetDirectChildren(NSArray *_array, NSString *_fn); +extern NSArray *SOGoMailExtractSubfolders(NSURL *_url, NSDictionary *_result); + #endif /* __SOGo_SOGoMailConnectionEntry_H__ */ diff --git a/SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.m b/SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.m index a113fda7..bfb7a166 100644 --- a/SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.m +++ b/SOGo/SoObjects/Mailer/SOGoMailConnectionEntry.m @@ -1,5 +1,5 @@ /* - Copyright (C) 2004 SKYRIX Software AG + Copyright (C) 2004-2005 SKYRIX Software AG This file is part of OpenGroupware.org. @@ -20,10 +20,35 @@ */ #include "SOGoMailConnectionEntry.h" +#include "SOGoMailboxInfo.h" #include "common.h" @implementation SOGoMailConnectionEntry +static BOOL debugOn = NO; +static BOOL debugCache = NO; +static BOOL debugKeys = NO; +static BOOL alwaysSelect = NO; +static BOOL onlyFetchInbox = NO; +static NSString *imap4Separator = nil; + ++ (void)initialize { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + + debugOn = [ud boolForKey:@"SOGoEnableIMAP4Debug"]; + debugCache = [ud boolForKey:@"SOGoEnableIMAP4CacheDebug"]; + alwaysSelect = [ud boolForKey:@"SOGoAlwaysSelectIMAP4Folder"]; + if (debugOn) NSLog(@"Note: SOGoEnableIMAP4Debug is enabled!"); + if (alwaysSelect) + NSLog(@"WARNING: 'SOGoAlwaysSelectIMAP4Folder' enabled (slow down)"); + + imap4Separator = [[ud stringForKey:@"SOGoIMAP4StringSeparator"] copy]; + if ([imap4Separator length] == 0) + imap4Separator = @"/"; + NSLog(@"Note(SOGoMailManager): using '%@' as the IMAP4 folder separator.", + imap4Separator); +} + - (id)initWithClient:(NGImap4Client *)_client password:(NSString *)_pwd { if (_client == nil || _pwd == nil) { [self release]; @@ -119,4 +144,732 @@ ASSIGN(self->cachedUIDs, nil); } +/* IMAP4 path/url processing methods */ + +NSArray *SOGoMailGetDirectChildren(NSArray *_array, NSString *_fn) { + /* + Scans string '_array' for strings which start with the string in '_fn'. + Then split on '/'. + */ + NSMutableArray *ma; + unsigned i, count, prefixlen; + + if ((count = [_array count]) < 2) + /* one entry is the folder itself, so we need at least two */ + return [NSArray array]; + +#if __APPLE__ + // TODO: somehow results are different on OSX + prefixlen = [_fn isEqualToString:@""] ? 0 : [_fn length] + 1; +#else + prefixlen = [_fn isEqualToString:@"/"] ? 1 : [_fn length] + 1; +#endif + ma = [NSMutableArray arrayWithCapacity:count]; + for (i = 0; i < count; i++) { + NSString *p; + + p = [_array objectAtIndex:i]; + if ([p length] <= prefixlen) + continue; + if (prefixlen != 0 && ![p hasPrefix:_fn]) + continue; + + /* cut of common part */ + p = [p substringFromIndex:prefixlen]; + + /* check whether the path is a sub-subfolder path */ + if ([p rangeOfString:@"/"].length > 0) + continue; + + [ma addObject:p]; + } + + [ma sortUsingSelector:@selector(compare:)]; + return ma; +} + +NSArray *SOGoMailExtractSubfolders(NSURL *_url, NSDictionary *_result) { + NSString *folderName; + NSDictionary *result; + NSArray *names; + NSArray *flags; + + /* Note: the result is normalized, that is, it contains / as the separator */ + folderName = [_url path]; +#if __APPLE__ + /* normalized results already have the / in front on libFoundation?! */ + if ([folderName hasPrefix:@"/"]) + folderName = [folderName substringFromIndex:1]; +#endif + + result = [_result valueForKey:@"list"]; + + /* Cyrus already tells us whether we need to check for children */ + flags = [result objectForKey:folderName]; + if ([flags containsObject:@"hasnochildren"]) { + if (debugKeys) + NSLog(@"%s: folder %@ has no children.", __PRETTY_FUNCTION__,folderName); + return nil; + } + + if (debugKeys) { + NSLog(@"%s: all keys %@: %@", __PRETTY_FUNCTION__, folderName, + [[result allKeys] componentsJoinedByString:@", "]); + } + + names = SOGoMailGetDirectChildren([result allKeys], folderName); + if (debugKeys) { + NSLog(@"%s: subfolders of '%@': %@", __PRETTY_FUNCTION__, folderName, + [names componentsJoinedByString:@","]); + } + return names; +} + +- (NSArray *)_getDirectChildren:(NSArray *)_array folderName:(NSString *)_fn { + return SOGoMailGetDirectChildren(_array, _fn); +} +- (NSArray *)extractSubfoldersForURL:(NSURL *)_url + fromResultSet:(NSDictionary *)_result +{ + return SOGoMailExtractSubfolders(_url, _result); +} + +- (NSString *)imap4Separator { + // TODO: make server specific ivar! + return imap4Separator; +} + +- (NSString *)imap4FolderNameForURL:(NSURL *)_url removeFileName:(BOOL)_delfn { + /* a bit hackish, but should be OK */ + NSString *folderName; + NSArray *names; + + if (_url == nil) + return nil; + + folderName = [_url path]; + if ([folderName length] == 0) + return nil; + if ([folderName characterAtIndex:0] == '/') + folderName = [folderName substringFromIndex:1]; + + if (_delfn) folderName = [folderName stringByDeletingLastPathComponent]; + + if ([[self imap4Separator] isEqualToString:@"/"]) + return folderName; + + names = [folderName pathComponents]; + return [names componentsJoinedByString:[self imap4Separator]]; +} +- (NSString *)imap4FolderNameForURL:(NSURL *)_url { + return [self imap4FolderNameForURL:_url removeFileName:NO]; +} + +- (NSArray *)extractFoldersFromResultSet:(NSDictionary *)_result { + /* Note: the result is normalized, that is, it contains / as the separator */ + return [[_result valueForKey:@"list"] allKeys]; +} + +/* folder selections */ + +- (BOOL)selectFolder:(id)_url { + NSDictionary *result; + NSString *newFolder; + + newFolder = [_url isKindOfClass:[NSURL class]] + ? [self imap4FolderNameForURL:_url] + : _url; + + if (!alwaysSelect) { + if ([[[self client] selectedFolderName] isEqualToString:newFolder]) + return YES; + } + + result = [[self client] select:newFolder]; + if (![[result valueForKey:@"result"] boolValue]) { + [self errorWithFormat:@"could not select URL: %@: %@", _url, result]; + return NO; + } + + return YES; +} + +- (BOOL)isPermissionDeniedResult:(id)_result { + if ([[_result valueForKey:@"result"] intValue] != 0) + return NO; + + return [[_result valueForKey:@"reason"] + isEqualToString:@"Permission denied"]; +} + +/* folder operations */ + +- (NSArray *)subfoldersForURL:(NSURL *)_url { + NSDictionary *result; + + /* check hierarchy cache */ + + if ((result = [self cachedHierarchyResults]) != nil) + return [self extractSubfoldersForURL:_url fromResultSet:result]; + + [self debugWithFormat:@" no folders cached yet .."]; + + /* fetch _all_ folders */ + + result = [[self client] list:(onlyFetchInbox ? @"INBOX" : @"*") + pattern:@"*"]; + if (![[result valueForKey:@"result"] boolValue]) { + [self errorWithFormat:@"listing of folder failed!"]; + return nil; + } + + /* cache results */ + + if ([result isNotNull]) { + [self cacheHierarchyResults:result]; + if (debugCache) { + [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)", + self, result, [result count]]; + } + } + + /* extract list */ + + return [self extractSubfoldersForURL:_url fromResultSet:result]; +} + +- (NSArray *)allFoldersForURL:(NSURL *)_url { + NSDictionary *result; + + /* check hierarchy cache */ + + if ((result = [self cachedHierarchyResults]) != nil) + return [self extractFoldersFromResultSet:result]; + + [self debugWithFormat:@" no folders cached yet .."]; + + /* fetch _all_ folders */ + + result = [[self client] list:@"INBOX" pattern:@"*"]; + if (![[result valueForKey:@"result"] boolValue]) { + [self logWithFormat:@"ERROR: listing of folder failed!"]; + return nil; + } + + /* cache results */ + + if ([result isNotNull]) { + [self cacheHierarchyResults:result]; + if (debugCache) { + [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)", + self, result, [result count]]; + } + } + + /* extract list */ + return [self extractFoldersFromResultSet:result]; +} + +/* message operations */ + +- (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_qualifier + sortOrdering:(id)_so +{ + /* + sortOrdering can be an NSString, an EOSortOrdering or an array of EOS. + */ + NSDictionary *result; + NSArray *uids; + + /* check cache */ + + uids = [self cachedUIDsForURL:_url qualifier:_qualifier sortOrdering:_so]; + if (uids != nil) { + if (debugCache) [self logWithFormat:@"reusing uid cache!"]; + return [uids isNotNull] ? uids : nil; + } + + /* select folder and fetch */ + + if (![self selectFolder:_url]) + return nil; + + result = [[self client] sort:_so qualifier:_qualifier encoding:@"UTF-8"]; + if (![[result valueForKey:@"result"] boolValue]) { + [self errorWithFormat:@"could not sort contents of URL: %@", _url]; + return nil; + } + + uids = [result valueForKey:@"sort"]; + if (![uids isNotNull]) { + [self errorWithFormat:@"got no UIDs for URL: %@: %@", _url, result]; + return nil; + } + + /* cache */ + + [self cacheUIDs:uids forURL:_url qualifier:_qualifier sortOrdering:_so]; + return uids; +} + +- (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url + parts:(NSArray *)_parts +{ + // currently returns a dict?! + /* + Allowed fetch keys: + UID + BODY.PEEK[
]<> + BODY [this is the bodystructure, supported] + BODYSTRUCTURE [not supported yet!] + ENVELOPE [this is a parsed header, but does not include type] + FLAGS + INTERNALDATE + RFC822 + RFC822.HEADER + RFC822.SIZE + RFC822.TEXT + */ + NSDictionary *result; + + if (_uids == nil) + return nil; + if ([_uids count] == 0) + return nil; // TODO: might break empty folders?! return a dict! + + /* select folder */ + + if (![self selectFolder:_url]) + return nil; + + /* fetch parts */ + + // TODO: split uids into batches, otherwise Cyrus will complain + // => not really important because we batch before (in the sort) + // if the list is too long, we get a: + // "* BYE Fatal error: word too long" + + result = [[self client] fetchUids:_uids parts:_parts]; + if (![[result valueForKey:@"result"] boolValue]) { + [self errorWithFormat:@"could not fetch %d uids for url: %@", + [_uids count],_url]; + return nil; + } + + //[self logWithFormat:@"RESULT: %@", result]; + return (id)result; +} + +- (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts { + // currently returns a dict + NSDictionary *result; + NSString *uid; + + if (![_url isNotNull]) return nil; + + /* select folder */ + + uid = [self imap4FolderNameForURL:_url removeFileName:YES]; + if (![self selectFolder:uid]) + return nil; + + /* fetch parts */ + + uid = [[_url path] lastPathComponent]; + + result = [client fetchUids:[NSArray arrayWithObject:uid] parts:_parts]; + if (![[result valueForKey:@"result"] boolValue]) { + [self errorWithFormat:@"could not fetch url: %@", _url]; + return nil; + } + //[self logWithFormat:@"RESULT: %@", result]; + return (id)result; +} + +- (NSData *)fetchContentOfBodyPart:(NSString *)_partId atURL:(NSURL *)_url { + NSString *key; + NSArray *parts; + id result, fetch, body; + + if (_partId == nil) return nil; + + key = [@"body[" stringByAppendingString:_partId]; + key = [key stringByAppendingString:@"]"]; + parts = [NSArray arrayWithObjects:&key count:1]; + + /* fetch */ + + result = [self fetchURL:_url parts:parts]; + + /* process results */ + + result = [result objectForKey:@"fetch"]; + if ([result count] == 0) { /* did not find part */ + [self errorWithFormat:@"did not find part: %@", _partId]; + return nil; + } + + fetch = [result objectAtIndex:0]; + if ((body = [fetch objectForKey:@"body"]) == nil) { + [self errorWithFormat:@"did not find body in response: %@", result]; + return nil; + } + + if ((result = [body objectForKey:@"data"]) == nil) { + [self errorWithFormat:@"did not find data in body: %@", fetch]; + return nil; + } + return result; +} + +/* message flags */ + +- (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f toURL:(NSURL *)_url { + id result; + + if (![_url isNotNull]) return nil; + if (![_f isNotNull]) return nil; + + if (![_f isKindOfClass:[NSArray class]]) + _f = [NSArray arrayWithObjects:&_f count:1]; + + /* select folder */ + + if (![self selectFolder: + [self imap4FolderNameForURL:_url removeFileName:YES]]) { + return [NSException exceptionWithHTTPStatus:404 /* server error */ + reason:@"could not select IMAP4 folder"]; + } + + /* store flags */ + + result = [[self client] storeUid:[[[_url path] lastPathComponent] intValue] + add:[NSNumber numberWithBool:_flag] + flags:_f]; + if (![[result valueForKey:@"result"] boolValue]) { + unsigned int status; + NSString *r; + + r = [result valueForKey:@"reason"]; + if ([r isEqualToString:@"Permission denied"]) { + /* different for each server?, no error codes in IMAP4 ... */ + status = 403 /* Forbidden */; + } + else + status = 500 /* internal server error */; + + [self logWithFormat:@"DEBUG: fail result %@", result]; + + r = [@"Failed to add flag to IMAP4 message: " stringByAppendingString:r]; + + return [NSException exceptionWithHTTPStatus:status /* server error */ + reason:r]; + } + /* result contains 'fetch' key with the current flags */ + return nil; +} +- (NSException *)addFlags:(id)_f toURL:(NSURL *)_u { + return [self addOrRemove:YES flags:_f toURL:_u]; +} +- (NSException *)removeFlags:(id)_f toURL:(NSURL *)_u { + return [self addOrRemove:NO flags:_f toURL:_u]; +} + +- (NSException *)markURLDeleted:(NSURL *)_url { + return [self addOrRemove:YES flags:@"Deleted" toURL:_url]; +} + +- (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url { + id result; + + if (![_url isNotNull]) return nil; + if (![_f isNotNull]) return nil; + + if (![_f isKindOfClass:[NSArray class]]) + _f = [NSArray arrayWithObjects:&_f count:1]; + + /* select folder */ + + if (![self selectFolder:[self imap4FolderNameForURL:_url]]) { + return [NSException exceptionWithHTTPStatus:404 /* not found */ + reason:@"could not select IMAP4 folder"]; + } + + /* fetch all sequence numbers */ + + result = [client searchWithQualifier:nil /* means: ALL */]; + if (![[result valueForKey:@"result"] boolValue]) { + return [NSException exceptionWithHTTPStatus:500 /* server error */ + reason:@"could not search in IMAP4 folder"]; + } + + result = [result valueForKey:@"search"]; + if ([result count] == 0) /* no messages in there, nothin' to be done */ + return nil; + + /* store flags */ + + result = [[self client] storeFlags:_f forMSNs:result addOrRemove:YES]; + if (![[result valueForKey:@"result"] boolValue]) { + return [NSException exceptionWithHTTPStatus:500 /* server error */ + reason:@"could not change flags in IMAP4 folder"]; + } + + return nil; +} + +/* posting new data */ + +- (NSException *)postData:(NSData *)_data flags:(id)_f + toFolderURL:(NSURL *)_url +{ + id result; + + if (![_url isNotNull]) return nil; + if (![_f isNotNull]) _f = [NSArray array]; + + if (![_f isKindOfClass:[NSArray class]]) + _f = [NSArray arrayWithObjects:&_f count:1]; + + result = [[self client] append:_data + toFolder:[self imap4FolderNameForURL:_url] + withFlags:_f]; + if (![[result valueForKey:@"result"] boolValue]) { + [self logWithFormat:@"DEBUG: fail result %@", result]; + return [NSException exceptionWithHTTPStatus:500 /* server error */ + reason:@"failed to store message to IMAP4 message"]; + } + /* result contains 'fetch' key with the current flags */ + + // TODO: need to flush any caches? + return nil; +} + +/* operations */ + +- (NSException *)expungeAtURL:(NSURL *)_url { + NSString *p; + id result; + + /* select folder */ + + p = [self imap4FolderNameForURL:_url removeFileName:NO]; + if (![self selectFolder:p]) { + return [NSException exceptionWithHTTPStatus:501 /* Server Error */ + reason:@"could not select IMAP4 folder!"]; + } + + /* expunge */ + + result = [[self client] expunge]; + + if (![[result valueForKey:@"result"] boolValue]) { + [self errorWithFormat:@"could not expunge url: %@", _url]; + return nil; + } + //[self logWithFormat:@"RESULT: %@", result]; + return nil; +} + +/* copying and moving */ + +- (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl { + NSString *srcname, *destname; + unsigned uid; + id result; + + /* names */ + + srcname = [self imap4FolderNameForURL:_srcurl removeFileName:YES]; + uid = [[[_srcurl path] lastPathComponent] unsignedIntValue]; + destname = [self imap4FolderNameForURL:_desturl]; + + /* select source folder */ + + if (![self selectFolder:srcname]) { + return [NSException exceptionWithHTTPStatus:404 /* Not Found */ + reason:@"did not find source folder"]; + } + + /* copy */ + + result = [[self client] copyUid:uid toFolder:destname]; + if (![[result valueForKey:@"result"] boolValue]) { + return [NSException exceptionWithHTTPStatus:500 /* Server Error */ + reason:@"copy operation failed"]; + } + + // TODO: need to flush some caches? + + return nil; +} + +/* managing folders */ + +- (BOOL)doesMailboxExistAtURL:(NSURL *)_url { + NSString *folderName; + id result; + + /* check in hierarchy cache */ + + if ((result = [self cachedHierarchyResults]) != nil) { + result = [result objectForKey:@"list"]; + return ([result objectForKey:[_url path]] != nil) ? YES : NO; + } + + /* check using IMAP4 select */ + // TODO: we should probably just fetch the whole hierarchy? + + folderName = [self imap4FolderNameForURL:_url]; + result = [[self client] select:folderName]; + if (![[result valueForKey:@"result"] boolValue]) + return NO; + + return YES; +} + +- (id)infoForMailboxAtURL:(NSURL *)_url { + SOGoMailboxInfo *info; + NSString *folderName; + id result; + + folderName = [self imap4FolderNameForURL:_url]; + result = [[self client] select:folderName]; + if (![[result valueForKey:@"result"] boolValue]) { + return [NSException exceptionWithHTTPStatus:404 /* Not Found */ + reason:@"did not find IMAP4 folder (select failed)"]; + } + + info = [[SOGoMailboxInfo alloc] initWithURL:_url folderName:folderName + selectDictionary:result]; + return [info autorelease]; +} + +- (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url { + NSString *newPath; + id result; + + /* construct path */ + + newPath = [self imap4FolderNameForURL:_url]; + newPath = [newPath stringByAppendingString:[self imap4Separator]]; + newPath = [newPath stringByAppendingString:_mailbox]; + + /* create */ + + result = [[self client] create:newPath]; + if ([self isPermissionDeniedResult:result]) { + return [NSException exceptionWithHTTPStatus:403 /* forbidden */ + reason:@"creation of folders not allowed"]; + } + else if ([[result valueForKey:@"result"] intValue] == 0) { + return [NSException exceptionWithHTTPStatus:500 /* server error */ + reason:[result valueForKey:@"reason"]]; + } + + [self flushFolderHierarchyCache]; + // [self debugWithFormat:@"created mailbox: %@: %@", newPath, result]; + return nil; +} + +- (NSException *)deleteMailboxAtURL:(NSURL *)_url { + NSString *path; + id result; + + /* delete */ + + path = [self imap4FolderNameForURL:_url]; + result = [[self client] delete:path]; + + if ([self isPermissionDeniedResult:result]) { + return [NSException exceptionWithHTTPStatus:403 /* forbidden */ + reason:@"creation of folders not allowed"]; + } + else if ([[result valueForKey:@"result"] intValue] == 0) { + return [NSException exceptionWithHTTPStatus:500 /* server error */ + reason:[result valueForKey:@"reason"]]; + } + + [self flushFolderHierarchyCache]; +#if 0 + [self debugWithFormat:@"delete mailbox %@: %@", _url, result]; +#endif + return nil; +} + +- (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl { + NSString *srcname, *destname; + id result; + + /* rename */ + + srcname = [self imap4FolderNameForURL:_srcurl]; + destname = [self imap4FolderNameForURL:_desturl]; + + result = [[self client] rename:srcname to:destname]; + + if ([self isPermissionDeniedResult:result]) { + return [NSException exceptionWithHTTPStatus:403 /* forbidden */ + reason:@"creation of folders not allowed"]; + } + else if ([[result valueForKey:@"result"] intValue] == 0) { + return [NSException exceptionWithHTTPStatus:500 /* server error */ + reason:[result valueForKey:@"reason"]]; + } + + [self flushFolderHierarchyCache]; +#if 0 + [self debugWithFormat:@"renamed mailbox %@: %@", _srcurl, result]; +#endif + return nil; +} + +/* ACLs */ + +- (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url { + /* + Returns a mapping of uid => permission strings, eg: + guizmo.g = lrs; + root = lrswipcda; + */ + NSString *folderName; + id result; + + folderName = [self imap4FolderNameForURL:_url]; + result = [[self client] getACL:folderName]; + if (![[result valueForKey:@"result"] boolValue]) { + [self logWithFormat:@"ERROR: getacl failed: %@", result]; + return [NSException exceptionWithHTTPStatus:404 /* Not Found */ + reason:@"did not find ACL for IMAP4 folder"]; + } + + return [result valueForKey:@"acl"]; +} + +- (NSString *)myRightsForMailboxAtURL:(NSURL *)_url { + NSString *folderName; + id result; + + /* check cache */ + + if ((result = [self cachedMyRightsForURL:_url]) != nil) + return result; + + /* run IMAP4 op */ + + folderName = [self imap4FolderNameForURL:_url]; + result = [[self client] myRights:folderName]; + if (![[result valueForKey:@"result"] boolValue]) { + [self logWithFormat:@"ERROR: myrights failed: %@", result]; + return [NSException exceptionWithHTTPStatus:404 /* Not Found */ + reason:@"did not find myrights for IMAP4 folder"]; + } + + /* cache results */ + + if ((result = [result valueForKey:@"myrights"]) != nil) + [self cacheMyRights:result forURL:_url]; + return result; +} + @end /* SOGoMailConnectionEntry */ diff --git a/SOGo/SoObjects/Mailer/SOGoMailManager.h b/SOGo/SoObjects/Mailer/SOGoMailManager.h index e7931c89..7cd9f7ed 100644 --- a/SOGo/SoObjects/Mailer/SOGoMailManager.h +++ b/SOGo/SoObjects/Mailer/SOGoMailManager.h @@ -50,8 +50,6 @@ /* folder hierarchy */ -- (NSString *)imap4Separator; -- (NSString *)imap4FolderNameForURL:(NSURL *)_url; - (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd; - (NSArray *)allFoldersForURL:(NSURL *)_url password:(NSString *)_pwd; diff --git a/SOGo/SoObjects/Mailer/SOGoMailManager.m b/SOGo/SoObjects/Mailer/SOGoMailManager.m index ea9e1fcf..0049d6bd 100644 --- a/SOGo/SoObjects/Mailer/SOGoMailManager.m +++ b/SOGo/SoObjects/Mailer/SOGoMailManager.m @@ -21,7 +21,6 @@ #include "SOGoMailManager.h" #include "SOGoMailConnectionEntry.h" -#include "SOGoMailboxInfo.h" #include "common.h" /* @@ -41,10 +40,7 @@ static BOOL debugOn = NO; static BOOL debugCache = NO; static BOOL debugKeys = NO; static BOOL poolingOff = NO; -static BOOL alwaysSelect = NO; -static BOOL onlyFetchInbox = NO; static NSTimeInterval PoolScanInterval = 5 * 60 /* every five minutes */; -static NSString *imap4Separator = nil; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; @@ -52,19 +48,9 @@ static NSString *imap4Separator = nil; debugOn = [ud boolForKey:@"SOGoEnableIMAP4Debug"]; debugCache = [ud boolForKey:@"SOGoEnableIMAP4CacheDebug"]; poolingOff = [ud boolForKey:@"SOGoDisableIMAP4Pooling"]; - alwaysSelect = [ud boolForKey:@"SOGoAlwaysSelectIMAP4Folder"]; if (debugOn) NSLog(@"Note: SOGoEnableIMAP4Debug is enabled!"); if (poolingOff) NSLog(@"WARNING: IMAP4 connection pooling is disabled!"); - - if (alwaysSelect) - NSLog(@"WARNING: 'SOGoAlwaysSelectIMAP4Folder' enabled (slow down)"); - - imap4Separator = [[ud stringForKey:@"SOGoIMAP4StringSeparator"] copy]; - if ([imap4Separator length] == 0) - imap4Separator = @"/"; - NSLog(@"Note(SOGoMailManager): using '%@' as the IMAP4 folder separator.", - imap4Separator); } + (id)defaultMailManager { @@ -161,6 +147,12 @@ static NSString *imap4Separator = nil; return [self entryForURL:_url]; } +- (NSException *)errorForMissingEntryAtURL:(NSURL *)_url { + // TODO: improve + return [NSException exceptionWithHTTPStatus:404 /* Not Found */ + reason:@"Did not find mail URL"]; +} + /* client object */ - (NGImap4Client *)imap4ClientForURL:(NSURL *)_url password:(NSString *)_pwd { @@ -226,149 +218,10 @@ static NSString *imap4Separator = nil; [entry flushMailCaches]; } -- (BOOL)selectFolder:(id)_url inClient:(NGImap4Client *)_client { - NSDictionary *result; - NSString *newFolder; - - newFolder = [_url isKindOfClass:[NSURL class]] - ? [self imap4FolderNameForURL:_url] - : _url; - - if (!alwaysSelect) { - if ([[_client selectedFolderName] isEqualToString:newFolder]) - return YES; - } - - result = [_client select:newFolder]; - if (![[result valueForKey:@"result"] boolValue]) { - [self errorWithFormat:@"could not select URL: %@: %@", _url, result]; - return NO; - } - - return YES; -} - /* folder hierarchy */ -- (NSArray *)_getDirectChildren:(NSArray *)_array folderName:(NSString *)_fn { - /* - Scans string '_array' for strings which start with the string in '_fn'. - Then split on '/'. - */ - NSMutableArray *ma; - unsigned i, count, prefixlen; - - if ((count = [_array count]) < 2) - /* one entry is the folder itself, so we need at least two */ - return [NSArray array]; - -#if __APPLE__ - // TODO: somehow results are different on OSX - prefixlen = [_fn isEqualToString:@""] ? 0 : [_fn length] + 1; -#else - prefixlen = [_fn isEqualToString:@"/"] ? 1 : [_fn length] + 1; -#endif - ma = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - NSString *p; - - p = [_array objectAtIndex:i]; - if ([p length] <= prefixlen) - continue; - if (prefixlen != 0 && ![p hasPrefix:_fn]) - continue; - - /* cut of common part */ - p = [p substringFromIndex:prefixlen]; - - /* check whether the path is a sub-subfolder path */ - if ([p rangeOfString:@"/"].length > 0) - continue; - - [ma addObject:p]; - } - - [ma sortUsingSelector:@selector(compare:)]; - return ma; -} - -- (NSString *)imap4Separator { - return imap4Separator; -} - -- (NSString *)imap4FolderNameForURL:(NSURL *)_url removeFileName:(BOOL)_delfn { - /* a bit hackish, but should be OK */ - NSString *folderName; - NSArray *names; - - if (_url == nil) - return nil; - - folderName = [_url path]; - if ([folderName length] == 0) - return nil; - if ([folderName characterAtIndex:0] == '/') - folderName = [folderName substringFromIndex:1]; - - if (_delfn) folderName = [folderName stringByDeletingLastPathComponent]; - - if ([[self imap4Separator] isEqualToString:@"/"]) - return folderName; - - names = [folderName pathComponents]; - return [names componentsJoinedByString:[self imap4Separator]]; -} -- (NSString *)imap4FolderNameForURL:(NSURL *)_url { - return [self imap4FolderNameForURL:_url removeFileName:NO]; -} - -- (NSArray *)extractSubfoldersForURL:(NSURL *)_url - fromResultSet:(NSDictionary *)_result -{ - NSString *folderName; - NSDictionary *result; - NSArray *names; - NSArray *flags; - - /* Note: the result is normalized, that is, it contains / as the separator */ - folderName = [_url path]; -#if __APPLE__ - /* normalized results already have the / in front on libFoundation?! */ - if ([folderName hasPrefix:@"/"]) - folderName = [folderName substringFromIndex:1]; -#endif - - result = [_result valueForKey:@"list"]; - - /* Cyrus already tells us whether we need to check for children */ - flags = [result objectForKey:folderName]; - if ([flags containsObject:@"hasnochildren"]) { - if (debugKeys) - [self logWithFormat:@"folder %@ has no children.", folderName]; - return nil; - } - - if (debugKeys) { - [self logWithFormat:@"all keys %@: %@", folderName, - [[result allKeys] componentsJoinedByString:@", "]]; - } - - names = [self _getDirectChildren:[result allKeys] folderName:folderName]; - if (debugKeys) { - [self logWithFormat:@"subfolders of '%@': %@", folderName, - [names componentsJoinedByString:@","]]; - } - return names; -} - -- (NSArray *)extractFoldersFromResultSet:(NSDictionary *)_result { - /* Note: the result is normalized, that is, it contains / as the separator */ - return [[_result valueForKey:@"list"] allKeys]; -} - - (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - NSDictionary *result; if (debugKeys) [self debugWithFormat:@"subfolders for URL: %@ ...",[_url absoluteString]]; @@ -378,44 +231,12 @@ static NSString *imap4Separator = nil; if ((entry = [self entryForURL:_url password:_pwd]) == nil) return nil; - /* check hierarchy cache */ - - if ((result = [entry cachedHierarchyResults]) != nil) - return [self extractSubfoldersForURL:_url fromResultSet:result]; - - [self debugWithFormat:@" no folders cached yet .."]; - - /* fetch _all_ folders */ - - result = [[entry client] list:(onlyFetchInbox ? @"INBOX" : @"*") - pattern:@"*"]; - if (![[result valueForKey:@"result"] boolValue]) { - [self errorWithFormat:@"listing of folder failed!"]; - return nil; - } - - /* cache results */ - - if ([result isNotNull]) { - if (entry == nil) /* required in case the entry was not setup */ - entry = [self entryForURL:_url]; - - [entry cacheHierarchyResults:result]; - if (debugCache) { - [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)", - entry, result, [result count]]; - } - } - - /* extract list */ - - return [self extractSubfoldersForURL:_url fromResultSet:result]; + return [entry subfoldersForURL:_url]; } - (NSArray *)allFoldersForURL:(NSURL *)_url password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - NSDictionary *result; - + if (debugKeys) [self debugWithFormat:@"folders for URL: %@ ...",[_url absoluteString]]; @@ -424,36 +245,7 @@ static NSString *imap4Separator = nil; if ((entry = [self entryForURL:_url password:_pwd]) == nil) return nil; - /* check hierarchy cache */ - - if ((result = [entry cachedHierarchyResults]) != nil) - return [self extractFoldersFromResultSet:result]; - - [self debugWithFormat:@" no folders cached yet .."]; - - /* fetch _all_ folders */ - - result = [[entry client] list:@"INBOX" pattern:@"*"]; - if (![[result valueForKey:@"result"] boolValue]) { - [self logWithFormat:@"ERROR: listing of folder failed!"]; - return nil; - } - - /* cache results */ - - if ([result isNotNull]) { - if (entry == nil) /* required in case the entry was not setup */ - entry = [self entryForURL:_url]; - - [entry cacheHierarchyResults:result]; - if (debugCache) { - [self logWithFormat:@"cached results in entry %@: 0x%08X(%d)", - entry, result, [result count]]; - } - } - - /* extract list */ - return [self extractFoldersFromResultSet:result]; + return [entry allFoldersForURL:_url]; } /* messages */ @@ -465,43 +257,12 @@ static NSString *imap4Separator = nil; sortOrdering can be an NSString, an EOSortOrdering or an array of EOS. */ SOGoMailConnectionEntry *entry; - NSDictionary *result; - NSArray *uids; /* check connection cache */ - if ((entry = [self entryForURL:_url password:_pwd]) == nil) return nil; - /* check cache */ - - uids = [entry cachedUIDsForURL:_url qualifier:_qualifier sortOrdering:_so]; - if (uids != nil) { - if (debugCache) [self logWithFormat:@"reusing uid cache!"]; - return [uids isNotNull] ? uids : nil; - } - - /* select folder and fetch */ - - if (![self selectFolder:_url inClient:[entry client]]) - return nil; - - result = [[entry client] sort:_so qualifier:_qualifier encoding:@"UTF-8"]; - if (![[result valueForKey:@"result"] boolValue]) { - [self errorWithFormat:@"could not sort contents of URL: %@", _url]; - return nil; - } - - uids = [result valueForKey:@"sort"]; - if (![uids isNotNull]) { - [self errorWithFormat:@"got no UIDs for URL: %@: %@", _url, result]; - return nil; - } - - /* cache */ - - [entry cacheUIDs:uids forURL:_url qualifier:_qualifier sortOrdering:_so]; - return uids; + return [entry fetchUIDsInURL:_url qualifier:_qualifier sortOrdering:_so]; } - (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url @@ -522,187 +283,59 @@ static NSString *imap4Separator = nil; RFC822.SIZE RFC822.TEXT */ - NGImap4Client *client; - NSDictionary *result; + SOGoMailConnectionEntry *entry; if (_uids == nil) return nil; if ([_uids count] == 0) return nil; // TODO: might break empty folders?! return a dict! - if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil) - return nil; - - /* select folder */ - - if (![self selectFolder:_url inClient:client]) - return nil; - - /* fetch parts */ - - // TODO: split uids into batches, otherwise Cyrus will complain - // => not really important because we batch before (in the sort) - // if the list is too long, we get a: - // "* BYE Fatal error: word too long" - - result = [client fetchUids:_uids parts:_parts]; - if (![[result valueForKey:@"result"] boolValue]) { - [self errorWithFormat:@"could not fetch %d uids for url: %@", - [_uids count],_url]; + /* check connection cache */ + if ((entry = [self entryForURL:_url password:_pwd]) == nil) return nil; - } - //[self logWithFormat:@"RESULT: %@", result]; - return (id)result; + return [entry fetchUIDs:_uids inURL:_url parts:_parts]; } - (NSException *)expungeAtURL:(NSURL *)_url password:(NSString *)_pwd { - NGImap4Client *client; - NSString *p; - id result; - - if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil) - return nil; // TODO: return error? - - /* select folder */ - - p = [self imap4FolderNameForURL:_url removeFileName:NO]; - if (![self selectFolder:p inClient:client]) - return nil; + SOGoMailConnectionEntry *entry; - /* expunge */ + if ((entry = [self entryForURL:_url password:_pwd]) == nil) + return [self errorForMissingEntryAtURL:_url]; - result = [client expunge]; - - if (![[result valueForKey:@"result"] boolValue]) { - [self errorWithFormat:@"could not expunge url: %@", _url]; - return nil; - } - //[self logWithFormat:@"RESULT: %@", result]; - return nil; + return [entry expungeAtURL:_url]; } - (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts password:(NSString *)_pwd{ - // currently returns a dict - NGImap4Client *client; - NSDictionary *result; - NSString *uid; + SOGoMailConnectionEntry *entry; if (![_url isNotNull]) return nil; + if ((entry = [self entryForURL:_url password:_pwd]) == nil) + return [self errorForMissingEntryAtURL:_url]; - if ((client = [self imap4ClientForURL:_url password:_pwd]) == nil) - return nil; - - /* select folder */ - - if (![self selectFolder:[self imap4FolderNameForURL:_url removeFileName:YES] - inClient:client]) - return nil; - - /* fetch parts */ - - uid = [[_url path] lastPathComponent]; - - result = [client fetchUids:[NSArray arrayWithObject:uid] parts:_parts]; - if (![[result valueForKey:@"result"] boolValue]) { - [self errorWithFormat:@"could not fetch url: %@", _url]; - return nil; - } - //[self logWithFormat:@"RESULT: %@", result]; - return (id)result; + return [entry fetchURL:_url parts:_parts]; } - (NSData *)fetchContentOfBodyPart:(NSString *)_partId atURL:(NSURL *)_url password:(NSString *)_pwd { - NSString *key; - NSArray *parts; - id result, fetch, body; - - if (_partId == nil) return nil; - - key = [@"body[" stringByAppendingString:_partId]; - key = [key stringByAppendingString:@"]"]; - parts = [NSArray arrayWithObjects:&key count:1]; - - /* fetch */ - - result = [self fetchURL:_url parts:parts password:_pwd]; - - /* process results */ - - result = [result objectForKey:@"fetch"]; - if ([result count] == 0) { /* did not find part */ - [self errorWithFormat:@"did not find part: %@", _partId]; - return nil; - } - - fetch = [result objectAtIndex:0]; - if ((body = [fetch objectForKey:@"body"]) == nil) { - [self errorWithFormat:@"did not find body in response: %@", result]; - return nil; - } - - if ((result = [body objectForKey:@"data"]) == nil) { - [self errorWithFormat:@"did not find data in body: %@", fetch]; - return nil; - } - return result; + SOGoMailConnectionEntry *entry; + + if ((entry = [self entryForURL:_url password:_pwd]) == nil) + return nil; // TODO: improve? + + return [entry fetchContentOfBodyPart:_partId atURL:_url]; } - (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f toURL:(NSURL *)_url password:(NSString *)_p { - NGImap4Client *client; - id result; - - if (![_url isNotNull]) return nil; - if (![_f isNotNull]) return nil; - - if (![_f isKindOfClass:[NSArray class]]) - _f = [NSArray arrayWithObjects:&_f count:1]; - - /* get client */ + SOGoMailConnectionEntry *entry; - if ((client = [self imap4ClientForURL:_url password:_p]) == nil) - // TODO: return 401? - return nil; - - /* select folder */ - - if (![self selectFolder:[self imap4FolderNameForURL:_url removeFileName:YES] - inClient:client]) { - return [NSException exceptionWithHTTPStatus:404 /* server error */ - reason:@"could not select IMAP4 folder"]; - return nil; - } - - /* store flags */ - - result = [client storeUid:[[[_url path] lastPathComponent] intValue] - add:[NSNumber numberWithBool:_flag] - flags:_f]; - if (![[result valueForKey:@"result"] boolValue]) { - unsigned int status; - NSString *r; - - r = [result valueForKey:@"reason"]; - if ([r isEqualToString:@"Permission denied"]) { - /* different for each server?, no error codes in IMAP4 ... */ - status = 403 /* Forbidden */; - } - else - status = 500 /* internal server error */; - - [self logWithFormat:@"DEBUG: fail result %@", result]; + if ((entry = [self entryForURL:_url password:_p]) == nil) + return [self errorForMissingEntryAtURL:_url]; - r = [@"Failed to add flag to IMAP4 message: " stringByAppendingString:r]; - - return [NSException exceptionWithHTTPStatus:status /* server error */ - reason:r]; - } - /* result contains 'fetch' key with the current flags */ - return nil; + return [entry addOrRemove:_flag flags:_f toURL:_url]; } - (NSException *)addFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p { return [self addOrRemove:YES flags:_f toURL:_u password:_p]; @@ -718,47 +351,25 @@ static NSString *imap4Separator = nil; - (NSException *)postData:(NSData *)_data flags:(id)_f toFolderURL:(NSURL *)_url password:(NSString *)_p { - NGImap4Client *client; - id result; - + SOGoMailConnectionEntry *entry; + if (![_url isNotNull]) return nil; - if (![_f isNotNull]) _f = [NSArray array]; - if ((client = [self imap4ClientForURL:_url password:_p]) == nil) - return nil; + if ((entry = [self entryForURL:_url password:_p]) == nil) + return [self errorForMissingEntryAtURL:_url]; - if (![_f isKindOfClass:[NSArray class]]) - _f = [NSArray arrayWithObjects:&_f count:1]; - - result = [client append:_data - toFolder:[self imap4FolderNameForURL:_url] - withFlags:_f]; - if (![[result valueForKey:@"result"] boolValue]) { - [self logWithFormat:@"DEBUG: fail result %@", result]; - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"failed to store message to IMAP4 message"]; - } - /* result contains 'fetch' key with the current flags */ - - // TODO: need to flush any caches? - return nil; + return [entry postData:_data flags:_f toFolderURL:_url]; } - (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - NSString *srcname, *destname; - unsigned uid; - id result; /* check connection cache */ - if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil) { - // TODO: better to use an auth exception? - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 folder (no entry)"]; - } + if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil) + return [self errorForMissingEntryAtURL:_srcurl]; /* check whether URLs are on different servers */ @@ -768,30 +379,7 @@ static NSString *imap4Separator = nil; reason:@"source and destination on different servers"]; } - /* names */ - - srcname = [self imap4FolderNameForURL:_srcurl removeFileName:YES]; - uid = [[[_srcurl path] lastPathComponent] unsignedIntValue]; - destname = [self imap4FolderNameForURL:_desturl]; - - /* select source folder */ - - if (![self selectFolder:srcname inClient:[entry client]]) { - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find source folder"]; - } - - /* copy */ - - result = [[entry client] copyUid:uid toFolder:destname]; - if (![[result valueForKey:@"result"] boolValue]) { - return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"copy operation failed"]; - } - - // TODO: need to flush some caches? - - return nil; + return [entry copyMailURL:_srcurl toFolderURL:_desturl]; } /* managing folders */ @@ -806,140 +394,54 @@ static NSString *imap4Separator = nil; - (BOOL)doesMailboxExistAtURL:(NSURL *)_url password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - NSString *folderName; - id result; if ((entry = [self entryForURL:_url password:_pwd]) == nil) return NO; - /* check in hierarchy cache */ - - if ((result = [entry cachedHierarchyResults]) != nil) { - result = [result objectForKey:@"list"]; - return ([result objectForKey:[_url path]] != nil) ? YES : NO; - } - - /* check using IMAP4 select */ - // TODO: we should probably just fetch the whole hierarchy? - - folderName = [self imap4FolderNameForURL:_url]; - result = [[entry client] select:folderName]; - if (![[result valueForKey:@"result"] boolValue]) - return NO; - - return YES; + return [entry doesMailboxExistAtURL:_url]; } - (id)infoForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - SOGoMailboxInfo *info; - NSString *folderName; - id result; - - if ((entry = [self entryForURL:_url password:_pwd]) == nil) { - // TODO: better to use an auth exception? - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 folder (no entry)"]; - } - folderName = [self imap4FolderNameForURL:_url]; - result = [[entry client] select:folderName]; - if (![[result valueForKey:@"result"] boolValue]) { - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 folder (select failed)"]; - } - - info = [[SOGoMailboxInfo alloc] initWithURL:_url folderName:folderName - selectDictionary:result]; - return [info autorelease]; + if ((entry = [self entryForURL:_url password:_pwd]) == nil) + return [self errorForMissingEntryAtURL:_url]; + + return [entry infoForMailboxAtURL:_url]; } - (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - NSString *newPath; - id result; - - /* check connection cache */ - if ((entry = [self entryForURL:_url password:_pwd]) == nil) { - // TODO: better to use an auth exception? - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 folder (no entry)"]; - } + /* check connection cache */ + if ((entry = [self entryForURL:_url password:_pwd]) == nil) + return [self errorForMissingEntryAtURL:_url]; - /* construct path */ - - newPath = [self imap4FolderNameForURL:_url]; - newPath = [newPath stringByAppendingString:[self imap4Separator]]; - newPath = [newPath stringByAppendingString:_mailbox]; - - /* create */ - - result = [[entry client] create:newPath]; - if ([self isPermissionDeniedResult:result]) { - return [NSException exceptionWithHTTPStatus:403 /* forbidden */ - reason:@"creation of folders not allowed"]; - } - else if ([[result valueForKey:@"result"] intValue] == 0) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:[result valueForKey:@"reason"]]; - } - - [entry flushFolderHierarchyCache]; - // [self debugWithFormat:@"created mailbox: %@: %@", newPath, result]; - return nil; + return [entry createMailbox:_mailbox atURL:_url]; } - (NSException *)deleteMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - NSString *path; - id result; - - /* check connection cache */ - if ((entry = [self entryForURL:_url password:_pwd]) == nil) { - // TODO: better to use an auth exception? - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 folder (no entry)"]; - } - - /* delete */ - - path = [self imap4FolderNameForURL:_url]; - result = [[entry client] delete:path]; + /* check connection cache */ - if ([self isPermissionDeniedResult:result]) { - return [NSException exceptionWithHTTPStatus:403 /* forbidden */ - reason:@"creation of folders not allowed"]; - } - else if ([[result valueForKey:@"result"] intValue] == 0) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:[result valueForKey:@"reason"]]; - } + if ((entry = [self entryForURL:_url password:_pwd]) == nil) + return [self errorForMissingEntryAtURL:_url]; - [entry flushFolderHierarchyCache]; -#if 0 - [self debugWithFormat:@"delete mailbox %@: %@", _url, result]; -#endif - return nil; + return [entry deleteMailboxAtURL:_url]; } - (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - NSString *srcname, *destname; - id result; /* check connection cache */ - if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil) { - // TODO: better to use an auth exception? - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 folder (no entry)"]; - } + if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil) + return [self errorForMissingEntryAtURL:_srcurl]; /* check whether URLs are on different servers */ @@ -949,27 +451,7 @@ static NSString *imap4Separator = nil; reason:@"source and destination on different servers"]; } - /* rename */ - - srcname = [self imap4FolderNameForURL:_srcurl]; - destname = [self imap4FolderNameForURL:_desturl]; - - result = [[entry client] rename:srcname to:destname]; - - if ([self isPermissionDeniedResult:result]) { - return [NSException exceptionWithHTTPStatus:403 /* forbidden */ - reason:@"creation of folders not allowed"]; - } - else if ([[result valueForKey:@"result"] intValue] == 0) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:[result valueForKey:@"reason"]]; - } - - [entry flushFolderHierarchyCache]; -#if 0 - [self debugWithFormat:@"renamed mailbox %@: %@", _srcurl, result]; -#endif - return nil; + return [entry moveMailboxAtURL:_srcurl toURL:_desturl]; } - (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd { @@ -979,57 +461,20 @@ static NSString *imap4Separator = nil; root = lrswipcda; */ SOGoMailConnectionEntry *entry; - NSString *folderName; - id result; - if ((entry = [self entryForURL:_url password:_pwd]) == nil) { - // TODO: better to use an auth exception? - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 folder (no entry)"]; - } - - folderName = [self imap4FolderNameForURL:_url]; - result = [[entry client] getACL:folderName]; - if (![[result valueForKey:@"result"] boolValue]) { - [self logWithFormat:@"ERROR: getacl failed: %@", result]; - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find ACL for IMAP4 folder"]; - } + if ((entry = [self entryForURL:_url password:_pwd]) == nil) + return (id)[self errorForMissingEntryAtURL:_url]; - return [result valueForKey:@"acl"]; + return [entry aclForMailboxAtURL:_url]; } - (NSString *)myRightsForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd { SOGoMailConnectionEntry *entry; - NSString *folderName; - id result; - - if ((entry = [self entryForURL:_url password:_pwd]) == nil) { - // TODO: better to use an auth exception? - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find IMAP4 folder (no entry)"]; - } - - /* check cache */ - if ((result = [entry cachedMyRightsForURL:_url]) != nil) - return result; - - /* run IMAP4 op */ - - folderName = [self imap4FolderNameForURL:_url]; - result = [[entry client] myRights:folderName]; - if (![[result valueForKey:@"result"] boolValue]) { - [self logWithFormat:@"ERROR: myrights failed: %@", result]; - return [NSException exceptionWithHTTPStatus:404 /* Not Found */ - reason:@"did not find myrights for IMAP4 folder"]; - } + if ((entry = [self entryForURL:_url password:_pwd]) == nil) + return (id)[self errorForMissingEntryAtURL:_url]; - /* cache results */ - - if ((result = [result valueForKey:@"myrights"]) != nil) - [entry cacheMyRights:result forURL:_url]; - return result; + return [entry myRightsForMailboxAtURL:_url]; } /* bulk flag adding (eg used for empty/trash) */ @@ -1037,49 +482,15 @@ static NSString *imap4Separator = nil; - (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url password:(NSString *)_p { - NGImap4Client *client; - id result; + SOGoMailConnectionEntry *entry; if (![_url isNotNull]) return nil; if (![_f isNotNull]) return nil; - - if (![_f isKindOfClass:[NSArray class]]) - _f = [NSArray arrayWithObjects:&_f count:1]; - - /* get client */ - - if ((client = [self imap4ClientForURL:_url password:_p]) == nil) - // TODO: return 401? - return nil; - - /* select folder */ - - if (![self selectFolder:[self imap4FolderNameForURL:_url] inClient:client]) { - return [NSException exceptionWithHTTPStatus:404 /* not found */ - reason:@"could not select IMAP4 folder"]; - } - - /* fetch all sequence numbers */ - result = [client searchWithQualifier:nil /* means: ALL */]; - if (![[result valueForKey:@"result"] boolValue]) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not search in IMAP4 folder"]; - } - - result = [result valueForKey:@"search"]; - if ([result count] == 0) /* no messages in there, nothin' to be done */ - return nil; - - /* store flags */ - - result = [client storeFlags:_f forMSNs:result addOrRemove:YES]; - if (![[result valueForKey:@"result"] boolValue]) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not change flags in IMAP4 folder"]; - } + if ((entry = [self entryForURL:_url password:_p]) == nil) + return [self errorForMissingEntryAtURL:_url]; - return nil; + return [entry addFlags:_f toAllMessagesInURL:_url]; } /* debugging */ diff --git a/SOGo/SoObjects/Mailer/Version b/SOGo/SoObjects/Mailer/Version index c2722d2d..67dd1fc6 100644 --- a/SOGo/SoObjects/Mailer/Version +++ b/SOGo/SoObjects/Mailer/Version @@ -1,6 +1,6 @@ # Version file -SUBMINOR_VERSION:=94 +SUBMINOR_VERSION:=95 # v0.9.91 requires libNGMime v4.5.222 # v0.9.69 requires libNGMime v4.5.210 -- 2.39.2