/Source/externals/GData/Source/OAuth2/GTMOAuth2Authentication.m

http://google-email-uploader-mac.googlecode.com/ · Objective C · 1327 lines · 1000 code · 208 blank · 119 comment · 150 complexity · e9c8636600a1908fa54f9fff67dfb0c6 MD5 · raw file

  1. /* Copyright (c) 2011 Google Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
  16. #import "GTMOAuth2Authentication.h"
  17. // Extern strings
  18. NSString *const kGTMOAuth2ServiceProviderGoogle = @"Google";
  19. NSString *const kGTMOAuth2ErrorDomain = @"com.google.GTMOAuth2";
  20. NSString *const kGTMOAuth2ErrorMessageKey = @"error";
  21. NSString *const kGTMOAuth2ErrorRequestKey = @"request";
  22. NSString *const kGTMOAuth2ErrorJSONKey = @"json";
  23. // Notifications
  24. NSString *const kGTMOAuth2FetchStarted = @"kGTMOAuth2FetchStarted";
  25. NSString *const kGTMOAuth2FetchStopped = @"kGTMOAuth2FetchStopped";
  26. NSString *const kGTMOAuth2FetcherKey = @"fetcher";
  27. NSString *const kGTMOAuth2FetchTypeKey = @"FetchType";
  28. NSString *const kGTMOAuth2FetchTypeToken = @"token";
  29. NSString *const kGTMOAuth2FetchTypeRefresh = @"refresh";
  30. NSString *const kGTMOAuth2FetchTypeAssertion = @"assertion";
  31. NSString *const kGTMOAuth2FetchTypeUserInfo = @"userInfo";
  32. NSString *const kGTMOAuth2ErrorKey = @"error";
  33. NSString *const kGTMOAuth2ErrorObjectKey = @"kGTMOAuth2ErrorObjectKey";
  34. NSString *const kGTMOAuth2ErrorInvalidRequest = @"invalid_request";
  35. NSString *const kGTMOAuth2ErrorInvalidClient = @"invalid_client";
  36. NSString *const kGTMOAuth2ErrorInvalidGrant = @"invalid_grant";
  37. NSString *const kGTMOAuth2ErrorUnauthorizedClient = @"unauthorized_client";
  38. NSString *const kGTMOAuth2ErrorUnsupportedGrantType = @"unsupported_grant_type";
  39. NSString *const kGTMOAuth2ErrorInvalidScope = @"invalid_scope";
  40. NSString *const kGTMOAuth2UserSignedIn = @"kGTMOAuth2UserSignedIn";
  41. NSString *const kGTMOAuth2AccessTokenRefreshed = @"kGTMOAuth2AccessTokenRefreshed";
  42. NSString *const kGTMOAuth2RefreshTokenChanged = @"kGTMOAuth2RefreshTokenChanged";
  43. NSString *const kGTMOAuth2AccessTokenRefreshFailed = @"kGTMOAuth2AccessTokenRefreshFailed";
  44. NSString *const kGTMOAuth2WebViewStartedLoading = @"kGTMOAuth2WebViewStartedLoading";
  45. NSString *const kGTMOAuth2WebViewStoppedLoading = @"kGTMOAuth2WebViewStoppedLoading";
  46. NSString *const kGTMOAuth2WebViewKey = @"kGTMOAuth2WebViewKey";
  47. NSString *const kGTMOAuth2WebViewStopKindKey = @"kGTMOAuth2WebViewStopKindKey";
  48. NSString *const kGTMOAuth2WebViewFinished = @"finished";
  49. NSString *const kGTMOAuth2WebViewFailed = @"failed";
  50. NSString *const kGTMOAuth2WebViewCancelled = @"cancelled";
  51. NSString *const kGTMOAuth2NetworkLost = @"kGTMOAuthNetworkLost";
  52. NSString *const kGTMOAuth2NetworkFound = @"kGTMOAuthNetworkFound";
  53. // standard OAuth keys
  54. static NSString *const kOAuth2AccessTokenKey = @"access_token";
  55. static NSString *const kOAuth2RefreshTokenKey = @"refresh_token";
  56. static NSString *const kOAuth2ScopeKey = @"scope";
  57. static NSString *const kOAuth2ErrorKey = @"error";
  58. static NSString *const kOAuth2TokenTypeKey = @"token_type";
  59. static NSString *const kOAuth2ExpiresInKey = @"expires_in";
  60. static NSString *const kOAuth2CodeKey = @"code";
  61. static NSString *const kOAuth2AssertionKey = @"assertion";
  62. static NSString *const kOAuth2RefreshScopeKey = @"refreshScope";
  63. // additional persistent keys
  64. static NSString *const kServiceProviderKey = @"serviceProvider";
  65. static NSString *const kUserIDKey = @"userID";
  66. static NSString *const kUserEmailKey = @"email";
  67. static NSString *const kUserEmailIsVerifiedKey = @"isVerified";
  68. // fetcher keys
  69. static NSString *const kTokenFetchDelegateKey = @"delegate";
  70. static NSString *const kTokenFetchSelectorKey = @"sel";
  71. // If GTMNSJSONSerialization is available, it is used for formatting JSON
  72. #if (TARGET_OS_MAC && !TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED < 1070)) || \
  73. (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED < 50000))
  74. @interface GTMNSJSONSerialization : NSObject
  75. + (id)JSONObjectWithData:(NSData *)data options:(NSUInteger)opt error:(NSError **)error;
  76. @end
  77. #endif
  78. @interface GTMOAuth2ParserClass : NSObject
  79. // just enough of SBJSON to be able to parse
  80. - (id)objectWithString:(NSString*)repr error:(NSError**)error;
  81. @end
  82. // wrapper class for requests needing authorization and their callbacks
  83. @interface GTMOAuth2AuthorizationArgs : NSObject {
  84. @private
  85. NSMutableURLRequest *request_;
  86. id delegate_;
  87. SEL sel_;
  88. id completionHandler_;
  89. NSThread *thread_;
  90. NSError *error_;
  91. }
  92. @property (retain) NSMutableURLRequest *request;
  93. @property (retain) id delegate;
  94. @property (assign) SEL selector;
  95. @property (copy) id completionHandler;
  96. @property (retain) NSThread *thread;
  97. @property (retain) NSError *error;
  98. + (GTMOAuth2AuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req
  99. delegate:(id)delegate
  100. selector:(SEL)sel
  101. completionHandler:(id)completionHandler
  102. thread:(NSThread *)thread;
  103. @end
  104. @implementation GTMOAuth2AuthorizationArgs
  105. @synthesize request = request_,
  106. delegate = delegate_,
  107. selector = sel_,
  108. completionHandler = completionHandler_,
  109. thread = thread_,
  110. error = error_;
  111. + (GTMOAuth2AuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req
  112. delegate:(id)delegate
  113. selector:(SEL)sel
  114. completionHandler:(id)completionHandler
  115. thread:(NSThread *)thread {
  116. GTMOAuth2AuthorizationArgs *obj;
  117. obj = [[[GTMOAuth2AuthorizationArgs alloc] init] autorelease];
  118. obj.request = req;
  119. obj.delegate = delegate;
  120. obj.selector = sel;
  121. obj.completionHandler = completionHandler;
  122. obj.thread = thread;
  123. return obj;
  124. }
  125. - (void)dealloc {
  126. [request_ release];
  127. [delegate_ release];
  128. [completionHandler_ release];
  129. [thread_ release];
  130. [error_ release];
  131. [super dealloc];
  132. }
  133. @end
  134. @interface GTMOAuth2Authentication ()
  135. @property (retain) NSMutableArray *authorizationQueue;
  136. @property (readonly) NSString *authorizationToken;
  137. - (void)setKeysForResponseJSONData:(NSData *)data;
  138. - (BOOL)authorizeRequestArgs:(GTMOAuth2AuthorizationArgs *)args;
  139. - (BOOL)authorizeRequestImmediateArgs:(GTMOAuth2AuthorizationArgs *)args;
  140. - (BOOL)shouldRefreshAccessToken;
  141. - (void)updateExpirationDate;
  142. - (void)tokenFetcher:(GTMHTTPFetcher *)fetcher
  143. finishedWithData:(NSData *)data
  144. error:(NSError *)error;
  145. - (void)auth:(GTMOAuth2Authentication *)auth
  146. finishedRefreshWithFetcher:(GTMHTTPFetcher *)fetcher
  147. error:(NSError *)error;
  148. - (void)invokeCallbackArgs:(GTMOAuth2AuthorizationArgs *)args;
  149. + (void)invokeDelegate:(id)delegate
  150. selector:(SEL)sel
  151. object:(id)obj1
  152. object:(id)obj2
  153. object:(id)obj3;
  154. + (NSString *)unencodedOAuthParameterForString:(NSString *)str;
  155. + (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict;
  156. + (NSDictionary *)dictionaryWithResponseData:(NSData *)data;
  157. @end
  158. @implementation GTMOAuth2Authentication
  159. @synthesize clientID = clientID_,
  160. clientSecret = clientSecret_,
  161. redirectURI = redirectURI_,
  162. parameters = parameters_,
  163. authorizationTokenKey = authorizationTokenKey_,
  164. tokenURL = tokenURL_,
  165. expirationDate = expirationDate_,
  166. additionalTokenRequestParameters = additionalTokenRequestParameters_,
  167. additionalGrantTypeRequestParameters = additionalGrantTypeRequestParameters_,
  168. refreshFetcher = refreshFetcher_,
  169. fetcherService = fetcherService_,
  170. parserClass = parserClass_,
  171. shouldAuthorizeAllRequests = shouldAuthorizeAllRequests_,
  172. userData = userData_,
  173. properties = properties_,
  174. authorizationQueue = authorizationQueue_;
  175. // Response parameters
  176. @dynamic accessToken,
  177. refreshToken,
  178. code,
  179. assertion,
  180. refreshScope,
  181. errorString,
  182. tokenType,
  183. scope,
  184. expiresIn,
  185. serviceProvider,
  186. userEmail,
  187. userEmailIsVerified;
  188. @dynamic canAuthorize;
  189. + (id)authenticationWithServiceProvider:(NSString *)serviceProvider
  190. tokenURL:(NSURL *)tokenURL
  191. redirectURI:(NSString *)redirectURI
  192. clientID:(NSString *)clientID
  193. clientSecret:(NSString *)clientSecret {
  194. GTMOAuth2Authentication *obj = [[[self alloc] init] autorelease];
  195. obj.serviceProvider = serviceProvider;
  196. obj.tokenURL = tokenURL;
  197. obj.redirectURI = redirectURI;
  198. obj.clientID = clientID;
  199. obj.clientSecret = clientSecret;
  200. return obj;
  201. }
  202. - (id)init {
  203. self = [super init];
  204. if (self) {
  205. authorizationQueue_ = [[NSMutableArray alloc] init];
  206. parameters_ = [[NSMutableDictionary alloc] init];
  207. }
  208. return self;
  209. }
  210. - (NSString *)description {
  211. NSArray *props = [NSArray arrayWithObjects:@"accessToken", @"refreshToken",
  212. @"code", @"assertion", @"expirationDate", @"errorString",
  213. nil];
  214. NSMutableString *valuesStr = [NSMutableString string];
  215. NSString *separator = @"";
  216. for (NSString *prop in props) {
  217. id result = [self valueForKey:prop];
  218. if (result) {
  219. [valuesStr appendFormat:@"%@%@=\"%@\"", separator, prop, result];
  220. separator = @", ";
  221. }
  222. }
  223. return [NSString stringWithFormat:@"%@ %p: {%@}",
  224. [self class], self, valuesStr];
  225. }
  226. - (void)dealloc {
  227. [clientID_ release];
  228. [clientSecret_ release];
  229. [redirectURI_ release];
  230. [parameters_ release];
  231. [authorizationTokenKey_ release];
  232. [tokenURL_ release];
  233. [expirationDate_ release];
  234. [additionalTokenRequestParameters_ release];
  235. [additionalGrantTypeRequestParameters_ release];
  236. [refreshFetcher_ release];
  237. [authorizationQueue_ release];
  238. [userData_ release];
  239. [properties_ release];
  240. [super dealloc];
  241. }
  242. #pragma mark -
  243. - (void)setKeysForResponseDictionary:(NSDictionary *)dict {
  244. if (dict == nil) return;
  245. // If a new code or access token is being set, remove the old expiration
  246. NSString *newCode = [dict objectForKey:kOAuth2CodeKey];
  247. NSString *newAccessToken = [dict objectForKey:kOAuth2AccessTokenKey];
  248. if (newCode || newAccessToken) {
  249. self.expiresIn = nil;
  250. }
  251. BOOL didRefreshTokenChange = NO;
  252. NSString *refreshToken = [dict objectForKey:kOAuth2RefreshTokenKey];
  253. if (refreshToken) {
  254. NSString *priorRefreshToken = self.refreshToken;
  255. if (priorRefreshToken != refreshToken
  256. && (priorRefreshToken == nil
  257. || ![priorRefreshToken isEqual:refreshToken])) {
  258. didRefreshTokenChange = YES;
  259. }
  260. }
  261. [self.parameters addEntriesFromDictionary:dict];
  262. [self updateExpirationDate];
  263. if (didRefreshTokenChange) {
  264. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  265. [nc postNotificationName:kGTMOAuth2RefreshTokenChanged
  266. object:self
  267. userInfo:nil];
  268. }
  269. // NSLog(@"keys set ----------------------------\n%@", dict);
  270. }
  271. - (void)setKeysForResponseString:(NSString *)str {
  272. NSDictionary *dict = [[self class] dictionaryWithResponseString:str];
  273. [self setKeysForResponseDictionary:dict];
  274. }
  275. - (void)setKeysForResponseJSONData:(NSData *)data {
  276. NSDictionary *dict = [[self class] dictionaryWithJSONData:data];
  277. [self setKeysForResponseDictionary:dict];
  278. }
  279. + (NSDictionary *)dictionaryWithJSONData:(NSData *)data {
  280. NSMutableDictionary *obj = nil;
  281. NSError *error = nil;
  282. Class serializer = NSClassFromString(@"NSJSONSerialization");
  283. if (serializer) {
  284. const NSUInteger kOpts = (1UL << 0); // NSJSONReadingMutableContainers
  285. obj = [serializer JSONObjectWithData:data
  286. options:kOpts
  287. error:&error];
  288. #if DEBUG
  289. if (error) {
  290. NSString *str = [[[NSString alloc] initWithData:data
  291. encoding:NSUTF8StringEncoding] autorelease];
  292. NSLog(@"NSJSONSerialization error %@ parsing %@",
  293. error, str);
  294. }
  295. #endif
  296. return obj;
  297. } else {
  298. // try SBJsonParser or SBJSON
  299. Class jsonParseClass = NSClassFromString(@"SBJsonParser");
  300. if (!jsonParseClass) {
  301. jsonParseClass = NSClassFromString(@"SBJSON");
  302. }
  303. if (jsonParseClass) {
  304. GTMOAuth2ParserClass *parser = [[[jsonParseClass alloc] init] autorelease];
  305. NSString *jsonStr = [[[NSString alloc] initWithData:data
  306. encoding:NSUTF8StringEncoding] autorelease];
  307. if (jsonStr) {
  308. obj = [parser objectWithString:jsonStr error:&error];
  309. #if DEBUG
  310. if (error) {
  311. NSLog(@"%@ error %@ parsing %@", NSStringFromClass(jsonParseClass),
  312. error, jsonStr);
  313. }
  314. #endif
  315. return obj;
  316. }
  317. } else {
  318. #if DEBUG
  319. NSAssert(0, @"GTMOAuth2Authentication: No parser available");
  320. #endif
  321. }
  322. }
  323. return nil;
  324. }
  325. #pragma mark Authorizing Requests
  326. // General entry point for authorizing requests
  327. #if NS_BLOCKS_AVAILABLE
  328. // Authorizing with a completion block
  329. - (void)authorizeRequest:(NSMutableURLRequest *)request
  330. completionHandler:(void (^)(NSError *error))handler {
  331. GTMOAuth2AuthorizationArgs *args;
  332. args = [GTMOAuth2AuthorizationArgs argsWithRequest:request
  333. delegate:nil
  334. selector:NULL
  335. completionHandler:handler
  336. thread:[NSThread currentThread]];
  337. [self authorizeRequestArgs:args];
  338. }
  339. #endif
  340. // Authorizing with a callback selector
  341. //
  342. // Selector has the signature
  343. // - (void)authentication:(GTMOAuth2Authentication *)auth
  344. // request:(NSMutableURLRequest *)request
  345. // finishedWithError:(NSError *)error;
  346. - (void)authorizeRequest:(NSMutableURLRequest *)request
  347. delegate:(id)delegate
  348. didFinishSelector:(SEL)sel {
  349. GTMAssertSelectorNilOrImplementedWithArgs(delegate, sel,
  350. @encode(GTMOAuth2Authentication *),
  351. @encode(NSMutableURLRequest *),
  352. @encode(NSError *), 0);
  353. GTMOAuth2AuthorizationArgs *args;
  354. args = [GTMOAuth2AuthorizationArgs argsWithRequest:request
  355. delegate:delegate
  356. selector:sel
  357. completionHandler:nil
  358. thread:[NSThread currentThread]];
  359. [self authorizeRequestArgs:args];
  360. }
  361. // Internal routine common to delegate and block invocations
  362. - (BOOL)authorizeRequestArgs:(GTMOAuth2AuthorizationArgs *)args {
  363. BOOL didAttempt = NO;
  364. @synchronized(authorizationQueue_) {
  365. BOOL shouldRefresh = [self shouldRefreshAccessToken];
  366. if (shouldRefresh) {
  367. // attempt to refresh now; once we have a fresh access token, we will
  368. // authorize the request and call back to the user
  369. didAttempt = YES;
  370. if (self.refreshFetcher == nil) {
  371. // there's not already a refresh pending
  372. SEL finishedSel = @selector(auth:finishedRefreshWithFetcher:error:);
  373. self.refreshFetcher = [self beginTokenFetchWithDelegate:self
  374. didFinishSelector:finishedSel];
  375. if (self.refreshFetcher) {
  376. [authorizationQueue_ addObject:args];
  377. }
  378. } else {
  379. // there's already a refresh pending
  380. [authorizationQueue_ addObject:args];
  381. }
  382. }
  383. if (!shouldRefresh || self.refreshFetcher == nil) {
  384. // we're not fetching a new access token, so we can authorize the request
  385. // now
  386. didAttempt = [self authorizeRequestImmediateArgs:args];
  387. }
  388. }
  389. return didAttempt;
  390. }
  391. - (void)auth:(GTMOAuth2Authentication *)auth
  392. finishedRefreshWithFetcher:(GTMHTTPFetcher *)fetcher
  393. error:(NSError *)error {
  394. @synchronized(authorizationQueue_) {
  395. // If there's an error, we want to try using the old access token anyway,
  396. // in case it's a backend problem preventing refresh, in which case
  397. // access tokens past their expiration date may still work
  398. self.refreshFetcher = nil;
  399. // Swap in a new auth queue in case the callbacks try to immediately auth
  400. // another request
  401. NSArray *pendingAuthQueue = [NSArray arrayWithArray:authorizationQueue_];
  402. [authorizationQueue_ removeAllObjects];
  403. BOOL hasAccessToken = ([self.accessToken length] > 0);
  404. NSString *noteName;
  405. NSDictionary *userInfo = nil;
  406. if (hasAccessToken && error == nil) {
  407. // Successful refresh.
  408. noteName = kGTMOAuth2AccessTokenRefreshed;
  409. userInfo = nil;
  410. } else {
  411. // Google's OAuth 2 implementation returns a 400 with JSON body
  412. // containing error key "invalid_grant" to indicate the refresh token
  413. // is invalid or has been revoked by the user. We'll promote the
  414. // JSON error key's value for easy inspection by the observer.
  415. noteName = kGTMOAuth2AccessTokenRefreshFailed;
  416. NSString *jsonErr = nil;
  417. if ([error code] == kGTMHTTPFetcherStatusBadRequest) {
  418. NSDictionary *json = [[error userInfo] objectForKey:kGTMOAuth2ErrorJSONKey];
  419. jsonErr = [json objectForKey:kGTMOAuth2ErrorMessageKey];
  420. }
  421. // error and jsonErr may be nil
  422. userInfo = [NSMutableDictionary dictionary];
  423. [userInfo setValue:error forKey:kGTMOAuth2ErrorObjectKey];
  424. [userInfo setValue:jsonErr forKey:kGTMOAuth2ErrorMessageKey];
  425. }
  426. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  427. [nc postNotificationName:noteName
  428. object:self
  429. userInfo:userInfo];
  430. for (GTMOAuth2AuthorizationArgs *args in pendingAuthQueue) {
  431. if (!hasAccessToken && args.error == nil) {
  432. args.error = error;
  433. }
  434. [self authorizeRequestImmediateArgs:args];
  435. }
  436. }
  437. }
  438. - (BOOL)isAuthorizingRequest:(NSURLRequest *)request {
  439. BOOL wasFound = NO;
  440. @synchronized(authorizationQueue_) {
  441. for (GTMOAuth2AuthorizationArgs *args in authorizationQueue_) {
  442. if ([args request] == request) {
  443. wasFound = YES;
  444. break;
  445. }
  446. }
  447. }
  448. return wasFound;
  449. }
  450. - (BOOL)isAuthorizedRequest:(NSURLRequest *)request {
  451. NSString *authStr = [request valueForHTTPHeaderField:@"Authorization"];
  452. return ([authStr length] > 0);
  453. }
  454. - (void)stopAuthorization {
  455. @synchronized(authorizationQueue_) {
  456. [authorizationQueue_ removeAllObjects];
  457. [self.refreshFetcher stopFetching];
  458. self.refreshFetcher = nil;
  459. }
  460. }
  461. - (void)stopAuthorizationForRequest:(NSURLRequest *)request {
  462. @synchronized(authorizationQueue_) {
  463. NSUInteger argIndex = 0;
  464. BOOL found = NO;
  465. for (GTMOAuth2AuthorizationArgs *args in authorizationQueue_) {
  466. if ([args request] == request) {
  467. found = YES;
  468. break;
  469. }
  470. argIndex++;
  471. }
  472. if (found) {
  473. [authorizationQueue_ removeObjectAtIndex:argIndex];
  474. // If the queue is now empty, go ahead and stop the fetcher.
  475. if ([authorizationQueue_ count] == 0) {
  476. [self stopAuthorization];
  477. }
  478. }
  479. }
  480. }
  481. - (BOOL)authorizeRequestImmediateArgs:(GTMOAuth2AuthorizationArgs *)args {
  482. // This authorization entry point never attempts to refresh the access token,
  483. // but does call the completion routine
  484. NSMutableURLRequest *request = args.request;
  485. NSURL *requestURL = [request URL];
  486. NSString *scheme = [requestURL scheme];
  487. BOOL isAuthorizableRequest = self.shouldAuthorizeAllRequests
  488. || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame
  489. || [requestURL isFileURL];
  490. if (!isAuthorizableRequest) {
  491. // Request is not https, so may be insecure
  492. //
  493. // The NSError will be created below
  494. #if DEBUG
  495. NSLog(@"Cannot authorize request with scheme %@ (%@)", scheme, request);
  496. #endif
  497. }
  498. // Get the access token.
  499. NSString *accessToken = self.authorizationToken;
  500. if (isAuthorizableRequest && [accessToken length] > 0) {
  501. if (request) {
  502. // we have a likely valid access token
  503. NSString *value = [NSString stringWithFormat:@"%s %@",
  504. GTM_OAUTH2_BEARER, accessToken];
  505. [request setValue:value forHTTPHeaderField:@"Authorization"];
  506. }
  507. // We've authorized the request, even if the previous refresh
  508. // failed with an error
  509. args.error = nil;
  510. } else if (args.error == nil) {
  511. NSDictionary *userInfo = nil;
  512. if (request) {
  513. userInfo = [NSDictionary dictionaryWithObject:request
  514. forKey:kGTMOAuth2ErrorRequestKey];
  515. }
  516. NSInteger code = (isAuthorizableRequest ?
  517. kGTMOAuth2ErrorAuthorizationFailed :
  518. kGTMOAuth2ErrorUnauthorizableRequest);
  519. args.error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain
  520. code:code
  521. userInfo:userInfo];
  522. }
  523. // Invoke any callbacks on the proper thread
  524. if (args.delegate || args.completionHandler) {
  525. NSThread *targetThread = args.thread;
  526. BOOL isSameThread = [targetThread isEqual:[NSThread currentThread]];
  527. if (isSameThread) {
  528. [self invokeCallbackArgs:args];
  529. } else {
  530. SEL sel = @selector(invokeCallbackArgs:);
  531. NSOperationQueue *delegateQueue = self.fetcherService.delegateQueue;
  532. if (delegateQueue) {
  533. NSInvocationOperation *op;
  534. op = [[[NSInvocationOperation alloc] initWithTarget:self
  535. selector:sel
  536. object:args] autorelease];
  537. [delegateQueue addOperation:op];
  538. } else {
  539. [self performSelector:sel
  540. onThread:targetThread
  541. withObject:args
  542. waitUntilDone:NO];
  543. }
  544. }
  545. }
  546. BOOL didAuth = (args.error == nil);
  547. return didAuth;
  548. }
  549. - (void)invokeCallbackArgs:(GTMOAuth2AuthorizationArgs *)args {
  550. // Invoke the callbacks
  551. NSError *error = args.error;
  552. id delegate = args.delegate;
  553. SEL sel = args.selector;
  554. if (delegate && sel) {
  555. NSMutableURLRequest *request = args.request;
  556. NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
  557. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
  558. [invocation setSelector:sel];
  559. [invocation setTarget:delegate];
  560. [invocation setArgument:&self atIndex:2];
  561. [invocation setArgument:&request atIndex:3];
  562. [invocation setArgument:&error atIndex:4];
  563. [invocation invoke];
  564. }
  565. #if NS_BLOCKS_AVAILABLE
  566. id handler = args.completionHandler;
  567. if (handler) {
  568. void (^authCompletionBlock)(NSError *) = handler;
  569. authCompletionBlock(error);
  570. }
  571. #endif
  572. }
  573. - (BOOL)authorizeRequest:(NSMutableURLRequest *)request {
  574. // Entry point for synchronous authorization mechanisms
  575. GTMOAuth2AuthorizationArgs *args;
  576. args = [GTMOAuth2AuthorizationArgs argsWithRequest:request
  577. delegate:nil
  578. selector:NULL
  579. completionHandler:nil
  580. thread:[NSThread currentThread]];
  581. return [self authorizeRequestImmediateArgs:args];
  582. }
  583. - (BOOL)canAuthorize {
  584. NSString *token = self.refreshToken;
  585. if (token == nil) {
  586. // For services which do not support refresh tokens, we'll just check
  587. // the access token.
  588. token = self.authorizationToken;
  589. }
  590. BOOL canAuth = [token length] > 0;
  591. return canAuth;
  592. }
  593. - (BOOL)shouldRefreshAccessToken {
  594. // We should refresh the access token when it's missing or nearly expired
  595. // and we have a refresh token
  596. BOOL shouldRefresh = NO;
  597. NSString *accessToken = self.accessToken;
  598. NSString *refreshToken = self.refreshToken;
  599. NSString *assertion = self.assertion;
  600. NSString *code = self.code;
  601. BOOL hasRefreshToken = ([refreshToken length] > 0);
  602. BOOL hasAccessToken = ([accessToken length] > 0);
  603. BOOL hasAssertion = ([assertion length] > 0);
  604. BOOL hasCode = ([code length] > 0);
  605. // Determine if we need to refresh the access token
  606. if (hasRefreshToken || hasAssertion || hasCode) {
  607. if (!hasAccessToken) {
  608. shouldRefresh = YES;
  609. } else {
  610. // We'll consider the token expired if it expires 60 seconds from now
  611. // or earlier
  612. NSDate *expirationDate = self.expirationDate;
  613. NSTimeInterval timeToExpire = [expirationDate timeIntervalSinceNow];
  614. if (expirationDate == nil || timeToExpire < 60.0) {
  615. // access token has expired, or will in a few seconds
  616. shouldRefresh = YES;
  617. }
  618. }
  619. }
  620. return shouldRefresh;
  621. }
  622. - (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds {
  623. // If there is a refresh fetcher pending, wait for it.
  624. //
  625. // This is only intended for unit test or for use in command-line tools.
  626. GTMHTTPFetcher *fetcher = self.refreshFetcher;
  627. [fetcher waitForCompletionWithTimeout:timeoutInSeconds];
  628. }
  629. #pragma mark Token Fetch
  630. - (NSString *)userAgent {
  631. NSBundle *bundle = [NSBundle mainBundle];
  632. NSString *appID = [bundle bundleIdentifier];
  633. NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
  634. if (version == nil) {
  635. version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
  636. }
  637. if (appID && version) {
  638. appID = [appID stringByAppendingFormat:@"/%@", version];
  639. }
  640. NSString *userAgent = @"gtm-oauth2";
  641. if (appID) {
  642. userAgent = [userAgent stringByAppendingFormat:@" %@", appID];
  643. }
  644. return userAgent;
  645. }
  646. - (GTMHTTPFetcher *)beginTokenFetchWithDelegate:(id)delegate
  647. didFinishSelector:(SEL)finishedSel {
  648. NSMutableDictionary *paramsDict = [NSMutableDictionary dictionary];
  649. NSString *fetchType;
  650. NSString *refreshToken = self.refreshToken;
  651. NSString *code = self.code;
  652. NSString *assertion = self.assertion;
  653. NSString *grantType = nil;
  654. if (refreshToken) {
  655. // We have a refresh token
  656. grantType = @"refresh_token";
  657. [paramsDict setObject:refreshToken forKey:@"refresh_token"];
  658. NSString *refreshScope = self.refreshScope;
  659. if ([refreshScope length] > 0) {
  660. [paramsDict setObject:refreshScope forKey:@"scope"];
  661. }
  662. fetchType = kGTMOAuth2FetchTypeRefresh;
  663. } else if (code) {
  664. // We have a code string
  665. grantType = @"authorization_code";
  666. [paramsDict setObject:code forKey:@"code"];
  667. NSString *redirectURI = self.redirectURI;
  668. if ([redirectURI length] > 0) {
  669. [paramsDict setObject:redirectURI forKey:@"redirect_uri"];
  670. }
  671. NSString *scope = self.scope;
  672. if ([scope length] > 0) {
  673. [paramsDict setObject:scope forKey:@"scope"];
  674. }
  675. fetchType = kGTMOAuth2FetchTypeToken;
  676. } else if (assertion) {
  677. // We have an assertion string
  678. grantType = @"http://oauth.net/grant_type/jwt/1.0/bearer";
  679. [paramsDict setObject:assertion forKey:@"assertion"];
  680. fetchType = kGTMOAuth2FetchTypeAssertion;
  681. } else {
  682. #if DEBUG
  683. NSAssert(0, @"unexpected lack of code or refresh token for fetching");
  684. #endif
  685. return nil;
  686. }
  687. [paramsDict setObject:grantType forKey:@"grant_type"];
  688. NSString *clientID = self.clientID;
  689. if ([clientID length] > 0) {
  690. [paramsDict setObject:clientID forKey:@"client_id"];
  691. }
  692. NSString *clientSecret = self.clientSecret;
  693. if ([clientSecret length] > 0) {
  694. [paramsDict setObject:clientSecret forKey:@"client_secret"];
  695. }
  696. NSDictionary *additionalParams = self.additionalTokenRequestParameters;
  697. if (additionalParams) {
  698. [paramsDict addEntriesFromDictionary:additionalParams];
  699. }
  700. NSDictionary *grantTypeParams =
  701. [self.additionalGrantTypeRequestParameters objectForKey:grantType];
  702. if (grantTypeParams) {
  703. [paramsDict addEntriesFromDictionary:grantTypeParams];
  704. }
  705. NSString *paramStr = [[self class] encodedQueryParametersForDictionary:paramsDict];
  706. NSData *paramData = [paramStr dataUsingEncoding:NSUTF8StringEncoding];
  707. NSURL *tokenURL = self.tokenURL;
  708. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:tokenURL];
  709. [request setValue:@"application/x-www-form-urlencoded"
  710. forHTTPHeaderField:@"Content-Type"];
  711. NSString *userAgent = [self userAgent];
  712. [request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
  713. GTMHTTPFetcher *fetcher;
  714. id <GTMHTTPFetcherServiceProtocol> fetcherService = self.fetcherService;
  715. if (fetcherService) {
  716. fetcher = [fetcherService fetcherWithRequest:request];
  717. // Don't use an authorizer for an auth token fetch
  718. fetcher.authorizer = nil;
  719. } else {
  720. fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
  721. }
  722. #if !STRIP_GTM_FETCH_LOGGING
  723. // The user email address is known at token refresh time, not during the initial code exchange.
  724. NSString *userEmail = [self userEmail];
  725. NSString *forStr = userEmail ? [NSString stringWithFormat:@"for \"%@\"", userEmail] : @"";
  726. [fetcher setCommentWithFormat:@"GTMOAuth2 %@ fetch to %@ %@", fetchType, [tokenURL host], forStr];
  727. #endif
  728. fetcher.postData = paramData;
  729. fetcher.retryEnabled = YES;
  730. fetcher.maxRetryInterval = 15.0;
  731. // Fetcher properties will retain the delegate
  732. [fetcher setProperty:delegate forKey:kTokenFetchDelegateKey];
  733. if (finishedSel) {
  734. NSString *selStr = NSStringFromSelector(finishedSel);
  735. [fetcher setProperty:selStr forKey:kTokenFetchSelectorKey];
  736. }
  737. if ([fetcher beginFetchWithDelegate:self
  738. didFinishSelector:@selector(tokenFetcher:finishedWithData:error:)]) {
  739. // Fetch began
  740. [self notifyFetchIsRunning:YES fetcher:fetcher type:fetchType];
  741. return fetcher;
  742. } else {
  743. // Failed to start fetching; typically a URL issue
  744. NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
  745. code:-1
  746. userInfo:nil];
  747. [[self class] invokeDelegate:delegate
  748. selector:finishedSel
  749. object:self
  750. object:nil
  751. object:error];
  752. return nil;
  753. }
  754. }
  755. - (void)tokenFetcher:(GTMHTTPFetcher *)fetcher
  756. finishedWithData:(NSData *)data
  757. error:(NSError *)error {
  758. [self notifyFetchIsRunning:NO fetcher:fetcher type:nil];
  759. NSDictionary *responseHeaders = [fetcher responseHeaders];
  760. NSString *responseType = [responseHeaders valueForKey:@"Content-Type"];
  761. BOOL isResponseJSON = [responseType hasPrefix:@"application/json"];
  762. BOOL hasData = ([data length] > 0);
  763. if (error) {
  764. // Failed. If the error body is JSON, parse it and add it to the error's
  765. // userInfo dictionary.
  766. if (hasData) {
  767. if (isResponseJSON) {
  768. NSDictionary *errorJson = [[self class] dictionaryWithJSONData:data];
  769. if ([errorJson count] > 0) {
  770. #if DEBUG
  771. NSLog(@"Error %@\nError data:\n%@", error, errorJson);
  772. #endif
  773. // Add the JSON error body to the userInfo of the error
  774. NSMutableDictionary *userInfo;
  775. userInfo = [NSMutableDictionary dictionaryWithObject:errorJson
  776. forKey:kGTMOAuth2ErrorJSONKey];
  777. NSDictionary *prevUserInfo = [error userInfo];
  778. if (prevUserInfo) {
  779. [userInfo addEntriesFromDictionary:prevUserInfo];
  780. }
  781. error = [NSError errorWithDomain:[error domain]
  782. code:[error code]
  783. userInfo:userInfo];
  784. }
  785. }
  786. }
  787. } else {
  788. // Succeeded; we have the requested token.
  789. #if DEBUG
  790. NSAssert(hasData, @"data missing in token response");
  791. #endif
  792. if (hasData) {
  793. if (isResponseJSON) {
  794. [self setKeysForResponseJSONData:data];
  795. } else {
  796. // Support for legacy token servers that return form-urlencoded data
  797. NSString *dataStr = [[[NSString alloc] initWithData:data
  798. encoding:NSUTF8StringEncoding] autorelease];
  799. [self setKeysForResponseString:dataStr];
  800. }
  801. #if DEBUG
  802. // Watch for token exchanges that return a non-bearer or unlabeled token
  803. NSString *tokenType = [self tokenType];
  804. if (tokenType == nil
  805. || [tokenType caseInsensitiveCompare:@"bearer"] != NSOrderedSame) {
  806. NSLog(@"GTMOAuth2: Unexpected token type: %@", tokenType);
  807. }
  808. #endif
  809. }
  810. }
  811. id delegate = [fetcher propertyForKey:kTokenFetchDelegateKey];
  812. SEL sel = NULL;
  813. NSString *selStr = [fetcher propertyForKey:kTokenFetchSelectorKey];
  814. if (selStr) sel = NSSelectorFromString(selStr);
  815. [[self class] invokeDelegate:delegate
  816. selector:sel
  817. object:self
  818. object:fetcher
  819. object:error];
  820. // Prevent a circular reference from retaining the delegate
  821. [fetcher setProperty:nil forKey:kTokenFetchDelegateKey];
  822. }
  823. #pragma mark Fetch Notifications
  824. - (void)notifyFetchIsRunning:(BOOL)isStarting
  825. fetcher:(GTMHTTPFetcher *)fetcher
  826. type:(NSString *)fetchType {
  827. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  828. NSString *name = (isStarting ? kGTMOAuth2FetchStarted : kGTMOAuth2FetchStopped);
  829. NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
  830. fetcher, kGTMOAuth2FetcherKey,
  831. fetchType, kGTMOAuth2FetchTypeKey, // fetchType may be nil
  832. nil];
  833. [nc postNotificationName:name
  834. object:self
  835. userInfo:dict];
  836. }
  837. #pragma mark Persistent Response Strings
  838. - (void)setKeysForPersistenceResponseString:(NSString *)str {
  839. // All persistence keys can be set directly as if returned by a server
  840. [self setKeysForResponseString:str];
  841. }
  842. // This returns a "response string" that can be passed later to
  843. // setKeysForResponseString: to reuse an old access token in a new auth object
  844. - (NSString *)persistenceResponseString {
  845. NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:4];
  846. NSString *refreshToken = self.refreshToken;
  847. NSString *accessToken = nil;
  848. if (refreshToken == nil) {
  849. // We store the access token only for services that do not support refresh
  850. // tokens; otherwise, we assume the access token is too perishable to
  851. // be worth storing
  852. accessToken = self.accessToken;
  853. }
  854. // Any nil values will not set a dictionary entry
  855. [dict setValue:refreshToken forKey:kOAuth2RefreshTokenKey];
  856. [dict setValue:accessToken forKey:kOAuth2AccessTokenKey];
  857. [dict setValue:self.serviceProvider forKey:kServiceProviderKey];
  858. [dict setValue:self.userID forKey:kUserIDKey];
  859. [dict setValue:self.userEmail forKey:kUserEmailKey];
  860. [dict setValue:self.userEmailIsVerified forKey:kUserEmailIsVerifiedKey];
  861. [dict setValue:self.scope forKey:kOAuth2ScopeKey];
  862. NSString *result = [[self class] encodedQueryParametersForDictionary:dict];
  863. return result;
  864. }
  865. - (BOOL)primeForRefresh {
  866. if (self.refreshToken == nil) {
  867. // Cannot refresh without a refresh token
  868. return NO;
  869. }
  870. self.accessToken = nil;
  871. self.expiresIn = nil;
  872. self.expirationDate = nil;
  873. self.errorString = nil;
  874. return YES;
  875. }
  876. - (void)reset {
  877. // Reset all per-authorization values
  878. self.code = nil;
  879. self.accessToken = nil;
  880. self.refreshToken = nil;
  881. self.assertion = nil;
  882. self.expiresIn = nil;
  883. self.errorString = nil;
  884. self.expirationDate = nil;
  885. self.userEmail = nil;
  886. self.userEmailIsVerified = nil;
  887. self.authorizationTokenKey = nil;
  888. }
  889. #pragma mark Accessors for Response Parameters
  890. - (NSString *)authorizationToken {
  891. // The token used for authorization is typically the access token unless
  892. // the user has specified that an alternative parameter be used.
  893. NSString *authorizationToken;
  894. NSString *authTokenKey = self.authorizationTokenKey;
  895. if (authTokenKey != nil) {
  896. authorizationToken = [self.parameters objectForKey:authTokenKey];
  897. } else {
  898. authorizationToken = self.accessToken;
  899. }
  900. return authorizationToken;
  901. }
  902. - (NSString *)accessToken {
  903. return [self.parameters objectForKey:kOAuth2AccessTokenKey];
  904. }
  905. - (void)setAccessToken:(NSString *)str {
  906. [self.parameters setValue:str forKey:kOAuth2AccessTokenKey];
  907. }
  908. - (NSString *)refreshToken {
  909. return [self.parameters objectForKey:kOAuth2RefreshTokenKey];
  910. }
  911. - (void)setRefreshToken:(NSString *)str {
  912. [self.parameters setValue:str forKey:kOAuth2RefreshTokenKey];
  913. }
  914. - (NSString *)code {
  915. return [self.parameters objectForKey:kOAuth2CodeKey];
  916. }
  917. - (void)setCode:(NSString *)str {
  918. [self.parameters setValue:str forKey:kOAuth2CodeKey];
  919. }
  920. - (NSString *)assertion {
  921. return [self.parameters objectForKey:kOAuth2AssertionKey];
  922. }
  923. - (void)setAssertion:(NSString *)str {
  924. [self.parameters setValue:str forKey:kOAuth2AssertionKey];
  925. }
  926. - (NSString *)refreshScope {
  927. return [self.parameters objectForKey:kOAuth2RefreshScopeKey];
  928. }
  929. - (void)setRefreshScope:(NSString *)str {
  930. [self.parameters setValue:str forKey:kOAuth2RefreshScopeKey];
  931. }
  932. - (NSString *)errorString {
  933. return [self.parameters objectForKey:kOAuth2ErrorKey];
  934. }
  935. - (void)setErrorString:(NSString *)str {
  936. [self.parameters setValue:str forKey:kOAuth2ErrorKey];
  937. }
  938. - (NSString *)tokenType {
  939. return [self.parameters objectForKey:kOAuth2TokenTypeKey];
  940. }
  941. - (void)setTokenType:(NSString *)str {
  942. [self.parameters setValue:str forKey:kOAuth2TokenTypeKey];
  943. }
  944. - (NSString *)scope {
  945. return [self.parameters objectForKey:kOAuth2ScopeKey];
  946. }
  947. - (void)setScope:(NSString *)str {
  948. [self.parameters setValue:str forKey:kOAuth2ScopeKey];
  949. }
  950. - (NSNumber *)expiresIn {
  951. id value = [self.parameters objectForKey:kOAuth2ExpiresInKey];
  952. if ([value isKindOfClass:[NSString class]]) {
  953. value = [NSNumber numberWithInteger:[value integerValue]];
  954. }
  955. return value;
  956. }
  957. - (void)setExpiresIn:(NSNumber *)num {
  958. [self.parameters setValue:num forKey:kOAuth2ExpiresInKey];
  959. [self updateExpirationDate];
  960. }
  961. - (void)updateExpirationDate {
  962. // Update our absolute expiration time to something close to when
  963. // the server expects the expiration
  964. NSDate *date = nil;
  965. NSNumber *expiresIn = self.expiresIn;
  966. if (expiresIn) {
  967. unsigned long deltaSeconds = [expiresIn unsignedLongValue];
  968. if (deltaSeconds > 0) {
  969. date = [NSDate dateWithTimeIntervalSinceNow:deltaSeconds];
  970. }
  971. }
  972. self.expirationDate = date;
  973. }
  974. //
  975. // Keys custom to this class, not part of OAuth 2
  976. //
  977. - (NSString *)serviceProvider {
  978. return [self.parameters objectForKey:kServiceProviderKey];
  979. }
  980. - (void)setServiceProvider:(NSString *)str {
  981. [self.parameters setValue:str forKey:kServiceProviderKey];
  982. }
  983. - (NSString *)userID {
  984. return [self.parameters objectForKey:kUserIDKey];
  985. }
  986. - (void)setUserID:(NSString *)str {
  987. [self.parameters setValue:str forKey:kUserIDKey];
  988. }
  989. - (NSString *)userEmail {
  990. return [self.parameters objectForKey:kUserEmailKey];
  991. }
  992. - (void)setUserEmail:(NSString *)str {
  993. [self.parameters setValue:str forKey:kUserEmailKey];
  994. }
  995. - (NSString *)userEmailIsVerified {
  996. return [self.parameters objectForKey:kUserEmailIsVerifiedKey];
  997. }
  998. - (void)setUserEmailIsVerified:(NSString *)str {
  999. [self.parameters setValue:str forKey:kUserEmailIsVerifiedKey];
  1000. }
  1001. #pragma mark User Properties
  1002. - (void)setProperty:(id)obj forKey:(NSString *)key {
  1003. if (obj == nil) {
  1004. // User passed in nil, so delete the property
  1005. [properties_ removeObjectForKey:key];
  1006. } else {
  1007. // Be sure the property dictionary exists
  1008. if (properties_ == nil) {
  1009. [self setProperties:[NSMutableDictionary dictionary]];
  1010. }
  1011. [properties_ setObject:obj forKey:key];
  1012. }
  1013. }
  1014. - (id)propertyForKey:(NSString *)key {
  1015. id obj = [properties_ objectForKey:key];
  1016. // Be sure the returned pointer has the life of the autorelease pool,
  1017. // in case self is released immediately
  1018. return [[obj retain] autorelease];
  1019. }
  1020. #pragma mark Utility Routines
  1021. + (NSString *)encodedOAuthValueForString:(NSString *)str {
  1022. CFStringRef originalString = (CFStringRef) str;
  1023. CFStringRef leaveUnescaped = NULL;
  1024. CFStringRef forceEscaped = CFSTR("!*'();:@&=+$,/?%#[]");
  1025. CFStringRef escapedStr = NULL;
  1026. if (str) {
  1027. escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
  1028. originalString,
  1029. leaveUnescaped,
  1030. forceEscaped,
  1031. kCFStringEncodingUTF8);
  1032. [(id)CFMakeCollectable(escapedStr) autorelease];
  1033. }
  1034. return (NSString *)escapedStr;
  1035. }
  1036. + (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict {
  1037. // Make a string like "cat=fluffy@dog=spot"
  1038. NSMutableString *result = [NSMutableString string];
  1039. NSArray *sortedKeys = [[dict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
  1040. NSString *joiner = @"";
  1041. for (NSString *key in sortedKeys) {
  1042. NSString *value = [dict objectForKey:key];
  1043. NSString *encodedValue = [self encodedOAuthValueForString:value];
  1044. NSString *encodedKey = [self encodedOAuthValueForString:key];
  1045. [result appendFormat:@"%@%@=%@", joiner, encodedKey, encodedValue];
  1046. joiner = @"&";
  1047. }
  1048. return result;
  1049. }
  1050. + (void)invokeDelegate:(id)delegate
  1051. selector:(SEL)sel
  1052. object:(id)obj1
  1053. object:(id)obj2
  1054. object:(id)obj3 {
  1055. if (delegate && sel) {
  1056. NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
  1057. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
  1058. [invocation setSelector:sel];
  1059. [invocation setTarget:delegate];
  1060. [invocation setArgument:&obj1 atIndex:2];
  1061. [invocation setArgument:&obj2 atIndex:3];
  1062. [invocation setArgument:&obj3 atIndex:4];
  1063. [invocation invoke];
  1064. }
  1065. }
  1066. + (NSString *)unencodedOAuthParameterForString:(NSString *)str {
  1067. NSString *plainStr = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  1068. return plainStr;
  1069. }
  1070. + (NSDictionary *)dictionaryWithResponseString:(NSString *)responseStr {
  1071. // Build a dictionary from a response string of the form
  1072. // "cat=fluffy&dog=spot". Missing or empty values are considered
  1073. // empty strings; keys and values are percent-decoded.
  1074. if (responseStr == nil) return nil;
  1075. NSArray *items = [responseStr componentsSeparatedByString:@"&"];
  1076. NSMutableDictionary *responseDict = [NSMutableDictionary dictionaryWithCapacity:[items count]];
  1077. for (NSString *item in items) {
  1078. NSString *key = nil;
  1079. NSString *value = @"";
  1080. NSRange equalsRange = [item rangeOfString:@"="];
  1081. if (equalsRange.location != NSNotFound) {
  1082. // The parameter has at least one '='
  1083. key = [item substringToIndex:equalsRange.location];
  1084. // There are characters after the '='
  1085. value = [item substringFromIndex:(equalsRange.location + 1)];
  1086. } else {
  1087. // The parameter has no '='
  1088. key = item;
  1089. }
  1090. NSString *plainKey = [[self class] unencodedOAuthParameterForString:key];
  1091. NSString *plainValue = [[self class] unencodedOAuthParameterForString:value];
  1092. [responseDict setObject:plainValue forKey:plainKey];
  1093. }
  1094. return responseDict;
  1095. }
  1096. + (NSDictionary *)dictionaryWithResponseData:(NSData *)data {
  1097. NSString *responseStr = [[[NSString alloc] initWithData:data
  1098. encoding:NSUTF8StringEncoding] autorelease];
  1099. NSDictionary *dict = [self dictionaryWithResponseString:responseStr];
  1100. return dict;
  1101. }
  1102. + (NSString *)scopeWithStrings:(NSString *)str, ... {
  1103. // concatenate the strings, joined by a single space
  1104. NSString *result = @"";
  1105. NSString *joiner = @"";
  1106. if (str) {
  1107. va_list argList;
  1108. va_start(argList, str);
  1109. while (str) {
  1110. result = [result stringByAppendingFormat:@"%@%@", joiner, str];
  1111. joiner = @" ";
  1112. str = va_arg(argList, id);
  1113. }
  1114. va_end(argList);
  1115. }
  1116. return result;
  1117. }
  1118. @end
  1119. #endif // GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES