02111-1307, USA.
*/
-#include "SOGoDraftObject.h"
-#include <SoObjects/SOGo/WOContext+Agenor.h>
-#include <NGMail/NGMimeMessage.h>
-#include <NGMail/NGMimeMessageGenerator.h>
-#include <NGMail/NGSendMail.h>
-#include <NGMime/NGMimeBodyPart.h>
-#include <NGMime/NGMimeFileData.h>
-#include <NGMime/NGMimeMultipartBody.h>
-#include <NGMime/NGMimeType.h>
-#include <NGImap4/NGImap4Envelope.h>
-#include <NGImap4/NGImap4EnvelopeAddress.h>
-#include <NGExtensions/NSFileManager+Extensions.h>
-#include "common.h"
+#import <Foundation/NSArray.h>
+#import <Foundation/NSAutoreleasePool.h>
+#import <Foundation/NSDictionary.h>
+#import <Foundation/NSKeyValueCoding.h>
+#import <Foundation/NSURL.h>
+#import <Foundation/NSUserDefaults.h>
+#import <Foundation/NSValue.h>
+
+#import <NGObjWeb/NSException+HTTP.h>
+#import <NGObjWeb/SoObject+SoDAV.h>
+#import <NGObjWeb/WOContext+SoObjects.h>
+#import <NGObjWeb/WORequest+So.h>
+#import <NGObjWeb/WOResponse.h>
+#import <NGExtensions/NGBase64Coding.h>
+#import <NGExtensions/NSFileManager+Extensions.h>
+#import <NGExtensions/NGHashMap.h>
+#import <NGExtensions/NSNull+misc.h>
+#import <NGExtensions/NSObject+Logs.h>
+#import <NGExtensions/NGQuotedPrintableCoding.h>
+#import <NGExtensions/NSString+misc.h>
+#import <NGImap4/NGImap4Connection.h>
+#import <NGImap4/NGImap4Client.h>
+#import <NGImap4/NGImap4Envelope.h>
+#import <NGImap4/NGImap4EnvelopeAddress.h>
+#import <NGMail/NGMimeMessage.h>
+#import <NGMail/NGMimeMessageGenerator.h>
+#import <NGMime/NGMimeBodyPart.h>
+#import <NGMime/NGMimeFileData.h>
+#import <NGMime/NGMimeMultipartBody.h>
+#import <NGMime/NGMimeType.h>
+#import <NGMime/NGMimeHeaderFieldGenerator.h>
+
+#import <SoObjects/SOGo/NSArray+Utilities.h>
+#import <SoObjects/SOGo/NSCalendarDate+SOGo.h>
+#import <SoObjects/SOGo/NSString+Utilities.h>
+#import <SoObjects/SOGo/SOGoMailer.h>
+#import <SoObjects/SOGo/SOGoUser.h>
+
+#import "NSData+Mail.h"
+#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};
@implementation SOGoDraftObject
static BOOL draftDeleteDisabled = NO; // for debugging
static BOOL debugOn = NO;
static BOOL showTextAttachmentsInline = NO;
-static NSString *fromInternetSuffixPattern = nil;
-+ (int)version {
- return [super version] + 0 /* v1 */;
-}
-
-+ (void)initialize {
++ (void) initialize
+{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
- NSAssert2([super version] == 1,
- @"invalid superclass (%@) version %i !",
- NSStringFromClass([self superclass]), [super version]);
-
/* 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)");
- fromInternetSuffixPattern = [ud stringForKey:@"SOGoInternetMailSuffix"];
- if ([fromInternetSuffixPattern length] == 0)
- NSLog(@"Note: no 'SOGoInternetMailSuffix' is configured.");
- else {
- fromInternetSuffixPattern =
- [@"\n" stringByAppendingString:fromInternetSuffixPattern];
- }
-
- 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 {
- [self->envelope release];
- [self->info release];
- [self->path release];
+- (id) init
+{
+ if ((self = [super init]))
+ {
+ IMAP4ID = -1;
+ headers = [NSMutableDictionary new];
+ text = @"";
+ sourceURL = nil;
+ sourceFlag = nil;
+ inReplyTo = nil;
+ }
+
+ return self;
+}
+
+- (void) dealloc
+{
+ [headers release];
+ [text release];
+ [envelope release];
+ [path release];
+ [sourceURL release];
+ [sourceFlag release];
+ [inReplyTo 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 (self->path != nil)
- return self->path;
-
- self->path = [[[self userSpoolFolderPath] stringByAppendingPathComponent:
- [self nameInContainer]] copy];
- return self->path;
+- (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: '%@'",
+- (void) setHeaders: (NSDictionary *) newHeaders
+{
+ id headerValue;
+ unsigned int count;
+
+ for (count = 0; count < 7; 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) setInReplyTo: (NSString *) newInReplyTo
+{
+ ASSIGN (inReplyTo, newInReplyTo);
+}
+
+- (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 (inReplyTo)
+ [infos setObject: inReplyTo forKey: @"inReplyTo"];
+ 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]];
- 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 */
- [self->info release]; self->info = nil;
-
- return nil /* everything is excellent */;
+ 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];
+
+ value = [infoDict objectForKey: @"inReplyTo"];
+ if (value)
+ [self setInReplyTo: value];
+}
+
+- (NSString *) relativeImap4Name
+{
+ return [NSString stringWithFormat: @"%d", IMAP4ID];
}
-- (NSDictionary *)fetchInfo {
+
+- (void) fetchInfo
+{
NSString *p;
+ NSDictionary *infos;
+ NSFileManager *fm;
- if (self->info != nil)
- return self->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) _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;
+
+ to = [NSMutableArray arrayWithCapacity:2];
+
+ /* first check for "reply-to" */
- self->info = [[NSDictionary alloc] initWithContentsOfFile:p];
- if (self->info == nil)
- [self errorWithFormat:@"draft info dictionary broken at path: %@", p];
+ 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' */
- return self->info;
+ if (_replyToAll)
+ {
+ to = [NSMutableArray arrayWithCapacity:8];
+
+ [self _addEMailsOfAddresses: [_envelope to] toArray: to];
+ [self _addEMailsOfAddresses: [_envelope cc] toArray: to];
+
+ [_info setObject: to forKey: @"cc"];
+ }
+}
+
+- (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths
+ fromResponseFetch: (NSDictionary *) fetch;
+{
+ NSEnumerator *attachmentKeys;
+ NSMutableArray *bodies;
+ NSString *currentKey;
+ NSDictionary *body;
+
+ bodies = [NSMutableArray array];
+
+ attachmentKeys = [paths objectEnumerator];
+ while ((currentKey = [attachmentKeys nextObject]))
+ {
+ body = [fetch objectForKey: [currentKey lowercaseString]];
+ [bodies addObject: [body objectForKey: @"data"]];
+ }
+
+ return bodies;
+}
+
+- (void) _fetchAttachments: (NSArray *) parts
+ fromMail: (SOGoMailObject *) sourceMail
+{
+ unsigned int count, max;
+ NSArray *paths, *bodies;
+ NSData *body;
+ NSDictionary *currentInfo;
+ NGHashMap *response;
+
+ max = [parts count];
+ if (max > 0)
+ {
+ paths = [parts keysWithFormat: @"BODY[%{path}]"];
+ response = [[sourceMail fetchParts: paths] objectForKey: @"RawResponse"];
+ bodies = [self _attachmentBodiesFromPaths: paths
+ fromResponseFetch: [response objectForKey: @"fetch"]];
+ for (count = 0; count < max; count++)
+ {
+ currentInfo = [parts objectAtIndex: count];
+ body = [[bodies objectAtIndex: count]
+ bodyDataFromEncoding: [currentInfo
+ objectForKey: @"encoding"]];
+ [self saveAttachment: body withMetadata: currentInfo];
+ }
+ }
+}
+
+- (void) fetchMailForEditing: (SOGoMailObject *) sourceMail
+{
+ NSString *subject;
+ NSMutableDictionary *info;
+ NSMutableArray *addresses;
+ NGImap4Envelope *sourceEnvelope;
+
+ [sourceMail fetchCoreInfos];
+
+ [self _fetchAttachments: [sourceMail fetchFileAttachmentKeys]
+ fromMail: sourceMail];
+ info = [NSMutableDictionary dictionaryWithCapacity: 16];
+ subject = [sourceMail subject];
+ if ([subject length] > 0)
+ [info setObject: subject forKey: @"subject"];
+
+ sourceEnvelope = [sourceMail envelope];
+ addresses = [NSMutableArray array];
+ [self _addEMailsOfAddresses: [sourceEnvelope to] toArray: addresses];
+ [info setObject: addresses forKey: @"to"];
+ addresses = [NSMutableArray array];
+ [self _addEMailsOfAddresses: [sourceEnvelope cc] toArray: addresses];
+ if ([addresses count] > 0)
+ [info setObject: addresses forKey: @"cc"];
+ addresses = [NSMutableArray array];
+ [self _addEMailsOfAddresses: [sourceEnvelope bcc] toArray: addresses];
+ if ([addresses count] > 0)
+ [info setObject: addresses forKey: @"bcc"];
+ addresses = [NSMutableArray array];
+ [self _addEMailsOfAddresses: [sourceEnvelope replyTo] toArray: addresses];
+ if ([addresses count] > 0)
+ [info setObject: addresses forKey: @"replyTo"];
+ [self setHeaders: info];
+
+ [self setText: [sourceMail contentForEditing]];
+ [self setSourceURL: [sourceMail imap4URLString]];
+ IMAP4ID = [[sourceMail nameInContainer] intValue];
+
+ [self storeInfo];
+}
+
+- (void) fetchMailForReplying: (SOGoMailObject *) sourceMail
+ toAll: (BOOL) toAll
+{
+ NSString *contentForReply, *msgID;
+ NSMutableDictionary *info;
+ NGImap4Envelope *sourceEnvelope;
+
+ [sourceMail fetchCoreInfos];
+
+ info = [NSMutableDictionary dictionaryWithCapacity: 16];
+ [info setObject: [sourceMail subjectForReply] forKey: @"subject"];
+
+ sourceEnvelope = [sourceMail envelope];
+ [self _fillInReplyAddresses: info replyToAll: toAll
+ envelope: sourceEnvelope];
+ msgID = [sourceEnvelope messageID];
+ if ([msgID length] > 0)
+ [self setInReplyTo: msgID];
+ 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;
+ SOGoUser *currentUser;
+
+ [sourceMail fetchCoreInfos];
+
+ info = [NSDictionary dictionaryWithObject: [sourceMail subjectForForward]
+ forKey: @"subject"];
+ [self setHeaders: info];
+ [self setSourceURL: [sourceMail imap4URLString]];
+ [self setSourceFlag: @"$Forwarded"];
+
+ /* attach message */
+ currentUser = [context activeUser];
+ if ([[currentUser messageForwarding] isEqualToString: @"inline"])
+ [self setText: [sourceMail contentForInlineForward]];
+ else
+ {
+ // TODO: use subject for filename?
+// error = [newDraft saveAttachment:content withName:@"forward.mail"];
+ attachment = [NSDictionary dictionaryWithObjectsAndKeys:
+ [sourceMail filenameForForward], @"filename",
+ @"message/rfc822", @"mimetype",
+ 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 {
- static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil };
+- (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]];
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 withName:(NSString *)_name {
- NSString *p;
-
+- (NSException *) saveAttachment: (NSData *) _attach
+ withMetadata: (NSDictionary *) metadata
+{
+ NSString *p, *name, *mimeType;
+ NSRange r;
+
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!"];
}
- if (![self isValidAttachmentName:_name])
- return [self invalidAttachmentNameError:_name];
+
+ name = [metadata objectForKey: @"filename"];
+ r = [name rangeOfString: @"\\"
+ options: NSBackwardsSearch];
+ if (r.length > 0)
+ name = [name substringFromIndex: r.location + 1];
+
+ if (![self isValidAttachmentName: name])
+ return [self invalidAttachmentNameError: name];
- p = [self pathToAttachmentWithName:_name];
- if (![_attach writeToFile:p atomically:YES]) {
- return [NSException exceptionWithHTTPStatus:500 /* Server Error */
- reason:@"Could not write attachment to draft!"];
- }
+ p = [self pathToAttachmentWithName: name];
+ if (![_attach writeToFile: p atomically: YES])
+ {
+ return [NSException exceptionWithHTTPStatus:500 /* Server Error */
+ reason: @"Could not write attachment to draft!"];
+ }
+
+ mimeType = [metadata objectForKey: @"mimetype"];
+ if ([mimeType length] > 0)
+ {
+ p = [self pathToAttachmentWithName:
+ [NSString stringWithFormat: @".%@.mime", name]];
+ if (![[mimeType dataUsingEncoding: NSUTF8StringEncoding]
+ writeToFile: p atomically: YES])
+ {
+ return [NSException exceptionWithHTTPStatus:500 /* Server Error */
+ 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:@"text/plain; charset=utf-8" 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 {
- NSDictionary *lInfo;
+- (NGMimeMessage *) mimeMessageForContentWithHeaderMap: (NGMutableHashMap *) map
+{
NGMimeMessage *message;
- WOContext *ctx;
- NSString *fromInternetSuffix;
- BOOL addSuffix;
+// BOOL addSuffix;
id body;
- if ((lInfo = [self fetchInfo]) == nil)
- return nil;
-
- ctx = [[WOApplication application] context];
- addSuffix = [ctx isAccessFromIntranet] ? NO : YES;
- if (addSuffix) {
- fromInternetSuffix =
- [fromInternetSuffixPattern stringByReplacingVariablesWithBindings:
- [ctx request]
- stringForUnknownBindings:@""];
-
- addSuffix = [fromInternetSuffix length] > 0 ? YES : NO;
- }
-
- [map setObject:@"text/plain" forKey:@"content-type"];
- if ((body = [lInfo objectForKey:@"text"]) != nil) {
- if ([body isKindOfClass:[NSString class]]) {
- if (addSuffix)
- body = [body stringByAppendingString:fromInternetSuffix];
-
- /* Note: just 'utf8' is displayed wrong in Mail.app */
- [map setObject:@"text/plain; charset=utf-8" forKey:@"content-type"];
- body = [body dataUsingEncoding:NSUTF8StringEncoding];
+ [map setObject: @"text/plain" 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 (addSuffix) {
+// [self warnWithFormat: @"Note: cannot add Internet marker to body: %@",
+// NSStringFromClass([body class])];
+// }
+
+ message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
+ [message setBody: body];
}
- else if ([body isKindOfClass:[NSData class]] && addSuffix) {
- body = [[body mutableCopy] autorelease];
- [(NSMutableData *)body appendData:
- [fromInternetSuffix dataUsingEncoding:
- NSUTF8StringEncoding]];
- }
- else if (addSuffix) {
- [self warnWithFormat:@"Note: cannot add Internet marker to body: %@",
- NSStringFromClass([body class])];
- }
- }
- else if (addSuffix)
- body = [fromInternetSuffix dataUsingEncoding:NSUTF8StringEncoding];
-
- 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 *s;
-
- s = [self mimeTypeForExtension:[_name pathExtension]];
- if ([_name length] > 0) {
- s = [s stringByAppendingString:@"; name=\""];
- s = [s stringByAppendingString:_name];
- s = [s stringByAppendingString:@"\""];
- }
+- (NSString *) contentTypeForAttachmentWithName: (NSString *) _name
+{
+ NSString *s, *p;
+ NSData *mimeData;
+
+ p = [self pathToAttachmentWithName:
+ [NSString stringWithFormat: @".%@.mime", _name]];
+ mimeData = [NSData dataWithContentsOfFile: p];
+ if (mimeData)
+ {
+ s = [[NSString alloc] initWithData: mimeData
+ encoding: NSUTF8StringEncoding];
+ [s autorelease];
+ }
+ else
+ {
+ s = [self mimeTypeForExtension:[_name pathExtension]];
+ if ([_name length] > 0)
+ 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 = [cd stringByAppendingString:_name];
- cd = [cd stringByAppendingString:@"\""];
-
+ cd = [cdtype stringByAppendingString: @"; filename=\""];
+ cd = [cd stringByAppendingString: _name];
+ 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;
/* 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;
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 */
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 {
/*
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]
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;
}
}
-- (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;
+ 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 count] == 0) {
- [self errorWithFormat:@"missing 'to' recipient in email!"];
- return nil;
- }
- [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: @"to"]) != nil)
+ [map setObjects: emails forKey: @"to"];
+ 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)
- [map setObject:s forKey:@"subject"];
+ if (inReplyTo)
+ [map setObject: inReplyTo forKey: @"in-reply-to"];
+
+ if ([(s = [headers objectForKey: @"subject"]) length] > 0)
+ [map setObject: [s asQPSubjectString: @"utf-8"]
+ forKey: @"subject"];
+// [map setObject: [s asQPSubjectString: @"utf-8"] forKey: @"subject"];
/* add standard headers */
-
- [map addObject:[NSCalendarDate date] forKey:@"date"];
- [map addObject:@"1.0" forKey:@"MIME-Version"];
- [map addObject:userAgent forKey:@"X-Mailer"];
+
+ dateString = [[NSCalendarDate date] rfc822DateString];
+ [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];
-}
-- (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_h {
- NGMimeMessageGenerator *gen;
- NSAutoreleasePool *pool;
- NGMimeMessage *message;
- NSString *tmpPath;
+ 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!"];
+ }
- 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];
+ return message;
}
-- (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];
-- (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;
+ return message;
}
-- (NSException *)sendMimeMessageAtPath:(NSString *)_path {
- static NGSendMail *mailer = nil;
- NSArray *recipients;
- NSString *from;
-
- /* validate */
-
- recipients = [self allRecipients];
- from = [self sender];
- if ([recipients count] == 0) {
- return [NSException exceptionWithHTTPStatus:500 /* server error */
- reason:@"draft has no recipients set!"];
- }
- if ([from length] == 0) {
- return [NSException exceptionWithHTTPStatus:500 /* server error */
- reason:@"draft has no sender (from) set!"];
- }
-
- /* setup mailer object */
-
- if (mailer == nil)
- mailer = [[NGSendMail sharedSendMail] retain];
- if (![mailer isSendMailAvailable]) {
- [self errorWithFormat:@"missing sendmail binary!"];
- return [NSException exceptionWithHTTPStatus:500 /* server error */
- reason:@"did not find sendmail binary!"];
- }
-
- /* send mail */
-
- return [mailer sendMailAtPath:_path toRecipients:recipients sender:from];
+- (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 *) 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 = [self sendMimeMessageAtPath:tmpPath];
-
- /* 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];
+ }
+ if (!draftDeleteDisabled)
+ error = [self delete];
+ }
+ }
+ }
return error;
}
-/* operations */
+- (NSException *) delete
+{
+ NSException *error;
-- (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 ([[NSFileManager defaultManager]
+ removeFileAtPath: [self draftFolderPath]
+ handler: nil])
+ error = nil;
+ else
+ error = [NSException exceptionWithHTTPStatus: 500 /* server error */
+ reason: @"could not 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;
+ return error;
}
-- (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];
+/* operations */
- return data;
-}
-- (NSString *)contentAsString {
+- (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 {
- /*
- Override, because SOGoObject's GETAction uses the less efficient
- -contentAsString method.
- */
- WORequest *rq;
-
- 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!"];
+ NSData *message;
+
+ 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 (self->envelope != nil)
- return self->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];
- }
-
- self->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 self->envelope;
+ return str;
}
/* debugging */
-- (BOOL)isDebuggingEnabled {
+- (BOOL) isDebuggingEnabled
+{
return debugOn;
}