multipart POST request directly in ASIFormDataRequest - cocoa

So you think it would be easy to find a direct example of POST information to ASIFormDataRequest, but I havent found a 1:1 in these formats. Here's the POST request I want to send:
POST /fileXfer HTTP/1.1
Content-Type: multipart/form-data; boundary=AaB03x
Content-Length: 200
AppID:myID
TransferID:abcd1234
-- AaB03x
Content-Disposition: form-data; name="data"; filename="xxx"
Content-Type: application/octet-stream
Content-Length: 100
... contents of file ...
--AaB03x--
EDIT:
Turns out my problems were more with the server receiving the data. But this is what I ended up with!
NSURL *url = [NSURL URLWithString:#"http://IP:PORT/fileXfer"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostFormat:ASIMultipartFormDataPostFormat];
//Sets headers of POST
[request addRequestHeader:#"X-Application-Id" value:#"myID"];
[request addRequestHeader:#"X-Transfer-Id" value:#"abcd1234"];
//Sets data
NSData *fileData = [NSData dataWithContentsOfFile:kEncryptedFilePath];
[request setData:fileData withFileName:#"xxx" andContentType:#"application/octet-stream" forKey:#"data"];
[request setDelegate:self];
[request startAsynchronous];
I also added the following line to ASIFormDataRequest's buildMultipartFormDataPostBody where Content-Disposition and Content-Type are set:
[self appendPostString:[NSString stringWithFormat:#"Content-Length: %lu\r\n\r\n", [data length]]];`
I'm not sure if there's an easier way to add that line directly from the request, but out of the things I tried, this seemed to be the one that worked.

Rather than building the HTTP body manually, which may lead to errors, you should better use the methods provided by ASIFormDataRequest.
NSURL *url = [NSURL URLWithString:#"http://IP:PORT/fileXfer"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request addRequestHeader:#"X-Application-Id" value:#"myID"];
[request addRequestHeader:#"X-Transfer-Id" value:#"abcd1234"];
[request setData:[self fileData] withFileName:#"xxx" andContentType:#"application/octet-stream" forKey:#"data"];
[request setDelegate:self];
[request startAsynchronous];

Related

Why must I send the CSRF token with AFNetworking but not with cURL?

When I perform a POST request via cURL, it completes successfully (HTTP 201) even without sending the CSRF token header.
curl -X POST http://server/app/addrecord/ -d '{"pk":"1", "lname":"Smith", "fname":"Robert"}' -H "Content-Type: application/json" > out.html
However when I attempt the same operation using AFNetworking in Obj-C, I get a 403 error that states the CSRF token is required.
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
AFJSONRequestSerializer *serializer = [AFJSONRequestSerializer serializer];
[serializer setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[serializer setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[manager setRequestSerializer:serializer];
AFJSONResponseSerializer *respSerializer = [AFJSONResponseSerializer serializer];
respSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/json", #"text/html", #"text/plain", #"text/json", nil];
manager.responseSerializer = respSerializer;
[manager POST:#"http://server/app/addrecord/" parameters:parameters success:^(NSURLSessionDataTask * task, id responseObject) {
NSLog(#"Success");
} failure:^(NSURLSessionDataTask * task, NSError * error) {
NSLog(#"Error");
}];
Simply by adding the following line to my request serializer, the error goes away.
[serializer setValue:csrf forHTTPHeaderField:#"X-CSRFTOKEN"];
While I don't need the AFNetworking code to work without the CSRF Token being sent, I would like to understand why the cURL command works without the token but the AFNetworking code doesn't.

get custom headers of AFHTTPRequestOperation

I attempt to get custom header key using AFHTTPRequestOperation, I try using allHeaderFields but nothing, here is header response
HTTP/1.1 302 Moved Temporarily
Server nginx
Date Tue, 19 Feb 2013 16:38:29 GMT
Content-Type text/html
Transfer-Encoding chunked
Connection keep-alive
Set-Cookie AUTH-ID="fjArrnmlyNMU9kfIu38Oc0LS451Y/UaMn0rb5sKj46CxmfJj8y8yr8CfwOewItFY"; HTTPOnly
X-AUTH-TOKEN mfy+426BNZdq1h92As3oXdZbf2iOI7wV7EOEUMAV3hAqtY7cOnWvA4df7h6RfjeD
Location /home.php
I use AFHTTPRequestOperation like this
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *headerData = [[operation response] allHeaderFields];
if ([headerData objectForKey:#"X-AUTH-TOKEN"] != nil)
token = [headerData objectForKey:#"X-AUTH-TOKEN"];
NSLog(#"headers = %#", headerData);
NSLog(#"token = %#", token);
...
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
...
}];
but X-AUTH-TOKEN key don't appear in headers NSLog, I'm sure the header key is present because I use Charles proxy to debug and Charles show me the X-AUTH-TOKEN key. Maybe the 302 status code is the problem, can anyone help me please?
Thanks.
[EDIT] when I try to show status code of response, it's 200.
Solved using some tweaks of AFURLConnectionOperation for intercept redirect response.

json response is interpreted as text/plain

I have the following code:
NSURL *URL = [NSURL URLWithString:[#"some-address"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
RKObjectRequestOperation *requestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:[self.objectManager responseDescriptors]];
[requestOperation start];
[requestOperation waitUntilFinished];
I get the following error.
Object request failed: Underlying HTTP request operation failed with error: Error Domain=AFNetworkingErrorDomain Code=-1016 "Expected content type {(
    "application/x-www-form-urlencoded",
    "application/json"
)}, got text/plain" UserInfo=0x1f5e3c40 {NSLocalizedRecoverySuggestion={"total_rows":16,"offset":1,"rows":[
{"id":"1","key":1,"value":{"_id":"1","_rev":"1-e75042683867a7030fc4d3aa3b72ef35",
"user":{
"userId":"1",
"name":"A",
.......
]}}, .....
Why I get this error, when the response is in Json format?
we did it. just set
[RKMIMETypeSerialization registerClass:[RKXMLReaderSerialization class] forMIMEType:#"text/plain"];
and feel free to change RKXMLReaderSerialization class with RKNSJSONSerialization class if you're using JSON instead of XML (XML was our case).
You didn't set the mime-type header properly in your response. Note the error says got text/plain, while the code is expecting either application/json or application/x-www-form-urlencoded.

Is this a mal-formed http request from an iPad, which kills Node.js multipart parser

The below code is used in an iPad app to send an HTTP request to a Node.js web server, which produces the following error, but works fine w/ a regular HTML+browser form.
The server is Node.js + formidable which has a multipart parser that only dies on this line of code with this error:
message: parser error, 0 of 29162
bytes parsed
stack: Error: parser error, 0 of 29162
bytes parsed
at IncomingForm.write (/usr/local/lib/node/.npm/formidable/0.9.8/package/lib/formidable/incoming_form.js:120:17)
at IncomingMessage. (/usr/local/lib/node/.npm/formidable/0.9.8/package/lib/formidable/incoming_form.js:73:12)
at IncomingMessage.emit (events:27:15)
at HTTPParser.onBody (http:100:23)
at Stream.ondata (http:763:22)
at IOWatcher.callback (net:494:29)
at node.js:768:9
This is the iPad code:
NSMutableURLRequest * theRequest = [[NSMutableURLRequest alloc] initWithURL:url];
[theRequest setTimeoutInterval:60];
[theRequest setHTTPMethod:#"POST"];
NSString *boundary = [NSString stringWithString:#"---------------------------14737809831466499882746641449"];
NSString *contentType = [NSString stringWithFormat:#"multipart/form-data; boundary=%#",boundary];
[theRequest addValue:contentType forHTTPHeaderField: #"Content-Type"];
NSMutableData *body = [NSMutableData data];
//media
[body appendData:[[NSString stringWithFormat:#"\r\n--%#\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithString:#"Content-Disposition: form-data; name=\"image\"; filename=\"iosaudio.cai\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithString:#"Content-Type: image/jpeg\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[NSData dataWithData:theAudio]];
[body appendData:[[NSString stringWithFormat:#"\r\n--%#--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// setting the body of the post to the reqeust
[theRequest setHTTPBody:body];
Is the request being sent malformed? If so, why and how should it be done?
We actually just had this same problem in some iOS code posting to Node.js. Our issue turned out to be the CR-LF preceding the first boundary. Node.js uses a component for parsing MIME that is very picky about the format and the preceding CR-LF characters are viewed as malformed. I'm not sure, but the CR-LF following your last boundary may cause the same issue. Your first boundary should look like this:
[body appendData:[[NSString stringWithFormat:#"--%#\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
Your final boundary should look like this:
[body appendData:[[NSString stringWithFormat:#"\r\n--%#--",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
Note that intervening boundaries should include the CR-LF before and after the boundary:
[body appendData:[[NSString stringWithFormat:#"\r\n--%#\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
My read of the specification indicates you can have data before the first boundary and after the final boundary, but the parser should ignore these areas. The component Node.js is using to parse this is being very picky. There is a patch to the parsing component, but it hasn't made its way into the project yet.

Why do I get two redirectResponses when receiving one 302 response?

I use the following code:
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSHTTPURLResponse *)response {
NSLog(#"Received redirect Response: %# %#", [response allHeaderFields], [NSHTTPURLResponse localizedStringForStatusCode:[response statusCode]]);
return request;
}
When I receive a 302 with the following header data:
< HTTP/1.1 302 Found
< Date: Wed, 03 Mar 2010 07:47:17 GMT
< Server: lighttpd/1.4.19
< Content-length: 0
< Content-type: text/html;charset=utf-8
< Location: `<new Location>`
< Vary: Accept-Encoding
this is the output in gdb console:
2010-03-03 08:42:03.265 MyProg[68106:207] Received redirect Response:
(null) server error 2010-03-03 08:42:14.414 MyProg[68106:207]
Received redirect Response: {
Connection = "Keep-Alive";
"Content-Encoding" = gzip;
"Content-Length" = 20;
"Content-Type" = "text/html;charset=utf-8";
Date = "Wed, 03 Mar 2010 07:42:10 GMT";
"Keep-Alive" = "timeout=15, max=100";
Location = "<new Location>";
Server = "lighttpd/1.4.19";
Vary = "Accept-Encoding"; } found
When using Curl I only get one response and tracedump tells the same, so I am sure that the server sends only one redirect.
Why is this selector called twice?
connection:willSendRequest:redirectResponse: gets called before every request, so it is called once on the original request, which was not a redirect so response is nil; then it gets called when loading the redirection target, where response is the 302 response to the initial request.

Resources