NSURLRequest with UTF8 password - cocoa

Here is a method I've written to connect to a server and get a user auth token:
+ (void)getAuthTokenForUsername:(NSString *)username
password:(NSString *)password
completionHandler:(void (^)(NSString *, NSError *))completionHandler
{
username = [username URLEncodedString];
password = [password URLEncodedString];
NSString *format = #"https://%#:%##api.example.com/v1/user/api_token?format=json";
NSString *string = [NSString stringWithFormat:format, username, password];
NSURL *URL = [NSURL URLWithString:string];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:URL]
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *URLResponse, NSData *data, NSError *error)
{
NSString *token;
if (data) {
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
token = [NSString stringWithFormat:#"%#:%#", username, dictionary[#"result"]];
}
completionHandler(token, error);
}];
}
A URL then looks something like this: https://username:hello%C2%B0#api.example.com/v1/user/api_token?format\=json, where the password is hello°. The URLEncodedString method properly encodes everything as in the example, but the request never works. The problem is not with escaping or the server, because I can curl the same URL and I get nice JSON and authentication works, even though there is a non-ASCII character in the password. It also works from other programming languages like ruby or python. But the same url never works with NSURLConnection and it also doesn't work in Safari, which of course uses NSURLConnection. I get an 'The operation could not be completed' with a '401 Forbidden' every time.
(My code works fine when the password just contains ASCII characters. I also tried using the NSURLCredential methods, same problem.)
What do I need to do for NSURLConnection to work with such a URL as https://username:hello%C2%B0#api.example.com/v1/user/api_token?format\=json where the password contains non-ASCII characters?

I have just performed several tests against my mockup server and I think I have a solution for you.
First of all, when you add username & password to an URL, they are not actually send to the server as part of the URL. They are sent as part of the Authorization header (see Basic access authentication).
The fastest workaround for you is to do
NSURLRequest* request = [NSURLRequest requestWithURL:URL];
NSString* usernamePassword = [[NSString stringWithFormat:#"%#:%#", username, password] base64Encode];
[request setValue:[NSString stringWithFormat:#"Basic %#", usernamePassword] forHTTPHeaderField:#"Authorization"]
To understand the problem, let's go a bit deeper. Let's forget NSURLConnection sendAsynchronousRequest: and let us create an old-fashioned connection with a NSURLConnectionDelegate. Then in the delegate, let's define the following methods:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(#"Proposal: %# - %#", challenge.proposedCredential.user, challenge.proposedCredential.password);
NSURLCredential* credential = [NSURLCredential credentialWithUser:#"username"
password:#"hello°"
persistence:NSURLCredentialPersistenceNone];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
If you don't create these methods, the username & password from your URL won't ever be added to the HTTP header.
If you add them, you'll see that the proposed password is hello%C2%B0. Obviously, that's wrong.
You can see the problem directly from
NSLog(#"Password: %#", [[NSURL URLWithString:#"http://username:hello%C2%B0#www.google.com"] password]);
which prints
hello%C2%B0
I believe this is a bug in the framework. NSURL returns password encoded and NSURLCredential doesn't decode it so we are left with an invalid password.

Related

Parameters are always null when AFJSONRequestSerializer is used for a POST in AFNetworking

I'm writing a REST API by using SlimFramework in server side and AFNetworking in client side.
I'd like to add a value in Header for Authorization so that I'm using AFJSONRequestSerializer before the POST. Here is my code:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:self.apikey forHTTPHeaderField:#"Authorization"];
[manager POST:url parameters:#{REST_PARAM_USERID: userId}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
But the failure callback is always called. I found that's because the parameters I passed to server are always null although they're not null when I debugged in my Xcode. When I comments out the requestSerializer then my server works well.I don't know what's the reason. Can anybody help? Thanks
When you use AFJSONRequestSerializer, your parameters will always be serialized as JSON in the body of the HTTP request. If your server is not expecting JSON, then you should either reconfigure your server, or not use AFJSONRequestSerializer.
If, for some reason, you want to send some parameters through normal URL encoding, and others through JSON, you'll need to manually append them to your URL like so:
NSString *urlWithParams = [NSString stringWithFormat:#"%#?%#=%#", url, REST_PARAM_USERID, userId"];
[manager POST:urlWithParams parameters:#{#"some other" : #"params"}
success:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];

How to add CSRF token into http header on AFHTTPRequestOperationManager?

I created a test application (using scaffold) at heroku and I built an iOS client (using AFNetworking 2) to this heroku application. I was trying to delete records from heroku using iOS app and It didn't work. I received 422 status error from server.
Looking at heroku logs I figure out that server is claiming for CSRF token. So I tried to do that with this code on my iOS client:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer new];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:#"application/json", nil];
[manager DELETE:contact.url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Response: %#", [operation description]) ;
if (block) {
block(error);
}
NSLog(#"Error: %#", error);
}];
It didn't work.
How can I add CSRF token into http header on AFHTTPRequestOperationManager ?
Two things here:
If your server is complaining about a lack of a CSRF token, then forging one isn't going to be the correct solution. See this Stack Overflow answer for more information. While you're just starting to get everything up-and-running, you can temporarily disable this by commenting out the protect_from_forgery call in your API controller.
AFHTTPRequestOperationManager is initialized with an AFJSONResponseSerializer already, so you don't need to set that yourself. You can add a default X-XSRF-TOKEN (or whatever header by doing [manager.requestSerializer setValue:#"..." forHTTPHeaderField:#"..."];
With AFNetworking2 you customize http headers in request serializer. So you need to subclass the one you currently work with and add this logic there.
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(NSDictionary *)parameters
error:(NSError *__autoreleasing *)error {
NSMutableDictionary *modifiedParams = [parameters mutableCopy];
modifiedParams[#"your-header-name"] = #"you-header-value";
NSMutableURLRequest *res = [super requestBySerializingRequest:request
withParameters:modifiedParams
error:error];
return res;
}

Bing Search API -The authorization type you provided is not supported(ios)

I am making use of Bing Search API available on Windows Azure Marketplace.
Subscription Type: Bing Search API (5000 transactions per month free).
I have obtained the account key.
Still I get response as
**The authorization type you provided is not supported.Only Basic and OAuth are supported**
When I copy url in the browser it asks for the user name and password (only password is mandatory and it is nothing but the account key) and after validating I can see data in XML/JSON format.
How I can achieve this through my program?
My code is:
NSString *urlString = [NSString stringWithFormat:#"%s","https://api.datamarket.azure.com/Data.ashx/Bing/Search/v1/Web?$format=json&$top=8&Query='querystring'"];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(#"%#",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]) ;
NSURLRequest *request = [NSURLRequest requestWithURL:url] ;
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start] ;

cocoa FTP connection authentication concern

I want to download a file from FTP connection for that I am using NSURL connection.
The connection have username and password.
If I pass the username and password in the url
ftp://user:password#ftp.example.com/foo/bar.zip
than it works fine. but I want to implement the authentication method where I can pass the password in the callback. But I am not receving any callbacks.
Below is the callback that I have implemented but it never gets called
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
Note:- I tried using Apple sample called SimpleFTPSample but it didnt helped.
To receive callbacks, please check both things:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:requestToServer delegate:self startImmediately:YES];
^^^^^^ must be
And u must call it from main queue:
dispatch_async(dispatch_get_main_queue(), ^(void) { // u url part here
Then, u callbacks will be done, u can using authenticate part user name and pass trick can impossible to parse then from code:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSString *user = [NSString stringWithFormat:#"%c%s%#", 'u', "se", #"r"];
NSString *password = [NSString stringWithFormat:#"%c%s%c%#", 'B', "FEBB", 'C', #"3CD036ED072A"];
NSURLCredential *credential = [NSURLCredential credentialWithUser:user
password:password
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
But as i know, user and pass will be sent anyway without any protection, so better solution is moving u content to https server. In that case u can be sure that u

NSURL from NSURLConnection?

It seems dead simple, as to create an NSURLConnection I usually do this:
NSURL *theURL = [NSURL URLWithString:urlString];
NSURLRequest *req = [NSURLRequest requestWithURL:theURL];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:req delegate:self];
But how can I get the URL back in the delegate methods? Short of hanging on to them myself (I'm running many connections at once, so this becomes slightly messy). It seems as though I should be able to get the URL back from a connection.
Am I missing something?
In -connection:didReceiveResponse: you can get the URL. Note that this may not be the same URL you created the connection with since the connection may have been redirected.
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
NSURL * url = [response URL]; // The URL
}

Resources