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 #import <Foundation/NSArray.h>
23 #import <Foundation/NSAutoreleasePool.h>
24 #import <Foundation/NSDictionary.h>
25 #import <Foundation/NSKeyValueCoding.h>
26 #import <Foundation/NSUserDefaults.h>
27 #import <Foundation/NSValue.h>
29 #import <NGObjWeb/NSException+HTTP.h>
30 #import <NGObjWeb/SoObject+SoDAV.h>
31 #import <NGObjWeb/WOContext.h>
32 #import <NGObjWeb/WORequest+So.h>
33 #import <NGObjWeb/WOResponse.h>
34 #import <NGExtensions/NGBase64Coding.h>
35 #import <NGExtensions/NSFileManager+Extensions.h>
36 #import <NGExtensions/NGHashMap.h>
37 #import <NGExtensions/NSNull+misc.h>
38 #import <NGExtensions/NSObject+Logs.h>
39 #import <NGExtensions/NGQuotedPrintableCoding.h>
40 #import <NGImap4/NGImap4Envelope.h>
41 #import <NGImap4/NGImap4EnvelopeAddress.h>
42 #import <NGMail/NGMimeMessage.h>
43 #import <NGMail/NGMimeMessageGenerator.h>
44 #import <NGMail/NGSendMail.h>
45 #import <NGMime/NGMimeBodyPart.h>
46 #import <NGMime/NGMimeFileData.h>
47 #import <NGMime/NGMimeMultipartBody.h>
48 #import <NGMime/NGMimeType.h>
49 #import <NGMime/NGMimeHeaderFieldGenerator.h>
51 #import <SoObjects/SOGo/NSCalendarDate+SOGo.h>
53 #import "SOGoDraftObject.h"
55 static NSString *contentTypeValue = @"text/plain; charset=utf-8";
57 @interface NSString (NGMimeHelpers)
59 - (NSString *) asQPSubjectString: (NSString *) encoding;
63 @implementation NSString (NGMimeHelpers)
65 - (NSString *) asQPSubjectString: (NSString *) encoding;
68 NSData *subjectData, *destSubjectData;
70 subjectData = [self dataUsingEncoding: NSUTF8StringEncoding];
71 destSubjectData = [subjectData dataByEncodingQuotedPrintable];
73 qpString = [[NSString alloc] initWithData: destSubjectData
74 encoding: NSASCIIStringEncoding];
75 [qpString autorelease];
77 return [NSString stringWithFormat: @"=?%@?Q?%@?=", encoding, qpString];
82 @implementation SOGoDraftObject
84 static NGMimeType *TextPlainType = nil;
85 static NGMimeType *MultiMixedType = nil;
86 static NSString *userAgent = @"SOGoMail 1.0";
87 static BOOL draftDeleteDisabled = NO; // for debugging
88 static BOOL debugOn = NO;
89 static BOOL showTextAttachmentsInline = NO;
92 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
94 /* Note: be aware of the charset issues before enabling this! */
95 showTextAttachmentsInline = [ud boolForKey:@"SOGoShowTextAttachmentsInline"];
97 if ((draftDeleteDisabled = [ud boolForKey:@"SOGoNoDraftDeleteAfterSend"]))
98 NSLog(@"WARNING: draft delete is disabled! (SOGoNoDraftDeleteAfterSend)");
100 TextPlainType = [[NGMimeType mimeType:@"text" subType:@"plain"] copy];
101 MultiMixedType = [[NGMimeType mimeType:@"multipart" subType:@"mixed"] copy];
111 /* draft folder functionality */
113 - (NSFileManager *)spoolFileManager {
114 return [[self container] spoolFileManager];
116 - (NSString *)userSpoolFolderPath {
117 return [[self container] userSpoolFolderPath];
119 - (BOOL)_ensureUserSpoolFolderPath {
120 return [[self container] _ensureUserSpoolFolderPath];
123 /* draft object functionality */
125 - (NSString *)draftFolderPath {
129 path = [[[self userSpoolFolderPath] stringByAppendingPathComponent:
130 [self nameInContainer]] copy];
133 - (BOOL)_ensureDraftFolderPath {
136 if (![self _ensureUserSpoolFolderPath])
139 if ((fm = [self spoolFileManager]) == nil) {
140 [self errorWithFormat:@"missing spool file manager!"];
143 return [fm createDirectoriesAtPath:[self draftFolderPath] attributes:nil];
146 - (NSString *)infoPath {
147 return [[self draftFolderPath]
148 stringByAppendingPathComponent:@".info.plist"];
153 - (NSException *)storeInfo:(NSDictionary *)_info {
155 return [NSException exceptionWithHTTPStatus:500 /* server error */
156 reason:@"got no info to write for draft!"];
158 if (![self _ensureDraftFolderPath]) {
159 [self errorWithFormat:@"could not create folder for draft: '%@'",
160 [self draftFolderPath]];
161 return [NSException exceptionWithHTTPStatus:500 /* server error */
162 reason:@"could not create folder for draft!"];
164 if (![_info writeToFile:[self infoPath] atomically:YES]) {
165 [self errorWithFormat:@"could not write info: '%@'", [self infoPath]];
166 return [NSException exceptionWithHTTPStatus:500 /* server error */
167 reason:@"could not write draft info!"];
170 /* reset info cache */
171 [info release]; info = nil;
173 return nil /* everything is excellent */;
175 - (NSDictionary *)fetchInfo {
182 if (![[self spoolFileManager] fileExistsAtPath:p]) {
183 [self debugWithFormat:@"Note: info object does not yet exist: %@", p];
187 info = [[NSDictionary alloc] initWithContentsOfFile:p];
189 [self errorWithFormat:@"draft info dictionary broken at path: %@", p];
196 - (NSString *)sender {
199 if ((tmp = [[self fetchInfo] objectForKey:@"from"]) == nil)
201 if ([tmp isKindOfClass:[NSArray class]])
202 return [tmp count] > 0 ? [tmp objectAtIndex:0] : nil;
208 - (NSArray *)fetchAttachmentNames {
214 fm = [self spoolFileManager];
215 if ((files = [fm directoryContentsAtPath:[self draftFolderPath]]) == nil)
218 count = [files count];
219 ma = [NSMutableArray arrayWithCapacity:count];
220 for (i = 0; i < count; i++) {
223 filename = [files objectAtIndex:i];
224 if ([filename hasPrefix:@"."])
227 [ma addObject:filename];
232 - (BOOL)isValidAttachmentName:(NSString *)_name {
233 static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil };
237 if (![_name isNotNull]) return NO;
238 if ([_name length] == 0) return NO;
239 if ([_name hasPrefix:@"."]) return NO;
241 for (i = 0; sescape[i] != nil; i++) {
242 r = [_name rangeOfString:sescape[i]];
243 if (r.length > 0) return NO;
248 - (NSString *)pathToAttachmentWithName:(NSString *)_name {
249 if ([_name length] == 0)
252 return [[self draftFolderPath] stringByAppendingPathComponent:_name];
255 - (NSException *)invalidAttachmentNameError:(NSString *)_name {
256 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
257 reason:@"Invalid attachment name!"];
260 - (NSException *) saveAttachment: (NSData *) _attach
261 withMetadata: (NSDictionary *) metadata
263 NSString *p, *name, *mimeType;
265 if (![_attach isNotNull]) {
266 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
267 reason:@"Missing attachment content!"];
270 if (![self _ensureDraftFolderPath]) {
271 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
272 reason:@"Could not create folder for draft!"];
274 name = [metadata objectForKey: @"filename"];
275 if (![self isValidAttachmentName: name])
276 return [self invalidAttachmentNameError: name];
278 p = [self pathToAttachmentWithName: name];
279 if (![_attach writeToFile: p atomically: YES])
281 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
282 reason:@"Could not write attachment to draft!"];
285 mimeType = [metadata objectForKey: @"mime-type"];
286 if ([mimeType length] > 0)
288 p = [self pathToAttachmentWithName:
289 [NSString stringWithFormat: @".%@.mime", name]];
290 if (![[mimeType dataUsingEncoding: NSUTF8StringEncoding]
291 writeToFile: p atomically: YES])
293 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
294 reason:@"Could not write attachment to draft!"];
298 return nil; /* everything OK */
301 - (NSException *)deleteAttachmentWithName:(NSString *)_name {
305 if (![self isValidAttachmentName:_name])
306 return [self invalidAttachmentNameError:_name];
308 fm = [self spoolFileManager];
309 p = [self pathToAttachmentWithName:_name];
310 if (![fm fileExistsAtPath:p])
311 return nil; /* well, doesn't exist, so its deleted ;-) */
313 if (![fm removeFileAtPath:p handler:nil]) {
314 [self logWithFormat:@"ERROR: failed to delete file: %@", p];
315 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
316 reason:@"Could not delete attachment from draft!"];
318 return nil; /* everything OK */
321 /* NGMime representations */
323 - (NGMimeBodyPart *)bodyPartForText
326 This add the text typed by the user (the primary plain/text part).
328 NGMutableHashMap *map;
329 NGMimeBodyPart *bodyPart;
333 if ((lInfo = [self fetchInfo]) == nil)
336 /* prepare header of body part */
338 map = [[[NGMutableHashMap alloc] initWithCapacity:2] autorelease];
340 // TODO: set charset in header!
341 [map setObject:@"text/plain" forKey:@"content-type"];
342 if ((body = [lInfo objectForKey:@"text"]) != nil) {
343 if ([body isKindOfClass: [NSString class]]) {
344 [map setObject: contentTypeValue
345 forKey: @"content-type"];
346 // body = [body dataUsingEncoding:NSUTF8StringEncoding];
350 /* prepare body content */
352 bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
353 [bodyPart setBody:body];
357 - (NGMimeMessage *)mimeMessageForContentWithHeaderMap:(NGMutableHashMap *)map
360 NGMimeMessage *message;
364 if ((lInfo = [self fetchInfo]) == nil)
367 [map setObject: @"text/plain" forKey: @"content-type"];
368 if ((body = [lInfo objectForKey:@"text"]) != nil) {
369 if ([body isKindOfClass:[NSString class]])
370 /* Note: just 'utf8' is displayed wrong in Mail.app */
371 [map setObject: contentTypeValue
372 forKey: @"content-type"];
373 // body = [body dataUsingEncoding:NSUTF8StringEncoding];
374 else if ([body isKindOfClass:[NSData class]] && addSuffix) {
375 body = [[body mutableCopy] autorelease];
377 else if (addSuffix) {
378 [self warnWithFormat:@"Note: cannot add Internet marker to body: %@",
379 NSStringFromClass([body class])];
383 message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
384 [message setBody:body];
389 - (NSString *)mimeTypeForExtension:(NSString *)_ext {
390 // TODO: make configurable
391 // TODO: use /etc/mime-types
392 if ([_ext isEqualToString:@"txt"]) return @"text/plain";
393 if ([_ext isEqualToString:@"html"]) return @"text/html";
394 if ([_ext isEqualToString:@"htm"]) return @"text/html";
395 if ([_ext isEqualToString:@"gif"]) return @"image/gif";
396 if ([_ext isEqualToString:@"jpg"]) return @"image/jpeg";
397 if ([_ext isEqualToString:@"jpeg"]) return @"image/jpeg";
398 if ([_ext isEqualToString:@"mail"]) return @"message/rfc822";
399 return @"application/octet-stream";
402 - (NSString *)contentTypeForAttachmentWithName:(NSString *)_name {
406 p = [self pathToAttachmentWithName:
407 [NSString stringWithFormat: @".%@.mime", _name]];
408 mimeData = [NSData dataWithContentsOfFile: p];
411 s = [[NSString alloc] initWithData: mimeData
412 encoding: NSUTF8StringEncoding];
417 s = [self mimeTypeForExtension:[_name pathExtension]];
418 if ([_name length] > 0)
419 s = [s stringByAppendingFormat:@"; name=\"%@\"", _name];
425 - (NSString *)contentDispositionForAttachmentWithName:(NSString *)_name {
430 type = [self contentTypeForAttachmentWithName:_name];
432 if ([type hasPrefix:@"text/"])
433 cdtype = showTextAttachmentsInline ? @"inline" : @"attachment";
434 else if ([type hasPrefix:@"image/"] || [type hasPrefix:@"message"])
437 cdtype = @"attachment";
439 cd = [cdtype stringByAppendingString:@"; filename=\""];
440 cd = [cd stringByAppendingString:_name];
441 cd = [cd stringByAppendingString:@"\""];
443 // TODO: add size parameter (useful addition, RFC 2183)
447 - (NGMimeBodyPart *)bodyPartForAttachmentWithName:(NSString *)_name {
449 NGMutableHashMap *map;
450 NGMimeBodyPart *bodyPart;
453 BOOL attachAsString, is7bit;
457 if (_name == nil) return nil;
459 /* check attachment */
461 fm = [self spoolFileManager];
462 p = [self pathToAttachmentWithName:_name];
463 if (![fm isReadableFileAtPath:p]) {
464 [self errorWithFormat:@"did not find attachment: '%@'", _name];
470 /* prepare header of body part */
472 map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease];
474 if ((s = [self contentTypeForAttachmentWithName:_name]) != nil) {
475 [map setObject:s forKey:@"content-type"];
476 if ([s hasPrefix:@"text/"])
477 attachAsString = YES;
478 else if ([s hasPrefix:@"message/rfc822"])
481 if ((s = [self contentDispositionForAttachmentWithName:_name]))
482 [map setObject:s forKey:@"content-disposition"];
484 /* prepare body content */
486 if (attachAsString) { // TODO: is this really necessary?
489 content = [[NSData alloc] initWithContentsOfMappedFile:p];
491 s = [[NSString alloc] initWithData:content
492 encoding:[NSString defaultCStringEncoding]];
495 [content release]; content = nil;
498 [self warnWithFormat:
499 @"could not get text attachment as string: '%@'", _name];
506 Note: Apparently NGMimeFileData objects are not processed by the MIME
509 body = [[NGMimeFileData alloc] initWithPath:p removeFile:NO];
510 [map setObject:@"7bit" forKey:@"content-transfer-encoding"];
511 [map setObject:[NSNumber numberWithInt:[body length]]
512 forKey:@"content-length"];
516 Note: in OGo this is done in LSWImapMailEditor.m:2477. Apparently
517 NGMimeFileData objects are not processed by the MIME generator!
521 content = [[NSData alloc] initWithContentsOfMappedFile:p];
522 encoded = [content dataByEncodingBase64];
523 [content release]; content = nil;
525 [map setObject:@"base64" forKey:@"content-transfer-encoding"];
526 [map setObject:[NSNumber numberWithInt:[encoded length]]
527 forKey:@"content-length"];
529 /* Note: the -init method will create a temporary file! */
530 body = [[NGMimeFileData alloc] initWithBytes:[encoded bytes]
531 length:[encoded length]];
534 bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
535 [bodyPart setBody:body];
537 [body release]; body = nil;
541 - (NSArray *)bodyPartsForAllAttachments {
542 /* returns nil on error */
543 NSMutableArray *bodyParts;
547 names = [self fetchAttachmentNames];
548 if ((count = [names count]) == 0)
549 return [NSArray array];
551 bodyParts = [NSMutableArray arrayWithCapacity:count];
552 for (i = 0; i < count; i++) {
553 NGMimeBodyPart *bodyPart;
555 bodyPart = [self bodyPartForAttachmentWithName:[names objectAtIndex:i]];
559 [bodyParts addObject:bodyPart];
564 - (NGMimeMessage *)mimeMultiPartMessageWithHeaderMap:(NGMutableHashMap *)map
565 andBodyParts:(NSArray *)_bodyParts
567 NGMimeMessage *message;
568 NGMimeMultipartBody *mBody;
569 NGMimeBodyPart *part;
572 [map addObject:MultiMixedType forKey:@"content-type"];
574 message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
575 mBody = [[NGMimeMultipartBody alloc] initWithPart:message];
577 part = [self bodyPartForText];
578 [mBody addBodyPart:part];
580 e = [_bodyParts objectEnumerator];
581 while ((part = [e nextObject]) != nil)
582 [mBody addBodyPart:part];
584 [message setBody:mBody];
585 [mBody release]; mBody = nil;
589 - (void)_addHeaders:(NSDictionary *)_h toHeaderMap:(NGMutableHashMap *)_map {
596 names = [_h keyEnumerator];
597 while ((name = [names nextObject]) != nil) {
600 value = [_h objectForKey:name];
601 [_map addObject:value forKey:name];
605 - (BOOL)isEmptyValue:(id)_value {
606 if (![_value isNotNull])
609 if ([_value isKindOfClass:[NSArray class]])
610 return [_value count] == 0 ? YES : NO;
612 if ([_value isKindOfClass:[NSString class]])
613 return [_value length] == 0 ? YES : NO;
618 - (NGMutableHashMap *)mimeHeaderMapWithHeaders:(NSDictionary *)_headers {
619 NGMutableHashMap *map;
620 NSDictionary *lInfo; // TODO: this should be some kind of object?
622 NSString *s, *dateString;
625 if ((lInfo = [self fetchInfo]) == nil)
628 map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
632 if ((emails = [lInfo objectForKey:@"to"]) != nil) {
633 if ([emails count] == 0) {
634 [self errorWithFormat:@"missing 'to' recipient in email!"];
637 [map setObjects:emails forKey:@"to"];
639 if ((emails = [lInfo objectForKey:@"cc"]) != nil)
640 [map setObjects:emails forKey:@"cc"];
641 if ((emails = [lInfo objectForKey:@"bcc"]) != nil)
642 [map setObjects:emails forKey:@"bcc"];
646 from = [lInfo objectForKey:@"from"];
647 replyTo = [lInfo objectForKey:@"replyTo"];
649 if (![self isEmptyValue:from]) {
650 if ([from isKindOfClass:[NSArray class]])
651 [map setObjects:from forKey:@"from"];
653 [map setObject:from forKey:@"from"];
656 if (![self isEmptyValue:replyTo]) {
657 if ([from isKindOfClass:[NSArray class]])
658 [map setObjects:from forKey:@"reply-to"];
660 [map setObject:from forKey:@"reply-to"];
662 else if (![self isEmptyValue:from])
663 [map setObjects:[map objectsForKey:@"from"] forKey:@"reply-to"];
667 if ([(s = [lInfo objectForKey:@"subject"]) length] > 0)
668 [map setObject: [s asQPSubjectString: @"utf-8"]
670 // [map setObject: [s asQPSubjectString: @"utf-8"] forKey:@"subject"];
672 /* add standard headers */
674 dateString = [[NSCalendarDate date] rfc822DateString];
675 [map addObject: dateString forKey:@"date"];
676 [map addObject: @"1.0" forKey:@"MIME-Version"];
677 [map addObject: userAgent forKey:@"X-Mailer"];
679 /* add custom headers */
681 [self _addHeaders:[lInfo objectForKey:@"headers"] toHeaderMap:map];
682 [self _addHeaders:_headers toHeaderMap:map];
687 - (NGMimeMessage *)mimeMessageWithHeaders:(NSDictionary *)_headers {
688 NSAutoreleasePool *pool;
689 NGMutableHashMap *map;
691 NGMimeMessage *message;
693 pool = [[NSAutoreleasePool alloc] init];
695 if ([self fetchInfo] == nil) {
696 [self errorWithFormat:@"could not locate draft fetch info!"];
700 if ((map = [self mimeHeaderMapWithHeaders:_headers]) == nil)
702 [self debugWithFormat:@"MIME Envelope: %@", map];
704 if ((bodyParts = [self bodyPartsForAllAttachments]) == nil) {
705 [self errorWithFormat:
706 @"could not create body parts for attachments!"];
707 return nil; // TODO: improve error handling, return exception
709 [self debugWithFormat:@"attachments: %@", bodyParts];
711 if ([bodyParts count] == 0) {
713 message = [self mimeMessageForContentWithHeaderMap:map];
716 /* attachments, create multipart/mixed */
717 message = [self mimeMultiPartMessageWithHeaderMap:map
718 andBodyParts:bodyParts];
720 [self debugWithFormat:@"message: %@", message];
722 message = [message retain];
724 return [message autorelease];
726 - (NGMimeMessage *)mimeMessage {
727 return [self mimeMessageWithHeaders:nil];
730 - (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_h {
731 NGMimeMessageGenerator *gen;
732 NSAutoreleasePool *pool;
733 NGMimeMessage *message;
736 pool = [[NSAutoreleasePool alloc] init];
738 message = [self mimeMessageWithHeaders:_h];
739 if (![message isNotNull])
741 if ([message isKindOfClass:[NSException class]]) {
742 [self errorWithFormat:@"error: %@", message];
746 gen = [[NGMimeMessageGenerator alloc] init];
747 tmpPath = [[gen generateMimeFromPartToFile:message] copy];
748 [gen release]; gen = nil;
751 return [tmpPath autorelease];
753 - (NSString *)saveMimeMessageToTemporaryFile {
754 return [self saveMimeMessageToTemporaryFileWithHeaders:nil];
757 - (void)deleteTemporaryMessageFile:(NSString *)_path {
760 if (![_path isNotNull])
763 fm = [NSFileManager defaultManager];
764 if (![fm fileExistsAtPath:_path])
767 [fm removeFileAtPath:_path handler:nil];
770 - (NSArray *)allRecipients {
775 if ((lInfo = [self fetchInfo]) == nil)
778 ma = [NSMutableArray arrayWithCapacity:16];
779 if ((tmp = [lInfo objectForKey:@"to"]) != nil)
780 [ma addObjectsFromArray:tmp];
781 if ((tmp = [lInfo objectForKey:@"cc"]) != nil)
782 [ma addObjectsFromArray:tmp];
783 if ((tmp = [lInfo objectForKey:@"bcc"]) != nil)
784 [ma addObjectsFromArray:tmp];
788 - (NSString *) _rawSender
790 NSString *startEmail, *rawSender;
793 startEmail = [self sender];
794 delimiter = [startEmail rangeOfString: @"<"];
795 if (delimiter.location == NSNotFound)
796 rawSender = startEmail;
799 rawSender = [startEmail substringFromIndex: NSMaxRange (delimiter)];
800 delimiter = [rawSender rangeOfString: @">"];
801 if (delimiter.location != NSNotFound)
802 rawSender = [rawSender substringToIndex: delimiter.location];
808 - (NSException *)sendMimeMessageAtPath:(NSString *)_path {
809 static NGSendMail *mailer = nil;
815 recipients = [self allRecipients];
816 from = [self _rawSender];
817 if ([recipients count] == 0) {
818 return [NSException exceptionWithHTTPStatus:500 /* server error */
819 reason:@"draft has no recipients set!"];
821 if ([from length] == 0) {
822 return [NSException exceptionWithHTTPStatus:500 /* server error */
823 reason:@"draft has no sender (from) set!"];
826 /* setup mailer object */
829 mailer = [[NGSendMail sharedSendMail] retain];
830 if (![mailer isSendMailAvailable]) {
831 [self errorWithFormat:@"missing sendmail binary!"];
832 return [NSException exceptionWithHTTPStatus:500 /* server error */
833 reason:@"did not find sendmail binary!"];
838 return [mailer sendMailAtPath:_path toRecipients:recipients sender:from];
841 - (NSException *)sendMail {
845 /* save MIME mail to file */
847 tmpPath = [self saveMimeMessageToTemporaryFile];
848 if (![tmpPath isNotNull]) {
849 return [NSException exceptionWithHTTPStatus:500 /* server error */
850 reason:@"could not save MIME message for draft!"];
854 error = [self sendMimeMessageAtPath:tmpPath];
856 /* delete temporary file */
857 [self deleteTemporaryMessageFile:tmpPath];
864 - (NSException *)delete {
869 if ((fm = [self spoolFileManager]) == nil) {
870 [self errorWithFormat:@"missing spool file manager!"];
871 return [NSException exceptionWithHTTPStatus:500 /* server error */
872 reason:@"missing spool file manager!"];
875 p = [self draftFolderPath];
876 if (![fm fileExistsAtPath:p]) {
877 return [NSException exceptionWithHTTPStatus:404 /* not found */
878 reason:@"did not find draft!"];
881 e = [[fm directoryContentsAtPath:p] objectEnumerator];
882 while ((sp = [e nextObject])) {
883 sp = [p stringByAppendingPathComponent:sp];
884 if (draftDeleteDisabled) {
885 [self logWithFormat:@"should delete draft file %@ ...", sp];
889 if (![fm removeFileAtPath:sp handler:nil]) {
890 return [NSException exceptionWithHTTPStatus:500 /* server error */
891 reason:@"failed to delete draft!"];
895 if (draftDeleteDisabled) {
896 [self logWithFormat:@"should delete draft directory: %@", p];
899 if (![fm removeFileAtPath:p handler:nil]) {
900 return [NSException exceptionWithHTTPStatus:500 /* server error */
901 reason:@"failed to delete draft directory!"];
907 - (NSData *)content {
908 /* Note: does not cache, expensive operation */
912 if ((p = [self saveMimeMessageToTemporaryFile]) == nil)
915 data = [NSData dataWithContentsOfMappedFile:p];
917 /* delete temporary file */
918 [self deleteTemporaryMessageFile:p];
922 - (NSString *)contentAsString {
926 if ((data = [self content]) == nil)
929 str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
931 [self errorWithFormat:@"could not load draft as ASCII (data size=%d)",
936 return [str autorelease];
941 - (id)DELETEAction:(id)_ctx {
944 if ((error = [self delete]) != nil)
947 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
950 - (id) GETAction: (id) _ctx
953 Override, because SOGoObject's GETAction uses the less efficient
954 -contentAsString method.
959 if ([rq isSoWebDAVRequest]) {
963 if ((content = [self content]) == nil) {
964 return [NSException exceptionWithHTTPStatus:500
965 reason:@"Could not generate MIME content!"];
968 [r setHeader:@"message/rfc822" forKey:@"content-type"];
969 [r setContent:content];
973 return [super GETAction:_ctx];
976 /* fake being a SOGoMailObject */
978 - (id)fetchParts:(NSArray *)_parts {
979 return [NSDictionary dictionaryWithObject:self forKey:@"fetch"];
983 return [self nameInContainer];
986 static NSArray *seenFlags = nil;
987 seenFlags = [[NSArray alloc] initWithObjects:@"seen", nil];
991 // TODO: size, hard to support, we would need to generate MIME?
995 - (NSArray *)imap4EnvelopeAddressesForStrings:(NSArray *)_emails {
1001 if ((count = [_emails count]) == 0)
1002 return [NSArray array];
1004 ma = [NSMutableArray arrayWithCapacity:count];
1005 for (i = 0; i < count; i++) {
1006 NGImap4EnvelopeAddress *envaddr;
1008 envaddr = [[NGImap4EnvelopeAddress alloc]
1009 initWithString:[_emails objectAtIndex:i]];
1010 if ([envaddr isNotNull])
1011 [ma addObject:envaddr];
1017 - (NGImap4Envelope *)envelope {
1018 NSDictionary *lInfo;
1021 if (envelope != nil)
1023 if ((lInfo = [self fetchInfo]) == nil)
1026 if ((from = [self sender]) != nil)
1027 from = [NSArray arrayWithObjects:&from count:1];
1029 if ((replyTo = [lInfo objectForKey:@"replyTo"]) != nil) {
1030 if (![replyTo isKindOfClass:[NSArray class]])
1031 replyTo = [NSArray arrayWithObjects:&replyTo count:1];
1035 [[NGImap4Envelope alloc] initWithMessageID:[self nameInContainer]
1036 subject:[lInfo objectForKey:@"subject"]
1037 from:from replyTo:replyTo
1038 to:[lInfo objectForKey:@"to"]
1039 cc:[lInfo objectForKey:@"cc"]
1040 bcc:[lInfo objectForKey:@"bcc"]];
1046 - (BOOL)isDebuggingEnabled {
1050 @end /* SOGoDraftObject */