(fix) properly XML escape wide characters (fixes #3616)

pull/206/head
Ludovic Marcotte 2016-04-06 11:19:18 -04:00
parent 17ebfce2c7
commit 35d1cab856
9 changed files with 108 additions and 104 deletions

View File

@ -43,99 +43,6 @@ static NSArray *easCommandParameters = nil;
@implementation NSString (ActiveSync) @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 -> � �
//
// We avoir naming it like the one in SOPE since if the ActiveSync
// bundle is loaded, it'll overwrite the one provided by SOPE.
//
- (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] < 0x20 || 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] initWithBytesNoCopy: buf
length: (j*sizeof(wchar_t))
encoding: NSUTF32StringEncoding
freeWhenDone: YES];
return [self autorelease];
}
- (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType - (NSString *) sanitizedServerIdWithType: (SOGoMicrosoftActiveSyncFolderType) folderType
{ {
if (folderType == ActiveSyncEventFolder) if (folderType == ActiveSyncEventFolder)
@ -155,7 +62,7 @@ static NSArray *easCommandParameters = nil;
- (NSString *) activeSyncRepresentationInContext: (WOContext *) context - (NSString *) activeSyncRepresentationInContext: (WOContext *) context
{ {
return [self _stringByEscapingXMLStringUsingCharacters]; return [self safeStringByEscapingXMLString];
} }
- (int) activeSyncFolderType - (int) activeSyncFolderType

View File

@ -2140,7 +2140,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
[content addObject: davElementWithContent (@"request-status", XMLNS_CALDAV, [content addObject: davElementWithContent (@"request-status", XMLNS_CALDAV,
@"2.0;Success")]; @"2.0;Success")];
[content addObject: davElementWithContent (@"calendar-data", XMLNS_CALDAV, [content addObject: davElementWithContent (@"calendar-data", XMLNS_CALDAV,
[calendarData stringByEscapingXMLString])]; [calendarData safeStringByEscapingXMLString])];
} }
else else
[content addObject: [content addObject:

View File

@ -498,7 +498,7 @@
methodSel = SOGoSelectorForPropertyGetter (*currentProperty); methodSel = SOGoSelectorForPropertyGetter (*currentProperty);
if (methodSel && [ldifEntry respondsToSelector: methodSel]) if (methodSel && [ldifEntry respondsToSelector: methodSel])
*currentValue = [[ldifEntry performSelector: methodSel] *currentValue = [[ldifEntry performSelector: methodSel]
stringByEscapingXMLString]; safeStringByEscapingXMLString];
currentProperty++; currentProperty++;
currentValue++; currentValue++;
} }

View File

@ -28,6 +28,7 @@
#import <DOM/DOMNode.h> #import <DOM/DOMNode.h>
#import <SaxObjC/SaxObjC.h> #import <SaxObjC/SaxObjC.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoUser.h> #import <SOGo/SOGoUser.h>
#import <SOGo/WOResponse+SOGo.h> #import <SOGo/WOResponse+SOGo.h>
@ -67,7 +68,7 @@
[component davEntityTag]]; [component davEntityTag]];
[r appendContentString: etagLine]; [r appendContentString: etagLine];
[r appendContentString: @"<C:address-data>"]; [r appendContentString: @"<C:address-data>"];
contactString = [[component contentAsString] stringByEscapingXMLString]; contactString = [[component contentAsString] safeStringByEscapingXMLString];
[r appendContentString: contactString]; [r appendContentString: contactString];
[r appendContentString: @"</C:address-data>" [r appendContentString: @"</C:address-data>"
@"<C:addressbook-data>"]; @"<C:addressbook-data>"];

View File

@ -54,6 +54,7 @@
/* Unicode safety */ /* Unicode safety */
- (NSString *) safeString; - (NSString *) safeString;
- (NSString *) safeStringByEscapingXMLString;
/* JSON */ /* JSON */
- (NSString *) jsonRepresentation; - (NSString *) jsonRepresentation;

View File

@ -319,6 +319,100 @@ static int cssEscapingCount;
return AUTORELEASE(s); return AUTORELEASE(s);
} }
//
// 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 -> &#128515; wrong: unichar -> &#55357; &#56835;
//
// We avoid naming it like the one in SOPE since if the ActiveSync
// bundle is loaded, it'll overwrite the one provided by SOPE.
//
- (NSString *) safeStringByEscapingXMLString {
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] < 0x20 || 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] initWithBytesNoCopy: buf
length: (j*sizeof(wchar_t))
encoding: NSUTF32StringEncoding
freeWhenDone: YES];
return [self autorelease];
}
- (NSString *) jsonRepresentation - (NSString *) jsonRepresentation
{ {
NSString *cleanedString; NSString *cleanedString;

View File

@ -2076,7 +2076,7 @@ static NSArray *childRecordFields = nil;
methodSel = SOGoSelectorForPropertyGetter (*currentProperty); methodSel = SOGoSelectorForPropertyGetter (*currentProperty);
if (methodSel && [sogoObject respondsToSelector: methodSel]) if (methodSel && [sogoObject respondsToSelector: methodSel])
*currentValue = [[sogoObject performSelector: methodSel] *currentValue = [[sogoObject performSelector: methodSel]
stringByEscapingXMLString]; safeStringByEscapingXMLString];
currentProperty++; currentProperty++;
currentValue++; currentValue++;
} }

