OAMutableURLRequest.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. //
  2. // OAMutableURLRequest.m
  3. // OAuthConsumer
  4. //
  5. // Created by Jon Crosby on 10/19/07.
  6. // Copyright 2007 Kaboomerang LLC. All rights reserved.
  7. //
  8. // Permission is hereby granted, free of charge, to any person obtaining a copy
  9. // of this software and associated documentation files (the "Software"), to deal
  10. // in the Software without restriction, including without limitation the rights
  11. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. // copies of the Software, and to permit persons to whom the Software is
  13. // furnished to do so, subject to the following conditions:
  14. //
  15. // The above copyright notice and this permission notice shall be included in
  16. // all copies or substantial portions of the Software.
  17. //
  18. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. // THE SOFTWARE.
  25. #import "OAMutableURLRequest.h"
  26. #import "OARequestParameter.h"
  27. #import "OAServiceTicket.h"
  28. @interface OAMutableURLRequest ()
  29. - (void)_generateTimestamp;
  30. - (void)_generateNonce;
  31. - (NSString *)_signatureBaseString;
  32. @property (nonatomic, retain) OAConsumer *consumer;
  33. @property (nonatomic, retain) OAToken *token;
  34. @property (nonatomic, retain) NSString *realm;
  35. @property (nonatomic, retain) id <OASignatureProviding> signatureProvider;
  36. @property (nonatomic, retain) NSMutableDictionary *extraOAuthParameters;
  37. @end
  38. @interface NSURL (OABaseAdditions)
  39. - (NSString *)URLStringWithoutQuery;
  40. @end
  41. @implementation NSURL (OABaseAdditions)
  42. - (NSString *)URLStringWithoutQuery {
  43. if (self.absoluteString.length == 0) {
  44. return nil;
  45. }
  46. NSArray *parts = [self.absoluteString componentsSeparatedByString:@"?"];
  47. return (parts.count == 0)?nil:[parts objectAtIndex:0];
  48. }
  49. @end
  50. @implementation OAMutableURLRequest
  51. + (void)fetchDataForRequest:(OAMutableURLRequest *)request withCompletionHandler:(void(^)(OAServiceTicket *, NSData *, NSError *))block {
  52. [request prepare];
  53. [NSURLConnection sendAsynchronousRequest:request queue:[[[NSOperationQueue alloc]init]autorelease] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
  54. OAServiceTicket *ticket = [[[OAServiceTicket alloc]initWithRequest:request response:response didSucceed:(error == nil)]autorelease];
  55. block(ticket, data, error);
  56. }];
  57. }
  58. + (OAMutableURLRequest *)requestWithURL:(NSURL *)aUrl consumer:(OAConsumer *)aConsumer token:(OAToken *)aToken {
  59. return [[[[self class]alloc]initWithURL:aUrl consumer:aConsumer token:aToken realm:nil signatureProvider:nil]autorelease];
  60. }
  61. + (OAMutableURLRequest *)requestWithURL:(NSURL *)aUrl consumer:(OAConsumer *)aConsumer token:(OAToken *)aToken realm:(NSString *)aRealm signatureProvider:(id<OASignatureProviding, NSObject>)aProvider {
  62. return [[[[self class]alloc]initWithURL:aUrl consumer:aConsumer token:aToken realm:aRealm signatureProvider:aProvider]autorelease];
  63. }
  64. - (id)initWithURL:(NSURL *)aUrl consumer:(OAConsumer *)aConsumer token:(OAToken *)aToken realm:(NSString *)aRealm signatureProvider:(id<OASignatureProviding, NSObject>)aProvider {
  65. self = [super initWithURL:aUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:25];
  66. if (self) {
  67. [self setConsumer:aConsumer];
  68. // empty token for Unauthorized Request Token transaction
  69. if (aToken == nil) {
  70. [self setToken:[OAToken token]];
  71. } else {
  72. [self setToken:aToken];
  73. }
  74. if (aRealm == nil) {
  75. [self setRealm:@""];
  76. } else {
  77. [self setRealm:aRealm];
  78. }
  79. // default to HMAC-SHA1
  80. if (aProvider == nil) {
  81. [self setSignatureProvider:[OAHMAC_SHA1SignatureProvider OAHMAC_SHA1SignatureProvider]];
  82. } else {
  83. [self setSignatureProvider:aProvider];
  84. }
  85. [self _generateTimestamp];
  86. [self _generateNonce];
  87. }
  88. return self;
  89. }
  90. // Setting a timestamp and nonce to known values. Can be helpful for testing
  91. - (id)initWithURL:(NSURL *)aUrl consumer:(OAConsumer *)aConsumer token:(OAToken *)aToken realm:(NSString *)aRealm signatureProvider:(id<OASignatureProviding, NSObject>)aProvider nonce:(NSString *)aNonce timestamp:(NSString *)aTimestamp {
  92. self = [super initWithURL:aUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
  93. if (self) {
  94. [self setConsumer:aConsumer];
  95. // empty token for Unauthorized Request Token transaction
  96. if (aToken == nil) {
  97. [self setToken:[OAToken token]];
  98. } else {
  99. [self setToken:aToken];
  100. }
  101. if (aRealm == nil) {
  102. [self setRealm:@""];
  103. } else {
  104. [self setRealm:aRealm];
  105. }
  106. // default to HMAC-SHA1
  107. if (aProvider == nil) {
  108. [self setSignatureProvider:[OAHMAC_SHA1SignatureProvider OAHMAC_SHA1SignatureProvider]];
  109. } else {
  110. [self setSignatureProvider:aProvider];
  111. }
  112. [self setTimestamp:aTimestamp];
  113. [self setNonce:aNonce];
  114. }
  115. return self;
  116. }
  117. - (void)setOAuthParameterName:(NSString*)parameterName withValue:(NSString*)parameterValue {
  118. if (!parameterName && !parameterValue) {
  119. NSLog(@"%s There was not parameter name nor value specified.", __PRETTY_FUNCTION__);
  120. return;
  121. }
  122. if (self.extraOAuthParameters == nil) {
  123. [self setExtraOAuthParameters:[NSMutableDictionary dictionary]];
  124. }
  125. [self.extraOAuthParameters setObject:parameterValue forKey:parameterName];
  126. }
  127. - (void)prepare {
  128. // sign
  129. // Secrets must be urlencoded before concatenated with '&'
  130. // TODO: if later RSA-SHA1 support is added then a little code redesign is needed
  131. self.signature = [self.signatureProvider signClearText:[self _signatureBaseString] withSecret:[NSString stringWithFormat:@"%@&%@", [self.consumer.secret URLEncodedString], [self.token.secret URLEncodedString]]];
  132. // set OAuth headers
  133. NSString *oauthToken;
  134. if ([self.token.key isEqualToString:@""]) {
  135. oauthToken = @"oauth_callback=\"oob\", ";
  136. } else if (self.token.verifier.length == 0) {
  137. oauthToken = [NSString stringWithFormat:@"oauth_token=\"%@\", ", [self.token.key URLEncodedString]];
  138. } else {
  139. oauthToken = [NSString stringWithFormat:@"oauth_token=\"%@\", oauth_verifier=\"%@\", ", [self.token.key URLEncodedString], [self.token.verifier URLEncodedString]];
  140. }
  141. NSMutableString *extraParameters = [NSMutableString string];
  142. // 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.
  143. for (NSString *parameterName in [[self.extraOAuthParameters allKeys]sortedArrayUsingSelector:@selector(compare:)]) {
  144. [extraParameters appendFormat:@", %@=\"%@\"",[parameterName URLEncodedString],[[self.extraOAuthParameters objectForKey:parameterName]URLEncodedString]];
  145. }
  146. 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];
  147. [self setValue:oauthHeader forHTTPHeaderField:@"Authorization"];
  148. }
  149. - (NSArray *)parameters {
  150. NSString *encodedParameters = nil;
  151. if ([self.HTTPMethod isEqualToString:@"GET"] || [self.HTTPMethod isEqualToString:@"DELETE"]) {
  152. encodedParameters = self.URL.query;
  153. } else if ([self.HTTPMethod isEqualToString:@"POST"] || [self.HTTPMethod isEqualToString:@"PUT"]) {
  154. encodedParameters = [[[NSString alloc]initWithData:self.HTTPBody encoding:NSASCIIStringEncoding]autorelease];
  155. }
  156. if (encodedParameters.length == 0) {
  157. return nil;
  158. }
  159. NSArray *encodedParameterPairs = [encodedParameters componentsSeparatedByString:@"&"];
  160. NSMutableArray *requestParameters = [NSMutableArray arrayWithCapacity:16];
  161. for (NSString *encodedPair in encodedParameterPairs) {
  162. NSArray *encodedPairElements = [encodedPair componentsSeparatedByString:@"="];
  163. OARequestParameter *parameter = [OARequestParameter requestParameterWithName:[[encodedPairElements objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] value:[[encodedPairElements objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
  164. [requestParameters addObject:parameter];
  165. }
  166. return requestParameters;
  167. }
  168. - (void)setParameters:(NSArray *)parameters {
  169. NSMutableString *encodedParameterPairs = [NSMutableString stringWithCapacity:256];
  170. int position = 1;
  171. for (OARequestParameter *requestParameter in parameters) {
  172. [encodedParameterPairs appendString:[requestParameter URLEncodedNameValuePair]];
  173. if (position < parameters.count) {
  174. [encodedParameterPairs appendString:@"&"];
  175. }
  176. position++;
  177. }
  178. if ([self.HTTPMethod isEqualToString:@"GET"] || [self.HTTPMethod isEqualToString:@"DELETE"]) {
  179. [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@", [self.URL URLStringWithoutQuery], encodedParameterPairs]]];
  180. } else if ([self.HTTPMethod isEqualToString:@"POST"] || [self.HTTPMethod isEqualToString:@"PUT"]) {
  181. NSData *postData = [encodedParameterPairs dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
  182. [self setHTTPBody:postData];
  183. [self setValue:[NSString stringWithFormat:@"%lu",(unsigned long)postData.length] forHTTPHeaderField:@"Content-Length"];
  184. [self setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
  185. }
  186. }
  187. - (void)_generateTimestamp {
  188. [self setTimestamp:[NSString stringWithFormat:@"%ld", time(nil)]];
  189. }
  190. - (void)_generateNonce {
  191. CFUUIDRef theUUID = CFUUIDCreate(nil);
  192. CFStringRef string = CFUUIDCreateString(nil, theUUID);
  193. CFRelease(theUUID);
  194. [self setNonce:[NSString stringWithString:(NSString *)string]];
  195. CFRelease(string);
  196. }
  197. - (NSString *)_signatureBaseString {
  198. // OAuth Spec, Section 9.1.1 "Normalize Request Parameters"
  199. // build a sorted array of both request parameters and OAuth header parameters
  200. NSArray *parameters = [self parameters];
  201. NSMutableArray *parameterPairs = [NSMutableArray arrayWithCapacity:(6+parameters.count)]; // 6 being the number of OAuth params in the Signature Base String
  202. [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_consumer_key" value:self.consumer.key]URLEncodedNameValuePair]];
  203. [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_signature_method" value:[self.signatureProvider name]] URLEncodedNameValuePair]];
  204. [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_timestamp" value:self.timestamp]URLEncodedNameValuePair]];
  205. [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_nonce" value:self.nonce]URLEncodedNameValuePair]];
  206. [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_version" value:@"1.0"]URLEncodedNameValuePair]];
  207. if (self.token.key.length > 0) {
  208. [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_token" value:self.token.key]URLEncodedNameValuePair]];
  209. if (self.token.verifier.length > 0) {
  210. [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_verifier" value:self.token.verifier]URLEncodedNameValuePair]];
  211. }
  212. } else {
  213. [parameterPairs addObject:[[OARequestParameter requestParameterWithName:@"oauth_callback" value:@"oob"]URLEncodedNameValuePair]];
  214. }
  215. for (OARequestParameter *param in parameters) {
  216. [parameterPairs addObject:[param URLEncodedNameValuePair]];
  217. }
  218. NSArray *sortedPairs = [parameterPairs sortedArrayUsingSelector:@selector(compare:)];
  219. NSString *normalizedRequestParameters = [sortedPairs componentsJoinedByString:@"&"];
  220. // OAuth Spec, Section 9.1.2 "Concatenate Request Elements"
  221. NSString *ret = [NSString stringWithFormat:@"%@&%@&%@", self.HTTPMethod, [[self.URL URLStringWithoutQuery]URLEncodedString], [normalizedRequestParameters URLEncodedString]];
  222. return ret;
  223. }
  224. - (void)dealloc {
  225. [self setExtraOAuthParameters:nil];
  226. [self setConsumer:nil];
  227. [self setToken:nil];
  228. [self setRealm:nil];
  229. [self setSignatureProvider:nil];
  230. [self setTimestamp:nil];
  231. [self setNonce:nil];
  232. [self setSignature:nil];
  233. [super dealloc];
  234. }
  235. @end