Skip to content
Browse files

start work on OAuth with OSM

  • Loading branch information...
1 parent afa6e37 commit 72ea514a56f243207be95e1c4bef10d377f4552f @davidchiles committed
View
692 GTMHTTPFetcher.h
@@ -0,0 +1,692 @@
+/* Copyright (c) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// GTMHTTPFetcher.h
+//
+
+// This is essentially a wrapper around NSURLConnection for POSTs and GETs.
+// If setPostData: is called, then POST is assumed.
+//
+// When would you use this instead of NSURLConnection?
+//
+// - When you just want the result from a GET, POST, or PUT
+// - When you want the "standard" behavior for connections (redirection handling
+// an so on)
+// - When you want automatic retry on failures
+// - When you want to avoid cookie collisions with Safari and other applications
+// - When you are fetching resources with ETags and want to avoid the overhead
+// of repeated fetches of unchanged data
+// - When you need to set a credential for the http operation
+//
+// This is assumed to be a one-shot fetch request; don't reuse the object
+// for a second fetch.
+//
+// The fetcher may be created auto-released, in which case it will release
+// itself after the fetch completion callback. The fetcher is implicitly
+// retained as long as a connection is pending.
+//
+// But if you may need to cancel the fetcher, retain it and have the delegate
+// release the fetcher in the callbacks.
+//
+// Sample usage:
+//
+// NSURLRequest *request = [NSURLRequest requestWithURL:myURL];
+// GTMHTTPFetcher* myFetcher = [GTMHTTPFetcher fetcherWithRequest:request];
+//
+// // optional upload body data
+// [myFetcher setPostData:[postString dataUsingEncoding:NSUTF8StringEncoding]];
+//
+// [myFetcher beginFetchWithDelegate:self
+// didFinishSelector:@selector(myFetcher:finishedWithData:error:)];
+//
+// Upon fetch completion, the callback selector is invoked; it should have
+// this signature (you can use any callback method name you want so long as
+// the signature matches this):
+//
+// - (void)myFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)retrievedData error:(NSError *)error;
+//
+// The block callback version looks like:
+//
+// [myFetcher beginFetchWithCompletionHandler:^(NSData *retrievedData, NSError *error) {
+// if (error != nil) {
+// // status code or network error
+// } else {
+// // succeeded
+// }
+// }];
+
+//
+// NOTE: Fetches may retrieve data from the server even though the server
+// returned an error. The failure selector is called when the server
+// status is >= 300, with an NSError having domain
+// kGTMHTTPFetcherStatusDomain and code set to the server status.
+//
+// Status codes are at <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>
+//
+//
+// Downloading to disk:
+//
+// To have downloaded data saved directly to disk, specify either a path for the
+// downloadPath property, or a file handle for the downloadFileHandle property.
+// When downloading to disk, callbacks will be passed a nil for the NSData*
+// arguments.
+//
+//
+// HTTP methods and headers:
+//
+// Alternative HTTP methods, like PUT, and custom headers can be specified by
+// creating the fetcher with an appropriate NSMutableURLRequest
+//
+//
+// Proxies:
+//
+// Proxy handling is invisible so long as the system has a valid credential in
+// the keychain, which is normally true (else most NSURL-based apps would have
+// difficulty.) But when there is a proxy authetication error, the the fetcher
+// will call the failedWithError: method with the NSURLChallenge in the error's
+// userInfo. The error method can get the challenge info like this:
+//
+// NSURLAuthenticationChallenge *challenge
+// = [[error userInfo] objectForKey:kGTMHTTPFetcherErrorChallengeKey];
+// BOOL isProxyChallenge = [[challenge protectionSpace] isProxy];
+//
+// If a proxy error occurs, you can ask the user for the proxy username/password
+// and call fetcher's setProxyCredential: to provide those for the
+// next attempt to fetch.
+//
+//
+// Cookies:
+//
+// There are three supported mechanisms for remembering cookies between fetches.
+//
+// By default, GTMHTTPFetcher uses a mutable array held statically to track
+// cookies for all instantiated fetchers. This avoids server cookies being set
+// by servers for the application from interfering with Safari cookie settings,
+// and vice versa. The fetcher cookies are lost when the application quits.
+//
+// To rely instead on WebKit's global NSHTTPCookieStorage, call
+// setCookieStorageMethod: with kGTMHTTPFetcherCookieStorageMethodSystemDefault.
+//
+// If the fetcher is created from a GTMHTTPFetcherService object
+// then the cookie storage mechanism is set to use the cookie storage in the
+// service object rather than the static storage.
+//
+//
+// Fetching for periodic checks:
+//
+// The fetcher object tracks ETag headers from responses and
+// provide an "If-None-Match" header. This allows the server to save
+// bandwidth by providing a status message instead of repeated response
+// data.
+//
+// To get this behavior, create the fetcher from an GTMHTTPFetcherService object
+// and look for a fetch callback error with code 304
+// (kGTMHTTPFetcherStatusNotModified) like this:
+//
+// - (void)myFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
+// if ([error code] == kGTMHTTPFetcherStatusNotModified) {
+// // |data| is empty; use the data from the previous finishedWithData: for this URL
+// } else {
+// // handle other server status code
+// }
+// }
+//
+//
+// Monitoring received data
+//
+// The optional received data selector can be set with setReceivedDataSelector:
+// and should have the signature
+//
+// - (void)myFetcher:(GTMHTTPFetcher *)fetcher receivedData:(NSData *)dataReceivedSoFar;
+//
+// The number bytes received so far is available as [fetcher downloadedLength].
+// This number may go down if a redirect causes the download to begin again from
+// a new server.
+//
+// If supplied by the server, the anticipated total download size is available
+// as [[myFetcher response] expectedContentLength] (and may be -1 for unknown
+// download sizes.)
+//
+//
+// Automatic retrying of fetches
+//
+// The fetcher can optionally create a timer and reattempt certain kinds of
+// fetch failures (status codes 408, request timeout; 503, service unavailable;
+// 504, gateway timeout; networking errors NSURLErrorTimedOut and
+// NSURLErrorNetworkConnectionLost.) The user may set a retry selector to
+// customize the type of errors which will be retried.
+//
+// Retries are done in an exponential-backoff fashion (that is, after 1 second,
+// 2, 4, 8, and so on.)
+//
+// Enabling automatic retries looks like this:
+// [myFetcher setRetryEnabled:YES];
+//
+// With retries enabled, the success or failure callbacks are called only
+// when no more retries will be attempted. Calling the fetcher's stopFetching
+// method will terminate the retry timer, without the finished or failure
+// selectors being invoked.
+//
+// Optionally, the client may set the maximum retry interval:
+// [myFetcher setMaxRetryInterval:60.0]; // in seconds; default is 60 seconds
+// // for downloads, 600 for uploads
+//
+// Also optionally, the client may provide a callback selector to determine
+// if a status code or other error should be retried.
+// [myFetcher setRetrySelector:@selector(myFetcher:willRetry:forError:)];
+//
+// If set, the retry selector should have the signature:
+// -(BOOL)fetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error
+// and return YES to set the retry timer or NO to fail without additional
+// fetch attempts.
+//
+// The retry method may return the |suggestedWillRetry| argument to get the
+// default retry behavior. Server status codes are present in the
+// error argument, and have the domain kGTMHTTPFetcherStatusDomain. The
+// user's method may look something like this:
+//
+// -(BOOL)myFetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error {
+//
+// // perhaps examine [error domain] and [error code], or [fetcher retryCount]
+// //
+// // return YES to start the retry timer, NO to proceed to the failure
+// // callback, or |suggestedWillRetry| to get default behavior for the
+// // current error domain and code values.
+// return suggestedWillRetry;
+// }
+
+
+
+#pragma once
+
+#import <Foundation/Foundation.h>
+
+#if defined(GTL_TARGET_NAMESPACE)
+ // we're using target namespace macros
+ #import "GTLDefines.h"
+#elif defined(GDATA_TARGET_NAMESPACE)
+ #import "GDataDefines.h"
+#else
+ #if TARGET_OS_IPHONE
+ #ifndef GTM_FOUNDATION_ONLY
+ #define GTM_FOUNDATION_ONLY 1
+ #endif
+ #ifndef GTM_IPHONE
+ #define GTM_IPHONE 1
+ #endif
+ #endif
+#endif
+
+#if TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 40000)
+ #define GTM_BACKGROUND_FETCHING 1
+#endif
+
+#undef _EXTERN
+#undef _INITIALIZE_AS
+#ifdef GTMHTTPFETCHER_DEFINE_GLOBALS
+ #define _EXTERN
+ #define _INITIALIZE_AS(x) =x
+#else
+ #if defined(__cplusplus)
+ #define _EXTERN extern "C"
+ #else
+ #define _EXTERN extern
+ #endif
+ #define _INITIALIZE_AS(x)
+#endif
+
+// notifications
+//
+// fetch started and stopped, and fetch retry delay started and stopped
+_EXTERN NSString* const kGTMHTTPFetcherStartedNotification _INITIALIZE_AS(@"kGTMHTTPFetcherStartedNotification");
+_EXTERN NSString* const kGTMHTTPFetcherStoppedNotification _INITIALIZE_AS(@"kGTMHTTPFetcherStoppedNotification");
+_EXTERN NSString* const kGTMHTTPFetcherRetryDelayStartedNotification _INITIALIZE_AS(@"kGTMHTTPFetcherRetryDelayStartedNotification");
+_EXTERN NSString* const kGTMHTTPFetcherRetryDelayStoppedNotification _INITIALIZE_AS(@"kGTMHTTPFetcherRetryDelayStoppedNotification");
+
+// callback constants
+_EXTERN NSString* const kGTMHTTPFetcherErrorDomain _INITIALIZE_AS(@"com.google.GTMHTTPFetcher");
+_EXTERN NSString* const kGTMHTTPFetcherStatusDomain _INITIALIZE_AS(@"com.google.HTTPStatus");
+_EXTERN NSString* const kGTMHTTPFetcherErrorChallengeKey _INITIALIZE_AS(@"challenge");
+_EXTERN NSString* const kGTMHTTPFetcherStatusDataKey _INITIALIZE_AS(@"data"); // data returned with a kGTMHTTPFetcherStatusDomain error
+
+enum {
+ kGTMHTTPFetcherErrorDownloadFailed = -1,
+ kGTMHTTPFetcherErrorAuthenticationChallengeFailed = -2,
+ kGTMHTTPFetcherErrorChunkUploadFailed = -3,
+ kGTMHTTPFetcherErrorFileHandleException = -4,
+ kGTMHTTPFetcherErrorBackgroundExpiration = -6,
+
+ // The code kGTMHTTPFetcherErrorAuthorizationFailed (-5) has been removed;
+ // look for status 401 instead.
+
+ kGTMHTTPFetcherStatusNotModified = 304,
+ kGTMHTTPFetcherStatusBadRequest = 400,
+ kGTMHTTPFetcherStatusUnauthorized = 401,
+ kGTMHTTPFetcherStatusForbidden = 403,
+ kGTMHTTPFetcherStatusPreconditionFailed = 412
+};
+
+// cookie storage methods
+enum {
+ kGTMHTTPFetcherCookieStorageMethodStatic = 0,
+ kGTMHTTPFetcherCookieStorageMethodFetchHistory = 1,
+ kGTMHTTPFetcherCookieStorageMethodSystemDefault = 2,
+ kGTMHTTPFetcherCookieStorageMethodNone = 3
+};
+
+void GTMAssertSelectorNilOrImplementedWithArgs(id obj, SEL sel, ...);
+
+// Utility functions for applications self-identifying to servers via a
+// user-agent header
+
+// Make a proper app name without whitespace from the given string, removing
+// whitespace and other characters that may be special parsed marks of
+// the full user-agent string.
+NSString *GTMCleanedUserAgentString(NSString *str);
+
+// Make an identifier like "MacOSX/10.7.1" or "iPod_Touch/4.1"
+NSString *GTMSystemVersionString(void);
+
+// Make a generic name and version for the current application, like
+// com.example.MyApp/1.2.3 relying on the bundle identifier and the
+// CFBundleShortVersionString or CFBundleVersion. If no bundle ID
+// is available, the process name preceded by "proc_" is used.
+NSString *GTMApplicationIdentifier(NSBundle *bundle);
+
+@class GTMHTTPFetcher;
+
+@protocol GTMCookieStorageProtocol <NSObject>
+// This protocol allows us to call into the service without requiring
+// GTMCookieStorage sources in this project
+//
+// The public interface for cookie handling is the GTMCookieStorage class,
+// accessible from a fetcher service object's fetchHistory or from the fetcher's
+// +staticCookieStorage method.
+- (NSArray *)cookiesForURL:(NSURL *)theURL;
+- (void)setCookies:(NSArray *)newCookies;
+@end
+
+@protocol GTMHTTPFetchHistoryProtocol <NSObject>
+// This protocol allows us to call the fetch history object without requiring
+// GTMHTTPFetchHistory sources in this project
+- (void)updateRequest:(NSMutableURLRequest *)request isHTTPGet:(BOOL)isHTTPGet;
+- (BOOL)shouldCacheETaggedData;
+- (NSData *)cachedDataForRequest:(NSURLRequest *)request;
+- (id <GTMCookieStorageProtocol>)cookieStorage;
+- (void)updateFetchHistoryWithRequest:(NSURLRequest *)request
+ response:(NSURLResponse *)response
+ downloadedData:(NSData *)downloadedData;
+- (void)removeCachedDataForRequest:(NSURLRequest *)request;
+@end
+
+@protocol GTMHTTPFetcherServiceProtocol <NSObject>
+// This protocol allows us to call into the service without requiring
+// GTMHTTPFetcherService sources in this project
+- (BOOL)fetcherShouldBeginFetching:(GTMHTTPFetcher *)fetcher;
+- (void)fetcherDidStop:(GTMHTTPFetcher *)fetcher;
+
+- (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request;
+- (BOOL)isDelayingFetcher:(GTMHTTPFetcher *)fetcher;
+@end
+
+@protocol GTMFetcherAuthorizationProtocol <NSObject>
+@required
+// This protocol allows us to call the authorizer without requiring its sources
+// in this project
+- (void)authorizeRequest:(NSMutableURLRequest *)request
+ delegate:(id)delegate
+ didFinishSelector:(SEL)sel;
+
+- (void)stopAuthorization;
+
+- (BOOL)isAuthorizingRequest:(NSURLRequest *)request;
+
+- (BOOL)isAuthorizedRequest:(NSURLRequest *)request;
+
+- (NSString *)userEmail;
+
+@optional
+@property (assign) id <GTMHTTPFetcherServiceProtocol> fetcherService; // WEAK
+
+- (BOOL)primeForRefresh;
+@end
+
+// GTMHTTPFetcher objects are used for async retrieval of an http get or post
+//
+// See additional comments at the beginning of this file
+@interface GTMHTTPFetcher : NSObject {
+ @protected
+ NSMutableURLRequest *request_;
+ NSURLConnection *connection_;
+ NSMutableData *downloadedData_;
+ NSString *downloadPath_;
+ NSString *temporaryDownloadPath_;
+ NSFileHandle *downloadFileHandle_;
+ unsigned long long downloadedLength_;
+ NSURLCredential *credential_; // username & password
+ NSURLCredential *proxyCredential_; // credential supplied to proxy servers
+ NSData *postData_;
+ NSInputStream *postStream_;
+ NSMutableData *loggedStreamData_;
+ NSURLResponse *response_; // set in connection:didReceiveResponse:
+ id delegate_;
+ SEL finishedSel_; // should by implemented by delegate
+ SEL sentDataSel_; // optional, set with setSentDataSelector
+ SEL receivedDataSel_; // optional, set with setReceivedDataSelector
+#if NS_BLOCKS_AVAILABLE
+ void (^completionBlock_)(NSData *, NSError *);
+ void (^receivedDataBlock_)(NSData *);
+ void (^sentDataBlock_)(NSInteger, NSInteger, NSInteger);
+ BOOL (^retryBlock_)(BOOL, NSError *);
+#elif !__LP64__
+ // placeholders: for 32-bit builds, keep the size of the object's ivar section
+ // the same with and without blocks
+ id completionPlaceholder_;
+ id receivedDataPlaceholder_;
+ id sentDataPlaceholder_;
+ id retryPlaceholder_;
+#endif
+ BOOL hasConnectionEnded_; // set if the connection need not be cancelled
+ BOOL isCancellingChallenge_; // set only when cancelling an auth challenge
+ BOOL isStopNotificationNeeded_; // set when start notification has been sent
+ BOOL shouldFetchInBackground_;
+#if GTM_BACKGROUND_FETCHING
+ NSUInteger backgroundTaskIdentifer_; // UIBackgroundTaskIdentifier
+#endif
+ id userData_; // retained, if set by caller
+ NSMutableDictionary *properties_; // more data retained for caller
+ NSArray *runLoopModes_; // optional, for 10.5 and later
+ id <GTMHTTPFetchHistoryProtocol> fetchHistory_; // if supplied by the caller, used for Last-Modified-Since checks and cookies
+ NSInteger cookieStorageMethod_; // constant from above
+ id <GTMCookieStorageProtocol> cookieStorage_;
+
+ id <GTMFetcherAuthorizationProtocol> authorizer_;
+
+ // the service object that created and monitors this fetcher, if any
+ id <GTMHTTPFetcherServiceProtocol> service_;
+ NSString *serviceHost_;
+ NSInteger servicePriority_;
+ NSThread *thread_;
+
+ BOOL isRetryEnabled_; // user wants auto-retry
+ SEL retrySel_; // optional; set with setRetrySelector
+ NSTimer *retryTimer_;
+ NSUInteger retryCount_;
+ NSTimeInterval maxRetryInterval_; // default 600 seconds
+ NSTimeInterval minRetryInterval_; // random between 1 and 2 seconds
+ NSTimeInterval retryFactor_; // default interval multiplier is 2
+ NSTimeInterval lastRetryInterval_;
+ BOOL hasAttemptedAuthRefresh_;
+
+ NSString *comment_; // comment for log
+ NSString *log_;
+}
+
+// Create a fetcher
+//
+// fetcherWithRequest will return an autoreleased fetcher, but if
+// the connection is successfully created, the connection should retain the
+// fetcher for the life of the connection as well. So the caller doesn't have
+// to retain the fetcher explicitly unless they want to be able to cancel it.
++ (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request;
+
+// Convenience methods that make a request, like +fetcherWithRequest
++ (GTMHTTPFetcher *)fetcherWithURL:(NSURL *)requestURL;
++ (GTMHTTPFetcher *)fetcherWithURLString:(NSString *)requestURLString;
+
+// Designated initializer
+- (id)initWithRequest:(NSURLRequest *)request;
+
+// Fetcher request
+//
+// The underlying request is mutable and may be modified by the caller
+@property (retain) NSMutableURLRequest *mutableRequest;
+
+// Setting the credential is optional; it is used if the connection receives
+// an authentication challenge
+@property (retain) NSURLCredential *credential;
+
+// Setting the proxy credential is optional; it is used if the connection
+// receives an authentication challenge from a proxy
+@property (retain) NSURLCredential *proxyCredential;
+
+// If post data or stream is not set, then a GET retrieval method is assumed
+@property (retain) NSData *postData;
+@property (retain) NSInputStream *postStream;
+
+// The default cookie storage method is kGTMHTTPFetcherCookieStorageMethodStatic
+// without a fetch history set, and kGTMHTTPFetcherCookieStorageMethodFetchHistory
+// with a fetch history set
+//
+// Applications needing control of cookies across a sequence of fetches should
+// create fetchers from a GTMHTTPFetcherService object (which encapsulates
+// fetch history) for a well-defined cookie store
+@property (assign) NSInteger cookieStorageMethod;
+
++ (id <GTMCookieStorageProtocol>)staticCookieStorage;
+
+// Object to add authorization to the request, if needed
+@property (retain) id <GTMFetcherAuthorizationProtocol> authorizer;
+
+// The service object that created and monitors this fetcher, if any
+@property (retain) id <GTMHTTPFetcherServiceProtocol> service;
+
+// The host, if any, used to classify this fetcher in the fetcher service
+@property (copy) NSString *serviceHost;
+
+// The priority, if any, used for starting fetchers in the fetcher service
+//
+// Lower values are higher priority; the default is 0, and values may
+// be negative or positive. This priority affects only the start order of
+// fetchers that are being delayed by a fetcher service.
+@property (assign) NSInteger servicePriority;
+
+// The thread used to run this fetcher in the fetcher service
+@property (retain) NSThread *thread;
+
+// The delegate is retained during the connection
+@property (retain) id delegate;
+
+// On iOS 4 and later, the fetch may optionally continue while the app is in the
+// background until finished or stopped by OS expiration
+//
+// The default value is NO
+//
+// For Mac OS X, background fetches are always supported, and this property
+// is ignored
+@property (assign) BOOL shouldFetchInBackground;
+
+// The delegate's optional sentData selector may be used to monitor upload
+// progress. It should have a signature like:
+// - (void)myFetcher:(GTMHTTPFetcher *)fetcher
+// didSendBytes:(NSInteger)bytesSent
+// totalBytesSent:(NSInteger)totalBytesSent
+// totalBytesExpectedToSend:(NSInteger)totalBytesExpectedToSend;
+//
+// +doesSupportSentDataCallback indicates if this delegate method is supported
++ (BOOL)doesSupportSentDataCallback;
+
+@property (assign) SEL sentDataSelector;
+
+// The delegate's optional receivedData selector may be used to monitor download
+// progress. It should have a signature like:
+// - (void)myFetcher:(GTMHTTPFetcher *)fetcher
+// receivedData:(NSData *)dataReceivedSoFar;
+//
+// The dataReceived argument will be nil when downloading to a file handle.
+//
+// Applications should not use this method to accumulate the received data;
+// the callback method or block supplied to the beginFetch call will have
+// the complete NSData received.
+@property (assign) SEL receivedDataSelector;
+
+#if NS_BLOCKS_AVAILABLE
+// The full interface to the block is provided rather than just a typedef for
+// its parameter list in order to get more useful code completion in the Xcode
+// editor
+@property (copy) void (^sentDataBlock)(NSInteger bytesSent, NSInteger totalBytesSent, NSInteger bytesExpectedToSend);
+
+// The dataReceived argument will be nil when downloading to a file handle
+@property (copy) void (^receivedDataBlock)(NSData *dataReceivedSoFar);
+#endif
+
+// retrying; see comments at the top of the file. Calling
+// setRetryEnabled(YES) resets the min and max retry intervals.
+@property (assign, getter=isRetryEnabled) BOOL retryEnabled;
+
+// Retry selector or block is optional for retries.
+//
+// If present, it should have the signature:
+// -(BOOL)fetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error
+// and return YES to cause a retry. See comments at the top of this file.
+@property (assign) SEL retrySelector;
+
+#if NS_BLOCKS_AVAILABLE
+@property (copy) BOOL (^retryBlock)(BOOL suggestedWillRetry, NSError *error);
+#endif
+
+// Retry intervals must be strictly less than maxRetryInterval, else
+// they will be limited to maxRetryInterval and no further retries will
+// be attempted. Setting maxRetryInterval to 0.0 will reset it to the
+// default value, 600 seconds.
+
+@property (assign) NSTimeInterval maxRetryInterval;
+
+// Starting retry interval. Setting minRetryInterval to 0.0 will reset it
+// to a random value between 1.0 and 2.0 seconds. Clients should normally not
+// call this except for unit testing.
+@property (assign) NSTimeInterval minRetryInterval;
+
+// Multiplier used to increase the interval between retries, typically 2.0.
+// Clients should not need to call this.
+@property (assign) double retryFactor;
+
+// Number of retries attempted
+@property (readonly) NSUInteger retryCount;
+
+// interval delay to precede next retry
+@property (readonly) NSTimeInterval nextRetryInterval;
+
+// Begin fetching the request
+//
+// The delegate can optionally implement the finished selectors or pass NULL
+// for it.
+//
+// Returns YES if the fetch is initiated. The delegate is retained between
+// the beginFetch call until after the finish callback.
+//
+// An error is passed to the callback for server statuses 300 or
+// higher, with the status stored as the error object's code.
+//
+// finishedSEL has a signature like:
+// - (void)fetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error;
+//
+
+- (BOOL)beginFetchWithDelegate:(id)delegate
+ didFinishSelector:(SEL)finishedSEL;
+
+#if NS_BLOCKS_AVAILABLE
+- (BOOL)beginFetchWithCompletionHandler:(void (^)(NSData *data, NSError *error))handler;
+#endif
+
+
+// Returns YES if this is in the process of fetching a URL
+- (BOOL)isFetching;
+
+// Cancel the fetch of the request that's currently in progress
+- (void)stopFetching;
+
+// Return the status code from the server response
+@property (readonly) NSInteger statusCode;
+
+// Return the http headers from the response
+@property (retain, readonly) NSDictionary *responseHeaders;
+
+// The response, once it's been received
+@property (retain) NSURLResponse *response;
+
+// Bytes downloaded so far
+@property (readonly) unsigned long long downloadedLength;
+
+// Buffer of currently-downloaded data
+@property (readonly, retain) NSData *downloadedData;
+
+// Path in which to non-atomically create a file for storing the downloaded data
+//
+// The path must be set before fetching begins. The download file handle
+// will be created for the path, and can be used to monitor progress. If a file
+// already exists at the path, it will be overwritten.
+@property (copy) NSString *downloadPath;
+
+// If downloadFileHandle is set, data received is immediately appended to
+// the file handle rather than being accumulated in the downloadedData property
+//
+// The file handle supplied must allow writing and support seekToFileOffset:,
+// and must be set before fetching begins. Setting a download path will
+// override the file handle property.
+@property (retain) NSFileHandle *downloadFileHandle;
+
+// The optional fetchHistory object is used for a sequence of fetchers to
+// remember ETags, cache ETagged data, and store cookies. Typically, this
+// is set by a GTMFetcherService object when it creates a fetcher.
+//
+// Side effect: setting fetch history implicitly calls setCookieStorageMethod:
+@property (retain) id <GTMHTTPFetchHistoryProtocol> fetchHistory;
+
+// userData is retained for the convenience of the caller
+@property (retain) id userData;
+
+// Stored property values are retained for the convenience of the caller
+@property (copy) NSMutableDictionary *properties;
+
+- (void)setProperty:(id)obj forKey:(NSString *)key; // pass nil obj to remove property
+- (id)propertyForKey:(NSString *)key;
+
+- (void)addPropertiesFromDictionary:(NSDictionary *)dict;
+
+// Comments are useful for logging
+@property (copy) NSString *comment;
+
+- (void)setCommentWithFormat:(id)format, ...;
+
+// Log of request and response, if logging is enabled
+@property (copy) NSString *log;
+
+// Using the fetcher while a modal dialog is displayed requires setting the
+// run-loop modes to include NSModalPanelRunLoopMode
+@property (retain) NSArray *runLoopModes;
+
+// Users who wish to replace GTMHTTPFetcher's use of NSURLConnection
+// can do so globally here. The replacement should be a subclass of
+// NSURLConnection.
++ (Class)connectionClass;
++ (void)setConnectionClass:(Class)theClass;
+
+// Spin the run loop, discarding events, until the fetch has completed
+//
+// This is only for use in testing or in tools without a user interface.
+//
+// Synchronous fetches should never be done by shipping apps; they are
+// sufficient reason for rejection from the app store.
+- (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds;
+
+#if STRIP_GTM_FETCH_LOGGING
+// if logging is stripped, provide a stub for the main method
+// for controlling logging
++ (void)setLoggingEnabled:(BOOL)flag;
+#endif // STRIP_GTM_FETCH_LOGGING
+
+@end
View
1,737 GTMHTTPFetcher.m
1,737 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
237 GTMOAuthAuthentication.h
@@ -0,0 +1,237 @@
+/* Copyright (c) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This class implements the OAuth 1.0a protocol for creating and signing
+// requests. http://oauth.net/core/1.0a/
+//
+// Users can rely on +authForInstalledApp for creating a complete authentication
+// object for use with Google's OAuth protocol.
+//
+// The user (typically the GTMOAuthSignIn object) can call the methods
+// - (void)setKeysForResponseData:(NSData *)data;
+// - (void)setKeysForResponseString:(NSString *)str;
+//
+// to set the parameters following each server interaction, and then can use
+// - (BOOL)authorizeRequest:(NSMutableURLRequest *)request
+//
+// to add the "Authorization: OAuth ..." header to future resource requests.
+
+#import <Foundation/Foundation.h>
+
+#ifdef GTL_TARGET_NAMESPACE
+ #import "GTLDefines.h"
+#endif
+
+#import "GTMHTTPFetcher.h"
+
+#undef _EXTERN
+#undef _INITIALIZE_AS
+#ifdef GTMOAUTHAUTHENTICATION_DEFINE_GLOBALS
+#define _EXTERN
+#define _INITIALIZE_AS(x) =x
+#else
+#define _EXTERN extern
+#define _INITIALIZE_AS(x)
+#endif
+
+_EXTERN NSString* const kGTMOAuthServiceProviderGoogle _INITIALIZE_AS(@"Google");
+
+_EXTERN NSString* const kGTMOAuthSignatureMethodHMAC_SHA1 _INITIALIZE_AS(@"HMAC-SHA1");
+
+//
+// GTMOAuthSignIn constants, included here for use by clients
+//
+_EXTERN NSString* const kGTMOAuthErrorDomain _INITIALIZE_AS(@"com.google.GTMOAuth");
+
+// notifications for token fetches
+_EXTERN NSString* const kGTMOAuthFetchStarted _INITIALIZE_AS(@"kGTMOAuthFetchStarted");
+_EXTERN NSString* const kGTMOAuthFetchStopped _INITIALIZE_AS(@"kGTMOAuthFetchStopped");
+
+_EXTERN NSString* const kGTMOAuthFetchTypeKey _INITIALIZE_AS(@"FetchType");
+_EXTERN NSString* const kGTMOAuthFetchTypeRequest _INITIALIZE_AS(@"request");
+_EXTERN NSString* const kGTMOAuthFetchTypeAccess _INITIALIZE_AS(@"access");
+_EXTERN NSString* const kGTMOAuthFetchTypeUserInfo _INITIALIZE_AS(@"userInfo");
+
+// Notification that sign-in has completed, and token fetches will begin (useful
+// for hiding pre-sign in messages, and showing post-sign in messages
+// during the access fetch)
+_EXTERN NSString* const kGTMOAuthUserWillSignIn _INITIALIZE_AS(@"kGTMOAuthUserWillSignIn");
+_EXTERN NSString* const kGTMOAuthUserHasSignedIn _INITIALIZE_AS(@"kGTMOAuthUserHasSignedIn");
+
+// notification for network loss during html sign-in display
+_EXTERN NSString* const kGTMOAuthNetworkLost _INITIALIZE_AS(@"kGTMOAuthNetworkLost");
+_EXTERN NSString* const kGTMOAuthNetworkFound _INITIALIZE_AS(@"kGTMOAuthNetworkFound");
+
+#if GTL_OAUTH_SUPPORTS_RSASHA1_SIGNING
+_EXTERN NSString* const kGTMOAuthSignatureMethodRSA_SHA1 _INITIALIZE_AS(@"RSA-SHA1");
+#endif
+
+@interface GTMOAuthAuthentication : NSObject <GTMFetcherAuthorizationProtocol> {
+@private
+ // paramValues_ contains the parameters used in requests and responses
+ NSMutableDictionary *paramValues_;
+
+ NSString *realm_;
+ NSString *privateKey_;
+ NSString *timestamp_; // set for testing only
+ NSString *nonce_; // set for testing only
+
+ // flag indicating if the token in paramValues is a request token or an
+ // access token
+ BOOL hasAccessToken_;
+
+ // flag indicating if authorizeRequest: adds a header or parameters
+ BOOL shouldUseParamsToAuthorize_;
+
+ id userData_;
+}
+
+// OAuth protocol parameters
+//
+// timestamp (seconds since 1970) and nonce (random number) are generated
+// uniquely for each request, except during testing, when they may be set
+// explicitly
+//
+// Note: we're using "assign" for these since they're stored inside
+// the dictionary of param values rather than retained by ivars.
+@property (nonatomic, copy) NSString *scope;
+@property (nonatomic, copy) NSString *displayName;
+@property (nonatomic, copy) NSString *hostedDomain;
+@property (nonatomic, copy) NSString *domain;
+@property (nonatomic, copy) NSString *iconURLString;
+@property (nonatomic, copy) NSString *language;
+@property (nonatomic, copy) NSString *mobile;
+@property (nonatomic, copy) NSString *consumerKey;
+@property (nonatomic, copy) NSString *signatureMethod;
+@property (nonatomic, copy) NSString *version;
+@property (nonatomic, copy) NSString *token;
+@property (nonatomic, copy) NSString *callback;
+@property (nonatomic, copy) NSString *verifier;
+@property (nonatomic, copy) NSString *tokenSecret;
+@property (nonatomic, copy) NSString *callbackConfirmed;
+@property (nonatomic, copy) NSString *timestamp;
+@property (nonatomic, copy) NSString *nonce;
+
+// other standard non-parameter OAuth protocol properties
+@property (nonatomic, copy) NSString *realm;
+@property (nonatomic, copy) NSString *privateKey;
+
+// service identifier, like "Google"; not used for authentication or signing
+@property (nonatomic, copy) NSString *serviceProvider;
+
+// user email and verified status; not used for authentication or signing
+//
+// The verified string can be checked with -boolValue. If the result is false,
+// then the email address is listed with the account on the server, but the
+// address has not been confirmed as belonging to the owner of the account.
+@property (nonatomic, copy) NSString *userEmail;
+@property (nonatomic, copy) NSString *userEmailIsVerified;
+
+// property for using a previously-authorized access token
+@property (nonatomic, copy) NSString *accessToken;
+
+// property indicating if authorization is done with parameters rather than a
+// header
+@property (nonatomic, assign) BOOL shouldUseParamsToAuthorize;
+
+// property indicating if this auth has an access token so is suitable for
+// authorizing a request. This does not guarantee that the token is valid.
+@property (nonatomic, readonly) BOOL canAuthorize;
+
+// userData is retained for the convenience of the caller
+@property (nonatomic, retain) id userData;
+
+
+// Create an authentication object, with hardcoded values for installed apps
+// with HMAC-SHA1 as signature method, and "anonymous" as the consumer key and
+// consumer secret (private key).
++ (GTMOAuthAuthentication *)authForInstalledApp;
+
+// Create an authentication object, specifying the consumer key and
+// private key (both anonymous for installed apps) and the signature method
+// ("HMAC-SHA1" for installed apps).
+//
+// For signature method "RSA-SHA1", a proper consumer key and private key
+// may be supplied (and the GTL_OAUTH_SUPPORTS_RSASHA1_SIGNING compiler
+// conditional must be set.)
+- (id)initWithSignatureMethod:(NSString *)signatureMethod
+ consumerKey:(NSString *)consumerKey
+ privateKey:(NSString *)privateKey;
+
+// clear out any authentication values, prepare for a new request fetch
+- (void)reset;
+
+// authorization entry point for GTL library
+- (BOOL)authorizeRequest:(NSMutableURLRequest *)request;
+
+// add OAuth headers
+//
+// any non-OAuth parameters (such as scope) will be included in the signature
+// but added as a URL parameter, not in the Auth header
+- (void)addRequestTokenHeaderToRequest:(NSMutableURLRequest *)request;
+- (void)addAuthorizeTokenHeaderToRequest:(NSMutableURLRequest *)request;
+- (void)addAccessTokenHeaderToRequest:(NSMutableURLRequest *)request;
+- (void)addResourceTokenHeaderToRequest:(NSMutableURLRequest *)request;
+
+// add OAuth URL params, as an alternative to adding headers
+- (void)addRequestTokenParamsToRequest:(NSMutableURLRequest *)request;
+- (void)addAuthorizeTokenParamsToRequest:(NSMutableURLRequest *)request;
+- (void)addAccessTokenParamsToRequest:(NSMutableURLRequest *)request;
+- (void)addResourceTokenParamsToRequest:(NSMutableURLRequest *)request;
+
+// parse and set token and token secret from response data
+- (void)setKeysForResponseData:(NSData *)data;
+- (void)setKeysForResponseString:(NSString *)str;
+
+// persistent token string for keychain storage
+//
+// we'll use the format "oauth_token=foo&oauth_token_secret=bar" so we can
+// easily alter what portions of the auth data are stored
+- (NSString *)persistenceResponseString;
+- (void)setKeysForPersistenceResponseString:(NSString *)str;
+
+// method for distinguishing between the OAuth token being a request token and
+// an access token; use the canAuthorize property to determine if the
+// auth object has an access token
+- (BOOL)hasAccessToken;
+- (void)setHasAccessToken:(BOOL)flag;
+
+// methods for unit testing
++ (NSString *)normalizeQueryString:(NSString *)str;
+
+//
+// utilities
+//
+
++ (NSString *)encodedOAuthParameterForString:(NSString *)str;
++ (NSString *)unencodedOAuthParameterForString:(NSString *)str;
+
++ (NSDictionary *)dictionaryWithResponseData:(NSData *)data;
++ (NSDictionary *)dictionaryWithResponseString:(NSString *)responseStr;
+
++ (NSString *)scopeWithStrings:(NSString *)str, ...;
+
++ (NSString *)stringWithBase64ForData:(NSData *)data;
+
++ (NSString *)HMACSHA1HashForConsumerSecret:(NSString *)consumerSecret
+ tokenSecret:(NSString *)tokenSecret
+ body:(NSString *)body;
+
+#if GTL_OAUTH_SUPPORTS_RSASHA1_SIGNING
++ (NSString *)RSASHA1HashForString:(NSString *)source
+ privateKeyPEMString:(NSString *)key;
+#endif
+
+@end
View
1,247 GTMOAuthAuthentication.m
@@ -0,0 +1,1247 @@
+/* Copyright (c) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// HMAC digest
+#import <CommonCrypto/CommonHMAC.h>
+
+// RSA SHA-1 signing
+#if GTL_OAUTH_SUPPORTS_RSASHA1_SIGNING
+#include <openssl/sha.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#endif
+
+#define GTMOAUTHAUTHENTICATION_DEFINE_GLOBALS 1
+#import "GTMOAuthAuthentication.h"
+
+// standard OAuth keys
+static NSString *const kOAuthConsumerKey = @"oauth_consumer_key";
+static NSString *const kOAuthTokenKey = @"oauth_token";
+static NSString *const kOAuthCallbackKey = @"oauth_callback";
+static NSString *const kOAuthCallbackConfirmedKey = @"oauth_callback_confirmed";
+static NSString *const kOAuthTokenSecretKey = @"oauth_token_secret";
+static NSString *const kOAuthSignatureMethodKey = @"oauth_signature_method";
+static NSString *const kOAuthSignatureKey = @"oauth_signature";
+static NSString *const kOAuthTimestampKey = @"oauth_timestamp";
+static NSString *const kOAuthNonceKey = @"oauth_nonce";
+static NSString *const kOAuthVerifierKey = @"oauth_verifier";
+static NSString *const kOAuthVersionKey = @"oauth_version";
+
+// GetRequestToken extensions
+static NSString *const kOAuthDisplayNameKey = @"xoauth_displayname";
+static NSString *const kOAuthScopeKey = @"scope";
+
+// AuthorizeToken extensions
+static NSString *const kOAuthDomainKey = @"domain";
+static NSString *const kOAuthHostedDomainKey = @"hd";
+static NSString *const kOAuthIconURLKey = @"iconUrl";
+static NSString *const kOAuthLanguageKey = @"hl";
+static NSString *const kOAuthMobileKey = @"btmpl";
+
+// additional persistent keys
+static NSString *const kServiceProviderKey = @"serviceProvider";
+static NSString *const kUserEmailKey = @"email";
+static NSString *const kUserEmailIsVerifiedKey = @"isVerified";
+
+@interface GTMOAuthAuthentication (PrivateMethods)
+
+- (void)addAuthorizationHeaderToRequest:(NSMutableURLRequest *)request
+ forKeys:(NSArray *)keys;
+- (void)addParamsForKeys:(NSArray *)keys
+ toRequest:(NSMutableURLRequest *)request;
+
++ (NSString *)paramStringForParams:(NSArray *)params
+ joiner:(NSString *)joiner
+ shouldQuote:(BOOL)shouldQuote
+ shouldSort:(BOOL)shouldSort;
+
+- (NSString *)normalizedRequestURLStringForRequest:(NSURLRequest *)request;
+
+- (NSString *)signatureForParams:(NSMutableArray *)params
+ request:(NSURLRequest *)request;
+
+@end
+
+// OAuthParameter is a local class that exists just to make it easier to
+// sort descriptor pairs by name and encoded value
+@interface OAuthParameter : NSObject {
+@private
+ NSString *name_;
+ NSString *value_;
+}
+
+@property (nonatomic, copy) NSString *name;
+@property (nonatomic, copy) NSString *value;
+
++ (OAuthParameter *)parameterWithName:(NSString *)name
+ value:(NSString *)value;
+
++ (NSArray *)sortDescriptors;
+@end
+
+@implementation GTMOAuthAuthentication
+
+@synthesize realm = realm_;
+@synthesize privateKey = privateKey_;
+@synthesize shouldUseParamsToAuthorize = shouldUseParamsToAuthorize_;
+@synthesize userData = userData_;
+
+// create an authentication object, with hardcoded values for installed apps
+// of HMAC-SHA1 as signature method, and "anonymous" as the consumer key and
+// consumer secret (private key)
++ (GTMOAuthAuthentication *)authForInstalledApp {
+ // installed apps have fixed parameters
+ return [[[self alloc] initWithSignatureMethod:@"HMAC-SHA1"
+ consumerKey:@"anonymous"
+ privateKey:@"anonymous"] autorelease];
+}
+
+// create an authentication object, specifying the consumer key and
+// private key (both "anonymous" for installed apps) and the signature method
+// ("HMAC-SHA1" for installed apps)
+//
+// for signature method "RSA-SHA1", a proper consumer key and private key
+// must be supplied
+- (id)initWithSignatureMethod:(NSString *)signatureMethod
+ consumerKey:(NSString *)consumerKey
+ privateKey:(NSString *)privateKey {
+
+ self = [super init];
+ if (self != nil) {
+ paramValues_ = [[NSMutableDictionary alloc] init];
+
+ [self setConsumerKey:consumerKey];
+ [self setSignatureMethod:signatureMethod];
+ [self setPrivateKey:privateKey];
+
+ [self setVersion:@"1.0"];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [paramValues_ release];
+ [realm_ release];
+ [privateKey_ release];
+ [timestamp_ release];
+ [nonce_ release];
+ [userData_ release];
+ [super dealloc];
+}
+
+#pragma mark -
+
+- (NSMutableArray *)paramsForKeys:(NSArray *)keys
+ request:(NSURLRequest *)request {
+ // this is the magic routine that collects the parameters for the specified
+ // keys, and signs them
+ NSMutableArray *params = [NSMutableArray array];
+
+ for (NSString *key in keys) {
+ NSString *value = [paramValues_ objectForKey:key];
+ if ([value length] > 0) {
+ [params addObject:[OAuthParameter parameterWithName:key
+ value:value]];
+ }
+ }
+
+ // nonce and timestamp are generated on-the-fly by the getters
+ if ([keys containsObject:kOAuthNonceKey]) {
+ NSString *nonce = [self nonce];
+ [params addObject:[OAuthParameter parameterWithName:kOAuthNonceKey
+ value:nonce]];
+ }
+
+ if ([keys containsObject:kOAuthTimestampKey]) {
+ NSString *timestamp = [self timestamp];
+ [params addObject:[OAuthParameter parameterWithName:kOAuthTimestampKey
+ value:timestamp]];
+ }
+
+ // finally, compute the signature, if requested; the params
+ // must be complete for this
+ if ([keys containsObject:kOAuthSignatureKey]) {
+ NSString *signature = [self signatureForParams:params
+ request:request];
+ [params addObject:[OAuthParameter parameterWithName:kOAuthSignatureKey
+ value:signature]];
+ }
+
+ return params;
+}
+
++ (void)addQueryString:(NSString *)query
+ toParams:(NSMutableArray *)array {
+ // make param objects from the query parameters, and add them
+ // to the supplied array
+
+ // look for a query like foo=cat&bar=dog
+ if ([query length] > 0) {
+ // the standard test cases insist that + in the query string
+ // be encoded as " " - http://wiki.oauth.net/TestCases
+ query = [query stringByReplacingOccurrencesOfString:@"+"
+ withString:@" "];
+
+ // separate and step through the query parameter assignments
+ NSArray *items = [query componentsSeparatedByString:@"&"];
+
+ for (NSString *item in items) {
+ NSString *name = nil;
+ NSString *value = @"";
+
+ NSRange equalsRange = [item rangeOfString:@"="];
+ if (equalsRange.location != NSNotFound) {
+ // the parameter has at least one '='
+ name = [item substringToIndex:equalsRange.location];
+
+ if (equalsRange.location + 1 < [item length]) {
+ // there are characters after the '='
+ value = [item substringFromIndex:(equalsRange.location + 1)];
+
+ // remove percent-escapes from the parameter value; they'll be
+ // added back by OAuthParameter
+ value = [self unencodedOAuthParameterForString:value];
+ } else {
+ // no characters after the '='
+ }
+ } else {
+ // the parameter has no '='
+ name = item;
+ }
+
+ // remove percent-escapes from the parameter name; they'll be
+ // added back by OAuthParameter
+ name = [self unencodedOAuthParameterForString:name];
+
+ OAuthParameter *param = [OAuthParameter parameterWithName:name
+ value:value];
+ [array addObject:param];
+ }
+ }
+}
+
++ (void)addQueryFromRequest:(NSURLRequest *)request
+ toParams:(NSMutableArray *)array {
+ // get the query string from the request
+ NSString *query = [[request URL] query];
+ [self addQueryString:query toParams:array];
+}
+
++ (void)addBodyFromRequest:(NSURLRequest *)request
+ toParams:(NSMutableArray *)array {
+ // add non-GET form parameters to the array of param objects
+ NSString *method = [request HTTPMethod];
+ if (method != nil && ![method isEqual:@"GET"]) {
+ NSString *type = [request valueForHTTPHeaderField:@"Content-Type"];
+ if ([type hasPrefix:@"application/x-www-form-urlencoded"]) {
+ NSData *data = [request HTTPBody];
+ if ([data length] > 0) {
+ NSString *str = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding] autorelease];
+ if ([str length] > 0) {
+ [[self class] addQueryString:str toParams:array];
+ }
+ }
+ }
+ }
+}
+
+- (NSString *)signatureForParams:(NSMutableArray *)params
+ request:(NSURLRequest *)request {
+ // construct signature base string per
+ // http://oauth.net/core/1.0a/#signing_process
+ NSString *requestURLStr = [self normalizedRequestURLStringForRequest:request];
+ NSString *method = [[request HTTPMethod] uppercaseString];
+ if ([method length] == 0) {
+ method = @"GET";
+ }
+
+ // the signature params exclude the signature
+ NSMutableArray *signatureParams = [NSMutableArray arrayWithArray:params];
+
+ // add request query parameters
+ [[self class] addQueryFromRequest:request toParams:signatureParams];
+
+ // add parameters from the POST body, if any
+ [[self class] addBodyFromRequest:request toParams:signatureParams];
+
+ NSString *paramStr = [[self class] paramStringForParams:signatureParams
+ joiner:@"&"
+ shouldQuote:NO
+ shouldSort:YES];
+
+ // the base string includes the method, normalized request URL, and params
+ NSString *requestURLStrEnc = [[self class] encodedOAuthParameterForString:requestURLStr];
+ NSString *paramStrEnc = [[self class] encodedOAuthParameterForString:paramStr];
+
+ NSString *sigBaseString = [NSString stringWithFormat:@"%@&%@&%@",
+ method, requestURLStrEnc, paramStrEnc];
+
+ NSString *privateKey = [self privateKey];
+ NSString *signatureMethod = [self signatureMethod];
+ NSString *signature = nil;
+
+#if GTL_DEBUG_OAUTH_SIGNING
+ NSLog(@"signing request: %@\n", request);
+ NSLog(@"signing params: %@\n", params);
+#endif
+
+ if ([signatureMethod isEqual:kGTMOAuthSignatureMethodHMAC_SHA1]) {
+ NSString *tokenSecret = [self tokenSecret];
+ signature = [[self class] HMACSHA1HashForConsumerSecret:privateKey
+ tokenSecret:tokenSecret
+ body:sigBaseString];
+#if GTL_DEBUG_OAUTH_SIGNING
+ NSLog(@"hashing: %@&%@",
+ privateKey ? privateKey : @"",
+ tokenSecret ? tokenSecret : @"");
+ NSLog(@"base string: %@", sigBaseString);
+ NSLog(@"signature: %@", signature);
+#endif
+ }
+
+#if GTL_OAUTH_SUPPORTS_RSASHA1_SIGNING
+ else if ([signatureMethod isEqual:kGTMOAuthSignatureMethodRSA_SHA1]) {
+ signature = [[self class] RSASHA1HashForString:sigBaseString
+ privateKeyPEMString:privateKey];
+ }
+#endif
+
+ return signature;
+}
+
++ (NSString *)paramStringForParams:(NSArray *)params
+ joiner:(NSString *)joiner
+ shouldQuote:(BOOL)shouldQuote
+ shouldSort:(BOOL)shouldSort {
+ // create a string by joining the supplied param objects
+
+ if (shouldSort) {
+ // sort params by name and value
+ NSArray *descs = [OAuthParameter sortDescriptors];
+ params = [params sortedArrayUsingDescriptors:descs];
+ }
+
+ // make an array of the encoded name=value items
+ NSArray *encodedArray;
+ if (shouldQuote) {
+ encodedArray = [params valueForKey:@"quotedEncodedParam"];
+ } else {
+ encodedArray = [params valueForKey:@"encodedParam"];
+ }
+
+ // join the items
+ NSString *result = [encodedArray componentsJoinedByString:joiner];
+ return result;
+}
+
+- (NSString *)normalizedRequestURLStringForRequest:(NSURLRequest *)request {
+ // http://oauth.net/core/1.0a/#anchor13
+
+ NSURL *url = [[request URL] absoluteURL];
+
+ NSString *scheme = [[url scheme] lowercaseString];
+ NSString *host = [[url host] lowercaseString];
+ int port = [[url port] intValue];
+
+ // NSURL's path method has an unfortunate side-effect of unescaping the path,
+ // but CFURLCopyPath does not
+ CFStringRef cfPath = CFURLCopyPath((CFURLRef)url);
+ NSString *path = [NSMakeCollectable(cfPath) autorelease];
+
+ // include only non-standard ports for http or https
+ NSString *portStr;
+ if (port == 0
+ || ([scheme isEqual:@"http"] && port == 80)
+ || ([scheme isEqual:@"https"] && port == 443)) {
+ portStr = @"";
+ } else {
+ portStr = [NSString stringWithFormat:@":%u", port];
+ }
+
+ if ([path length] == 0) {
+ path = @"/";
+ }
+
+ NSString *result = [NSString stringWithFormat:@"%@://%@%@%@",
+ scheme, host, portStr, path];
+ return result;
+}
+
++ (NSArray *)tokenRequestKeys {
+ // keys for obtaining a request token, http://oauth.net/core/1.0a/#auth_step1
+ NSArray *keys = [NSArray arrayWithObjects:
+ kOAuthConsumerKey,
+ kOAuthSignatureMethodKey,
+ kOAuthSignatureKey,
+ kOAuthTimestampKey,
+ kOAuthNonceKey,
+ kOAuthVersionKey,
+ kOAuthCallbackKey,
+ // extensions
+ kOAuthDisplayNameKey,
+ kOAuthScopeKey,
+ nil];
+ return keys;
+}
+
++ (NSArray *)tokenAuthorizeKeys {
+ // keys for opening the authorize page, http://oauth.net/core/1.0a/#auth_step2
+ NSArray *keys = [NSArray arrayWithObjects:
+ kOAuthTokenKey,
+ // extensions
+ kOAuthDomainKey,
+ kOAuthHostedDomainKey,
+ kOAuthLanguageKey,
+ kOAuthMobileKey,
+ kOAuthScopeKey,
+ nil];
+ return keys;
+}
+
++ (NSArray *)tokenAccessKeys {
+ // keys for obtaining an access token, http://oauth.net/core/1.0a/#auth_step3
+ NSArray *keys = [NSArray arrayWithObjects:
+ kOAuthConsumerKey,
+ kOAuthTokenKey,
+ kOAuthSignatureMethodKey,
+ kOAuthSignatureKey,
+ kOAuthTimestampKey,
+ kOAuthNonceKey,
+ kOAuthVersionKey,
+ kOAuthVerifierKey, nil];
+ return keys;
+}
+
++ (NSArray *)tokenResourceKeys {
+ // keys for accessing a protected resource,
+ // http://oauth.net/core/1.0a/#anchor12
+ NSArray *keys = [NSArray arrayWithObjects:
+ kOAuthConsumerKey,
+ kOAuthTokenKey,
+ kOAuthSignatureMethodKey,
+ kOAuthSignatureKey,
+ kOAuthTimestampKey,
+ kOAuthNonceKey,
+ kOAuthVersionKey, nil];
+ return keys;
+}
+
+#pragma mark -
+
+- (void)setKeysForResponseDictionary:(NSDictionary *)dict {
+ NSString *token = [dict objectForKey:kOAuthTokenKey];
+ if (token) {
+ [self setToken:token];
+ }
+
+ NSString *secret = [dict objectForKey:kOAuthTokenSecretKey];
+ if (secret) {
+ [self setTokenSecret:secret];
+ }
+
+ NSString *callbackConfirmed = [dict objectForKey:kOAuthCallbackConfirmedKey];
+ if (callbackConfirmed) {
+ [self setCallbackConfirmed:callbackConfirmed];
+ }
+
+ NSString *verifier = [dict objectForKey:kOAuthVerifierKey];
+ if (verifier) {
+ [self setVerifier:verifier];
+ }
+
+ NSString *provider = [dict objectForKey:kServiceProviderKey];
+ if (provider) {
+ [self setServiceProvider:provider];
+ }
+
+ NSString *email = [dict objectForKey:kUserEmailKey];
+ if (email) {
+ [self setUserEmail:email];
+ }
+
+ NSString *verified = [dict objectForKey:kUserEmailIsVerifiedKey];
+ if (verified) {
+ [self setUserEmailIsVerified:verified];
+ }
+}
+
+- (void)setKeysForResponseData:(NSData *)data {
+ NSDictionary *dict = [[self class] dictionaryWithResponseData:data];
+ [self setKeysForResponseDictionary:dict];
+}
+
+- (void)setKeysForResponseString:(NSString *)str {
+ NSDictionary *dict = [[self class] dictionaryWithResponseString:str];
+ [self setKeysForResponseDictionary:dict];
+}
+
+#pragma mark -
+
+//
+// Methods for adding OAuth parameters either to queries or as a request header
+//
+
+- (void)addRequestTokenHeaderToRequest:(NSMutableURLRequest *)request {
+ // add request token params to the request's header
+ NSArray *keys = [[self class] tokenRequestKeys];
+ [self addAuthorizationHeaderToRequest:request
+ forKeys:keys];
+}
+
+- (void)addRequestTokenParamsToRequest:(NSMutableURLRequest *)request {
+ // add request token params to the request URL (not to the header)
+ NSArray *keys = [[self class] tokenRequestKeys];
+ [self addParamsForKeys:keys toRequest:request];
+}
+
+- (void)addAuthorizeTokenHeaderToRequest:(NSMutableURLRequest *)request {
+ // add authorize token params to the request's header
+ NSArray *keys = [[self class] tokenAuthorizeKeys];
+ [self addAuthorizationHeaderToRequest:request
+ forKeys:keys];
+}
+
+- (void)addAuthorizeTokenParamsToRequest:(NSMutableURLRequest *)request {
+ // add authorize token params to the request URL (not to the header)
+ NSArray *keys = [[self class] tokenAuthorizeKeys];
+ [self addParamsForKeys:keys toRequest:request];
+}
+
+- (void)addAccessTokenHeaderToRequest:(NSMutableURLRequest *)request {
+ // add access token params to the request's header
+ NSArray *keys = [[self class] tokenAccessKeys];
+ [self addAuthorizationHeaderToRequest:request
+ forKeys:keys];
+}
+
+- (void)addAccessTokenParamsToRequest:(NSMutableURLRequest *)request {
+ // add access token params to the request URL (not to the header)
+ NSArray *keys = [[self class] tokenAccessKeys];
+ [self addParamsForKeys:keys toRequest:request];
+}
+
+- (void)addResourceTokenHeaderToRequest:(NSMutableURLRequest *)request {
+ // add resource access token params to the request's header
+ NSArray *keys = [[self class] tokenResourceKeys];
+ [self addAuthorizationHeaderToRequest:request
+ forKeys:keys];
+}
+
+- (void)addResourceTokenParamsToRequest:(NSMutableURLRequest *)request {
+ // add resource access token params to the request URL (not to the header)
+ NSArray *keys = [[self class] tokenResourceKeys];
+ [self addParamsForKeys:keys toRequest:request];
+}
+
+//
+// underlying methods for constructing query parameters or request headers
+//
+
+- (void)addParams:(NSArray *)params toRequest:(NSMutableURLRequest *)request {
+ NSString *paramStr = [[self class] paramStringForParams:params
+ joiner:@"&"
+ shouldQuote:NO
+ shouldSort:NO];
+ NSURL *oldURL = [request URL];
+ NSString *query = [oldURL query];
+ if ([query length] > 0) {
+ query = [query stringByAppendingFormat:@"&%@", paramStr];
+ } else {
+ query = paramStr;
+ }
+
+ NSString *portStr = @"";
+ NSString *oldPort = [[oldURL port] stringValue];
+ if ([oldPort length] > 0) {
+ portStr = [@":" stringByAppendingString:oldPort];
+ }
+
+ NSString *qMark = [query length] > 0 ? @"?" : @"";
+ NSString *newURLStr = [NSString stringWithFormat:@"%@://%@%@%@%@%@",
+ [oldURL scheme], [oldURL host], portStr,
+ [oldURL path], qMark, query];
+
+ [request setURL:[NSURL URLWithString:newURLStr]];
+}
+
+- (void)addParamsForKeys:(NSArray *)keys toRequest:(NSMutableURLRequest *)request {
+ // For the specified keys, add the keys and values to the request URL.
+
+ NSMutableArray *params = [self paramsForKeys:keys request:request];
+ [self addParams:params toRequest:request];
+}
+
+- (void)addAuthorizationHeaderToRequest:(NSMutableURLRequest *)request
+ forKeys:(NSArray *)keys {
+ // make all the parameters, including a signature for all
+ NSMutableArray *params = [self paramsForKeys:keys request:request];
+
+ // split the params into "oauth_" params which go into the Auth header
+ // and others which get added to the query
+ NSMutableArray *oauthParams = [NSMutableArray array];
+ NSMutableArray *extendedParams = [NSMutableArray array];
+
+ for (OAuthParameter *param in params) {
+ NSString *name = [param name];
+ BOOL hasPrefix = [name hasPrefix:@"oauth_"];
+ if (hasPrefix) {
+ [oauthParams addObject:param];
+ } else {
+ [extendedParams addObject:param];
+ }
+ }
+
+ NSString *paramStr = [[self class] paramStringForParams:oauthParams
+ joiner:@", "
+ shouldQuote:YES
+ shouldSort:NO];
+
+ // include the realm string, if any, in the auth header
+ // http://oauth.net/core/1.0a/#auth_header
+ NSString *realmParam = @"";
+ NSString *realm = [self realm];
+ if ([realm length] > 0) {
+ NSString *encodedVal = [[self class] encodedOAuthParameterForString:realm];
+ realmParam = [NSString stringWithFormat:@"realm=\"%@\", ", encodedVal];
+ }
+
+ // set the parameters for "oauth_" keys and the realm
+ // in the authorization header
+ NSString *authHdr = [NSString stringWithFormat:@"OAuth %@%@",
+ realmParam, paramStr];
+ [request setValue:authHdr forHTTPHeaderField:@"Authorization"];
+
+ // add any other params as URL query parameters
+ if ([extendedParams count] > 0) {
+ [self addParams:extendedParams toRequest:request];
+ }
+
+#if GTL_DEBUG_OAUTH_SIGNING
+ NSLog(@"adding auth header: %@", authHdr);
+ NSLog(@"final request: %@", request);
+#endif
+}
+
+// general entry point for GTL library
+- (BOOL)authorizeRequest:(NSMutableURLRequest *)request {
+ NSString *token = [self token];
+ if ([token length] == 0) {
+ return NO;
+ } else {
+ if ([self shouldUseParamsToAuthorize]) {
+ [self addResourceTokenParamsToRequest:request];
+ } else {
+ [self addResourceTokenHeaderToRequest:request];
+ }
+ return YES;
+ }
+}
+
+- (BOOL)canAuthorize {
+ // this method's is just a higher-level version of hasAccessToken
+ return [self hasAccessToken];
+}
+
+#pragma mark GTMFetcherAuthorizationProtocol Methods
+
+// Implementation of GTMFetcherAuthorizationProtocol methods
+
+- (void)authorizeRequest:(NSMutableURLRequest *)request
+ delegate:(id)delegate
+ didFinishSelector:(SEL)sel {
+ // Authorization entry point with callback for OAuth 2
+ NSError *error = nil;
+ if (![self authorizeRequest:request]) {
+ // failed
+ error = [NSError errorWithDomain:kGTMOAuthErrorDomain
+ code:-1
+ userInfo:nil];
+ }
+
+ if (delegate && sel) {
+ NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:delegate];
+ [invocation setArgument:&self atIndex:2];
+ [invocation setArgument:&request atIndex:3];
+ [invocation setArgument:&error atIndex:4];
+ [invocation invoke];
+ }
+}
+
+- (void)stopAuthorization {
+ // nothing to do, since OAuth 1 authorization is synchronous
+}
+
+- (BOOL)isAuthorizingRequest:(NSURLRequest *)request {
+ // OAuth 1 auth is synchronous, so authorizations are never pending
+ return NO;
+}
+
+- (BOOL)isAuthorizedRequest:(NSURLRequest *)request {
+ if ([self shouldUseParamsToAuthorize]) {
+ // look for query parameter authorization
+ NSString *query = [[request URL] query];
+ NSDictionary *dict = [[self class] dictionaryWithResponseString:query];
+ NSString *token = [dict valueForKey:kOAuthTokenKey];
+ return ([token length] > 0);
+ } else {
+ // look for header authorization
+ NSString *authStr = [request valueForHTTPHeaderField:@"Authorization"];
+ return ([authStr length] > 0);
+ }
+}
+
+#pragma mark Persistence Response Strings
+
+- (void)setKeysForPersistenceResponseString:(NSString *)str {
+ // all persistence keys map directly to keys in paramValues_
+ [self setKeysForResponseString:str];
+}
+
+// this returns a "response string" that can be passed later to
+// setKeysForResponseString: to reuse an old access token in a new auth object
+- (NSString *)persistenceResponseString {
+ // make an array of OAuthParameters for the actual parameters we're
+ // persisting
+ NSArray *persistenceKeys = [NSArray arrayWithObjects:
+ kOAuthTokenKey,
+ kOAuthTokenSecretKey,
+ kServiceProviderKey,
+ kUserEmailKey,
+ kUserEmailIsVerifiedKey,
+ nil];
+
+ NSMutableArray *params = [self paramsForKeys:persistenceKeys request:nil];
+
+ NSString *responseStr = [[self class] paramStringForParams:params
+ joiner:@"&"
+ shouldQuote:NO
+ shouldSort:NO];
+ return responseStr;
+}
+
+- (void)reset {
+ [self setHasAccessToken:NO];
+ [self setToken:nil];
+ [self setTokenSecret:nil];
+ [self setUserEmail:nil];
+ [self setUserEmailIsVerified:nil];
+}
+
+#pragma mark Accessors
+
+- (NSString *)scope {
+ return [paramValues_ objectForKey:kOAuthScopeKey];
+}
+
+- (void)setScope:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthScopeKey];
+}
+
+- (NSString *)displayName {
+ return [paramValues_ objectForKey:kOAuthDisplayNameKey];
+}
+
+- (void)setDisplayName:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthDisplayNameKey];
+}
+
+- (NSString *)hostedDomain {
+ return [paramValues_ objectForKey:kOAuthHostedDomainKey];
+}
+
+- (void)setHostedDomain:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthHostedDomainKey];
+}
+
+- (NSString *)domain {
+ return [paramValues_ objectForKey:kOAuthDomainKey];
+}
+
+- (void)setDomain:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthDomainKey];
+}
+
+- (NSString *)iconURLString {
+ return [paramValues_ objectForKey:kOAuthIconURLKey];
+}
+
+- (void)setIconURLString:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthIconURLKey];
+}
+
+- (NSString *)language {
+ return [paramValues_ objectForKey:kOAuthLanguageKey];
+}
+
+- (void)setLanguage:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthLanguageKey];
+}
+
+- (NSString *)mobile {
+ return [paramValues_ objectForKey:kOAuthMobileKey];
+}
+
+- (void)setMobile:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthMobileKey];
+}
+
+- (NSString *)signatureMethod {
+ return [paramValues_ objectForKey:kOAuthSignatureMethodKey];
+}
+
+- (void)setSignatureMethod:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthSignatureMethodKey];
+}
+
+- (NSString *)consumerKey {
+ return [paramValues_ objectForKey:kOAuthConsumerKey];
+}
+
+- (void)setConsumerKey:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthConsumerKey];
+}
+
+- (NSString *)token {
+ return [paramValues_ objectForKey:kOAuthTokenKey];
+}
+
+- (void)setToken:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthTokenKey];
+}
+
+- (NSString *)callback {
+ return [paramValues_ objectForKey:kOAuthCallbackKey];
+}
+
+
+- (void)setCallback:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthCallbackKey];
+}
+
+- (NSString *)verifier {
+ return [paramValues_ objectForKey:kOAuthVerifierKey];
+}
+
+- (void)setVerifier:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthVerifierKey];
+}
+
+- (NSString *)serviceProvider {
+ return [paramValues_ objectForKey:kServiceProviderKey];
+}
+
+- (void)setServiceProvider:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kServiceProviderKey];
+}
+
+- (NSString *)userEmail {
+ return [paramValues_ objectForKey:kUserEmailKey];
+}
+
+- (void)setUserEmail:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kUserEmailKey];
+}
+
+- (NSString *)userEmailIsVerified {
+ return [paramValues_ objectForKey:kUserEmailIsVerifiedKey];
+}
+
+- (void)setUserEmailIsVerified:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kUserEmailIsVerifiedKey];
+}
+
+- (NSString *)tokenSecret {
+ return [paramValues_ objectForKey:kOAuthTokenSecretKey];
+}
+
+- (void)setTokenSecret:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthTokenSecretKey];
+}
+
+- (NSString *)callbackConfirmed {
+ return [paramValues_ objectForKey:kOAuthCallbackConfirmedKey];
+}
+
+- (void)setCallbackConfirmed:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthCallbackConfirmedKey];
+}
+
+- (NSString *)version {
+ return [paramValues_ objectForKey:kOAuthVersionKey];
+}
+
+- (void)setVersion:(NSString *)str {
+ [paramValues_ setValue:[[str copy] autorelease]
+ forKey:kOAuthVersionKey];
+}
+
+- (NSString *)timestamp {
+
+ if (timestamp_) return timestamp_; // for testing only
+
+ NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970];
+ unsigned long long timestampVal = (unsigned long long) timeInterval;
+ NSString *timestamp = [NSString stringWithFormat:@"%qu", timestampVal];
+ return timestamp;
+}
+
+- (void)setTimestamp:(NSString *)str {
+ // set a fixed timestamp, for testing only
+ [timestamp_ autorelease];
+ timestamp_ = [str copy];
+}
+
+- (NSString *)nonce {
+
+ if (nonce_) return nonce_; // for testing only
+
+ // make a random 64-bit number
+ unsigned long long nonceVal = ((unsigned long long) arc4random()) << 32
+ | (unsigned long long) arc4random();
+
+ NSString *nonce = [NSString stringWithFormat:@"%qu", nonceVal];
+ return nonce;
+}
+
+- (void)setNonce:(NSString *)str {
+ // set a fixed nonce, for testing only
+ [nonce_ autorelease];
+ nonce_ = [str copy];
+}
+
+// to avoid the ambiguity between request and access flavors of tokens,
+// we'll provide accessors solely for access tokens
+- (BOOL)hasAccessToken {
+ return hasAccessToken_ && ([[self token] length] > 0);
+}
+
+- (void)setHasAccessToken:(BOOL)flag {
+ hasAccessToken_ = flag;
+}
+
+- (NSString *)accessToken {
+ if (hasAccessToken_) {
+ return [self token];
+ } else {
+ return nil;
+ }
+}
+
+- (void)setAccessToken:(NSString *)str {
+ [self setToken:str];
+ [self setHasAccessToken:YES];
+}
+
+#pragma mark Utility Routines
+
++ (NSString *)encodedOAuthParameterForString:(NSString *)str {
+ // http://oauth.net/core/1.0a/#encoding_parameters
+
+ CFStringRef originalString = (CFStringRef) str;
+
+ CFStringRef leaveUnescaped = CFSTR("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "-._~");
+ CFStringRef forceEscaped = CFSTR("%!$&'()*+,/:;=?@");
+
+ CFStringRef escapedStr = NULL;
+ if (str) {
+ escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
+ originalString,
+ leaveUnescaped,
+ forceEscaped,
+ kCFStringEncodingUTF8);
+ [(id)CFMakeCollectable(escapedStr) autorelease];
+ }
+
+ return (NSString *)escapedStr;
+}
+
++ (NSString *)unencodedOAuthParameterForString:(NSString *)str {
+ NSString *plainStr = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ return plainStr;
+}
+
++ (NSDictionary *)dictionaryWithResponseString:(NSString *)responseStr {
+ // build a dictionary from a response string of the form
+ // "foo=cat&bar=dog". Missing or empty values are considered
+ // empty strings; keys and values are percent-decoded.
+ if (responseStr == nil) return nil;
+
+ NSMutableDictionary *responseDict = [NSMutableDictionary dictionary];
+
+ NSArray *items = [responseStr componentsSeparatedByString:@"&"];
+ for (NSString *item in items) {
+ NSScanner *scanner = [NSScanner scannerWithString:item];
+ NSString *key;
+
+ [scanner setCharactersToBeSkipped:nil];
+ if ([scanner scanUpToString:@"=" intoString:&key]) {
+ // if there's an "=", then scan the value, too, if any
+ NSString *value = @"";
+ if ([scanner scanString:@"=" intoString:nil]) {
+ // scan the rest of the string
+ [scanner scanUpToString:@"&" intoString:&value];
+ }
+ NSString *plainKey = [[self class] unencodedOAuthParameterForString:key];
+ NSString *plainValue = [[self class] unencodedOAuthParameterForString:value];
+
+ [responseDict setObject:plainValue
+ forKey:plainKey];
+ }
+ }
+ return responseDict;
+}
+
++ (NSDictionary *)dictionaryWithResponseData:(NSData *)data {
+ NSString *responseStr = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding] autorelease];
+ NSDictionary *dict = [self dictionaryWithResponseString:responseStr];
+ return dict;
+}
+
++ (NSString *)scopeWithStrings:(NSString *)str, ... {
+ // concatenate the strings, joined by a single space
+ NSString *result = @"";
+ NSString *joiner = @"";
+ if (str) {
+ va_list argList;
+ va_start(argList, str);
+ while (str) {
+ result = [result stringByAppendingFormat:@"%@%@", joiner, str];
+ joiner = @" ";
+ str = va_arg(argList, id);
+ }
+ va_end(argList);
+ }
+ return result;
+}
+
+#pragma mark Signing Methods
+
++ (NSString *)HMACSHA1HashForConsumerSecret:(NSString *)consumerSecret
+ tokenSecret:(NSString *)tokenSecret
+ body:(NSString *)body {
+ NSString *encodedConsumerSecret = [self encodedOAuthParameterForString:consumerSecret];
+ NSString *encodedTokenSecret = [self encodedOAuthParameterForString:tokenSecret];
+
+ NSString *key = [NSString stringWithFormat:@"%@&%@",
+ encodedConsumerSecret ? encodedConsumerSecret : @"",
+ encodedTokenSecret ? encodedTokenSecret : @""];
+
+ NSMutableData *sigData = [NSMutableData dataWithLength:CC_SHA1_DIGEST_LENGTH];
+
+ CCHmac(kCCHmacAlgSHA1,
+ [key UTF8String], [key length],
+ [body UTF8String], [body length],
+ [sigData mutableBytes]);
+
+ NSString *signature = [self stringWithBase64ForData:sigData];
+ return signature;
+}
+
+#if GTL_OAUTH_SUPPORTS_RSASHA1_SIGNING
++ (NSString *)RSASHA1HashForString:(NSString *)source
+ privateKeyPEMString:(NSString *)key {
+ if (source == nil || key == nil) return nil;
+
+ OpenSSL_add_all_algorithms();
+ // add EVP_cleanup
+
+ NSString *signature = nil;
+
+ // make a SHA-1 digest of the source string
+ const char* sourceChars = [source UTF8String];
+
+ unsigned char digest[SHA_DIGEST_LENGTH];
+ SHA1((const unsigned char *)sourceChars, strlen(sourceChars), digest);
+
+ // get an RSA from the private key PEM, and use it to sign the digest
+ const char* keyChars = [key UTF8String];
+ BIO* keyBio = BIO_new_mem_buf((char *) keyChars, -1); // -1 = use strlen()
+
+
+ if (keyBio != NULL) {
+ // BIO_set_flags(keyBio, BIO_FLAGS_BASE64_NO_NL);
+ RSA *rsa_key = NULL;
+
+ rsa_key = PEM_read_bio_RSAPrivateKey(keyBio, NULL, NULL, NULL);
+ if (rsa_key != NULL) {
+
+ unsigned int sigLen = 0;
+ unsigned char *sigBuff = malloc(RSA_size(rsa_key));
+
+ int result = RSA_sign(NID_sha1, digest, (unsigned int) sizeof(digest),
+ sigBuff, &sigLen, rsa_key);
+
+ if (result != 0) {
+ NSData *sigData = [NSData dataWithBytes:sigBuff length:sigLen];
+ signature = [self stringWithBase64ForData:sigData];
+ }
+
+ free(sigBuff);
+
+ RSA_free(rsa_key);
+ }
+ BIO_free(keyBio);
+ }
+
+ return signature;
+}
+#endif // GTL_OAUTH_SUPPORTS_RSASHA1_SIGNING
+
++ (NSString *)stringWithBase64ForData:(NSData *)data {
+ // Cyrus Najmabadi elegent little encoder from
+ // http://www.cocoadev.com/index.pl?BaseSixtyFour
+ if (data == nil) return nil;
+
+ const uint8_t* input = [data bytes];
+ NSUInteger length = [data length];
+
+ NSUInteger bufferSize = ((length + 2) / 3) * 4;
+ NSMutableData* buffer = [NSMutableData dataWithLength:bufferSize];
+
+ uint8_t* output = [buffer mutableBytes];
+
+ static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ for (NSUInteger i = 0; i < length; i += 3) {
+ NSInteger value = 0;
+ for (NSUInteger j = i; j < (i + 3); j++) {
+ value <<= 8;
+
+ if (j < length) {
+ value |= (0xFF & input[j]);
+ }
+ }
+
+ NSInteger idx = (i / 3) * 4;
+ output[idx + 0] = table[(value >> 18) & 0x3F];
+ output[idx + 1] = table[(value >> 12) & 0x3F];
+ output[idx + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
+ output[idx + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
+ }
+
+ NSString *result = [[[NSString alloc] initWithData:buffer
+ encoding:NSASCIIStringEncoding] autorelease];
+ return result;
+}
+
+#pragma mark Unit Test Entry Points
+
++ (NSString *)normalizeQueryString:(NSString *)str {
+ // unit testing method
+
+ // convert the string of parameters to sortable param objects
+ NSMutableArray *params = [NSMutableArray array];
+ [self addQueryString:str toParams:params];
+
+ // sort and join the param objects
+ NSString *paramStr = [self paramStringForParams:params
+ joiner:@"&"
+ shouldQuote:NO
+ shouldSort:YES];
+ return paramStr;
+}
+
+@end
+
+// This class represents key-value pairs so they can be sorted by both
+// name and encoded value
+@implementation OAuthParameter
+
+@synthesize name = name_;
+@synthesize value = value_;
+
++ (OAuthParameter *)parameterWithName:(NSString *)name
+ value:(NSString *)value {
+ OAuthParameter *obj = [[[self alloc] init] autorelease];
+ [obj setName:name];
+ [obj setValue:value];
+ return obj;
+}
+
+- (void)dealloc {
+ [name_ release];
+ [value_ release];
+ [super dealloc];
+}
+
+- (NSString *)encodedValue {
+ NSString *value = [self value];
+ NSString *result = [GTMOAuthAuthentication encodedOAuthParameterForString:value];
+ return result;
+}
+
+- (NSString *)encodedName {
+ NSString *name = [self name];
+ NSString *result = [GTMOAuthAuthentication encodedOAuthParameterForString:name];
+ return result;
+}
+
+- (NSString *)encodedParam {
+ NSString *str = [NSString stringWithFormat:@"%@=%@",
+ [self encodedName], [self encodedValue]];
+ return str;
+}
+
+- (NSString *)quotedEncodedParam {
+ NSString *str = [NSString stringWithFormat:@"%@=\"%@\"",
+ [self encodedName], [self encodedValue]];
+ return str;
+}
+
+- (NSString *)description {
+ return [self encodedParam];
+}
+
++ (NSArray *)sortDescriptors {
+ // sort by name and value
+ SEL sel = @selector(compare:);
+
+ NSSortDescriptor *desc1, *desc2;
+ desc1 = [[[NSSortDescriptor alloc] initWithKey:@"name"
+ ascending:YES
+ selector:sel] autorelease];
+ desc2 = [[[NSSortDescriptor alloc] initWithKey:@"encodedValue"
+ ascending:YES
+ selector:sel] autorelease];
+
+ NSArray *sortDescriptors = [NSArray arrayWithObjects:desc1, desc2, nil];
+ return sortDescriptors;
+}
+
+@end
View
156 GTMOAuthSignIn.h
@@ -0,0 +1,156 @@
+/* Copyright (c) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// This sign-in object handles the sequence of fetches and displays and closes
+// the web view window window as needed for users to sign in.
+//
+// Typically, this will be managed for the application by
+// GTMOAuthWindowController, so it's interesting only if
+// you are creating your own window controller for sign-in.
+//
+//
+// Window controller delegate methods
+//
+// The window controller implements two methods for use by the sign-in object,
+// the webRequestSelector and the finishedSelector:
+//
+// webRequestSelector has a signature matching
+// - (void)signIn:(GTMOAuthSignIn *)signIn displayRequest:(NSURLRequest *)request
+//
+// The web request selector will be invoked with a request to be displayed, or
+// nil to close the window when the final callback request has been encountered.
+//
+//
+// finishedSelector has a signature matching
+// - (void)signin:(GTMOAuthSignIn *)signin finishedWithAuth:(GTMOAuthAuthentication *)auth error:(NSError *)error
+//
+// The finished selector will be invoked when sign-in has completed, except
+// when explicitly canceled by calling cancelSigningIn
+//
+
+#import <Foundation/Foundation.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+
+#ifdef GTL_TARGET_NAMESPACE
+ #import "GTLDefines.h"
+#endif
+
+#import "GTMOAuthAuthentication.h"
+#import "GTMHTTPFetcher.h"
+
+enum {
+ // error code indicating that the window was prematurely closed
+ kGTMOAuthErrorWindowClosed = -1000
+};
+
+@interface GTMOAuthSignIn : NSObject {
+ @private
+ GTMOAuthAuthentication *auth_;
+
+ NSURL *requestURL_;
+ NSURL *authorizeURL_;
+ NSURL *accessURL_;
+
+ id delegate_;
+ SEL webRequestSelector_;
+ SEL finishedSelector_;
+
+ id <GTMHTTPFetcherServiceProtocol> fetcherService_;
+
+ GTMHTTPFetcher *pendingFetcher_;
+
+#if !GTM_OAUTH_SKIP_GOOGLE_SUPPORT
+ BOOL shouldFetchGoogleUserInfo_;
+#endif
+
+ SCNetworkReachabilityRef reachabilityRef_;
+ NSTimer *networkLossTimer_;
+ NSTimeInterval networkLossTimeoutInterval_;
+ BOOL hasNotifiedNetworkLoss_;
+
+ id userData_;
+}
+
+@property (nonatomic, retain) id delegate;
+@property (nonatomic, retain) GTMOAuthAuthentication *authentication;
+@property (nonatomic, retain) id userData;
+
+@property (nonatomic, retain, readonly) NSURL *requestTokenURL;
+@property (nonatomic, retain, readonly) NSURL *authorizeTokenURL;
+@property (nonatomic, retain, readonly) NSURL *accessTokenURL;
+
+#if !GTM_OAUTH_SKIP_GOOGLE_SUPPORT
+@property (nonatomic, assign) BOOL shouldFetchGoogleUserInfo;
+#endif
+
+// Property for the optional fetcher service instance to be used to create
+// fetchers
+@property (nonatomic, retain) id <GTMHTTPFetcherServiceProtocol> fetcherService;
+
+// The default timeout for an unreachable network during display of the
+// sign-in page is 30 seconds; set this to 0 to have no timeout
+@property (nonatomic, assign) NSTimeInterval networkLossTimeoutInterval;
+
+#if !GTM_OAUTH_SKIP_GOOGLE_SUPPORT
+// Convenience entry point for accessing Google APIs; this creates the
+// authentication object, and uses standard URL endpoints for OAuth to
+// Google services
+//
+// The delegate is retained until sign-in has completed or been canceled
+- (id)initWithGoogleAuthenticationForScope:(NSString *)scope
+ language:(NSString *)language
+ delegate:(id)delegate
+ webRequestSelector:(SEL)webRequestSelector
+ finishedSelector:(SEL)finishedSelector;
+#endif
+
+// Entry point for accessing non-Google APIs
+//
+// designated initializer
+- (id)initWithAuthentication:(GTMOAuthAuthentication *)auth
+ requestTokenURL:(NSURL *)requestURL
+ authorizeTokenURL:(NSURL *)authorizeURL
+ accessTokenURL:(NSURL *)accessURL
+ delegate:(id)delegate
+ webRequestSelector:(SEL)webRequestSelector
+ finishedSelector:(SEL)finishedSelector;
+
+#pragma mark Methods used by the Window Controller
+
+// Start the sequence of fetches and sign-in window display for sign-in
+- (BOOL)startSigningIn;
+
+// Stop any pending fetches, and close the window (but don't call the
+// delegate's finishedSelector)
+- (void)cancelSigningIn;
+
+// Window controllers must tell the sign-in object about any redirect
+// requested by the web view; if this returns YES then the redirect
+// was handled by the sign-in object (typically by closing the window)
+// and the request should be ignored by the window controller's web view
+- (BOOL)requestRedirectedToRequest:(NSURLRequest *)redirectedRequest;
+
+// Window controllers must tell the sign-in object if the window was closed
+// prematurely by the user (but not by the sign-in object); this calls the
+// delegate's finishedSelector
+- (void)windowWasClosed;
+
+#if !GTM_OAUTH_SKIP_GOOGLE_SUPPORT
+// Revocation of an authorized token from Google
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuthAuthentication *)auth;
+#endif
+
+@end
View
609 GTMOAuthSignIn.m
@@ -0,0 +1,609 @@
+/* Copyright (c) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define GTMOAUTHSIGNIN_DEFINE_GLOBALS 1
+#import "GTMOAuthSignIn.h"
+
+// we'll default to timing out if the network becomes unreachable for more
+// than 30 seconds when the sign-in page is displayed
+static const NSTimeInterval kDefaultNetworkLossTimeoutInterval = 30.0;
+
+@interface GTMOAuthSignIn ()
+
+@property (nonatomic, retain, readwrite) NSURL *requestTokenURL;
+@property (nonatomic, retain, readwrite) NSURL *authorizeTokenURL;
+@property (nonatomic, retain, readwrite) NSURL *accessTokenURL;
+
+- (void)invokeFinalCallbackWithError:(NSError *)error;
+
+- (void)startWebRequest;
+#if !GTM_OAUTH_SKIP_GOOGLE_SUPPORT
+- (void)fetchGoogleUserInfo;
+#endif
+
+- (GTMHTTPFetcher *)pendingFetcher;
+- (void)setPendingFetcher:(GTMHTTPFetcher *)obj fetchType:(NSString *)fetchType;
+
+- (void)accessFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error;
+- (void)requestFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error;
+#if !GTM_OAUTH_SKIP_GOOGLE_SUPPORT
+- (void)infoFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error;
+#endif
+
+- (void)closeTheWindow;
+
+- (void)startReachabilityCheck;
+- (void)stopReachabilityCheck;
+- (void)reachabilityTarget:(SCNetworkReachabilityRef)reachabilityRef
+ changedFlags:(SCNetworkConnectionFlags)flags;
+- (void)reachabilityTimerFired:(NSTimer *)timer;
+@end
+
+@implementation GTMOAuthSignIn
+
+@synthesize delegate = delegate_,
+ authentication = auth_,
+ fetcherService = fetcherService_,
+ userData = userData_,
+ requestTokenURL = requestURL_,
+ authorizeTokenURL = authorizeURL_,
+ accessTokenURL = accessURL_,
+#if !GTM_OAUTH_SKIP_GOOGLE_SUPPORT
+ shouldFetchGoogleUserInfo = shouldFetchGoogleUserInfo_,
+#endif
+ networkLossTimeoutInterval = networkLossTimeoutInterval_;
+
+#if !GTM_OAUTH_SKIP_GOOGLE_SUPPORT
+- (id)initWithGoogleAuthenticationForScope:(NSString *)scope
+ language:(NSString *)language
+ delegate:(id)delegate
+ webRequestSelector:(SEL)webRequestSelector
+ finishedSelector:(SEL)finishedSelector {
+ // standard Google OAuth endpoints
+ //
+ // http://code.google.com/apis/accounts/docs/OAuth_ref.html
+ NSURL *requestURL = [NSURL URLWithString:@"https://www.google.com/accounts/OAuthGetRequestToken"];
+ NSURL *authorizeURL = [NSURL URLWithString:@"https://www.google.com/accounts/OAuthAuthorizeToken"];
+ NSURL *accessURL = [NSURL URLWithString:@"https://www.google.com/accounts/OAuthGetAccessToken"];
+
+ GTMOAuthAuthentication *auth = [GTMOAuthAuthentication authForInstalledApp];
+ [auth setScope:scope];
+ [auth setLanguage:language];
+ [auth setServiceProvider:kGTMOAuthServiceProviderGoogle];
+
+ // open question: should we call [auth setHostedDomain: ] here too?
+
+ // we'll use the mobile user interface for embedded sign-in as it's smaller
+ // and somewhat more suitable for embedded usage
+ [auth setMobile:@"mobile"];
+
+ // we'll use a non-existent callback address, and close the window
+ // immediately when it's requested
+ [auth setCallback:@"http://www.google.com/OAuthCallback"];
+
+ return [self initWithAuthentication:auth
+ requestTokenURL:requestURL
+ authorizeTokenURL:authorizeURL
+ accessTokenURL:accessURL
+ delegate:delegate
+ webRequestSelector:webRequestSelector
+ finishedSelector:finishedSelector];
+}
+#endif
+
+- (id)initWithAuthentication:(GTMOAuthAuthentication *)auth
+ requestTokenURL:(NSURL *)requestURL
+ authorizeTokenURL:(NSURL *)authorizeURL
+ accessTokenURL:(NSURL *)accessURL
+ delegate:(id)delegate
+ webRequestSelector:(SEL)webRequestSelector
+ finishedSelector:(SEL)finishedSelector {
+
+ // check the selectors on debug builds
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, webRequestSelector,
+ @encode(GTMOAuthSignIn *), @encode(NSURLRequest *), 0);
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector,
+ @encode(GTMOAuthSignIn *), @encode(GTMOAuthAuthentication *),