iPhone - Strange behaviour when '&' is included in HTTP request body - iphone

I've got a very strange problem related to sending POST request from my iPhone app.
The app needs to send the HTTP post data to a third-party service. Request is XML and it'll get XML response. Here's my code for sending the request:
-(void)sendRequest:(NSString *)aRequest
{
//aRequest parameter contains the XML string to send.
//this string is already entity-encoded
isDataRequest = NO;
//the following line will created string REQUEST=<myxml>
NSString *httpBody = [NSString stringWithFormat:#"%#=%#",requestString,aRequest];
//I'm not sure what this next string is doing, frankly, as I didn't write this code initially
httpBody = [(NSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)httpBody, NULL, CFSTR("+"), kCFStringEncodingUTF8) autorelease];
NSData *aData = [httpBody dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:kOOURLRequest]] autorelease];
[request setHTTPBody:aData];
[request setHTTPMethod:#"POST"];
self.feedURLConnection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
}
This works perfectly well as long as the request XML doesn't contain & symbol, for example, this XML request:
<?xml version="1.0"?>
<request type="search" group="0" language="en" version="2.5.2">
<auth>
<serial>623E1579-AC18-571B-9022-3659764542E7</serial>
</auth>
<data>
<location>
<lattitude>51.528536</lattitude>
<longtitude>-0.108865</longtitude>
</location>
<search>archive</search>
</data>
</request>
is sent as expected and the correct response is received as expected.
However, when the request contains & character (specifically in the "search" element) - like so:
<?xml version="1.0"?>
<request type="search" group="0" language="en" version="2.5.2">
<auth>
<serial>623E1579-AC18-571B-9022-3659764542E7</serial>
</auth>
<data>
<location>
<lattitude>51.528536</lattitude>
<longtitude>-0.108865</longtitude>
</location>
<search>& archive</search>
</data>
</request>
Only everything up to the & character is sent to the server. The server doesn't seem to receive anything beyond this character. Note that I have a pretty much the same code working in an Android app and everything is working correctly, so it's not a problem on the server.
Any ideas how I can get this fixed would be greatly appreciated!

Thanks to Zaph's comment, I finally sorted it. I used WireShark to see what was actually sent to the server and discovered that the request wasn't encoded completely. In the final HTTP body the actual symbol & was present (part of &). This naturally didn't work very well on the server side, because it was receiving something like:
REQUEST=first_half_of_request&second_half_of_request
When the server decoded the POST variables, & was taken as the separator of variables, therefore the REQUEST variable was only set to the first_half_of_request - everything up to the & char.
The solution was quite simple. In line
httpBody = [(NSString*)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)httpBody, NULL, CFSTR("+"), kCFStringEncodingUTF8) autorelease];
replace CFSTR("+") with CFSTR("+&") to encode & as well. Now this, combined with entity encoding (& for &), resulted in the correct data being sent to the server and the correct response being received.

Related

connecting to WCF from iOS

