Everybody is shortening URLs these days. While this saves spaces in Tweets it has several disadvantages for the user: you don’t see the domain that this link refers to. And you know that for certain clicks on the short URL are recorded and data-mined. Just have a look at my public bit.ly timeline. I check there often … pure vanity.
You might remember my hobby project Tweet Curator which allows the filtering of tweets that have certain domains. Now the next extension of this concept would be to also expand those pesky short URLs and use the referred domains for blocking too. There is NO hiding any more!
Technically it is quite easy to active actually, I wonder why nobody bothered to do that so far. At least I didn’t hear from anybody doing it. You could show the un-shortened link in the twitter app but still open the shortened one so that the person tracking you still gets to record a hit.
Abizer Nasir blogged how you can use the Unix curl command to find out the target url. His worry was that there might be a Rick-Roll lurking. In our Cocoa world the equivalent would be NSURLRequest and NSURLConnection. So without much further ado, I present to you…
The Fabulous URL Unshortener!
Since I plan to actually use this for practical purposes I put it into my DTFoundation project on GitHub.
@implementation NSURL (DTUnshorten) - (void)unshortenWithCompletion:(NSURLUnshortenCompletionHandler)completion { static NSCache *unshortenCache = nil; static dispatch_queue_t shortenQueue = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ unshortenCache = [[NSCache alloc] init]; shortenQueue = dispatch_queue_create("DTUnshortenQueue", 0); }); NSURL *shortURL = self; // assume HTTP if scheme is missing if (![self scheme]) { NSString *str = [@"http://" stringByAppendingString:[self absoluteString]]; shortURL = [NSURL URLWithString:str]; } dispatch_async(shortenQueue, ^{ // look into cache first NSURL *longURL = [unshortenCache objectForKey:shortURL]; // nothing cached, load it if (!longURL) { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:shortURL]; request.HTTPMethod = @"HEAD"; NSError *error = nil; NSHTTPURLResponse *response = nil; [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; longURL = [response URL]; // cache result if (longURL) { [unshortenCache setObject:longURL forKey:shortURL]; } } if (completion) { completion(longURL); } }); } @end |
This has some additional bells and whistles. Responses are cached and requests are queued serially. This way if you ask for the same URL twice only the first will perform the network operation. The second one will already come from the cache. Also it prepends http:// if the URL scheme is missing as they often are. It uses the HEAD verb instead of the default GET because we are not interested in the file, just the headers. NSURLConnection is so helpful as to do all the redirects for us and then report the final URL we arrived at via property.
Also, wo don’t bother with changing the cache policy or timeout. iOS might be doing some caching there, but since we don’t get any actual data that’s fine and might even speed it up.
Example usage:
NSURL *url = [NSURL URLWithString:@"buff.ly/L4uGoza"]; [url unshortenWithCompletion:^(NSURL *url) { NSLog(@"Unshortened: %@", url); }]; |
Isn’t it great? If not, it’s at least LONG.
Categories: Recipes