diff --git a/SoObjects/Mailer/SOGoDraftObject.m b/SoObjects/Mailer/SOGoDraftObject.m index ad2b3a30a3..6f53875807 100644 --- a/SoObjects/Mailer/SOGoDraftObject.m +++ b/SoObjects/Mailer/SOGoDraftObject.m @@ -859,7 +859,7 @@ - (void) _fillInReplyAddresses: (NSMutableDictionary *) _info // // // -- (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail +- (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail onlyImages: (BOOL) onlyImages { NSMutableDictionary *currentInfo; NSArray *attachments; @@ -871,15 +871,19 @@ - (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail for (count = 0; count < max; count++) { currentInfo = [attachments objectAtIndex: count]; - [self saveAttachment: [currentInfo objectForKey: @"body"] + if (!onlyImages + || (onlyImages && [[NGMimeBodyPart imageMimeTypes] containsObject: [currentInfo objectForKey: @"mimetype"]])) { + [self saveAttachment: [currentInfo objectForKey: @"body"] withMetadata: currentInfo]; + } + } } // // // -- (void) _fileAttachmentsFromPart: (id) thePart +- (void) _fileAttachmentsFromPart: (id) thePart onlyImages: (BOOL) onlyImages { // Small hack to avoid SOPE's stupid behavior to wrap a multipart // object in a NGMimeBodyPart. @@ -889,10 +893,15 @@ - (void) _fileAttachmentsFromPart: (id) thePart if ([thePart isKindOfClass: [NGMimeBodyPart class]]) { - NSString *filename, *mimeType; + NSString *filename, *mimeType, *bodyId; id body; + if (onlyImages && ![thePart isImage]) { + return; + } + mimeType = [[thePart contentType] stringValue]; + bodyId = [[thePart contentId] stringValue]; body = [thePart body]; filename = [(NGMimeContentDispositionHeaderField *)[thePart headerForKey: @"content-disposition"] filename]; @@ -906,6 +915,7 @@ - (void) _fileAttachmentsFromPart: (id) thePart currentInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: filename, @"filename", mimeType, @"mimetype", + bodyId, @"bodyid", nil]; [self saveAttachment: body withMetadata: currentInfo]; @@ -919,7 +929,7 @@ - (void) _fileAttachmentsFromPart: (id) thePart parts = [thePart parts]; for (i = 0; i < [parts count]; i++) { - [self _fileAttachmentsFromPart: [parts objectAtIndex: i]]; + [self _fileAttachmentsFromPart: [parts objectAtIndex: i] onlyImages: onlyImages]; } } } @@ -928,7 +938,7 @@ - (void) _fileAttachmentsFromPart: (id) thePart // // // -- (void) _fetchAttachmentsFromEncryptedMail: (SOGoMailObject *) sourceMail +- (void) _fetchAttachmentsFromEncryptedMail: (SOGoMailObject *) sourceMail onlyImages: (BOOL) onlyImages { NSData *certificate; @@ -941,7 +951,7 @@ - (void) _fetchAttachmentsFromEncryptedMail: (SOGoMailObject *) sourceMail NGMimeMessage *m; m = [[sourceMail content] messageFromEncryptedDataAndCertificate: certificate]; - [self _fileAttachmentsFromPart: [m body]]; + [self _fileAttachmentsFromPart: [m body] onlyImages: onlyImages]; } } @@ -949,12 +959,12 @@ - (void) _fetchAttachmentsFromEncryptedMail: (SOGoMailObject *) sourceMail // // // -- (void) _fetchAttachmentsFromOpaqueSignedMail: (SOGoMailObject *) sourceMail +- (void) _fetchAttachmentsFromOpaqueSignedMail: (SOGoMailObject *) sourceMail onlyImages: (BOOL) onlyImages { NGMimeMessage *m; m = [[sourceMail content] messageFromOpaqueSignedData]; - [self _fileAttachmentsFromPart: [m body]]; + [self _fileAttachmentsFromPart: [m body] onlyImages: onlyImages]; } @@ -973,7 +983,7 @@ - (void) fetchMailForEditing: (SOGoMailObject *) sourceMail [sourceMail fetchCoreInfos]; - [self _fetchAttachmentsFromMail: sourceMail]; + [self _fetchAttachmentsFromMail: sourceMail onlyImages: NO]; info = [NSMutableDictionary dictionaryWithCapacity: 16]; subject = [sourceMail subject]; if ([subject length] > 0) @@ -1057,6 +1067,22 @@ - (void) fetchMailForReplying: (SOGoMailObject *) sourceMail [self setSourceIMAP4ID: [[sourceMail nameInContainer] intValue]]; [self setSourceFolderWithMailObject: sourceMail]; + + ud = [[context activeUser] userDefaults]; + // TODO: Change mailMessageForwarding for reply + if ([[ud mailMessageForwarding] isEqualToString: @"inline"]) + { + [self setText: [sourceMail contentForInlineForward]]; + if ([sourceMail isEncrypted]) + [self _fetchAttachmentsFromEncryptedMail: sourceMail onlyImages: YES]; + else if ([sourceMail isOpaqueSigned]) + [self _fetchAttachmentsFromOpaqueSignedMail: sourceMail onlyImages: YES]; + else + [self _fetchAttachmentsFromMail: sourceMail onlyImages: YES]; + } + + [self save]; + [self storeInfo]; } @@ -1090,15 +1116,16 @@ - (void) fetchMailForForwarding: (SOGoMailObject *) sourceMail /* attach message */ ud = [[context activeUser] userDefaults]; + if ([[ud mailMessageForwarding] isEqualToString: @"inline"]) { [self setText: [sourceMail contentForInlineForward]]; if ([sourceMail isEncrypted]) - [self _fetchAttachmentsFromEncryptedMail: sourceMail]; + [self _fetchAttachmentsFromEncryptedMail: sourceMail onlyImages: NO]; else if ([sourceMail isOpaqueSigned]) - [self _fetchAttachmentsFromOpaqueSignedMail: sourceMail]; + [self _fetchAttachmentsFromOpaqueSignedMail: sourceMail onlyImages: NO]; else - [self _fetchAttachmentsFromMail: sourceMail]; + [self _fetchAttachmentsFromMail: sourceMail onlyImages: NO]; } else { @@ -1195,7 +1222,7 @@ - (NSException *) saveAttachment: (NSData *) _attach withMetadata: (NSMutableDictionary *) metadata { NSFileManager *fm; - NSString *p, *pmime, *name, *baseName, *extension, *mimeType; + NSString *p, *pmime, *pbodyId, *name, *baseName, *extension, *mimeType, *bodyId; int i; if (![_attach isNotNull]) @@ -1216,6 +1243,7 @@ - (NSException *) saveAttachment: (NSData *) _attach fm = [NSFileManager defaultManager]; p = [self pathToAttachmentWithName: name]; i = 1; + bodyId = nil; while ([fm isReadableFileAtPath: p]) { @@ -1244,6 +1272,18 @@ - (NSException *) saveAttachment: (NSData *) _attach reason: @"Could not write attachment to draft!"]; } } + + bodyId = [metadata objectForKey: @"bodyId"]; + if ([bodyId length] > 0) + { + pbodyId = [self pathToAttachmentWithName: [NSString stringWithFormat: @".%@.bodyid", name]]; + if (![[bodyId dataUsingEncoding: NSUTF8StringEncoding] writeToFile: pbodyId atomically: YES]) + { + [[NSFileManager defaultManager] removeFileAtPath: p handler: nil]; + return [NSException exceptionWithHTTPStatus: 500 /* Server Error */ + reason: @"Could not write body idattachment to draft!"]; + } + } return nil; /* everything OK */ } @@ -1381,6 +1421,27 @@ - (NSString *) contentTypeForAttachmentWithName: (NSString *) _name return s; } +- (NSString *) bodyIdForAttachmentWithName: (NSString *) _name +{ + NSString *s, *p; + NSData *bodyIdData; + + p = [self pathToAttachmentWithName: [NSString stringWithFormat: @".%@.bodyid", _name]]; + bodyIdData = [NSData dataWithContentsOfFile: p]; + if (bodyIdData) + { + s = [[NSString alloc] initWithData: bodyIdData + encoding: NSUTF8StringEncoding]; + [s autorelease]; + } + else + { + s = nil; + } + + return s; +} + - (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name andContentType: (NSString *) _type { @@ -1441,6 +1502,9 @@ - (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name else if ([s hasPrefix: @"message/rfc822"]) attachAsRFC822 = YES; } + if ((s = [self bodyIdForAttachmentWithName:_name]) != nil) { + [map setObject: s forKey: @"content-id"]; + } if ((s = [self contentDispositionForAttachmentWithName: _name andContentType: s])) { NGMimeContentDispositionHeaderField *o; diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index e8ed39697b..6db87bb377 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -91,7 +91,9 @@ SOGo_HEADER_FILES = \ SOGoTextTemplateFile.h \ SOGoZipArchiver.h \ \ - JWT.h + JWT.h \ + \ + NGMimeBodyPart+SOGo.h all:: @touch SOGoBuild.m @@ -176,7 +178,9 @@ SOGo_OBJC_FILES = \ SOGoTextTemplateFile.m \ SOGoZipArchiver.m \ \ - JWT.m + JWT.m \ + \ + NGMimeBodyPart+SOGo.m SOGo_C_FILES += lmhash.c aes.c crypt_blowfish.c pkcs5_pbkdf2.c diff --git a/SoObjects/SOGo/NGMimeBodyPart+SOGo.h b/SoObjects/SOGo/NGMimeBodyPart+SOGo.h new file mode 100644 index 0000000000..213a836783 --- /dev/null +++ b/SoObjects/SOGo/NGMimeBodyPart+SOGo.h @@ -0,0 +1,35 @@ +/* NGMimeBodyPart+SOGo.h - this file is part of SOGo + * + * Copyright (C) 2023 Alinto + * + * Author: Sébastien Mizrahi + * + * 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 NGMIMEBODYPART_SOGO_H +#define NGMIMEBODYPART_SOGO_H + +#import + +@interface NGMimeBodyPart (SOGoExtensions) + ++ (NSArray *) imageMimeTypes; +- (BOOL) isImage; + +@end + +#endif /* NGMIMEBODYPART_SOGO_H */ diff --git a/SoObjects/SOGo/NGMimeBodyPart+SOGo.m b/SoObjects/SOGo/NGMimeBodyPart+SOGo.m new file mode 100644 index 0000000000..d160273a2f --- /dev/null +++ b/SoObjects/SOGo/NGMimeBodyPart+SOGo.m @@ -0,0 +1,41 @@ +/* NGMimeBodyPart+SOGo.m - this file is part of SOGo + * + * Copyright (C) 2023 Alinto + * + * Author: Sébastien Mizrahi + * + * 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 "NGMimeBodyPart+SOGo.h" + +#import +#import + +@implementation NGMimeBodyPart (SOGoExtensions) + ++ (NSArray *) imageMimeTypes +{ + return [NSArray arrayWithObjects: @"image/png", @"image/gif", @"image/tiff" + , @"image/jpeg", @"image/bmp", @"image/svg+xml", @"image/webp", nil]; +} + +- (BOOL) isImage +{ + return [[NGMimeBodyPart imageMimeTypes] containsObject: [[self contentType] stringValue]]; +} + +@end diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index 9f451b9eef..a03e7901e3 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -42,6 +42,7 @@ #import #import #import +#import #import #import #import @@ -702,6 +703,34 @@ - (BOOL) hasAttachments return [[self attachmentAttrs] count] > 0 ? YES : NO; } +/* + * This method replace cid images in body with base64 inline image data + */ +- (void) setBase64ImagesInText:(SOGoDraftObject *) draft +{ + NSString *contentId, *lText; + NGMimeBodyPart *mime; + + if ([self isHTML] && [[draft fetchAttachmentAttrs] count] > 0) { + for (NSDictionary *draftFileAttachement in [draft fetchAttachmentAttrs]) { + mime = [draftFileAttachement objectForKey: @"part"]; + if ([mime isImage]) { + contentId = [mime contentId]; + contentId = [contentId stringByReplacingOccurrencesOfString: @"<" withString: @"cid:"]; + contentId = [contentId stringByReplacingOccurrencesOfString: @">" withString: @""]; + + if ([[mime encoding] isEqualToString: @"base64"]) { + lText = [text stringByReplacingOccurrencesOfString: contentId + withString: [NSString stringWithFormat: @"data:%@;base64,%@", + [[mime contentType] stringValue], + [NSString stringWithUTF8String: [[mime body] bytes]]]]; + [self setText: lText]; + } + } + } + } +} + - (id ) editAction { id response; @@ -720,7 +749,6 @@ - (BOOL) hasAttachments data = [NSMutableDictionary dictionaryWithObjectsAndKeys: [self localeCode], @"locale", [NSNumber numberWithBool: [self isHTML]], @"isHTML", - text, @"text", nil]; if ((value = [self from])) [data setObject: value forKey: @"from"]; @@ -737,6 +765,10 @@ - (BOOL) hasAttachments if ((value = [self attachmentAttrs])) [data setObject: value forKey: @"attachmentAttrs"]; + [self setBase64ImagesInText: co]; + [data setObject: text forKey: @"text"]; + + response = [self responseWithStatus: 200 andString: [data jsonRepresentation]];