Skip to content

Instantly share code, notes, and snippets.

@HT154
Created November 2, 2012 19:19
Show Gist options
  • Save HT154/4003721 to your computer and use it in GitHub Desktop.
Save HT154/4003721 to your computer and use it in GitHub Desktop.
// TumblrXAuth
//
// Created by Eric Johnson on 08/27/2010.
// Copyright 2010 Eric Johnson. All rights reserved.
//
// Heavily modified by HT154 on 06/15/2012.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
#import <Foundation/Foundation.h>
#import "JSONKit.h"
#include <CommonCrypto/CommonHMAC.h>
#import "NSData+Base64.h"
@interface NSString (URLEncode)
- (NSString *)encodeForURL;
- (NSString *)encodeForURLReplacingSpacesWithPlus;
- (NSString *)decodeFromURL;
@end
@interface NSData (URLEncode)
- (NSString *)encodeForURL;
- (NSString *)stringWithoutURLEncoding;
- (NSString *)encodeForOauthBaseString;
@end
@class TumblrXAuth;
@protocol TumblrXAuthDelegate <NSObject>
@optional
-(void)tumblrXAuthAuthorizationDidFail:(TumblrXAuth *)tumblrXAuth error:(NSError *)error;
-(void)tumblrXAuthDidAuthorize:(TumblrXAuth *)tumblrXAuth;
-(void)tumblrXAuthDidFail:(TumblrXAuth *)tumblrXAuth error:(NSError *)error;
-(void)tumblrXAuthDidRespond:(TumblrXAuth *)tumblrXAuth response:(NSString *)response;
@end
typedef enum {
TumblrXAuthStateDefault,
TumblrXAuthStateAuthorize,
TumblrXAuthStateAPI,
} TumblrXAuthState;
@interface TumblrXAuth : NSObject {
NSString *nonce;
NSString *timestamp;
NSString *consumerKey;
NSString *password;
NSString *username;
NSString *consumerSecret;
NSString *token;
NSString *tokenSecret;
NSMutableData * data;
TumblrXAuthState state;
id<TumblrXAuthDelegate> delegate;
NSDictionary *currentParams;
NSString *tumblrURL;
NSString *method;
}
@property (nonatomic,copy) NSString * consumerKey;
@property (nonatomic,copy) NSString * password;
@property (nonatomic,copy) NSString * username;
@property (nonatomic,copy) NSString * consumerSecret;
@property (nonatomic,copy) NSString * token; //oauth_token
@property (nonatomic,copy) NSString * tokenSecret; //oauth_token_secret
@property (nonatomic,assign) id<TumblrXAuthDelegate> delegate;
-(void)authorize;
-(id)initWithDelegate:(id<TumblrXAuthDelegate>)aDelegate consumerKey:(NSString *)ck consumerSecret:(NSString *)cs tokenKey:(NSString *)tk tokenSecret:(NSString *)ts;
-(void)sendOAuthRequestWithURL:(NSString *)url method:(NSString *)httpMethod parameters:(NSDictionary *)arguments;
-(void)sendAPIKeyRequestWithURL:(NSString *)url parameters:(NSDictionary *)arguments;
- (NSString *) nonce;
@end
@implementation NSString (URLEncode)
- (NSString *)encodeForURL{
const CFStringRef legalURLCharactersToBeEscaped = CFSTR("!*'();:@&=+$,/?#[]<>\"{}|\\`^% ");
return [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, NULL, legalURLCharactersToBeEscaped, kCFStringEncodingUTF8)) autorelease];
}
- (NSString *)encodeForURLReplacingSpacesWithPlus;{
const CFStringRef legalURLCharactersToBeEscaped = CFSTR("!*'();:@&=$,/?#[]<>\"{}|\\`^% ");
NSString *replaced = [self stringByReplacingOccurrencesOfString:@" " withString:@"+"];
return [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)replaced, NULL, legalURLCharactersToBeEscaped, kCFStringEncodingUTF8)) autorelease];
}
- (NSString *)decodeFromURL{
NSString *decoded = [NSMakeCollectable(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)self, CFSTR(""), kCFStringEncodingUTF8)) autorelease];
return [decoded stringByReplacingOccurrencesOfString:@"+" withString:@" "];
}
@end
@implementation NSData (URLEncode)
- (NSString *) stringWithoutURLEncoding {
NSString *hexDataDesc = [self description];
hexDataDesc = [[hexDataDesc stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]] stringByReplacingOccurrencesOfString:@" " withString:@""];
NSMutableString * newString = [NSMutableString string];
for (int x=0; x<[hexDataDesc length]; x+=2) {
NSString *component = [hexDataDesc substringWithRange:NSMakeRange(x, 2)];
int value = 0;
sscanf([component cStringUsingEncoding:NSASCIIStringEncoding], "%x", &value);
if((value <=46 && value >= 45) || (value <=57 && value >= 48) || (value <=90 && value >= 65) || (value == 95) || (value <=122 && value >= 97)){ //48-57, 65-90, 97-122
[newString appendFormat:@"%c", (char)value];
}else{
[newString appendFormat:@"%%%@", [component uppercaseString]];
}
}
NSString *aNewString = [newString stringByReplacingOccurrencesOfString:@"%20" withString:@"+"];
return aNewString;
}
- (NSString *) encodeForURL {
NSString *newString = [self stringWithoutURLEncoding];
newString = [newString stringByReplacingOccurrencesOfString:@"+" withString:@"%20"];
const CFStringRef legalURLCharactersToBeEscaped = CFSTR("!*'();:@&=+$,/?#[]<>\"{}|\\`^% ");
NSString *encodeForURLdString = [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)newString, NULL, legalURLCharactersToBeEscaped, kCFStringEncodingUTF8)) autorelease];
return encodeForURLdString;
}
- (NSString *) encodeForOauthBaseString {
NSString *newString = [self encodeForURL];
newString =[newString stringByReplacingOccurrencesOfString:@"%257E" withString:@"~"];
return newString;
}
@end
@interface TumblrXAuth ()
- (void) resetNonce;
- (void) resetTimestamp;
- (NSString *) timestamp;
- (NSString *) baseString;
- (NSString *) signature;
- (NSString *) authorizationHeader;
@end
@implementation TumblrXAuth
@synthesize consumerKey, password, username, consumerSecret, token, tokenSecret;
@synthesize delegate;
-(id)initWithDelegate:(id<TumblrXAuthDelegate>)aDelegate consumerKey:(NSString *)ck consumerSecret:(NSString *)cs tokenKey:(NSString *)tk tokenSecret:(NSString *)ts{
if ((self = [super init])) {
state = TumblrXAuthStateDefault;
data = [[NSMutableData alloc] init];
if(!ts){self.tokenSecret = @"";
}else{self.tokenSecret = ts;}
if(tk){self.token = tk;}
if(ck){self.consumerKey = ck;}
if(cs){self.consumerSecret = cs;}
if(aDelegate){self.delegate = aDelegate;}
method = @"GET";
}
return self;
}
-(id)init{
return [self initWithDelegate:nil consumerKey:nil consumerSecret:nil tokenKey:nil tokenSecret:nil];
}
-(void)dealloc{
[data release];
[currentParams release];
[super dealloc];
}
#pragma mark Authentication Methods
-(void)resetNonce{[nonce release]; nonce = nil;}
-(void)resetTimestamp{[timestamp release]; timestamp = nil;}
-(NSString *)nonce{if (nonce == nil){nonce = [[NSString stringWithFormat:@"%d", arc4random()] retain];} return nonce;}
-(NSString *)timestamp{if (timestamp == nil){timestamp = [[NSString stringWithFormat:@"%d", (int)(((float)([[NSDate date] timeIntervalSince1970])) + 0.5)] retain];} return timestamp;}
-(NSString *)signature{
NSString * secret = [NSString stringWithFormat:@"%@&%@", self.consumerSecret, self.tokenSecret];
NSData * secretData = [secret dataUsingEncoding:NSUTF8StringEncoding];
NSData * baseData = [self.baseString dataUsingEncoding:NSUTF8StringEncoding];
//uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
uint8_t digest[20] = {0};
CCHmac(kCCHmacAlgSHA1, secretData.bytes, secretData.length, baseData.bytes, baseData.length, digest);
//NSData * signatureData = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
NSData * signatureData = [NSData dataWithBytes:digest length:20];
return [signatureData base64EncodedString];
}
-(NSString *)authorizationHeader{
NSArray * keysAndValues = [NSArray arrayWithObjects:
[NSString stringWithFormat:@"%@=\"%@\"", @"oauth_nonce", [self.nonce encodeForURL]],
[NSString stringWithFormat:@"%@=\"%@\"", @"oauth_signature_method", [[NSString stringWithString:@"HMAC-SHA1"] encodeForURL]],
[NSString stringWithFormat:@"%@=\"%@\"", @"oauth_timestamp", [self.timestamp encodeForURL]],
[NSString stringWithFormat:@"%@=\"%@\"", @"oauth_consumer_key", [self.consumerKey encodeForURL]],
[NSString stringWithFormat:@"%@=\"%@\"", @"oauth_signature", [self.signature encodeForURL]],
[NSString stringWithFormat:@"%@=\"%@\"", @"oauth_version", [[NSString stringWithString:@"1.0"] encodeForURL]],
nil];
if (state == TumblrXAuthStateAPI && self.token && [self.token length] > 0)
keysAndValues = [keysAndValues arrayByAddingObject:[NSString stringWithFormat:@"%@=\"%@\"", @"oauth_token", [self.token encodeForURL]]];
return [NSString stringWithFormat:@"OAuth %@", [keysAndValues componentsJoinedByString:@", "]];
}
-(void)authorize{
//send POST to https://www.tumblr.com/oauth/access_token with parameters: x_auth_username, x_auth_password, x_auth_mode
[self resetTimestamp];
[self resetNonce];
state = TumblrXAuthStateAuthorize;
[tumblrURL release];
tumblrURL = [[NSString stringWithString:@"https://www.tumblr.com/oauth/access_token"] retain];
NSMutableURLRequest* postRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:tumblrURL]];
[postRequest setHTTPMethod: @"POST"];
method = @"POST";
NSArray * parameterArray = [NSArray arrayWithObjects:
[NSString stringWithFormat:@"%@=%@", @"x_auth_mode", @"client_auth"],
[NSString stringWithFormat:@"%@=%@", @"x_auth_password", [self.password encodeForURL]],
[NSString stringWithFormat:@"%@=%@", @"x_auth_username", [self.username encodeForURL]],
nil];
[postRequest setHTTPBody:[[parameterArray componentsJoinedByString:@"&"] dataUsingEncoding:NSUTF8StringEncoding]];
[postRequest addValue:self.authorizationHeader forHTTPHeaderField:@"Authorization"];
[data setLength:0];
[NSURLConnection connectionWithRequest:postRequest delegate:self];
}
-(NSString *)baseString{
//method&url&parameters
NSString * url = nil;
url = [tumblrURL encodeForURL];
NSString * parameters;
NSString * oauth_consumer_key = [self.consumerKey encodeForURL];
NSString * oauth_nonce = [self.nonce encodeForURL];
NSString * oauth_signature_method = [[NSString stringWithString:@"HMAC-SHA1"] encodeForURL];
NSString * oauth_timestamp = [self.timestamp encodeForURL];
NSString * oauth_version = [[NSString stringWithString:@"1.0"] encodeForURL];
NSString * x_auth_mode = [[NSString stringWithString:@"client_auth"] encodeForURL];
NSString * x_auth_password = [self.password encodeForURL];
NSString * x_auth_username = [self.username encodeForURL];
NSArray * params = [NSArray arrayWithObjects:
[NSString stringWithFormat:@"%@%%3D%@", @"oauth_consumer_key", oauth_consumer_key],
[NSString stringWithFormat:@"%@%%3D%@", @"oauth_nonce", oauth_nonce],
[NSString stringWithFormat:@"%@%%3D%@", @"oauth_signature_method", oauth_signature_method],
[NSString stringWithFormat:@"%@%%3D%@", @"oauth_timestamp", oauth_timestamp],
[NSString stringWithFormat:@"%@%%3D%@", @"oauth_version", oauth_version],
nil];
if (state == TumblrXAuthStateAuthorize)
params = [params arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:[NSString stringWithFormat:@"%@%%3D%@", @"x_auth_mode", x_auth_mode],
[NSString stringWithFormat:@"%@%%3D%@", @"x_auth_password", [x_auth_password encodeForURL]],
[NSString stringWithFormat:@"%@%%3D%@", @"x_auth_username", [x_auth_username encodeForURL]],
nil]];
if (state == TumblrXAuthStateAPI){
params = [params arrayByAddingObject:[NSString stringWithFormat:@"%@%%3D%@", @"oauth_token", [self.token encodeForURL]]];
if(currentParams){
for(id key in currentParams){
params = [params arrayByAddingObject:[NSString stringWithFormat:@"%@%%3D%@", key,[[[currentParams objectForKey:key] encodeForURL] encodeForURL]]];
}
}
}
//sort paramaters alphabetically
params = [[params sortedArrayUsingSelector:@selector(compare:)] retain];
parameters = [params componentsJoinedByString:@"%26"];
NSArray *baseComponents = [NSArray arrayWithObjects:method, url, parameters, nil];
NSString *baseString = [baseComponents componentsJoinedByString:@"&"];
return baseString;
}
#pragma mark API Assists
-(void)sendOAuthRequestWithURL:(NSString *)url method:(NSString *)httpMethod parameters:(NSDictionary *)arguments{
[self resetTimestamp];
[self resetNonce];
state = TumblrXAuthStateAPI;
currentParams = arguments;
NSMutableArray * parameterArray = [NSMutableArray array];
if(arguments){for(id key in arguments){[parameterArray addObject:[NSString stringWithFormat:@"%@=%@", [key encodeForURL],[[arguments objectForKey:key] encodeForURL]]];}}
[tumblrURL release];
tumblrURL = [[NSString stringWithString:url] retain];
if([httpMethod isEqual:@"GET"]){
url = [url stringByAppendingFormat:@"?%@",[parameterArray componentsJoinedByString:@"&"]];
}
NSMutableURLRequest* postRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[postRequest setHTTPMethod:httpMethod];
method = httpMethod;
if([httpMethod isEqual:@"POST"]){
[postRequest setHTTPBody:[[parameterArray componentsJoinedByString:@"&"] dataUsingEncoding:NSUTF8StringEncoding]];
}
[postRequest addValue:self.authorizationHeader forHTTPHeaderField:@"Authorization"];
[postRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-type"];
[data setLength:0];
[NSURLConnection connectionWithRequest:postRequest delegate:self];
}
-(void)sendAPIKeyRequestWithURL:(NSString *)url parameters:(NSDictionary *)arguments{
state = TumblrXAuthStateAPI;
NSMutableString *tURL = [NSMutableString stringWithCapacity:30];
[tURL appendString:[NSString stringWithFormat:@"%@?api_key=%@",url,self.consumerKey]];
if(arguments){
for (id key in arguments){
[tURL appendString:[NSString stringWithFormat:@"&%@=%@",key,[arguments objectForKey:key]]];
}
}
NSMutableURLRequest *postRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:tURL]];
[postRequest setHTTPMethod: @"GET"];
[data setLength:0];
[NSURLConnection connectionWithRequest:postRequest delegate:self];
}
#pragma mark NSURLConnection Delegate Methods
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
if (state == TumblrXAuthStateAuthorize && delegate && [delegate respondsToSelector:@selector(tumblrXAuthAuthorizationDidFail:error:)])
[delegate tumblrXAuthAuthorizationDidFail:self error:error];
if (state == TumblrXAuthStateAPI && delegate && [delegate respondsToSelector:@selector(tumblrXAuthDidFail:error:)])
[delegate tumblrXAuthDidFail:self error:error];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)newData{
[data appendData:newData];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
if ([response respondsToSelector:@selector(statusCode)]) {
NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode];
if (statusCode >= 400) {
[connection cancel];
NSDictionary * errorInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:NSLocalizedString(@"Server returned status code %d",@""), statusCode] forKey:NSLocalizedDescriptionKey];
NSError * statusError = [NSError errorWithDomain:@"HTTP Property Status Code" code:statusCode userInfo:errorInfo];
[self connection:connection didFailWithError:statusError];
}
}
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
if (state == TumblrXAuthStateAuthorize) {
NSString * response = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSArray * parameters = [response componentsSeparatedByString:@"&"];
NSMutableDictionary * dictionary = [NSMutableDictionary dictionary];
for (NSString * parameter in parameters) {
NSArray * keyAndValue = [parameter componentsSeparatedByString:@"="];
if (keyAndValue == nil || [keyAndValue count] != 2)
continue;
NSString * key = [keyAndValue objectAtIndex:0];
NSString * value = [keyAndValue lastObject];
[dictionary setObject:value forKey:key];
}
if ([dictionary objectForKey:@"oauth_token_secret"])
self.tokenSecret = [dictionary objectForKey:@"oauth_token_secret"];
if ([dictionary objectForKey:@"oauth_token"])
self.token = [dictionary objectForKey:@"oauth_token"];
if (delegate && [delegate respondsToSelector:@selector(tumblrXAuthDidAuthorize:)])
[delegate tumblrXAuthDidAuthorize:self];
}else if (state == TumblrXAuthStateAPI) {
if (delegate && [delegate respondsToSelector:@selector(tumblrXAuthDidRespond:response:)])
[delegate tumblrXAuthDidRespond:self response:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment