(fix) properly XML escape wide characters (fixes #3616)
parent
17ebfce2c7
commit
35d1cab856
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>"];
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
|
|
||||||
/* Unicode safety */
|
/* Unicode safety */
|
||||||
- (NSString *) safeString;
|
- (NSString *) safeString;
|
||||||
|
- (NSString *) safeStringByEscapingXMLString;
|
||||||
|
|
||||||
/* JSON */
|
/* JSON */
|
||||||
- (NSString *) jsonRepresentation;
|
- (NSString *) jsonRepresentation;
|
||||||
|
|
|
@ -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 -> 😃 wrong: unichar -> � �
|
||||||
|
//
|
||||||
|
// 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;
|
||||||
|
|
|
@ -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++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
|
@ -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>"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue