BuySellAds.com

Until Dec 3rd, 44% off all Manning books, including Barcodes with iOS! Promo code: mobicftw
Our DNA is written in Objective-C
Jump

URL Encoding

When transmitting data in the context of the HTTP protocol you often need to encode text in a way that does not interfere with special characters used in URLs. This is of importance if you want to put unicode characters into an URL query but also for simple things like constructing a body for a HTTP POST request. A form post also takes the form fields and puts them into the form that you know from an URL: field=text&another=more. That’s what the HTML content type “application/x-www-form-urlencoded” means.

The first thing that jumps out of the documentation when looking for a standard function to achieve such “URL Encoding” is stringByAddingPercentEscapesUsingEncoding. So that is what I was using for encoding the password for my iTunes Connect class which drives MyAppSales. And until now this worked without a hitch until customer #113 who was the first to use a plus character in his password. The poor guy ended up locking his iTunes account. Sorry!

It turns out that + is an anachronistic special character substitution for a space. I would have expected for it to be encoded properly by the above mentioned method as %20, but this is not the case.

This you can verify for yourself by this experiment:

NSString *password = @"Top+Secret. ";
NSString *encoded_normal = [password stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *encoded_safer = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,  (CFStringRef)password,  NULL,  (CFStringRef)@"!*'();:@&=+$,/?%#[]",  kCFStringEncodingUTF8);
 
// output with %@ otherwise the % escapes cause strange output
NSLog(@"%@", password);
NSLog(@"%@", encoded_normal);
NSLog(@"%@", encoded_safer);

You will find that the + does not get encoded by stringByAddingPercentEscapesUsingEncoding. This example also contains a solution for the dilemma. CoreFoundation provides a cousin of the NSString instance method which also allows to specify characters for which you want to forced encoding even though they are deemed safe. So-called “toll free bridging” allows us to simple typecast CFStringRef into NSString and vice versa. This does the trick.

Thanks to Andrew Nicolle who pointed this out to me in completely unrelated circumstances. Your solution has wider reaching consequences than anticipated! ;-)

Obviously it’s smart to put the above mentioned method together with all the other NSString helper methods so that you can save yourself unnecessary duplication of code.

NSString+Helpers.h

#import 
 
@interface NSString (Helpers)
 
// helper functions
- (NSString *) stringByUrlEncoding;
 
@end

NSString+Helpers.m

#import "NSString+Helpers.h"
 
@implementation NSString (Helpers)
 
#pragma mark Helpers
- (NSString *) stringByUrlEncoding
{
	return (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,  (CFStringRef)self,  NULL,  (CFStringRef)@"!*'();:@&=+$,/?%#[]",  kCFStringEncodingUTF8);
}
 
@end

Finally, here is an example of building and sending a HTTP POST that uses this method. If somebody knows of a more elegant way to construct one please let me know. The regular NSURLRequest is a GET and the only way I found that allowed me to change the verb to POST was to use an NSMutableURLRequest instead. This intentionally omits the 4 necessary call-back methods of the NSURLConnectionDelegate protocol for brevity.

// #import for NSString+Helpers.h at the top
 
NSMutableURLRequest *theRequest=[NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://server/post_url"]
								   cachePolicy:NSURLRequestUseProtocolCachePolicy
							   timeoutInterval:30.0];
 
[theRequest setHTTPMethod:@"POST"];
[theRequest addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField: @"Content-Type"];
 
//create the body
NSMutableData *postBody = [NSMutableData data];
[postBody appendData:[[NSString stringWithFormat:@"name=%@&password=%@",
					   [username stringByUrlEncoding],
					   [password stringByUrlEncoding]] dataUsingEncoding:NSUTF8StringEncoding]];
[theRequest setHTTPBody:postBody];
 
NSURLConnection *theConnection=[[[NSURLConnection alloc] initWithRequest:theRequest delegate:self] autorelease];

Coming to think of it, if there really is no smarter way to make an asychronous HTTP POST then maybe I should put all of this into a category for NSURLRequest. But that’s another story.


Categories: Recipes

%d bloggers like this: