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 @interface NSString (NGMimeHelpers)
40 - (NSString *) asQPSubjectString;
44 @implementation NSString (NGMimeHelpers)
46 - (NSString *) asQPSubjectString
49 unsigned char *data, *dest;
50 unsigned int dataLen, destLen;
52 dataLen = [self length];
53 data = calloc(dataLen, sizeof (unsigned char*));
54 [self getCString: (char *) data];
56 destLen = dataLen * 3;
57 dest = calloc(dataLen * 3, sizeof (unsigned char*));
58 NGEncodeQuotedPrintableMime (data, dataLen, dest, destLen);
60 qpString = [NSString stringWithFormat: @"=?utf-8?Q?%s?=", dest];
70 @implementation SOGoDraftObject
72 static NGMimeType *TextPlainType = nil;
73 static NGMimeType *MultiMixedType = nil;
74 static NSString *userAgent = @"SOGoMail 1.0";
75 static BOOL draftDeleteDisabled = NO; // for debugging
76 static BOOL debugOn = NO;
77 static BOOL showTextAttachmentsInline = NO;
78 static NSString *fromInternetSuffixPattern = nil;
81 return [super version] + 0 /* v1 */;
85 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
87 NSAssert2([super version] == 1,
88 @"invalid superclass (%@) version %i !",
89 NSStringFromClass([self superclass]), [super version]);
91 /* Note: be aware of the charset issues before enabling this! */
92 showTextAttachmentsInline = [ud boolForKey:@"SOGoShowTextAttachmentsInline"];
94 if ((draftDeleteDisabled = [ud boolForKey:@"SOGoNoDraftDeleteAfterSend"]))
95 NSLog(@"WARNING: draft delete is disabled! (SOGoNoDraftDeleteAfterSend)");
97 fromInternetSuffixPattern = [ud stringForKey:@"SOGoInternetMailSuffix"];
98 if ([fromInternetSuffixPattern length] == 0)
99 NSLog(@"Note: no 'SOGoInternetMailSuffix' is configured.");
101 fromInternetSuffixPattern =
102 [@"\n" stringByAppendingString:fromInternetSuffixPattern];
105 TextPlainType = [[NGMimeType mimeType:@"text" subType:@"plain"] copy];
106 MultiMixedType = [[NGMimeType mimeType:@"multipart" subType:@"mixed"] copy];
110 [self->envelope release];
111 [self->info release];
112 [self->path release];
116 /* draft folder functionality */
118 - (NSFileManager *)spoolFileManager {
119 return [[self container] spoolFileManager];
121 - (NSString *)userSpoolFolderPath {
122 return [[self container] userSpoolFolderPath];
124 - (BOOL)_ensureUserSpoolFolderPath {
125 return [[self container] _ensureUserSpoolFolderPath];
128 /* draft object functionality */
130 - (NSString *)draftFolderPath {
131 if (self->path != nil)
134 self->path = [[[self userSpoolFolderPath] stringByAppendingPathComponent:
135 [self nameInContainer]] copy];
138 - (BOOL)_ensureDraftFolderPath {
141 if (![self _ensureUserSpoolFolderPath])
144 if ((fm = [self spoolFileManager]) == nil) {
145 [self errorWithFormat:@"missing spool file manager!"];
148 return [fm createDirectoriesAtPath:[self draftFolderPath] attributes:nil];
151 - (NSString *)infoPath {
152 return [[self draftFolderPath]
153 stringByAppendingPathComponent:@".info.plist"];
158 - (NSException *)storeInfo:(NSDictionary *)_info {
160 return [NSException exceptionWithHTTPStatus:500 /* server error */
161 reason:@"got no info to write for draft!"];
163 if (![self _ensureDraftFolderPath]) {
164 [self errorWithFormat:@"could not create folder for draft: '%@'",
165 [self draftFolderPath]];
166 return [NSException exceptionWithHTTPStatus:500 /* server error */
167 reason:@"could not create folder for draft!"];
169 if (![_info writeToFile:[self infoPath] atomically:YES]) {
170 [self errorWithFormat:@"could not write info: '%@'", [self infoPath]];
171 return [NSException exceptionWithHTTPStatus:500 /* server error */
172 reason:@"could not write draft info!"];
175 /* reset info cache */
176 [self->info release]; self->info = nil;
178 return nil /* everything is excellent */;
180 - (NSDictionary *)fetchInfo {
183 if (self->info != nil)
187 if (![[self spoolFileManager] fileExistsAtPath:p]) {
188 [self debugWithFormat:@"Note: info object does not yet exist: %@", p];
192 self->info = [[NSDictionary alloc] initWithContentsOfFile:p];
193 if (self->info == nil)
194 [self errorWithFormat:@"draft info dictionary broken at path: %@", p];
201 - (NSString *)sender {
204 if ((tmp = [[self fetchInfo] objectForKey:@"from"]) == nil)
206 if ([tmp isKindOfClass:[NSArray class]])
207 return [tmp count] > 0 ? [tmp objectAtIndex:0] : nil;
213 - (NSArray *)fetchAttachmentNames {
219 fm = [self spoolFileManager];
220 if ((files = [fm directoryContentsAtPath:[self draftFolderPath]]) == nil)
223 count = [files count];
224 ma = [NSMutableArray arrayWithCapacity:count];
225 for (i = 0; i < count; i++) {
228 filename = [files objectAtIndex:i];
229 if ([filename hasPrefix:@"."])
232 [ma addObject:filename];
237 - (BOOL)isValidAttachmentName:(NSString *)_name {
238 static NSString *sescape[] = { @"/", @"..", @"~", @"\"", @"'", @" ", nil };
242 if (![_name isNotNull]) return NO;
243 if ([_name length] == 0) return NO;
244 if ([_name hasPrefix:@"."]) return NO;
246 for (i = 0; sescape[i] != nil; i++) {
247 r = [_name rangeOfString:sescape[i]];
248 if (r.length > 0) return NO;
253 - (NSString *)pathToAttachmentWithName:(NSString *)_name {
254 if ([_name length] == 0)
257 return [[self draftFolderPath] stringByAppendingPathComponent:_name];
260 - (NSException *)invalidAttachmentNameError:(NSString *)_name {
261 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
262 reason:@"Invalid attachment name!"];
265 - (NSException *)saveAttachment:(NSData *)_attach withName:(NSString *)_name {
268 if (![_attach isNotNull]) {
269 return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
270 reason:@"Missing attachment content!"];
273 if (![self _ensureDraftFolderPath]) {
274 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
275 reason:@"Could not create folder for draft!"];
277 if (![self isValidAttachmentName:_name])
278 return [self invalidAttachmentNameError:_name];
280 p = [self pathToAttachmentWithName:_name];
281 if (![_attach writeToFile:p atomically:YES]) {
282 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
283 reason:@"Could not write attachment to draft!"];
286 return nil; /* everything OK */
289 - (NSException *)deleteAttachmentWithName:(NSString *)_name {
293 if (![self isValidAttachmentName:_name])
294 return [self invalidAttachmentNameError:_name];
296 fm = [self spoolFileManager];
297 p = [self pathToAttachmentWithName:_name];
298 if (![fm fileExistsAtPath:p])
299 return nil; /* well, doesn't exist, so its deleted ;-) */
301 if (![fm removeFileAtPath:p handler:nil]) {
302 [self logWithFormat:@"ERROR: failed to delete file: %@", p];
303 return [NSException exceptionWithHTTPStatus:500 /* Server Error */
304 reason:@"Could not delete attachment from draft!"];
306 return nil; /* everything OK */
309 /* NGMime representations */
311 - (NGMimeBodyPart *)bodyPartForText
314 This add the text typed by the user (the primary plain/text part).
316 NGMutableHashMap *map;
317 NGMimeBodyPart *bodyPart;
321 if ((lInfo = [self fetchInfo]) == nil)
324 /* prepare header of body part */
326 map = [[[NGMutableHashMap alloc] initWithCapacity:2] autorelease];
328 // TODO: set charset in header!
329 [map setObject:@"text/plain" forKey:@"content-type"];
330 if ((body = [lInfo objectForKey:@"text"]) != nil) {
331 if ([body isKindOfClass:[NSString class]]) {
332 [map setObject:@"text/plain; charset=utf-8" forKey:@"content-type"];
333 // body = [body dataUsingEncoding:NSUTF8StringEncoding];
337 /* prepare body content */
339 bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
340 [bodyPart setBody:body];
344 - (NGMimeMessage *)mimeMessageForContentWithHeaderMap:(NGMutableHashMap *)map
347 NGMimeMessage *message;
348 NSString *fromInternetSuffix;
352 if ((lInfo = [self fetchInfo]) == nil)
355 addSuffix = [context isAccessFromIntranet] ? NO : YES;
358 [fromInternetSuffixPattern stringByReplacingVariablesWithBindings:
360 stringForUnknownBindings:@""];
362 addSuffix = [fromInternetSuffix length] > 0 ? YES : NO;
365 [map setObject:@"text/plain" forKey:@"content-type"];
366 if ((body = [lInfo objectForKey:@"text"]) != nil) {
367 if ([body isKindOfClass:[NSString class]]) {
369 body = [body stringByAppendingString:fromInternetSuffix];
371 /* Note: just 'utf8' is displayed wrong in Mail.app */
372 [map setObject:@"text/plain; charset=utf-8" forKey:@"content-type"];
373 // body = [body dataUsingEncoding:NSUTF8StringEncoding];
375 else if ([body isKindOfClass:[NSData class]] && addSuffix) {
376 body = [[body mutableCopy] autorelease];
377 [(NSMutableData *)body
378 appendData: [fromInternetSuffix dataUsingEncoding:NSUTF8StringEncoding]];
380 else if (addSuffix) {
381 [self warnWithFormat:@"Note: cannot add Internet marker to body: %@",
382 NSStringFromClass([body class])];
386 body = fromInternetSuffix;
388 message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
389 [message setBody:body];
393 - (NSString *)mimeTypeForExtension:(NSString *)_ext {
394 // TODO: make configurable
395 // TODO: use /etc/mime-types
396 if ([_ext isEqualToString:@"txt"]) return @"text/plain";
397 if ([_ext isEqualToString:@"html"]) return @"text/html";
398 if ([_ext isEqualToString:@"htm"]) return @"text/html";
399 if ([_ext isEqualToString:@"gif"]) return @"image/gif";
400 if ([_ext isEqualToString:@"jpg"]) return @"image/jpeg";
401 if ([_ext isEqualToString:@"jpeg"]) return @"image/jpeg";
402 if ([_ext isEqualToString:@"mail"]) return @"message/rfc822";
403 return @"application/octet-stream";
406 - (NSString *)contentTypeForAttachmentWithName:(NSString *)_name {
409 s = [self mimeTypeForExtension:[_name pathExtension]];
410 if ([_name length] > 0)
411 s = [s stringByAppendingFormat:@"; name=\"%@\"", _name];
415 - (NSString *)contentDispositionForAttachmentWithName:(NSString *)_name {
420 type = [self contentTypeForAttachmentWithName:_name];
422 if ([type hasPrefix:@"text/"])
423 cdtype = showTextAttachmentsInline ? @"inline" : @"attachment";
424 else if ([type hasPrefix:@"image/"] || [type hasPrefix:@"message"])
427 cdtype = @"attachment";
429 cd = [cdtype stringByAppendingString:@"; filename=\""];
430 cd = [cd stringByAppendingString:_name];
431 cd = [cd stringByAppendingString:@"\""];
433 // TODO: add size parameter (useful addition, RFC 2183)
437 - (NGMimeBodyPart *)bodyPartForAttachmentWithName:(NSString *)_name {
439 NGMutableHashMap *map;
440 NGMimeBodyPart *bodyPart;
443 BOOL attachAsString, is7bit;
447 if (_name == nil) return nil;
449 /* check attachment */
451 fm = [self spoolFileManager];
452 p = [self pathToAttachmentWithName:_name];
453 if (![fm isReadableFileAtPath:p]) {
454 [self errorWithFormat:@"did not find attachment: '%@'", _name];
460 /* prepare header of body part */
462 map = [[[NGMutableHashMap alloc] initWithCapacity:4] autorelease];
464 if ((s = [self contentTypeForAttachmentWithName:_name]) != nil) {
465 [map setObject:s forKey:@"content-type"];
466 if ([s hasPrefix:@"text/"])
467 attachAsString = YES;
468 else if ([s hasPrefix:@"message/rfc822"])
471 if ((s = [self contentDispositionForAttachmentWithName:_name]))
472 [map setObject:s forKey:@"content-disposition"];
474 /* prepare body content */
476 if (attachAsString) { // TODO: is this really necessary?
479 content = [[NSData alloc] initWithContentsOfMappedFile:p];
481 s = [[NSString alloc] initWithData:content
482 encoding:[NSString defaultCStringEncoding]];
485 [content release]; content = nil;
488 [self warnWithFormat:
489 @"could not get text attachment as string: '%@'", _name];
496 Note: Apparently NGMimeFileData objects are not processed by the MIME
499 body = [[NGMimeFileData alloc] initWithPath:p removeFile:NO];
500 [map setObject:@"7bit" forKey:@"content-transfer-encoding"];
501 [map setObject:[NSNumber numberWithInt:[body length]]
502 forKey:@"content-length"];
506 Note: in OGo this is done in LSWImapMailEditor.m:2477. Apparently
507 NGMimeFileData objects are not processed by the MIME generator!
511 content = [[NSData alloc] initWithContentsOfMappedFile:p];
512 encoded = [content dataByEncodingBase64];
513 [content release]; content = nil;
515 [map setObject:@"base64" forKey:@"content-transfer-encoding"];
516 [map setObject:[NSNumber numberWithInt:[encoded length]]
517 forKey:@"content-length"];
519 /* Note: the -init method will create a temporary file! */
520 body = [[NGMimeFileData alloc] initWithBytes:[encoded bytes]
521 length:[encoded length]];
524 bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease];
525 [bodyPart setBody:body];
527 [body release]; body = nil;
531 - (NSArray *)bodyPartsForAllAttachments {
532 /* returns nil on error */
533 NSMutableArray *bodyParts;
537 names = [self fetchAttachmentNames];
538 if ((count = [names count]) == 0)
539 return [NSArray array];
541 bodyParts = [NSMutableArray arrayWithCapacity:count];
542 for (i = 0; i < count; i++) {
543 NGMimeBodyPart *bodyPart;
545 bodyPart = [self bodyPartForAttachmentWithName:[names objectAtIndex:i]];
549 [bodyParts addObject:bodyPart];
554 - (NGMimeMessage *)mimeMultiPartMessageWithHeaderMap:(NGMutableHashMap *)map
555 andBodyParts:(NSArray *)_bodyParts
557 NGMimeMessage *message;
558 NGMimeMultipartBody *mBody;
559 NGMimeBodyPart *part;
562 [map addObject:MultiMixedType forKey:@"content-type"];
564 message = [[[NGMimeMessage alloc] initWithHeader:map] autorelease];
565 mBody = [[NGMimeMultipartBody alloc] initWithPart:message];
567 part = [self bodyPartForText];
568 [mBody addBodyPart:part];
570 e = [_bodyParts objectEnumerator];
571 while ((part = [e nextObject]) != nil)
572 [mBody addBodyPart:part];
574 [message setBody:mBody];
575 [mBody release]; mBody = nil;
579 - (void)_addHeaders:(NSDictionary *)_h toHeaderMap:(NGMutableHashMap *)_map {
586 names = [_h keyEnumerator];
587 while ((name = [names nextObject]) != nil) {
590 value = [_h objectForKey:name];
591 [_map addObject:value forKey:name];
595 - (BOOL)isEmptyValue:(id)_value {
596 if (![_value isNotNull])
599 if ([_value isKindOfClass:[NSArray class]])
600 return [_value count] == 0 ? YES : NO;
602 if ([_value isKindOfClass:[NSString class]])
603 return [_value length] == 0 ? YES : NO;
608 - (NGMutableHashMap *)mimeHeaderMapWithHeaders:(NSDictionary *)_headers {
609 NGMutableHashMap *map;
610 NSDictionary *lInfo; // TODO: this should be some kind of object?
612 NSString *s, *dateString;
615 if ((lInfo = [self fetchInfo]) == nil)
618 map = [[[NGMutableHashMap alloc] initWithCapacity:16] autorelease];
622 if ((emails = [lInfo objectForKey:@"to"]) != nil) {
623 if ([emails count] == 0) {
624 [self errorWithFormat:@"missing 'to' recipient in email!"];
627 [map setObjects:emails forKey:@"to"];
629 if ((emails = [lInfo objectForKey:@"cc"]) != nil)
630 [map setObjects:emails forKey:@"cc"];
631 if ((emails = [lInfo objectForKey:@"bcc"]) != nil)
632 [map setObjects:emails forKey:@"bcc"];
636 from = [lInfo objectForKey:@"from"];
637 replyTo = [lInfo objectForKey:@"replyTo"];
639 if (![self isEmptyValue:from]) {
640 if ([from isKindOfClass:[NSArray class]])
641 [map setObjects:from forKey:@"from"];
643 [map setObject:from forKey:@"from"];
646 if (![self isEmptyValue:replyTo]) {
647 if ([from isKindOfClass:[NSArray class]])
648 [map setObjects:from forKey:@"reply-to"];
650 [map setObject:from forKey:@"reply-to"];
652 else if (![self isEmptyValue:from])
653 [map setObjects:[map objectsForKey:@"from"] forKey:@"reply-to"];
657 if ([(s = [lInfo objectForKey:@"subject"]) length] > 0)
658 [map setObject: [s asQPSubjectString] forKey:@"subject"];
660 /* add standard headers */
662 dateString = [[NSCalendarDate date] rfc822DateString];
663 [map addObject: dateString forKey:@"date"];
664 [map addObject: @"1.0" forKey:@"MIME-Version"];
665 [map addObject: userAgent forKey:@"X-Mailer"];
667 /* add custom headers */
669 [self _addHeaders:[lInfo objectForKey:@"headers"] toHeaderMap:map];
670 [self _addHeaders:_headers toHeaderMap:map];
675 - (NGMimeMessage *)mimeMessageWithHeaders:(NSDictionary *)_headers {
676 NSAutoreleasePool *pool;
677 NGMutableHashMap *map;
679 NGMimeMessage *message;
681 pool = [[NSAutoreleasePool alloc] init];
683 if ([self fetchInfo] == nil) {
684 [self errorWithFormat:@"could not locate draft fetch info!"];
688 if ((map = [self mimeHeaderMapWithHeaders:_headers]) == nil)
690 [self debugWithFormat:@"MIME Envelope: %@", map];
692 if ((bodyParts = [self bodyPartsForAllAttachments]) == nil) {
693 [self errorWithFormat:
694 @"could not create body parts for attachments!"];
695 return nil; // TODO: improve error handling, return exception
697 [self debugWithFormat:@"attachments: %@", bodyParts];
699 if ([bodyParts count] == 0) {
701 message = [self mimeMessageForContentWithHeaderMap:map];
704 /* attachments, create multipart/mixed */
705 message = [self mimeMultiPartMessageWithHeaderMap:map
706 andBodyParts:bodyParts];
708 [self debugWithFormat:@"message: %@", message];
710 message = [message retain];
712 return [message autorelease];
714 - (NGMimeMessage *)mimeMessage {
715 return [self mimeMessageWithHeaders:nil];
718 - (NSString *)saveMimeMessageToTemporaryFileWithHeaders:(NSDictionary *)_h {
719 NGMimeMessageGenerator *gen;
720 NSAutoreleasePool *pool;
721 NGMimeMessage *message;
724 pool = [[NSAutoreleasePool alloc] init];
726 message = [self mimeMessageWithHeaders:_h];
727 if (![message isNotNull])
729 if ([message isKindOfClass:[NSException class]]) {
730 [self errorWithFormat:@"error: %@", message];
734 gen = [[NGMimeMessageGenerator alloc] init];
735 tmpPath = [[gen generateMimeFromPartToFile:message] copy];
736 [gen release]; gen = nil;
739 return [tmpPath autorelease];
741 - (NSString *)saveMimeMessageToTemporaryFile {
742 return [self saveMimeMessageToTemporaryFileWithHeaders:nil];
745 - (void)deleteTemporaryMessageFile:(NSString *)_path {
748 if (![_path isNotNull])
751 fm = [NSFileManager defaultManager];
752 if (![fm fileExistsAtPath:_path])
755 [fm removeFileAtPath:_path handler:nil];
758 - (NSArray *)allRecipients {
763 if ((lInfo = [self fetchInfo]) == nil)
766 ma = [NSMutableArray arrayWithCapacity:16];
767 if ((tmp = [lInfo objectForKey:@"to"]) != nil)
768 [ma addObjectsFromArray:tmp];
769 if ((tmp = [lInfo objectForKey:@"cc"]) != nil)
770 [ma addObjectsFromArray:tmp];
771 if ((tmp = [lInfo objectForKey:@"bcc"]) != nil)
772 [ma addObjectsFromArray:tmp];
776 - (NSString *) _rawSender
778 NSString *startEmail, *rawSender;
781 startEmail = [self sender];
782 delimiter = [startEmail rangeOfString: @"<"];
783 if (delimiter.location == NSNotFound)
784 rawSender = startEmail;
787 rawSender = [startEmail substringFromIndex: NSMaxRange (delimiter)];
788 delimiter = [rawSender rangeOfString: @">"];
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 (self->envelope != nil)
1008 return self->envelope;
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"]];
1027 return self->envelope;
1032 - (BOOL)isDebuggingEnabled {
1036 @end /* SOGoDraftObject */