Asynchronous NSURLConnection receives null-bytes in between every byte - ios

I'm trying to fetch an XML-document from a server, but the response I'm getting in clear text is just ÿþ<.
I found out, by reading the HEX-data of the NSData that what follows after those first three bytes is the whole response I wanted, but with a nullbyte in between every byte. Now, I could just strip the data from those bytes, but if I get the same data with Netcat, there are no nullbytes there, so I figured there is something wrong with my code.
I've sent the exact same headers that the iPad-simulator has sent, but everything seems normal from there. This is the code I'm using to fetch the XML-data:
NSString *postData = #"SomePostData";
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSString *URLString = [NSString stringWithFormat:#"https://www.example.com/xmlexport?sessionid=%#", sessionID];
[request setURL:[NSURL URLWithString:URLString]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:[postData dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
And these are the delegate methods I'm using.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
myData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[myData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *myString = [[NSString alloc] initWithData:myData encoding:NSWindowsCP1252StringEncoding];
NSLog(#"%#", myString);
}
This is the response I'm getting from the server:
{ URL: https://www5.whentowork.com/cgi-bin/w2wE.dll/mgrexportemplist?SID=2835115254228 } { status code: 200, headers {
"Access-Control-Allow-Origin" = "*";
"Content-Disposition" = "attachment;filename=empdata.xml";
"Content-Encoding" = gzip;
"Content-Type" = "application/x-msdownload";
Date = "Mon, 14 Apr 2014 19:37:54 GMT";
Server = "Microsoft-IIS/6.0";
"Transfer-Encoding" = Identity;
Vary = "Accept-Encoding";
} }
This is the first part of the XML:
<?xml version="1.0" encoding="windows-1252" standalone="yes"?>
<!-- Generated by Exmaple.com -->
<!DOCTYPE tutorial [
<!ENTITY auml "&auml;">
<!ENTITY ouml "&ouml;">
<!ENTITY uuml "&uuml;">
<!ENTITY Auml "&Auml;">
<!ENTITY Ouml "&Ouml;">
<!ENTITY Uuml "&Uuml;">
I'm running the latest Xcode and running the code in the iOS simulator (Version 7.1 (463.9.41)) for iPad.
I really don't get this. I spent so much time trying to figure out why I only got three bytes, then later figured out I got everything, but with nullbytes in between everywhere. I do not have control over the server, so I can not change it's configuration.
EDIT: An NSLog of the data return this: (just the first couple of lines)
fffe3c00 3f007800 6d006c00 20007600 65007200 73006900 6f006e00 3d002200 31002e00 30002200
20006500 6e006300 6f006400 69006e00 67003d00 22007700 69006e00 64006f00 77007300 2d003100
32003500 32002200 20007300 74006100 6e006400 61006c00 6f006e00 65003d00 22007900 65007300
22003f00 3e000d00 0a003c00 21002d00 2d002000 47006500 6e006500 72006100 74006500 64002000
62007900 20005700 68006500 6e005400 6f005700 6f007200 6b002d00 2d003e00 0d000a00 3c002100
44004f00 43005400 59005000 45002000 74007500 74006f00 72006900 61006c00 20005b00 0d000a00
20002000 20002000 3c002100 45004e00 54004900 54005900 20006100 75006d00 6c002000 22002600
61006d00 70003b00 61007500 6d006c00 3b002200 3e000d00 0a002000 20002000 20003c00 21004500
4e005400 49005400 59002000 6f007500 6d006c00 20002200 26006100 6d007000 3b006f00 75006d00
6c003b00 22003e00 0d000a00 20002000 20002000 3c002100 45004e00 54004900 54005900 20007500
75006d00 6c002000 22002600 61006d00 70003b00 75007500 6d006c00 3b002200 3e000d00 0a002000
20002000 20003c00 21004500 4e005400 49005400 59002000 41007500 6d006c00 20002200

The fffe at the start is a byte order mark that indicates that the response is is little endian UTF-16. (See this byte order mark table.) This is further confirmed if you look at the rest of your data, where 3c00 is the little endian UTF-16 representation of <, 3f00 is ?, 7800 is x, etc.
Thus:
fffe3c00 3f007800 6d006c00 20007600 65007200 73006900 6f006e00 3d002200
31002e00 30002200 20006500 6e006300 6f006400 69006e00 67003d00 22007700
69006e00 64006f00 77007300 2d003100 32003500 32002200 20007300 74006100
6e006400 61006c00 6f006e00 65003d00 22007900 65007300 22003f00 3e00
is
<?xml version="1.0" encoding="windows-1252" standalone="yes"?>
You should therefore convert it to a string using:
NSString *myString = [[NSString alloc] initWithData:myData encoding:NSUTF16LittleEndianStringEncoding];
This will automatically handle the byte order mark, as well (eliminating that curious ÿþ at the start).
By the way, if you're ever unclear about the representation, you can always try saving the NSData to a file and then using stringWithContentsOfFile:usedEncoding:error:. Personally, I'd always first look for a the byte order mark or for obvious UTF-16 or UTF-32 data (which is generally pretty easy to identify in western languages), as we did above, but this can be useful sometimes:
NSStringEncoding encoding;
NSError *error;
NSString *string = [NSString stringWithContentsOfFile:path usedEncoding:&encoding error:&error];
if (string) {
NSLog(#"string = %#", string);
NSLog(#"encoding = %d", encoding);
} else {
NSLog(#"stringWithContentsOfFile error: %#", error);
}
It doesn't always work, but sometimes it provides interesting clues.

Related

How to send the character "&" via HTTP POST to server with iOS 7?

I want to send some data via http post from my App to the server. All symbols can be accepted by the server, except the &.
the server is php;
content-type is application/x-www-form-urlencoded, and charset is utf-8.
Encoding is NSUTF8StringEncoding.
I tried also changing the & with URLEncoding, i.e. & --> %26. the server can receive it, but cannot display it recht. It's shown in %26, not &.
But, the server can properly received and displayed, if it is sent by android or web side.
The code is the following:
NSMutableURLRequest *postRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"xxx.com/server.php"]];
postRequest.HTTPMethod = #"POST";
[postRequest setValue:#"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
NSString *bodyString = [NSString stringWithFormat:#"data=&&&&"];
postBody = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
NSURLConnection *postConnect = [[NSURLConnection alloc]initWithRequest:postRequest delegate:self];
hope this helps you
NSString *encodedString = [myString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
It won't replace your string inline; it'll return a new string. That's implied by the fact that the method starts with the word "string". It's a convenience method to instantiate a new instance of NSString based on the current NSString.
Note--that new string will be autorelease'd, so don't call release on it when you're done with it.
It should be with the function
(NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)url, NULL, (CFStringRef)#"&", kCFStringEncodingUTF8))

AFNetworking 2 Upload Plist Data to Apache Server

I am writing a sample app to test how AFNetworking can be used as a replacement for ASIHTTPLib.. The old library made it simple to upload a file to an Apache server (provided the user has write access to a URL/directory). No other server side support is used..
This code has some problem, but I have not pinpointed it: executing the method reports an upload success, but the plist file on the cloud side does not change…
-(void)uploadReminders:(NSArray*)reminders
{
NSLog(#"AppDelegate Synch Reminders to cloud");
//NSLog(#"Data: %#", reminders);
[self persistReminders:reminders atCustomPath:nil];
NSString *cacheDirectoryPath = [self cachesDirectoryPath];
NSString *urlString = [NSString stringWithFormat:#"%#/%#",kStandardCloudAreaAccessURL,kStandardLocalCachePlistFile];
//NSLog(#"URL: %#", urlString);
NSString *remindersPlistFile = [NSString stringWithFormat:#"%#/%#",cacheDirectoryPath,kStandardLocalCachePlistFile];
//NSLog(#"Filepath: %#",remindersPlistFile);
NSURLCredential *defaultCredential = [NSURLCredential credentialWithUser:kStandardCloudAreaAccessUsername password:kStandardCloudAreaAccessUserPW persistence:NSURLCredentialPersistenceNone];
/**/
NSURL *url = [NSURL URLWithString:urlString];
NSString *host = [url host];
NSInteger port = [[url port] integerValue];
NSString *protocol = [url scheme];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
NSURLCredentialStorage *credentials = [NSURLCredentialStorage sharedCredentialStorage];
[credentials setDefaultCredential:defaultCredential forProtectionSpace:protectionSpace];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
[configuration setURLCredentialStorage:credentials];
[configuration setHTTPAdditionalHeaders: #{#"Accept": #"text/plain"}];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
[manager.securityPolicy setAllowInvalidCertificates:YES];
manager.responseSerializer = [AFPropertyListResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/plain"];
NSURL *URL = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURL *filePath = [NSURL fileURLWithPath:remindersPlistFile];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"Upload Error: %#", error);
} else {
//NSLog(#"Upload Success");
NSLog(#"Upload Success: %# %#", response, responseObject);
}
}];
[uploadTask resume];
}
The console shows this:
Upload Success: <NSHTTPURLResponse: 0x8d4ec50> { URL: https://[.....]/CodeTests/Reminders/Reminders.plist } { status code: 200, headers {
"Accept-Ranges" = bytes;
Connection = "Keep-Alive";
"Content-Length" = 1324;
"Content-Type" = "text/plain";
Date = "Mon, 10 Mar 2014 14:48:13 GMT";
Etag = "\"3f06e5-52c-4f430180dae80\"";
"Keep-Alive" = "timeout=5, max=100";
"Last-Modified" = "Sun, 09 Mar 2014 17:48:26 GMT";
Server = "Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.7l DAV/2";
} } {
reminders = (
{
completed = 1;
created = "2014-03-09 17:47:41 +0000";
description = "";
title = Reminder;
updated = "2014-03-09 17:47:41 +0000";
},
{
completed = 1;
created = "2014-03-08 09:47:58 +0000";
description = "Orza!!! Ma orza in fretta... Ah: funziona? Ebbene, s\U00ec! O no?\n";
title = "Reminder Orza";
updated = "2014-03-08 11:39:43 +0000";
},
{
completed = 0;
created = "2014-03-07 11:09:59 +0000";
description = "Whatever you like; and of course you can even make it quite long.\n\nYeooww..\nReally long!\n\n\n\n";
title = "Reminder A";
updated = "2014-03-08 11:34:24 +0000";
}
);
version = "1.0";
}
The only catch is that when I reopen the app, it jumps back to the test reminders I did manually put on the server: the Reminders.plist gets never changed.
Thanks!
I'm assuming your NSLog of the responseObject is confirming that the plist was successfully received by the server. If so, then that may eliminate the above "upload" code as the source of the problem. You may want to inspect the data on your server (not through the app, but manually inspect it yourself) and see whether your new list of reminders is there or not. It seems that there a couple of possible logical possibilities:
If the updated data is not there, then look at your server's "save" logic, as it would appear to be failing.
If it is there, then you should look at your client's "retrieve" logic. I wonder, for example, if your app is caching the responses to its requests, and thus when you attempt to download again, perhaps you're getting the cached original response. I'd try turning off caching.
In these cases, a tool like Charles can be useful, where you can inspect the requests and responses that you're getting. That can be helpful in narrowing down precisely where the problem is occurring.
Taking a closer look at your request, I notice that you're not specifying the request type. I would have thought that your request would be a POST:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"POST"];
[request setValue:#"text/plain" forHTTPHeaderField:#"Accept"]; // I'd also personally set the `Accept` here at the request, not at the `NSURLSessionConfiguration`
Having said that, given that your web service is successfully reporting your plist data back at you, I would have inferred that it successfully received it on the basis of the evidence you shared with us thus far. But maybe the failure to make the request a POST request means that the web service concluded it didn't need to save anything.
Generally, when interfacing with a web service, rather than tweaking the request, like I have above, I'd encourage you to post data using one of the standard AFHTTPSessionManager variations of the POST method (one is for multipart/form-data requests, the other is for other requests). But I can't figure out what your server is doing on the basis of what you've provided thus far (e.g. it makes no sense that the server is sending the body of your request back to you at all; it makes no sense that the server would appear to have received your request, but doesn't do anything with it and doesn't report some error; etc.). So maybe try making the request a POST request and see if that fixes it. If not, run Charles on your old ASIHTTP source code, and you'll see precisely what the old request looks like and you should be able to reproduce it with AFNetworking.

HTTP post w/ JSON to rails server from iOS

I have succeeded in making a post using a HTTP Client by setting the content type as application/json and this json code:
{
"order": {
"name": "Tia Carter",
"location": "Corams",
"phone_number": "707",
"food": "Bobcat Burger"
}
}
The code works perfect and the order is registered in the database. I am trying to work this into my iOS app but keep getting syntax errors regarding the colons in the json. This is the objective-c code:
NSURL *nsURL = [[NSURL alloc] initWithString:#"http://0.0.0.0:3000/orders.json"];
NSMutableURLRequest *nsMutableURLRequest = [[NSMutableURLRequest alloc] initWithURL:nsURL];
// Set the request's content type to application/x-www-form-urlencoded
[nsMutableURLRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
// Set HTTP method to POST
[nsMutableURLRequest setHTTPMethod:#"POST"];
// Set up the parameters to send.
NSString *paramDataString = [NSString stringWithFormat:#
"{ "order": {"%#:" ="%#","%#:"="%#","%#:"="%#","%#:"="%#"}}", #"name", _name.text, #"location", _location.text, #"phone_number", _phoneNumber.text, #"food", _order.text];
// Encode the parameters to default for NSMutableURLRequest.
NSData *paramData = [paramDataString dataUsingEncoding:NSUTF8StringEncoding];
// Set the NSMutableURLRequest body data.
[nsMutableURLRequest setHTTPBody: paramData];
// Create NSURLConnection and start the request.
NSURLConnection *nsUrlConnection=[[NSURLConnection alloc]initWithRequest:nsMutableURLRequest delegate:self];
I'd appreciate any ideas or guidance. Thanks.
I believe you have two problems:
You didn't escape quotas (put \ before all of them)
You don't need to put text "name", "location" and etc in parameters (it's not a problem per se, just a style thing)
Also, I would recommend to work with NSDictionary and convert it to JSON when you need to (it will save you a lot of nerves for unescaped quotas, missing bracket and so on).
Look this question how to convert NSDictionary to JSON:
Generate JSON string from NSDictionary in iOS

Large Base64 uploads in iOS

Description:
I need to send a base 64 encoded string in a HTTP POST parameter that represents a file to be uploaded.
Current method: I'm using a ASIFormDataRequest and encoding my file into a base64 string like so:
NSData *data = [[NSFileManager defaultManager] contentsAtPath:#"my path here"];
NSString *base64Data = [data encodeBase64ForData];
However when uploading large files the App runs out of memory and dies a horrid death!
Proposed solution: Does anybody now how I would go about, say, reading the file from disk, converting it to base64 strings in chunks and attaching the converted base64 string to a parameter in the HTTP request?
Effectively any way that avoids reading the whole file into memory.
Any input would be greatly appreciated!
My http request code in full:
NSURL *url = [NSURL URLWithString:requestProgressURLString];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:evidence.uri];
NSString *base64Data = [data encodeBase64ForData];
NSURL *fileURL = [[NSURL alloc] initWithString:evidence.uri];
request = [ASIFormDataRequest requestWithURL:url];
request.shouldAttemptPersistentConnection = false;
[request setShowAccurateProgress:YES];
[request setPostValue:accessToken forKey:#"AccessToken"];
[request setPostValue:[[fileURL path] lastPathComponent] forKey:#"Filename"];
[request setPostValue:evidence.name forKey:#"Title"];
[request setPostValue:base64Data forKey:#"FileData"]; //wants string value (base64 encoded)
[request addRequestHeader:#"Content-Type" value:#"application/x-www-form-urlencoded"];
[request setTimeOutSeconds:60];
[request setUploadProgressDelegate:self];
[request setDownloadProgressDelegate:self];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
I would split this problem into two:
1. Encode large file to Base64 in chunks
2. Upload that file
1. Encode large file to Base64 in chunks:
Find some code that encodes NSData instances to Base64. Then use one NSInputStream to read data in chunks, encode it and use NSOutputStream to write to a temporary file. I found this question/answer: Is it possible to base64-encode a file in chunks? Make the chunk size a multiple of 3 and it should work. You may want to move this to background thread.
Important: This will create a temporary file, that is larger than the original. There must be enough space on device for that moment. After you begin uploading you can delete it.
2. Upload that file
It seems that you have this already done. You can use that ASIFormDataRequest exactly like in your example. And since it makes a private copy of the file (it builds the multipart POST file), you can delete your copy.
If you want to optimize it by parallelizing these steps, you will need to find another way to upload the multipart file.
Instead of reading the file in one hit you need to read it in chunks
You can use an input stream to do this
Apple have some info on here
Basically you need to create an NSInputStream pointed at your file as in the example
- (void)setUpStreamForFile:(NSString *)path {
// iStream is NSInputStream instance variable
iStream = [[NSInputStream alloc] initWithFileAtPath:path];
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[iStream open];
}
You can then read the data in chunks using the NSStreamDelegate method
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
Again Apple give an example of this
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if(!_data) {
_data = [[NSMutableData data] retain];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len) {
[_data appendBytes:(const void *)buf length:len];
// bytesRead is an instance variable of type NSNumber.
[bytesRead setIntValue:[bytesRead intValue]+len];
// DO SOMETHING with DATA HERE
} else {
NSLog(#"no buffer!");
}
break;
}
The size of chunks you choose will need to be a balance of memory performance vs network performance as there is obviously some overhead with the HTTP headers
You can also further optimise by sending your NSData directly ( and therefore save the base64 overhead) check this post
File Upload to HTTP server in iphone programming

NSURLConnection Data broken in asynchronous connection

I have a NSURLConnection that receives data output from a url pointing to a php script on my server.
Most of the time everything works fine and the data is retrieved in its complete form.
However, sometimes I receive NULL or broken (i.e. the bottom half) of data at:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
When this happens, if I reload the connection it will always return the same null or broken block
of data for the request.
EDIT*:
I've realized that when I receive what I thought was nil data, I actually received data
but the NSString created from this data is nil. I still don't understand why though. My php encoding output is always UTF-8 so I don't think it is an issue of encoding and besides it works most of the time with this.
I have checked the php script with that same request to verify that it is not a problem on the server side or with the php script and confirmed that it is NOT.
My code is Below:
-(void)setUpConnectionAndMakeRequest {
NSString *URLpath = #"http://www.example.com/myphp.php";
NSURL *myURL = [[NSURL alloc] initWithString:URLpath];
NSMutableURLRequest *myURLRequest = [NSMutableURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
[myURL release];
[myURLRequest setHTTPMethod:#"POST"];
//I added this because I thought it may be a problem relating to cache but it isn't
NSURLCache *cache = [NSURLCache sharedURLCache];
[cache removeAllCachedResponses];
NSString *httpBodystr = [NSString stringWithString:#"command=runscript"];
[myURLRequest setHTTPBody:[httpBodystr dataUsingEncoding:NSUTF8StringEncoding]];
[mailData setData:nil]; //mailData is a NSMutableData object which accumulates the data retrieved by the request
[NSURLConnection connectionWithRequest:myURLRequest delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSString *untrimmedDataSTR = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; //Created so I can see the data (always text) in NSLog
NSLog(#"Live Data: %#", untrimmedDataSTR); //This is where I see that the data is broken or null when it shouldn't be
[mailData appendData:data]; //Append accumulated data to NSMutableData object used later in my app.
[untrimmedDataSTR release];
}
Any help would be much appreciated.
According to the NSString reference, -initWithData:encoding: returns nil if "the initialization fails for some reason (for example if data does not represent valid data for encoding)."
That almost certainly means that the response from the server is not, in fact, UTF-8 encoded data.
The way to check would be to NSLog the data before trying to convert to an NSString:
NSLog(#"Raw Data: %#", data);
(The -description method on NSData will return a hexadecimal representation of the contents; that's what will get logged).

Resources