Coding under the Hammer
URL Encoding
If you have tried to send any information using a GET web request, you would have come across an annoying problem. That annoying problem is making sure that the URL is correctly encoded.
At first glance it would seem that the Cocoa Frameworks do this for you, and you would be right …. well kind of.
The issue is that by default most of these methods leave characters such as & = ? within a URL, as they are strictly speaking valid. The problem is that these characters have special meanings in a GET request, and will more than likely make your request in valid.
Luckily there is a function in Core Foundation that helps:
CFStringRef CFURLCreateStringByAddingPercentEscapes (
CFAllocatorRef allocator,
CFStringRef originalString,
CFStringRef charactersToLeaveUnescaped,
CFStringRef legalURLCharactersToBeEscaped,
CFStringEncoding encoding
);
What makes this function useful, is the legalURLCharactersToBeEscaped parameter. This will escape legal characters such as & ? = if they are supplied. This allows you to escape parameters using the following code.
CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)parameter, NULL, CFSTR(":/?#[]@!$&’()*+,;="), kCFStringEncodingUTF8)
An example of when to use this, is Twitters Update status API. You can find that here http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses%C2%A0update
To update your status to the following:
This is my status
You would need to post up the following URL:
http://twitter.com/statuses/update.xml?status=This%20is%20my%20status
As this is such a common problem of mine, I have created a category on NSURL. This allows you to pass in a base URL and a parameters dictionary.
+ (NSURL *)URLWithBaseString:(NSString *)baseString parameters:(NSDictionary *)parameters{
NSMutableString *urlString =[NSMutableString string];
//The URL starts with the base string
[urlString appendString:baseString];
NSString *escapedString;
NSInteger keyIndex = 0;
for (id key in parameters) {
//First Parameter needs to be prefixed with a ? and any other parameter needs to be prefixed with an &
if(keyIndex ==0)
{
escapedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)[parameters valueForKey:key], NULL, CFSTR(":/?#[]@!$&’()*+,;="), kCFStringEncodingUTF8);
[urlString appendFormat:@"?%@=%@",key,escapedString];
[escapedString release];
}else{
escapedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)[parameters valueForKey:key], NULL, CFSTR(":/?#[]@!$&’()*+,;="), kCFStringEncodingUTF8);
[urlString appendFormat:@"&%@=%@",key,escapedString];
[escapedString release];
}
keyIndex++;
}
return [NSURL URLWithString:urlString];
}
Using a parameters dictionary keeps the code nice and clean, but beware, to use the category method above you still have to make sure that your keys, and the base URL are correctly encoded (no spaces or invalid characters !!!!!).
As we now have a category method to do all the hard work for us, to create the Twitter URL you just need to do the following:
NSString *baseString=@"http://twitter.com/statuses/update.xml";
NSDictionary *dictionary=[NSDictionary dictionaryWithObjectsAndKeys:@"This is my status",@"status",nil];
NSURL *url=[NSURL URLWithBaseString:baseString parameters:dictionary];
And thats it. Obviously this category can be used for things other than twitter ….. if you really want to.
about 4 months ago
CFURLCreateStringByAddingPercentEscapes() creates an object whose memory you need to manage explicitly. It behaves like malloc() or [[NSString alloc] init]: the memory for the thing returned has to be freed by the caller or you get a leak.
The implementation above will leak two CFStringRefs every time it’s called.
CFStringRef *encoded = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)[parameters valueForKey:key], NULL, CFSTR(“:/?#[]@!$&’()*+,;=”), kCFStringEncodingUTF8);
[urlString appendFormat:@"?%@=%@",key, encoded];
CFRelease(encoded);
Using CoreFoundation objects always seems to add a lot of noise to otherwise clean code. I guess that’s the price we pay, though.
about 4 months ago
This category method is an elegant solution and I’ve got a place where I can use it. Thanks!
about 4 months ago
Thanks Bill,
I noticed this the other day when I did static analysis on one of my projects, but I forgot I did a blog post on it. I have updated the code snippet to the one I know use in my projects.