From d02a97468e5f4625b547868c0a489facc3be4973 Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Tue, 20 Feb 2018 11:15:46 -0500 Subject: [PATCH] (fix) EAS with drafts with attachments --- ActiveSync/NGDOMElement+ActiveSync.m | 42 +++++- ActiveSync/SOGoActiveSyncDispatcher+Sync.m | 78 +++++++++- ActiveSync/SOGoMailObject+ActiveSync.h | 1 + ActiveSync/SOGoMailObject+ActiveSync.m | 162 +++++++++++++++++++-- NEWS | 1 + 5 files changed, 263 insertions(+), 21 deletions(-) diff --git a/ActiveSync/NGDOMElement+ActiveSync.m b/ActiveSync/NGDOMElement+ActiveSync.m index 61a6015d0..6477508a4 100644 --- a/ActiveSync/NGDOMElement+ActiveSync.m +++ b/ActiveSync/NGDOMElement+ActiveSync.m @@ -34,6 +34,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import static NSArray *asElementArray = nil; +static NSArray *considerAsSameArray = nil; @implementation NGDOMElement (ActiveSync) @@ -101,7 +102,44 @@ static NSArray *asElementArray = nil; int i, count; if (!asElementArray) - asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", nil]; + asElementArray = [[NSArray alloc] initWithObjects: @"Attendee", @"Category", @"Exception", @"Add", @"Delete", nil]; + +// FIXME +/* if the client sends + + +... + + +... + + + +the result is a NSarray i.e. {add1; add2; ...} + +if the client sends + + + +... + + +... + + +... + + +... + + + + +the result is a NSDictionary ie. Add = { }; Delete = { }; the dictionary would contain only one entry for add +*/ + if (!considerAsSameArray) + considerAsSameArray = [[NSArray alloc] initWithObjects: @"Add", @"Delete", nil]; + data = [NSMutableDictionary dictionary]; @@ -151,7 +189,7 @@ static NSArray *asElementArray = nil; if (!innerTag) innerTag = [innerElement tagName]; - if ([innerTag isEqualToString: [innerElement tagName]]) + if ([innerTag isEqualToString: [innerElement tagName]] || [considerAsSameArray containsObject: innerTag]) { if ([(id)innerElement isTextNode]) [innerElements addObject: [(NGDOMElement *)innerElement textValue]]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index f6e157ea0..0f3dcb16e 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -311,8 +311,34 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. anAddition = [additions objectAtIndex: i]; is_new = YES; - clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] lastObject] textValue]; + +/* +FIXME + + 338 + + <foot@bar.com> + + test + foo@bar.com + 1 + 1 + + + 152-ab557915-8451-49a7-a9c6-a9ac153021ad + + +-> lastObject returns the ClientId in Attachments element -> try with objectAtIndex: 0 -> is this correct? +*/ + //clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] lastObject] textValue]; + clientId = [[(id)[anAddition getElementsByTagName: @"ClientId"] objectAtIndex: 0] textValue]; + allValues = [NSMutableDictionary dictionaryWithDictionary: [[(id)[anAddition getElementsByTagName: @"ApplicationData"] lastObject] applicationData]]; + + // FIXME: ignore the elements of Attachemnts - above (id)[theDocumentElement getElementsByTagName: @"Add"]; return any elements instead of only the direct childs of the element .. + if (![allValues count]) + continue; + switch (theFolderType) { @@ -367,23 +393,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. case ActiveSyncMailFolder: default: { - // Support SMS to Exchange eMail sync. + // Support Draft Mail/SMS to Exchange eMail sync. NSString *serverId; + NSMutableString *s; + NSDictionary *result; + NSNumber *modseq; + + serverId = nil; + s = [NSMutableString string]; + sogoObject = [SOGoMailObject objectWithName: @"Mail" inContainer: theCollection]; - serverId = [sogoObject storeMail: allValues inContext: context]; + serverId = [sogoObject storeMail: allValues inBuffer: s inContext: context]; if (serverId) { + sogoObject = [theCollection lookupName: serverId inContext: context acquire: 0]; + [sogoObject takeActiveSyncValues: allValues inContext: context]; + // Everything is fine, lets generate our response - // serverId = clientId - There is no furhter processing after adding the SMS to the inbox. [theBuffer appendString: @""]; [theBuffer appendFormat: @"%@", clientId]; [theBuffer appendFormat: @"%@", serverId]; [theBuffer appendFormat: @"%d", 1]; + [theBuffer appendString: s]; [theBuffer appendString: @""]; + folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; syncCache = [folderMetadata objectForKey: @"SyncCache"]; - [syncCache setObject: @"0" forKey: serverId]; + + result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]]; + modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"]; + + [syncCache setObject: [modseq stringValue] forKey: serverId]; + [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; continue; @@ -670,6 +712,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NSDictionary *result; NSNumber *modseq; + // Process an update to a Draft Mail. + if ([allChanges objectForKey: @"Body"]) + { + NSString *serverId; + NSMutableString *s; + + serverId = nil; + s = [NSMutableString string]; + + serverId = [sogoObject storeMail: allChanges inBuffer: s inContext: context]; + if (serverId) + { + // we delete the original email - next sync will update the client with the new mail + [sogoObject delete]; + sogoObject = [theCollection lookupName: serverId inContext: context acquire: 0]; + } + } + [sogoObject takeActiveSyncValues: allChanges inContext: context]; result = [sogoObject fetchParts: [NSArray arrayWithObject: @"MODSEQ"]]; @@ -684,7 +744,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [theBuffer appendString: @""]; [theBuffer appendFormat: @"%@", origServerId]; - [theBuffer appendFormat: @"%d", 1]; + + // A body element is sent only for draft mails - status 8 will delete the mail on the client - the next sync update fetch the new mail + if ([allChanges objectForKey: @"Body"] && theFolderType == ActiveSyncMailFolder) + [theBuffer appendFormat: @"%d", 8]; + else + [theBuffer appendFormat: @"%d", 1]; + [theBuffer appendString: @""]; } } diff --git a/ActiveSync/SOGoMailObject+ActiveSync.h b/ActiveSync/SOGoMailObject+ActiveSync.h index dc1633fbe..c50e41be7 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.h +++ b/ActiveSync/SOGoMailObject+ActiveSync.h @@ -43,6 +43,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - (void) takeActiveSyncValues: (NSDictionary *) theValues inContext: (WOContext *) context; - (NSString *) storeMail: (NSDictionary *) theValues + inBuffer: (NSMutableString *) theBuffer inContext: (WOContext *) _context; @end diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index b120340e4..13f643fe2 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -65,6 +65,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import +#import #include "iCalTimeZone+ActiveSync.h" #include "NSData+ActiveSync.h" @@ -1449,18 +1450,22 @@ struct GlobalObjectId { } - (NSString *) storeMail: (NSDictionary *) theValues + inBuffer: (NSMutableString *) theBuffer inContext: (WOContext *) _context { - NSString *dateReceived, *folder, *s; + NSString *dateReceived, *folder, *s, *serverId, *messageId; NGMimeMessageGenerator *generator; - NGMimeMessage *bounceMessage; + NGMimeMessage *draftMessage; + NGMimeBodyPart *bodyPart; + NGMimeMultipartBody *body; NSDictionary *identity; NGMutableHashMap *map; NGImap4Client *client; NSData *message_data; + NSArray *attachmentKeys; - id o, result; - int bodyType; + id o, a, result, attachments; + int bodyType, i; identity = [[context activeUser] primaryIdentity]; message_data = nil; @@ -1480,11 +1485,21 @@ struct GlobalObjectId { map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; [map setObject: [NSString stringWithFormat: @"%@ <%@>", [identity objectForKey: @"fullName"], [identity objectForKey: @"email"]] forKey: @"from"]; + if ((o = [theValues objectForKey: @"To"])) [map setObject: o forKey: @"to"]; + if ((o = [theValues objectForKey: @"Cc"])) + [map setObject: o forKey: @"cc"]; + + if ((o = [theValues objectForKey: @"Bcc"])) + [map setObject: o forKey: @"bcc"]; + + if ((o = [theValues objectForKey: @"Reply-To"])) + [map setObject: o forKey: @"Reply-To"]; + if ((o = [theValues objectForKey: @"Subject"])) - [map setObject: o forKey: @"subject"]; + [map setObject: o forKey: @"subject"]; o = [[theValues objectForKey: @"DateReceived"] calendarDate]; @@ -1510,17 +1525,114 @@ struct GlobalObjectId { #endif [map setObject: dateReceived forKey: @"date"]; - [map setObject: [NSString generateMessageID] forKey: @"message-id"]; - [map setObject: (bodyType == 1 ? @"text/plain; charset=utf-8" : @"text/html; charset=utf-8") - forKey: @"content-type"]; - [map setObject: @"quoted-printable" forKey: @"content-transfer-encoding"]; - bounceMessage = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; + messageId = [NSString generateMessageID]; + [map setObject: messageId forKey: @"message-id"]; - [bounceMessage setBody: [[NSString stringWithFormat: @"%@", [[theValues objectForKey: @"Body"] objectForKey: @"Data"] ] dataUsingEncoding: NSUTF8StringEncoding]]; + attachmentKeys = [self fetchFileAttachmentKeys]; + + if ((attachments = [theValues objectForKey: @"Attachments"]) || [attachmentKeys count]) + { + [map setObject: @"multipart/mixed" forKey: @"content-type"]; + draftMessage = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; + body = [[[NGMimeMultipartBody alloc] initWithPart: draftMessage] autorelease]; + + map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; + + [map setObject: (bodyType == 1 ? @"text/plain; charset=utf-8" : @"text/html; charset=utf-8") + forKey: @"content-type"]; + [map setObject: @"quoted-printable" forKey: @"content-transfer-encoding"]; + + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; + + [bodyPart setBody: [[NSString stringWithFormat: @"%@", [[theValues objectForKey: @"Body"] objectForKey: @"Data"] ] dataUsingEncoding: NSUTF8StringEncoding]]; + + [body addBodyPart: bodyPart]; + + // Add attachments from the original mail - skip deletions + if ([attachmentKeys count]) + { + id currentAttachment; + NGHashMap *response; + NSData *bodydata; + NSArray *paths; + NGMimeFileData *fdata; + int i, ii; + BOOL found; + + paths = [attachmentKeys keysWithFormat: @"BODY[%{path}]"]; + response = [[self fetchParts: paths] objectForKey: @"RawResponse"]; + + for (i = 0; i < [attachmentKeys count]; i++) + { + currentAttachment = [attachmentKeys objectAtIndex: i]; + found = NO; + for (ii = 0; ii < [attachments count]; ii++) + { + // no ClientId means its a deletio + if (![[attachments objectAtIndex: ii] objectForKey: @"ClientId"] && + [[[[attachments objectAtIndex: ii] objectForKey: @"FileReference"] lastPathComponent] isEqualToString: [currentAttachment objectForKey: @"path"]]) + { + found = YES; + break; + } + } + + // skip deletions + if (found) + continue; + + bodydata = [[[response objectForKey: @"fetch"] objectForKey: [NSString stringWithFormat: @"body[%@]", [currentAttachment objectForKey: @"path"]]] valueForKey: @"data"]; + + map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; + [map setObject: [currentAttachment objectForKey: @"mimetype"] forKey: @"content-type"]; + [map setObject: [currentAttachment objectForKey: @"encoding"] forKey: @"content-transfer-encoding"]; + [map addObject: [NSString stringWithFormat: @"attachment; filename=\"%@\"", [currentAttachment objectForKey: @"filename"]] forKey: @"content-disposition"]; + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader: map] autorelease]; + + fdata = [[NGMimeFileData alloc] initWithBytes:[bodydata bytes] length:[bodydata length]]; + [bodyPart setBody: fdata]; + RELEASE(fdata); + [body addBodyPart: bodyPart]; + } + } + + // add new attachments + for (i = 0; i < [attachments count]; i++) + { + + map = [[[NGMutableHashMap alloc] initWithCapacity: 1] autorelease]; + a = [attachments objectAtIndex: i]; + + // no ClientId means its a deletion + if (![a objectForKey: @"ClientId"]) + continue; + + [map setObject: @"application/octet-stream" forKey: @"content-type"]; // FIXME ?? can we guess the right content-type + [map setObject: @"base64" forKey: @"content-transfer-encoding"]; + [map setObject: [NSString stringWithFormat: @"attachment; filename=\"%@\"", [a objectForKey: @"DisplayName"]] forKey: @"content-disposition"]; + + bodyPart = [[[NGMimeBodyPart alloc] initWithHeader:map] autorelease]; + [bodyPart setBody: [[a objectForKey: @"Content"] stringByDecodingBase64]]; + [body addBodyPart: bodyPart]; + } + + + [draftMessage setBody: body]; + } + else + { + [map setObject: (bodyType == 1 ? @"text/plain; charset=utf-8" : @"text/html; charset=utf-8") + forKey: @"content-type"]; + [map setObject: @"quoted-printable" forKey: @"content-transfer-encoding"]; + + draftMessage = [[[NGMimeMessage alloc] initWithHeader: map] autorelease]; + + [draftMessage setBody: [[NSString stringWithFormat: @"%@", [[theValues objectForKey: @"Body"] objectForKey: @"Data"] ] dataUsingEncoding: NSUTF8StringEncoding]]; + } generator = [[[NGMimeMessageGenerator alloc] init] autorelease]; - message_data = [generator generateMimeFromPart: bounceMessage]; + message_data = [generator generateMimeFromPart: draftMessage]; } if (message_data) @@ -1540,8 +1652,32 @@ struct GlobalObjectId { toFolder: folder withFlags: [NSArray arrayWithObjects: @"draft", nil]]; + serverId = [NSString stringWithFormat: @"%d", [self IMAP4IDFromAppendResult: result]]; + + [theBuffer appendString: @""]; + [theBuffer appendFormat: @"%@", [[messageId dataUsingEncoding: NSUTF8StringEncoding] activeSyncRepresentationInContext: context]]; + + if ([attachments count]) + { + [theBuffer appendString: @""]; + + for (i = 0; i < [attachments count]; i++) + { + a = [attachments objectAtIndex: i]; + + [theBuffer appendString: @""]; + [theBuffer appendFormat: @"%@", [a objectForKey: @"ClientId"]]; + [theBuffer appendFormat: @"mail/%@/%@/%d", [[[self container] relativeImap4Name] stringByEscapingURL], serverId, i+2]; + [theBuffer appendString: @""]; + } + + [theBuffer appendString: @""]; + } + + [theBuffer appendString: @""]; + if ([[result objectForKey: @"result"] boolValue]) - return [NSString stringWithFormat: @"%d", [self IMAP4IDFromAppendResult: result]]; + return serverId; } return nil; diff --git a/NEWS b/NEWS index f8ca90e97..cb3f9684e 100644 --- a/NEWS +++ b/NEWS @@ -57,6 +57,7 @@ Bug fixes - [eas] avoid sync requests for shared folders every second (#4275) - [eas] we skip the organizer from the attendees list (#4402) - [eas] correctly handle all-day events with EAS v16 (#4397) + - [eas] fixed EAS save in drafts with attachments 3.2.10 (2017-07-05) -------------------