Ugh. I have read a few similar questions here on SOF but so far none of those solutions have worked for me.
I am trying to connect an iOS7 client to a WCF web service. Let me go ahead and point out that I didn't write the WCF service and have never written one - so I'm pretty WCF stupid. As I understand it - the idea is basically create a SOAP xml packet, and send it over, and wait for an xml response. There is a test client running on .NET that works fine, so I had the guy who wrote that turn on Fiddler so I could see what the xml and request headers should look like. Mine now are identical to his, yet the only response I can muster from the server is:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><s:Fault><faultcode xmlns:a="http://schemas.microsoft.com/ws/2005/05/addressing/none">a:ActionNotSupported</faultcode><faultstring xml:lang="en-US">The message with Action 'http://www.tempuri.org/IFoolSpoon_SoWo_Service/Check_Item_test' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).</faultstring></s:Fault></s:Body></s:Envelope>
Since that is scrolling WAY off the screen - it's a ContractMismatch error - whatever that actually means.
Side challenge: in googling the above error message - it seems impossible for anyone to explain the term "Contract" without using the word "contract". ;) Every explanation I saw was basically "it's like...the contract - you know...like the contract between the client and server...it's like a contract." :(
Anyway, here is the relevant obj-c code, with names/urls changed to protect the guilty:
// construct a URL that will ask the service for what we want
NSURL *url = [NSURL URLWithString:#"http://mymachine.myisp.com/SOWO/MyService.svc"];
// build the SOAP envelope
NSString *soapMessage = [NSString stringWithFormat:
#"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
"<s:Body >\n"
"<Check_Item_test xmlns=\"http://derp.org/\">\n"
"<UPC>1090000021</UPC>\n"
"</Check_Item_test>\n"
"</s:Body>\n"
"</s:Envelope>\n"];
soapMessage = [soapMessage stringByReplacingOccurrencesOfString:#"\n" withString:#"\r\n"];
// put URL into a NSURLRequest
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength = [NSString stringWithFormat:#"%d", [soapMessage length]];
NSLog(#"length = %#", msgLength);
[req addValue: #"text/xml" forHTTPHeaderField:#"Content-type"];
[req addValue: #"http://www.tempuri.org/IServiceInterface/Check_Item_test"
forHTTPHeaderField:#"SOAPAction"];
[req addValue: msgLength forHTTPHeaderField:#"Content-Length"];
[req addValue:#"100-continue" forHTTPHeaderField:#"Expect"];
[req setHTTPMethod:#"POST"];
[req setHTTPBody: [soapMessage dataUsingEncoding:NSUTF8StringEncoding]];
Some notes about the above code. There is another developer calling the same service from Ruby and he mentioned a couple of things: changing the line endings to \r\n from \n was his suggestion, since he found out that was a problem for him. I also adjusted some header capitalizations: SOAPaction to SOAPAction, etc. So now I'm matching HIS xml/headers exactly too, but no luck.
My request looks like this (from an OSX Fiddler equivalent):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body >
<Check_Item_test xmlns="http://derp.org/">
<UPC>1090000021</UPC>
</Check_Item_test>
</s:Body>
</s:Envelope>
That seems to match every other client and all of them work save mine. So yeah - help? I am one degree away from the web.config for the service and I have access to the .svclog on the server. Interestingly enough - my requests aren't getting logged, even tho logMalformedRequests is set to true. Suggestions on where else to check for whatever bitbucket my requests are going into are appreciated.
So yeah - annoyed, frustrated, tired. For something that should be so simple, this has turned out to be a gigantic pain. It's probably something stupid, so perhaps I just need more eyes on it than my own.
TIA.
So the answer turned out to be: stop using SOAP.
I ended up rewriting the service endpoints as REST endpoints and all is fine and dandy.

How to submit a very-very long String to server via POST and get jSON response

I wish to send a very very long string(length of string is more than 10000) to the server and in return get the jSON response from the string.What is the best approach for the task. I am sending various parameters along with this very very long string.
Split down your long string to parts which can be send over one request. Create a json like this
{
"index":"0",
"length":"LENGTH_OF_STRING",
"string":"xsfsffwff.......",
//other json parameters
}
then you can send your string
The problem is that you're trying to put this all into a query parameter. Most servers have built-in limits for URLs, and for good reason.
There's nothing special about the body of an HTTP POST, so just send that up like you would anything else. Just make sure that you set the Content-Length header (since you know that; it might be covered by the HTTP library) and then just stream your data up. No need for any encoding or query params.
I don't know much about objective-c, but I'm sure there's a way to send data like this in an HTTP POST very simply. I've done this with Go and node.js, and both have simple ways of sending arbitrary data in a POST request body.
If you are using the ASI-Http classes , then you can send request like this
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:[WebService getAddPhoto]]];
[request addPostValue:[[imgArray objectAtIndex:i] valueForKey:#"vComments"] forKey:#"comment"];
NSData *imageData = UIImagePNGRepresentation([UIImage imageWithContentsOfFile:[[imgArray objectAtIndex:i] valueForKey:#"vPhoto"]]);
NSString *encodedString = [imageData base64EncodingWithLineLength:[imageData length]];
[request addPostValue:encodedString forKey:#"Photo"];
[request setDelegate:self];
[request startAsynchronous]

NSMutableURLRequest adding extra information to the body, making Content-Length incorrect

I'm using an NSMutableURLRequest to perform a simple file upload from iOS5 to a custom server. When I send the request, I have it print out the length of bodyContents, which contains the body of the request. For the particular request I am working on, it will say that the length is 46784. I don't set the content-length, since I found out it was doing that automatically, but I pull the info anyway in case I need it later. The headers being received by the server say that the content-length is 46784. When I do a character count on the body at the server end, it tells me that the length of the body is 46788, 4 too many. I ran another request, again, the request had 4 more characters than what I sent.
At first, I thought it had to be on the server side, just because it didn't make any sense. So I sent a request from a test program that I know works to the server, and checked the length of the body it sent against the content-length header and they were identical.
What is the best way to deal with this? Should I manually set the content-length to 4+[bodyContents length]? I don't think that would be what I would want to do, just because I shouldn't have to add extra space for data I don't know that I want.
Here is the code that sends the request
-(void)send:(id)delegate
{
bodyContents = [bodyContents stringByAppendingFormat:#"--%#--",boundry];
NSInteger length = [bodyContents length];
NSLog(#"Length is %i",length);
[request setHTTPBody:[bodyContents dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:delegate];
if(connection)
{
NSLog(#"Connection good");
}
else
{
NSLog(#"Connection bad");
}
}
I set the content-type property of the request in the init method of the class.
Any ideas?
When you specify an encoding, the size of the contents can change... get the size after you set the HTTPBody and then see if that adjusts things up by four bytes automatically.

Web service iPhone

I'm trying to connect to a web service, with the next code:
NSURL *soapURL;
WSMethodInvocationRef soapCall;
NSString *methodName;
NSMutableDictionary *params;
NSDictionary *result;
soapURL = [NSURL URLWithString:#"http://wicaweb2.intec.ugent.be:80/FaceTubeWebServiceService/FaceTubeWebService?WSDL"];
methodName = #"getMostViewed";
soapCall = WSMethodInvocationCreate((CFURLRef)soapURL,
(CFStri ngRef)methodName, kWSSOAP2001Protocol);
params = [NSMutableDictionary dictionaryWithCapacity:2];
[params setObject:#"1" forKey:#"arg0"];
[params setObject:#"all_time" forKey:#"arg1"];
NSArray *paramsOrder = [NSArray arrayWithObjects:#"arg0",#"arg1", nil];
WSMethodInvocationSetParameters(soapCall,
(CFDictionaryRef)params,(CFArrayRef)paramsOrder);
WSMethodInvocationSetProperty(soapCall,
kWSSOAPMethodNamespaceURI,
(CFTypeRef)#"http://webservice.facetube.wica.intec.ugent.be/");
result = (NSDictionary*)WSMethodInvocationInvoke(soapCall);
NSString *resultado = [result objectForKey: (NSString*)kWSMethodInvocationResult];
NSLog(#"Result:%#",resultado);
But I obtain the same reply, than if I don't send parameters.
I got this:
[Session started at 2009-07-07 22:01:53 +0200.]
2009-07-07 22:01:57.669 Hello_SOAP[6058:20b] Result:{
}
Exactly, seems like this but I will show you also the parameters that the method needs:
SOAP Request
> <?xml version="1.0" encoding="UTF-8"?>
> <soapenv:Envelope
> xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
> xmlns:xsd="http://www.w3.org/2001/XMLSchema"
> xmlns:ns1="http://webservice.facetube.wica.intec.ugent.be/">
> <soapenv:Body>
> <ns1:getMostViewed>
> <arg0>1</arg0>
> <arg1>all_time</arg1>
> </ns1:getMostViewed>
> </soapenv:Body> </soapenv:Envelope>
As you can see, the name of the parameters that I need are arg0 and arg1, for this I can't understand what is going wrong :)
Try posting the reply you get. It seems like you're parameters are incorrect, given that you get the same response as if you sent no parameters.
Danger. Are you are aware that WSMethodInvocationRef (or any of the SOAP helper classes) do not exist on the iPhone? (your question is tagged as iPhone).
It will compile for the simulator but not for the phone. I'd hate to see you waste much time getting this working if your real target is the device and not the Mac.
Instead use wsdl2objc.
Who knows, that may well solve your problem - since it's a stub generation approach it probably will work fine.
If possible though, I'd recommend using REST for server communication instead of SOAP. Depends on what control you have over the server.

iPhone URL Request: Add value to HTTP header field

I'm trying to add a value to the header for a URL request.
Something like this works just fine:
[urlRequest addValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
But this doesn't even show up in the header:
NSString *authString = [[NSString alloc] initWithString:
[defaults objectForKey:#"auth"]];
[urlRequest addValue:authString forHTTPHeaderField:#"iphoneID"];
I'm completely stumped. The auth string is around 90 characters long. Is this a problem?
Edit:
Here's the code I'm trying:
NSString *authString = [[NSString alloc] initWithString:[defaults objectForKey:#"auth"]];
[urlRequest addValue:authString forHTTPHeaderField:#"iphoneid"];
[urlRequest addValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
I can see the Accept-Encoding header being sent through Wireshark, but iphoneid is nowhere to be found. It's just a string, 80-90 characters long.
Another Update:
So it seems that the problem isn't the field "iphoneid" but rather the authString I'm trying to pass into it. Other strings that I just create with the #"something" work fine, but the auth string that I pull from NSUserDefaults doesn't appear.
Suggestions on how to debug this?
The true problem.
The string I was pulling from NSUserDefaults already had a line ending. When set as a header, another \r\n is appended, which apparently isn't valid. Thus, the header wouldn't appear in any outgoing packets.
The fix:
Use this to trim off the characters before setting as a header value.
[yourString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
Testing checklist:
Verify that you actually have a NSMutableURLRequest (and not a NSURLRequest) at this point. In particular, check your logs for an exception due to "unrecognized selector."
Verify that urlRequest is not nil.
Switch to setValue:forHTTPHeaderField: rather than addValue:forHTTPHeaderField:.
Swap the forHTTPHeaderField: value to #"Accept-Encoding" to see if the field is the problem
Swap #"gzip" for auth to see if the value is the problem.
You need Charles web proxy, to see what header values are really outbound and inbound. Then you can see if the problem is really in your code or some magic on the server discarding things.
There's a free trial, and after you install it if you hit record any traffic the simulator sends will go through the proxy. Very nice.
http://www.charlesproxy.com/