From 6bc471ad9a4dc35ec0789dd8efb73a6de5a6ea3f Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Wed, 10 Jun 2015 10:58:59 -0400 Subject: [PATCH] (fix) properly support big characters in EAS and fix encoding QP EAS error for Outlook (#3082) --- ActiveSync/NSString+ActiveSync.m | 94 ++++++++++++++++++++++++-- ActiveSync/SOGoMailObject+ActiveSync.m | 46 ++++++++++--- NEWS | 1 + 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/ActiveSync/NSString+ActiveSync.m b/ActiveSync/NSString+ActiveSync.m index a66f38252..7060d569c 100644 --- a/ActiveSync/NSString+ActiveSync.m +++ b/ActiveSync/NSString+ActiveSync.m @@ -46,6 +46,94 @@ static NSArray *easCommandParameters = nil; @implementation NSString (ActiveSync) +// +// This is a copy from NSString+XMLEscaping.m from SOPE. +// The difference here is that we use wchar_t instead of unichar. +// This is needed to get the rigth numeric character reference. +// e.g. SMILING FACE WITH OPEN MOUTH +// ok: wchar_t -> 😃 wrong: unichar -> � � +// +- (NSString *)stringByEscapingXMLStringUsingCharacters { + register unsigned i, len, j; + register wchar_t *buf; + const wchar_t *chars; + unsigned escapeCount; + + if ([self length] == 0) return @""; + + NSData *data = [self dataUsingEncoding:NSUTF32StringEncoding]; + chars = [data bytes]; + len = [data length]/4; + + /* check for characters to escape ... */ + for (i = 0, escapeCount = 0; i < len; i++) { + switch (chars[i]) { + case '&': case '"': case '<': case '>': case '\r': + escapeCount++; + break; + default: + if (chars[i] > 127) + escapeCount++; + break; + } + } + if (escapeCount == 0 ) { + /* nothing to escape ... */ + return [[self copy] autorelease]; + } + + buf = calloc((len + 5) + (escapeCount * 16), sizeof(wchar_t)); + for (i = 0, j = 0; i < len; i++) { + switch (chars[i]) { + /* escape special chars */ + case '\r': + buf[j] = '&'; j++; buf[j] = '#'; j++; buf[j] = '1'; j++; + buf[j] = '3'; j++; buf[j] = ';'; j++; + break; + case '&': + buf[j] = '&'; j++; buf[j] = 'a'; j++; buf[j] = 'm'; j++; + buf[j] = 'p'; j++; buf[j] = ';'; j++; + break; + case '"': + buf[j] = '&'; j++; buf[j] = 'q'; j++; buf[j] = 'u'; j++; + buf[j] = 'o'; j++; buf[j] = 't'; j++; buf[j] = ';'; j++; + break; + case '<': + buf[j] = '&'; j++; buf[j] = 'l'; j++; buf[j] = 't'; j++; + buf[j] = ';'; j++; + break; + case '>': + buf[j] = '&'; j++; buf[j] = 'g'; j++; buf[j] = 't'; j++; + buf[j] = ';'; j++; + break; + + default: + /* escape big chars */ + if (chars[i] > 127) { + unsigned char nbuf[32]; + unsigned int k; + + sprintf((char *)nbuf, "&#%i;", (int)chars[i]); + for (k = 0; nbuf[k] != '\0'; k++) { + buf[j] = nbuf[k]; + j++; + } + } + else if (chars[i] == 0x9 || chars[i] == 0xA || chars[i] == 0xD || chars[i] >= 0x20) { // ignore any unsupported control character + /* nothing to escape */ + buf[j] = chars[i]; + j++; + } + break; + } + } + + self = [[[NSString alloc] initWithBytes:buf length:(j) * sizeof(wchar_t) encoding:NSUTF32StringEncoding] autorelease]; + + if (buf) free(buf); + return self; +} + - (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType { if (folderType == ActiveSyncEventFolder) @@ -65,11 +153,7 @@ static NSArray *easCommandParameters = nil; - (NSString *) activeSyncRepresentationInContext: (WOContext *) context { - NSString *s; - - s = [self safeString]; - - return [s stringByEscapingHTMLString]; + return [self stringByEscapingXMLStringUsingCharacters]; } - (int) activeSyncFolderType diff --git a/ActiveSync/SOGoMailObject+ActiveSync.m b/ActiveSync/SOGoMailObject+ActiveSync.m index d3095a966..164abb6db 100644 --- a/ActiveSync/SOGoMailObject+ActiveSync.m +++ b/ActiveSync/SOGoMailObject+ActiveSync.m @@ -381,11 +381,6 @@ struct GlobalObjectId { if (s) { - // We sanitize the content immediately, in case we have non-UNICODE safe - // characters that would be re-encoded later in HTML entities and thus, - // ignore afterwards. - s = [s safeString]; - body = [s dataUsingEncoding: NSUTF8StringEncoding]; } @@ -866,10 +861,41 @@ struct GlobalObjectId { if (d) { + NSMutableData *sanitizedData; NSString *content; int len, truncated; - - content = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; + + // Outlook fails to decode quoted-printable (see #3082) if lines are not termined by CRLF. + const char *bytes; + char *mbytes; + int mlen; + + len = [d length]; + mlen = 0; + + sanitizedData = [NSMutableData dataWithLength: len*2]; + + bytes = [d bytes]; + mbytes = [sanitizedData mutableBytes]; + + while (len > 0) + { + if (*bytes == '\n' && *(bytes-1) != '\r' && mlen > 0) + { + *mbytes = '\r'; + mbytes++; + mlen++; + } + + *mbytes = *bytes; + mbytes++; bytes++; + len--; + mlen++; + } + + [sanitizedData setLength: mlen]; + + content = [[NSString alloc] initWithData: sanitizedData encoding: NSUTF8StringEncoding]; // FIXME: This is a hack. We should normally avoid doing this as we might get // broken encodings. We should rather tell that the data was truncated and expect @@ -879,15 +905,15 @@ struct GlobalObjectId { // for an "interesting" discussion around this. // if (!content) - content = [[NSString alloc] initWithData: d encoding: NSISOLatin1StringEncoding]; + content = [[NSString alloc] initWithData: sanitizedData encoding: NSISOLatin1StringEncoding]; AUTORELEASE(content); content = [content activeSyncRepresentationInContext: context]; truncated = 0; - + len = [content length]; - + if ([[[context request] headerForKey: @"MS-ASProtocolVersion"] isEqualToString: @"2.5"]) { [s appendFormat: @"%@", content]; diff --git a/NEWS b/NEWS index b06a4175c..950aa010a 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ Bug fixes - fixed some rare cornercases in multidomain configurations - properly escape folder after creation using EAS (#3237) - fixed potential organizer highjacking when using EAS (#3131) + - properly support big characters in EAS and fix encoding QP EAS error for Outlook (#3082) 2.3.0 (2015-06-01) -------------------