From 5c60bd943f9b4c99dc2ff514817ee3b9062b98a0 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Fri, 17 Aug 2007 02:11:34 +0000 Subject: [PATCH] Monotone-Parent: 884877bd53c2d3dbf1a3a0b3b87f122ccdec9640 Monotone-Revision: ed5df203630a48a2ac353943b36f22b0ba1acd7c Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-08-17T02:11:34 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 32 + SoObjects/Mailer/SOGoDraftObject.h | 55 +- SoObjects/Mailer/SOGoDraftObject.m | 1161 +++++++++++++++------------- 3 files changed, 714 insertions(+), 534 deletions(-) diff --git a/ChangeLog b/ChangeLog index 92aae14f2..6264e27f1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,37 @@ 2007-08-16 Wolfgang Sourdeau + * SoObjects/Mailer/SOGoDraftObject.m ([SOGoDraftObject -init]): + new method, initializing the new ivars: IMAP4ID, headers, text, + sourceURL and sourceFlag. + ([-spoolFileManager], [SOGoDraftObject -userSpoolFolderPath]) + ([-_ensureUserSpoolFolderPath]) + ([-saveMimeMessageToTemporaryFileWithHeaders:]) + ([SOGoDraftObject -mimeMessageWithHeaders:_headers]) + ([-deleteTemporaryMessageFile:], [-delete], [-content]) + ([-GETAction:_ctx], [-DELETEAction:_ctx], [-fetchParts:]) + ([-uid], [-flags], [-size], [-envelope]): removed methods. + ([SOGoDraftObject -storeInfo]): rewrote method to take the new + ivars into account. + ([SOGoDraftObject -setSourceURL:newSourceURL]) + ([SOGoDraftObject -setSourceFlag:newSourceFlag]): new accessor + methods to store the url of the original message between accesses + so that, depending on the action taken (forward or reply), the + correct flag can be given. + ([SOGoDraftObject -setIMAP4ID:]): new accessor method that sets + the imap4 id that is returned for the message whenever it is saved + into the drafts folder. + ([SOGoDraftObject -save]): new method that takes in charge the + saving of the message in the IMAP drafts folder. + ([SOGoDraftObject -fetchMailForReplying:sourceMailtoAll:toAll]): + new method that fills the original data of the new message with + the reply content of the original message. + ([SOGoDraftObject -fetchMailForForwarding:sourceMail]): same as + above for message forwarding. + ([-spoolFileManager]): removed useless method (only returned the + default filemanager...) + ([SOGoDraftObject -mimeMessageAsData]): new method that returns + the resulting message as an NSData chunk. + * SoObjects/Mailer/SOGoMailObject+Draft.m: new extension module containing a rewrite of the mail action methods found in UI/Mailer/, that needed to be put in the SOGoMailObject class. diff --git a/SoObjects/Mailer/SOGoDraftObject.h b/SoObjects/Mailer/SOGoDraftObject.h index 2995f7227..95b7c5fc4 100644 --- a/SoObjects/Mailer/SOGoDraftObject.h +++ b/SoObjects/Mailer/SOGoDraftObject.h @@ -22,7 +22,7 @@ #ifndef __Mailer_SOGoDraftObject_H__ #define __Mailer_SOGoDraftObject_H__ -#include +#import "SOGoMailBaseObject.h" /* SOGoDraftsFolder @@ -36,20 +36,48 @@ TODO: store-info should be an own object, not NSDictionary. */ -@class NSString, NSArray, NSDictionary, NSData, NSException; -@class NGMimeMessage, NGImap4Envelope; +@class NSArray; +@class NSData; +@class NSDictionary; +@class NSException; +@class NGImap4Envelope; +@class NGMimeMessage; +@class NSMutableDictionary; +@class NSString; + +@class SOGoMailObject; @interface SOGoDraftObject : SOGoMailBaseObject { - NSString *path; - NSDictionary *info; /* stores the envelope information */ + NSString *path; NGImap4Envelope *envelope; + int IMAP4ID; + NSMutableDictionary *headers; + NSString *text; + NSString *sourceURL; + NSString *sourceFlag; } /* contents */ +- (void) fetchInfo; +- (NSException *) storeInfo; -- (NSDictionary *) fetchInfo; -- (NSException *) storeInfo: (NSDictionary *) _info; +- (void) fetchMailForEditing: (SOGoMailObject *) sourceMail; +- (void) fetchMailForReplying: (SOGoMailObject *) sourceMail + toAll: (BOOL) toAll; +- (void) fetchMailForForwarding: (SOGoMailObject *) sourceMail; + +- (void) setHeaders: (NSDictionary *) newHeaders; +- (NSDictionary *) headers; +- (void) setText: (NSString *) newText; +- (NSString *) text; + +/* for replies and forwards */ +- (void) setSourceURL: (NSString *) newSurceURL; +- (void) setSourceFlag: (NSString *) newSourceFlag; + +- (void) setIMAP4ID: (int) newIMAPID; +- (int) IMAP4ID; /* attachments */ @@ -62,19 +90,16 @@ /* NGMime representations */ - (NGMimeMessage *) mimeMessage; - -- (NSString *) saveMimeMessageToTemporaryFile; -- (NSString *) saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_addh; - -- (NSException *) sendMail; +- (NSData *) mimeMessageAsData; /* operations */ -- (NSException *) delete; +- (NSException *) sendMail; +- (NSException *) save; -/* fake being a SOGoMailObject */ +// /* fake being a SOGoMailObject */ -- (id) fetchParts: (NSArray *) _parts; +// - (id) fetchParts: (NSArray *) _parts; @end diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 1d627aba1..e42ec5e4b 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -23,6 +23,7 @@ #import #import #import +#import #import #import @@ -37,6 +38,9 @@ #import #import #import +#import +#import +#import #import #import #import @@ -49,10 +53,16 @@ #import #import +#import "SOGoMailAccount.h" +#import "SOGoMailFolder.h" +#import "SOGoMailObject.h" +#import "SOGoMailObject+Draft.h" #import "SOGoDraftObject.h" static NSString *contentTypeValue = @"text/plain; charset=utf-8"; +static NSString *headerKeys[] = {@"subject", @"to", @"cc", @"bcc", + @"from", @"replyTo", nil}; @interface NSString (NGMimeHelpers) @@ -88,155 +98,425 @@ static BOOL draftDeleteDisabled = NO; // for debugging static BOOL debugOn = NO; static BOOL showTextAttachmentsInline = NO; -+ (void)initialize { ++ (void) initialize +{ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; /* Note: be aware of the charset issues before enabling this! */ - showTextAttachmentsInline = [ud boolForKey:@"SOGoShowTextAttachmentsInline"]; + showTextAttachmentsInline = [ud boolForKey: @"SOGoShowTextAttachmentsInline"]; - if ((draftDeleteDisabled = [ud boolForKey:@"SOGoNoDraftDeleteAfterSend"])) + if ((draftDeleteDisabled = [ud boolForKey: @"SOGoNoDraftDeleteAfterSend"])) NSLog(@"WARNING: draft delete is disabled! (SOGoNoDraftDeleteAfterSend)"); - TextPlainType = [[NGMimeType mimeType:@"text" subType:@"plain"] copy]; - MultiMixedType = [[NGMimeType mimeType:@"multipart" subType:@"mixed"] copy]; + TextPlainType = [[NGMimeType mimeType: @"text" subType: @"plain"] copy]; + MultiMixedType = [[NGMimeType mimeType: @"multipart" subType: @"mixed"] copy]; } -- (void)dealloc { +- (id) init +{ + if ((self = [super init])) + { + IMAP4ID = -1; + headers = [NSMutableDictionary new]; + text = @""; + sourceURL = nil; + sourceFlag = nil; + } + + return self; +} + +- (void) dealloc +{ + [headers release]; + [text release]; [envelope release]; - [info release]; [path release]; + [sourceURL release]; + [sourceFlag release]; [super dealloc]; } /* draft folder functionality */ -- (NSFileManager *)spoolFileManager { - return [[self container] spoolFileManager]; -} -- (NSString *)userSpoolFolderPath { +- (NSString *) userSpoolFolderPath +{ return [[self container] userSpoolFolderPath]; } -- (BOOL)_ensureUserSpoolFolderPath { - return [[self container] _ensureUserSpoolFolderPath]; -} /* draft object functionality */ -- (NSString *)draftFolderPath { - if (path != nil) - return path; - - path = [[[self userSpoolFolderPath] stringByAppendingPathComponent: - [self nameInContainer]] copy]; +- (NSString *) draftFolderPath +{ + if (!path) + { + path = [[self userSpoolFolderPath] stringByAppendingPathComponent: + nameInContainer]; + [path retain]; + } + return path; } -- (BOOL)_ensureDraftFolderPath { + +- (BOOL) _ensureDraftFolderPath +{ NSFileManager *fm; + + fm = [NSFileManager defaultManager]; - if (![self _ensureUserSpoolFolderPath]) - return NO; - - if ((fm = [self spoolFileManager]) == nil) { - [self errorWithFormat:@"missing spool file manager!"]; - return NO; - } - return [fm createDirectoriesAtPath:[self draftFolderPath] attributes:nil]; + return ([fm createDirectoriesAtPath: [container userSpoolFolderPath] + attributes: nil] + && [fm createDirectoriesAtPath: [self draftFolderPath] + attributes:nil]); } -- (NSString *)infoPath { - return [[self draftFolderPath] - stringByAppendingPathComponent:@".info.plist"]; +- (NSString *) infoPath +{ + return [[self draftFolderPath] + stringByAppendingPathComponent: @".info.plist"]; } /* contents */ -- (NSException *)storeInfo:(NSDictionary *)_info { - if (_info == nil) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"got no info to write for draft!"]; - } - if (![self _ensureDraftFolderPath]) { - [self errorWithFormat:@"could not create folder for draft: '%@'", - [self draftFolderPath]]; - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not create folder for draft!"]; - } - if (![_info writeToFile:[self infoPath] atomically:YES]) { - [self errorWithFormat:@"could not write info: '%@'", [self infoPath]]; - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not write draft info!"]; - } - - /* reset info cache */ - [info release]; info = nil; - - return nil /* everything is excellent */; -} -- (NSDictionary *)fetchInfo { - NSString *p; +- (void) setHeaders: (NSDictionary *) newHeaders +{ + id headerValue; + unsigned int count; + + for (count = 0; count < 6; count++) + { + headerValue = [newHeaders objectForKey: headerKeys[count]]; + if (headerValue) + [headers setObject: headerValue + forKey: headerKeys[count]]; + else + [headers removeObjectForKey: headerKeys[count]]; + } +} + +- (NSDictionary *) headers +{ + return headers; +} + +- (void) setText: (NSString *) newText +{ + ASSIGN (text, newText); +} + +- (NSString *) text +{ + return text; +} + +- (void) setSourceURL: (NSString *) newSourceURL +{ + ASSIGN (sourceURL, newSourceURL); +} + +- (void) setSourceFlag: (NSString *) newSourceFlag +{ + ASSIGN (sourceFlag, newSourceFlag); +} + +- (NSException *) storeInfo +{ + NSMutableDictionary *infos; + NSException *error; + + if ([self _ensureDraftFolderPath]) + { + infos = [NSMutableDictionary new]; + [infos setObject: headers forKey: @"headers"]; + if (text) + [infos setObject: text forKey: @"text"]; + if (IMAP4ID > -1) + [infos setObject: [NSNumber numberWithInt: IMAP4ID] + forKey: @"IMAP4ID"]; + if (sourceURL && sourceFlag) + { + [infos setObject: sourceURL forKey: @"sourceURL"]; + [infos setObject: sourceFlag forKey: @"sourceFlag"]; + } + + if ([infos writeToFile: [self infoPath] atomically:YES]) + error = nil; + else + { + [self errorWithFormat: @"could not write info: '%@'", + [self infoPath]]; + error = [NSException exceptionWithHTTPStatus:500 /* server error */ + reason: @"could not write draft info!"]; + } + + [infos release]; + } + else + { + [self errorWithFormat: @"could not create folder for draft: '%@'", + [self draftFolderPath]]; + error = [NSException exceptionWithHTTPStatus:500 /* server error */ + reason: @"could not create folder for draft!"]; + } + + return error; +} + +- (void) _loadInfosFromDictionary: (NSDictionary *) infoDict +{ + id value; + + value = [infoDict objectForKey: @"headers"]; + if (value) + [self setHeaders: value]; + + value = [infoDict objectForKey: @"text"]; + if ([value length] > 0) + [self setText: value]; + + value = [infoDict objectForKey: @"IMAP4ID"]; + if (value) + [self setIMAP4ID: [value intValue]]; + + value = [infoDict objectForKey: @"sourceURL"]; + if (value) + [self setSourceURL: value]; + value = [infoDict objectForKey: @"sourceFlag"]; + if (value) + [self setSourceFlag: value]; +} + +- (NSString *) relativeImap4Name +{ + return [NSString stringWithFormat: @"%d", IMAP4ID]; +} + +- (void) fetchInfo +{ + NSString *p; + NSDictionary *infos; + NSFileManager *fm; - if (info != nil) - return info; - p = [self infoPath]; - if (![[self spoolFileManager] fileExistsAtPath:p]) { - [self debugWithFormat:@"Note: info object does not yet exist: %@", p]; - return nil; - } + + fm = [NSFileManager defaultManager]; + if ([fm fileExistsAtPath: p]) + { + infos = [NSDictionary dictionaryWithContentsOfFile: p]; + if (infos) + [self _loadInfosFromDictionary: infos]; +// else +// [self errorWithFormat: @"draft info dictionary broken at path: %@", p]; + } + else + [self debugWithFormat: @"Note: info object does not yet exist: %@", p]; +} + +- (void) setIMAP4ID: (int) newIMAP4ID +{ + IMAP4ID = newIMAP4ID; +} + +- (int) IMAP4ID +{ + return IMAP4ID; +} + +- (int) _IMAP4IDFromAppendResult: (NSDictionary *) result +{ + NSDictionary *results; + NSString *flag, *newIdString; + + results = [[result objectForKey: @"RawResponse"] + objectForKey: @"ResponseResult"]; + flag = [results objectForKey: @"flag"]; + newIdString = [[flag componentsSeparatedByString: @" "] objectAtIndex: 2]; + + return [newIdString intValue]; +} + +- (NSException *) save +{ + NGImap4Client *client; + NSException *error; + NSData *message; + NSString *folder; + id result; + + error = nil; + message = [self mimeMessageAsData]; + + client = [[self imap4Connection] client]; + folder = [imap4 imap4FolderNameForURL: [container imap4URL]]; + result + = [client append: message toFolder: folder + withFlags: [NSArray arrayWithObjects: @"seen", @"draft", nil]]; + if ([[result objectForKey: @"result"] boolValue]) + { + if (IMAP4ID > -1) + error = [imap4 markURLDeleted: [self imap4URL]]; + IMAP4ID = [self _IMAP4IDFromAppendResult: result]; + [self storeInfo]; + } + else + error = [NSException exceptionWithHTTPStatus:500 /* Server Error */ + reason: @"Failed to store message"]; + + return error; +} + +- (void) fetchMailForEditing: (SOGoMailObject *) sourceMail +{ +#warning unimplemented +} + +- (void) _addEMailsOfAddresses: (NSArray *) _addrs + toArray: (NSMutableArray *) _ma +{ + unsigned i, count; + + for (i = 0, count = [_addrs count]; i < count; i++) + [_ma addObject: + [(NGImap4EnvelopeAddress *) [_addrs objectAtIndex: i] email]]; +} + +- (void) _fillInReplyAddresses: (NSMutableDictionary *) _info + replyToAll: (BOOL) _replyToAll + envelope: (NGImap4Envelope *) _envelope +{ + /* + The rules as implemented by Thunderbird: + - if there is a 'reply-to' header, only include that (as TO) + - if we reply to all, all non-from addresses are added as CC + - the from is always the lone TO (except for reply-to) + + Note: we cannot check reply-to, because Cyrus even sets a reply-to in the + envelope if none is contained in the message itself! (bug or + feature?) + + TODO: what about sender (RFC 822 3.6.2) + */ + NSMutableArray *to; + NSArray *addrs; - info = [[NSDictionary alloc] initWithContentsOfFile:p]; - if (info == nil) - [self errorWithFormat:@"draft info dictionary broken at path: %@", p]; + to = [NSMutableArray arrayWithCapacity:2]; + + /* first check for "reply-to" */ - return info; + addrs = [_envelope replyTo]; + if ([addrs count] == 0) + /* no "reply-to", try "from" */ + addrs = [_envelope from]; + + [self _addEMailsOfAddresses: addrs toArray: to]; + [_info setObject: to forKey: @"to"]; + + /* CC processing if we reply-to-all: add all 'to' and 'cc' */ + + if (_replyToAll) + { + to = [NSMutableArray arrayWithCapacity:8]; + + [self _addEMailsOfAddresses: [_envelope to] toArray: to]; + [self _addEMailsOfAddresses: [_envelope cc] toArray: to]; + + [_info setObject: to forKey: @"cc"]; + } +} + +- (void) fetchMailForReplying: (SOGoMailObject *) sourceMail + toAll: (BOOL) toAll +{ + NSString *contentForReply; + NSMutableDictionary *info; + + [sourceMail fetchCoreInfos]; + + info = [NSMutableDictionary dictionaryWithCapacity: 16]; + [info setObject: [sourceMail subjectForReply] forKey: @"subject"]; + [self _fillInReplyAddresses: info replyToAll: toAll + envelope: [sourceMail envelope]]; + contentForReply = [sourceMail contentForReply]; + [self setText: contentForReply]; + [self setHeaders: info]; + [self setSourceURL: [sourceMail imap4URLString]]; + [self setSourceFlag: @"Answered"]; + [self storeInfo]; +} + +- (void) fetchMailForForwarding: (SOGoMailObject *) sourceMail +{ + NSDictionary *info, *attachment; + + [sourceMail fetchCoreInfos]; + + info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward] + forKey: @"subject"]; + [self setHeaders: info]; + [self setSourceURL: [sourceMail imap4URLString]]; + [self setSourceFlag: @"$Forwarded"]; + + /* attach message */ + + // TODO: use subject for filename? +// error = [newDraft saveAttachment:content withName:@"forward.mail"]; + attachment = [NSDictionary dictionaryWithObjectsAndKeys: + [sourceMail filenameForForward], @"filename", + @"message/rfc822", @"mime-type", + nil]; + [self saveAttachment: [sourceMail content] + withMetadata: attachment]; + [self storeInfo]; } /* accessors */ -- (NSString *)sender { +- (NSString *) sender +{ id tmp; - if ((tmp = [[self fetchInfo] objectForKey:@"from"]) == nil) + if ((tmp = [headers objectForKey: @"from"]) == nil) return nil; if ([tmp isKindOfClass:[NSArray class]]) - return [tmp count] > 0 ? [tmp objectAtIndex:0] : nil; + return [tmp count] > 0 ? [tmp objectAtIndex: 0] : nil; + return tmp; } /* attachments */ -- (NSArray *)fetchAttachmentNames { +- (NSArray *) fetchAttachmentNames +{ NSMutableArray *ma; - NSFileManager *fm; - NSArray *files; - unsigned i, count; - - fm = [self spoolFileManager]; - if ((files = [fm directoryContentsAtPath:[self draftFolderPath]]) == nil) - return nil; - - count = [files count]; - ma = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - NSString *filename; - - filename = [files objectAtIndex:i]; - if ([filename hasPrefix:@"."]) - continue; - - [ma addObject:filename]; - } + NSFileManager *fm; + NSArray *files; + unsigned count, max; + NSString *filename; + + fm = [NSFileManager defaultManager]; + files = [fm directoryContentsAtPath: [self draftFolderPath]]; + + max = [files count]; + ma = [NSMutableArray arrayWithCapacity: max]; + for (count = 0; count < max; count++) + { + filename = [files objectAtIndex: count]; + if (![filename hasPrefix: @"."]) + [ma addObject: filename]; + } + return ma; } -- (BOOL)isValidAttachmentName:(NSString *)_name { +- (BOOL) isValidAttachmentName: (NSString *) _name +{ static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil }; unsigned i; NSRange r; if (![_name isNotNull]) return NO; if ([_name length] == 0) return NO; - if ([_name hasPrefix:@"."]) return NO; + if ([_name hasPrefix: @"."]) return NO; for (i = 0; sescape[i] != nil; i++) { r = [_name rangeOfString:sescape[i]]; @@ -245,16 +525,18 @@ static BOOL showTextAttachmentsInline = NO; return YES; } -- (NSString *)pathToAttachmentWithName:(NSString *)_name { +- (NSString *) pathToAttachmentWithName: (NSString *) _name +{ if ([_name length] == 0) return nil; return [[self draftFolderPath] stringByAppendingPathComponent:_name]; } -- (NSException *)invalidAttachmentNameError:(NSString *)_name { +- (NSException *) invalidAttachmentNameError: (NSString *) _name +{ return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ - reason:@"Invalid attachment name!"]; + reason: @"Invalid attachment name!"]; } - (NSException *) saveAttachment: (NSData *) _attach @@ -264,12 +546,12 @@ static BOOL showTextAttachmentsInline = NO; if (![_attach isNotNull]) { return [NSException exceptionWithHTTPStatus:400 /* Bad Request */ - reason:@"Missing attachment content!"]; + reason: @"Missing attachment content!"]; } if (![self _ensureDraftFolderPath]) { return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Could not create folder for draft!"]; + reason: @"Could not create folder for draft!"]; } name = [metadata objectForKey: @"filename"]; if (![self isValidAttachmentName: name]) @@ -279,7 +561,7 @@ static BOOL showTextAttachmentsInline = NO; if (![_attach writeToFile: p atomically: YES]) { return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Could not write attachment to draft!"]; + reason: @"Could not write attachment to draft!"]; } mimeType = [metadata objectForKey: @"mime-type"]; @@ -291,115 +573,121 @@ static BOOL showTextAttachmentsInline = NO; writeToFile: p atomically: YES]) { return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Could not write attachment to draft!"]; + reason: @"Could not write attachment to draft!"]; } } return nil; /* everything OK */ } -- (NSException *)deleteAttachmentWithName:(NSString *)_name { +- (NSException *) deleteAttachmentWithName: (NSString *) _name +{ NSFileManager *fm; NSString *p; - - if (![self isValidAttachmentName:_name]) - return [self invalidAttachmentNameError:_name]; - - fm = [self spoolFileManager]; - p = [self pathToAttachmentWithName:_name]; - if (![fm fileExistsAtPath:p]) - return nil; /* well, doesn't exist, so its deleted ;-) */ - - if (![fm removeFileAtPath:p handler:nil]) { - [self logWithFormat:@"ERROR: failed to delete file: %@", p]; - return [NSException exceptionWithHTTPStatus:500 /* Server Error */ - reason:@"Could not delete attachment from draft!"]; - } - return nil; /* everything OK */ + NSException *error; + + error = nil; + + if ([self isValidAttachmentName:_name]) + { + fm = [NSFileManager defaultManager]; + p = [self pathToAttachmentWithName:_name]; + if ([fm fileExistsAtPath: p]) + if (![fm removeFileAtPath: p handler: nil]) + error + = [NSException exceptionWithHTTPStatus: 500 /* Server Error */ + reason: @"Could not delete attachment from draft!"]; + } + else + error = [self invalidAttachmentNameError:_name]; + + return error; } /* NGMime representations */ -- (NGMimeBodyPart *)bodyPartForText +- (NGMimeBodyPart *) bodyPartForText { /* This add the text typed by the user (the primary plain/text part). */ NGMutableHashMap *map; NGMimeBodyPart *bodyPart; - NSDictionary *lInfo; - id body; - - if ((lInfo = [self fetchInfo]) == nil) - return nil; /* prepare header of body part */ - map = [[[NGMutableHashMap alloc] initWithCapacity:2] autorelease]; + map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; // TODO: set charset in header! - [map setObject:@"text/plain" forKey:@"content-type"]; - if ((body = [lInfo objectForKey:@"text"]) != nil) { - if ([body isKindOfClass: [NSString class]]) { - [map setObject: contentTypeValue - forKey: @"content-type"]; -// body = [body dataUsingEncoding:NSUTF8StringEncoding]; - } - } + [map setObject: @"text/plain" forKey: @"content-type"]; + if (text) + [map setObject: contentTypeValue forKey: @"content-type"]; + +// if ((body = text) != nil) { +// if ([body isKindOfClass: [NSString class]]) { +// [map setObject: contentTypeValue +// forKey: @"content-type"]; +// // body = [body dataUsingEncoding:NSUTF8StringEncoding]; +// } +// } /* prepare body content */ bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; - [bodyPart setBody:body]; + [bodyPart setBody: text]; + return bodyPart; } -- (NGMimeMessage *)mimeMessageForContentWithHeaderMap:(NGMutableHashMap *)map +- (NGMimeMessage *) mimeMessageForContentWithHeaderMap: (NGMutableHashMap *) map { - NSDictionary *lInfo; NGMimeMessage *message; - BOOL addSuffix; +// BOOL addSuffix; id body; - if ((lInfo = [self fetchInfo]) == nil) - return nil; - [map setObject: @"text/plain" forKey: @"content-type"]; - if ((body = [lInfo objectForKey:@"text"]) != nil) { - if ([body isKindOfClass:[NSString class]]) - /* Note: just 'utf8' is displayed wrong in Mail.app */ - [map setObject: contentTypeValue - forKey: @"content-type"]; + body = text; + if (body) + { +// if ([body isKindOfClass:[NSString class]]) + /* Note: just 'utf8' is displayed wrong in Mail.app */ + [map setObject: contentTypeValue + forKey: @"content-type"]; // body = [body dataUsingEncoding:NSUTF8StringEncoding]; - else if ([body isKindOfClass:[NSData class]] && addSuffix) { - body = [[body mutableCopy] autorelease]; +// else if ([body isKindOfClass:[NSData class]] && addSuffix) { +// body = [[body mutableCopy] autorelease]; +// } +// else if (addSuffix) { +// [self warnWithFormat: @"Note: cannot add Internet marker to body: %@", +// NSStringFromClass([body class])]; +// } + + message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease]; + [message setBody: body]; } - else if (addSuffix) { - [self warnWithFormat:@"Note: cannot add Internet marker to body: %@", - NSStringFromClass([body class])]; - } - } - - message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease]; - [message setBody:body]; + else + message = nil; + return message; } -- (NSString *)mimeTypeForExtension:(NSString *)_ext { +- (NSString *) mimeTypeForExtension: (NSString *) _ext +{ // TODO: make configurable // TODO: use /etc/mime-types - if ([_ext isEqualToString:@"txt"]) return @"text/plain"; - if ([_ext isEqualToString:@"html"]) return @"text/html"; - if ([_ext isEqualToString:@"htm"]) return @"text/html"; - if ([_ext isEqualToString:@"gif"]) return @"image/gif"; - if ([_ext isEqualToString:@"jpg"]) return @"image/jpeg"; - if ([_ext isEqualToString:@"jpeg"]) return @"image/jpeg"; - if ([_ext isEqualToString:@"mail"]) return @"message/rfc822"; + if ([_ext isEqualToString: @"txt"]) return @"text/plain"; + if ([_ext isEqualToString: @"html"]) return @"text/html"; + if ([_ext isEqualToString: @"htm"]) return @"text/html"; + if ([_ext isEqualToString: @"gif"]) return @"image/gif"; + if ([_ext isEqualToString: @"jpg"]) return @"image/jpeg"; + if ([_ext isEqualToString: @"jpeg"]) return @"image/jpeg"; + if ([_ext isEqualToString: @"mail"]) return @"message/rfc822"; return @"application/octet-stream"; } -- (NSString *)contentTypeForAttachmentWithName:(NSString *)_name { +- (NSString *) contentTypeForAttachmentWithName: (NSString *) _name +{ NSString *s, *p; NSData *mimeData; @@ -416,35 +704,37 @@ static BOOL showTextAttachmentsInline = NO; { s = [self mimeTypeForExtension:[_name pathExtension]]; if ([_name length] > 0) - s = [s stringByAppendingFormat:@"; name=\"%@\"", _name]; + s = [s stringByAppendingFormat: @"; name=\"%@\"", _name]; } return s; } -- (NSString *)contentDispositionForAttachmentWithName:(NSString *)_name { +- (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name +{ NSString *type; NSString *cdtype; NSString *cd; type = [self contentTypeForAttachmentWithName:_name]; - if ([type hasPrefix:@"text/"]) + if ([type hasPrefix: @"text/"]) cdtype = showTextAttachmentsInline ? @"inline" : @"attachment"; - else if ([type hasPrefix:@"image/"] || [type hasPrefix:@"message"]) + else if ([type hasPrefix: @"image/"] || [type hasPrefix: @"message"]) cdtype = @"inline"; else cdtype = @"attachment"; - cd = [cdtype stringByAppendingString:@"; filename=\""]; + cd = [cdtype stringByAppendingString: @"; filename=\""]; cd = [cd stringByAppendingString:_name]; - cd = [cd stringByAppendingString:@"\""]; + cd = [cd stringByAppendingString: @"\""]; // TODO: add size parameter (useful addition, RFC 2183) return cd; } -- (NGMimeBodyPart *)bodyPartForAttachmentWithName:(NSString *)_name { +- (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name +{ NSFileManager *fm; NGMutableHashMap *map; NGMimeBodyPart *bodyPart; @@ -458,10 +748,10 @@ static BOOL showTextAttachmentsInline = NO; /* check attachment */ - fm = [self spoolFileManager]; + fm = [NSFileManager defaultManager]; p = [self pathToAttachmentWithName:_name]; if (![fm isReadableFileAtPath:p]) { - [self errorWithFormat:@"did not find attachment: '%@'", _name]; + [self errorWithFormat: @"did not find attachment: '%@'", _name]; return nil; } attachAsString = NO; @@ -472,14 +762,14 @@ static BOOL showTextAttachmentsInline = NO; map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease]; if ((s = [self contentTypeForAttachmentWithName:_name]) != nil) { - [map setObject:s forKey:@"content-type"]; - if ([s hasPrefix:@"text/"]) + [map setObject:s forKey: @"content-type"]; + if ([s hasPrefix: @"text/"]) attachAsString = YES; - else if ([s hasPrefix:@"message/rfc822"]) + else if ([s hasPrefix: @"message/rfc822"]) is7bit = YES; } if ((s = [self contentDispositionForAttachmentWithName:_name])) - [map setObject:s forKey:@"content-disposition"]; + [map setObject:s forKey: @"content-disposition"]; /* prepare body content */ @@ -507,9 +797,9 @@ static BOOL showTextAttachmentsInline = NO; generator! */ body = [[NGMimeFileData alloc] initWithPath:p removeFile:NO]; - [map setObject:@"7bit" forKey:@"content-transfer-encoding"]; + [map setObject: @"7bit" forKey: @"content-transfer-encoding"]; [map setObject:[NSNumber numberWithInt:[body length]] - forKey:@"content-length"]; + forKey: @"content-length"]; } else { /* @@ -522,9 +812,9 @@ static BOOL showTextAttachmentsInline = NO; encoded = [content dataByEncodingBase64]; [content release]; content = nil; - [map setObject:@"base64" forKey:@"content-transfer-encoding"]; + [map setObject: @"base64" forKey: @"content-transfer-encoding"]; [map setObject:[NSNumber numberWithInt:[encoded length]] - forKey:@"content-length"]; + forKey: @"content-length"]; /* Note: the -init method will create a temporary file! */ body = [[NGMimeFileData alloc] initWithBytes:[encoded bytes] @@ -538,55 +828,61 @@ static BOOL showTextAttachmentsInline = NO; return bodyPart; } -- (NSArray *)bodyPartsForAllAttachments { +- (NSArray *) bodyPartsForAllAttachments +{ /* returns nil on error */ - NSMutableArray *bodyParts; NSArray *names; unsigned i, count; - + NGMimeBodyPart *bodyPart; + NSMutableArray *bodyParts; + names = [self fetchAttachmentNames]; - if ((count = [names count]) == 0) - return [NSArray array]; - - bodyParts = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - NGMimeBodyPart *bodyPart; - - bodyPart = [self bodyPartForAttachmentWithName:[names objectAtIndex:i]]; - if (bodyPart == nil) - return nil; - - [bodyParts addObject:bodyPart]; - } + count = [names count]; + bodyParts = [NSMutableArray arrayWithCapacity: count]; + + for (i = 0; i < count; i++) + { + bodyPart = [self bodyPartForAttachmentWithName: [names objectAtIndex: i]]; + [bodyParts addObject: bodyPart]; + } + return bodyParts; } -- (NGMimeMessage *)mimeMultiPartMessageWithHeaderMap:(NGMutableHashMap *)map - andBodyParts:(NSArray *)_bodyParts +- (NGMimeMessage *) mimeMultiPartMessageWithHeaderMap: (NGMutableHashMap *) map + andBodyParts: (NSArray *) _bodyParts { NGMimeMessage *message; NGMimeMultipartBody *mBody; NGMimeBodyPart *part; NSEnumerator *e; - [map addObject:MultiMixedType forKey:@"content-type"]; - - message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease]; - mBody = [[NGMimeMultipartBody alloc] initWithPart:message]; - + [map addObject: MultiMixedType forKey: @"content-type"]; + + message = [[NGMimeMessage alloc] initWithHeader: map]; + [message autorelease]; + mBody = [[NGMimeMultipartBody alloc] initWithPart: message]; + part = [self bodyPartForText]; - [mBody addBodyPart:part]; - + [mBody addBodyPart: part]; + e = [_bodyParts objectEnumerator]; - while ((part = [e nextObject]) != nil) - [mBody addBodyPart:part]; - - [message setBody:mBody]; - [mBody release]; mBody = nil; + part = [e nextObject]; + while (part) + { + [mBody addBodyPart: part]; + part = [e nextObject]; + } + + [message setBody: mBody]; + [mBody release]; + return message; } -- (void)_addHeaders:(NSDictionary *)_h toHeaderMap:(NGMutableHashMap *)_map { +- (void) _addHeaders: (NSDictionary *) _h + toHeaderMap: (NGMutableHashMap *) _map +{ NSEnumerator *names; NSString *name; @@ -602,398 +898,225 @@ static BOOL showTextAttachmentsInline = NO; } } -- (BOOL)isEmptyValue:(id)_value { +- (BOOL) isEmptyValue: (id) _value +{ if (![_value isNotNull]) return YES; - if ([_value isKindOfClass:[NSArray class]]) + if ([_value isKindOfClass: [NSArray class]]) return [_value count] == 0 ? YES : NO; - if ([_value isKindOfClass:[NSString class]]) + if ([_value isKindOfClass: [NSString class]]) return [_value length] == 0 ? YES : NO; - + return NO; } -- (NGMutableHashMap *)mimeHeaderMapWithHeaders:(NSDictionary *)_headers { +- (NGMutableHashMap *) mimeHeaderMapWithHeaders: (NSDictionary *) _headers +{ NGMutableHashMap *map; - NSDictionary *lInfo; // TODO: this should be some kind of object? NSArray *emails; NSString *s, *dateString; id from, replyTo; - if ((lInfo = [self fetchInfo]) == nil) - return nil; - map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease]; /* add recipients */ - if ((emails = [lInfo objectForKey:@"to"]) != nil) { + if ((emails = [headers objectForKey: @"to"]) != nil) { if ([emails count] == 0) { - [self errorWithFormat:@"missing 'to' recipient in email!"]; + [self errorWithFormat: @"missing 'to' recipient in email!"]; return nil; } - [map setObjects:emails forKey:@"to"]; + [map setObjects: emails forKey: @"to"]; } - if ((emails = [lInfo objectForKey:@"cc"]) != nil) - [map setObjects:emails forKey:@"cc"]; - if ((emails = [lInfo objectForKey:@"bcc"]) != nil) - [map setObjects:emails forKey:@"bcc"]; + if ((emails = [headers objectForKey: @"cc"]) != nil) + [map setObjects:emails forKey: @"cc"]; + if ((emails = [headers objectForKey: @"bcc"]) != nil) + [map setObjects:emails forKey: @"bcc"]; /* add senders */ - from = [lInfo objectForKey:@"from"]; - replyTo = [lInfo objectForKey:@"replyTo"]; + from = [headers objectForKey: @"from"]; + replyTo = [headers objectForKey: @"replyTo"]; if (![self isEmptyValue:from]) { if ([from isKindOfClass:[NSArray class]]) - [map setObjects:from forKey:@"from"]; + [map setObjects: from forKey: @"from"]; else - [map setObject:from forKey:@"from"]; + [map setObject: from forKey: @"from"]; } - if (![self isEmptyValue:replyTo]) { + if (![self isEmptyValue: replyTo]) { if ([from isKindOfClass:[NSArray class]]) - [map setObjects:from forKey:@"reply-to"]; + [map setObjects:from forKey: @"reply-to"]; else - [map setObject:from forKey:@"reply-to"]; + [map setObject:from forKey: @"reply-to"]; } else if (![self isEmptyValue:from]) - [map setObjects:[map objectsForKey:@"from"] forKey:@"reply-to"]; + [map setObjects:[map objectsForKey: @"from"] forKey: @"reply-to"]; /* add subject */ - if ([(s = [lInfo objectForKey:@"subject"]) length] > 0) + if ([(s = [headers objectForKey: @"subject"]) length] > 0) [map setObject: [s asQPSubjectString: @"utf-8"] - forKey:@"subject"]; -// [map setObject: [s asQPSubjectString: @"utf-8"] forKey:@"subject"]; + forKey: @"subject"]; +// [map setObject: [s asQPSubjectString: @"utf-8"] forKey: @"subject"]; /* add standard headers */ dateString = [[NSCalendarDate date] rfc822DateString]; - [map addObject: dateString forKey:@"date"]; - [map addObject: @"1.0" forKey:@"MIME-Version"]; - [map addObject: userAgent forKey:@"X-Mailer"]; + [map addObject: dateString forKey: @"date"]; + [map addObject: @"1.0" forKey: @"MIME-Version"]; + [map addObject: userAgent forKey: @"X-Mailer"]; /* add custom headers */ - [self _addHeaders:[lInfo objectForKey:@"headers"] toHeaderMap:map]; - [self _addHeaders:_headers toHeaderMap:map]; +// [self _addHeaders: [lInfo objectForKey: @"headers"] toHeaderMap:map]; + [self _addHeaders: _headers toHeaderMap: map]; return map; } -- (NGMimeMessage *)mimeMessageWithHeaders:(NSDictionary *)_headers { - NSAutoreleasePool *pool; +- (NGMimeMessage *) mimeMessageWithHeaders: (NSDictionary *) _headers +{ NGMutableHashMap *map; NSArray *bodyParts; NGMimeMessage *message; - - pool = [[NSAutoreleasePool alloc] init]; - - if ([self fetchInfo] == nil) { - [self errorWithFormat:@"could not locate draft fetch info!"]; - return nil; - } - - if ((map = [self mimeHeaderMapWithHeaders:_headers]) == nil) - return nil; - [self debugWithFormat:@"MIME Envelope: %@", map]; - - if ((bodyParts = [self bodyPartsForAllAttachments]) == nil) { - [self errorWithFormat: - @"could not create body parts for attachments!"]; - return nil; // TODO: improve error handling, return exception - } - [self debugWithFormat:@"attachments: %@", bodyParts]; - - if ([bodyParts count] == 0) { - /* no attachments */ - message = [self mimeMessageForContentWithHeaderMap:map]; - } - else { - /* attachments, create multipart/mixed */ - message = [self mimeMultiPartMessageWithHeaderMap:map - andBodyParts:bodyParts]; - } - [self debugWithFormat:@"message: %@", message]; - message = [message retain]; - [pool release]; - return [message autorelease]; -} -- (NGMimeMessage *)mimeMessage { - return [self mimeMessageWithHeaders:nil]; + message = nil; + + map = [self mimeHeaderMapWithHeaders: _headers]; + if (map) + { + [self debugWithFormat: @"MIME Envelope: %@", map]; + + bodyParts = [self bodyPartsForAllAttachments]; + if (bodyParts) + { + [self debugWithFormat: @"attachments: %@", bodyParts]; + + if ([bodyParts count] == 0) + /* no attachments */ + message = [self mimeMessageForContentWithHeaderMap: map]; + else + /* attachments, create multipart/mixed */ + message = [self mimeMultiPartMessageWithHeaderMap: map + andBodyParts: bodyParts]; + [self debugWithFormat: @"message: %@", message]; + } + else + [self errorWithFormat: + @"could not create body parts for attachments!"]; + } + + return message; } -- (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_h { - NGMimeMessageGenerator *gen; - NSAutoreleasePool *pool; - NGMimeMessage *message; - NSString *tmpPath; - - pool = [[NSAutoreleasePool alloc] init]; - - message = [self mimeMessageWithHeaders:_h]; - if (![message isNotNull]) - return nil; - if ([message isKindOfClass:[NSException class]]) { - [self errorWithFormat:@"error: %@", message]; - return nil; - } - - gen = [[NGMimeMessageGenerator alloc] init]; - tmpPath = [[gen generateMimeFromPartToFile:message] copy]; - [gen release]; gen = nil; - - [pool release]; - return [tmpPath autorelease]; -} -- (NSString *)saveMimeMessageToTemporaryFile { - return [self saveMimeMessageToTemporaryFileWithHeaders:nil]; +- (NGMimeMessage *) mimeMessage +{ + return [self mimeMessageWithHeaders: nil]; } -- (void)deleteTemporaryMessageFile:(NSString *)_path { - NSFileManager *fm; - - if (![_path isNotNull]) - return; +- (NSData *) mimeMessageAsData +{ + NGMimeMessageGenerator *generator; + NSData *message; - fm = [NSFileManager defaultManager]; - if (![fm fileExistsAtPath:_path]) - return; - - [fm removeFileAtPath:_path handler:nil]; + generator = [NGMimeMessageGenerator new]; + message = [generator generateMimeFromPart: [self mimeMessage]]; + [generator release]; + + return message; } -- (NSArray *)allRecipients { - NSDictionary *lInfo; - NSMutableArray *ma; - NSArray *tmp; - - if ((lInfo = [self fetchInfo]) == nil) - return nil; - - ma = [NSMutableArray arrayWithCapacity:16]; - if ((tmp = [lInfo objectForKey:@"to"]) != nil) - [ma addObjectsFromArray:tmp]; - if ((tmp = [lInfo objectForKey:@"cc"]) != nil) - [ma addObjectsFromArray:tmp]; - if ((tmp = [lInfo objectForKey:@"bcc"]) != nil) - [ma addObjectsFromArray:tmp]; - return ma; +- (NSArray *) allRecipients +{ + NSMutableArray *allRecipients; + NSArray *recipients; + NSString *fieldNames[] = {@"to", @"cc", @"bcc"}; + unsigned int count; + + allRecipients = [NSMutableArray arrayWithCapacity: 16]; + + for (count = 0; count < 3; count++) + { + recipients = [headers objectForKey: fieldNames[count]]; + if ([recipients count] > 0) + [allRecipients addObjectsFromArray: recipients]; + } + + return allRecipients; } - (NSException *) sendMail { NSException *error; - NSString *tmpPath; - - /* save MIME mail to file */ - - tmpPath = [self saveMimeMessageToTemporaryFile]; - if (![tmpPath isNotNull]) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"could not save MIME message for draft!"]; - } + SOGoMailFolder *sentFolder; + NSData *message; + NSURL *sourceIMAP4URL; /* send mail */ - error = [[SOGoMailer sharedMailer] sendMailAtPath: tmpPath - toRecipients: [self allRecipients] - sender: [self sender]]; - - /* delete temporary file */ - [self deleteTemporaryMessageFile:tmpPath]; + sentFolder = [[self mailAccountFolder] sentFolderInContext: context]; + if ([sentFolder isKindOfClass: [NSException class]]) + error = (NSException *) sentFolder; + else + { + message = [self mimeMessageAsData]; + error = [[SOGoMailer sharedMailer] sendMailData: message + toRecipients: [self allRecipients] + sender: [self sender]]; + if (!error) + { + error = [sentFolder postData: message flags: @"seen"]; + if (!error) + { + [self imap4Connection]; + if (IMAP4ID > -1) + [imap4 markURLDeleted: [self imap4URL]]; + if (sourceURL && sourceFlag) + { + sourceIMAP4URL = [NSURL URLWithString: sourceURL]; + [imap4 addFlags: sourceFlag toURL: sourceIMAP4URL]; + } + } + } + } return error; } /* operations */ -- (NSException *)delete { - NSFileManager *fm; - NSString *p, *sp; - NSEnumerator *e; - - if ((fm = [self spoolFileManager]) == nil) { - [self errorWithFormat:@"missing spool file manager!"]; - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"missing spool file manager!"]; - } - - p = [self draftFolderPath]; - if (![fm fileExistsAtPath:p]) { - return [NSException exceptionWithHTTPStatus:404 /* not found */ - reason:@"did not find draft!"]; - } - - e = [[fm directoryContentsAtPath:p] objectEnumerator]; - while ((sp = [e nextObject])) { - sp = [p stringByAppendingPathComponent:sp]; - if (draftDeleteDisabled) { - [self logWithFormat:@"should delete draft file %@ ...", sp]; - continue; - } - - if (![fm removeFileAtPath:sp handler:nil]) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"failed to delete draft!"]; - } - } - - if (draftDeleteDisabled) { - [self logWithFormat:@"should delete draft directory: %@", p]; - } - else { - if (![fm removeFileAtPath:p handler:nil]) { - return [NSException exceptionWithHTTPStatus:500 /* server error */ - reason:@"failed to delete draft directory!"]; - } - } - return nil; -} - -- (NSData *)content { - /* Note: does not cache, expensive operation */ - NSData *data; - NSString *p; - - if ((p = [self saveMimeMessageToTemporaryFile]) == nil) - return nil; - - data = [NSData dataWithContentsOfMappedFile:p]; - - /* delete temporary file */ - [self deleteTemporaryMessageFile:p]; - - return data; -} -- (NSString *)contentAsString { - NSString *str; - NSData *data; - - if ((data = [self content]) == nil) - return nil; - - str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; - if (str == nil) { - [self errorWithFormat:@"could not load draft as ASCII (data size=%d)", - [data length]]; - return nil; - } - - return [str autorelease]; -} - -/* actions */ - -- (id)DELETEAction:(id)_ctx { - NSException *error; - - if ((error = [self delete]) != nil) - return error; - - return [NSNumber numberWithBool:YES]; /* delete worked out ... */ -} - -- (id) GETAction: (id) _ctx +- (NSString *) contentAsString { - /* - Override, because SOGoObject's GETAction uses the less efficient - -contentAsString method. - */ - WORequest *rq; + NSString *str; + NSData *message; - rq = [_ctx request]; - if ([rq isSoWebDAVRequest]) { - WOResponse *r; - NSData *content; - - if ((content = [self content]) == nil) { - return [NSException exceptionWithHTTPStatus:500 - reason:@"Could not generate MIME content!"]; + message = [self mimeMessageAsData]; + if (message) + { + str = [[NSString alloc] initWithData: message + encoding: NSUTF8StringEncoding]; + if (!str) + [self errorWithFormat: @"could not load draft as UTF-8 (data size=%d)", + [message length]]; + else + [str autorelease]; + } + else + { + [self errorWithFormat: @"message data is empty"]; + str = nil; } - r = [_ctx response]; - [r setHeader:@"message/rfc822" forKey:@"content-type"]; - [r setContent:content]; - return r; - } - - return [super GETAction:_ctx]; -} -/* fake being a SOGoMailObject */ - -- (id)fetchParts:(NSArray *)_parts { - return [NSDictionary dictionaryWithObject:self forKey:@"fetch"]; -} - -- (NSString *)uid { - return [self nameInContainer]; -} -- (NSArray *)flags { - static NSArray *seenFlags = nil; - seenFlags = [[NSArray alloc] initWithObjects:@"seen", nil]; - return seenFlags; -} -- (unsigned)size { - // TODO: size, hard to support, we would need to generate MIME? - return 0; -} - -- (NSArray *)imap4EnvelopeAddressesForStrings:(NSArray *)_emails { - NSMutableArray *ma; - unsigned i, count; - - if (_emails == nil) - return nil; - if ((count = [_emails count]) == 0) - return [NSArray array]; - - ma = [NSMutableArray arrayWithCapacity:count]; - for (i = 0; i < count; i++) { - NGImap4EnvelopeAddress *envaddr; - - envaddr = [[NGImap4EnvelopeAddress alloc] - initWithString:[_emails objectAtIndex:i]]; - if ([envaddr isNotNull]) - [ma addObject:envaddr]; - [envaddr release]; - } - return ma; -} - -- (NGImap4Envelope *)envelope { - NSDictionary *lInfo; - id from, replyTo; - - if (envelope != nil) - return envelope; - if ((lInfo = [self fetchInfo]) == nil) - return nil; - - if ((from = [self sender]) != nil) - from = [NSArray arrayWithObjects:&from count:1]; - - if ((replyTo = [lInfo objectForKey:@"replyTo"]) != nil) { - if (![replyTo isKindOfClass:[NSArray class]]) - replyTo = [NSArray arrayWithObjects:&replyTo count:1]; - } - - envelope = - [[NGImap4Envelope alloc] initWithMessageID:[self nameInContainer] - subject:[lInfo objectForKey:@"subject"] - from:from replyTo:replyTo - to:[lInfo objectForKey:@"to"] - cc:[lInfo objectForKey:@"cc"] - bcc:[lInfo objectForKey:@"bcc"]]; - return envelope; + return str; } /* debugging */ -- (BOOL)isDebuggingEnabled { +- (BOOL) isDebuggingEnabled +{ return debugOn; }