View File

@ -1255,7 +1255,7 @@
if (!cn) if (!cn)
cn = user; cn = user;
[userRecord appendFormat: @"<displayName>%@</displayName>", [userRecord appendFormat: @"<displayName>%@</displayName>",
[cn stringByEscapingXMLString]]; [cn safeStringByEscapingXMLString]];
} }
if (![params containsObject: @"noemail"]) if (![params containsObject: @"noemail"])

View File

@ -37,6 +37,7 @@
#import "NSArray+Utilities.h" #import "NSArray+Utilities.h"
#import "NSDictionary+Utilities.h" #import "NSDictionary+Utilities.h"
#import "NSString+Utilities.h"
#import "SOGoUserManager.h" #import "SOGoUserManager.h"
#import "SOGoPermissions.h" #import "SOGoPermissions.h"
#import "SOGoSystemDefaults.h" #import "SOGoSystemDefaults.h"
@ -271,7 +272,7 @@
[r appendContentString: @"<D:status>HTTP/1.1 200 OK</D:status>"]; [r appendContentString: @"<D:status>HTTP/1.1 200 OK</D:status>"];
[r appendContentString: @"<D:prop><D:displayname>"]; [r appendContentString: @"<D:prop><D:displayname>"];
data = [currentFolder objectForKey: @"displayName"]; data = [currentFolder objectForKey: @"displayName"];
[r appendContentString: [data stringByEscapingXMLString]]; [r appendContentString: [data safeStringByEscapingXMLString]];
[r appendContentString: @"</D:displayname></D:prop></D:propstat>"]; [r appendContentString: @"</D:displayname></D:prop></D:propstat>"];
/* Remove this once extensions 0.8x are no longer used */ /* Remove this once extensions 0.8x are no longer used */
@ -284,12 +285,12 @@
ownerUser = [SOGoUser userWithLogin: [currentFolder objectForKey: @"owner"] ownerUser = [SOGoUser userWithLogin: [currentFolder objectForKey: @"owner"]
roles: nil]; roles: nil];
data = [ownerUser cn]; data = [ownerUser cn];
[r appendContentString: [data stringByEscapingXMLString]]; [r appendContentString: [data safeStringByEscapingXMLString]];
[r appendContentString: @"</ownerdisplayname>"]; [r appendContentString: @"</ownerdisplayname>"];
[r appendContentString: @"<D:displayname>"]; [r appendContentString: @"<D:displayname>"];
data = [currentFolder objectForKey: @"displayName"]; data = [currentFolder objectForKey: @"displayName"];
[r appendContentString: [data stringByEscapingXMLString]]; [r appendContentString: [data safeStringByEscapingXMLString]];
[r appendContentString: @"</D:displayname>"]; [r appendContentString: @"</D:displayname>"];
/* end of temporary compatibility hack */ /* end of temporary compatibility hack */
@ -421,14 +422,14 @@
[field stringByEscapingXMLString]]; [field stringByEscapingXMLString]];
field = [currentUser objectForKey: @"cn"]; field = [currentUser objectForKey: @"cn"];
[fetch appendFormat: @"<displayName>%@</displayName>", [fetch appendFormat: @"<displayName>%@</displayName>",
[field stringByEscapingXMLString]]; [field safeStringByEscapingXMLString]];
field = [currentUser objectForKey: @"c_email"]; field = [currentUser objectForKey: @"c_email"];
[fetch appendFormat: @"<email>%@</email>", [fetch appendFormat: @"<email>%@</email>",
[field stringByEscapingXMLString]]; [field stringByEscapingXMLString]];
field = [currentUser objectForKey: @"c_info"]; field = [currentUser objectForKey: @"c_info"];
if ([field length]) if ([field length])
[fetch appendFormat: @"<info>%@</info>", [fetch appendFormat: @"<info>%@</info>",
[field stringByEscapingXMLString]]; [field safeStringByEscapingXMLString]];
[fetch appendString: @"</user>"]; [fetch appendString: @"</user>"];
} }
} }