An application that I’m working on uses a SOAP web service to send user registration data to a server, which I implemented using Web Services Core Framework. After the user enters his details, I create a SOAP request to send it to the server:
NSDictionary* headers = [NSDictionary dictionaryWithObjectsAndKeys: soapaction, @"SOAPAction", "text/xml; charset=utf-8", @"Content-Type", nil];
fRef = WSMethodInvocationCreate((CFURLRef) url, (CFStringRef) method, (CFStringRef) kWSSOAP2001Protocol);
WSMethodInvocationSetProperty(fRef, kWSHTTPExtraHeaders, headers);
WSMethodInvocationSetProperty(fRef, kWSSOAPBodyEncodingStyle, kWSSOAPStyleDoc);
WSMethodInvocationSetProperty(fRef, kWSSOAPMethodNamespaceURI, Namespace);
WSMethodInvocationSetParameters(fRef, (CFDictionaryRef)params, nil);
fResult = (NSDictionary*)WSMethodInvocationInvoke(fRef);
This works perfectly unless the user enters multi-byte Asian characters. I immediately suspected that I was assuming somewhere that characters are one byte, but it turned out to be crashing deep inside CFNetwork code.
The stack trace always looked like this:
#0 0x94ee7372 in szone_error ()
#1 0x94e119fe in szone_free ()
#2 0x94e112cd in free ()
#3 0x938f98d8 in entityEncodeString ()
#4 0x938f2769 in SOAPProtocolHandler::serialize ()
#5 0x938f3715 in SOAPProtocolHandler::createRequest ()
#6 0x938f381d in SOAPWebServiceMethodInvocation::constructMessage ()
#7 0x938f1041 in HTTPClosureSource::initConnection ()
#8 0x938f16b6 in HTTPClosureSource::perform ()
#9 0x91df263f in CFRunLoopRunSpecific ()
#10 0x91df2cd8 in CFRunLoopRunInMode ()
#11 0x938f6753 in WSMethodInvocationInvoke ()
After lots of googling & experimentation, I came up with a fairly simple fix using WSMethodInvocationAddSerializationOverride to add a custom serialization function.
Right after I create the web service invocation, I added:
WSMethodInvocationAddSerializationOverride(fRef, CFStringGetTypeID(), _stringToXML, NULL);
My custom serialization function is very trivial:
static CFStringRef _stringToXML(WSMethodInvocationRef invocation, CFTypeRef obj, void *info)
{
if ((CFGetTypeID(obj) == CFStringGetTypeID()) && ![(NSString *)obj isEqualToString:@""])
{
NSString *result = [[NSString alloc] initWithFormat:@"<%%@>%@</%%@>", obj];
return (CFStringRef)result;
}
return NULL;
}
This simply returns an XML snippet such as <Name>Your name here</Name>.