2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "SOGoDraftObject.h"
23 #include <SoObjects/SOGo/WOContext+Agenor.h>
24 #include <NGMail/NGMimeMessage.h>
25 #include <NGMail/NGMimeMessageGenerator.h>
26 #include <NGMail/NGSendMail.h>
27 #include <NGMime/NGMimeBodyPart.h>
28 #include <NGMime/NGMimeFileData.h>
29 #include <NGMime/NGMimeMultipartBody.h>
30 #include <NGMime/NGMimeType.h>
31 #include <NGImap4/NGImap4Envelope.h>
32 #include <NGImap4/NGImap4EnvelopeAddress.h>
33 #include <NGExtensions/NSFileManager+Extensions.h>
36 @implementation SOGoDraftObject
38 static NGMimeType *TextPlainType = nil;
39 static NGMimeType *MultiMixedType = nil;
40 static NSString *userAgent = @"SOGoMail 1.0";
41 static BOOL draftDeleteDisabled = NO; // for debugging
42 static BOOL debugOn = NO;
43 static BOOL showTextAttachmentsInline = NO;
44 static NSString *fromInternetSuffixPattern = nil;
47 return [super version] + 0 /* v1 */;
51 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
53 NSAssert2([super version] == 1,
54 @"invalid superclass (%@) version %i !",
55 NSStringFromClass([self superclass]), [super version]);
57 /* Note: be aware of the charset issues before enabling this! */
58 showTextAttachmentsInline = [ud boolForKey:@"SOGoShowTextAttachmentsInline"];
60 if ((draftDeleteDisabled = [ud boolForKey:@"SOGoNoDraftDeleteAfterSend"]))
61 NSLog(@"WARNING: draft delete is disabled! (SOGoNoDraftDeleteAfterSend)");
63 fromInternetSuffixPattern = [ud stringForKey:@"SOGoInternetMailSuffix"];
64 if ([fromInternetSuffixPattern length] == 0)
65 NSLog(@"Note: no 'SOGoInternetMailSuffix' is configured.");
67 fromInternetSuffixPattern =
68 [@"\n" stringByAppendingString:fromInternetSuffixPattern];
71 TextPlainType = [[NGMimeType mimeType:@"text" subType:@"plain"] copy];
72 MultiMixedType = [[NGMimeType mimeType:@"multipart" subType:@"mixed"] copy];
76 [self->envelope release];
82 /* draft folder functionality */
84 - (NSFileManager *)spoolFileManager {
85 return [[self container] spoolFileManager];
87 - (NSString *)userSpoolFolderPath {
88 return [[self container] userSpoolFolderPath];
90 - (BOOL)_ensureUserSpoolFolderPath {
91 return [[self container] _ensureUserSpoolFolderPath];
94 /* draft object functionality */
96 - (NSString *)draftFolderPath {
97 if (self->path != nil)
100 self->path = [[[self userSpoolFolderPath] stringByAppendingPathComponent:
101 [self nameInContainer]] copy];
104 - (BOOL)_ensureDraftFolderPath {
107 if (![self _ensureUserSpoolFolderPath])
110 if ((fm = [self spoolFileManager]) == nil) {
111 [self errorWithFormat:@"missing spool file manager!"];
114 return [fm createDirectoriesAtPath:[self draftFolderPath] attributes:nil];
117 - (NSString *)infoPath {
118 return [[self draftFolderPath]
119 stringByAppendingPathComponent:@".info.plist"];
124 - (NSException *)storeInfo:(NSDictionary *)_info {
126 return [NSException exceptionWithHTTPStatus:500 /* server error */
127 reason:@"got no info to write for draft!"];
129 if (![self _ensureDraftFolderPath]) {
130 [self errorWithFormat:@"could not create folder for draft: '%@'",
131 [self draftFolderPath]];
132 return [NSException exceptionWithHTTPStatus:500 /* server error */
133 reason:@"could not create folder for draft!"];
135 if (![_info writeToFile:[self infoPath] atomically:YES]) {
136 [self errorWithFormat:@"could not write info: '%@'", [self infoPath]];
137 return [NSException exceptionWithHTTPStatus:500 /* server error */
138 reason:@"could not write draft info!"];
141 /* reset info cache */
142 [self->info release]; self->info = nil;
144 return nil /* everything is excellent */;
146 - (NSDictionary *)fetchInfo {
149 if (self->info != nil)
153 if (![[self spoolFileManager] fileExistsAtPath:p]) {
154 [self debugWithFormat:@"Note: info object does not yet exist: %@", p];
158 self->info = [[NSDictionary alloc] initWithContentsOfFile:p];
159 if (self->info == nil)
160 [self errorWithFormat:@"draft info dictionary broken at path: %@", p];
167 - (NSString *)sender {
170 if ((tmp = [[self fetchInfo] objectForKey:@"from"]) == nil)
172 if ([tmp isKindOfClass:[NSArray class]])
173 return [tmp count] > 0 ? [tmp objectAtIndex:0] : nil;
179 - (NSArray *)fetchAttachmentNames {
185 fm = [self spoolFileManager];
186 if ((files = [fm directoryContentsAtPath:[self draftFolderPath]]) == nil)
189 count = [files count];
190 ma = [NSMutableArray arrayWithCapacity:count];
191 for (i = 0; i < count; i++) {
194 filename = [files objectAtIndex:i];
195 if ([filename hasPrefix:@"."])
198 [ma addObject:filename];
203 - (BOOL)isValidAttachmentName:(NSString *)_name {
204 static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil };
208 if (![_name isNotNull]) return NO;
209 if ([_name length] == 0) return NO;
210 if ([_name hasPrefix:@"."]) return NO;
212 for (i = 0; sescape[i] != nil; i++) {
213 r = [_name rangeOfString:sescape[i]];
214 if (r.length > 0) return NO;
219 - (NSString *)pathToAttachmentWithName:(NSString *)_name {
220 if ([_name length] == 0)
223 return [[self draftFolderPath] stringByAppendingPathComponent:_name];
226 - (NSException *)invalidAttachmentNameError:(NSString *)_name {
227 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
228 reason:@"Invalid attachment name!"];
231 - (NSException *)saveAttachment:(NSData *)_attach withName:(NSString *)_name {
234 if (![_attach isNotNull]) {
235 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
236 reason:@"Missing attachment content!"];
239 if (![self _ensureDraftFolderPath]) {
240 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
241 reason:@"Could not create folder for draft!"];
243 if (![self isValidAttachmentName:_name])
244 return [self invalidAttachmentNameError:_name];
246 p = [self pathToAttachmentWithName:_name];
247 if (![_attach writeToFile:p atomically:YES]) {
248 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
249 reason:@"Could not write attachment to draft!"];
252 return nil; /* everything OK */
255 - (NSException *)deleteAttachmentWithName:(NSString *)_name {
259 if (![self isValidAttachmentName:_name])
260 return [self invalidAttachmentNameError:_name];
262 fm = [self spoolFileManager];
263 p = [self pathToAttachmentWithName:_name];
264 if (![fm fileExistsAtPath:p])
265 return nil; /* well, doesn't exist, so its deleted ;-) */
267 if (![fm removeFileAtPath:p handler:nil]) {
268 [self logWithFormat:@"ERROR: failed to delete file: %@", p];
269 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
270 reason:@"Could not delete attachment from draft!"];
272 return nil; /* everything OK */
275 /* NGMime representations */
277 - (NGMimeBodyPart *)bodyPartForText {
279 This add the text typed by the user (the primary plain/text part).
281 NGMutableHashMap *map;
282 NGMimeBodyPart *bodyPart;
286 if ((lInfo = [self fetchInfo]) == nil)
289 /* prepare header of body part */
291 map = [[[NGMutableHashMap alloc] initWithCapacity:2] autorelease];
293 // TODO: set charset in header!
294 [map setObject:@"text/plain" forKey:@"content-type"];
295 if ((body = [lInfo objectForKey:@"text"]) != nil) {
296 if ([body isKindOfClass:[NSString class]]) {
297 [map setObject:@"text/plain; charset=utf-8" forKey:@"content-type"];
298 body = [body dataUsingEncoding:NSUTF8StringEncoding];
302 /* prepare body content */
304 bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
305 [bodyPart setBody:body];
309 - (NGMimeMessage *)mimeMessageForContentWithHeaderMap:(NGMutableHashMap *)map {
311 NGMimeMessage *message;
313 NSString *fromInternetSuffix;
317 if ((lInfo = [self fetchInfo]) == nil)
320 ctx = [[WOApplication application] context];
321 addSuffix = [ctx isAccessFromIntranet] ? NO : YES;
324 [fromInternetSuffixPattern stringByReplacingVariablesWithBindings:
326 stringForUnknownBindings:@""];
328 addSuffix = [fromInternetSuffix length] > 0 ? YES : NO;
331 [map setObject:@"text/plain" forKey:@"content-type"];
332 if ((body = [lInfo objectForKey:@"text"]) != nil) {
333 if ([body isKindOfClass:[NSString class]]) {
335 body = [body stringByAppendingString:fromInternetSuffix];
337 /* Note: just 'utf8' is displayed wrong in Mail.app */
338 [map setObject:@"text/plain; charset=utf-8" forKey:@"content-type"];
339 body = [body dataUsingEncoding:NSUTF8StringEncoding];
341 else if ([body isKindOfClass:[NSData class]] && addSuffix) {
342 body = [[body mutableCopy] autorelease];
343 [(NSMutableData *)body appendData:
344 [fromInternetSuffix dataUsingEncoding:
345 NSUTF8StringEncoding]];
347 else if (addSuffix) {
348 [self warnWithFormat:@"Note: cannot add Internet marker to body: %@",
349 NSStringFromClass([body class])];
353 body = [fromInternetSuffix dataUsingEncoding:NSUTF8StringEncoding];
355 message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
356 [message setBody:body];
360 - (NSString *)mimeTypeForExtension:(NSString *)_ext {
361 // TODO: make configurable
362 // TODO: use /etc/mime-types
363 if ([_ext isEqualToString:@"txt"]) return @"text/plain";
364 if ([_ext isEqualToString:@"html"]) return @"text/html";
365 if ([_ext isEqualToString:@"htm"]) return @"text/html";
366 if ([_ext isEqualToString:@"gif"]) return @"image/gif";
367 if ([_ext isEqualToString:@"jpg"]) return @"image/jpeg";
368 if ([_ext isEqualToString:@"jpeg"]) return @"image/jpeg";
369 if ([_ext isEqualToString:@"mail"]) return @"message/rfc822";
370 return @"application/octet-stream";
373 - (NSString *)contentTypeForAttachmentWithName:(NSString *)_name {
376 s = [self mimeTypeForExtension:[_name pathExtension]];
377 if ([_name length] > 0) {
378 s = [s stringByAppendingString:@"; name=\""];
379 s = [s stringByAppendingString:_name];
380 s = [s stringByAppendingString:@"\""];
384 - (NSString *)contentDispositionForAttachmentWithName:(NSString *)_name {
389 type = [self contentTypeForAttachmentWithName:_name];
391 if ([type hasPrefix:@"text/"])
392 cdtype = showTextAttachmentsInline ? @"inline" : @"attachment";
393 else if ([type hasPrefix:@"image/"] || [type hasPrefix:@"message"])
396 cdtype = @"attachment";
398 cd = [cdtype stringByAppendingString:@"; filename=\""];
399 cd = [cd stringByAppendingString:_name];
400 cd = [cd stringByAppendingString:@"\""];
402 // TODO: add size parameter (useful addition, RFC 2183)
406 - (NGMimeBodyPart *)bodyPartForAttachmentWithName:(NSString *)_name {
408 NGMutableHashMap *map;
409 NGMimeBodyPart *bodyPart;
412 BOOL attachAsString, is7bit;
416 if (_name == nil) return nil;
418 /* check attachment */
420 fm = [self spoolFileManager];
421 p = [self pathToAttachmentWithName:_name];
422 if (![fm isReadableFileAtPath:p]) {
423 [self errorWithFormat:@"did not find attachment: '%@'", _name];
429 /* prepare header of body part */
431 map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease];
433 if ((s = [self contentTypeForAttachmentWithName:_name]) != nil) {
434 [map setObject:s forKey:@"content-type"];
435 if ([s hasPrefix:@"text/"])
436 attachAsString = YES;
437 else if ([s hasPrefix:@"message/rfc822"])
440 if ((s = [self contentDispositionForAttachmentWithName:_name]))
441 [map setObject:s forKey:@"content-disposition"];
443 /* prepare body content */
445 if (attachAsString) { // TODO: is this really necessary?
448 content = [[NSData alloc] initWithContentsOfMappedFile:p];
450 s = [[NSString alloc] initWithData:content
451 encoding:[NSString defaultCStringEncoding]];
454 [content release]; content = nil;
457 [self warnWithFormat:
458 @"could not get text attachment as string: '%@'", _name];
465 Note: Apparently NGMimeFileData objects are not processed by the MIME
468 body = [[NGMimeFileData alloc] initWithPath:p removeFile:NO];
469 [map setObject:@"7bit" forKey:@"content-transfer-encoding"];
470 [map setObject:[NSNumber numberWithInt:[body length]]
471 forKey:@"content-length"];
475 Note: in OGo this is done in LSWImapMailEditor.m:2477. Apparently
476 NGMimeFileData objects are not processed by the MIME generator!
480 content = [[NSData alloc] initWithContentsOfMappedFile:p];
481 encoded = [content dataByEncodingBase64];
482 [content release]; content = nil;
484 [map setObject:@"base64" forKey:@"content-transfer-encoding"];
485 [map setObject:[NSNumber numberWithInt:[encoded length]]
486 forKey:@"content-length"];
488 /* Note: the -init method will create a temporary file! */
489 body = [[NGMimeFileData alloc] initWithBytes:[encoded bytes]
490 length:[encoded length]];
493 bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
494 [bodyPart setBody:body];
496 [body release]; body = nil;
500 - (NSArray *)bodyPartsForAllAttachments {
501 /* returns nil on error */
502 NSMutableArray *bodyParts;
506 names = [self fetchAttachmentNames];
507 if ((count = [names count]) == 0)
508 return [NSArray array];
510 bodyParts = [NSMutableArray arrayWithCapacity:count];
511 for (i = 0; i < count; i++) {
512 NGMimeBodyPart *bodyPart;
514 bodyPart = [self bodyPartForAttachmentWithName:[names objectAtIndex:i]];
518 [bodyParts addObject:bodyPart];
523 - (NGMimeMessage *)mimeMultiPartMessageWithHeaderMap:(NGMutableHashMap *)map
524 andBodyParts:(NSArray *)_bodyParts
526 NGMimeMessage *message;
527 NGMimeMultipartBody *mBody;
528 NGMimeBodyPart *part;
531 [map addObject:MultiMixedType forKey:@"content-type"];
533 message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
534 mBody = [[NGMimeMultipartBody alloc] initWithPart:message];
536 part = [self bodyPartForText];
537 [mBody addBodyPart:part];
539 e = [_bodyParts objectEnumerator];
540 while ((part = [e nextObject]) != nil)
541 [mBody addBodyPart:part];
543 [message setBody:mBody];
544 [mBody release]; mBody = nil;
548 - (void)_addHeaders:(NSDictionary *)_h toHeaderMap:(NGMutableHashMap *)_map {
555 names = [_h keyEnumerator];
556 while ((name = [names nextObject]) != nil) {
559 value = [_h objectForKey:name];
560 [_map addObject:value forKey:name];
564 - (BOOL)isEmptyValue:(id)_value {
565 if (![_value isNotNull])
568 if ([_value isKindOfClass:[NSArray class]])
569 return [_value count] == 0 ? YES : NO;
571 if ([_value isKindOfClass:[NSString class]])
572 return [_value length] == 0 ? YES : NO;
577 - (NGMutableHashMap *)mimeHeaderMapWithHeaders:(NSDictionary *)_headers {
578 NGMutableHashMap *map;
579 NSDictionary *lInfo; // TODO: this should be some kind of object?
584 if ((lInfo = [self fetchInfo]) == nil)
587 map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
591 if ((emails = [lInfo objectForKey:@"to"]) != nil) {
592 if ([emails count] == 0) {
593 [self errorWithFormat:@"missing 'to' recipient in email!"];
596 [map setObjects:emails forKey:@"to"];
598 if ((emails = [lInfo objectForKey:@"cc"]) != nil)
599 [map setObjects:emails forKey:@"cc"];
600 if ((emails = [lInfo objectForKey:@"bcc"]) != nil)
601 [map setObjects:emails forKey:@"bcc"];
605 from = [lInfo objectForKey:@"from"];
606 replyTo = [lInfo objectForKey:@"replyTo"];
608 if (![self isEmptyValue:from]) {
609 if ([from isKindOfClass:[NSArray class]])
610 [map setObjects:from forKey:@"from"];
612 [map setObject:from forKey:@"from"];
615 if (![self isEmptyValue:replyTo]) {
616 if ([from isKindOfClass:[NSArray class]])
617 [map setObjects:from forKey:@"reply-to"];
619 [map setObject:from forKey:@"reply-to"];
621 else if (![self isEmptyValue:from])
622 [map setObjects:[map objectsForKey:@"from"] forKey:@"reply-to"];
626 if ([(s = [lInfo objectForKey:@"subject"]) length] > 0)
627 [map setObject:s forKey:@"subject"];
629 /* add standard headers */
631 [map addObject:[NSCalendarDate date] forKey:@"date"];
632 [map addObject:@"1.0" forKey:@"MIME-Version"];
633 [map addObject:userAgent forKey:@"X-Mailer"];
635 /* add custom headers */
637 [self _addHeaders:[lInfo objectForKey:@"headers"] toHeaderMap:map];
638 [self _addHeaders:_headers toHeaderMap:map];
643 - (NGMimeMessage *)mimeMessageWithHeaders:(NSDictionary *)_headers {
644 NSAutoreleasePool *pool;
645 NGMutableHashMap *map;
647 NGMimeMessage *message;
649 pool = [[NSAutoreleasePool alloc] init];
651 if ([self fetchInfo] == nil) {
652 [self errorWithFormat:@"could not locate draft fetch info!"];
656 if ((map = [self mimeHeaderMapWithHeaders:_headers]) == nil)
658 [self debugWithFormat:@"MIME Envelope: %@", map];
660 if ((bodyParts = [self bodyPartsForAllAttachments]) == nil) {
661 [self errorWithFormat:
662 @"could not create body parts for attachments!"];
663 return nil; // TODO: improve error handling, return exception
665 [self debugWithFormat:@"attachments: %@", bodyParts];
667 if ([bodyParts count] == 0) {
669 message = [self mimeMessageForContentWithHeaderMap:map];
672 /* attachments, create multipart/mixed */
673 message = [self mimeMultiPartMessageWithHeaderMap:map
674 andBodyParts:bodyParts];
676 [self debugWithFormat:@"message: %@", message];
678 message = [message retain];
680 return [message autorelease];
682 - (NGMimeMessage *)mimeMessage {
683 return [self mimeMessageWithHeaders:nil];
686 - (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_h {
687 NGMimeMessageGenerator *gen;
688 NSAutoreleasePool *pool;
689 NGMimeMessage *message;
692 pool = [[NSAutoreleasePool alloc] init];
694 message = [self mimeMessageWithHeaders:_h];
695 if (![message isNotNull])
697 if ([message isKindOfClass:[NSException class]]) {
698 [self errorWithFormat:@"error: %@", message];
702 gen = [[NGMimeMessageGenerator alloc] init];
703 tmpPath = [[gen generateMimeFromPartToFile:message] copy];
704 [gen release]; gen = nil;
707 return [tmpPath autorelease];
709 - (NSString *)saveMimeMessageToTemporaryFile {
710 return [self saveMimeMessageToTemporaryFileWithHeaders:nil];
713 - (void)deleteTemporaryMessageFile:(NSString *)_path {
716 if (![_path isNotNull])
719 fm = [NSFileManager defaultManager];
720 if (![fm fileExistsAtPath:_path])
723 [fm removeFileAtPath:_path handler:nil];
726 - (NSArray *)allRecipients {
731 if ((lInfo = [self fetchInfo]) == nil)
734 ma = [NSMutableArray arrayWithCapacity:16];
735 if ((tmp = [lInfo objectForKey:@"to"]) != nil)
736 [ma addObjectsFromArray:tmp];
737 if ((tmp = [lInfo objectForKey:@"cc"]) != nil)
738 [ma addObjectsFromArray:tmp];
739 if ((tmp = [lInfo objectForKey:@"bcc"]) != nil)
740 [ma addObjectsFromArray:tmp];
744 - (NSException *)sendMimeMessageAtPath:(NSString *)_path {
745 static NGSendMail *mailer = nil;
751 recipients = [self allRecipients];
752 from = [self sender];
753 if ([recipients count] == 0) {
754 return [NSException exceptionWithHTTPStatus:500 /* server error */
755 reason:@"draft has no recipients set!"];
757 if ([from length] == 0) {
758 return [NSException exceptionWithHTTPStatus:500 /* server error */
759 reason:@"draft has no sender (from) set!"];
762 /* setup mailer object */
765 mailer = [[NGSendMail sharedSendMail] retain];
766 if (![mailer isSendMailAvailable]) {
767 [self errorWithFormat:@"missing sendmail binary!"];
768 return [NSException exceptionWithHTTPStatus:500 /* server error */
769 reason:@"did not find sendmail binary!"];
774 return [mailer sendMailAtPath:_path toRecipients:recipients sender:from];
777 - (NSException *)sendMail {
781 /* save MIME mail to file */
783 tmpPath = [self saveMimeMessageToTemporaryFile];
784 if (![tmpPath isNotNull]) {
785 return [NSException exceptionWithHTTPStatus:500 /* server error */
786 reason:@"could not save MIME message for draft!"];
790 error = [self sendMimeMessageAtPath:tmpPath];
792 /* delete temporary file */
793 [self deleteTemporaryMessageFile:tmpPath];
800 - (NSException *)delete {
805 if ((fm = [self spoolFileManager]) == nil) {
806 [self errorWithFormat:@"missing spool file manager!"];
807 return [NSException exceptionWithHTTPStatus:500 /* server error */
808 reason:@"missing spool file manager!"];
811 p = [self draftFolderPath];
812 if (![fm fileExistsAtPath:p]) {
813 return [NSException exceptionWithHTTPStatus:404 /* not found */
814 reason:@"did not find draft!"];
817 e = [[fm directoryContentsAtPath:p] objectEnumerator];
818 while ((sp = [e nextObject])) {
819 sp = [p stringByAppendingPathComponent:sp];
820 if (draftDeleteDisabled) {
821 [self logWithFormat:@"should delete draft file %@ ...", sp];
825 if (![fm removeFileAtPath:sp handler:nil]) {
826 return [NSException exceptionWithHTTPStatus:500 /* server error */
827 reason:@"failed to delete draft!"];
831 if (draftDeleteDisabled) {
832 [self logWithFormat:@"should delete draft directory: %@", p];
835 if (![fm removeFileAtPath:p handler:nil]) {
836 return [NSException exceptionWithHTTPStatus:500 /* server error */
837 reason:@"failed to delete draft directory!"];
843 - (NSData *)content {
844 /* Note: does not cache, expensive operation */
848 if ((p = [self saveMimeMessageToTemporaryFile]) == nil)
851 data = [NSData dataWithContentsOfMappedFile:p];
853 /* delete temporary file */
854 [self deleteTemporaryMessageFile:p];
858 - (NSString *)contentAsString {
862 if ((data = [self content]) == nil)
865 str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
867 [self errorWithFormat:@"could not load draft as ASCII (data size=%d)",
872 return [str autorelease];
877 - (id)DELETEAction:(id)_ctx {
880 if ((error = [self delete]) != nil)
883 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
886 - (id)GETAction:(id)_ctx {
888 Override, because SOGoObject's GETAction uses the less efficient
889 -contentAsString method.
894 if ([rq isSoWebDAVRequest]) {
898 if ((content = [self content]) == nil) {
899 return [NSException exceptionWithHTTPStatus:500
900 reason:@"Could not generate MIME content!"];
903 [r setHeader:@"message/rfc822" forKey:@"content-type"];
904 [r setContent:content];
908 return [super GETAction:_ctx];
911 /* fake being a SOGoMailObject */
913 - (id)fetchParts:(NSArray *)_parts {
914 return [NSDictionary dictionaryWithObject:self forKey:@"fetch"];
918 return [self nameInContainer];
921 static NSArray *seenFlags = nil;
922 seenFlags = [[NSArray alloc] initWithObjects:@"seen", nil];
926 // TODO: size, hard to support, we would need to generate MIME?
930 - (NSArray *)imap4EnvelopeAddressesForStrings:(NSArray *)_emails {
936 if ((count = [_emails count]) == 0)
937 return [NSArray array];
939 ma = [NSMutableArray arrayWithCapacity:count];
940 for (i = 0; i < count; i++) {
941 NGImap4EnvelopeAddress *envaddr;
943 envaddr = [[NGImap4EnvelopeAddress alloc]
944 initWithString:[_emails objectAtIndex:i]];
945 if ([envaddr isNotNull])
946 [ma addObject:envaddr];
952 - (NGImap4Envelope *)envelope {
956 if (self->envelope != nil)
957 return self->envelope;
958 if ((lInfo = [self fetchInfo]) == nil)
961 if ((from = [self sender]) != nil)
962 from = [NSArray arrayWithObjects:&from count:1];
964 if ((replyTo = [lInfo objectForKey:@"replyTo"]) != nil) {
965 if (![replyTo isKindOfClass:[NSArray class]])
966 replyTo = [NSArray arrayWithObjects:&replyTo count:1];
970 [[NGImap4Envelope alloc] initWithMessageID:[self nameInContainer]
971 subject:[lInfo objectForKey:@"subject"]
972 from:from replyTo:replyTo
973 to:[lInfo objectForKey:@"to"]
974 cc:[lInfo objectForKey:@"cc"]
975 bcc:[lInfo objectForKey:@"bcc"]];
976 return self->envelope;
981 - (BOOL)isDebuggingEnabled {
985 @end /* SOGoDraftObject */