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 <SoObjects/SOGo/NSCalendarDate+SOGo.h>
25 #include <NGMail/NGMimeMessage.h>
26 #include <NGMail/NGMimeMessageGenerator.h>
27 #include <NGMail/NGSendMail.h>
28 #include <NGMime/NGMimeBodyPart.h>
29 #include <NGMime/NGMimeFileData.h>
30 #include <NGMime/NGMimeMultipartBody.h>
31 #include <NGMime/NGMimeType.h>
32 #include <NGMime/NGMimeHeaderFieldGenerator.h>
33 #include <NGImap4/NGImap4Envelope.h>
34 #include <NGImap4/NGImap4EnvelopeAddress.h>
35 #include <NGExtensions/NSFileManager+Extensions.h>
38 static NSString *contentTypeValue = @"text/plain; charset=utf-8";
40 @interface NSString (NGMimeHelpers)
42 - (NSString *) asQPSubjectString: (NSString *) encoding;
46 @implementation NSString (NGMimeHelpers)
48 - (NSString *) asQPSubjectString: (NSString *) encoding;
51 NSData *subjectData, *destSubjectData;
53 subjectData = [self dataUsingEncoding: NSUTF8StringEncoding];
54 destSubjectData = [subjectData dataByEncodingQuotedPrintable];
56 qpString = [[NSString alloc] initWithData: destSubjectData
57 encoding: NSASCIIStringEncoding];
58 [qpString autorelease];
60 return [NSString stringWithFormat: @"=?%@?Q?%@?=", encoding, qpString];
65 @implementation SOGoDraftObject
67 static NGMimeType *TextPlainType = nil;
68 static NGMimeType *MultiMixedType = nil;
69 static NSString *userAgent = @"SOGoMail 1.0";
70 static BOOL draftDeleteDisabled = NO; // for debugging
71 static BOOL debugOn = NO;
72 static BOOL showTextAttachmentsInline = NO;
73 static NSString *fromInternetSuffixPattern = nil;
76 return [super version] + 0 /* v1 */;
80 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
82 NSAssert2([super version] == 1,
83 @"invalid superclass (%@) version %i !",
84 NSStringFromClass([self superclass]), [super version]);
86 /* Note: be aware of the charset issues before enabling this! */
87 showTextAttachmentsInline = [ud boolForKey:@"SOGoShowTextAttachmentsInline"];
89 if ((draftDeleteDisabled = [ud boolForKey:@"SOGoNoDraftDeleteAfterSend"]))
90 NSLog(@"WARNING: draft delete is disabled! (SOGoNoDraftDeleteAfterSend)");
92 fromInternetSuffixPattern = [ud stringForKey:@"SOGoInternetMailSuffix"];
93 if ([fromInternetSuffixPattern length] == 0)
94 NSLog(@"Note: no 'SOGoInternetMailSuffix' is configured.");
96 fromInternetSuffixPattern =
97 [@"\n" stringByAppendingString:fromInternetSuffixPattern];
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 withName:(NSString *)_name {
263 if (![_attach isNotNull]) {
264 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
265 reason:@"Missing attachment content!"];
268 if (![self _ensureDraftFolderPath]) {
269 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
270 reason:@"Could not create folder for draft!"];
272 if (![self isValidAttachmentName:_name])
273 return [self invalidAttachmentNameError:_name];
275 p = [self pathToAttachmentWithName:_name];
276 if (![_attach writeToFile:p atomically:YES]) {
277 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
278 reason:@"Could not write attachment to draft!"];
281 return nil; /* everything OK */
284 - (NSException *)deleteAttachmentWithName:(NSString *)_name {
288 if (![self isValidAttachmentName:_name])
289 return [self invalidAttachmentNameError:_name];
291 fm = [self spoolFileManager];
292 p = [self pathToAttachmentWithName:_name];
293 if (![fm fileExistsAtPath:p])
294 return nil; /* well, doesn't exist, so its deleted ;-) */
296 if (![fm removeFileAtPath:p handler:nil]) {
297 [self logWithFormat:@"ERROR: failed to delete file: %@", p];
298 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
299 reason:@"Could not delete attachment from draft!"];
301 return nil; /* everything OK */
304 /* NGMime representations */
306 - (NGMimeBodyPart *)bodyPartForText
309 This add the text typed by the user (the primary plain/text part).
311 NGMutableHashMap *map;
312 NGMimeBodyPart *bodyPart;
316 if ((lInfo = [self fetchInfo]) == nil)
319 /* prepare header of body part */
321 map = [[[NGMutableHashMap alloc] initWithCapacity:2] autorelease];
323 // TODO: set charset in header!
324 [map setObject:@"text/plain" forKey:@"content-type"];
325 if ((body = [lInfo objectForKey:@"text"]) != nil) {
326 if ([body isKindOfClass: [NSString class]]) {
327 [map setObject: contentTypeValue
328 forKey: @"content-type"];
329 // body = [body dataUsingEncoding:NSUTF8StringEncoding];
333 /* prepare body content */
335 bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
336 [bodyPart setBody:body];
340 - (NGMimeMessage *)mimeMessageForContentWithHeaderMap:(NGMutableHashMap *)map
343 NGMimeMessage *message;
344 NSString *fromInternetSuffix;
348 if ((lInfo = [self fetchInfo]) == nil)
351 addSuffix = [context isAccessFromIntranet] ? NO : YES;
354 [fromInternetSuffixPattern stringByReplacingVariablesWithBindings:
356 stringForUnknownBindings:@""];
358 addSuffix = [fromInternetSuffix length] > 0 ? YES : NO;
361 [map setObject:@"text/plain" forKey:@"content-type"];
362 if ((body = [lInfo objectForKey:@"text"]) != nil) {
363 if ([body isKindOfClass:[NSString class]]) {
365 body = [body stringByAppendingString:fromInternetSuffix];
367 /* Note: just 'utf8' is displayed wrong in Mail.app */
368 [map setObject: contentTypeValue
369 forKey: @"content-type"];
370 // body = [body dataUsingEncoding:NSUTF8StringEncoding];
372 else if ([body isKindOfClass:[NSData class]] && addSuffix) {
373 body = [[body mutableCopy] autorelease];
374 [(NSMutableData *)body
375 appendData: [fromInternetSuffix dataUsingEncoding:NSUTF8StringEncoding]];
377 else if (addSuffix) {
378 [self warnWithFormat:@"Note: cannot add Internet marker to body: %@",
379 NSStringFromClass([body class])];
383 body = fromInternetSuffix;
385 message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
386 [message setBody:body];
390 - (NSString *)mimeTypeForExtension:(NSString *)_ext {
391 // TODO: make configurable
392 // TODO: use /etc/mime-types
393 if ([_ext isEqualToString:@"txt"]) return @"text/plain";
394 if ([_ext isEqualToString:@"html"]) return @"text/html";
395 if ([_ext isEqualToString:@"htm"]) return @"text/html";
396 if ([_ext isEqualToString:@"gif"]) return @"image/gif";
397 if ([_ext isEqualToString:@"jpg"]) return @"image/jpeg";
398 if ([_ext isEqualToString:@"jpeg"]) return @"image/jpeg";
399 if ([_ext isEqualToString:@"mail"]) return @"message/rfc822";
400 return @"application/octet-stream";
403 - (NSString *)contentTypeForAttachmentWithName:(NSString *)_name {
406 s = [self mimeTypeForExtension:[_name pathExtension]];
407 if ([_name length] > 0)
408 s = [s stringByAppendingFormat:@"; name=\"%@\"", _name];
412 - (NSString *)contentDispositionForAttachmentWithName:(NSString *)_name {
417 type = [self contentTypeForAttachmentWithName:_name];
419 if ([type hasPrefix:@"text/"])
420 cdtype = showTextAttachmentsInline ? @"inline" : @"attachment";
421 else if ([type hasPrefix:@"image/"] || [type hasPrefix:@"message"])
424 cdtype = @"attachment";
426 cd = [cdtype stringByAppendingString:@"; filename=\""];
427 cd = [cd stringByAppendingString:_name];
428 cd = [cd stringByAppendingString:@"\""];
430 // TODO: add size parameter (useful addition, RFC 2183)
434 - (NGMimeBodyPart *)bodyPartForAttachmentWithName:(NSString *)_name {
436 NGMutableHashMap *map;
437 NGMimeBodyPart *bodyPart;
440 BOOL attachAsString, is7bit;
444 if (_name == nil) return nil;
446 /* check attachment */
448 fm = [self spoolFileManager];
449 p = [self pathToAttachmentWithName:_name];
450 if (![fm isReadableFileAtPath:p]) {
451 [self errorWithFormat:@"did not find attachment: '%@'", _name];
457 /* prepare header of body part */
459 map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease];
461 if ((s = [self contentTypeForAttachmentWithName:_name]) != nil) {
462 [map setObject:s forKey:@"content-type"];
463 if ([s hasPrefix:@"text/"])
464 attachAsString = YES;
465 else if ([s hasPrefix:@"message/rfc822"])
468 if ((s = [self contentDispositionForAttachmentWithName:_name]))
469 [map setObject:s forKey:@"content-disposition"];
471 /* prepare body content */
473 if (attachAsString) { // TODO: is this really necessary?
476 content = [[NSData alloc] initWithContentsOfMappedFile:p];
478 s = [[NSString alloc] initWithData:content
479 encoding:[NSString defaultCStringEncoding]];
482 [content release]; content = nil;
485 [self warnWithFormat:
486 @"could not get text attachment as string: '%@'", _name];
493 Note: Apparently NGMimeFileData objects are not processed by the MIME
496 body = [[NGMimeFileData alloc] initWithPath:p removeFile:NO];
497 [map setObject:@"7bit" forKey:@"content-transfer-encoding"];
498 [map setObject:[NSNumber numberWithInt:[body length]]
499 forKey:@"content-length"];
503 Note: in OGo this is done in LSWImapMailEditor.m:2477. Apparently
504 NGMimeFileData objects are not processed by the MIME generator!
508 content = [[NSData alloc] initWithContentsOfMappedFile:p];
509 encoded = [content dataByEncodingBase64];
510 [content release]; content = nil;
512 [map setObject:@"base64" forKey:@"content-transfer-encoding"];
513 [map setObject:[NSNumber numberWithInt:[encoded length]]
514 forKey:@"content-length"];
516 /* Note: the -init method will create a temporary file! */
517 body = [[NGMimeFileData alloc] initWithBytes:[encoded bytes]
518 length:[encoded length]];
521 bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
522 [bodyPart setBody:body];
524 [body release]; body = nil;
528 - (NSArray *)bodyPartsForAllAttachments {
529 /* returns nil on error */
530 NSMutableArray *bodyParts;
534 names = [self fetchAttachmentNames];
535 if ((count = [names count]) == 0)
536 return [NSArray array];
538 bodyParts = [NSMutableArray arrayWithCapacity:count];
539 for (i = 0; i < count; i++) {
540 NGMimeBodyPart *bodyPart;
542 bodyPart = [self bodyPartForAttachmentWithName:[names objectAtIndex:i]];
546 [bodyParts addObject:bodyPart];
551 - (NGMimeMessage *)mimeMultiPartMessageWithHeaderMap:(NGMutableHashMap *)map
552 andBodyParts:(NSArray *)_bodyParts
554 NGMimeMessage *message;
555 NGMimeMultipartBody *mBody;
556 NGMimeBodyPart *part;
559 [map addObject:MultiMixedType forKey:@"content-type"];
561 message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
562 mBody = [[NGMimeMultipartBody alloc] initWithPart:message];
564 part = [self bodyPartForText];
565 [mBody addBodyPart:part];
567 e = [_bodyParts objectEnumerator];
568 while ((part = [e nextObject]) != nil)
569 [mBody addBodyPart:part];
571 [message setBody:mBody];
572 [mBody release]; mBody = nil;
576 - (void)_addHeaders:(NSDictionary *)_h toHeaderMap:(NGMutableHashMap *)_map {
583 names = [_h keyEnumerator];
584 while ((name = [names nextObject]) != nil) {
587 value = [_h objectForKey:name];
588 [_map addObject:value forKey:name];
592 - (BOOL)isEmptyValue:(id)_value {
593 if (![_value isNotNull])
596 if ([_value isKindOfClass:[NSArray class]])
597 return [_value count] == 0 ? YES : NO;
599 if ([_value isKindOfClass:[NSString class]])
600 return [_value length] == 0 ? YES : NO;
605 - (NGMutableHashMap *)mimeHeaderMapWithHeaders:(NSDictionary *)_headers {
606 NGMutableHashMap *map;
607 NSDictionary *lInfo; // TODO: this should be some kind of object?
609 NSString *s, *dateString;
612 if ((lInfo = [self fetchInfo]) == nil)
615 map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
619 if ((emails = [lInfo objectForKey:@"to"]) != nil) {
620 if ([emails count] == 0) {
621 [self errorWithFormat:@"missing 'to' recipient in email!"];
624 [map setObjects:emails forKey:@"to"];
626 if ((emails = [lInfo objectForKey:@"cc"]) != nil)
627 [map setObjects:emails forKey:@"cc"];
628 if ((emails = [lInfo objectForKey:@"bcc"]) != nil)
629 [map setObjects:emails forKey:@"bcc"];
633 from = [lInfo objectForKey:@"from"];
634 replyTo = [lInfo objectForKey:@"replyTo"];
636 if (![self isEmptyValue:from]) {
637 if ([from isKindOfClass:[NSArray class]])
638 [map setObjects:from forKey:@"from"];
640 [map setObject:from forKey:@"from"];
643 if (![self isEmptyValue:replyTo]) {
644 if ([from isKindOfClass:[NSArray class]])
645 [map setObjects:from forKey:@"reply-to"];
647 [map setObject:from forKey:@"reply-to"];
649 else if (![self isEmptyValue:from])
650 [map setObjects:[map objectsForKey:@"from"] forKey:@"reply-to"];
654 if ([(s = [lInfo objectForKey:@"subject"]) length] > 0)
655 [map setObject: [s asQPSubjectString: @"utf-8"]
657 // [map setObject: [s asQPSubjectString: @"utf-8"] forKey:@"subject"];
659 /* add standard headers */
661 dateString = [[NSCalendarDate date] rfc822DateString];
662 [map addObject: dateString forKey:@"date"];
663 [map addObject: @"1.0" forKey:@"MIME-Version"];
664 [map addObject: userAgent forKey:@"X-Mailer"];
666 /* add custom headers */
668 [self _addHeaders:[lInfo objectForKey:@"headers"] toHeaderMap:map];
669 [self _addHeaders:_headers toHeaderMap:map];
674 - (NGMimeMessage *)mimeMessageWithHeaders:(NSDictionary *)_headers {
675 NSAutoreleasePool *pool;
676 NGMutableHashMap *map;
678 NGMimeMessage *message;
680 pool = [[NSAutoreleasePool alloc] init];
682 if ([self fetchInfo] == nil) {
683 [self errorWithFormat:@"could not locate draft fetch info!"];
687 if ((map = [self mimeHeaderMapWithHeaders:_headers]) == nil)
689 [self debugWithFormat:@"MIME Envelope: %@", map];
691 if ((bodyParts = [self bodyPartsForAllAttachments]) == nil) {
692 [self errorWithFormat:
693 @"could not create body parts for attachments!"];
694 return nil; // TODO: improve error handling, return exception
696 [self debugWithFormat:@"attachments: %@", bodyParts];
698 if ([bodyParts count] == 0) {
700 message = [self mimeMessageForContentWithHeaderMap:map];
703 /* attachments, create multipart/mixed */
704 message = [self mimeMultiPartMessageWithHeaderMap:map
705 andBodyParts:bodyParts];
707 [self debugWithFormat:@"message: %@", message];
709 message = [message retain];
711 return [message autorelease];
713 - (NGMimeMessage *)mimeMessage {
714 return [self mimeMessageWithHeaders:nil];
717 - (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_h {
718 NGMimeMessageGenerator *gen;
719 NSAutoreleasePool *pool;
720 NGMimeMessage *message;
723 pool = [[NSAutoreleasePool alloc] init];
725 message = [self mimeMessageWithHeaders:_h];
726 if (![message isNotNull])
728 if ([message isKindOfClass:[NSException class]]) {
729 [self errorWithFormat:@"error: %@", message];
733 gen = [[NGMimeMessageGenerator alloc] init];
734 tmpPath = [[gen generateMimeFromPartToFile:message] copy];
735 [gen release]; gen = nil;
738 return [tmpPath autorelease];
740 - (NSString *)saveMimeMessageToTemporaryFile {
741 return [self saveMimeMessageToTemporaryFileWithHeaders:nil];
744 - (void)deleteTemporaryMessageFile:(NSString *)_path {
747 if (![_path isNotNull])
750 fm = [NSFileManager defaultManager];
751 if (![fm fileExistsAtPath:_path])
754 [fm removeFileAtPath:_path handler:nil];
757 - (NSArray *)allRecipients {
762 if ((lInfo = [self fetchInfo]) == nil)
765 ma = [NSMutableArray arrayWithCapacity:16];
766 if ((tmp = [lInfo objectForKey:@"to"]) != nil)
767 [ma addObjectsFromArray:tmp];
768 if ((tmp = [lInfo objectForKey:@"cc"]) != nil)
769 [ma addObjectsFromArray:tmp];
770 if ((tmp = [lInfo objectForKey:@"bcc"]) != nil)
771 [ma addObjectsFromArray:tmp];
775 - (NSString *) _rawSender
777 NSString *startEmail, *rawSender;
780 startEmail = [self sender];
781 delimiter = [startEmail rangeOfString: @"<"];
782 if (delimiter.location == NSNotFound)
783 rawSender = startEmail;
786 rawSender = [startEmail substringFromIndex: NSMaxRange (delimiter)];
787 delimiter = [rawSender rangeOfString: @">"];
788 if (delimiter.location != NSNotFound)
789 rawSender = [rawSender substringToIndex: delimiter.location];
795 - (NSException *)sendMimeMessageAtPath:(NSString *)_path {
796 static NGSendMail *mailer = nil;
802 recipients = [self allRecipients];
803 from = [self _rawSender];
804 if ([recipients count] == 0) {
805 return [NSException exceptionWithHTTPStatus:500 /* server error */
806 reason:@"draft has no recipients set!"];
808 if ([from length] == 0) {
809 return [NSException exceptionWithHTTPStatus:500 /* server error */
810 reason:@"draft has no sender (from) set!"];
813 /* setup mailer object */
816 mailer = [[NGSendMail sharedSendMail] retain];
817 if (![mailer isSendMailAvailable]) {
818 [self errorWithFormat:@"missing sendmail binary!"];
819 return [NSException exceptionWithHTTPStatus:500 /* server error */
820 reason:@"did not find sendmail binary!"];
825 return [mailer sendMailAtPath:_path toRecipients:recipients sender:from];
828 - (NSException *)sendMail {
832 /* save MIME mail to file */
834 tmpPath = [self saveMimeMessageToTemporaryFile];
835 if (![tmpPath isNotNull]) {
836 return [NSException exceptionWithHTTPStatus:500 /* server error */
837 reason:@"could not save MIME message for draft!"];
841 error = [self sendMimeMessageAtPath:tmpPath];
843 /* delete temporary file */
844 [self deleteTemporaryMessageFile:tmpPath];
851 - (NSException *)delete {
856 if ((fm = [self spoolFileManager]) == nil) {
857 [self errorWithFormat:@"missing spool file manager!"];
858 return [NSException exceptionWithHTTPStatus:500 /* server error */
859 reason:@"missing spool file manager!"];
862 p = [self draftFolderPath];
863 if (![fm fileExistsAtPath:p]) {
864 return [NSException exceptionWithHTTPStatus:404 /* not found */
865 reason:@"did not find draft!"];
868 e = [[fm directoryContentsAtPath:p] objectEnumerator];
869 while ((sp = [e nextObject])) {
870 sp = [p stringByAppendingPathComponent:sp];
871 if (draftDeleteDisabled) {
872 [self logWithFormat:@"should delete draft file %@ ...", sp];
876 if (![fm removeFileAtPath:sp handler:nil]) {
877 return [NSException exceptionWithHTTPStatus:500 /* server error */
878 reason:@"failed to delete draft!"];
882 if (draftDeleteDisabled) {
883 [self logWithFormat:@"should delete draft directory: %@", p];
886 if (![fm removeFileAtPath:p handler:nil]) {
887 return [NSException exceptionWithHTTPStatus:500 /* server error */
888 reason:@"failed to delete draft directory!"];
894 - (NSData *)content {
895 /* Note: does not cache, expensive operation */
899 if ((p = [self saveMimeMessageToTemporaryFile]) == nil)
902 data = [NSData dataWithContentsOfMappedFile:p];
904 /* delete temporary file */
905 [self deleteTemporaryMessageFile:p];
909 - (NSString *)contentAsString {
913 if ((data = [self content]) == nil)
916 str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
918 [self errorWithFormat:@"could not load draft as ASCII (data size=%d)",
923 return [str autorelease];
928 - (id)DELETEAction:(id)_ctx {
931 if ((error = [self delete]) != nil)
934 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
937 - (id)GETAction:(id)_ctx {
939 Override, because SOGoObject's GETAction uses the less efficient
940 -contentAsString method.
945 if ([rq isSoWebDAVRequest]) {
949 if ((content = [self content]) == nil) {
950 return [NSException exceptionWithHTTPStatus:500
951 reason:@"Could not generate MIME content!"];
954 [r setHeader:@"message/rfc822" forKey:@"content-type"];
955 [r setContent:content];
959 return [super GETAction:_ctx];
962 /* fake being a SOGoMailObject */
964 - (id)fetchParts:(NSArray *)_parts {
965 return [NSDictionary dictionaryWithObject:self forKey:@"fetch"];
969 return [self nameInContainer];
972 static NSArray *seenFlags = nil;
973 seenFlags = [[NSArray alloc] initWithObjects:@"seen", nil];
977 // TODO: size, hard to support, we would need to generate MIME?
981 - (NSArray *)imap4EnvelopeAddressesForStrings:(NSArray *)_emails {
987 if ((count = [_emails count]) == 0)
988 return [NSArray array];
990 ma = [NSMutableArray arrayWithCapacity:count];
991 for (i = 0; i < count; i++) {
992 NGImap4EnvelopeAddress *envaddr;
994 envaddr = [[NGImap4EnvelopeAddress alloc]
995 initWithString:[_emails objectAtIndex:i]];
996 if ([envaddr isNotNull])
997 [ma addObject:envaddr];
1003 - (NGImap4Envelope *)envelope {
1004 NSDictionary *lInfo;
1007 if (envelope != nil)
1009 if ((lInfo = [self fetchInfo]) == nil)
1012 if ((from = [self sender]) != nil)
1013 from = [NSArray arrayWithObjects:&from count:1];
1015 if ((replyTo = [lInfo objectForKey:@"replyTo"]) != nil) {
1016 if (![replyTo isKindOfClass:[NSArray class]])
1017 replyTo = [NSArray arrayWithObjects:&replyTo count:1];
1021 [[NGImap4Envelope alloc] initWithMessageID:[self nameInContainer]
1022 subject:[lInfo objectForKey:@"subject"]
1023 from:from replyTo:replyTo
1024 to:[lInfo objectForKey:@"to"]
1025 cc:[lInfo objectForKey:@"cc"]
1026 bcc:[lInfo objectForKey:@"bcc"]];
1032 - (BOOL)isDebuggingEnabled {
1036 @end /* SOGoDraftObject */