axmol/plugin/plugins/twitter/proj.ios/FHSTwitterEngine/OAuthConsumer/OAMutableURLRequest.m

297 lines
13 KiB
Objective-C
Executable File

//
// OAMutableURLRequest.m
// OAuthConsumer
//
// Created by Jon Crosby on 10/19/07.
// Copyright 2007 Kaboomerang LLC. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "OAMutableURLRequest.h"
#import "OARequestParameter.h"
#import "OAServiceTicket.h"
@interface OAMutableURLRequest ()
- (void)_generateTimestamp;
- (void)_generateNonce;
- (NSString *)_signatureBaseString;
@property (nonatomic, retain) OAConsumer *consumer;
@property (nonatomic, retain) OAToken *token;
@property (nonatomic, retain) NSString *realm;
@property (nonatomic, retain) id <OASignatureProviding> signatureProvider;
@property (nonatomic, retain) NSMutableDictionary *extraOAuthParameters;
@end
@interface NSURL (OABaseAdditions)
- (NSString *)URLStringWithoutQuery;
@end
@implementation NSURL (OABaseAdditions)
- (NSString *)URLStringWithoutQuery {
if (self.absoluteString.length == 0) {
return nil;
}
NSArray *parts = [self.absoluteString componentsSeparatedByString:@"?"];
return (parts.count == 0)?nil:[parts objectAtIndex:0];
}
@end
@implementation OAMutableURLRequest
+ (void)fetchDataForRequest:(OAMutableURLRequest *)request withCompletionHandler:(void(^)(OAServiceTicket *, NSData *, NSError *))block {
[request prepare];
[NSURLConnection sendAsynchronousRequest:request queue:[[[NSOperationQueue alloc]init]autorelease] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
OAServiceTicket *ticket = [[[OAServiceTicket alloc]initWithRequest:request response:response didSucceed:(error == nil)]autorelease];
block(ticket, data, error);
}];
}
+ (OAMutableURLRequest *)requestWithURL:(NSURL *)aUrl consumer:(OAConsumer *)aConsumer token:(OAToken *)aToken {
return [[[[self class]alloc]initWithURL:aUrl consumer:aConsumer token:aToken realm:nil signatureProvider:nil]autorelease];
}
+ (OAMutableURLRequest *)requestWithURL:(NSURL *)aUrl consumer:(OAConsumer *)aConsumer token:(OAToken *)aToken realm:(NSString *)aRealm signatureProvider:(id<OASignatureProviding, NSObject>)aProvider {
return [[[[self class]alloc]initWithURL:aUrl consumer:aConsumer token:aToken realm:aRealm signatureProvider:aProvider]autorelease];
}
- (id)initWithURL:(NSURL *)aUrl consumer:(OAConsumer *)aConsumer token:(OAToken *)aToken realm:(NSString *)aRealm signatureProvider:(id<OASignatureProviding, NSObject>)aProvider {
self = [super initWithURL:aUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:25];
if (self) {
[self setConsumer:aConsumer];
// empty token for Unauthorized Request Token transaction
if (aToken == nil) {
[self setToken:[OAToken token]];
} else {
[self setToken:aToken];
}
if (aRealm == nil) {
[self setRealm:@""];
} else {
[self setRealm:aRealm];
}
// default to HMAC-SHA1
if (aProvider == nil) {
[self setSignatureProvider:[OAHMAC_SHA1SignatureProvider OAHMAC_SHA1SignatureProvider]];
} else {
[self setSignatureProvider:aProvider];
}
[self _generateTimestamp];
[self _generateNonce];
}
return self;
}
// Setting a timestamp and nonce to known values. Can be helpful for testing
- (id)initWithURL:(NSURL *)aUrl consumer:(OAConsumer *)aConsumer token:(OAToken *)aToken realm:(NSString *)aRealm signatureProvider:(id<OASignatureProviding, NSObject>)aProvider nonce:(NSString *)aNonce timestamp:(NSString *)aTimestamp {
self = [super initWithURL:aUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
if (self) {
[self setConsumer:aConsumer];
// empty token for Unauthorized Request Token transaction
if (aToken == nil) {
[self setToken:[OAToken token]];
} else {
[self setToken:aToken];
}
if (aRealm == nil) {
[self setRealm:@""];
} else {
[self setRealm:aRealm];
}
// default to HMAC-SHA1
if (aProvider == nil) {
[self setSignatureProvider:[OAHMAC_SHA1SignatureProvider OAHMAC_SHA1SignatureProvider]];
} else {
[self setSignatureProvider:aProvider];
}
[self setTimestamp:aTimestamp];
[self setNonce:aNonce];
}
return self;
}
- (void)setOAuthParameterName:(NSString*)parameterName withValue:(NSString*)parameterValue {
if (!parameterName && !parameterValue) {
NSLog(@"%s There was not parameter name nor value specified.", __PRETTY_FUNCTION__);
return;
}
if (self.extraOAuthParameters == nil) {
[self setExtraOAuthParameters:[NSMutableDictionary dictionary]];
}
[self.extraOAuthParameters setObject:parameterValue forKey:parameterName];
}
- (void)prepare {
// sign
// Secrets must be urlencoded before concatenated with '&'
// TODO: if later RSA-SHA1 support is added then a little code redesign is needed
self.signature = [self.signatureProvider signClearText:[self _signatureBaseString] withSecret:[NSString stringWithFormat:@"%@&%@", [self.consumer.secret URLEncodedString], [self.token.secret URLEncodedString]]];
// set OAuth headers
NSString *oauthToken;
if ([self.token.key isEqualToString:@""]) {
oauthToken = @"oauth_callback=\"oob\", ";
} else if (self.token.verifier.length == 0) {
oauthToken = [NSString stringWithFormat:@"oauth_token=\"%@\", ", [self.token.key URLEncodedString]];
} else {
oauthToken = [NSString stringWithFormat:@"oauth_token=\"%@\", oauth_verifier=\"%@\", ", [self.token.key URLEncodedString], [self.token.verifier URLEncodedString]];
}
NSMutableString *extraParameters = [NSMutableString string];
// Adding the optional parameters in sorted order isn't required by the OAuth spec, but it makes it possible to hard-code expected values in the unit tests.
for (NSString *parameterName in [[self.extraOAuthParameters allKeys]sortedArrayUsingSelector:@selector(compare:)]) {
[extraParameters appendFormat:@", %@=\"%@\"",[parameterName URLEncodedString],[[self.extraOAuthParameters objectForKey:parameterName]URLEncodedString]];
}
NSString *oauthHeader = [NSString stringWithFormat:@"OAuth realm=\"%@\", oauth_consumer_key=\"%@\", %@oauth_signature_method=\"%@\", oauth_signature=\"%@\", oauth_timestamp=\"%@\", oauth_nonce=\"%@\", oauth_version=\"1.0\"%@", [self.realm URLEncodedString], [self.consumer.key URLEncodedString], oauthToken, [[self.signatureProvider name] URLEncodedString], [self.signature URLEncodedString], self.timestamp, self.nonce, extraParameters];
[self setValue:oauthHeader forHTTPHeaderField:@"Authorization"];
}
- (NSArray *)parameters {
NSString *encodedParameters = nil;
if ([self.HTTPMethod isEqualToString:@"GET"] || [self.HTTPMethod isEqualToString:@"DELETE"]) {
encodedParameters = self.URL.query;
} else if ([self.HTTPMethod isEqualToString:@"POST"] || [self.HTTPMethod isEqualToString:@"PUT"]) {
encodedParameters = [[[NSString alloc]initWithData:self.HTTPBody encoding:NSASCIIStringEncoding]autorelease];
}
if (encodedParameters.length == 0) {
return nil;
}
NSArray *encodedParameterPairs = [encodedParameters componentsSeparatedByString:@"&"];
NSMutableArray *requestParameters = [NSMutableArray arrayWithCapacity:16];
for (NSString *encodedPair in encodedParameterPairs) {
NSArray *encodedPairElements = [encodedPair componentsSeparatedByString:@"="];
OARequestParameter *parameter = [OARequestParameter requestParameterWithName:[[encodedPairElements objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] value:[[encodedPairElements objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
[requestParameters addObject:parameter];
}
return requestParameters;
}
- (void)setParameters:(NSArray *)parameters {
NSMutableString *encodedParameterPairs = [NSMutableString stringWithCapacity:256];
int position = 1;
for (OARequestParameter *requestParameter in parameters) {
[encodedParameterPairs appendString:[requestParameter URLEncodedNameValuePair]];
if (position < parameters.count) {
[encodedParameterPairs appendString:@"&"];
}
position++;
}
if ([self.HTTPMethod isEqualToString:@"GET"] || [self.HTTPMethod isEqualToString:@"DELETE"]) {
[self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", [self.URL URLStringWithoutQuery], encodedParameterPairs]]];
} else if ([self.HTTPMethod isEqualToString:@"POST"] || [self.HTTPMethod isEqualToString:@"PUT"]) {
NSData *postData = [encodedParameterPairs dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
[self setHTTPBody:postData];
[self setValue:[NSString stringWithFormat:@"%d",postData.length] forHTTPHeaderField:@"Content-Length"];
[self setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
}
- (void)_generateTimestamp {
[self setTimestamp:[NSString stringWithFormat:@"%ld", time(nil)]];
}
- (void)_generateNonce {
CFUUIDRef theUUID = CFUUIDCreate(nil);
CFStringRef string = CFUUIDCreateString(nil, theUUID);
CFRelease(theUUID);
[self setNonce:[NSString stringWithString:(NSString *)string]];
CFRelease(string);
}
- (NSString *)_signatureBaseString {
// OAuth Spec, Section 9.1.1 "Normalize Request Parameters"
// build a sorted array of both request parameters and OAuth header parameters
NSArray *parameters = [self parameters];
NSMutableArray *parameterPairs = [NSMutableArray arrayWithCapacity:(6+parameters.count)]; // 6 being the number of OAuth params in the Signature Base String
[parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_consumer_key" value:self.consumer.key]URLEncodedNameValuePair]];
[parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_signature_method" value:[self.signatureProvider name]] URLEncodedNameValuePair]];
[parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_timestamp" value:self.timestamp]URLEncodedNameValuePair]];
[parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_nonce" value:self.nonce]URLEncodedNameValuePair]];
[parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_version" value:@"1.0"]URLEncodedNameValuePair]];
if (self.token.key.length > 0) {
[parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_token" value:self.token.key]URLEncodedNameValuePair]];
if (self.token.verifier.length > 0) {
[parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_verifier" value:self.token.verifier]URLEncodedNameValuePair]];
}
} else {
[parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_callback" value:@"oob"]URLEncodedNameValuePair]];
}
for (OARequestParameter *param in parameters) {
[parameterPairs addObject:[param URLEncodedNameValuePair]];
}
NSArray *sortedPairs = [parameterPairs sortedArrayUsingSelector:@selector(compare:)];
NSString *normalizedRequestParameters = [sortedPairs componentsJoinedByString:@"&"];
// OAuth Spec, Section 9.1.2 "Concatenate Request Elements"
NSString *ret = [NSString stringWithFormat:@"%@&%@&%@", self.HTTPMethod, [[self.URL URLStringWithoutQuery]URLEncodedString], [normalizedRequestParameters URLEncodedString]];
return ret;
}
- (void)dealloc {
[self setExtraOAuthParameters:nil];
[self setConsumer:nil];
[self setToken:nil];
[self setRealm:nil];
[self setSignatureProvider:nil];
[self setTimestamp:nil];
[self setNonce:nil];
[self setSignature:nil];
[super dealloc];
}
@end