diff --git a/ChangeLog b/ChangeLog index 9cc24ef9f..1fd10a59b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,27 @@ +2007-10-25 Wolfgang Sourdeau + + * SoObjects/Mailer/NSData+Mail.m ([NSData + -bodyDataFromEncoding:encoding]): new utility method that decodes + the NSData instance properly depending on the encoding string + passed as parameter. + + * SoObjects/Mailer/SOGoMailObject+Draft.m ([SOGoMailObject + -fetchFileAttachmentKeys]): fetch the attachment encoding as well. + + * SoObjects/Mailer/SOGoMailObject.m ([SOGoMailObject + -stringForData:_datapartInfo:_info]): simplified by invoking + -bodyDataFromEncoding: from our new NSData category methods. + + * SoObjects/Mailer/SOGoDraftObject.m ([SOGoDraftObject + -fetchMailForEditing:sourceMail]): work-around a bug in SOPE-mime + where only the body part of the first of the keys fetched was + returned. Also decodes the body parts properly following their + encoding. + + * SoObjects/Mailer/NSData+Mail.[hm]: new extension module that + extends the NSData class with utility methods useful for handling + mail. + 2007-10-23 Wolfgang Sourdeau * SoObjects/SOGo/NSArray+Utilities.m diff --git a/SoObjects/Mailer/GNUmakefile b/SoObjects/Mailer/GNUmakefile index cc04cf258..381e3fbdd 100644 --- a/SoObjects/Mailer/GNUmakefile +++ b/SoObjects/Mailer/GNUmakefile @@ -30,7 +30,9 @@ Mailer_OBJC_FILES += \ SOGoDraftsFolder.m \ SOGoDraftObject.m \ \ - SOGoMailForward.m + SOGoMailForward.m \ + \ + NSData+Mail.m Mailer_RESOURCE_FILES += \ Version \ diff --git a/SoObjects/Mailer/NSData+Mail.h b/SoObjects/Mailer/NSData+Mail.h new file mode 100644 index 000000000..45e033f74 --- /dev/null +++ b/SoObjects/Mailer/NSData+Mail.h @@ -0,0 +1,36 @@ +/* NSData+Mail.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef NSDATA_MAIL_H +#define NSDATA_MAIL_H + +#import + +@class NSString; + +@interface NSData (SOGoMailUtilities) + +- (NSData *) bodyDataFromEncoding: (NSString *) encoding; + +@end + +#endif /* NSDATA_MAIL_H */ diff --git a/SoObjects/Mailer/NSData+Mail.m b/SoObjects/Mailer/NSData+Mail.m new file mode 100644 index 000000000..eb3051f6f --- /dev/null +++ b/SoObjects/Mailer/NSData+Mail.m @@ -0,0 +1,55 @@ +/* NSData+Mail.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import +#import + +#import "NSData+Mail.h" + +@implementation NSData (SOGoMailUtilities) + +- (NSData *) bodyDataFromEncoding: (NSString *) encoding +{ + NSString *realEncoding; + NSData *decodedData; + + realEncoding = [encoding lowercaseString]; + + if ([realEncoding isEqualToString: @"7bit"] + || [realEncoding isEqualToString: @"8bit"]) + decodedData = self; + else if ([realEncoding isEqualToString: @"base64"]) + decodedData = [self dataByDecodingBase64]; + else if ([realEncoding isEqualToString: @"quoted-printable"]) + decodedData = [self dataByDecodingQuotedPrintable]; + else + { + decodedData = nil; + NSLog (@"encoding '%@' unknown, returning nil data", realEncoding); + } + + return decodedData; +} + +@end diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index 3aa25f15e..2cc71010e 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -56,6 +56,8 @@ #import #import #import + +#import "NSData+Mail.h" #import "SOGoMailAccount.h" #import "SOGoMailFolder.h" #import "SOGoMailObject.h" @@ -411,30 +413,49 @@ static BOOL showTextAttachmentsInline = NO; } } +- (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths + fromResponseFetch: (NSDictionary *) fetch; +{ + NSEnumerator *attachmentKeys; + NSMutableArray *bodies; + NSString *currentKey; + NSDictionary *body; + + bodies = [NSMutableArray array]; + + attachmentKeys = [paths objectEnumerator]; + while ((currentKey = [attachmentKeys nextObject])) + { + body = [fetch objectForKey: [currentKey lowercaseString]]; + [bodies addObject: [body objectForKey: @"data"]]; + } + + return bodies; +} + - (void) _fetchAttachments: (NSArray *) parts fromMail: (SOGoMailObject *) sourceMail { unsigned int count, max; - NSDictionary *currentPart, *attachment, *body; - NSArray *paths, *result; + NSArray *paths, *bodies; + NSData *body; + NSDictionary *currentInfo; + NGHashMap *response; max = [parts count]; if (max > 0) { paths = [parts keysWithFormat: @"BODY[%{path}]"]; - result = [[sourceMail fetchParts: paths] objectForKey: @"fetch"]; + response = [[sourceMail fetchParts: paths] objectForKey: @"RawResponse"]; + bodies = [self _attachmentBodiesFromPaths: paths + fromResponseFetch: [response objectForKey: @"fetch"]]; for (count = 0; count < max; count++) { - currentPart = [parts objectAtIndex: count]; - body = [[result objectAtIndex: count] objectForKey: @"body"]; - attachment = [NSDictionary dictionaryWithObjectsAndKeys: - [currentPart objectForKey: @"filename"], - @"filename", - [currentPart objectForKey: @"mimetype"], - @"mime-type", - nil]; - [self saveAttachment: [body objectForKey: @"data"] - withMetadata: attachment]; + currentInfo = [parts objectAtIndex: count]; + body = [[bodies objectAtIndex: count] + bodyDataFromEncoding: [currentInfo + objectForKey: @"encoding"]]; + [self saveAttachment: body withMetadata: currentInfo]; } } } @@ -529,7 +550,7 @@ static BOOL showTextAttachmentsInline = NO; // error = [newDraft saveAttachment:content withName:@"forward.mail"]; attachment = [NSDictionary dictionaryWithObjectsAndKeys: [sourceMail filenameForForward], @"filename", - @"message/rfc822", @"mime-type", + @"message/rfc822", @"mimetype", nil]; [self saveAttachment: [sourceMail content] withMetadata: attachment]; @@ -639,7 +660,7 @@ static BOOL showTextAttachmentsInline = NO; reason: @"Could not write attachment to draft!"]; } - mimeType = [metadata objectForKey: @"mime-type"]; + mimeType = [metadata objectForKey: @"mimetype"]; if ([mimeType length] > 0) { p = [self pathToAttachmentWithName: diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.m b/SoObjects/Mailer/SOGoMailObject+Draft.m index 18e6e0f2f..ebe7e04a7 100644 --- a/SoObjects/Mailer/SOGoMailObject+Draft.m +++ b/SoObjects/Mailer/SOGoMailObject+Draft.m @@ -232,20 +232,22 @@ intoArray: (NSMutableArray *) keys withPath: (NSString *) path { - NSDictionary *parameters, *currentFile; + NSDictionary *disposition, *currentFile; NSString *filename, *mimeType; - parameters = [[part objectForKey: @"disposition"] - objectForKey: @"parameterList"]; - if (parameters) + disposition = [part objectForKey: @"disposition"]; + filename = [[disposition objectForKey: @"parameterList"] + objectForKey: @"filename"]; + if (filename) { - filename = [parameters objectForKey: @"filename"]; mimeType = [NSString stringWithFormat: @"%@/%@", [part objectForKey: @"type"], [part objectForKey: @"subtype"]]; currentFile = [NSDictionary dictionaryWithObjectsAndKeys: filename, @"filename", [mimeType lowercaseString], @"mimetype", + [part + objectForKey: @"encoding"], @"encoding", path, @"path", nil]; [keys addObject: currentFile]; } diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index cd2e8d481..86eb94e8e 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -31,10 +31,8 @@ #import #import #import -#import #import #import -#import #import #import #import @@ -44,6 +42,8 @@ #import #import + +#import "NSData+Mail.h" #import "SOGoMailFolder.h" #import "SOGoMailAccount.h" #import "SOGoMailManager.h" @@ -619,37 +619,29 @@ static BOOL debugSoParts = NO; } /* convert parts to strings */ - - (NSString *) stringForData: (NSData *) _data partInfo: (NSDictionary *) _info { - NSString *charset, *encoding, *s; + NSString *charset, *s; NSData *mailData; - if (![_data isNotNull]) - return nil; - - s = nil; - - encoding = [[_info objectForKey: @"encoding"] lowercaseString]; - - if ([encoding isEqualToString: @"7bit"] - || [encoding isEqualToString: @"8bit"]) - mailData = _data; - else if ([encoding isEqualToString: @"base64"]) - mailData = [_data dataByDecodingBase64]; - else if ([encoding isEqualToString: @"quoted-printable"]) - mailData = [_data dataByDecodingQuotedPrintable]; - - charset = [[_info valueForKey: @"parameterList"] valueForKey: @"charset"]; - if (![charset length]) + if ([_data isNotNull]) { - s = [[NSString alloc] initWithData:mailData encoding:NSUTF8StringEncoding]; - [s autorelease]; + mailData + = [_data bodyDataFromEncoding: [_info objectForKey: @"encoding"]]; + + charset = [[_info valueForKey: @"parameterList"] valueForKey: @"charset"]; + if (![charset length]) + { + s = [[NSString alloc] initWithData: mailData encoding: NSUTF8StringEncoding]; + [s autorelease]; + } + else + s = [NSString stringWithData: mailData + usingEncodingNamed: charset]; } else - s = [NSString stringWithData: mailData - usingEncodingNamed: charset]; + s = nil; return s; } diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index a2eb9ee00..2950bd39d 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -280,13 +280,14 @@ static NSArray *infoKeys = nil; for (count = 0; count < max; count++) { part = [parts objectAtIndex: count]; - header = (NGMimeContentDispositionHeaderField *) [part headerForKey: @"content-disposition"]; - mimeType = [(NGMimeType *) [part headerForKey: @"content-type"] stringValue]; + header = (NGMimeContentDispositionHeaderField *) + [part headerForKey: @"content-disposition"]; + mimeType = [(NGMimeType *) + [part headerForKey: @"content-type"] stringValue]; attachment = [NSDictionary dictionaryWithObjectsAndKeys: [header filename], @"filename", - mimeType, @"mime-type", nil]; - [filenames setObject: attachment - forKey: [header name]]; + mimeType, @"mimetype", nil]; + [filenames setObject: attachment forKey: [header name]]; } return filenames; diff --git a/UI/WebServerResources/UIxMailEditor.js b/UI/WebServerResources/UIxMailEditor.js index be10667e4..fd8d1e7da 100644 --- a/UI/WebServerResources/UIxMailEditor.js +++ b/UI/WebServerResources/UIxMailEditor.js @@ -16,7 +16,7 @@ function onContactAdd() { w.focus(); return false; - } +} function addContact(tag, fullContactName, contactId, contactName, contactEmail) { if (!mailIsRecipient(contactEmail)) { @@ -121,59 +121,76 @@ function updateInlineAttachmentList(sender, attachments) { /* mail editor */ function validateEditorInput(sender) { - var errortext = ""; - var field; + var errortext = ""; + var field; - field = document.pageform.subject; - if (field.value == "") - errortext = errortext + labels["error_missingsubject"] + "\n"; + field = document.pageform.subject; + if (field.value == "") + errortext = errortext + labels["error_missingsubject"] + "\n"; - if (!UIxRecipientSelectorHasRecipients()) - errortext = errortext + labels["error_missingrecipients"] + "\n"; + if (!UIxRecipientSelectorHasRecipients()) + errortext = errortext + labels["error_missingrecipients"] + "\n"; - if (errortext.length > 0) { - alert(labels["error_validationfailed"] + ":\n" + errortext); - return false; - } + if (errortext.length > 0) { + alert(labels["error_validationfailed"] + ":\n" + errortext); + return false; + } - return true; + return true; } function clickedEditorSend(sender) { - if (!validateEditorInput(sender)) - return false; + if (!validateEditorInput(sender)) + return false; - window.shouldPreserve = true; - document.pageform.action = "send"; - document.pageform.submit(); + var input = currentAttachmentInput(); + if (input) + input.parentNode.removeChild(input); - return false; + var toolbar = document.getElementById("toolbar"); + if (!document.busyAnim) + document.busyAnim = startAnimation(toolbar); + + window.shouldPreserve = true; + document.pageform.action = "send"; + document.pageform.submit(); + + return false; +} + +function currentAttachmentInput() { + var input = null; + + var inputs = $("attachmentsArea").getElementsByTagName("input"); + var i = 0; + while (!input && i < inputs.length) + if ($(inputs[i]).hasClassName("currentAttachment")) + input = inputs[i]; + else + i++; + + return input; } function clickedEditorAttach(sender) { - var area = $("attachmentsArea"); + var input = currentAttachmentInput(); + if (!input) { + var area = $("attachmentsArea"); - if (!area.style.display) { - area.setStyle({ display: "block" }); - onWindowResize(null); - } - - var inputs = area.getElementsByTagName("input"); - - // Verify if there's already a visible file input field - for (var i = 0; i < inputs.length; i++) - if ($(inputs[i]).hasClassName("currentAttachment")) - return false; - - // Add new file input field - var attachmentName = "attachment" + inputs.length; - var newAttachment = createElement("input", attachmentName, - "currentAttachment", null, - { type: "file", - name: attachmentName }, - area); - Event.observe(newAttachment, "change", - onAttachmentChange.bindAsEventListener(newAttachment)); + if (!area.style.display) { + area.setStyle({ display: "block" }); + onWindowResize(null); + } + var inputs = area.getElementsByTagName("input"); + var attachmentName = "attachment" + inputs.length; + var newAttachment = createElement("input", attachmentName, + "currentAttachment", null, + { type: "file", + name: attachmentName }, + area); + Event.observe(newAttachment, "change", + onAttachmentChange.bindAsEventListener(newAttachment)); + } return false; } @@ -211,6 +228,14 @@ function createAttachment(node, list) { } function clickedEditorSave(sender) { + var input = currentAttachmentInput(); + if (input) + input.parentNode.removeChild(input); + + var toolbar = document.getElementById("toolbar"); + if (!document.busyAnim) + document.busyAnim = startAnimation(toolbar); + window.shouldPreserve = true; document.pageform.action = "save"; document.pageform.submit();