URL encoding an NSString on iOS

James Higgs
James Higgs

I’ve been working on an iPhone app for the last few weeks, which I’ve really enjoyed. Every now and again, though, you hit what seems like a bug in the iOS SDK.

 This seems to happen much more frequently than it ever did when I was coding in C#. As a result, my default debugging approach – that any problem with my app must be my fault rather than something in the framework – has shifted slightly. I’m now much more likely to question the framework itself, and with a quick Google search it’s common to find other developers who have experienced the same problem.

Here’s one that bit me recently. NSString has a method calledstringByAddingPercentEscapesUsingEncoding, which purports to make the string safe for use, say, as a parameter to a URL. There are several characters that are reserved in parameters toURLs – for example the slash character, or the ampersand, because these are characters that are used to delimit the URL itself. Therefore we encode these, and this is done using the percent encoding scheme.

As an example, if you want to use the string hell & brimstone + earthly/delight (and why wouldn’t you?) as a parameter to a URL then you’ll need to convert it to hell+%26+brimstone+%2B+earthly%2Fdelight so that the ampersand becomes %26, the plus becomes %2B and the slash becomes %2F. Note that spaces are encoded as pluses, which is why the original plus sign needs to be encoded.

But stringByAddingPercentEscapesUsingEncoding doesn’t respect these rules, and actually produces this: hell%20&%20brimstone%20+%20earthly/delight

It fails to encode the ampersand, the slash and the plus, and many (most?) web servers will be confused by that. It encodes the spaces as %20, which is just as acceptable as encoding them as a plus.

In short, it’s completely broken, which is frustrating. But thankfully there’s a lower-level API we can use which, thanks to the magic of Objective-C categories, we can tack on to NSString. This is based on a blogpost by Simon Woodside, which I’ve just turned into a category.

Here’s the category’s header file:

#import <Foundation/Foundation.h>
@interface NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding;
@end

And here’s the implementation:

#import "NSString+URLEncoding.h"
@implementation NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding {
	return (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
			   (CFStringRef)self,
			   NULL,
			   (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ",
			   CFStringConvertNSStringEncodingToEncoding(encoding));
}
@end

And now we can simply do this:

NSString *raw = @"hell & brimstone + earthly/delight";
NSString *url = [NSString stringWithFormat:@"http://example.com/example?param=%@",
			[raw urlEncodeUsingEncoding:NSUTF8Encoding]];
NSLog(url);

And the result will be exactly as we hoped:

http://example.com/example?param=hell%20%26%20brimstone%20%2B%20earthly%2Fdelight

7 comments

Nice but the constant should be NSUTF8StringEncoding, not NSUTF8Encoding

Marc

Many thanks, this came in handy for a nasty email verification HTTP call.

Lutz

arc errors all over the place with this. I just did:

NSString *charactersToEscape = @"!*'();:@&=+$,/?%#[]\" ";
NSCharacterSet *allowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:charactersToEscape] invertedSet];

NSString *url = [NSString stringWithFormat:@"%@", [_video objectForKey:@"video_url"]];
NSString *encodedUrl = [url stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];

mike

How would you do the reverse? as in convert this new URLencodedString to a normal NSString by removing all the percentage escapes?

Timothy

{"errors":[{"message":"Could not authenticate you","code":32}]}

vik

Thanks, man! Putting it into my app.

Alex

Timothy - to decode/reverse - you can try this:

-(NSString *)urlDecodeUsingEncoding {
return (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL,
(__bridge CFStringRef)self,
CFSTR(""),
kCFStringEncodingUTF8));

Girish