Bitching and Stiching iPhone Apps (almost) since 1974
RSS icon Email icon Home icon
  • URL Encoding

    Posted on August 17th, 2009 drops No comments

    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 <UIKit/UIKit.h>
     
    @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.

    Leave a reply

    You must be logged in to post a comment.