aboutsummaryrefslogtreecommitdiffhomepage
path: root/example
diff options
context:
space:
mode:
authorGravatar Hoa V. DINH <dinh.viet.hoa@gmail.com>2013-07-08 20:36:55 -0700
committerGravatar Hoa V. DINH <dinh.viet.hoa@gmail.com>2013-07-08 20:37:41 -0700
commit89801946f05774d3037b15f11b6a5d249c776804 (patch)
tree7d3b7512a11ae45bb9573811e784f44a4ead84cd /example
parent9c721bbf410e2064a6fb69f8b1249fd785d1c275 (diff)
Added OAuth 2.0 support to examples
Diffstat (limited to 'example')
-rwxr-xr-xexample/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetchHistory.h187
-rwxr-xr-xexample/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetchHistory.m605
-rwxr-xr-xexample/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetcher.h766
-rwxr-xr-xexample/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetcher.m1956
-rw-r--r--example/common/gtm-oauth2/Source/GTMOAuth2Authentication.h356
-rw-r--r--example/common/gtm-oauth2/Source/GTMOAuth2Authentication.m1275
-rw-r--r--example/common/gtm-oauth2/Source/GTMOAuth2SignIn.h187
-rw-r--r--example/common/gtm-oauth2/Source/GTMOAuth2SignIn.m939
-rw-r--r--example/common/gtm-oauth2/Source/Mac/GTMOAuth2Window.xib109
-rw-r--r--example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.h332
-rw-r--r--example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m727
-rw-r--r--example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.h376
-rw-r--r--example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.m1070
-rw-r--r--example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewTouch.xib494
-rw-r--r--example/ios/iOS UI Test/iOS UI Test.xcodeproj/project.pbxproj71
-rw-r--r--example/ios/iOS UI Test/iOS UI Test/MasterViewController.m77
-rw-r--r--example/ios/iOS UI Test/iOS UI Test/SettingsViewController.h3
-rw-r--r--example/ios/iOS UI Test/iOS UI Test/SettingsViewController.m3
-rw-r--r--example/ios/iOS UI Test/iOS UI Test/SettingsViewController.xib283
-rw-r--r--example/mac/macExample/macExample.xcodeproj/project.pbxproj86
-rw-r--r--example/mac/macExample/macExample/AppDelegate.m117
-rw-r--r--example/mac/macExample/macExample/MCTMsgListViewController.h5
-rw-r--r--example/mac/macExample/macExample/MCTMsgListViewController.m27
-rw-r--r--example/mac/macExample/macExample/en.lproj/MainMenu.xib4700
24 files changed, 10693 insertions, 4058 deletions
diff --git a/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetchHistory.h b/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetchHistory.h
new file mode 100755
index 00000000..96018f5d
--- /dev/null
+++ b/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetchHistory.h
@@ -0,0 +1,187 @@
+/* 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.
+ */
+
+//
+// GTMHTTPFetchHistory.h
+//
+
+//
+// Users of the GTMHTTPFetcher class may optionally create and set a fetch
+// history object. The fetch history provides "memory" between subsequent
+// fetches, including:
+//
+// - For fetch responses with Etag headers, the fetch history
+// remembers the response headers. Future fetcher requests to the same URL
+// will be given an "If-None-Match" header, telling the server to return
+// a 304 Not Modified status if the response is unchanged, reducing the
+// server load and network traffic.
+//
+// - Optionally, the fetch history can cache the ETagged data that was returned
+// in the responses that contained Etag headers. If a later fetch
+// results in a 304 status, the fetcher will return the cached ETagged data
+// to the client along with a 200 status, hiding the 304.
+//
+// - The fetch history can track cookies.
+//
+
+#pragma once
+
+#import <Foundation/Foundation.h>
+
+#import "GTMHTTPFetcher.h"
+
+#undef _EXTERN
+#undef _INITIALIZE_AS
+#ifdef GTMHTTPFETCHHISTORY_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
+
+
+// default data cache size for when we're caching responses to handle "not
+// modified" errors for the client
+#if GTM_IPHONE
+// iPhone: up to 1MB memory
+_EXTERN const NSUInteger kGTMDefaultETaggedDataCacheMemoryCapacity _INITIALIZE_AS(1*1024*1024);
+#else
+// Mac OS X: up to 15MB memory
+_EXTERN const NSUInteger kGTMDefaultETaggedDataCacheMemoryCapacity _INITIALIZE_AS(15*1024*1024);
+#endif
+
+// forward declarations
+@class GTMURLCache;
+@class GTMCookieStorage;
+
+@interface GTMHTTPFetchHistory : NSObject <GTMHTTPFetchHistoryProtocol> {
+ @private
+ GTMURLCache *etaggedDataCache_;
+ BOOL shouldRememberETags_;
+ BOOL shouldCacheETaggedData_; // if NO, then only headers are cached
+ GTMCookieStorage *cookieStorage_;
+}
+
+// With caching enabled, previously-cached data will be returned instead of
+// 304 Not Modified responses when repeating a fetch of an URL that previously
+// included an ETag header in its response
+@property (assign) BOOL shouldRememberETags; // default: NO
+@property (assign) BOOL shouldCacheETaggedData; // default: NO
+
+// the default ETag data cache capacity is kGTMDefaultETaggedDataCacheMemoryCapacity
+@property (assign) NSUInteger memoryCapacity;
+
+@property (retain) GTMCookieStorage *cookieStorage;
+
+- (id)initWithMemoryCapacity:(NSUInteger)totalBytes
+ shouldCacheETaggedData:(BOOL)shouldCacheETaggedData;
+
+- (void)updateRequest:(NSMutableURLRequest *)request isHTTPGet:(BOOL)isHTTPGet;
+
+- (void)clearETaggedDataCache;
+- (void)clearHistory;
+
+- (void)removeAllCookies;
+
+@end
+
+
+// GTMURLCache and GTMCachedURLResponse have interfaces similar to their
+// NSURLCache counterparts, in hopes that someday the NSURLCache versions
+// can be used. But in 10.5.8, those are not reliable enough except when
+// used with +setSharedURLCache. Our goal here is just to cache
+// responses for handling If-None-Match requests that return
+// "Not Modified" responses, not for replacing the general URL
+// caches.
+
+@interface GTMCachedURLResponse : NSObject {
+ @private
+ NSURLResponse *response_;
+ NSData *data_;
+ NSDate *useDate_; // date this response was last saved or used
+ NSDate *reservationDate_; // date this response's ETag was used
+}
+
+@property (readonly) NSURLResponse* response;
+@property (readonly) NSData* data;
+
+// date the response was saved or last accessed
+@property (retain) NSDate *useDate;
+
+// date the response's ETag header was last used for a fetch request
+@property (retain) NSDate *reservationDate;
+
+- (id)initWithResponse:(NSURLResponse *)response data:(NSData *)data;
+@end
+
+@interface GTMURLCache : NSObject {
+ NSMutableDictionary *responses_; // maps request URL to GTMCachedURLResponse
+ NSUInteger memoryCapacity_; // capacity of NSDatas in the responses
+ NSUInteger totalDataSize_; // sum of sizes of NSDatas of all responses
+ NSTimeInterval reservationInterval_; // reservation expiration interval
+}
+
+@property (assign) NSUInteger memoryCapacity;
+
+- (id)initWithMemoryCapacity:(NSUInteger)totalBytes;
+
+- (GTMCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;
+- (void)storeCachedResponse:(GTMCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;
+- (void)removeCachedResponseForRequest:(NSURLRequest *)request;
+- (void)removeAllCachedResponses;
+
+// for unit testing
+- (void)setReservationInterval:(NSTimeInterval)secs;
+- (NSDictionary *)responses;
+- (NSUInteger)totalDataSize;
+@end
+
+@interface GTMCookieStorage : NSObject <GTMCookieStorageProtocol> {
+ @private
+ // The cookie storage object manages an array holding cookies, but the array
+ // is allocated externally (it may be in a fetcher object or the static
+ // fetcher cookie array.) See the fetcher's setCookieStorageMethod:
+ // for allocation of this object and assignment of its cookies array.
+ NSMutableArray *cookies_;
+}
+
+// add all NSHTTPCookies in the supplied array to the storage array,
+// replacing cookies in the storage array as appropriate
+// Side effect: removes expired cookies from the storage array
+- (void)setCookies:(NSArray *)newCookies;
+
+// retrieve all cookies appropriate for the given URL, considering
+// domain, path, cookie name, expiration, security setting.
+// Side effect: removes expired cookies from the storage array
+- (NSArray *)cookiesForURL:(NSURL *)theURL;
+
+// return a cookie with the same name, domain, and path as the
+// given cookie, or else return nil if none found
+//
+// Both the cookie being tested and all stored cookies should
+// be valid (non-nil name, domains, paths)
+- (NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie;
+
+// remove any expired cookies, excluding cookies with nil expirations
+- (void)removeExpiredCookies;
+
+- (void)removeAllCookies;
+
+@end
diff --git a/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetchHistory.m b/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetchHistory.m
new file mode 100755
index 00000000..2c859230
--- /dev/null
+++ b/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetchHistory.m
@@ -0,0 +1,605 @@
+/* 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.
+ */
+
+//
+// GTMHTTPFetchHistory.m
+//
+
+#define GTMHTTPFETCHHISTORY_DEFINE_GLOBALS 1
+
+#import "GTMHTTPFetchHistory.h"
+
+const NSTimeInterval kCachedURLReservationInterval = 60.0; // 1 minute
+static NSString* const kGTMIfNoneMatchHeader = @"If-None-Match";
+static NSString* const kGTMETagHeader = @"Etag";
+
+@implementation GTMCookieStorage
+
+- (id)init {
+ self = [super init];
+ if (self != nil) {
+ cookies_ = [[NSMutableArray alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [cookies_ release];
+ [super dealloc];
+}
+
+// Add all cookies in the new cookie array to the storage,
+// replacing stored cookies as appropriate.
+//
+// Side effect: removes expired cookies from the storage array.
+- (void)setCookies:(NSArray *)newCookies {
+
+ @synchronized(cookies_) {
+ [self removeExpiredCookies];
+
+ for (NSHTTPCookie *newCookie in newCookies) {
+ if ([[newCookie name] length] > 0
+ && [[newCookie domain] length] > 0
+ && [[newCookie path] length] > 0) {
+
+ // remove the cookie if it's currently in the array
+ NSHTTPCookie *oldCookie = [self cookieMatchingCookie:newCookie];
+ if (oldCookie) {
+ [cookies_ removeObjectIdenticalTo:oldCookie];
+ }
+
+ // make sure the cookie hasn't already expired
+ NSDate *expiresDate = [newCookie expiresDate];
+ if ((!expiresDate) || [expiresDate timeIntervalSinceNow] > 0) {
+ [cookies_ addObject:newCookie];
+ }
+
+ } else {
+ NSAssert1(NO, @"Cookie incomplete: %@", newCookie);
+ }
+ }
+ }
+}
+
+- (void)deleteCookie:(NSHTTPCookie *)cookie {
+ @synchronized(cookies_) {
+ NSHTTPCookie *foundCookie = [self cookieMatchingCookie:cookie];
+ if (foundCookie) {
+ [cookies_ removeObjectIdenticalTo:foundCookie];
+ }
+ }
+}
+
+// Retrieve all cookies appropriate for the given URL, considering
+// domain, path, cookie name, expiration, security setting.
+// Side effect: removed expired cookies from the storage array.
+- (NSArray *)cookiesForURL:(NSURL *)theURL {
+
+ NSMutableArray *foundCookies = nil;
+
+ @synchronized(cookies_) {
+ [self removeExpiredCookies];
+
+ // We'll prepend "." to the desired domain, since we want the
+ // actual domain "nytimes.com" to still match the cookie domain
+ // ".nytimes.com" when we check it below with hasSuffix.
+ NSString *host = [[theURL host] lowercaseString];
+ NSString *path = [theURL path];
+ NSString *scheme = [theURL scheme];
+
+ NSString *domain = nil;
+ BOOL isLocalhostRetrieval = NO;
+
+ if ([host isEqual:@"localhost"]) {
+ isLocalhostRetrieval = YES;
+ } else {
+ if (host) {
+ domain = [@"." stringByAppendingString:host];
+ }
+ }
+
+ NSUInteger numberOfCookies = [cookies_ count];
+ for (NSUInteger idx = 0; idx < numberOfCookies; idx++) {
+
+ NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:idx];
+
+ NSString *cookieDomain = [[storedCookie domain] lowercaseString];
+ NSString *cookiePath = [storedCookie path];
+ BOOL cookieIsSecure = [storedCookie isSecure];
+
+ BOOL isDomainOK;
+
+ if (isLocalhostRetrieval) {
+ // prior to 10.5.6, the domain stored into NSHTTPCookies for localhost
+ // is "localhost.local"
+ isDomainOK = [cookieDomain isEqual:@"localhost"]
+ || [cookieDomain isEqual:@"localhost.local"];
+ } else {
+ isDomainOK = [domain hasSuffix:cookieDomain];
+ }
+
+ BOOL isPathOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath];
+ BOOL isSecureOK = (!cookieIsSecure) || [scheme isEqual:@"https"];
+
+ if (isDomainOK && isPathOK && isSecureOK) {
+ if (foundCookies == nil) {
+ foundCookies = [NSMutableArray arrayWithCapacity:1];
+ }
+ [foundCookies addObject:storedCookie];
+ }
+ }
+ }
+ return foundCookies;
+}
+
+// Return a cookie from the array with the same name, domain, and path as the
+// given cookie, or else return nil if none found.
+//
+// Both the cookie being tested and all cookies in the storage array should
+// be valid (non-nil name, domains, paths).
+//
+// Note: this should only be called from inside a @synchronized(cookies_) block
+- (NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie {
+
+ NSUInteger numberOfCookies = [cookies_ count];
+ NSString *name = [cookie name];
+ NSString *domain = [cookie domain];
+ NSString *path = [cookie path];
+
+ NSAssert3(name && domain && path, @"Invalid cookie (name:%@ domain:%@ path:%@)",
+ name, domain, path);
+
+ for (NSUInteger idx = 0; idx < numberOfCookies; idx++) {
+
+ NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:idx];
+
+ if ([[storedCookie name] isEqual:name]
+ && [[storedCookie domain] isEqual:domain]
+ && [[storedCookie path] isEqual:path]) {
+
+ return storedCookie;
+ }
+ }
+ return nil;
+}
+
+
+// Internal routine to remove any expired cookies from the array, excluding
+// cookies with nil expirations.
+//
+// Note: this should only be called from inside a @synchronized(cookies_) block
+- (void)removeExpiredCookies {
+
+ // count backwards since we're deleting items from the array
+ for (NSInteger idx = (NSInteger)[cookies_ count] - 1; idx >= 0; idx--) {
+
+ NSHTTPCookie *storedCookie = [cookies_ objectAtIndex:(NSUInteger)idx];
+
+ NSDate *expiresDate = [storedCookie expiresDate];
+ if (expiresDate && [expiresDate timeIntervalSinceNow] < 0) {
+ [cookies_ removeObjectAtIndex:(NSUInteger)idx];
+ }
+ }
+}
+
+- (void)removeAllCookies {
+ @synchronized(cookies_) {
+ [cookies_ removeAllObjects];
+ }
+}
+@end
+
+//
+// GTMCachedURLResponse
+//
+
+@implementation GTMCachedURLResponse
+
+@synthesize response = response_;
+@synthesize data = data_;
+@synthesize reservationDate = reservationDate_;
+@synthesize useDate = useDate_;
+
+- (id)initWithResponse:(NSURLResponse *)response data:(NSData *)data {
+ self = [super init];
+ if (self != nil) {
+ response_ = [response retain];
+ data_ = [data retain];
+ useDate_ = [[NSDate alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [response_ release];
+ [data_ release];
+ [useDate_ release];
+ [reservationDate_ release];
+ [super dealloc];
+}
+
+- (NSString *)description {
+ NSString *reservationStr = reservationDate_ ?
+ [NSString stringWithFormat:@" resDate:%@", reservationDate_] : @"";
+
+ return [NSString stringWithFormat:@"%@ %p: {bytes:%@ useDate:%@%@}",
+ [self class], self,
+ data_ ? [NSNumber numberWithInt:(int)[data_ length]] : nil,
+ useDate_,
+ reservationStr];
+}
+
+- (NSComparisonResult)compareUseDate:(GTMCachedURLResponse *)other {
+ return [useDate_ compare:[other useDate]];
+}
+
+@end
+
+//
+// GTMURLCache
+//
+
+@implementation GTMURLCache
+
+@dynamic memoryCapacity;
+
+- (id)init {
+ return [self initWithMemoryCapacity:kGTMDefaultETaggedDataCacheMemoryCapacity];
+}
+
+- (id)initWithMemoryCapacity:(NSUInteger)totalBytes {
+ self = [super init];
+ if (self != nil) {
+ memoryCapacity_ = totalBytes;
+
+ responses_ = [[NSMutableDictionary alloc] initWithCapacity:5];
+
+ reservationInterval_ = kCachedURLReservationInterval;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [responses_ release];
+ [super dealloc];
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"%@ %p: {responses:%@}",
+ [self class], self, [responses_ allValues]];
+}
+
+// Setters/getters
+
+- (void)pruneCacheResponses {
+ // Internal routine to remove the least-recently-used responses when the
+ // cache has grown too large
+ if (memoryCapacity_ >= totalDataSize_) return;
+
+ // Sort keys by date
+ SEL sel = @selector(compareUseDate:);
+ NSArray *sortedKeys = [responses_ keysSortedByValueUsingSelector:sel];
+
+ // The least-recently-used keys are at the beginning of the sorted array;
+ // remove those (except ones still reserved) until the total data size is
+ // reduced sufficiently
+ for (NSURL *key in sortedKeys) {
+ GTMCachedURLResponse *response = [responses_ objectForKey:key];
+
+ NSDate *resDate = [response reservationDate];
+ BOOL isResponseReserved = (resDate != nil)
+ && ([resDate timeIntervalSinceNow] > -reservationInterval_);
+
+ if (!isResponseReserved) {
+ // We can remove this response from the cache
+ NSUInteger storedSize = [[response data] length];
+ totalDataSize_ -= storedSize;
+ [responses_ removeObjectForKey:key];
+ }
+
+ // If we've removed enough response data, then we're done
+ if (memoryCapacity_ >= totalDataSize_) break;
+ }
+}
+
+- (void)storeCachedResponse:(GTMCachedURLResponse *)cachedResponse
+ forRequest:(NSURLRequest *)request {
+ @synchronized(self) {
+ // Remove any previous entry for this request
+ [self removeCachedResponseForRequest:request];
+
+ // cache this one only if it's not bigger than our cache
+ NSUInteger storedSize = [[cachedResponse data] length];
+ if (storedSize < memoryCapacity_) {
+
+ NSURL *key = [request URL];
+ [responses_ setObject:cachedResponse forKey:key];
+ totalDataSize_ += storedSize;
+
+ [self pruneCacheResponses];
+ }
+ }
+}
+
+- (GTMCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
+ GTMCachedURLResponse *response;
+
+ @synchronized(self) {
+ NSURL *key = [request URL];
+ response = [[[responses_ objectForKey:key] retain] autorelease];
+
+ // Touch the date to indicate this was recently retrieved
+ [response setUseDate:[NSDate date]];
+ }
+ return response;
+}
+
+- (void)removeCachedResponseForRequest:(NSURLRequest *)request {
+ @synchronized(self) {
+ NSURL *key = [request URL];
+ totalDataSize_ -= [[[responses_ objectForKey:key] data] length];
+ [responses_ removeObjectForKey:key];
+ }
+}
+
+- (void)removeAllCachedResponses {
+ @synchronized(self) {
+ [responses_ removeAllObjects];
+ totalDataSize_ = 0;
+ }
+}
+
+- (NSUInteger)memoryCapacity {
+ return memoryCapacity_;
+}
+
+- (void)setMemoryCapacity:(NSUInteger)totalBytes {
+ @synchronized(self) {
+ BOOL didShrink = (totalBytes < memoryCapacity_);
+ memoryCapacity_ = totalBytes;
+
+ if (didShrink) {
+ [self pruneCacheResponses];
+ }
+ }
+}
+
+// Methods for unit testing.
+- (void)setReservationInterval:(NSTimeInterval)secs {
+ reservationInterval_ = secs;
+}
+
+- (NSDictionary *)responses {
+ return responses_;
+}
+
+- (NSUInteger)totalDataSize {
+ return totalDataSize_;
+}
+
+@end
+
+//
+// GTMHTTPFetchHistory
+//
+
+@interface GTMHTTPFetchHistory ()
+- (NSString *)cachedETagForRequest:(NSURLRequest *)request;
+- (void)removeCachedDataForRequest:(NSURLRequest *)request;
+@end
+
+@implementation GTMHTTPFetchHistory
+
+@synthesize cookieStorage = cookieStorage_;
+
+@dynamic shouldRememberETags;
+@dynamic shouldCacheETaggedData;
+@dynamic memoryCapacity;
+
+- (id)init {
+ return [self initWithMemoryCapacity:kGTMDefaultETaggedDataCacheMemoryCapacity
+ shouldCacheETaggedData:NO];
+}
+
+- (id)initWithMemoryCapacity:(NSUInteger)totalBytes
+ shouldCacheETaggedData:(BOOL)shouldCacheETaggedData {
+ self = [super init];
+ if (self != nil) {
+ etaggedDataCache_ = [[GTMURLCache alloc] initWithMemoryCapacity:totalBytes];
+ shouldRememberETags_ = shouldCacheETaggedData;
+ shouldCacheETaggedData_ = shouldCacheETaggedData;
+ cookieStorage_ = [[GTMCookieStorage alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [etaggedDataCache_ release];
+ [cookieStorage_ release];
+ [super dealloc];
+}
+
+- (void)updateRequest:(NSMutableURLRequest *)request isHTTPGet:(BOOL)isHTTPGet {
+ @synchronized(self) {
+ if ([self shouldRememberETags]) {
+ // If this URL is in the history, and no ETag has been set, then
+ // set the ETag header field
+
+ // If we have a history, we're tracking across fetches, so we don't
+ // want to pull results from any other cache
+ [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
+
+ if (isHTTPGet) {
+ // We'll only add an ETag if there's no ETag specified in the user's
+ // request
+ NSString *specifiedETag = [request valueForHTTPHeaderField:kGTMIfNoneMatchHeader];
+ if (specifiedETag == nil) {
+ // No ETag: extract the previous ETag for this request from the
+ // fetch history, and add it to the request
+ NSString *cachedETag = [self cachedETagForRequest:request];
+
+ if (cachedETag != nil) {
+ [request addValue:cachedETag forHTTPHeaderField:kGTMIfNoneMatchHeader];
+ }
+ } else {
+ // Has an ETag: remove any stored response in the fetch history
+ // for this request, as the If-None-Match header could lead to
+ // a 304 Not Modified, and we want that error delivered to the
+ // user since they explicitly specified the ETag
+ [self removeCachedDataForRequest:request];
+ }
+ }
+ }
+ }
+}
+
+- (void)updateFetchHistoryWithRequest:(NSURLRequest *)request
+ response:(NSURLResponse *)response
+ downloadedData:(NSData *)downloadedData {
+ @synchronized(self) {
+ if (![self shouldRememberETags]) return;
+
+ if (![response respondsToSelector:@selector(allHeaderFields)]) return;
+
+ NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
+
+ if (statusCode != kGTMHTTPFetcherStatusNotModified) {
+ // Save this ETag string for successful results (<300)
+ // If there's no last modified string, clear the dictionary
+ // entry for this URL. Also cache or delete the data, if appropriate
+ // (when etaggedDataCache is non-nil.)
+ NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
+ NSString* etag = [headers objectForKey:kGTMETagHeader];
+
+ if (etag != nil && statusCode < 300) {
+
+ // we want to cache responses for the headers, even if the client
+ // doesn't want the response body data caches
+ NSData *dataToStore = shouldCacheETaggedData_ ? downloadedData : nil;
+
+ GTMCachedURLResponse *cachedResponse;
+ cachedResponse = [[[GTMCachedURLResponse alloc] initWithResponse:response
+ data:dataToStore] autorelease];
+ [etaggedDataCache_ storeCachedResponse:cachedResponse
+ forRequest:request];
+ } else {
+ [etaggedDataCache_ removeCachedResponseForRequest:request];
+ }
+ }
+ }
+}
+
+- (NSString *)cachedETagForRequest:(NSURLRequest *)request {
+ // Internal routine.
+ GTMCachedURLResponse *cachedResponse;
+ cachedResponse = [etaggedDataCache_ cachedResponseForRequest:request];
+
+ NSURLResponse *response = [cachedResponse response];
+ NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
+ NSString *cachedETag = [headers objectForKey:kGTMETagHeader];
+ if (cachedETag) {
+ // Since the request having an ETag implies this request is about
+ // to be fetched again, reserve the cached response to ensure that
+ // that it will be around at least until the fetch completes.
+ //
+ // When the fetch completes, either the cached response will be replaced
+ // with a new response, or the cachedDataForRequest: method below will
+ // clear the reservation.
+ [cachedResponse setReservationDate:[NSDate date]];
+ }
+ return cachedETag;
+}
+
+- (NSData *)cachedDataForRequest:(NSURLRequest *)request {
+ @synchronized(self) {
+ GTMCachedURLResponse *cachedResponse;
+ cachedResponse = [etaggedDataCache_ cachedResponseForRequest:request];
+
+ NSData *cachedData = [cachedResponse data];
+
+ // Since the data for this cached request is being obtained from the cache,
+ // we can clear the reservation as the fetch has completed.
+ [cachedResponse setReservationDate:nil];
+
+ return cachedData;
+ }
+}
+
+- (void)removeCachedDataForRequest:(NSURLRequest *)request {
+ @synchronized(self) {
+ [etaggedDataCache_ removeCachedResponseForRequest:request];
+ }
+}
+
+- (void)clearETaggedDataCache {
+ @synchronized(self) {
+ [etaggedDataCache_ removeAllCachedResponses];
+ }
+}
+
+- (void)clearHistory {
+ @synchronized(self) {
+ [self clearETaggedDataCache];
+ [cookieStorage_ removeAllCookies];
+ }
+}
+
+- (void)removeAllCookies {
+ @synchronized(self) {
+ [cookieStorage_ removeAllCookies];
+ }
+}
+
+- (BOOL)shouldRememberETags {
+ return shouldRememberETags_;
+}
+
+- (void)setShouldRememberETags:(BOOL)flag {
+ BOOL wasRemembering = shouldRememberETags_;
+ shouldRememberETags_ = flag;
+
+ if (wasRemembering && !flag) {
+ // Free up the cache memory
+ [self clearETaggedDataCache];
+ }
+}
+
+- (BOOL)shouldCacheETaggedData {
+ return shouldCacheETaggedData_;
+}
+
+- (void)setShouldCacheETaggedData:(BOOL)flag {
+ BOOL wasCaching = shouldCacheETaggedData_;
+ shouldCacheETaggedData_ = flag;
+
+ if (flag) {
+ self.shouldRememberETags = YES;
+ }
+
+ if (wasCaching && !flag) {
+ // users expect turning off caching to free up the cache memory
+ [self clearETaggedDataCache];
+ }
+}
+
+- (NSUInteger)memoryCapacity {
+ return [etaggedDataCache_ memoryCapacity];
+}
+
+- (void)setMemoryCapacity:(NSUInteger)totalBytes {
+ [etaggedDataCache_ setMemoryCapacity:totalBytes];
+}
+
+@end
diff --git a/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetcher.h b/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetcher.h
new file mode 100755
index 00000000..cd9a9ff6
--- /dev/null
+++ b/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetcher.h
@@ -0,0 +1,766 @@
+/* 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>
+//
+//
+// Threading and queue support:
+//
+// Callbacks require either that the thread used to start the fetcher have a run
+// loop spinning (typically the main thread), or that an NSOperationQueue be
+// provided upon which the delegate callbacks will be called. Starting with
+// iOS 6 and Mac OS X 10.7, clients may simply create an operation queue for
+// callbacks on a background thread:
+//
+// NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
+// [queue setMaxConcurrentOperationCount:1];
+// fetcher.delegateQueue = queue;
+//
+// or specify the main queue for callbacks on the main thread:
+//
+// fetcher.delegateQueue = [NSOperationQueue mainQueue];
+//
+// The client may also re-dispatch from the callbacks and notifications to
+// a known dispatch queue:
+//
+// [myFetcher beginFetchWithCompletionHandler:^(NSData *retrievedData, NSError *error) {
+// if (error == nil) {
+// dispatch_async(myDispatchQueue, ^{
+// ...
+// });
+// }
+// }];
+//
+//
+//
+// 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
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+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);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+@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
+
+@property (retain) NSOperationQueue *delegateQueue;
+
+- (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;
+
+- (void)stopAuthorizationForRequest:(NSURLRequest *)request;
+
+- (BOOL)isAuthorizingRequest:(NSURLRequest *)request;
+
+- (BOOL)isAuthorizedRequest:(NSURLRequest *)request;
+
+@property (retain, readonly) NSString *userEmail;
+
+@optional
+
+// Indicate if authorization may be attempted. Even if this succeeds,
+// authorization may fail if the user's permissions have been revoked.
+@property (readonly) BOOL canAuthorize;
+
+// For development only, allow authorization of non-SSL requests, allowing
+// transmission of the bearer token unencrypted.
+@property (assign) BOOL shouldAuthorizeAllRequests;
+
+#if NS_BLOCKS_AVAILABLE
+- (void)authorizeRequest:(NSMutableURLRequest *)request
+ completionHandler:(void (^)(NSError *error))handler;
+#endif
+
+@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
+ NSOperationQueue *delegateQueue_; // optional; available iOS 6/10.7 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_;
+#if !STRIP_GTM_FETCH_LOGGING
+ NSURL *redirectedFromURL_;
+ NSString *logRequestBody_;
+ NSString *logResponseBody_;
+ BOOL shouldDeferResponseBodyLogging_;
+#endif
+}
+
+// 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 when no operation
+// queue is provided.
+@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 path or 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 path or 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;
+//
+// If the application has specified a downloadPath or downloadFileHandle
+// for the fetcher, the data parameter passed to the callback will be nil.
+
+- (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:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2);
+
+// Log of request and response, if logging is enabled
+@property (copy) NSString *log;
+
+// Callbacks can be invoked on an operation queue rather than via the run loop,
+// starting on 10.7 and iOS 6. If a delegate queue is supplied. the run loop
+// modes are ignored.
+@property (retain) NSOperationQueue *delegateQueue;
+
+// 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
diff --git a/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetcher.m b/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetcher.m
new file mode 100755
index 00000000..ebbdd0de
--- /dev/null
+++ b/example/common/gtm-oauth2/HTTPFetcher/GTMHTTPFetcher.m
@@ -0,0 +1,1956 @@
+/* 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.m
+//
+
+#define GTMHTTPFETCHER_DEFINE_GLOBALS 1
+
+#import "GTMHTTPFetcher.h"
+
+#if GTM_BACKGROUND_FETCHING
+#import <UIKit/UIKit.h>
+#endif
+
+static id <GTMCookieStorageProtocol> gGTMFetcherStaticCookieStorage = nil;
+static Class gGTMFetcherConnectionClass = nil;
+
+// The default max retry interview is 10 minutes for uploads (POST/PUT/PATCH),
+// 1 minute for downloads.
+static const NSTimeInterval kUnsetMaxRetryInterval = -1;
+static const NSTimeInterval kDefaultMaxDownloadRetryInterval = 60.0;
+static const NSTimeInterval kDefaultMaxUploadRetryInterval = 60.0 * 10.;
+
+// delegateQueue callback parameters
+static NSString *const kCallbackData = @"data";
+static NSString *const kCallbackError = @"error";
+
+//
+// GTMHTTPFetcher
+//
+
+@interface GTMHTTPFetcher ()
+
+@property (copy) NSString *temporaryDownloadPath;
+@property (retain) id <GTMCookieStorageProtocol> cookieStorage;
+@property (readwrite, retain) NSData *downloadedData;
+#if NS_BLOCKS_AVAILABLE
+@property (copy) void (^completionBlock)(NSData *, NSError *);
+#endif
+
+- (BOOL)beginFetchMayDelay:(BOOL)mayDelay
+ mayAuthorize:(BOOL)mayAuthorize;
+- (void)failToBeginFetchWithError:(NSError *)error;
+- (void)failToBeginFetchDeferWithError:(NSError *)error;
+
+#if GTM_BACKGROUND_FETCHING
+- (void)endBackgroundTask;
+- (void)backgroundFetchExpired;
+#endif
+
+- (BOOL)authorizeRequest;
+- (void)authorizer:(id <GTMFetcherAuthorizationProtocol>)auth
+ request:(NSMutableURLRequest *)request
+ finishedWithError:(NSError *)error;
+
+- (NSString *)createTempDownloadFilePathForPath:(NSString *)targetPath;
+- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks;
+- (BOOL)shouldReleaseCallbacksUponCompletion;
+
+- (void)addCookiesToRequest:(NSMutableURLRequest *)request;
+- (void)handleCookiesForResponse:(NSURLResponse *)response;
+
+- (void)invokeFetchCallbacksWithData:(NSData *)data
+ error:(NSError *)error;
+- (void)invokeFetchCallback:(SEL)sel
+ target:(id)target
+ data:(NSData *)data
+ error:(NSError *)error;
+- (void)invokeFetchCallbacksOnDelegateQueueWithData:(NSData *)data
+ error:(NSError *)error;
+- (void)releaseCallbacks;
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
+
+- (BOOL)shouldRetryNowForStatus:(NSInteger)status error:(NSError *)error;
+- (void)destroyRetryTimer;
+- (void)beginRetryTimer;
+- (void)primeRetryTimerWithNewTimeInterval:(NSTimeInterval)secs;
+- (void)sendStopNotificationIfNeeded;
+- (void)retryFetch;
+- (void)retryTimerFired:(NSTimer *)timer;
+@end
+
+@interface GTMHTTPFetcher (GTMHTTPFetcherLoggingInternal)
+- (void)setupStreamLogging;
+- (void)logFetchWithError:(NSError *)error;
+@end
+
+@implementation GTMHTTPFetcher
+
++ (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request {
+ return [[[[self class] alloc] initWithRequest:request] autorelease];
+}
+
++ (GTMHTTPFetcher *)fetcherWithURL:(NSURL *)requestURL {
+ return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]];
+}
+
++ (GTMHTTPFetcher *)fetcherWithURLString:(NSString *)requestURLString {
+ return [self fetcherWithURL:[NSURL URLWithString:requestURLString]];
+}
+
++ (void)initialize {
+ // initialize is guaranteed by the runtime to be called in a
+ // thread-safe manner
+ if (!gGTMFetcherStaticCookieStorage) {
+ Class cookieStorageClass = NSClassFromString(@"GTMCookieStorage");
+ if (cookieStorageClass) {
+ gGTMFetcherStaticCookieStorage = [[cookieStorageClass alloc] init];
+ }
+ }
+}
+
+- (id)init {
+ return [self initWithRequest:nil];
+}
+
+- (id)initWithRequest:(NSURLRequest *)request {
+ self = [super init];
+ if (self) {
+ request_ = [request mutableCopy];
+
+ if (gGTMFetcherStaticCookieStorage != nil) {
+ // The user has compiled with the cookie storage class available;
+ // default to static cookie storage, so our cookies are independent
+ // of the cookies of other apps.
+ [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodStatic];
+ } else {
+ // Default to system default cookie storage
+ [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodSystemDefault];
+ }
+#if !STRIP_GTM_FETCH_LOGGING
+ // Encourage developers to set the comment property or use
+ // setCommentWithFormat: by providing a default string.
+ comment_ = @"(No fetcher comment set)";
+#endif
+ }
+ return self;
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+ // disallow use of fetchers in a copy property
+ [self doesNotRecognizeSelector:_cmd];
+ return nil;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"%@ %p (%@)",
+ [self class], self, [self.mutableRequest URL]];
+}
+
+#if !GTM_IPHONE
+- (void)finalize {
+ [self stopFetchReleasingCallbacks:YES]; // releases connection_, destroys timers
+ [super finalize];
+}
+#endif
+
+- (void)dealloc {
+#if DEBUG
+ NSAssert(!isStopNotificationNeeded_,
+ @"unbalanced fetcher notification for %@", [request_ URL]);
+#endif
+
+ // Note: if a connection or a retry timer was pending, then this instance
+ // would be retained by those so it wouldn't be getting dealloc'd,
+ // hence we don't need to stopFetch here
+ [request_ release];
+ [connection_ release];
+ [downloadedData_ release];
+ [downloadPath_ release];
+ [temporaryDownloadPath_ release];
+ [downloadFileHandle_ release];
+ [credential_ release];
+ [proxyCredential_ release];
+ [postData_ release];
+ [postStream_ release];
+ [loggedStreamData_ release];
+ [response_ release];
+#if NS_BLOCKS_AVAILABLE
+ [completionBlock_ release];
+ [receivedDataBlock_ release];
+ [sentDataBlock_ release];
+ [retryBlock_ release];
+#endif
+ [userData_ release];
+ [properties_ release];
+ [delegateQueue_ release];
+ [runLoopModes_ release];
+ [fetchHistory_ release];
+ [cookieStorage_ release];
+ [authorizer_ release];
+ [service_ release];
+ [serviceHost_ release];
+ [thread_ release];
+ [retryTimer_ release];
+ [comment_ release];
+ [log_ release];
+#if !STRIP_GTM_FETCH_LOGGING
+ [redirectedFromURL_ release];
+ [logRequestBody_ release];
+ [logResponseBody_ release];
+#endif
+
+ [super dealloc];
+}
+
+#pragma mark -
+
+// Begin fetching the URL (or begin a retry fetch). The delegate is retained
+// for the duration of the fetch connection.
+
+- (BOOL)beginFetchWithDelegate:(id)delegate
+ didFinishSelector:(SEL)finishedSelector {
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector, @encode(GTMHTTPFetcher *), @encode(NSData *), @encode(NSError *), 0);
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, receivedDataSel_, @encode(GTMHTTPFetcher *), @encode(NSData *), 0);
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, retrySel_, @encode(GTMHTTPFetcher *), @encode(BOOL), @encode(NSError *), 0);
+
+ // We'll retain the delegate only during the outstanding connection (similar
+ // to what Cocoa does with performSelectorOnMainThread:) and during
+ // authorization or delays, since the app would crash
+ // if the delegate was released before the fetch calls back
+ [self setDelegate:delegate];
+ finishedSel_ = finishedSelector;
+
+ return [self beginFetchMayDelay:YES
+ mayAuthorize:YES];
+}
+
+- (BOOL)beginFetchMayDelay:(BOOL)mayDelay
+ mayAuthorize:(BOOL)mayAuthorize {
+ // This is the internal entry point for re-starting fetches
+ NSError *error = nil;
+
+ if (connection_ != nil) {
+ NSAssert1(connection_ != nil, @"fetch object %@ being reused; this should never happen", self);
+ goto CannotBeginFetch;
+ }
+
+ if (request_ == nil || [request_ URL] == nil) {
+ NSAssert(request_ != nil, @"beginFetchWithDelegate requires a request with a URL");
+ goto CannotBeginFetch;
+ }
+
+ self.downloadedData = nil;
+ downloadedLength_ = 0;
+
+ if (mayDelay && service_) {
+ BOOL shouldFetchNow = [service_ fetcherShouldBeginFetching:self];
+ if (!shouldFetchNow) {
+ // the fetch is deferred, but will happen later
+ return YES;
+ }
+ }
+
+ NSString *effectiveHTTPMethod = [request_ valueForHTTPHeaderField:@"X-HTTP-Method-Override"];
+ if (effectiveHTTPMethod == nil) {
+ effectiveHTTPMethod = [request_ HTTPMethod];
+ }
+ BOOL isEffectiveHTTPGet = (effectiveHTTPMethod == nil
+ || [effectiveHTTPMethod isEqual:@"GET"]);
+
+ if (postData_ || postStream_) {
+ if (isEffectiveHTTPGet) {
+ [request_ setHTTPMethod:@"POST"];
+ isEffectiveHTTPGet = NO;
+ }
+
+ if (postData_) {
+ [request_ setHTTPBody:postData_];
+ } else {
+ if ([self respondsToSelector:@selector(setupStreamLogging)]) {
+ [self performSelector:@selector(setupStreamLogging)];
+ }
+
+ [request_ setHTTPBodyStream:postStream_];
+ }
+ }
+
+ // We authorize after setting up the http method and body in the request
+ // because OAuth 1 may need to sign the request body
+ if (mayAuthorize && authorizer_) {
+ BOOL isAuthorized = [authorizer_ isAuthorizedRequest:request_];
+ if (!isAuthorized) {
+ // authorization needed
+ return [self authorizeRequest];
+ }
+ }
+
+ [fetchHistory_ updateRequest:request_ isHTTPGet:isEffectiveHTTPGet];
+
+ // set the default upload or download retry interval, if necessary
+ if (isRetryEnabled_
+ && maxRetryInterval_ <= kUnsetMaxRetryInterval) {
+ if (isEffectiveHTTPGet || [effectiveHTTPMethod isEqual:@"HEAD"]) {
+ [self setMaxRetryInterval:kDefaultMaxDownloadRetryInterval];
+ } else {
+ [self setMaxRetryInterval:kDefaultMaxUploadRetryInterval];
+ }
+ }
+
+ [self addCookiesToRequest:request_];
+
+ if (downloadPath_ != nil) {
+ // downloading to a path, so create a temporary file and a file handle for
+ // downloading
+ NSString *tempPath = [self createTempDownloadFilePathForPath:downloadPath_];
+
+ BOOL didCreate = [[NSData data] writeToFile:tempPath
+ options:0
+ error:&error];
+ if (!didCreate) goto CannotBeginFetch;
+
+ [self setTemporaryDownloadPath:tempPath];
+
+ NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:tempPath];
+ if (fh == nil) goto CannotBeginFetch;
+
+ [self setDownloadFileHandle:fh];
+ }
+
+ // finally, start the connection
+
+ Class connectionClass = [[self class] connectionClass];
+
+ NSOperationQueue *delegateQueue = delegateQueue_;
+ if (delegateQueue &&
+ ![connectionClass instancesRespondToSelector:@selector(setDelegateQueue:)]) {
+ // NSURLConnection has no setDelegateQueue: on iOS 4 and Mac OS X 10.5.
+ delegateQueue = nil;
+ self.delegateQueue = nil;
+ }
+
+#if DEBUG && TARGET_OS_IPHONE
+ BOOL isPreIOS6 = (NSFoundationVersionNumber <= 890.1);
+ if (isPreIOS6 && delegateQueue) {
+ NSLog(@"GTMHTTPFetcher delegateQueue not safe in iOS 5");
+ }
+#endif
+
+ if (downloadFileHandle_ != nil) {
+ // Downloading to a file, so downloadedData_ remains nil.
+ } else {
+ self.downloadedData = [NSMutableData data];
+ }
+
+ hasConnectionEnded_ = NO;
+ if ([runLoopModes_ count] == 0 && delegateQueue == nil) {
+ // No custom callback modes or queue were specified, so start the connection
+ // on the current run loop in the current mode
+ connection_ = [[connectionClass connectionWithRequest:request_
+ delegate:self] retain];
+ } else {
+ // Specify callbacks be on an operation queue or on the current run loop
+ // in the specified modes
+ connection_ = [[connectionClass alloc] initWithRequest:request_
+ delegate:self
+ startImmediately:NO];
+ if (delegateQueue) {
+ [connection_ performSelector:@selector(setDelegateQueue:)
+ withObject:delegateQueue];
+ } else if (runLoopModes_) {
+ NSRunLoop *rl = [NSRunLoop currentRunLoop];
+ for (NSString *mode in runLoopModes_) {
+ [connection_ scheduleInRunLoop:rl forMode:mode];
+ }
+ }
+ [connection_ start];
+ }
+
+ if (!connection_) {
+ NSAssert(connection_ != nil, @"beginFetchWithDelegate could not create a connection");
+ self.downloadedData = nil;
+ goto CannotBeginFetch;
+ }
+
+#if GTM_BACKGROUND_FETCHING
+ backgroundTaskIdentifer_ = 0; // UIBackgroundTaskInvalid is 0 on iOS 4
+ if (shouldFetchInBackground_) {
+ // For iOS 3 compatibility, ensure that UIApp supports backgrounding
+ UIApplication *app = [UIApplication sharedApplication];
+ if ([app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) {
+ // Tell UIApplication that we want to continue even when the app is in the
+ // background.
+ NSThread *thread = delegateQueue_ ? nil : [NSThread currentThread];
+ backgroundTaskIdentifer_ = [app beginBackgroundTaskWithExpirationHandler:^{
+ // Background task expiration callback - this block is always invoked by
+ // UIApplication on the main thread.
+ if (thread) {
+ // Run the user's callbacks on the thread used to start the
+ // fetch.
+ [self performSelector:@selector(backgroundFetchExpired)
+ onThread:thread
+ withObject:nil
+ waitUntilDone:YES];
+ } else {
+ // backgroundFetchExpired invokes callbacks on the provided delegate
+ // queue.
+ [self backgroundFetchExpired];
+ }
+ }];
+ }
+ }
+#endif
+
+ // Once connection_ is non-nil we can send the start notification
+ isStopNotificationNeeded_ = YES;
+ NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
+ [defaultNC postNotificationName:kGTMHTTPFetcherStartedNotification
+ object:self];
+ return YES;
+
+CannotBeginFetch:
+ [self failToBeginFetchDeferWithError:error];
+ return NO;
+}
+
+- (void)failToBeginFetchDeferWithError:(NSError *)error {
+ if (delegateQueue_) {
+ // Deferring will happen by the callback being invoked on the specified
+ // queue.
+ [self failToBeginFetchWithError:error];
+ } else {
+ // No delegate queue has been specified, so put the callback
+ // on an appropriate run loop.
+ NSArray *modes = (runLoopModes_ ? runLoopModes_ :
+ [NSArray arrayWithObject:NSRunLoopCommonModes]);
+ [self performSelector:@selector(failToBeginFetchWithError:)
+ onThread:[NSThread currentThread]
+ withObject:error
+ waitUntilDone:NO
+ modes:modes];
+ }
+}
+
+- (void)failToBeginFetchWithError:(NSError *)error {
+ if (error == nil) {
+ error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain
+ code:kGTMHTTPFetcherErrorDownloadFailed
+ userInfo:nil];
+ }
+
+ [[self retain] autorelease]; // In case the callback releases us
+
+ [self invokeFetchCallbacksOnDelegateQueueWithData:nil
+ error:error];
+
+ [self releaseCallbacks];
+
+ [service_ fetcherDidStop:self];
+
+ self.authorizer = nil;
+
+ if (temporaryDownloadPath_) {
+ [[NSFileManager defaultManager] removeItemAtPath:temporaryDownloadPath_
+ error:NULL];
+ self.temporaryDownloadPath = nil;
+ }
+}
+
+#if GTM_BACKGROUND_FETCHING
+- (void)backgroundFetchExpired {
+ // On background expiration, we stop the fetch and invoke the callbacks
+ NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain
+ code:kGTMHTTPFetcherErrorBackgroundExpiration
+ userInfo:nil];
+ [self invokeFetchCallbacksOnDelegateQueueWithData:nil
+ error:error];
+ @synchronized(self) {
+ // Stopping the fetch here will indirectly call endBackgroundTask
+ [self stopFetchReleasingCallbacks:NO];
+
+ [self releaseCallbacks];
+ self.authorizer = nil;
+ }
+}
+
+- (void)endBackgroundTask {
+ @synchronized(self) {
+ // Whenever the connection stops or background execution expires,
+ // we need to tell UIApplication we're done
+ if (backgroundTaskIdentifer_) {
+ // If backgroundTaskIdentifer_ is non-zero, we know we're on iOS 4
+ UIApplication *app = [UIApplication sharedApplication];
+ [app endBackgroundTask:backgroundTaskIdentifer_];
+
+ backgroundTaskIdentifer_ = 0;
+ }
+ }
+}
+#endif // GTM_BACKGROUND_FETCHING
+
+- (BOOL)authorizeRequest {
+ id authorizer = self.authorizer;
+ SEL asyncAuthSel = @selector(authorizeRequest:delegate:didFinishSelector:);
+ if ([authorizer respondsToSelector:asyncAuthSel]) {
+ SEL callbackSel = @selector(authorizer:request:finishedWithError:);
+ [authorizer authorizeRequest:request_
+ delegate:self
+ didFinishSelector:callbackSel];
+ return YES;
+ } else {
+ NSAssert(authorizer == nil, @"invalid authorizer for fetch");
+
+ // No authorizing possible, and authorizing happens only after any delay;
+ // just begin fetching
+ return [self beginFetchMayDelay:NO
+ mayAuthorize:NO];
+ }
+}
+
+- (void)authorizer:(id <GTMFetcherAuthorizationProtocol>)auth
+ request:(NSMutableURLRequest *)request
+ finishedWithError:(NSError *)error {
+ if (error != nil) {
+ // We can't fetch without authorization
+ [self failToBeginFetchDeferWithError:error];
+ } else {
+ [self beginFetchMayDelay:NO
+ mayAuthorize:NO];
+ }
+}
+
+#if NS_BLOCKS_AVAILABLE
+- (BOOL)beginFetchWithCompletionHandler:(void (^)(NSData *data, NSError *error))handler {
+ self.completionBlock = handler;
+
+ // The user may have called setDelegate: earlier if they want to use other
+ // delegate-style callbacks during the fetch; otherwise, the delegate is nil,
+ // which is fine.
+ return [self beginFetchWithDelegate:[self delegate]
+ didFinishSelector:nil];
+}
+#endif
+
+- (NSString *)createTempDownloadFilePathForPath:(NSString *)targetPath {
+ NSString *tempDir = nil;
+
+#if (!TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060))
+ // Find an appropriate directory for the download, ideally on the same disk
+ // as the final target location so the temporary file won't have to be moved
+ // to a different disk.
+ //
+ // Available in SDKs for 10.6 and iOS 4
+ //
+ // Oct 2011: We previously also used URLForDirectory for
+ // (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 40000))
+ // but that is returning a non-temporary directory for iOS, unfortunately
+
+ SEL sel = @selector(URLForDirectory:inDomain:appropriateForURL:create:error:);
+ if ([NSFileManager instancesRespondToSelector:sel]) {
+ NSError *error = nil;
+ NSURL *targetURL = [NSURL fileURLWithPath:targetPath];
+ NSFileManager *fileMgr = [NSFileManager defaultManager];
+
+ NSURL *tempDirURL = [fileMgr URLForDirectory:NSItemReplacementDirectory
+ inDomain:NSUserDomainMask
+ appropriateForURL:targetURL
+ create:YES
+ error:&error];
+ tempDir = [tempDirURL path];
+ }
+#endif
+
+ if (tempDir == nil) {
+ tempDir = NSTemporaryDirectory();
+ }
+
+ static unsigned int counter = 0;
+ NSString *name = [NSString stringWithFormat:@"gtmhttpfetcher_%u_%u",
+ ++counter, (unsigned int) arc4random()];
+ NSString *result = [tempDir stringByAppendingPathComponent:name];
+ return result;
+}
+
+- (void)addCookiesToRequest:(NSMutableURLRequest *)request {
+ // Get cookies for this URL from our storage array, if
+ // we have a storage array
+ if (cookieStorageMethod_ != kGTMHTTPFetcherCookieStorageMethodSystemDefault
+ && cookieStorageMethod_ != kGTMHTTPFetcherCookieStorageMethodNone) {
+
+ NSArray *cookies = [cookieStorage_ cookiesForURL:[request URL]];
+ if ([cookies count] > 0) {
+
+ NSDictionary *headerFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
+ NSString *cookieHeader = [headerFields objectForKey:@"Cookie"]; // key used in header dictionary
+ if (cookieHeader) {
+ [request addValue:cookieHeader forHTTPHeaderField:@"Cookie"]; // header name
+ }
+ }
+ }
+}
+
+// Returns YES if this is in the process of fetching a URL, or waiting to
+// retry, or waiting for authorization, or waiting to be issued by the
+// service object
+- (BOOL)isFetching {
+ if (connection_ != nil || retryTimer_ != nil) return YES;
+
+ BOOL isAuthorizing = [authorizer_ isAuthorizingRequest:request_];
+ if (isAuthorizing) return YES;
+
+ BOOL isDelayed = [service_ isDelayingFetcher:self];
+ return isDelayed;
+}
+
+// Returns the status code set in connection:didReceiveResponse:
+- (NSInteger)statusCode {
+
+ NSInteger statusCode;
+
+ if (response_ != nil
+ && [response_ respondsToSelector:@selector(statusCode)]) {
+
+ statusCode = [(NSHTTPURLResponse *)response_ statusCode];
+ } else {
+ // Default to zero, in hopes of hinting "Unknown" (we can't be
+ // sure that things are OK enough to use 200).
+ statusCode = 0;
+ }
+ return statusCode;
+}
+
+- (NSDictionary *)responseHeaders {
+ if (response_ != nil
+ && [response_ respondsToSelector:@selector(allHeaderFields)]) {
+
+ NSDictionary *headers = [(NSHTTPURLResponse *)response_ allHeaderFields];
+ return headers;
+ }
+ return nil;
+}
+
+- (void)releaseCallbacks {
+ [delegate_ autorelease];
+ delegate_ = nil;
+
+ [delegateQueue_ autorelease];
+ delegateQueue_ = nil;
+
+#if NS_BLOCKS_AVAILABLE
+ self.completionBlock = nil;
+ self.sentDataBlock = nil;
+ self.receivedDataBlock = nil;
+ self.retryBlock = nil;
+#endif
+}
+
+// Cancel the fetch of the URL that's currently in progress.
+- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks {
+ id <GTMHTTPFetcherServiceProtocol> service;
+
+ // if the connection or the retry timer is all that's retaining the fetcher,
+ // we want to be sure this instance survives stopping at least long enough for
+ // the stack to unwind
+ [[self retain] autorelease];
+
+ [self destroyRetryTimer];
+
+ @synchronized(self) {
+ service = [[service_ retain] autorelease];
+
+ if (connection_) {
+ // in case cancelling the connection calls this recursively, we want
+ // to ensure that we'll only release the connection and delegate once,
+ // so first set connection_ to nil
+ NSURLConnection* oldConnection = connection_;
+ connection_ = nil;
+
+ if (!hasConnectionEnded_) {
+ [oldConnection cancel];
+ }
+
+ // this may be called in a callback from the connection, so use autorelease
+ [oldConnection autorelease];
+ }
+ } // @synchronized(self)
+
+ // send the stopped notification
+ [self sendStopNotificationIfNeeded];
+
+ @synchronized(self) {
+ [authorizer_ stopAuthorizationForRequest:request_];
+
+ if (shouldReleaseCallbacks) {
+ [self releaseCallbacks];
+
+ self.authorizer = nil;
+ }
+
+ if (temporaryDownloadPath_) {
+ [[NSFileManager defaultManager] removeItemAtPath:temporaryDownloadPath_
+ error:NULL];
+ self.temporaryDownloadPath = nil;
+ }
+ } // @synchronized(self)
+
+ [service fetcherDidStop:self];
+
+#if GTM_BACKGROUND_FETCHING
+ [self endBackgroundTask];
+#endif
+}
+
+// External stop method
+- (void)stopFetching {
+ [self stopFetchReleasingCallbacks:YES];
+}
+
+- (void)sendStopNotificationIfNeeded {
+ BOOL sendNow = NO;
+ @synchronized(self) {
+ if (isStopNotificationNeeded_) {
+ isStopNotificationNeeded_ = NO;
+ sendNow = YES;
+ }
+ }
+
+ if (sendNow) {
+ NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
+ [defaultNC postNotificationName:kGTMHTTPFetcherStoppedNotification
+ object:self];
+ }
+}
+
+- (void)retryFetch {
+ [self stopFetchReleasingCallbacks:NO];
+
+ [self beginFetchWithDelegate:delegate_
+ didFinishSelector:finishedSel_];
+}
+
+- (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds {
+ NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
+
+ // Loop until the callbacks have been called and released, and until
+ // the connection is no longer pending, or until the timeout has expired
+ BOOL isMainThread = [NSThread isMainThread];
+
+ while ((!hasConnectionEnded_
+#if NS_BLOCKS_AVAILABLE
+ || completionBlock_ != nil
+#endif
+ || delegate_ != nil)
+ && [giveUpDate timeIntervalSinceNow] > 0) {
+
+ // Run the current run loop 1/1000 of a second to give the networking
+ // code a chance to work
+ if (isMainThread || delegateQueue_ == nil) {
+ NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
+ [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
+ } else {
+ [NSThread sleepForTimeInterval:0.001];
+ }
+ }
+}
+
+#pragma mark NSURLConnection Delegate Methods
+
+//
+// NSURLConnection Delegate Methods
+//
+
+// This method just says "follow all redirects", which _should_ be the default behavior,
+// According to file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Conceptual/URLLoadingSystem
+// but the redirects were not being followed until I added this method. May be
+// a bug in the NSURLConnection code, or the documentation.
+//
+// In OS X 10.4.8 and earlier, the redirect request doesn't
+// get the original's headers and body. This causes POSTs to fail.
+// So we construct a new request, a copy of the original, with overrides from the
+// redirect.
+//
+// Docs say that if redirectResponse is nil, just return the redirectRequest.
+
+- (NSURLRequest *)connection:(NSURLConnection *)connection
+ willSendRequest:(NSURLRequest *)redirectRequest
+ redirectResponse:(NSURLResponse *)redirectResponse {
+ @synchronized(self) {
+ if (redirectRequest && redirectResponse) {
+ // save cookies from the response
+ [self handleCookiesForResponse:redirectResponse];
+
+ NSMutableURLRequest *newRequest = [[request_ mutableCopy] autorelease];
+ // copy the URL
+ NSURL *redirectURL = [redirectRequest URL];
+ NSURL *url = [newRequest URL];
+
+ // disallow scheme changes (say, from https to http)
+ NSString *redirectScheme = [url scheme];
+ NSString *newScheme = [redirectURL scheme];
+ NSString *newResourceSpecifier = [redirectURL resourceSpecifier];
+
+ if ([redirectScheme caseInsensitiveCompare:@"http"] == NSOrderedSame
+ && newScheme != nil
+ && [newScheme caseInsensitiveCompare:@"https"] == NSOrderedSame) {
+
+ // allow the change from http to https
+ redirectScheme = newScheme;
+ }
+
+ NSString *newUrlString = [NSString stringWithFormat:@"%@:%@",
+ redirectScheme, newResourceSpecifier];
+
+ NSURL *newURL = [NSURL URLWithString:newUrlString];
+ [newRequest setURL:newURL];
+
+ // any headers in the redirect override headers in the original.
+ NSDictionary *redirectHeaders = [redirectRequest allHTTPHeaderFields];
+ for (NSString *key in redirectHeaders) {
+ NSString *value = [redirectHeaders objectForKey:key];
+ [newRequest setValue:value forHTTPHeaderField:key];
+ }
+
+ [self addCookiesToRequest:newRequest];
+
+ redirectRequest = newRequest;
+
+ // log the response we just received
+ [self setResponse:redirectResponse];
+ [self logNowWithError:nil];
+
+ // update the request for future logging
+ NSMutableURLRequest *mutable = [[redirectRequest mutableCopy] autorelease];
+ [self setMutableRequest:mutable];
+ }
+ return redirectRequest;
+ } // @synchronized(self)
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
+ @synchronized(self) {
+ // This method is called when the server has determined that it
+ // has enough information to create the NSURLResponse
+ // it can be called multiple times, for example in the case of a
+ // redirect, so each time we reset the data.
+ [downloadedData_ setLength:0];
+ [downloadFileHandle_ truncateFileAtOffset:0];
+ downloadedLength_ = 0;
+
+ [self setResponse:response];
+
+ // Save cookies from the response
+ [self handleCookiesForResponse:response];
+ }
+}
+
+
+// handleCookiesForResponse: handles storage of cookies for responses passed to
+// connection:willSendRequest:redirectResponse: and connection:didReceiveResponse:
+- (void)handleCookiesForResponse:(NSURLResponse *)response {
+
+ if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodSystemDefault
+ || cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodNone) {
+
+ // do nothing special for NSURLConnection's default storage mechanism
+ // or when we're ignoring cookies
+
+ } else if ([response respondsToSelector:@selector(allHeaderFields)]) {
+
+ // grab the cookies from the header as NSHTTPCookies and store them either
+ // into our static array or into the fetchHistory
+
+ NSDictionary *responseHeaderFields = [(NSHTTPURLResponse *)response allHeaderFields];
+ if (responseHeaderFields) {
+
+ NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseHeaderFields
+ forURL:[response URL]];
+ if ([cookies count] > 0) {
+ [cookieStorage_ setCookies:cookies];
+ }
+ }
+ }
+}
+
+-(void)connection:(NSURLConnection *)connection
+didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
+ @synchronized(self) {
+ if ([challenge previousFailureCount] <= 2) {
+
+ NSURLCredential *credential = credential_;
+
+ if ([[challenge protectionSpace] isProxy] && proxyCredential_ != nil) {
+ credential = proxyCredential_;
+ }
+
+ // Here, if credential is still nil, then we *could* try to get it from
+ // NSURLCredentialStorage's defaultCredentialForProtectionSpace:.
+ // We don't, because we're assuming:
+ //
+ // - for server credentials, we only want ones supplied by the program
+ // calling http fetcher
+ // - for proxy credentials, if one were necessary and available in the
+ // keychain, it would've been found automatically by NSURLConnection
+ // and this challenge delegate method never would've been called
+ // anyway
+
+ if (credential) {
+ // try the credential
+ [[challenge sender] useCredential:credential
+ forAuthenticationChallenge:challenge];
+ return;
+ }
+ } // @synchronized(self)
+
+ // If we don't have credentials, or we've already failed auth 3x,
+ // report the error, putting the challenge as a value in the userInfo
+ // dictionary.
+#if DEBUG
+ NSAssert(!isCancellingChallenge_, @"isCancellingChallenge_ unexpected");
+#endif
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:challenge
+ forKey:kGTMHTTPFetcherErrorChallengeKey];
+ NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain
+ code:kGTMHTTPFetcherErrorAuthenticationChallengeFailed
+ userInfo:userInfo];
+
+ // cancelAuthenticationChallenge seems to indirectly call
+ // connection:didFailWithError: now, though that isn't documented
+ //
+ // We'll use an ivar to make the indirect invocation of the
+ // delegate method do nothing.
+ isCancellingChallenge_ = YES;
+ [[challenge sender] cancelAuthenticationChallenge:challenge];
+ isCancellingChallenge_ = NO;
+
+ [self connection:connection didFailWithError:error];
+ }
+}
+
+- (void)invokeFetchCallbacksWithData:(NSData *)data
+ error:(NSError *)error {
+ // To avoid deadlocks, this should not be called inside of @synchronized(self)
+ id target;
+ SEL sel;
+#if NS_BLOCKS_AVAILABLE
+ void (^block)(NSData *, NSError *);
+#endif
+ @synchronized(self) {
+ target = delegate_;
+ sel = finishedSel_;
+ block = completionBlock_;
+ }
+
+ [[self retain] autorelease]; // In case the callback releases us
+
+ [self invokeFetchCallback:sel
+ target:target
+ data:data
+ error:error];
+
+#if NS_BLOCKS_AVAILABLE
+ if (block) {
+ block(data, error);
+ }
+#endif
+}
+
+- (void)invokeFetchCallback:(SEL)sel
+ target:(id)target
+ data:(NSData *)data
+ error:(NSError *)error {
+ // This method is available to subclasses which may provide a customized
+ // target pointer.
+ if (target && sel) {
+ NSMethodSignature *sig = [target methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:target];
+ [invocation setArgument:&self atIndex:2];
+ [invocation setArgument:&data atIndex:3];
+ [invocation setArgument:&error atIndex:4];
+ [invocation invoke];
+ }
+}
+
+- (void)invokeFetchCallbacksOnDelegateQueueWithData:(NSData *)data
+ error:(NSError *)error {
+ // This is called by methods that are not already on the delegateQueue
+ // (as NSURLConnection callbacks should already be, but other failures
+ // are not.)
+ if (!delegateQueue_) {
+ [self invokeFetchCallbacksWithData:data error:error];
+ }
+
+ // Values may be nil.
+ NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:2];
+ [dict setValue:data forKey:kCallbackData];
+ [dict setValue:error forKey:kCallbackError];
+ NSInvocationOperation *op =
+ [[[NSInvocationOperation alloc] initWithTarget:self
+ selector:@selector(invokeOnQueueWithDictionary:)
+ object:dict] autorelease];
+ [delegateQueue_ addOperation:op];
+}
+
+- (void)invokeOnQueueWithDictionary:(NSDictionary *)dict {
+ NSData *data = [dict objectForKey:kCallbackData];
+ NSError *error = [dict objectForKey:kCallbackError];
+
+ [self invokeFetchCallbacksWithData:data error:error];
+}
+
+
+- (void)invokeSentDataCallback:(SEL)sel
+ target:(id)target
+ didSendBodyData:(NSInteger)bytesWritten
+ totalBytesWritten:(NSInteger)totalBytesWritten
+ totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
+ if (target && sel) {
+ NSMethodSignature *sig = [target methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:target];
+ [invocation setArgument:&self atIndex:2];
+ [invocation setArgument:&bytesWritten atIndex:3];
+ [invocation setArgument:&totalBytesWritten atIndex:4];
+ [invocation setArgument:&totalBytesExpectedToWrite atIndex:5];
+ [invocation invoke];
+ }
+}
+
+- (BOOL)invokeRetryCallback:(SEL)sel
+ target:(id)target
+ willRetry:(BOOL)willRetry
+ error:(NSError *)error {
+ if (target && sel) {
+ NSMethodSignature *sig = [target methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:target];
+ [invocation setArgument:&self atIndex:2];
+ [invocation setArgument:&willRetry atIndex:3];
+ [invocation setArgument:&error atIndex:4];
+ [invocation invoke];
+
+ [invocation getReturnValue:&willRetry];
+ }
+ return willRetry;
+}
+
+- (void)connection:(NSURLConnection *)connection
+ didSendBodyData:(NSInteger)bytesWritten
+ totalBytesWritten:(NSInteger)totalBytesWritten
+totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
+ @synchronized(self) {
+ SEL sel = [self sentDataSelector];
+ [self invokeSentDataCallback:sel
+ target:delegate_
+ didSendBodyData:bytesWritten
+ totalBytesWritten:totalBytesWritten
+ totalBytesExpectedToWrite:totalBytesExpectedToWrite];
+
+#if NS_BLOCKS_AVAILABLE
+ if (sentDataBlock_) {
+ sentDataBlock_(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
+ }
+#endif
+ }
+}
+
+- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
+ @synchronized(self) {
+#if DEBUG
+ NSAssert(!hasConnectionEnded_, @"Connection received data after ending");
+
+ // The download file handle should be set or the data object allocated
+ // before the fetch is started.
+ NSAssert((downloadFileHandle_ == nil) != (downloadedData_ == nil),
+ @"received data accumulates as either NSData (%d) or"
+ @" NSFileHandle (%d)",
+ (downloadedData_ != nil), (downloadFileHandle_ != nil));
+#endif
+ // Hopefully, we'll never see this execute out-of-order, receiving data
+ // after we've received the finished or failed callback.
+ if (hasConnectionEnded_) return;
+
+ if (downloadFileHandle_ != nil) {
+ // Append to file
+ @try {
+ [downloadFileHandle_ writeData:data];
+
+ downloadedLength_ = [downloadFileHandle_ offsetInFile];
+ }
+ @catch (NSException *exc) {
+ // Couldn't write to file, probably due to a full disk
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[exc reason]
+ forKey:NSLocalizedDescriptionKey];
+ NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
+ code:kGTMHTTPFetcherErrorFileHandleException
+ userInfo:userInfo];
+ [self connection:connection didFailWithError:error];
+ return;
+ }
+ } else {
+ // append to mutable data
+ [downloadedData_ appendData:data];
+
+ downloadedLength_ = [downloadedData_ length];
+ }
+
+ if (receivedDataSel_) {
+ [delegate_ performSelector:receivedDataSel_
+ withObject:self
+ withObject:downloadedData_];
+ }
+
+#if NS_BLOCKS_AVAILABLE
+ if (receivedDataBlock_) {
+ receivedDataBlock_(downloadedData_);
+ }
+#endif
+ } // @synchronized(self)
+}
+
+// For error 304's ("Not Modified") where we've cached the data, return
+// status 200 ("OK") to the caller (but leave the fetcher status as 304)
+// and copy the cached data.
+//
+// For other errors or if there's no cached data, just return the actual status.
+- (NSData *)cachedDataForStatus {
+ if ([self statusCode] == kGTMHTTPFetcherStatusNotModified
+ && [fetchHistory_ shouldCacheETaggedData]) {
+ NSData *cachedData = [fetchHistory_ cachedDataForRequest:request_];
+ return cachedData;
+ }
+ return nil;
+}
+
+- (NSInteger)statusAfterHandlingNotModifiedError {
+ NSInteger status = [self statusCode];
+ NSData *cachedData = [self cachedDataForStatus];
+ if (cachedData) {
+ // Forge the status to pass on to the delegate
+ status = 200;
+
+ // Copy our stored data
+ if (downloadFileHandle_ != nil) {
+ @try {
+ // Downloading to a file handle won't save to the cache (the data is
+ // likely inappropriately large for caching), but will still read from
+ // the cache, on the unlikely chance that the response was Not Modified
+ // and the URL response was indeed present in the cache.
+ [downloadFileHandle_ truncateFileAtOffset:0];
+ [downloadFileHandle_ writeData:cachedData];
+ downloadedLength_ = [downloadFileHandle_ offsetInFile];
+ }
+ @catch (NSException *) {
+ // Failed to write data, likely due to lack of disk space
+ status = kGTMHTTPFetcherErrorFileHandleException;
+ }
+ } else {
+ [downloadedData_ setData:cachedData];
+ downloadedLength_ = [cachedData length];
+ }
+ }
+ return status;
+}
+
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
+ BOOL shouldStopFetching = YES;
+ BOOL shouldSendStopNotification = NO;
+ NSError *error = nil;
+ NSData *downloadedData;
+#if !STRIP_GTM_FETCH_LOGGING
+ BOOL shouldDeferLogging = NO;
+#endif
+ BOOL shouldBeginRetryTimer = NO;
+ BOOL hasLogged = NO;
+
+ @synchronized(self) {
+ // We no longer need to cancel the connection
+ hasConnectionEnded_ = YES;
+
+ // Skip caching ETagged results when the data is being saved to a file
+ if (downloadFileHandle_ == nil) {
+ [fetchHistory_ updateFetchHistoryWithRequest:request_
+ response:response_
+ downloadedData:downloadedData_];
+ } else {
+ [fetchHistory_ removeCachedDataForRequest:request_];
+ }
+
+ [[self retain] autorelease]; // in case the callback releases us
+
+ NSInteger status = [self statusCode];
+ if ([self cachedDataForStatus] != nil) {
+ // Log the pre-cache response.
+ [self logNowWithError:nil];
+ hasLogged = YES;
+ status = [self statusAfterHandlingNotModifiedError];
+ }
+
+ shouldSendStopNotification = YES;
+
+ if (status >= 0 && status < 300) {
+ // success
+ if (downloadPath_) {
+ // Avoid deleting the downloaded file when the fetch stops
+ [downloadFileHandle_ closeFile];
+ self.downloadFileHandle = nil;
+
+ NSFileManager *fileMgr = [NSFileManager defaultManager];
+ [fileMgr removeItemAtPath:downloadPath_
+ error:NULL];
+
+ if ([fileMgr moveItemAtPath:temporaryDownloadPath_
+ toPath:downloadPath_
+ error:&error]) {
+ self.temporaryDownloadPath = nil;
+ }
+ }
+ } else {
+ // unsuccessful
+ if (!hasLogged) {
+ [self logNowWithError:nil];
+ hasLogged = YES;
+ }
+ // Status over 300; retry or notify the delegate of failure
+ if ([self shouldRetryNowForStatus:status error:nil]) {
+ // retrying
+ shouldBeginRetryTimer = YES;
+ shouldStopFetching = NO;
+ } else {
+ NSDictionary *userInfo = nil;
+ if ([downloadedData_ length] > 0) {
+ userInfo = [NSDictionary dictionaryWithObject:downloadedData_
+ forKey:kGTMHTTPFetcherStatusDataKey];
+ }
+ error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
+ code:status
+ userInfo:userInfo];
+ }
+ }
+ downloadedData = downloadedData_;
+#if !STRIP_GTM_FETCH_LOGGING
+ shouldDeferLogging = shouldDeferResponseBodyLogging_;
+#endif
+ } // @synchronized(self)
+
+ if (shouldBeginRetryTimer) {
+ [self beginRetryTimer];
+ }
+
+ if (shouldSendStopNotification) {
+ // We want to send the stop notification before calling the delegate's
+ // callback selector, since the callback selector may release all of
+ // the fetcher properties that the client is using to track the fetches.
+ //
+ // We'll also stop now so that, to any observers watching the notifications,
+ // it doesn't look like our wait for a retry (which may be long,
+ // 30 seconds or more) is part of the network activity.
+ [self sendStopNotificationIfNeeded];
+ }
+
+ if (shouldStopFetching) {
+ // Call the callbacks (outside of the @synchronized to avoid deadlocks.)
+ [self invokeFetchCallbacksWithData:downloadedData
+ error:error];
+ BOOL shouldRelease = [self shouldReleaseCallbacksUponCompletion];
+ [self stopFetchReleasingCallbacks:shouldRelease];
+ }
+
+ @synchronized(self) {
+ BOOL shouldLogNow = !hasLogged;
+#if !STRIP_GTM_FETCH_LOGGING
+ if (shouldDeferLogging) shouldLogNow = NO;
+#endif
+ if (shouldLogNow) {
+ [self logNowWithError:nil];
+ }
+ }
+}
+
+- (BOOL)shouldReleaseCallbacksUponCompletion {
+ // A subclass can override this to keep callbacks around after the
+ // connection has finished successfully
+ return YES;
+}
+
+- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
+ @synchronized(self) {
+ // Prevent the failure callback from being called twice, since the stopFetch
+ // call below (either the explicit one at the end of this method, or the
+ // implicit one when the retry occurs) will release the delegate.
+ if (connection_ == nil) return;
+
+ // If this method was invoked indirectly by cancellation of an authentication
+ // challenge, defer this until it is called again with the proper error object
+ if (isCancellingChallenge_) return;
+
+ // We no longer need to cancel the connection
+ hasConnectionEnded_ = YES;
+
+ [self logNowWithError:error];
+ }
+
+ // See comment about sendStopNotificationIfNeeded
+ // in connectionDidFinishLoading:
+ [self sendStopNotificationIfNeeded];
+
+ if ([self shouldRetryNowForStatus:0 error:error]) {
+ [self beginRetryTimer];
+ } else {
+ [[self retain] autorelease]; // in case the callback releases us
+
+ [self invokeFetchCallbacksWithData:nil
+ error:error];
+
+ [self stopFetchReleasingCallbacks:YES];
+ }
+}
+
+- (void)logNowWithError:(NSError *)error {
+ // If the logging category is available, then log the current request,
+ // response, data, and error
+ if ([self respondsToSelector:@selector(logFetchWithError:)]) {
+ [self performSelector:@selector(logFetchWithError:) withObject:error];
+ }
+}
+
+#pragma mark Retries
+
+- (BOOL)isRetryError:(NSError *)error {
+
+ struct retryRecord {
+ NSString *const domain;
+ int code;
+ };
+
+ struct retryRecord retries[] = {
+ { kGTMHTTPFetcherStatusDomain, 408 }, // request timeout
+ { kGTMHTTPFetcherStatusDomain, 503 }, // service unavailable
+ { kGTMHTTPFetcherStatusDomain, 504 }, // request timeout
+ { NSURLErrorDomain, NSURLErrorTimedOut },
+ { NSURLErrorDomain, NSURLErrorNetworkConnectionLost },
+ { nil, 0 }
+ };
+
+ // NSError's isEqual always returns false for equal but distinct instances
+ // of NSError, so we have to compare the domain and code values explicitly
+
+ for (int idx = 0; retries[idx].domain != nil; idx++) {
+
+ if ([[error domain] isEqual:retries[idx].domain]
+ && [error code] == retries[idx].code) {
+
+ return YES;
+ }
+ }
+ return NO;
+}
+
+
+// shouldRetryNowForStatus:error: returns YES if the user has enabled retries
+// and the status or error is one that is suitable for retrying. "Suitable"
+// means either the isRetryError:'s list contains the status or error, or the
+// user's retrySelector: is present and returns YES when called, or the
+// authorizer may be able to fix.
+- (BOOL)shouldRetryNowForStatus:(NSInteger)status
+ error:(NSError *)error {
+ // Determine if a refreshed authorizer may avoid an authorization error
+ BOOL shouldRetryForAuthRefresh = NO;
+ BOOL isFirstAuthError = (authorizer_ != nil)
+ && !hasAttemptedAuthRefresh_
+ && (status == kGTMHTTPFetcherStatusUnauthorized); // 401
+
+ if (isFirstAuthError) {
+ if ([authorizer_ respondsToSelector:@selector(primeForRefresh)]) {
+ BOOL hasPrimed = [authorizer_ primeForRefresh];
+ if (hasPrimed) {
+ shouldRetryForAuthRefresh = YES;
+ hasAttemptedAuthRefresh_ = YES;
+ [request_ setValue:nil forHTTPHeaderField:@"Authorization"];
+ }
+ }
+ }
+
+ // Determine if we're doing exponential backoff retries
+ BOOL shouldDoIntervalRetry = [self isRetryEnabled]
+ && ([self nextRetryInterval] < [self maxRetryInterval]);
+
+ BOOL willRetry = NO;
+ BOOL canRetry = shouldRetryForAuthRefresh || shouldDoIntervalRetry;
+ if (canRetry) {
+ // Check if this is a retryable error
+ if (error == nil) {
+ // Make an error for the status
+ NSDictionary *userInfo = nil;
+ if ([downloadedData_ length] > 0) {
+ userInfo = [NSDictionary dictionaryWithObject:downloadedData_
+ forKey:kGTMHTTPFetcherStatusDataKey];
+ }
+ error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
+ code:status
+ userInfo:userInfo];
+ }
+
+ willRetry = shouldRetryForAuthRefresh || [self isRetryError:error];
+
+ // If the user has installed a retry callback, consult that
+ willRetry = [self invokeRetryCallback:retrySel_
+ target:delegate_
+ willRetry:willRetry
+ error:error];
+#if NS_BLOCKS_AVAILABLE
+ if (retryBlock_) {
+ willRetry = retryBlock_(willRetry, error);
+ }
+#endif
+ }
+ return willRetry;
+}
+
+- (void)beginRetryTimer {
+ @synchronized(self) {
+ if (delegateQueue_ != nil && ![NSThread isMainThread]) {
+ // A delegate queue is set, so the thread we're running on may not
+ // have a run loop. We'll defer creating and starting the timer
+ // until we're on the main thread to ensure it has a run loop.
+ // (If we weren't supporting 10.5, we could use dispatch_after instead
+ // of an NSTimer.)
+ [self performSelectorOnMainThread:_cmd
+ withObject:nil
+ waitUntilDone:NO];
+ return;
+ }
+ }
+
+ NSTimeInterval nextInterval = [self nextRetryInterval];
+ NSTimeInterval maxInterval = [self maxRetryInterval];
+ NSTimeInterval newInterval = MIN(nextInterval, maxInterval);
+
+ [self primeRetryTimerWithNewTimeInterval:newInterval];
+
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:kGTMHTTPFetcherRetryDelayStartedNotification
+ object:self];
+}
+
+- (void)primeRetryTimerWithNewTimeInterval:(NSTimeInterval)secs {
+
+ [self destroyRetryTimer];
+
+ @synchronized(self) {
+ lastRetryInterval_ = secs;
+
+ retryTimer_ = [NSTimer timerWithTimeInterval:secs
+ target:self
+ selector:@selector(retryTimerFired:)
+ userInfo:nil
+ repeats:NO];
+ [retryTimer_ retain];
+
+ NSRunLoop *timerRL = (self.delegateQueue ?
+ [NSRunLoop mainRunLoop] : [NSRunLoop currentRunLoop]);
+ [timerRL addTimer:retryTimer_
+ forMode:NSDefaultRunLoopMode];
+ }
+}
+
+- (void)retryTimerFired:(NSTimer *)timer {
+ [self destroyRetryTimer];
+
+ @synchronized(self) {
+ retryCount_++;
+
+ [self retryFetch];
+ }
+}
+
+- (void)destroyRetryTimer {
+ BOOL shouldNotify = NO;
+ @synchronized(self) {
+ if (retryTimer_) {
+ [retryTimer_ invalidate];
+ [retryTimer_ autorelease];
+ retryTimer_ = nil;
+ shouldNotify = YES;
+ }
+ } // @synchronized(self)
+
+ if (shouldNotify) {
+ NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
+ [defaultNC postNotificationName:kGTMHTTPFetcherRetryDelayStoppedNotification
+ object:self];
+ }
+}
+
+- (NSUInteger)retryCount {
+ return retryCount_;
+}
+
+- (NSTimeInterval)nextRetryInterval {
+ // The next wait interval is the factor (2.0) times the last interval,
+ // but never less than the minimum interval.
+ NSTimeInterval secs = lastRetryInterval_ * retryFactor_;
+ secs = MIN(secs, maxRetryInterval_);
+ secs = MAX(secs, minRetryInterval_);
+
+ return secs;
+}
+
+- (BOOL)isRetryEnabled {
+ return isRetryEnabled_;
+}
+
+- (void)setRetryEnabled:(BOOL)flag {
+
+ if (flag && !isRetryEnabled_) {
+ // We defer initializing these until the user calls setRetryEnabled
+ // to avoid using the random number generator if it's not needed.
+ // However, this means min and max intervals for this fetcher are reset
+ // as a side effect of calling setRetryEnabled.
+ //
+ // Make an initial retry interval random between 1.0 and 2.0 seconds
+ [self setMinRetryInterval:0.0];
+ [self setMaxRetryInterval:kUnsetMaxRetryInterval];
+ [self setRetryFactor:2.0];
+ lastRetryInterval_ = 0.0;
+ }
+ isRetryEnabled_ = flag;
+};
+
+- (NSTimeInterval)maxRetryInterval {
+ return maxRetryInterval_;
+}
+
+- (void)setMaxRetryInterval:(NSTimeInterval)secs {
+ if (secs > 0) {
+ maxRetryInterval_ = secs;
+ } else {
+ maxRetryInterval_ = kUnsetMaxRetryInterval;
+ }
+}
+
+- (double)minRetryInterval {
+ return minRetryInterval_;
+}
+
+- (void)setMinRetryInterval:(NSTimeInterval)secs {
+ if (secs > 0) {
+ minRetryInterval_ = secs;
+ } else {
+ // Set min interval to a random value between 1.0 and 2.0 seconds
+ // so that if multiple clients start retrying at the same time, they'll
+ // repeat at different times and avoid overloading the server
+ minRetryInterval_ = 1.0 + ((double)(arc4random() & 0x0FFFF) / (double) 0x0FFFF);
+ }
+}
+
+#pragma mark Getters and Setters
+
+@dynamic cookieStorageMethod,
+ retryEnabled,
+ maxRetryInterval,
+ minRetryInterval,
+ retryCount,
+ nextRetryInterval,
+ statusCode,
+ responseHeaders,
+ fetchHistory,
+ userData,
+ properties;
+
+@synthesize mutableRequest = request_,
+ credential = credential_,
+ proxyCredential = proxyCredential_,
+ postData = postData_,
+ postStream = postStream_,
+ delegate = delegate_,
+ authorizer = authorizer_,
+ service = service_,
+ serviceHost = serviceHost_,
+ servicePriority = servicePriority_,
+ thread = thread_,
+ sentDataSelector = sentDataSel_,
+ receivedDataSelector = receivedDataSel_,
+ retrySelector = retrySel_,
+ retryFactor = retryFactor_,
+ response = response_,
+ downloadedLength = downloadedLength_,
+ downloadedData = downloadedData_,
+ downloadPath = downloadPath_,
+ temporaryDownloadPath = temporaryDownloadPath_,
+ downloadFileHandle = downloadFileHandle_,
+ delegateQueue = delegateQueue_,
+ runLoopModes = runLoopModes_,
+ comment = comment_,
+ log = log_,
+ cookieStorage = cookieStorage_;
+
+#if NS_BLOCKS_AVAILABLE
+@synthesize completionBlock = completionBlock_,
+ sentDataBlock = sentDataBlock_,
+ receivedDataBlock = receivedDataBlock_,
+ retryBlock = retryBlock_;
+#endif
+
+@synthesize shouldFetchInBackground = shouldFetchInBackground_;
+
+- (NSInteger)cookieStorageMethod {
+ return cookieStorageMethod_;
+}
+
+- (void)setCookieStorageMethod:(NSInteger)method {
+
+ cookieStorageMethod_ = method;
+
+ if (method == kGTMHTTPFetcherCookieStorageMethodSystemDefault) {
+ // System default
+ [request_ setHTTPShouldHandleCookies:YES];
+
+ // No need for a cookie storage object
+ self.cookieStorage = nil;
+
+ } else {
+ // Not system default
+ [request_ setHTTPShouldHandleCookies:NO];
+
+ if (method == kGTMHTTPFetcherCookieStorageMethodStatic) {
+ // Store cookies in the static array
+ NSAssert(gGTMFetcherStaticCookieStorage != nil,
+ @"cookie storage requires GTMHTTPFetchHistory");
+
+ self.cookieStorage = gGTMFetcherStaticCookieStorage;
+ } else if (method == kGTMHTTPFetcherCookieStorageMethodFetchHistory) {
+ // store cookies in the fetch history
+ self.cookieStorage = [fetchHistory_ cookieStorage];
+ } else {
+ // kGTMHTTPFetcherCookieStorageMethodNone - ignore cookies
+ self.cookieStorage = nil;
+ }
+ }
+}
+
++ (id <GTMCookieStorageProtocol>)staticCookieStorage {
+ return gGTMFetcherStaticCookieStorage;
+}
+
++ (BOOL)doesSupportSentDataCallback {
+#if GTM_IPHONE
+ // NSURLConnection's didSendBodyData: delegate support appears to be
+ // available starting in iPhone OS 3.0
+ return (NSFoundationVersionNumber >= 678.47);
+#else
+ // Per WebKit's MaxFoundationVersionWithoutdidSendBodyDataDelegate
+ //
+ // Indicates if NSURLConnection will invoke the didSendBodyData: delegate
+ // method
+ return (NSFoundationVersionNumber > 677.21);
+#endif
+}
+
+- (id <GTMHTTPFetchHistoryProtocol>)fetchHistory {
+ return fetchHistory_;
+}
+
+- (void)setFetchHistory:(id <GTMHTTPFetchHistoryProtocol>)fetchHistory {
+ [fetchHistory_ autorelease];
+ fetchHistory_ = [fetchHistory retain];
+
+ if (fetchHistory_ != nil) {
+ // set the fetch history's cookie array to be the cookie store
+ [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodFetchHistory];
+
+ } else {
+ // The fetch history was removed
+ if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodFetchHistory) {
+ // Fall back to static storage
+ [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodStatic];
+ }
+ }
+}
+
+- (id)userData {
+ @synchronized(self) {
+ return userData_;
+ }
+}
+
+- (void)setUserData:(id)theObj {
+ @synchronized(self) {
+ [userData_ autorelease];
+ userData_ = [theObj retain];
+ }
+}
+
+- (void)setProperties:(NSMutableDictionary *)dict {
+ @synchronized(self) {
+ [properties_ autorelease];
+
+ // This copies rather than retains the parameter for compatiblity with
+ // an earlier version that took an immutable parameter and copied it.
+ properties_ = [dict mutableCopy];
+ }
+}
+
+- (NSMutableDictionary *)properties {
+ @synchronized(self) {
+ return properties_;
+ }
+}
+
+- (void)setProperty:(id)obj forKey:(NSString *)key {
+ @synchronized(self) {
+ if (properties_ == nil && obj != nil) {
+ [self setProperties:[NSMutableDictionary dictionary]];
+ }
+ [properties_ setValue:obj forKey:key];
+ }
+}
+
+- (id)propertyForKey:(NSString *)key {
+ @synchronized(self) {
+ return [properties_ objectForKey:key];
+ }
+}
+
+- (void)addPropertiesFromDictionary:(NSDictionary *)dict {
+ @synchronized(self) {
+ if (properties_ == nil && dict != nil) {
+ [self setProperties:[[dict mutableCopy] autorelease]];
+ } else {
+ [properties_ addEntriesFromDictionary:dict];
+ }
+ }
+}
+
+- (void)setCommentWithFormat:(id)format, ... {
+#if !STRIP_GTM_FETCH_LOGGING
+ NSString *result = format;
+ if (format) {
+ va_list argList;
+ va_start(argList, format);
+
+ result = [[[NSString alloc] initWithFormat:format
+ arguments:argList] autorelease];
+ va_end(argList);
+ }
+ [self setComment:result];
+#endif
+}
+
++ (Class)connectionClass {
+ if (gGTMFetcherConnectionClass == nil) {
+ gGTMFetcherConnectionClass = [NSURLConnection class];
+ }
+ return gGTMFetcherConnectionClass;
+}
+
++ (void)setConnectionClass:(Class)theClass {
+ gGTMFetcherConnectionClass = theClass;
+}
+
+#if STRIP_GTM_FETCH_LOGGING
++ (void)setLoggingEnabled:(BOOL)flag {
+}
+#endif // STRIP_GTM_FETCH_LOGGING
+
+@end
+
+void GTMAssertSelectorNilOrImplementedWithArgs(id obj, SEL sel, ...) {
+
+ // Verify that the object's selector is implemented with the proper
+ // number and type of arguments
+#if DEBUG
+ va_list argList;
+ va_start(argList, sel);
+
+ if (obj && sel) {
+ // Check that the selector is implemented
+ if (![obj respondsToSelector:sel]) {
+ NSLog(@"\"%@\" selector \"%@\" is unimplemented or misnamed",
+ NSStringFromClass([obj class]),
+ NSStringFromSelector(sel));
+ NSCAssert(0, @"callback selector unimplemented or misnamed");
+ } else {
+ const char *expectedArgType;
+ unsigned int argCount = 2; // skip self and _cmd
+ NSMethodSignature *sig = [obj methodSignatureForSelector:sel];
+
+ // Check that each expected argument is present and of the correct type
+ while ((expectedArgType = va_arg(argList, const char*)) != 0) {
+
+ if ([sig numberOfArguments] > argCount) {
+ const char *foundArgType = [sig getArgumentTypeAtIndex:argCount];
+
+ if(0 != strncmp(foundArgType, expectedArgType, strlen(expectedArgType))) {
+ NSLog(@"\"%@\" selector \"%@\" argument %d should be type %s",
+ NSStringFromClass([obj class]),
+ NSStringFromSelector(sel), (argCount - 2), expectedArgType);
+ NSCAssert(0, @"callback selector argument type mistake");
+ }
+ }
+ argCount++;
+ }
+
+ // Check that the proper number of arguments are present in the selector
+ if (argCount != [sig numberOfArguments]) {
+ NSLog( @"\"%@\" selector \"%@\" should have %d arguments",
+ NSStringFromClass([obj class]),
+ NSStringFromSelector(sel), (argCount - 2));
+ NSCAssert(0, @"callback selector arguments incorrect");
+ }
+ }
+ }
+
+ va_end(argList);
+#endif
+}
+
+NSString *GTMCleanedUserAgentString(NSString *str) {
+ // Reference http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html
+ // and http://www-archive.mozilla.org/build/user-agent-strings.html
+
+ if (str == nil) return nil;
+
+ NSMutableString *result = [NSMutableString stringWithString:str];
+
+ // Replace spaces with underscores
+ [result replaceOccurrencesOfString:@" "
+ withString:@"_"
+ options:0
+ range:NSMakeRange(0, [result length])];
+
+ // Delete http token separators and remaining whitespace
+ static NSCharacterSet *charsToDelete = nil;
+ if (charsToDelete == nil) {
+ // Make a set of unwanted characters
+ NSString *const kSeparators = @"()<>@,;:\\\"/[]?={}";
+
+ NSMutableCharacterSet *mutableChars;
+ mutableChars = [[[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy] autorelease];
+ [mutableChars addCharactersInString:kSeparators];
+ charsToDelete = [mutableChars copy]; // hang on to an immutable copy
+ }
+
+ while (1) {
+ NSRange separatorRange = [result rangeOfCharacterFromSet:charsToDelete];
+ if (separatorRange.location == NSNotFound) break;
+
+ [result deleteCharactersInRange:separatorRange];
+ };
+
+ return result;
+}
+
+NSString *GTMSystemVersionString(void) {
+ NSString *systemString = @"";
+
+#if TARGET_OS_MAC && !TARGET_OS_IPHONE
+ // Mac build
+ static NSString *savedSystemString = nil;
+ if (savedSystemString == nil) {
+ // With Gestalt inexplicably deprecated in 10.8, we're reduced to reading
+ // the system plist file.
+ NSString *const kPath = @"/System/Library/CoreServices/SystemVersion.plist";
+ NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:kPath];
+ NSString *versString = [plist objectForKey:@"ProductVersion"];
+ if ([versString length] == 0) {
+ versString = @"10.?.?";
+ }
+ savedSystemString = [[NSString alloc] initWithFormat:@"MacOSX/%@", versString];
+ }
+ systemString = savedSystemString;
+#elif TARGET_OS_IPHONE
+ // Compiling against the iPhone SDK
+
+ static NSString *savedSystemString = nil;
+ if (savedSystemString == nil) {
+ // Avoid the slowness of calling currentDevice repeatedly on the iPhone
+ UIDevice* currentDevice = [UIDevice currentDevice];
+
+ NSString *rawModel = [currentDevice model];
+ NSString *model = GTMCleanedUserAgentString(rawModel);
+
+ NSString *systemVersion = [currentDevice systemVersion];
+
+ savedSystemString = [[NSString alloc] initWithFormat:@"%@/%@",
+ model, systemVersion]; // "iPod_Touch/2.2"
+ }
+ systemString = savedSystemString;
+
+#elif (GTL_IPHONE || GDATA_IPHONE)
+ // Compiling iOS libraries against the Mac SDK
+ systemString = @"iPhone/x.x";
+
+#elif defined(_SYS_UTSNAME_H)
+ // Foundation-only build
+ struct utsname unameRecord;
+ uname(&unameRecord);
+
+ systemString = [NSString stringWithFormat:@"%s/%s",
+ unameRecord.sysname, unameRecord.release]; // "Darwin/8.11.1"
+#endif
+
+ return systemString;
+}
+
+// Return a generic name and version for the current application; this avoids
+// anonymous server transactions.
+NSString *GTMApplicationIdentifier(NSBundle *bundle) {
+ static NSString *sAppID = nil;
+ if (sAppID != nil) return sAppID;
+
+ // If there's a bundle ID, use that; otherwise, use the process name
+ if (bundle == nil) {
+ bundle = [NSBundle mainBundle];
+ }
+
+ NSString *identifier;
+ NSString *bundleID = [bundle bundleIdentifier];
+ if ([bundleID length] > 0) {
+ identifier = bundleID;
+ } else {
+ // Fall back on the procname, prefixed by "proc" to flag that it's
+ // autogenerated and perhaps unreliable
+ NSString *procName = [[NSProcessInfo processInfo] processName];
+ identifier = [NSString stringWithFormat:@"proc_%@", procName];
+ }
+
+ // Clean up whitespace and special characters
+ identifier = GTMCleanedUserAgentString(identifier);
+
+ // If there's a version number, append that
+ NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+ if ([version length] == 0) {
+ version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
+ }
+
+ // Clean up whitespace and special characters
+ version = GTMCleanedUserAgentString(version);
+
+ // Glue the two together (cleanup done above or else cleanup would strip the
+ // slash)
+ if ([version length] > 0) {
+ identifier = [identifier stringByAppendingFormat:@"/%@", version];
+ }
+
+ sAppID = [identifier copy];
+ return sAppID;
+}
diff --git a/example/common/gtm-oauth2/Source/GTMOAuth2Authentication.h b/example/common/gtm-oauth2/Source/GTMOAuth2Authentication.h
new file mode 100644
index 00000000..8703164b
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/GTMOAuth2Authentication.h
@@ -0,0 +1,356 @@
+/* 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.
+ */
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+// This class implements the OAuth 2 protocol for authorizing requests.
+// http://tools.ietf.org/html/draft-ietf-oauth-v2
+
+#import <Foundation/Foundation.h>
+
+// GTMHTTPFetcher.h brings in GTLDefines/GDataDefines
+#import "GTMHTTPFetcher.h"
+
+#undef _EXTERN
+#undef _INITIALIZE_AS
+#ifdef GTMOAUTH2AUTHENTICATION_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
+
+// Until all OAuth 2 providers are up to the same spec, we'll provide a crude
+// way here to override the "Bearer" string in the Authorization header
+#ifndef GTM_OAUTH2_BEARER
+#define GTM_OAUTH2_BEARER "Bearer"
+#endif
+
+// Service provider name allows stored authorization to be associated with
+// the authorizing service
+_EXTERN NSString* const kGTMOAuth2ServiceProviderGoogle _INITIALIZE_AS(@"Google");
+
+//
+// GTMOAuth2SignIn constants, included here for use by clients
+//
+_EXTERN NSString* const kGTMOAuth2ErrorDomain _INITIALIZE_AS(@"com.google.GTMOAuth2");
+
+// Error userInfo keys
+_EXTERN NSString* const kGTMOAuth2ErrorMessageKey _INITIALIZE_AS(@"error");
+_EXTERN NSString* const kGTMOAuth2ErrorRequestKey _INITIALIZE_AS(@"request");
+_EXTERN NSString* const kGTMOAuth2ErrorJSONKey _INITIALIZE_AS(@"json");
+
+enum {
+ // Error code indicating that the window was prematurely closed
+ kGTMOAuth2ErrorWindowClosed = -1000,
+ kGTMOAuth2ErrorAuthorizationFailed = -1001,
+ kGTMOAuth2ErrorTokenExpired = -1002,
+ kGTMOAuth2ErrorTokenUnavailable = -1003,
+ kGTMOAuth2ErrorUnauthorizableRequest = -1004
+};
+
+
+// Notifications for token fetches
+_EXTERN NSString* const kGTMOAuth2FetchStarted _INITIALIZE_AS(@"kGTMOAuth2FetchStarted");
+_EXTERN NSString* const kGTMOAuth2FetchStopped _INITIALIZE_AS(@"kGTMOAuth2FetchStopped");
+
+_EXTERN NSString* const kGTMOAuth2FetcherKey _INITIALIZE_AS(@"fetcher");
+_EXTERN NSString* const kGTMOAuth2FetchTypeKey _INITIALIZE_AS(@"FetchType");
+_EXTERN NSString* const kGTMOAuth2FetchTypeToken _INITIALIZE_AS(@"token");
+_EXTERN NSString* const kGTMOAuth2FetchTypeRefresh _INITIALIZE_AS(@"refresh");
+_EXTERN NSString* const kGTMOAuth2FetchTypeAssertion _INITIALIZE_AS(@"assertion");
+_EXTERN NSString* const kGTMOAuth2FetchTypeUserInfo _INITIALIZE_AS(@"userInfo");
+
+// Token-issuance errors
+_EXTERN NSString* const kGTMOAuth2ErrorKey _INITIALIZE_AS(@"error");
+_EXTERN NSString* const kGTMOAuth2ErrorObjectKey _INITIALIZE_AS(@"kGTMOAuth2ErrorObjectKey");
+
+_EXTERN NSString* const kGTMOAuth2ErrorInvalidRequest _INITIALIZE_AS(@"invalid_request");
+_EXTERN NSString* const kGTMOAuth2ErrorInvalidClient _INITIALIZE_AS(@"invalid_client");
+_EXTERN NSString* const kGTMOAuth2ErrorInvalidGrant _INITIALIZE_AS(@"invalid_grant");
+_EXTERN NSString* const kGTMOAuth2ErrorUnauthorizedClient _INITIALIZE_AS(@"unauthorized_client");
+_EXTERN NSString* const kGTMOAuth2ErrorUnsupportedGrantType _INITIALIZE_AS(@"unsupported_grant_type");
+_EXTERN NSString* const kGTMOAuth2ErrorInvalidScope _INITIALIZE_AS(@"invalid_scope");
+
+// Notification that sign-in has completed, and token fetches will begin (useful
+// for displaying interstitial messages after the window has closed)
+_EXTERN NSString* const kGTMOAuth2UserSignedIn _INITIALIZE_AS(@"kGTMOAuth2UserSignedIn");
+
+// Notification for token changes
+_EXTERN NSString* const kGTMOAuth2AccessTokenRefreshed _INITIALIZE_AS(@"kGTMOAuth2AccessTokenRefreshed");
+_EXTERN NSString* const kGTMOAuth2RefreshTokenChanged _INITIALIZE_AS(@"kGTMOAuth2RefreshTokenChanged");
+_EXTERN NSString* const kGTMOAuth2AccessTokenRefreshFailed _INITIALIZE_AS(@"kGTMOAuth2AccessTokenRefreshFailed");
+
+// Notification for WebView loading
+_EXTERN NSString* const kGTMOAuth2WebViewStartedLoading _INITIALIZE_AS(@"kGTMOAuth2WebViewStartedLoading");
+_EXTERN NSString* const kGTMOAuth2WebViewStoppedLoading _INITIALIZE_AS(@"kGTMOAuth2WebViewStoppedLoading");
+_EXTERN NSString* const kGTMOAuth2WebViewKey _INITIALIZE_AS(@"kGTMOAuth2WebViewKey");
+_EXTERN NSString* const kGTMOAuth2WebViewStopKindKey _INITIALIZE_AS(@"kGTMOAuth2WebViewStopKindKey");
+_EXTERN NSString* const kGTMOAuth2WebViewFinished _INITIALIZE_AS(@"finished");
+_EXTERN NSString* const kGTMOAuth2WebViewFailed _INITIALIZE_AS(@"failed");
+_EXTERN NSString* const kGTMOAuth2WebViewCancelled _INITIALIZE_AS(@"cancelled");
+
+// Notification for network loss during html sign-in display
+_EXTERN NSString* const kGTMOAuth2NetworkLost _INITIALIZE_AS(@"kGTMOAuthNetworkLost");
+_EXTERN NSString* const kGTMOAuth2NetworkFound _INITIALIZE_AS(@"kGTMOAuthNetworkFound");
+
+@interface GTMOAuth2Authentication : NSObject <GTMFetcherAuthorizationProtocol> {
+ @private
+ NSString *clientID_;
+ NSString *clientSecret_;
+ NSString *redirectURI_;
+ NSMutableDictionary *parameters_;
+
+ // authorization parameters
+ NSURL *tokenURL_;
+ NSDate *expirationDate_;
+
+ NSString *authorizationTokenKey_;
+
+ NSDictionary *additionalTokenRequestParameters_;
+ NSDictionary *additionalGrantTypeRequestParameters_;
+
+ // queue of requests for authorization waiting for a valid access token
+ GTMHTTPFetcher *refreshFetcher_;
+ NSMutableArray *authorizationQueue_;
+
+ id <GTMHTTPFetcherServiceProtocol> fetcherService_; // WEAK
+
+ Class parserClass_;
+
+ BOOL shouldAuthorizeAllRequests_;
+
+ // arbitrary data retained for the user
+ id userData_;
+ NSMutableDictionary *properties_;
+}
+
+// OAuth2 standard protocol parameters
+//
+// These should be the plain strings; any needed escaping will be provided by
+// the library.
+
+// Request properties
+@property (copy) NSString *clientID;
+@property (copy) NSString *clientSecret;
+@property (copy) NSString *redirectURI;
+@property (retain) NSString *scope;
+@property (retain) NSString *tokenType;
+@property (retain) NSString *assertion;
+@property (retain) NSString *refreshScope;
+
+// Apps may optionally add parameters here to be provided to the token
+// endpoint on token requests and refreshes.
+@property (retain) NSDictionary *additionalTokenRequestParameters;
+
+// Apps may optionally add parameters here to be provided to the token
+// endpoint on specific token requests and refreshes, keyed by the grant_type.
+// For example, if a different "type" parameter is required for obtaining
+// the auth code and on refresh, this might be:
+//
+// viewController.authentication.additionalGrantTypeRequestParameters = @{
+// @"authorization_code" : @{ @"type" : @"code" },
+// @"refresh_token" : @{ @"type" : @"refresh" }
+// };
+@property (retain) NSDictionary *additionalGrantTypeRequestParameters;
+
+// Response properties
+@property (retain) NSMutableDictionary *parameters;
+
+@property (retain) NSString *accessToken;
+@property (retain) NSString *refreshToken;
+@property (retain) NSNumber *expiresIn;
+@property (retain) NSString *code;
+@property (retain) NSString *errorString;
+
+// URL for obtaining access tokens
+@property (copy) NSURL *tokenURL;
+
+// Calculated expiration date (expiresIn seconds added to the
+// time the access token was received.)
+@property (copy) NSDate *expirationDate;
+
+// Service identifier, like "Google"; not used for authentication
+//
+// The provider name is just for allowing stored authorization to be associated
+// with the authorizing service.
+@property (copy) NSString *serviceProvider;
+
+// User ID; not used for authentication
+@property (retain) NSString *userID;
+
+// User email and verified status; not used for authentication
+//
+// 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 (retain) NSString *userEmail;
+@property (retain) NSString *userEmailIsVerified;
+
+// Property indicating if this auth has a refresh or access token so is suitable
+// for authorizing a request. This does not guarantee that the token is valid.
+@property (readonly) BOOL canAuthorize;
+
+// Property indicating if this object will authorize plain http request
+// (as well as any non-https requests.) Default is NO, only requests with the
+// scheme https are authorized, since security may be compromised if tokens
+// are sent over the wire using an unencrypted protocol like http.
+@property (assign) BOOL shouldAuthorizeAllRequests;
+
+// 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 (retain) NSDictionary *properties;
+
+// Property for the optional fetcher service instance to be used to create
+// fetchers
+//
+// Fetcher service objects retain authorizations, so this is weak to avoid
+// circular retains.
+@property (assign) id <GTMHTTPFetcherServiceProtocol> fetcherService; // WEAK
+
+// Alternative JSON parsing class; this should implement the
+// GTMOAuth2ParserClass informal protocol. If this property is
+// not set, the class SBJSON must be available in the runtime.
+@property (assign) Class parserClass;
+
+// Key for the response parameter used for the authorization header; by default,
+// "access_token" is used, but some servers may expect alternatives, like
+// "id_token".
+@property (copy) NSString *authorizationTokenKey;
+
+// Convenience method for creating an authentication object
++ (id)authenticationWithServiceProvider:(NSString *)serviceProvider
+ tokenURL:(NSURL *)tokenURL
+ redirectURI:(NSString *)redirectURI
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret;
+
+// Clear out any authentication values, prepare for a new request fetch
+- (void)reset;
+
+// Main authorization entry points
+//
+// These will refresh the access token, if necessary, add the access token to
+// the request, then invoke the callback.
+//
+// The request argument may be nil to just force a refresh of the access token,
+// if needed.
+//
+// NOTE: To avoid accidental leaks of bearer tokens, the request must
+// be for a URL with the scheme https unless the shouldAuthorizeAllRequests
+// property is set.
+
+// The finish selector should have a signature matching
+// - (void)authentication:(GTMOAuth2Authentication *)auth
+// request:(NSMutableURLRequest *)request
+// finishedWithError:(NSError *)error;
+
+- (void)authorizeRequest:(NSMutableURLRequest *)request
+ delegate:(id)delegate
+ didFinishSelector:(SEL)sel;
+
+#if NS_BLOCKS_AVAILABLE
+- (void)authorizeRequest:(NSMutableURLRequest *)request
+ completionHandler:(void (^)(NSError *error))handler;
+#endif
+
+// Synchronous entry point; authorizing this way cannot refresh an expired
+// access token
+- (BOOL)authorizeRequest:(NSMutableURLRequest *)request;
+
+// If the authentication is waiting for a refresh to complete, 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.
+- (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds;
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Internal properties and methods for use by GTMOAuth2SignIn
+//
+
+// Pending fetcher to get a new access token, if any
+@property (retain) GTMHTTPFetcher *refreshFetcher;
+
+// Check if a request is queued up to be authorized
+- (BOOL)isAuthorizingRequest:(NSURLRequest *)request;
+
+// Check if a request appears to be authorized
+- (BOOL)isAuthorizedRequest:(NSURLRequest *)request;
+
+// Stop any pending refresh fetch. This will also cancel the authorization
+// for all fetch requests pending authorization.
+- (void)stopAuthorization;
+
+// Prevents authorization callback for a given request.
+- (void)stopAuthorizationForRequest:(NSURLRequest *)request;
+
+// OAuth fetch user-agent header value
+- (NSString *)userAgent;
+
+// Parse and set token and token secret from response data
+- (void)setKeysForResponseString:(NSString *)str;
+- (void)setKeysForResponseDictionary:(NSDictionary *)dict;
+
+// Persistent token string for keychain storage
+//
+// We'll use the format "refresh_token=foo&serviceProvider=bar" so we can
+// easily alter what portions of the auth data are stored
+//
+// Use these methods for serialization
+- (NSString *)persistenceResponseString;
+- (void)setKeysForPersistenceResponseString:(NSString *)str;
+
+// method to begin fetching an access token, used by the sign-in object
+- (GTMHTTPFetcher *)beginTokenFetchWithDelegate:(id)delegate
+ didFinishSelector:(SEL)finishedSel;
+
+// Entry point to post a notification about a fetcher currently used for
+// obtaining or refreshing a token; the sign-in object will also use this
+// to indicate when the user's email address is being fetched.
+//
+// Fetch type constants are above under "notifications for token fetches"
+- (void)notifyFetchIsRunning:(BOOL)isStarting
+ fetcher:(GTMHTTPFetcher *)fetcher
+ type:(NSString *)fetchType;
+
+// Arbitrary key-value properties retained for the user
+- (void)setProperty:(id)obj forKey:(NSString *)key;
+- (id)propertyForKey:(NSString *)key;
+
+//
+// Utilities
+//
+
++ (NSString *)encodedOAuthValueForString:(NSString *)str;
+
++ (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict;
+
++ (NSDictionary *)dictionaryWithResponseString:(NSString *)responseStr;
+
++ (NSDictionary *)dictionaryWithJSONData:(NSData *)data;
+
++ (NSString *)scopeWithStrings:(NSString *)firsStr, ... NS_REQUIRES_NIL_TERMINATION;
+@end
+
+#endif // GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/GTMOAuth2Authentication.m b/example/common/gtm-oauth2/Source/GTMOAuth2Authentication.m
new file mode 100644
index 00000000..b0c99776
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/GTMOAuth2Authentication.m
@@ -0,0 +1,1275 @@
+/* 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.
+ */
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#define GTMOAUTH2AUTHENTICATION_DEFINE_GLOBALS 1
+#import "GTMOAuth2Authentication.h"
+
+// standard OAuth keys
+static NSString *const kOAuth2AccessTokenKey = @"access_token";
+static NSString *const kOAuth2RefreshTokenKey = @"refresh_token";
+static NSString *const kOAuth2ClientIDKey = @"client_id";
+static NSString *const kOAuth2ClientSecretKey = @"client_secret";
+static NSString *const kOAuth2RedirectURIKey = @"redirect_uri";
+static NSString *const kOAuth2ResponseTypeKey = @"response_type";
+static NSString *const kOAuth2ScopeKey = @"scope";
+static NSString *const kOAuth2ErrorKey = @"error";
+static NSString *const kOAuth2TokenTypeKey = @"token_type";
+static NSString *const kOAuth2ExpiresInKey = @"expires_in";
+static NSString *const kOAuth2CodeKey = @"code";
+static NSString *const kOAuth2AssertionKey = @"assertion";
+static NSString *const kOAuth2RefreshScopeKey = @"refreshScope";
+
+// additional persistent keys
+static NSString *const kServiceProviderKey = @"serviceProvider";
+static NSString *const kUserIDKey = @"userID";
+static NSString *const kUserEmailKey = @"email";
+static NSString *const kUserEmailIsVerifiedKey = @"isVerified";
+
+// fetcher keys
+static NSString *const kTokenFetchDelegateKey = @"delegate";
+static NSString *const kTokenFetchSelectorKey = @"sel";
+
+static NSString *const kRefreshFetchArgsKey = @"requestArgs";
+
+// If GTMNSJSONSerialization is available, it is used for formatting JSON
+#if (TARGET_OS_MAC && !TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED < 1070)) || \
+ (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED < 50000))
+@interface GTMNSJSONSerialization : NSObject
++ (id)JSONObjectWithData:(NSData *)data options:(NSUInteger)opt error:(NSError **)error;
+@end
+#endif
+
+@interface GTMOAuth2ParserClass : NSObject
+// just enough of SBJSON to be able to parse
+- (id)objectWithString:(NSString*)repr error:(NSError**)error;
+@end
+
+// wrapper class for requests needing authorization and their callbacks
+@interface GTMOAuth2AuthorizationArgs : NSObject {
+ @private
+ NSMutableURLRequest *request_;
+ id delegate_;
+ SEL sel_;
+ id completionHandler_;
+ NSThread *thread_;
+ NSError *error_;
+}
+
+@property (retain) NSMutableURLRequest *request;
+@property (retain) id delegate;
+@property (assign) SEL selector;
+@property (copy) id completionHandler;
+@property (retain) NSThread *thread;
+@property (retain) NSError *error;
+
++ (GTMOAuth2AuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req
+ delegate:(id)delegate
+ selector:(SEL)sel
+ completionHandler:(id)completionHandler
+ thread:(NSThread *)thread;
+@end
+
+@implementation GTMOAuth2AuthorizationArgs
+
+@synthesize request = request_,
+ delegate = delegate_,
+ selector = sel_,
+ completionHandler = completionHandler_,
+ thread = thread_,
+ error = error_;
+
++ (GTMOAuth2AuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req
+ delegate:(id)delegate
+ selector:(SEL)sel
+ completionHandler:(id)completionHandler
+ thread:(NSThread *)thread {
+ GTMOAuth2AuthorizationArgs *obj;
+ obj = [[[GTMOAuth2AuthorizationArgs alloc] init] autorelease];
+ obj.request = req;
+ obj.delegate = delegate;
+ obj.selector = sel;
+ obj.completionHandler = completionHandler;
+ obj.thread = thread;
+ return obj;
+}
+
+- (void)dealloc {
+ [request_ release];
+ [delegate_ release];
+ [completionHandler_ release];
+ [thread_ release];
+ [error_ release];
+
+ [super dealloc];
+}
+@end
+
+
+@interface GTMOAuth2Authentication ()
+
+@property (retain) NSMutableArray *authorizationQueue;
+@property (readonly) NSString *authorizationToken;
+
+- (void)setKeysForResponseJSONData:(NSData *)data;
+
+- (BOOL)authorizeRequestArgs:(GTMOAuth2AuthorizationArgs *)args;
+
+- (BOOL)authorizeRequestImmediateArgs:(GTMOAuth2AuthorizationArgs *)args;
+
+- (BOOL)shouldRefreshAccessToken;
+
+- (void)updateExpirationDate;
+
+- (void)tokenFetcher:(GTMHTTPFetcher *)fetcher
+ finishedWithData:(NSData *)data
+ error:(NSError *)error;
+
+- (void)auth:(GTMOAuth2Authentication *)auth
+finishedRefreshWithFetcher:(GTMHTTPFetcher *)fetcher
+ error:(NSError *)error;
+
+- (void)invokeCallbackArgs:(GTMOAuth2AuthorizationArgs *)args;
+
++ (void)invokeDelegate:(id)delegate
+ selector:(SEL)sel
+ object:(id)obj1
+ object:(id)obj2
+ object:(id)obj3;
+
++ (NSString *)unencodedOAuthParameterForString:(NSString *)str;
++ (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict;
+
++ (NSDictionary *)dictionaryWithResponseData:(NSData *)data;
+
+@end
+
+@implementation GTMOAuth2Authentication
+
+@synthesize clientID = clientID_,
+ clientSecret = clientSecret_,
+ redirectURI = redirectURI_,
+ parameters = parameters_,
+ authorizationTokenKey = authorizationTokenKey_,
+ tokenURL = tokenURL_,
+ expirationDate = expirationDate_,
+ additionalTokenRequestParameters = additionalTokenRequestParameters_,
+ additionalGrantTypeRequestParameters = additionalGrantTypeRequestParameters_,
+ refreshFetcher = refreshFetcher_,
+ fetcherService = fetcherService_,
+ parserClass = parserClass_,
+ shouldAuthorizeAllRequests = shouldAuthorizeAllRequests_,
+ userData = userData_,
+ properties = properties_,
+ authorizationQueue = authorizationQueue_;
+
+// Response parameters
+@dynamic accessToken,
+ refreshToken,
+ code,
+ assertion,
+ refreshScope,
+ errorString,
+ tokenType,
+ scope,
+ expiresIn,
+ serviceProvider,
+ userEmail,
+ userEmailIsVerified;
+
+@dynamic canAuthorize;
+
++ (id)authenticationWithServiceProvider:(NSString *)serviceProvider
+ tokenURL:(NSURL *)tokenURL
+ redirectURI:(NSString *)redirectURI
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret {
+ GTMOAuth2Authentication *obj = [[[self alloc] init] autorelease];
+ obj.serviceProvider = serviceProvider;
+ obj.tokenURL = tokenURL;
+ obj.redirectURI = redirectURI;
+ obj.clientID = clientID;
+ obj.clientSecret = clientSecret;
+ return obj;
+}
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ authorizationQueue_ = [[NSMutableArray alloc] init];
+ parameters_ = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (NSString *)description {
+ NSArray *props = [NSArray arrayWithObjects:@"accessToken", @"refreshToken",
+ @"code", @"assertion", @"expirationDate", @"errorString",
+ nil];
+ NSMutableString *valuesStr = [NSMutableString string];
+ NSString *separator = @"";
+ for (NSString *prop in props) {
+ id result = [self valueForKey:prop];
+ if (result) {
+ [valuesStr appendFormat:@"%@%@=\"%@\"", separator, prop, result];
+ separator = @", ";
+ }
+ }
+
+ return [NSString stringWithFormat:@"%@ %p: {%@}",
+ [self class], self, valuesStr];
+}
+
+- (void)dealloc {
+ [clientID_ release];
+ [clientSecret_ release];
+ [redirectURI_ release];
+ [parameters_ release];
+ [authorizationTokenKey_ release];
+ [tokenURL_ release];
+ [expirationDate_ release];
+ [additionalTokenRequestParameters_ release];
+ [additionalGrantTypeRequestParameters_ release];
+ [refreshFetcher_ release];
+ [authorizationQueue_ release];
+ [userData_ release];
+ [properties_ release];
+
+ [super dealloc];
+}
+
+#pragma mark -
+
+- (void)setKeysForResponseDictionary:(NSDictionary *)dict {
+ if (dict == nil) return;
+
+ // If a new code or access token is being set, remove the old expiration
+ NSString *newCode = [dict objectForKey:kOAuth2CodeKey];
+ NSString *newAccessToken = [dict objectForKey:kOAuth2AccessTokenKey];
+ if (newCode || newAccessToken) {
+ self.expiresIn = nil;
+ }
+
+ BOOL didRefreshTokenChange = NO;
+ NSString *refreshToken = [dict objectForKey:kOAuth2RefreshTokenKey];
+ if (refreshToken) {
+ NSString *priorRefreshToken = self.refreshToken;
+
+ if (priorRefreshToken != refreshToken
+ && (priorRefreshToken == nil
+ || ![priorRefreshToken isEqual:refreshToken])) {
+ didRefreshTokenChange = YES;
+ }
+ }
+
+ [self.parameters addEntriesFromDictionary:dict];
+ [self updateExpirationDate];
+
+ if (didRefreshTokenChange) {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:kGTMOAuth2RefreshTokenChanged
+ object:self
+ userInfo:nil];
+ }
+ // NSLog(@"keys set ----------------------------\n%@", dict);
+}
+
+- (void)setKeysForResponseString:(NSString *)str {
+ NSDictionary *dict = [[self class] dictionaryWithResponseString:str];
+ [self setKeysForResponseDictionary:dict];
+}
+
+- (void)setKeysForResponseJSONData:(NSData *)data {
+ NSDictionary *dict = [[self class] dictionaryWithJSONData:data];
+ [self setKeysForResponseDictionary:dict];
+}
+
++ (NSDictionary *)dictionaryWithJSONData:(NSData *)data {
+ NSMutableDictionary *obj = nil;
+ NSError *error = nil;
+
+ Class serializer = NSClassFromString(@"NSJSONSerialization");
+ if (serializer) {
+ const NSUInteger kOpts = (1UL << 0); // NSJSONReadingMutableContainers
+ obj = [serializer JSONObjectWithData:data
+ options:kOpts
+ error:&error];
+#if DEBUG
+ if (error) {
+ NSString *str = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding] autorelease];
+ NSLog(@"NSJSONSerialization error %@ parsing %@",
+ error, str);
+ }
+#endif
+ return obj;
+ } else {
+ // try SBJsonParser or SBJSON
+ Class jsonParseClass = NSClassFromString(@"SBJsonParser");
+ if (!jsonParseClass) {
+ jsonParseClass = NSClassFromString(@"SBJSON");
+ }
+ if (jsonParseClass) {
+ GTMOAuth2ParserClass *parser = [[[jsonParseClass alloc] init] autorelease];
+ NSString *jsonStr = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding] autorelease];
+ if (jsonStr) {
+ obj = [parser objectWithString:jsonStr error:&error];
+#if DEBUG
+ if (error) {
+ NSLog(@"%@ error %@ parsing %@", NSStringFromClass(jsonParseClass),
+ error, jsonStr);
+ }
+#endif
+ return obj;
+ }
+ } else {
+#if DEBUG
+ NSAssert(0, @"GTMOAuth2Authentication: No parser available");
+#endif
+ }
+ }
+ return nil;
+}
+
+#pragma mark Authorizing Requests
+
+// General entry point for authorizing requests
+
+#if NS_BLOCKS_AVAILABLE
+// Authorizing with a completion block
+- (void)authorizeRequest:(NSMutableURLRequest *)request
+ completionHandler:(void (^)(NSError *error))handler {
+
+ GTMOAuth2AuthorizationArgs *args;
+ args = [GTMOAuth2AuthorizationArgs argsWithRequest:request
+ delegate:nil
+ selector:NULL
+ completionHandler:handler
+ thread:[NSThread currentThread]];
+ [self authorizeRequestArgs:args];
+}
+#endif
+
+// Authorizing with a callback selector
+//
+// Selector has the signature
+// - (void)authentication:(GTMOAuth2Authentication *)auth
+// request:(NSMutableURLRequest *)request
+// finishedWithError:(NSError *)error;
+- (void)authorizeRequest:(NSMutableURLRequest *)request
+ delegate:(id)delegate
+ didFinishSelector:(SEL)sel {
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, sel,
+ @encode(GTMOAuth2Authentication *),
+ @encode(NSMutableURLRequest *),
+ @encode(NSError *), 0);
+
+ GTMOAuth2AuthorizationArgs *args;
+ args = [GTMOAuth2AuthorizationArgs argsWithRequest:request
+ delegate:delegate
+ selector:sel
+ completionHandler:nil
+ thread:[NSThread currentThread]];
+ [self authorizeRequestArgs:args];
+}
+
+// Internal routine common to delegate and block invocations
+- (BOOL)authorizeRequestArgs:(GTMOAuth2AuthorizationArgs *)args {
+ BOOL didAttempt = NO;
+
+ @synchronized(authorizationQueue_) {
+
+ BOOL shouldRefresh = [self shouldRefreshAccessToken];
+
+ if (shouldRefresh) {
+ // attempt to refresh now; once we have a fresh access token, we will
+ // authorize the request and call back to the user
+ didAttempt = YES;
+
+ if (self.refreshFetcher == nil) {
+ // there's not already a refresh pending
+ SEL finishedSel = @selector(auth:finishedRefreshWithFetcher:error:);
+ self.refreshFetcher = [self beginTokenFetchWithDelegate:self
+ didFinishSelector:finishedSel];
+ if (self.refreshFetcher) {
+ [authorizationQueue_ addObject:args];
+ }
+ } else {
+ // there's already a refresh pending
+ [authorizationQueue_ addObject:args];
+ }
+ }
+
+ if (!shouldRefresh || self.refreshFetcher == nil) {
+ // we're not fetching a new access token, so we can authorize the request
+ // now
+ didAttempt = [self authorizeRequestImmediateArgs:args];
+ }
+ }
+ return didAttempt;
+}
+
+- (void)auth:(GTMOAuth2Authentication *)auth
+finishedRefreshWithFetcher:(GTMHTTPFetcher *)fetcher
+ error:(NSError *)error {
+ @synchronized(authorizationQueue_) {
+ // If there's an error, we want to try using the old access token anyway,
+ // in case it's a backend problem preventing refresh, in which case
+ // access tokens past their expiration date may still work
+
+ self.refreshFetcher = nil;
+
+ // Swap in a new auth queue in case the callbacks try to immediately auth
+ // another request
+ NSArray *pendingAuthQueue = [NSArray arrayWithArray:authorizationQueue_];
+ [authorizationQueue_ removeAllObjects];
+
+ BOOL hasAccessToken = ([self.accessToken length] > 0);
+
+ NSString *noteName;
+ NSDictionary *userInfo = nil;
+ if (hasAccessToken && error == nil) {
+ // Successful refresh.
+ noteName = kGTMOAuth2AccessTokenRefreshed;
+ userInfo = nil;
+ } else {
+ // Google's OAuth 2 implementation returns a 400 with JSON body
+ // containing error key "invalid_grant" to indicate the refresh token
+ // is invalid or has been revoked by the user. We'll promote the
+ // JSON error key's value for easy inspection by the observer.
+ noteName = kGTMOAuth2AccessTokenRefreshFailed;
+ NSString *jsonErr = nil;
+ if ([error code] == kGTMHTTPFetcherStatusBadRequest) {
+ NSDictionary *json = [[error userInfo] objectForKey:kGTMOAuth2ErrorJSONKey];
+ jsonErr = [json objectForKey:kGTMOAuth2ErrorMessageKey];
+ }
+ // error and jsonErr may be nil
+ userInfo = [NSMutableDictionary dictionary];
+ [userInfo setValue:error forKey:kGTMOAuth2ErrorObjectKey];
+ [userInfo setValue:jsonErr forKey:kGTMOAuth2ErrorMessageKey];
+ }
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:noteName
+ object:self
+ userInfo:userInfo];
+
+ for (GTMOAuth2AuthorizationArgs *args in pendingAuthQueue) {
+ if (!hasAccessToken && args.error == nil) {
+ args.error = error;
+ }
+
+ [self authorizeRequestImmediateArgs:args];
+ }
+ }
+}
+
+- (BOOL)isAuthorizingRequest:(NSURLRequest *)request {
+ BOOL wasFound = NO;
+ @synchronized(authorizationQueue_) {
+ for (GTMOAuth2AuthorizationArgs *args in authorizationQueue_) {
+ if ([args request] == request) {
+ wasFound = YES;
+ break;
+ }
+ }
+ }
+ return wasFound;
+}
+
+- (BOOL)isAuthorizedRequest:(NSURLRequest *)request {
+ NSString *authStr = [request valueForHTTPHeaderField:@"Authorization"];
+ return ([authStr length] > 0);
+}
+
+- (void)stopAuthorization {
+ @synchronized(authorizationQueue_) {
+ [authorizationQueue_ removeAllObjects];
+
+ [self.refreshFetcher stopFetching];
+ self.refreshFetcher = nil;
+ }
+}
+
+- (void)stopAuthorizationForRequest:(NSURLRequest *)request {
+ @synchronized(authorizationQueue_) {
+ NSUInteger argIndex = 0;
+ BOOL found = NO;
+ for (GTMOAuth2AuthorizationArgs *args in authorizationQueue_) {
+ if ([args request] == request) {
+ found = YES;
+ break;
+ }
+ argIndex++;
+ }
+
+ if (found) {
+ [authorizationQueue_ removeObjectAtIndex:argIndex];
+
+ // If the queue is now empty, go ahead and stop the fetcher.
+ if ([authorizationQueue_ count] == 0) {
+ [self stopAuthorization];
+ }
+ }
+ }
+}
+
+- (BOOL)authorizeRequestImmediateArgs:(GTMOAuth2AuthorizationArgs *)args {
+ // This authorization entry point never attempts to refresh the access token,
+ // but does call the completion routine
+
+ NSMutableURLRequest *request = args.request;
+
+ NSString *scheme = [[request URL] scheme];
+ BOOL isAuthorizableRequest = self.shouldAuthorizeAllRequests
+ || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame;
+ if (!isAuthorizableRequest) {
+ // Request is not https, so may be insecure
+ //
+ // The NSError will be created below
+#if DEBUG
+ NSLog(@"Cannot authorize request with scheme %@ (%@)", scheme, request);
+#endif
+ }
+
+ // Get the access token.
+ NSString *accessToken = self.authorizationToken;
+ if (isAuthorizableRequest && [accessToken length] > 0) {
+ if (request) {
+ // we have a likely valid access token
+ NSString *value = [NSString stringWithFormat:@"%s %@",
+ GTM_OAUTH2_BEARER, accessToken];
+ [request setValue:value forHTTPHeaderField:@"Authorization"];
+ }
+
+ // We've authorized the request, even if the previous refresh
+ // failed with an error
+ args.error = nil;
+ } else if (args.error == nil) {
+ NSDictionary *userInfo = nil;
+ if (request) {
+ userInfo = [NSDictionary dictionaryWithObject:request
+ forKey:kGTMOAuth2ErrorRequestKey];
+ }
+ NSInteger code = (isAuthorizableRequest ?
+ kGTMOAuth2ErrorAuthorizationFailed :
+ kGTMOAuth2ErrorUnauthorizableRequest);
+ args.error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain
+ code:code
+ userInfo:userInfo];
+ }
+
+ // Invoke any callbacks on the proper thread
+ if (args.delegate || args.completionHandler) {
+ NSThread *targetThread = args.thread;
+ BOOL isSameThread = [targetThread isEqual:[NSThread currentThread]];
+
+ if (isSameThread) {
+ [self invokeCallbackArgs:args];
+ } else {
+ SEL sel = @selector(invokeCallbackArgs:);
+ NSOperationQueue *delegateQueue = self.fetcherService.delegateQueue;
+ if (delegateQueue) {
+ NSInvocationOperation *op;
+ op = [[[NSInvocationOperation alloc] initWithTarget:self
+ selector:sel
+ object:args] autorelease];
+ [delegateQueue addOperation:op];
+ } else {
+ [self performSelector:sel
+ onThread:targetThread
+ withObject:args
+ waitUntilDone:NO];
+ }
+ }
+ }
+
+ BOOL didAuth = (args.error == nil);
+ return didAuth;
+}
+
+- (void)invokeCallbackArgs:(GTMOAuth2AuthorizationArgs *)args {
+ // Invoke the callbacks
+ NSError *error = args.error;
+
+ id delegate = args.delegate;
+ SEL sel = args.selector;
+ if (delegate && sel) {
+ NSMutableURLRequest *request = args.request;
+
+ 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];
+ }
+
+#if NS_BLOCKS_AVAILABLE
+ id handler = args.completionHandler;
+ if (handler) {
+ void (^authCompletionBlock)(NSError *) = handler;
+ authCompletionBlock(error);
+ }
+#endif
+}
+
+- (BOOL)authorizeRequest:(NSMutableURLRequest *)request {
+ // Entry point for synchronous authorization mechanisms
+ GTMOAuth2AuthorizationArgs *args;
+ args = [GTMOAuth2AuthorizationArgs argsWithRequest:request
+ delegate:nil
+ selector:NULL
+ completionHandler:nil
+ thread:[NSThread currentThread]];
+ return [self authorizeRequestImmediateArgs:args];
+}
+
+- (BOOL)canAuthorize {
+ NSString *token = self.refreshToken;
+ if (token == nil) {
+ // For services which do not support refresh tokens, we'll just check
+ // the access token.
+ token = self.authorizationToken;
+ }
+ BOOL canAuth = [token length] > 0;
+ return canAuth;
+}
+
+- (BOOL)shouldRefreshAccessToken {
+ // We should refresh the access token when it's missing or nearly expired
+ // and we have a refresh token
+ BOOL shouldRefresh = NO;
+ NSString *accessToken = self.accessToken;
+ NSString *refreshToken = self.refreshToken;
+ NSString *assertion = self.assertion;
+ NSString *code = self.code;
+
+ BOOL hasRefreshToken = ([refreshToken length] > 0);
+ BOOL hasAccessToken = ([accessToken length] > 0);
+ BOOL hasAssertion = ([assertion length] > 0);
+ BOOL hasCode = ([code length] > 0);
+
+ // Determine if we need to refresh the access token
+ if (hasRefreshToken || hasAssertion || hasCode) {
+ if (!hasAccessToken) {
+ shouldRefresh = YES;
+ } else {
+ // We'll consider the token expired if it expires 60 seconds from now
+ // or earlier
+ NSDate *expirationDate = self.expirationDate;
+ NSTimeInterval timeToExpire = [expirationDate timeIntervalSinceNow];
+ if (expirationDate == nil || timeToExpire < 60.0) {
+ // access token has expired, or will in a few seconds
+ shouldRefresh = YES;
+ }
+ }
+ }
+ return shouldRefresh;
+}
+
+- (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds {
+ // If there is a refresh fetcher pending, wait for it.
+ //
+ // This is only intended for unit test or for use in command-line tools.
+ GTMHTTPFetcher *fetcher = self.refreshFetcher;
+ [fetcher waitForCompletionWithTimeout:timeoutInSeconds];
+}
+
+#pragma mark Token Fetch
+
+- (NSString *)userAgent {
+ NSBundle *bundle = [NSBundle mainBundle];
+ NSString *appID = [bundle bundleIdentifier];
+
+ NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+ if (version == nil) {
+ version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
+ }
+
+ if (appID && version) {
+ appID = [appID stringByAppendingFormat:@"/%@", version];
+ }
+
+ NSString *userAgent = @"gtm-oauth2";
+ if (appID) {
+ userAgent = [userAgent stringByAppendingFormat:@" %@", appID];
+ }
+ return userAgent;
+}
+
+- (GTMHTTPFetcher *)beginTokenFetchWithDelegate:(id)delegate
+ didFinishSelector:(SEL)finishedSel {
+
+ NSMutableDictionary *paramsDict = [NSMutableDictionary dictionary];
+
+ NSString *fetchType;
+
+ NSString *refreshToken = self.refreshToken;
+ NSString *code = self.code;
+ NSString *assertion = self.assertion;
+ NSString *grantType = nil;
+
+ if (refreshToken) {
+ // We have a refresh token
+ grantType = @"refresh_token";
+ [paramsDict setObject:refreshToken forKey:@"refresh_token"];
+
+ NSString *refreshScope = self.refreshScope;
+ if ([refreshScope length] > 0) {
+ [paramsDict setObject:refreshScope forKey:@"scope"];
+ }
+
+ fetchType = kGTMOAuth2FetchTypeRefresh;
+ } else if (code) {
+ // We have a code string
+ grantType = @"authorization_code";
+ [paramsDict setObject:code forKey:@"code"];
+
+ NSString *redirectURI = self.redirectURI;
+ if ([redirectURI length] > 0) {
+ [paramsDict setObject:redirectURI forKey:@"redirect_uri"];
+ }
+
+ NSString *scope = self.scope;
+ if ([scope length] > 0) {
+ [paramsDict setObject:scope forKey:@"scope"];
+ }
+
+ fetchType = kGTMOAuth2FetchTypeToken;
+ } else if (assertion) {
+ // We have an assertion string
+ grantType = @"http://oauth.net/grant_type/jwt/1.0/bearer";
+ [paramsDict setObject:assertion forKey:@"assertion"];
+ fetchType = kGTMOAuth2FetchTypeAssertion;
+ } else {
+#if DEBUG
+ NSAssert(0, @"unexpected lack of code or refresh token for fetching");
+#endif
+ return nil;
+ }
+ [paramsDict setObject:grantType forKey:@"grant_type"];
+
+ NSString *clientID = self.clientID;
+ if ([clientID length] > 0) {
+ [paramsDict setObject:clientID forKey:@"client_id"];
+ }
+
+ NSString *clientSecret = self.clientSecret;
+ if ([clientSecret length] > 0) {
+ [paramsDict setObject:clientSecret forKey:@"client_secret"];
+ }
+
+ NSDictionary *additionalParams = self.additionalTokenRequestParameters;
+ if (additionalParams) {
+ [paramsDict addEntriesFromDictionary:additionalParams];
+ }
+ NSDictionary *grantTypeParams =
+ [self.additionalGrantTypeRequestParameters objectForKey:grantType];
+ if (grantTypeParams) {
+ [paramsDict addEntriesFromDictionary:grantTypeParams];
+ }
+
+ NSString *paramStr = [[self class] encodedQueryParametersForDictionary:paramsDict];
+ NSData *paramData = [paramStr dataUsingEncoding:NSUTF8StringEncoding];
+
+ NSURL *tokenURL = self.tokenURL;
+
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:tokenURL];
+ [request setValue:@"application/x-www-form-urlencoded"
+ forHTTPHeaderField:@"Content-Type"];
+
+ NSString *userAgent = [self userAgent];
+ [request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
+
+ GTMHTTPFetcher *fetcher;
+ id <GTMHTTPFetcherServiceProtocol> fetcherService = self.fetcherService;
+ if (fetcherService) {
+ fetcher = [fetcherService fetcherWithRequest:request];
+
+ // Don't use an authorizer for an auth token fetch
+ fetcher.authorizer = nil;
+ } else {
+ fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
+ }
+
+ NSString *const template = (refreshToken ? @"refresh token for %@ %@" : @"fetch tokens for %@ %@");
+ [fetcher setCommentWithFormat:template, [tokenURL host], [self userEmail]];
+ fetcher.postData = paramData;
+ fetcher.retryEnabled = YES;
+ fetcher.maxRetryInterval = 15.0;
+
+ // Fetcher properties will retain the delegate
+ [fetcher setProperty:delegate forKey:kTokenFetchDelegateKey];
+ if (finishedSel) {
+ NSString *selStr = NSStringFromSelector(finishedSel);
+ [fetcher setProperty:selStr forKey:kTokenFetchSelectorKey];
+ }
+
+ if ([fetcher beginFetchWithDelegate:self
+ didFinishSelector:@selector(tokenFetcher:finishedWithData:error:)]) {
+ // Fetch began
+ [self notifyFetchIsRunning:YES fetcher:fetcher type:fetchType];
+ return fetcher;
+ } else {
+ // Failed to start fetching; typically a URL issue
+ NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
+ code:-1
+ userInfo:nil];
+ [[self class] invokeDelegate:delegate
+ selector:finishedSel
+ object:self
+ object:nil
+ object:error];
+ return nil;
+ }
+}
+
+- (void)tokenFetcher:(GTMHTTPFetcher *)fetcher
+ finishedWithData:(NSData *)data
+ error:(NSError *)error {
+ [self notifyFetchIsRunning:NO fetcher:fetcher type:nil];
+
+ NSDictionary *responseHeaders = [fetcher responseHeaders];
+ NSString *responseType = [responseHeaders valueForKey:@"Content-Type"];
+ BOOL isResponseJSON = [responseType hasPrefix:@"application/json"];
+ BOOL hasData = ([data length] > 0);
+
+ if (error) {
+ // Failed. If the error body is JSON, parse it and add it to the error's
+ // userInfo dictionary.
+ if (hasData) {
+ if (isResponseJSON) {
+ NSDictionary *errorJson = [[self class] dictionaryWithJSONData:data];
+ if ([errorJson count] > 0) {
+#if DEBUG
+ NSLog(@"Error %@\nError data:\n%@", error, errorJson);
+#endif
+ // Add the JSON error body to the userInfo of the error
+ NSMutableDictionary *userInfo;
+ userInfo = [NSMutableDictionary dictionaryWithObject:errorJson
+ forKey:kGTMOAuth2ErrorJSONKey];
+ NSDictionary *prevUserInfo = [error userInfo];
+ if (prevUserInfo) {
+ [userInfo addEntriesFromDictionary:prevUserInfo];
+ }
+ error = [NSError errorWithDomain:[error domain]
+ code:[error code]
+ userInfo:userInfo];
+ }
+ }
+ }
+ } else {
+ // Succeeded; we have the requested token.
+#if DEBUG
+ NSAssert(hasData, @"data missing in token response");
+#endif
+
+ if (hasData) {
+ if (isResponseJSON) {
+ [self setKeysForResponseJSONData:data];
+ } else {
+ // Support for legacy token servers that return form-urlencoded data
+ NSString *dataStr = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding] autorelease];
+ [self setKeysForResponseString:dataStr];
+ }
+
+#if DEBUG
+ // Watch for token exchanges that return a non-bearer or unlabeled token
+ NSString *tokenType = [self tokenType];
+ if (tokenType == nil
+ || [tokenType caseInsensitiveCompare:@"bearer"] != NSOrderedSame) {
+ NSLog(@"GTMOAuth2: Unexpected token type: %@", tokenType);
+ }
+#endif
+ }
+ }
+
+ id delegate = [fetcher propertyForKey:kTokenFetchDelegateKey];
+ SEL sel = NULL;
+ NSString *selStr = [fetcher propertyForKey:kTokenFetchSelectorKey];
+ if (selStr) sel = NSSelectorFromString(selStr);
+
+ [[self class] invokeDelegate:delegate
+ selector:sel
+ object:self
+ object:fetcher
+ object:error];
+
+ // Prevent a circular reference from retaining the delegate
+ [fetcher setProperty:nil forKey:kTokenFetchDelegateKey];
+}
+
+#pragma mark Fetch Notifications
+
+- (void)notifyFetchIsRunning:(BOOL)isStarting
+ fetcher:(GTMHTTPFetcher *)fetcher
+ type:(NSString *)fetchType {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+
+ NSString *name = (isStarting ? kGTMOAuth2FetchStarted : kGTMOAuth2FetchStopped);
+ NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
+ fetcher, kGTMOAuth2FetcherKey,
+ fetchType, kGTMOAuth2FetchTypeKey, // fetchType may be nil
+ nil];
+ [nc postNotificationName:name
+ object:self
+ userInfo:dict];
+}
+
+#pragma mark Persistent Response Strings
+
+- (void)setKeysForPersistenceResponseString:(NSString *)str {
+ // All persistence keys can be set directly as if returned by a server
+ [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 {
+ NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:4];
+
+ NSString *refreshToken = self.refreshToken;
+ NSString *accessToken = nil;
+ if (refreshToken == nil) {
+ // We store the access token only for services that do not support refresh
+ // tokens; otherwise, we assume the access token is too perishable to
+ // be worth storing
+ accessToken = self.accessToken;
+ }
+
+ // Any nil values will not set a dictionary entry
+ [dict setValue:refreshToken forKey:kOAuth2RefreshTokenKey];
+ [dict setValue:accessToken forKey:kOAuth2AccessTokenKey];
+ [dict setValue:self.serviceProvider forKey:kServiceProviderKey];
+ [dict setValue:self.userID forKey:kUserIDKey];
+ [dict setValue:self.userEmail forKey:kUserEmailKey];
+ [dict setValue:self.userEmailIsVerified forKey:kUserEmailIsVerifiedKey];
+ [dict setValue:self.scope forKey:kOAuth2ScopeKey];
+
+ NSString *result = [[self class] encodedQueryParametersForDictionary:dict];
+ return result;
+}
+
+- (BOOL)primeForRefresh {
+ if (self.refreshToken == nil) {
+ // Cannot refresh without a refresh token
+ return NO;
+ }
+ self.accessToken = nil;
+ self.expiresIn = nil;
+ self.expirationDate = nil;
+ self.errorString = nil;
+ return YES;
+}
+
+- (void)reset {
+ // Reset all per-authorization values
+ self.code = nil;
+ self.accessToken = nil;
+ self.refreshToken = nil;
+ self.assertion = nil;
+ self.expiresIn = nil;
+ self.errorString = nil;
+ self.expirationDate = nil;
+ self.userEmail = nil;
+ self.userEmailIsVerified = nil;
+ self.authorizationTokenKey = nil;
+}
+
+#pragma mark Accessors for Response Parameters
+
+- (NSString *)authorizationToken {
+ // The token used for authorization is typically the access token unless
+ // the user has specified that an alternative parameter be used.
+ NSString *authorizationToken;
+ NSString *authTokenKey = self.authorizationTokenKey;
+ if (authTokenKey != nil) {
+ authorizationToken = [self.parameters objectForKey:authTokenKey];
+ } else {
+ authorizationToken = self.accessToken;
+ }
+ return authorizationToken;
+}
+
+- (NSString *)accessToken {
+ return [self.parameters objectForKey:kOAuth2AccessTokenKey];
+}
+
+- (void)setAccessToken:(NSString *)str {
+ [self.parameters setValue:str forKey:kOAuth2AccessTokenKey];
+}
+
+- (NSString *)refreshToken {
+ return [self.parameters objectForKey:kOAuth2RefreshTokenKey];
+}
+
+- (void)setRefreshToken:(NSString *)str {
+ [self.parameters setValue:str forKey:kOAuth2RefreshTokenKey];
+}
+
+- (NSString *)code {
+ return [self.parameters objectForKey:kOAuth2CodeKey];
+}
+
+- (void)setCode:(NSString *)str {
+ [self.parameters setValue:str forKey:kOAuth2CodeKey];
+}
+
+- (NSString *)assertion {
+ return [self.parameters objectForKey:kOAuth2AssertionKey];
+}
+
+- (void)setAssertion:(NSString *)str {
+ [self.parameters setValue:str forKey:kOAuth2AssertionKey];
+}
+
+- (NSString *)refreshScope {
+ return [self.parameters objectForKey:kOAuth2RefreshScopeKey];
+}
+
+- (void)setRefreshScope:(NSString *)str {
+ [self.parameters setValue:str forKey:kOAuth2RefreshScopeKey];
+}
+
+- (NSString *)errorString {
+ return [self.parameters objectForKey:kOAuth2ErrorKey];
+}
+
+- (void)setErrorString:(NSString *)str {
+ [self.parameters setValue:str forKey:kOAuth2ErrorKey];
+}
+
+- (NSString *)tokenType {
+ return [self.parameters objectForKey:kOAuth2TokenTypeKey];
+}
+
+- (void)setTokenType:(NSString *)str {
+ [self.parameters setValue:str forKey:kOAuth2TokenTypeKey];
+}
+
+- (NSString *)scope {
+ return [self.parameters objectForKey:kOAuth2ScopeKey];
+}
+
+- (void)setScope:(NSString *)str {
+ [self.parameters setValue:str forKey:kOAuth2ScopeKey];
+}
+
+- (NSNumber *)expiresIn {
+ return [self.parameters objectForKey:kOAuth2ExpiresInKey];
+}
+
+- (void)setExpiresIn:(NSNumber *)num {
+ [self.parameters setValue:num forKey:kOAuth2ExpiresInKey];
+ [self updateExpirationDate];
+}
+
+- (void)updateExpirationDate {
+ // Update our absolute expiration time to something close to when
+ // the server expects the expiration
+ NSDate *date = nil;
+ NSNumber *expiresIn = self.expiresIn;
+ if (expiresIn) {
+ unsigned long deltaSeconds = [expiresIn unsignedLongValue];
+ if (deltaSeconds > 0) {
+ date = [NSDate dateWithTimeIntervalSinceNow:deltaSeconds];
+ }
+ }
+ self.expirationDate = date;
+}
+
+//
+// Keys custom to this class, not part of OAuth 2
+//
+
+- (NSString *)serviceProvider {
+ return [self.parameters objectForKey:kServiceProviderKey];
+}
+
+- (void)setServiceProvider:(NSString *)str {
+ [self.parameters setValue:str forKey:kServiceProviderKey];
+}
+
+- (NSString *)userID {
+ return [self.parameters objectForKey:kUserIDKey];
+}
+
+- (void)setUserID:(NSString *)str {
+ [self.parameters setValue:str forKey:kUserIDKey];
+}
+
+- (NSString *)userEmail {
+ return [self.parameters objectForKey:kUserEmailKey];
+}
+
+- (void)setUserEmail:(NSString *)str {
+ [self.parameters setValue:str forKey:kUserEmailKey];
+}
+
+- (NSString *)userEmailIsVerified {
+ return [self.parameters objectForKey:kUserEmailIsVerifiedKey];
+}
+
+- (void)setUserEmailIsVerified:(NSString *)str {
+ [self.parameters setValue:str forKey:kUserEmailIsVerifiedKey];
+}
+
+#pragma mark User Properties
+
+- (void)setProperty:(id)obj forKey:(NSString *)key {
+ if (obj == nil) {
+ // User passed in nil, so delete the property
+ [properties_ removeObjectForKey:key];
+ } else {
+ // Be sure the property dictionary exists
+ if (properties_ == nil) {
+ [self setProperties:[NSMutableDictionary dictionary]];
+ }
+ [properties_ setObject:obj forKey:key];
+ }
+}
+
+- (id)propertyForKey:(NSString *)key {
+ id obj = [properties_ objectForKey:key];
+
+ // Be sure the returned pointer has the life of the autorelease pool,
+ // in case self is released immediately
+ return [[obj retain] autorelease];
+}
+
+#pragma mark Utility Routines
+
++ (NSString *)encodedOAuthValueForString:(NSString *)str {
+ CFStringRef originalString = (CFStringRef) str;
+ CFStringRef leaveUnescaped = NULL;
+ CFStringRef forceEscaped = CFSTR("!*'();:@&=+$,/?%#[]");
+
+ CFStringRef escapedStr = NULL;
+ if (str) {
+ escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
+ originalString,
+ leaveUnescaped,
+ forceEscaped,
+ kCFStringEncodingUTF8);
+ [(id)CFMakeCollectable(escapedStr) autorelease];
+ }
+
+ return (NSString *)escapedStr;
+}
+
++ (NSString *)encodedQueryParametersForDictionary:(NSDictionary *)dict {
+ // Make a string like "cat=fluffy@dog=spot"
+ NSMutableString *result = [NSMutableString string];
+ NSArray *sortedKeys = [[dict allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
+ NSString *joiner = @"";
+ for (NSString *key in sortedKeys) {
+ NSString *value = [dict objectForKey:key];
+ NSString *encodedValue = [self encodedOAuthValueForString:value];
+ NSString *encodedKey = [self encodedOAuthValueForString:key];
+ [result appendFormat:@"%@%@=%@", joiner, encodedKey, encodedValue];
+ joiner = @"&";
+ }
+ return result;
+}
+
++ (void)invokeDelegate:(id)delegate
+ selector:(SEL)sel
+ object:(id)obj1
+ object:(id)obj2
+ object:(id)obj3 {
+ if (delegate && sel) {
+ NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:delegate];
+ [invocation setArgument:&obj1 atIndex:2];
+ [invocation setArgument:&obj2 atIndex:3];
+ [invocation setArgument:&obj3 atIndex:4];
+ [invocation invoke];
+ }
+}
+
++ (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
+ // "cat=fluffy&dog=spot". Missing or empty values are considered
+ // empty strings; keys and values are percent-decoded.
+ if (responseStr == nil) return nil;
+
+ NSArray *items = [responseStr componentsSeparatedByString:@"&"];
+
+ NSMutableDictionary *responseDict = [NSMutableDictionary dictionaryWithCapacity:[items count]];
+
+ for (NSString *item in items) {
+ NSString *key = nil;
+ NSString *value = @"";
+
+ NSRange equalsRange = [item rangeOfString:@"="];
+ if (equalsRange.location != NSNotFound) {
+ // The parameter has at least one '='
+ key = [item substringToIndex:equalsRange.location];
+
+ // There are characters after the '='
+ value = [item substringFromIndex:(equalsRange.location + 1)];
+ } else {
+ // The parameter has no '='
+ key = item;
+ }
+
+ 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;
+}
+
+@end
+
+#endif // GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/GTMOAuth2SignIn.h b/example/common/gtm-oauth2/Source/GTMOAuth2SignIn.h
new file mode 100644
index 00000000..ded279bd
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/GTMOAuth2SignIn.h
@@ -0,0 +1,187 @@
+/* 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.
+ */
+
+//
+// This sign-in object opens and closes the web view window as needed for
+// users to sign in. For signing in to Google, it also obtains
+// the authenticated user's email address.
+//
+// Typically, this will be managed for the application by
+// GTMOAuth2ViewControllerTouch or GTMOAuth2WindowController, so this
+// class's interface is interesting only if
+// you are creating your own window controller for sign-in.
+//
+//
+// Delegate methods implemented by the window controller
+//
+// 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:(GTMOAuth2SignIn *)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:(GTMOAuth2SignIn *)signin finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error
+//
+// The finished selector will be invoked when sign-in has completed, except
+// when explicitly canceled by calling cancelSigningIn
+//
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#import <Foundation/Foundation.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+
+// GTMHTTPFetcher brings in GTLDefines/GDataDefines
+#import "GTMHTTPFetcher.h"
+
+#import "GTMOAuth2Authentication.h"
+
+@interface GTMOAuth2SignIn : NSObject {
+ @private
+ GTMOAuth2Authentication *auth_;
+
+ // the endpoint for displaying the sign-in page
+ NSURL *authorizationURL_;
+ NSDictionary *additionalAuthorizationParameters_;
+
+ id delegate_;
+ SEL webRequestSelector_;
+ SEL finishedSelector_;
+
+ BOOL hasHandledCallback_;
+
+ GTMHTTPFetcher *pendingFetcher_;
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ BOOL shouldFetchGoogleUserEmail_;
+ BOOL shouldFetchGoogleUserProfile_;
+ NSDictionary *userProfile_;
+#endif
+
+ SCNetworkReachabilityRef reachabilityRef_;
+ NSTimer *networkLossTimer_;
+ NSTimeInterval networkLossTimeoutInterval_;
+ BOOL hasNotifiedNetworkLoss_;
+
+ id userData_;
+}
+
+@property (nonatomic, retain) GTMOAuth2Authentication *authentication;
+
+@property (nonatomic, retain) NSURL *authorizationURL;
+@property (nonatomic, retain) NSDictionary *additionalAuthorizationParameters;
+
+// The delegate is released when signing in finishes or is cancelled
+@property (nonatomic, retain) id delegate;
+@property (nonatomic, assign) SEL webRequestSelector;
+@property (nonatomic, assign) SEL finishedSelector;
+
+@property (nonatomic, retain) id userData;
+
+// By default, signing in to Google will fetch the user's email, but will not
+// fetch the user's profile.
+//
+// The email is saved in the auth object.
+// The profile is available immediately after sign-in.
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+@property (nonatomic, assign) BOOL shouldFetchGoogleUserEmail;
+@property (nonatomic, assign) BOOL shouldFetchGoogleUserProfile;
+@property (nonatomic, retain, readonly) NSDictionary *userProfile;
+#endif
+
+// 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;
+
+// The delegate is retained until sign-in has completed or been canceled
+//
+// designated initializer
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ delegate:(id)delegate
+ webRequestSelector:(SEL)webRequestSelector
+ finishedSelector:(SEL)finishedSelector;
+
+// A default authentication object for signing in to Google services
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)standardGoogleAuthenticationForScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret;
+#endif
+
+#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, and any changes in the webview window title
+//
+// If these return YES then the event was handled by the
+// sign-in object (typically by closing the window) and should be ignored by
+// the window controller's web view
+
+- (BOOL)requestRedirectedToRequest:(NSURLRequest *)redirectedRequest;
+- (BOOL)titleChanged:(NSString *)title;
+- (BOOL)cookiesChanged:(NSHTTPCookieStorage *)cookieStorage;
+- (BOOL)loadFailedWithError:(NSError *)error;
+
+// 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;
+
+// Start the sequences for signing in with an authorization code. The
+// authentication must contain an authorization code, otherwise the process
+// will fail.
+- (void)authCodeObtained;
+
+#pragma mark -
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+// Revocation of an authorized token from Google
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth;
+
+// Create a fetcher for obtaining the user's Google email address or profile,
+// according to the current auth scopes.
+//
+// The auth object must have been created with appropriate scopes.
+//
+// The fetcher's response data can be parsed with NSJSONSerialization.
++ (GTMHTTPFetcher *)userInfoFetcherWithAuth:(GTMOAuth2Authentication *)auth;
+#endif
+
+#pragma mark -
+
+// Standard authentication values
++ (NSString *)nativeClientRedirectURI;
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (NSURL *)googleAuthorizationURL;
++ (NSURL *)googleTokenURL;
++ (NSURL *)googleUserInfoURL;
+#endif
+
+@end
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/GTMOAuth2SignIn.m b/example/common/gtm-oauth2/Source/GTMOAuth2SignIn.m
new file mode 100644
index 00000000..d9df97e4
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/GTMOAuth2SignIn.m
@@ -0,0 +1,939 @@
+/* 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.
+ */
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#define GTMOAUTH2SIGNIN_DEFINE_GLOBALS 1
+#import "GTMOAuth2SignIn.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;
+
+// URI indicating an installed app is signing in. This is described at
+//
+// http://code.google.com/apis/accounts/docs/OAuth2.html#IA
+//
+NSString *const kOOBString = @"urn:ietf:wg:oauth:2.0:oob";
+
+
+@interface GTMOAuth2SignIn ()
+@property (assign) BOOL hasHandledCallback;
+@property (retain) GTMHTTPFetcher *pendingFetcher;
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+@property (nonatomic, retain, readwrite) NSDictionary *userProfile;
+#endif
+
+- (void)invokeFinalCallbackWithError:(NSError *)error;
+
+- (BOOL)startWebRequest;
++ (NSMutableURLRequest *)mutableURLRequestWithURL:(NSURL *)oldURL
+ paramString:(NSString *)paramStr;
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+- (void)addScopeForGoogleUserInfo;
+- (void)fetchGoogleUserInfo;
+#endif
+- (void)finishSignInWithError:(NSError *)error;
+
+- (void)auth:(GTMOAuth2Authentication *)auth
+finishedWithFetcher:(GTMHTTPFetcher *)fetcher
+ error:(NSError *)error;
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+- (void)infoFetcher:(GTMHTTPFetcher *)fetcher
+ finishedWithData:(NSData *)data
+ error:(NSError *)error;
++ (NSData *)decodeWebSafeBase64:(NSString *)base64Str;
+#endif
+
+- (void)closeTheWindow;
+
+- (void)startReachabilityCheck;
+- (void)stopReachabilityCheck;
+- (void)reachabilityTarget:(SCNetworkReachabilityRef)reachabilityRef
+ changedFlags:(SCNetworkConnectionFlags)flags;
+- (void)reachabilityTimerFired:(NSTimer *)timer;
+@end
+
+@implementation GTMOAuth2SignIn
+
+@synthesize authentication = auth_;
+
+@synthesize authorizationURL = authorizationURL_;
+@synthesize additionalAuthorizationParameters = additionalAuthorizationParameters_;
+
+@synthesize delegate = delegate_;
+@synthesize webRequestSelector = webRequestSelector_;
+@synthesize finishedSelector = finishedSelector_;
+@synthesize hasHandledCallback = hasHandledCallback_;
+@synthesize pendingFetcher = pendingFetcher_;
+@synthesize userData = userData_;
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+@synthesize shouldFetchGoogleUserEmail = shouldFetchGoogleUserEmail_;
+@synthesize shouldFetchGoogleUserProfile = shouldFetchGoogleUserProfile_;
+@synthesize userProfile = userProfile_;
+#endif
+
+@synthesize networkLossTimeoutInterval = networkLossTimeoutInterval_;
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (NSURL *)googleAuthorizationURL {
+ NSString *str = @"https://accounts.google.com/o/oauth2/auth";
+ return [NSURL URLWithString:str];
+}
+
++ (NSURL *)googleTokenURL {
+ NSString *str = @"https://accounts.google.com/o/oauth2/token";
+ return [NSURL URLWithString:str];
+}
+
++ (NSURL *)googleRevocationURL {
+ NSString *urlStr = @"https://accounts.google.com/o/oauth2/revoke";
+ return [NSURL URLWithString:urlStr];
+}
+
++ (NSURL *)googleUserInfoURL {
+ NSString *urlStr = @"https://www.googleapis.com/oauth2/v1/userinfo";
+ return [NSURL URLWithString:urlStr];
+}
+#endif
+
++ (NSString *)nativeClientRedirectURI {
+ return kOOBString;
+}
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)standardGoogleAuthenticationForScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret {
+ NSString *redirectURI = [self nativeClientRedirectURI];
+ NSURL *tokenURL = [self googleTokenURL];
+
+ GTMOAuth2Authentication *auth;
+ auth = [GTMOAuth2Authentication authenticationWithServiceProvider:kGTMOAuth2ServiceProviderGoogle
+ tokenURL:tokenURL
+ redirectURI:redirectURI
+ clientID:clientID
+ clientSecret:clientSecret];
+ auth.scope = scope;
+
+ return auth;
+}
+
+- (void)addScopeForGoogleUserInfo {
+ GTMOAuth2Authentication *auth = self.authentication;
+ if (self.shouldFetchGoogleUserEmail) {
+ NSString *const emailScope = @"https://www.googleapis.com/auth/userinfo.email";
+ NSString *scope = auth.scope;
+ if ([scope rangeOfString:emailScope].location == NSNotFound) {
+ scope = [GTMOAuth2Authentication scopeWithStrings:scope, emailScope, nil];
+ auth.scope = scope;
+ }
+ }
+
+ if (self.shouldFetchGoogleUserProfile) {
+ NSString *const profileScope = @"https://www.googleapis.com/auth/userinfo.profile";
+ NSString *scope = auth.scope;
+ if ([scope rangeOfString:profileScope].location == NSNotFound) {
+ scope = [GTMOAuth2Authentication scopeWithStrings:scope, profileScope, nil];
+ auth.scope = scope;
+ }
+ }
+}
+#endif
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ delegate:(id)delegate
+ webRequestSelector:(SEL)webRequestSelector
+ finishedSelector:(SEL)finishedSelector {
+ // check the selectors on debug builds
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, webRequestSelector,
+ @encode(GTMOAuth2SignIn *), @encode(NSURLRequest *), 0);
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector,
+ @encode(GTMOAuth2SignIn *), @encode(GTMOAuth2Authentication *),
+ @encode(NSError *), 0);
+
+ // designated initializer
+ self = [super init];
+ if (self) {
+ auth_ = [auth retain];
+ authorizationURL_ = [authorizationURL retain];
+ delegate_ = [delegate retain];
+ webRequestSelector_ = webRequestSelector;
+ finishedSelector_ = finishedSelector;
+
+ // for Google authentication, we want to automatically fetch user info
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ NSString *host = [authorizationURL host];
+ if ([host hasSuffix:@".google.com"]) {
+ shouldFetchGoogleUserEmail_ = YES;
+ }
+#endif
+
+ // default timeout for a lost internet connection while the server
+ // UI is displayed is 30 seconds
+ networkLossTimeoutInterval_ = kDefaultNetworkLossTimeoutInterval;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self stopReachabilityCheck];
+
+ [auth_ release];
+ [authorizationURL_ release];
+ [additionalAuthorizationParameters_ release];
+ [delegate_ release];
+ [pendingFetcher_ release];
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ [userProfile_ release];
+#endif
+ [userData_ release];
+
+ [super dealloc];
+}
+
+#pragma mark Sign-in Sequence Methods
+
+// stop any pending fetches, and close the window (but don't call the
+// delegate's finishedSelector)
+- (void)cancelSigningIn {
+ [self.pendingFetcher stopFetching];
+ self.pendingFetcher = nil;
+
+ [self.authentication stopAuthorization];
+
+ [self closeTheWindow];
+
+ [delegate_ autorelease];
+ delegate_ = nil;
+}
+
+//
+// This is the entry point to begin the sequence
+// - display the authentication web page, and monitor redirects
+// - exchange the code for an access token and a refresh token
+// - for Google sign-in, fetch the user's email address
+// - tell the delegate we're finished
+//
+- (BOOL)startSigningIn {
+ // For signing in to Google, append the scope for obtaining the authenticated
+ // user email and profile, as appropriate
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ [self addScopeForGoogleUserInfo];
+#endif
+
+ // start the authorization
+ return [self startWebRequest];
+}
+
+- (NSMutableDictionary *)parametersForWebRequest {
+ GTMOAuth2Authentication *auth = self.authentication;
+ NSString *clientID = auth.clientID;
+ NSString *redirectURI = auth.redirectURI;
+
+ BOOL hasClientID = ([clientID length] > 0);
+ BOOL hasRedirect = ([redirectURI length] > 0
+ || redirectURI == [[self class] nativeClientRedirectURI]);
+ if (!hasClientID || !hasRedirect) {
+#if DEBUG
+ NSAssert(hasClientID, @"GTMOAuth2SignIn: clientID needed");
+ NSAssert(hasRedirect, @"GTMOAuth2SignIn: redirectURI needed");
+#endif
+ return NO;
+ }
+
+ // invoke the UI controller's web request selector to display
+ // the authorization page
+
+ // add params to the authorization URL
+ NSString *scope = auth.scope;
+ if ([scope length] == 0) scope = nil;
+
+ NSMutableDictionary *paramsDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ @"code", @"response_type",
+ clientID, @"client_id",
+ scope, @"scope", // scope may be nil
+ nil];
+ if (redirectURI) {
+ [paramsDict setObject:redirectURI forKey:@"redirect_uri"];
+ }
+ return paramsDict;
+}
+
+- (BOOL)startWebRequest {
+ NSMutableDictionary *paramsDict = [self parametersForWebRequest];
+
+ NSDictionary *additionalParams = self.additionalAuthorizationParameters;
+ if (additionalParams) {
+ [paramsDict addEntriesFromDictionary:additionalParams];
+ }
+
+ NSString *paramStr = [GTMOAuth2Authentication encodedQueryParametersForDictionary:paramsDict];
+
+ NSURL *authorizationURL = self.authorizationURL;
+ NSMutableURLRequest *request;
+ request = [[self class] mutableURLRequestWithURL:authorizationURL
+ paramString:paramStr];
+
+ [delegate_ performSelector:self.webRequestSelector
+ withObject:self
+ withObject:request];
+
+ // at this point, we're waiting on the server-driven html UI, so
+ // we want notification if we lose connectivity to the web server
+ [self startReachabilityCheck];
+ return YES;
+}
+
+// utility for making a request from an old URL with some additional parameters
++ (NSMutableURLRequest *)mutableURLRequestWithURL:(NSURL *)oldURL
+ paramString:(NSString *)paramStr {
+ if ([paramStr length] == 0) {
+ return [NSMutableURLRequest requestWithURL:oldURL];
+ }
+
+ 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];
+ NSURL *newURL = [NSURL URLWithString:newURLStr];
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:newURL];
+ return request;
+}
+
+// entry point for the window controller to tell us that the window
+// prematurely closed
+- (void)windowWasClosed {
+ [self stopReachabilityCheck];
+
+ NSError *error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain
+ code:kGTMOAuth2ErrorWindowClosed
+ userInfo:nil];
+ [self invokeFinalCallbackWithError:error];
+}
+
+// internal method to tell the window controller to close the window
+- (void)closeTheWindow {
+ [self stopReachabilityCheck];
+
+ // a nil request means the window should be closed
+ [delegate_ performSelector:self.webRequestSelector
+ withObject:self
+ withObject:nil];
+}
+
+// entry point for the window controller to tell us what web page has been
+// requested
+//
+// When the request is for the callback URL, this method invokes
+// authCodeObtained and returns YES
+- (BOOL)requestRedirectedToRequest:(NSURLRequest *)redirectedRequest {
+ // for Google's installed app sign-in protocol, we'll look for the
+ // end-of-sign-in indicator in the titleChanged: method below
+ NSString *redirectURI = self.authentication.redirectURI;
+ if (redirectURI == nil) return NO;
+
+ // when we're searching for the window title string, then we can ignore
+ // redirects
+ NSString *standardURI = [[self class] nativeClientRedirectURI];
+ if (standardURI != nil && [redirectURI isEqual:standardURI]) return NO;
+
+ // compare the redirectURI, which tells us when the web sign-in is done,
+ // to the actual redirection
+ NSURL *redirectURL = [NSURL URLWithString:redirectURI];
+ NSURL *requestURL = [redirectedRequest URL];
+
+ // avoid comparing to nil host and path values (such as when redirected to
+ // "about:blank")
+ NSString *requestHost = [requestURL host];
+ NSString *requestPath = [requestURL path];
+ BOOL isCallback;
+ if (requestHost && requestPath) {
+ isCallback = [[redirectURL host] isEqual:[requestURL host]]
+ && [[redirectURL path] isEqual:[requestURL path]];
+ } else if (requestURL) {
+ // handle "about:blank"
+ isCallback = [redirectURL isEqual:requestURL];
+ } else {
+ isCallback = NO;
+ }
+
+ if (!isCallback) {
+ // tell the caller that this request is nothing interesting
+ return NO;
+ }
+
+ // we've reached the callback URL
+
+ // try to get the access code
+ if (!self.hasHandledCallback) {
+ NSString *responseStr = [[redirectedRequest URL] query];
+ [self.authentication setKeysForResponseString:responseStr];
+
+#if DEBUG
+ NSAssert([self.authentication.code length] > 0
+ || [self.authentication.errorString length] > 0,
+ @"response lacks auth code or error");
+#endif
+
+ [self authCodeObtained];
+ }
+ // tell the delegate that we did handle this request
+ return YES;
+}
+
+// entry point for the window controller to tell us when a new page title has
+// been loadded
+//
+// When the title indicates sign-in has completed, this method invokes
+// authCodeObtained and returns YES
+- (BOOL)titleChanged:(NSString *)title {
+ // return YES if the OAuth flow ending title was detected
+
+ // right now we're just looking for a parameter list following the last space
+ // in the title string, but hopefully we'll eventually get something better
+ // from the server to search for
+ NSRange paramsRange = [title rangeOfString:@" "
+ options:NSBackwardsSearch];
+ NSUInteger spaceIndex = paramsRange.location;
+ if (spaceIndex != NSNotFound) {
+ NSString *responseStr = [title substringFromIndex:(spaceIndex + 1)];
+
+ NSDictionary *dict = [GTMOAuth2Authentication dictionaryWithResponseString:responseStr];
+
+ NSString *code = [dict objectForKey:@"code"];
+ NSString *error = [dict objectForKey:@"error"];
+ if ([code length] > 0 || [error length] > 0) {
+
+ if (!self.hasHandledCallback) {
+ [self.authentication setKeysForResponseDictionary:dict];
+
+ [self authCodeObtained];
+ }
+ return YES;
+ }
+ }
+ return NO;
+}
+
+- (BOOL)cookiesChanged:(NSHTTPCookieStorage *)cookieStorage {
+ // We're ignoring these.
+ return NO;
+};
+
+// entry point for the window controller to tell us when a load has failed
+// in the webview
+//
+// if the initial authorization URL fails, bail out so the user doesn't
+// see an empty webview
+- (BOOL)loadFailedWithError:(NSError *)error {
+ NSURL *authorizationURL = self.authorizationURL;
+ NSURL *failedURL = [[error userInfo] valueForKey:@"NSErrorFailingURLKey"]; // NSURLErrorFailingURLErrorKey defined in 10.6
+
+ BOOL isAuthURL = [[failedURL host] isEqual:[authorizationURL host]]
+ && [[failedURL path] isEqual:[authorizationURL path]];
+
+ if (isAuthURL) {
+ // We can assume that we have no pending fetchers, since we only
+ // handle failure to load the initial authorization URL
+ [self closeTheWindow];
+ [self invokeFinalCallbackWithError:error];
+ return YES;
+ }
+ return NO;
+}
+
+- (void)authCodeObtained {
+ // the callback page was requested, or the authenticate code was loaded
+ // into a page's title, so exchange the auth code for access & refresh tokens
+ // and tell the window to close
+
+ // avoid duplicate signals that the callback point has been reached
+ self.hasHandledCallback = YES;
+
+ // If the signin was request for exchanging an authentication token to a
+ // refresh token, there is no window to close.
+ if (self.webRequestSelector) {
+ [self closeTheWindow];
+ } else {
+ // For signing in to Google, append the scope for obtaining the
+ // authenticated user email and profile, as appropriate. This is usually
+ // done by the startSigningIn method, but this method is not called when
+ // exchanging an authentication token for a refresh token.
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ [self addScopeForGoogleUserInfo];
+#endif
+ }
+
+ NSError *error = nil;
+
+ GTMOAuth2Authentication *auth = self.authentication;
+ NSString *code = auth.code;
+ if ([code length] > 0) {
+ // exchange the code for a token
+ SEL sel = @selector(auth:finishedWithFetcher:error:);
+ GTMHTTPFetcher *fetcher = [auth beginTokenFetchWithDelegate:self
+ didFinishSelector:sel];
+ if (fetcher == nil) {
+ error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
+ code:-1
+ userInfo:nil];
+ } else {
+ self.pendingFetcher = fetcher;
+ }
+
+ // notify the app so it can put up a post-sign in, pre-token exchange UI
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:kGTMOAuth2UserSignedIn
+ object:self
+ userInfo:nil];
+ } else {
+ // the callback lacked an auth code
+ NSString *errStr = auth.errorString;
+ NSDictionary *userInfo = nil;
+ if ([errStr length] > 0) {
+ userInfo = [NSDictionary dictionaryWithObject:errStr
+ forKey:kGTMOAuth2ErrorMessageKey];
+ }
+
+ error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain
+ code:kGTMOAuth2ErrorAuthorizationFailed
+ userInfo:userInfo];
+ }
+
+ if (error) {
+ [self finishSignInWithError:error];
+ }
+}
+
+- (void)auth:(GTMOAuth2Authentication *)auth
+finishedWithFetcher:(GTMHTTPFetcher *)fetcher
+ error:(NSError *)error {
+ self.pendingFetcher = nil;
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+ if (error == nil
+ && (self.shouldFetchGoogleUserEmail || self.shouldFetchGoogleUserProfile)
+ && [self.authentication.serviceProvider isEqual:kGTMOAuth2ServiceProviderGoogle]) {
+ // fetch the user's information from the Google server
+ [self fetchGoogleUserInfo];
+ } else {
+ // we're not authorizing with Google, so we're done
+ [self finishSignInWithError:error];
+ }
+#else
+ [self finishSignInWithError:error];
+#endif
+}
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMHTTPFetcher *)userInfoFetcherWithAuth:(GTMOAuth2Authentication *)auth {
+ // create a fetcher for obtaining the user's email or profile
+ NSURL *infoURL = [[self class] googleUserInfoURL];
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:infoURL];
+
+ if ([auth respondsToSelector:@selector(userAgent)]) {
+ NSString *userAgent = [auth userAgent];
+ [request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
+ }
+ [request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"];
+
+ GTMHTTPFetcher *fetcher;
+ id <GTMHTTPFetcherServiceProtocol> fetcherService = nil;
+ if ([auth respondsToSelector:@selector(fetcherService)]) {
+ fetcherService = auth.fetcherService;
+ };
+ if (fetcherService) {
+ fetcher = [fetcherService fetcherWithRequest:request];
+ } else {
+ fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
+ }
+ fetcher.authorizer = auth;
+ fetcher.retryEnabled = YES;
+ fetcher.maxRetryInterval = 15.0;
+ fetcher.comment = @"user info";
+ return fetcher;
+}
+
+- (void)fetchGoogleUserInfo {
+ if (!self.shouldFetchGoogleUserProfile) {
+ // If we only need email and user ID, not the full profile, and we have an
+ // id_token, it may have the email and user ID so we won't need to fetch
+ // them.
+ GTMOAuth2Authentication *auth = self.authentication;
+ NSString *idToken = [auth.parameters objectForKey:@"id_token"];
+ if ([idToken length] > 0) {
+ // The id_token has three dot-delimited parts. The second is the
+ // JSON profile.
+ //
+ // http://www.tbray.org/ongoing/When/201x/2013/04/04/ID-Tokens
+ NSArray *parts = [idToken componentsSeparatedByString:@"."];
+ if ([parts count] == 3) {
+ NSString *part2 = [parts objectAtIndex:1];
+ if ([part2 length] > 0) {
+ NSData *data = [[self class] decodeWebSafeBase64:part2];
+ if ([data length] > 0) {
+ [self updateGoogleUserInfoWithData:data];
+ if ([[auth userID] length] > 0 && [[auth userEmail] length] > 0) {
+ // We obtained user ID and email from the ID token.
+ [self finishSignInWithError:nil];
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Fetch the email and profile from the userinfo endpoint.
+ GTMOAuth2Authentication *auth = self.authentication;
+ GTMHTTPFetcher *fetcher = [[self class] userInfoFetcherWithAuth:auth];
+ [fetcher beginFetchWithDelegate:self
+ didFinishSelector:@selector(infoFetcher:finishedWithData:error:)];
+
+ self.pendingFetcher = fetcher;
+
+ [auth notifyFetchIsRunning:YES
+ fetcher:fetcher
+ type:kGTMOAuth2FetchTypeUserInfo];
+}
+
+- (void)infoFetcher:(GTMHTTPFetcher *)fetcher
+ finishedWithData:(NSData *)data
+ error:(NSError *)error {
+ GTMOAuth2Authentication *auth = self.authentication;
+ [auth notifyFetchIsRunning:NO
+ fetcher:fetcher
+ type:nil];
+
+ self.pendingFetcher = nil;
+
+ if (error) {
+#if DEBUG
+ if (data) {
+ NSString *dataStr = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding] autorelease];
+ NSLog(@"infoFetcher error: %@\n%@", error, dataStr);
+ }
+#endif
+ } else {
+ // We have the authenticated user's info
+ [self updateGoogleUserInfoWithData:data];
+ }
+ [self finishSignInWithError:error];
+}
+
+- (void)updateGoogleUserInfoWithData:(NSData *)data {
+ if (!data) return;
+
+ GTMOAuth2Authentication *auth = self.authentication;
+ NSDictionary *profileDict = [[auth class] dictionaryWithJSONData:data];
+ if (profileDict) {
+ self.userProfile = profileDict;
+
+ // Save the ID into the auth object
+ NSString *identifier = [profileDict objectForKey:@"id"];
+ [auth setUserID:identifier];
+
+ // Save the email into the auth object
+ NSString *email = [profileDict objectForKey:@"email"];
+ [auth setUserEmail:email];
+
+ // The verified_email key is a boolean NSNumber in the userinfo
+ // endpoint response, but it is a string like "true" in the id_token.
+ // We want to consistently save it as a string of the boolean value,
+ // like @"1".
+ id verified = [profileDict objectForKey:@"verified_email"];
+ if ([verified isKindOfClass:[NSString class]]) {
+ verified = [NSNumber numberWithBool:[verified boolValue]];
+ }
+
+ [auth setUserEmailIsVerified:[verified stringValue]];
+ }
+}
+
+#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+
+- (void)finishSignInWithError:(NSError *)error {
+ [self invokeFinalCallbackWithError:error];
+}
+
+// convenience method for making the final call to our delegate
+- (void)invokeFinalCallbackWithError:(NSError *)error {
+ if (delegate_ && finishedSelector_) {
+ GTMOAuth2Authentication *auth = self.authentication;
+
+ NSMethodSignature *sig = [delegate_ methodSignatureForSelector:finishedSelector_];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:finishedSelector_];
+ [invocation setTarget:delegate_];
+ [invocation setArgument:&self atIndex:2];
+ [invocation setArgument:&auth atIndex:3];
+ [invocation setArgument:&error atIndex:4];
+ [invocation invoke];
+ }
+
+ // we'll no longer send messages to the delegate
+ //
+ // we want to autorelease it rather than assign to the property in case
+ // the delegate is below us in the call stack
+ [delegate_ autorelease];
+ delegate_ = nil;
+}
+
+#pragma mark Reachability monitoring
+
+static void ReachabilityCallBack(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void *info) {
+ // pass the flags to the signIn object
+ GTMOAuth2SignIn *signIn = (GTMOAuth2SignIn *)info;
+
+ [signIn reachabilityTarget:target
+ changedFlags:flags];
+}
+
+- (void)startReachabilityCheck {
+ // the user may set the timeout to 0 to skip the reachability checking
+ // during display of the sign-in page
+ if (networkLossTimeoutInterval_ <= 0.0 || reachabilityRef_ != NULL) {
+ return;
+ }
+
+ // create a reachability target from the authorization URL, add our callback,
+ // and schedule it on the run loop so we'll be notified if the network drops
+ NSURL *url = self.authorizationURL;
+ const char* host = [[url host] UTF8String];
+ reachabilityRef_ = SCNetworkReachabilityCreateWithName(kCFAllocatorSystemDefault,
+ host);
+ if (reachabilityRef_) {
+ BOOL isScheduled = NO;
+ SCNetworkReachabilityContext ctx = { 0, self, NULL, NULL, NULL };
+
+ if (SCNetworkReachabilitySetCallback(reachabilityRef_,
+ ReachabilityCallBack, &ctx)) {
+ if (SCNetworkReachabilityScheduleWithRunLoop(reachabilityRef_,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode)) {
+ isScheduled = YES;
+ }
+ }
+
+ if (!isScheduled) {
+ CFRelease(reachabilityRef_);
+ reachabilityRef_ = NULL;
+ }
+ }
+}
+
+- (void)destroyUnreachabilityTimer {
+ [networkLossTimer_ invalidate];
+ [networkLossTimer_ autorelease];
+ networkLossTimer_ = nil;
+}
+
+- (void)reachabilityTarget:(SCNetworkReachabilityRef)reachabilityRef
+ changedFlags:(SCNetworkConnectionFlags)flags {
+ BOOL isConnected = (flags & kSCNetworkFlagsReachable) != 0
+ && (flags & kSCNetworkFlagsConnectionRequired) == 0;
+
+ if (isConnected) {
+ // server is again reachable
+ [self destroyUnreachabilityTimer];
+
+ if (hasNotifiedNetworkLoss_) {
+ // tell the user that the network has been found
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:kGTMOAuth2NetworkFound
+ object:self
+ userInfo:nil];
+ hasNotifiedNetworkLoss_ = NO;
+ }
+ } else {
+ // the server has become unreachable; start the timer, if necessary
+ if (networkLossTimer_ == nil
+ && networkLossTimeoutInterval_ > 0
+ && !hasNotifiedNetworkLoss_) {
+ SEL sel = @selector(reachabilityTimerFired:);
+ networkLossTimer_ = [[NSTimer scheduledTimerWithTimeInterval:networkLossTimeoutInterval_
+ target:self
+ selector:sel
+ userInfo:nil
+ repeats:NO] retain];
+ }
+ }
+}
+
+- (void)reachabilityTimerFired:(NSTimer *)timer {
+ // the user may call [[notification object] cancelSigningIn] to
+ // dismiss the sign-in
+ if (!hasNotifiedNetworkLoss_) {
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:kGTMOAuth2NetworkLost
+ object:self
+ userInfo:nil];
+ hasNotifiedNetworkLoss_ = YES;
+ }
+
+ [self destroyUnreachabilityTimer];
+}
+
+- (void)stopReachabilityCheck {
+ [self destroyUnreachabilityTimer];
+
+ if (reachabilityRef_) {
+ SCNetworkReachabilityUnscheduleFromRunLoop(reachabilityRef_,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+ SCNetworkReachabilitySetCallback(reachabilityRef_, NULL, NULL);
+
+ CFRelease(reachabilityRef_);
+ reachabilityRef_ = NULL;
+ }
+}
+
+#pragma mark Token Revocation
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth {
+ if (auth.refreshToken != nil
+ && auth.canAuthorize
+ && [auth.serviceProvider isEqual:kGTMOAuth2ServiceProviderGoogle]) {
+
+ // create a signed revocation request for this authentication object
+ NSURL *url = [self googleRevocationURL];
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
+ [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
+
+ NSString *token = auth.refreshToken;
+ NSString *encoded = [GTMOAuth2Authentication encodedOAuthValueForString:token];
+ if (encoded != nil) {
+ NSString *body = [@"token=" stringByAppendingString:encoded];
+
+ [request setHTTPBody:[body dataUsingEncoding:NSUTF8StringEncoding]];
+ [request setHTTPMethod:@"POST"];
+
+ NSString *userAgent = [auth userAgent];
+ [request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
+
+ // there's nothing to be done if revocation succeeds or fails
+ GTMHTTPFetcher *fetcher;
+ id <GTMHTTPFetcherServiceProtocol> fetcherService = auth.fetcherService;
+ if (fetcherService) {
+ fetcher = [fetcherService fetcherWithRequest:request];
+ } else {
+ fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
+ }
+ fetcher.comment = @"revoke token";
+
+ // Use a completion handler fetch for better debugging, but only if we're
+ // guaranteed that blocks are available in the runtime
+#if (!TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)) || \
+ (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000))
+ // Blocks are available
+ [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
+ #if DEBUG
+ if (error) {
+ NSString *errStr = [[[NSString alloc] initWithData:data
+ encoding:NSUTF8StringEncoding] autorelease];
+ NSLog(@"revoke error: %@", errStr);
+ }
+ #endif // DEBUG
+ }];
+#else
+ // Blocks may not be available
+ [fetcher beginFetchWithDelegate:nil didFinishSelector:NULL];
+#endif
+ }
+ }
+ [auth reset];
+}
+
+
+// Based on Cyrus Najmabadi's elegent little encoder and decoder from
+// http://www.cocoadev.com/index.pl?BaseSixtyFour and on GTLBase64
+
++ (NSData *)decodeWebSafeBase64:(NSString *)base64Str {
+ static char decodingTable[128];
+ static BOOL hasInited = NO;
+
+ if (!hasInited) {
+ char webSafeEncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+ memset(decodingTable, 0, 128);
+ for (unsigned int i = 0; i < sizeof(webSafeEncodingTable); i++) {
+ decodingTable[(unsigned int) webSafeEncodingTable[i]] = (char)i;
+ }
+ hasInited = YES;
+ }
+
+ // The input string should be plain ASCII.
+ const char *cString = [base64Str cStringUsingEncoding:NSASCIIStringEncoding];
+ if (cString == nil) return nil;
+
+ NSInteger inputLength = (NSInteger)strlen(cString);
+ // Input length is not being restricted to multiples of 4.
+ if (inputLength == 0) return [NSData data];
+
+ while (inputLength > 0 && cString[inputLength - 1] == '=') {
+ inputLength--;
+ }
+
+ NSInteger outputLength = inputLength * 3 / 4;
+ NSMutableData* data = [NSMutableData dataWithLength:(NSUInteger)outputLength];
+ uint8_t *output = [data mutableBytes];
+
+ NSInteger inputPoint = 0;
+ NSInteger outputPoint = 0;
+ char *table = decodingTable;
+
+ while (inputPoint < inputLength - 1) {
+ int i0 = cString[inputPoint++];
+ int i1 = cString[inputPoint++];
+ int i2 = inputPoint < inputLength ? cString[inputPoint++] : 'A'; // 'A' will decode to \0
+ int i3 = inputPoint < inputLength ? cString[inputPoint++] : 'A';
+
+ output[outputPoint++] = (uint8_t)((table[i0] << 2) | (table[i1] >> 4));
+ if (outputPoint < outputLength) {
+ output[outputPoint++] = (uint8_t)(((table[i1] & 0xF) << 4) | (table[i2] >> 2));
+ }
+ if (outputPoint < outputLength) {
+ output[outputPoint++] = (uint8_t)(((table[i2] & 0x3) << 6) | table[i3]);
+ }
+ }
+
+ return data;
+}
+
+#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+
+@end
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/Mac/GTMOAuth2Window.xib b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2Window.xib
new file mode 100644
index 00000000..befc2123
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2Window.xib
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4457.6" systemVersion="12E55" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
+ <dependencies>
+ <deployment version="1050" defaultVersion="1080" identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4457.6"/>
+ <plugIn identifier="com.apple.WebKitIBPlugin" version="3330"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="GTMOAuth2WindowController">
+ <connections>
+ <action selector="closeWindow:" destination="17" id="42"/>
+ <outlet property="keychainCheckbox" destination="43" id="46"/>
+ <outlet property="webBackButton" destination="19" id="47"/>
+ <outlet property="webCloseButton" destination="17" id="48"/>
+ <outlet property="webView" destination="5" id="49"/>
+ <outlet property="window" destination="3" id="8"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application"/>
+ <window title="Sign In" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" wantsToBeColor="NO" visibleAtLaunch="NO" animationBehavior="default" id="3">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="74" y="707" width="515" height="419"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/>
+ <value key="minSize" type="size" width="475" height="290"/>
+ <view key="contentView" id="4">
+ <rect key="frame" x="0.0" y="0.0" width="515" height="419"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <webView id="5">
+ <rect key="frame" x="0.0" y="20" width="515" height="399"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <webPreferences key="preferences" defaultFontSize="12" defaultFixedFontSize="12"/>
+ <connections>
+ <action selector="goBack:" destination="19" id="28"/>
+ <action selector="goForward:" destination="26" id="29"/>
+ </connections>
+ </webView>
+ <button id="17">
+ <rect key="frame" x="479" y="0.0" width="16" height="19"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
+ <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSStopProgressTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" inset="2" id="18">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ <string key="keyEquivalent" base64-UTF8="YES">
+Gw
+</string>
+ </buttonCell>
+ </button>
+ <button id="19">
+ <rect key="frame" x="437" y="0.0" width="16" height="19"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
+ <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSGoLeftTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" inset="2" id="20">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <binding destination="-2" name="enabled" keyPath="webView.canGoBack" id="31"/>
+ </connections>
+ </button>
+ <button id="26">
+ <rect key="frame" x="456" y="0.0" width="16" height="19"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
+ <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSGoRightTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="27">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <binding destination="-2" name="enabled" keyPath="webView.canGoForward" id="35"/>
+ </connections>
+ </button>
+ <button id="43">
+ <rect key="frame" x="2" y="1" width="429" height="18"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <buttonCell key="cell" type="check" title="Stay signed in to this account" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" inset="2" id="44">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ </button>
+ </subviews>
+ </view>
+ <connections>
+ <outlet property="delegate" destination="-2" id="7"/>
+ </connections>
+ </window>
+ <userDefaultsController id="32"/>
+ </objects>
+ <resources>
+ <image name="NSGoLeftTemplate" width="9" height="9"/>
+ <image name="NSGoRightTemplate" width="9" height="9"/>
+ <image name="NSStopProgressTemplate" width="11" height="11"/>
+ </resources>
+ <classes>
+ <class className="GTMOAuth2WindowController" superclassName="NSWindowController">
+ <source key="sourceIdentifier" type="project" relativePath="./Classes/GTMOAuth2WindowController.h"/>
+ <relationships>
+ <relationship kind="action" name="closeWindow:"/>
+ <relationship kind="outlet" name="completionPlaceholder_"/>
+ <relationship kind="outlet" name="delegate_"/>
+ <relationship kind="outlet" name="keychainCheckbox" candidateClass="NSButton"/>
+ <relationship kind="outlet" name="userData_"/>
+ <relationship kind="outlet" name="webBackButton" candidateClass="NSButton"/>
+ <relationship kind="outlet" name="webCloseButton" candidateClass="NSButton"/>
+ <relationship kind="outlet" name="webView" candidateClass="WebView"/>
+ </relationships>
+ </class>
+ </classes>
+</document> \ No newline at end of file
diff --git a/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.h b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.h
new file mode 100644
index 00000000..9ff89b70
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.h
@@ -0,0 +1,332 @@
+/* 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.
+ */
+
+// GTMOAuth2WindowController
+//
+// This window controller for Mac handles sign-in via OAuth2 to Google or
+// other services.
+//
+// This controller is not reusable; create a new instance of this controller
+// every time the user will sign in.
+//
+// Sample usage for signing in to a Google service:
+//
+// static NSString *const kKeychainItemName = @”My App: Google Plus”;
+// NSString *scope = @"https://www.googleapis.com/auth/plus.me";
+//
+//
+// GTMOAuth2WindowController *windowController;
+// windowController = [[[GTMOAuth2WindowController alloc] initWithScope:scope
+// clientID:clientID
+// clientSecret:clientSecret
+// keychainItemName:kKeychainItemName
+// resourceBundle:nil] autorelease];
+//
+// [windowController signInSheetModalForWindow:mMainWindow
+// delegate:self
+// finishedSelector:@selector(windowController:finishedWithAuth:error:)];
+//
+// The finished selector should have a signature matching this:
+//
+// - (void)windowController:(GTMOAuth2WindowController *)windowController
+// finishedWithAuth:(GTMOAuth2Authentication *)auth
+// error:(NSError *)error {
+// if (error != nil) {
+// // sign in failed
+// } else {
+// // sign in succeeded
+// //
+// // with the GTL library, pass the authentication to the service object,
+// // like
+// // [[self contactService] setAuthorizer:auth];
+// //
+// // or use it to sign a request directly, like
+// // BOOL isAuthorizing = [self authorizeRequest:request
+// // delegate:self
+// // didFinishSelector:@selector(auth:finishedWithError:)];
+// }
+// }
+//
+// To sign in to services other than Google, use the longer init method,
+// as shown in the sample application
+//
+// If the network connection is lost for more than 30 seconds while the sign-in
+// html is displayed, the notification kGTLOAuthNetworkLost will be sent.
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#include <Foundation/Foundation.h>
+
+#if !TARGET_OS_IPHONE
+
+#import <Cocoa/Cocoa.h>
+#import <WebKit/WebKit.h>
+
+// GTMHTTPFetcher.h brings in GTLDefines/GDataDefines
+#import "GTMHTTPFetcher.h"
+
+#import "GTMOAuth2SignIn.h"
+#import "GTMOAuth2Authentication.h"
+#import "GTMHTTPFetchHistory.h" // for GTMCookieStorage
+
+@class GTMOAuth2SignIn;
+
+@interface GTMOAuth2WindowController : NSWindowController {
+ @private
+ // IBOutlets
+ NSButton *keychainCheckbox_;
+ WebView *webView_;
+ NSButton *webCloseButton_;
+ NSButton *webBackButton_;
+
+ // the object responsible for the sign-in networking sequence; it holds
+ // onto the authentication object as well
+ GTMOAuth2SignIn *signIn_;
+
+ // the page request to load when awakeFromNib occurs
+ NSURLRequest *initialRequest_;
+
+ // local storage for WebKit cookies so they're not shared with Safari
+ GTMCookieStorage *cookieStorage_;
+
+ // the user we're calling back
+ //
+ // the delegate is retained only until the callback is invoked
+ // or the sign-in is canceled
+ id delegate_;
+ SEL finishedSelector_;
+
+#if NS_BLOCKS_AVAILABLE
+ void (^completionBlock_)(GTMOAuth2Authentication *, NSError *);
+#elif !__LP64__
+ // placeholders: for 32-bit builds, keep the size of the object's ivar section
+ // the same with and without blocks
+#ifndef __clang_analyzer__
+ id completionPlaceholder_;
+#endif
+#endif
+
+ // flag allowing application to quit during display of sign-in sheet on 10.6
+ // and later
+ BOOL shouldAllowApplicationTermination_;
+
+ // delegate method for handling URLs to be opened in external windows
+ SEL externalRequestSelector_;
+
+ BOOL isWindowShown_;
+
+ // paranoid flag to ensure we only close once during the sign-in sequence
+ BOOL hasDoneFinalRedirect_;
+
+ // paranoid flag to ensure we only call the user back once
+ BOOL hasCalledFinished_;
+
+ // if non-nil, we display as a sheet on the specified window
+ NSWindow *sheetModalForWindow_;
+
+ // if non-empty, the name of the application and service used for the
+ // keychain item
+ NSString *keychainItemName_;
+
+ // if non-nil, the html string to be displayed immediately upon opening
+ // of the web view
+ NSString *initialHTMLString_;
+
+ // if true, we allow default WebView handling of cookies, so the
+ // same user remains signed in each time the dialog is displayed
+ BOOL shouldPersistUser_;
+
+ // user-defined data
+ id userData_;
+ NSMutableDictionary *properties_;
+}
+
+// User interface elements
+@property (nonatomic, assign) IBOutlet NSButton *keychainCheckbox;
+@property (nonatomic, assign) IBOutlet WebView *webView;
+@property (nonatomic, assign) IBOutlet NSButton *webCloseButton;
+@property (nonatomic, assign) IBOutlet NSButton *webBackButton;
+
+// The application and service name to use for saving the auth tokens
+// to the keychain
+@property (nonatomic, copy) NSString *keychainItemName;
+
+// If true, the sign-in will remember which user was last signed in
+//
+// Defaults to false, so showing the sign-in window will always ask for
+// the username and password, rather than skip to the grant authorization
+// page. During development, it may be convenient to set this to true
+// to speed up signing in.
+@property (nonatomic, assign) BOOL shouldPersistUser;
+
+// Optional html string displayed immediately upon opening the web view
+//
+// This string is visible just until the sign-in web page loads, and
+// may be used for a "Loading..." type of message
+@property (nonatomic, copy) NSString *initialHTMLString;
+
+// The default timeout for an unreachable network during display of the
+// sign-in page is 30 seconds, after which the notification
+// kGTLOAuthNetworkLost is sent; set this to 0 to have no timeout
+@property (nonatomic, assign) NSTimeInterval networkLossTimeoutInterval;
+
+// On 10.6 and later, the sheet can allow application termination by calling
+// NSWindow's setPreventsApplicationTerminationWhenModal:
+@property (nonatomic, assign) BOOL shouldAllowApplicationTermination;
+
+// Selector for a delegate method to handle requests sent to an external
+// browser.
+//
+// Selector should have a signature matching
+// - (void)windowController:(GTMOAuth2WindowController *)controller
+// opensRequest:(NSURLRequest *)request;
+//
+// The controller's default behavior is to use NSWorkspace's openURL:
+@property (nonatomic, assign) SEL externalRequestSelector;
+
+// The underlying object to hold authentication tokens and authorize http
+// requests
+@property (nonatomic, retain, readonly) GTMOAuth2Authentication *authentication;
+
+// The underlying object which performs the sign-in networking sequence
+@property (nonatomic, retain, readonly) GTMOAuth2SignIn *signIn;
+
+// Any arbitrary data object the user would like the controller to retain
+@property (nonatomic, retain) id userData;
+
+// Stored property values are retained for the convenience of the caller
+- (void)setProperty:(id)obj forKey:(NSString *)key;
+- (id)propertyForKey:(NSString *)key;
+
+@property (nonatomic, retain) NSDictionary *properties;
+
+- (IBAction)closeWindow:(id)sender;
+
+// Create a controller for authenticating to Google services
+//
+// scope is the requested scope of authorization
+// (like "http://www.google.com/m8/feeds")
+//
+// keychainItemName is used for storing the token on the keychain,
+// and is required for the "remember for later" checkbox to be shown;
+// keychainItemName should be like "My Application: Google Contacts"
+// (or set to nil if no persistent keychain storage is desired)
+//
+// resourceBundle may be nil if the window is in the main bundle's nib
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName // may be nil
+ resourceBundle:(NSBundle *)bundle; // may be nil
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle;
+#endif
+
+// Create a controller for authenticating to non-Google services, taking
+// explicit endpoint URLs and an authentication object
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName // may be nil
+ resourceBundle:(NSBundle *)bundle; // may be nil
+
+// This is the designated initializer
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle;
+
+// Entry point to begin displaying the sign-in window
+//
+// the finished selector should have a signature matching
+// - (void)windowController:(GTMOAuth2WindowController *)windowController
+// finishedWithAuth:(GTMOAuth2Authentication *)auth
+// error:(NSError *)error {
+//
+// Once the finished method has been invoked with no error, the auth object
+// may be used to authorize requests (refreshing the access token, if necessary,
+// and adding the auth header) like:
+//
+// [authorizer authorizeRequest:myNSMutableURLRequest]
+// delegate:self
+// didFinishSelector:@selector(auth:finishedWithError:)];
+//
+// or can be stored in a GTL service object like
+// GTLServiceGoogleContact *service = [self contactService];
+// [service setAuthorizer:auth];
+//
+// The delegate is retained only until the finished selector is invoked or
+// the sign-in is canceled
+- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+#if NS_BLOCKS_AVAILABLE
+- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
+ completionHandler:(void (^)(GTMOAuth2Authentication *auth, NSError *error))handler;
+#endif
+
+- (void)cancelSigningIn;
+
+// Subclasses may override authNibName to specify a custom name
++ (NSString *)authNibName;
+
+// apps may replace the sign-in class with their own subclass of it
++ (Class)signInClass;
++ (void)setSignInClass:(Class)theClass;
+
+// Revocation of an authorized token from Google
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth;
+#endif
+
+// Keychain
+//
+// The keychain checkbox is shown if the keychain application service
+// name (typically set in the initWithScope: method) is non-empty
+//
+
+// Create an authentication object for Google services from the access
+// token and secret stored in the keychain; if no token is available, return
+// an unauthorized auth object
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret;
+#endif
+
+// Add tokens from the keychain, if available, to the authentication object
+//
+// returns YES if the authentication object was authorized from the keychain
++ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth;
+
+// Method for deleting the stored access token and secret, useful for "signing
+// out"
++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName;
+
+// Method for saving the stored access token and secret; typically, this method
+// is used only by the window controller
++ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth;
+@end
+
+#endif // #if !TARGET_OS_IPHONE
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m
new file mode 100644
index 00000000..948975bf
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m
@@ -0,0 +1,727 @@
+/* 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#if !TARGET_OS_IPHONE
+
+#import "GTMOAuth2WindowController.h"
+
+@interface GTMOAuth2WindowController ()
+@property (nonatomic, retain) GTMOAuth2SignIn *signIn;
+@property (nonatomic, copy) NSURLRequest *initialRequest;
+@property (nonatomic, retain) GTMCookieStorage *cookieStorage;
+@property (nonatomic, retain) NSWindow *sheetModalForWindow;
+
+- (void)signInCommonForWindow:(NSWindow *)parentWindowOrNil;
+- (void)setupSheetTerminationHandling;
+- (void)destroyWindow;
+- (void)handlePrematureWindowClose;
+- (BOOL)shouldUseKeychain;
+- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request;
+- (void)signIn:(GTMOAuth2SignIn *)signIn finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error;
+- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
+
+- (void)handleCookiesForResponse:(NSURLResponse *)response;
+- (NSURLRequest *)addCookiesToRequest:(NSURLRequest *)request;
+@end
+
+const char *kKeychainAccountName = "OAuth";
+
+@implementation GTMOAuth2WindowController
+
+// IBOutlets
+@synthesize keychainCheckbox = keychainCheckbox_,
+ webView = webView_,
+ webCloseButton = webCloseButton_,
+ webBackButton = webBackButton_;
+
+// regular ivars
+@synthesize signIn = signIn_,
+ initialRequest = initialRequest_,
+ cookieStorage = cookieStorage_,
+ sheetModalForWindow = sheetModalForWindow_,
+ keychainItemName = keychainItemName_,
+ initialHTMLString = initialHTMLString_,
+ shouldAllowApplicationTermination = shouldAllowApplicationTermination_,
+ externalRequestSelector = externalRequestSelector_,
+ shouldPersistUser = shouldPersistUser_,
+ userData = userData_,
+ properties = properties_;
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+// Create a controller for authenticating to Google services
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle {
+ return [[[self alloc] initWithScope:scope
+ clientID:clientID
+ clientSecret:clientSecret
+ keychainItemName:keychainItemName
+ resourceBundle:bundle] autorelease];
+}
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle {
+ Class signInClass = [[self class] signInClass];
+ GTMOAuth2Authentication *auth;
+ auth = [signInClass standardGoogleAuthenticationForScope:scope
+ clientID:clientID
+ clientSecret:clientSecret];
+ NSURL *authorizationURL = [signInClass googleAuthorizationURL];
+ return [self initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ resourceBundle:bundle];
+}
+#endif
+
+// Create a controller for authenticating to any service
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle {
+ return [[[self alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ resourceBundle:bundle] autorelease];
+}
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle {
+ if (bundle == nil) {
+ bundle = [NSBundle mainBundle];
+ }
+
+ NSString *nibName = [[self class] authNibName];
+ NSString *nibPath = [bundle pathForResource:nibName
+ ofType:@"nib"];
+ self = [super initWithWindowNibPath:nibPath
+ owner:self];
+ if (self != nil) {
+ // use the supplied auth and OAuth endpoint URLs
+ Class signInClass = [[self class] signInClass];
+ signIn_ = [[signInClass alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ delegate:self
+ webRequestSelector:@selector(signIn:displayRequest:)
+ finishedSelector:@selector(signIn:finishedWithAuth:error:)];
+ keychainItemName_ = [keychainItemName copy];
+
+ // create local, temporary storage for WebKit cookies
+ cookieStorage_ = [[GTMCookieStorage alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [signIn_ release];
+ [initialRequest_ release];
+ [cookieStorage_ release];
+ [delegate_ release];
+#if NS_BLOCKS_AVAILABLE
+ [completionBlock_ release];
+#endif
+ [sheetModalForWindow_ release];
+ [keychainItemName_ release];
+ [initialHTMLString_ release];
+ [userData_ release];
+ [properties_ release];
+
+ [super dealloc];
+}
+
+- (void)awakeFromNib {
+ // load the requested initial sign-in page
+ [self.webView setResourceLoadDelegate:self];
+ [self.webView setPolicyDelegate:self];
+
+ // the app may prefer some html other than blank white to be displayed
+ // before the sign-in web page loads
+ NSString *html = self.initialHTMLString;
+ if ([html length] > 0) {
+ [[self.webView mainFrame] loadHTMLString:html baseURL:nil];
+ }
+
+ // hide the keychain checkbox if we're not supporting keychain
+ BOOL hideKeychainCheckbox = ![self shouldUseKeychain];
+
+ const NSTimeInterval kJanuary2011 = 1293840000;
+ BOOL isDateValid = ([[NSDate date] timeIntervalSince1970] > kJanuary2011);
+ if (isDateValid) {
+ // start the asynchronous load of the sign-in web page
+ [[self.webView mainFrame] performSelector:@selector(loadRequest:)
+ withObject:self.initialRequest
+ afterDelay:0.01
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ } else {
+ // clock date is invalid, so signing in would fail with an unhelpful error
+ // from the server. Warn the user in an html string showing a watch icon,
+ // question mark, and the system date and time. Hopefully this will clue
+ // in brighter users, or at least let them make a useful screenshot to show
+ // to developers.
+ //
+ // Even better is for apps to check the system clock and show some more
+ // helpful, localized instructions for users; this is really a fallback.
+ NSString *const htmlTemplate = @"<html><body><div align=center><font size='7'>"
+ @"&#x231A; ?<br><i>System Clock Incorrect</i><br>%@"
+ @"</font></div></body></html>";
+ NSString *errHTML = [NSString stringWithFormat:htmlTemplate, [NSDate date]];
+
+ [[webView_ mainFrame] loadHTMLString:errHTML baseURL:nil];
+ hideKeychainCheckbox = YES;
+ }
+
+#if DEBUG
+ // Verify that Javascript is enabled
+ BOOL hasJS = [[webView_ preferences] isJavaScriptEnabled];
+ NSAssert(hasJS, @"GTMOAuth2: Javascript is required");
+#endif
+
+ [keychainCheckbox_ setHidden:hideKeychainCheckbox];
+}
+
++ (NSString *)authNibName {
+ // subclasses may override this to specify a custom nib name
+ return @"GTMOAuth2Window";
+}
+
+#pragma mark -
+
+- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ // check the selector on debug builds
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector,
+ @encode(GTMOAuth2WindowController *), @encode(GTMOAuth2Authentication *),
+ @encode(NSError *), 0);
+
+ delegate_ = [delegate retain];
+ finishedSelector_ = finishedSelector;
+
+ [self signInCommonForWindow:parentWindowOrNil];
+}
+
+#if NS_BLOCKS_AVAILABLE
+- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
+ completionHandler:(void (^)(GTMOAuth2Authentication *, NSError *))handler {
+ completionBlock_ = [handler copy];
+
+ [self signInCommonForWindow:parentWindowOrNil];
+}
+#endif
+
+- (void)signInCommonForWindow:(NSWindow *)parentWindowOrNil {
+ self.sheetModalForWindow = parentWindowOrNil;
+ hasDoneFinalRedirect_ = NO;
+ hasCalledFinished_ = NO;
+
+ [self.signIn startSigningIn];
+}
+
+- (void)cancelSigningIn {
+ // The user has explicitly asked us to cancel signing in
+ // (so no further callback is required)
+ hasCalledFinished_ = YES;
+
+ [delegate_ autorelease];
+ delegate_ = nil;
+
+#if NS_BLOCKS_AVAILABLE
+ [completionBlock_ autorelease];
+ completionBlock_ = nil;
+#endif
+
+ // The signIn object's cancel method will close the window
+ [self.signIn cancelSigningIn];
+ hasDoneFinalRedirect_ = YES;
+}
+
+- (IBAction)closeWindow:(id)sender {
+ // dismiss the window/sheet before we call back the client
+ [self destroyWindow];
+ [self handlePrematureWindowClose];
+}
+
+#pragma mark SignIn callbacks
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request {
+ // this is the signIn object's webRequest method, telling the controller
+ // to either display the request in the webview, or close the window
+ //
+ // All web requests and all window closing goes through this routine
+
+#if DEBUG
+ if ((isWindowShown_ && request != nil)
+ || (!isWindowShown_ && request == nil)) {
+ NSLog(@"Window state unexpected for request %@", [request URL]);
+ return;
+ }
+#endif
+
+ if (request != nil) {
+ // display the request
+ self.initialRequest = request;
+
+ NSWindow *parentWindow = self.sheetModalForWindow;
+ if (parentWindow) {
+ [self setupSheetTerminationHandling];
+
+ NSWindow *sheet = [self window];
+ [NSApp beginSheet:sheet
+ modalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ contextInfo:nil];
+ } else {
+ // modeless
+ [self showWindow:self];
+ }
+ isWindowShown_ = YES;
+ } else {
+ // request was nil
+ [self destroyWindow];
+ }
+}
+
+- (void)setupSheetTerminationHandling {
+ NSWindow *sheet = [self window];
+
+ SEL sel = @selector(setPreventsApplicationTerminationWhenModal:);
+ if ([sheet respondsToSelector:sel]) {
+ // setPreventsApplicationTerminationWhenModal is available in NSWindow
+ // on 10.6 and later
+ BOOL boolVal = !self.shouldAllowApplicationTermination;
+ NSMethodSignature *sig = [sheet methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:sheet];
+ [invocation setArgument:&boolVal atIndex:2];
+ [invocation invoke];
+ }
+}
+
+- (void)destroyWindow {
+ // no request; close the window
+
+ // Avoid more callbacks after the close happens, as the window
+ // controller may be gone.
+ [self.webView stopLoading:nil];
+
+ NSWindow *parentWindow = self.sheetModalForWindow;
+ if (parentWindow) {
+ [NSApp endSheet:[self window]];
+ } else {
+ // defer closing the window, in case we're responding to some window event
+ [[self window] performSelector:@selector(close)
+ withObject:nil
+ afterDelay:0.1
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+
+ }
+ isWindowShown_ = NO;
+}
+
+- (void)handlePrematureWindowClose {
+ if (!hasDoneFinalRedirect_) {
+ // tell the sign-in object to tell the user's finished method
+ // that we're done
+ [self.signIn windowWasClosed];
+ hasDoneFinalRedirect_ = YES;
+ }
+}
+
+- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
+ [sheet orderOut:self];
+
+ self.sheetModalForWindow = nil;
+}
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error {
+ if (!hasCalledFinished_) {
+ hasCalledFinished_ = YES;
+
+ if (error == nil) {
+ BOOL shouldUseKeychain = [self shouldUseKeychain];
+ if (shouldUseKeychain) {
+ BOOL canAuthorize = auth.canAuthorize;
+ BOOL isKeychainChecked = ([keychainCheckbox_ state] == NSOnState);
+
+ NSString *keychainItemName = self.keychainItemName;
+
+ if (isKeychainChecked && canAuthorize) {
+ // save the auth params in the keychain
+ [[self class] saveAuthToKeychainForName:keychainItemName
+ authentication:auth];
+ } else {
+ // remove the auth params from the keychain
+ [[self class] removeAuthFromKeychainForName:keychainItemName];
+ }
+ }
+ }
+
+ if (delegate_ && finishedSelector_) {
+ SEL sel = finishedSelector_;
+ NSMethodSignature *sig = [delegate_ methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:delegate_];
+ [invocation setArgument:&self atIndex:2];
+ [invocation setArgument:&auth atIndex:3];
+ [invocation setArgument:&error atIndex:4];
+ [invocation invoke];
+ }
+
+ [delegate_ autorelease];
+ delegate_ = nil;
+
+#if NS_BLOCKS_AVAILABLE
+ if (completionBlock_) {
+ completionBlock_(auth, error);
+
+ // release the block here to avoid a retain loop on the controller
+ [completionBlock_ autorelease];
+ completionBlock_ = nil;
+ }
+#endif
+ }
+}
+
+static Class gSignInClass = Nil;
+
++ (Class)signInClass {
+ if (gSignInClass == Nil) {
+ gSignInClass = [GTMOAuth2SignIn class];
+ }
+ return gSignInClass;
+}
+
++ (void)setSignInClass:(Class)theClass {
+ gSignInClass = theClass;
+}
+
+#pragma mark Token Revocation
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth {
+ [[self signInClass] revokeTokenForGoogleAuthentication:auth];
+}
+#endif
+
+#pragma mark WebView methods
+
+- (NSURLRequest *)webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)dataSource {
+ // override WebKit's cookie storage with our own to avoid cookie persistence
+ // across sign-ins and interaction with the Safari browser's sign-in state
+ [self handleCookiesForResponse:redirectResponse];
+ request = [self addCookiesToRequest:request];
+
+ if (!hasDoneFinalRedirect_) {
+ hasDoneFinalRedirect_ = [self.signIn requestRedirectedToRequest:request];
+ if (hasDoneFinalRedirect_) {
+ // signIn has told the window to close
+ return nil;
+ }
+ }
+ return request;
+}
+
+- (void)webView:(WebView *)sender resource:(id)identifier didReceiveResponse:(NSURLResponse *)response fromDataSource:(WebDataSource *)dataSource {
+ // override WebKit's cookie storage with our own
+ [self handleCookiesForResponse:response];
+}
+
+- (void)webView:(WebView *)sender resource:(id)identifier didFinishLoadingFromDataSource:(WebDataSource *)dataSource {
+ NSString *title = [sender stringByEvaluatingJavaScriptFromString:@"document.title"];
+ if ([title length] > 0) {
+ [self.signIn titleChanged:title];
+ }
+
+ [signIn_ cookiesChanged:(NSHTTPCookieStorage *)cookieStorage_];
+}
+
+- (void)webView:(WebView *)sender resource:(id)identifier didFailLoadingWithError:(NSError *)error fromDataSource:(WebDataSource *)dataSource {
+ [self.signIn loadFailedWithError:error];
+}
+
+- (void)windowWillClose:(NSNotification *)note {
+ if (isWindowShown_) {
+ [self handlePrematureWindowClose];
+ }
+ isWindowShown_ = NO;
+}
+
+- (void)webView:(WebView *)webView
+decidePolicyForNewWindowAction:(NSDictionary *)actionInformation
+ request:(NSURLRequest *)request
+ newFrameName:(NSString *)frameName
+decisionListener:(id<WebPolicyDecisionListener>)listener {
+ SEL sel = self.externalRequestSelector;
+ if (sel) {
+ [delegate_ performSelector:sel
+ withObject:self
+ withObject:request];
+ } else {
+ // default behavior is to open the URL in NSWorkspace's default browser
+ NSURL *url = [request URL];
+ [[NSWorkspace sharedWorkspace] openURL:url];
+ }
+ [listener ignore];
+}
+
+#pragma mark Cookie management
+
+// Rather than let the WebView use Safari's default cookie storage, we intercept
+// requests and response to segregate and later discard cookies from signing in.
+//
+// This allows the application to actually sign out by discarding the auth token
+// rather than the user being kept signed in by the cookies.
+
+- (void)handleCookiesForResponse:(NSURLResponse *)response {
+ if (self.shouldPersistUser) {
+ // we'll let WebKit handle the cookies; they'll persist across apps
+ // and across runs of this app
+ return;
+ }
+
+ if ([response respondsToSelector:@selector(allHeaderFields)]) {
+ // grab the cookies from the header as NSHTTPCookies and store them locally
+ NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
+ if (headers) {
+ NSURL *url = [response URL];
+ NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headers
+ forURL:url];
+ if ([cookies count] > 0) {
+ [cookieStorage_ setCookies:cookies];
+ }
+ }
+ }
+}
+
+- (NSURLRequest *)addCookiesToRequest:(NSURLRequest *)request {
+ if (self.shouldPersistUser) {
+ // we'll let WebKit handle the cookies; they'll persist across apps
+ // and across runs of this app
+ return request;
+ }
+
+ // override WebKit's usual automatic storage of cookies
+ NSMutableURLRequest *mutableRequest = [[request mutableCopy] autorelease];
+ [mutableRequest setHTTPShouldHandleCookies:NO];
+
+ // add our locally-stored cookies for this URL, if any
+ NSArray *cookies = [cookieStorage_ cookiesForURL:[request URL]];
+ if ([cookies count] > 0) {
+ NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
+ NSString *cookieHeader = [headers objectForKey:@"Cookie"];
+ if (cookieHeader) {
+ [mutableRequest setValue:cookieHeader forHTTPHeaderField:@"Cookie"];
+ }
+ }
+ return mutableRequest;
+}
+
+#pragma mark Keychain support
+
++ (NSString *)prefsKeyForName:(NSString *)keychainItemName {
+ NSString *result = [@"OAuth2: " stringByAppendingString:keychainItemName];
+ return result;
+}
+
++ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth {
+
+ [self removeAuthFromKeychainForName:keychainItemName];
+
+ // don't save unless we have a token that can really authorize requests
+ if (!auth.canAuthorize) return NO;
+
+ // make a response string containing the values we want to save
+ NSString *password = [auth persistenceResponseString];
+
+ SecKeychainRef defaultKeychain = NULL;
+ SecKeychainItemRef *dontWantItemRef= NULL;
+ const char *utf8ServiceName = [keychainItemName UTF8String];
+ const char *utf8Password = [password UTF8String];
+
+ OSStatus err = SecKeychainAddGenericPassword(defaultKeychain,
+ (UInt32) strlen(utf8ServiceName), utf8ServiceName,
+ (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
+ (UInt32) strlen(utf8Password), utf8Password,
+ dontWantItemRef);
+ BOOL didSucceed = (err == noErr);
+ if (didSucceed) {
+ // write to preferences that we have a keychain item (so we know later
+ // that we can read from the keychain without raising a permissions dialog)
+ NSString *prefKey = [self prefsKeyForName:keychainItemName];
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ [defaults setBool:YES forKey:prefKey];
+ }
+
+ return didSucceed;
+}
+
++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName {
+
+ SecKeychainRef defaultKeychain = NULL;
+ SecKeychainItemRef itemRef = NULL;
+ const char *utf8ServiceName = [keychainItemName UTF8String];
+
+ // we don't really care about the password here, we just want to
+ // get the SecKeychainItemRef so we can delete it.
+ OSStatus err = SecKeychainFindGenericPassword (defaultKeychain,
+ (UInt32) strlen(utf8ServiceName), utf8ServiceName,
+ (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
+ 0, NULL, // ignore password
+ &itemRef);
+ if (err != noErr) {
+ // failure to find is success
+ return YES;
+ } else {
+ // found something, so delete it
+ err = SecKeychainItemDelete(itemRef);
+ CFRelease(itemRef);
+
+ // remove our preference key
+ NSString *prefKey = [self prefsKeyForName:keychainItemName];
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ [defaults removeObjectForKey:prefKey];
+
+ return (err == noErr);
+ }
+}
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret {
+ Class signInClass = [self signInClass];
+ NSURL *tokenURL = [signInClass googleTokenURL];
+ NSString *redirectURI = [signInClass nativeClientRedirectURI];
+
+ GTMOAuth2Authentication *auth;
+ auth = [GTMOAuth2Authentication authenticationWithServiceProvider:kGTMOAuth2ServiceProviderGoogle
+ tokenURL:tokenURL
+ redirectURI:redirectURI
+ clientID:clientID
+ clientSecret:clientSecret];
+
+ [GTMOAuth2WindowController authorizeFromKeychainForName:keychainItemName
+ authentication:auth];
+ return auth;
+}
+#endif
+
++ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)newAuth {
+ [newAuth setAccessToken:nil];
+
+ // before accessing the keychain, check preferences to verify that we've
+ // previously saved a token to the keychain (so we don't needlessly raise
+ // a keychain access permission dialog)
+ NSString *prefKey = [self prefsKeyForName:keychainItemName];
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ BOOL flag = [defaults boolForKey:prefKey];
+ if (!flag) {
+ return NO;
+ }
+
+ BOOL didGetTokens = NO;
+
+ SecKeychainRef defaultKeychain = NULL;
+ const char *utf8ServiceName = [keychainItemName UTF8String];
+ SecKeychainItemRef *dontWantItemRef = NULL;
+
+ void *passwordBuff = NULL;
+ UInt32 passwordBuffLength = 0;
+
+ OSStatus err = SecKeychainFindGenericPassword(defaultKeychain,
+ (UInt32) strlen(utf8ServiceName), utf8ServiceName,
+ (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
+ &passwordBuffLength, &passwordBuff,
+ dontWantItemRef);
+ if (err == noErr && passwordBuff != NULL) {
+
+ NSString *password = [[[NSString alloc] initWithBytes:passwordBuff
+ length:passwordBuffLength
+ encoding:NSUTF8StringEncoding] autorelease];
+
+ // free the password buffer that was allocated above
+ SecKeychainItemFreeContent(NULL, passwordBuff);
+
+ if (password != nil) {
+ [newAuth setKeysForResponseString:password];
+ didGetTokens = YES;
+ }
+ }
+ return didGetTokens;
+}
+
+#pragma mark User Properties
+
+- (void)setProperty:(id)obj forKey:(NSString *)key {
+ if (obj == nil) {
+ // User passed in nil, so delete the property
+ [properties_ removeObjectForKey:key];
+ } else {
+ // Be sure the property dictionary exists
+ if (properties_ == nil) {
+ [self setProperties:[NSMutableDictionary dictionary]];
+ }
+ [properties_ setObject:obj forKey:key];
+ }
+}
+
+- (id)propertyForKey:(NSString *)key {
+ id obj = [properties_ objectForKey:key];
+
+ // Be sure the returned pointer has the life of the autorelease pool,
+ // in case self is released immediately
+ return [[obj retain] autorelease];
+}
+
+#pragma mark Accessors
+
+- (GTMOAuth2Authentication *)authentication {
+ return self.signIn.authentication;
+}
+
+- (void)setNetworkLossTimeoutInterval:(NSTimeInterval)val {
+ self.signIn.networkLossTimeoutInterval = val;
+}
+
+- (NSTimeInterval)networkLossTimeoutInterval {
+ return self.signIn.networkLossTimeoutInterval;
+}
+
+- (BOOL)shouldUseKeychain {
+ NSString *name = self.keychainItemName;
+ return ([name length] > 0);
+}
+
+@end
+
+#endif // #if !TARGET_OS_IPHONE
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.h b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.h
new file mode 100644
index 00000000..33adbf71
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.h
@@ -0,0 +1,376 @@
+/* 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.
+ */
+
+//
+// GTMOAuth2ViewControllerTouch.h
+//
+// This view controller for iPhone handles sign-in via OAuth to Google or
+// other services.
+//
+// This controller is not reusable; create a new instance of this controller
+// every time the user will sign in.
+//
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#import <Foundation/Foundation.h>
+
+#if TARGET_OS_IPHONE
+
+#import <UIKit/UIKit.h>
+
+#import "GTMOAuth2Authentication.h"
+
+#undef _EXTERN
+#undef _INITIALIZE_AS
+#ifdef GTMOAUTH2VIEWCONTROLLERTOUCH_DEFINE_GLOBALS
+#define _EXTERN
+#define _INITIALIZE_AS(x) =x
+#else
+#define _EXTERN extern
+#define _INITIALIZE_AS(x)
+#endif
+
+_EXTERN NSString* const kGTMOAuth2KeychainErrorDomain _INITIALIZE_AS(@"com.google.GTMOAuthKeychain");
+
+@class GTMOAuth2SignIn;
+@class GTMOAuth2ViewControllerTouch;
+
+typedef void (^GTMOAuth2ViewControllerCompletionHandler)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error);
+
+@interface GTMOAuth2ViewControllerTouch : UIViewController<UINavigationControllerDelegate, UIWebViewDelegate> {
+ @private
+ UIButton *backButton_;
+ UIButton *forwardButton_;
+ UIActivityIndicatorView *initialActivityIndicator_;
+ UIView *navButtonsView_;
+ UIBarButtonItem *rightBarButtonItem_;
+ UIWebView *webView_;
+
+ // The object responsible for the sign-in networking sequence; it holds
+ // onto the authentication object as well.
+ GTMOAuth2SignIn *signIn_;
+
+ // the page request to load when awakeFromNib occurs
+ NSURLRequest *request_;
+
+ // The user we're calling back
+ //
+ // The delegate is retained only until the callback is invoked
+ // or the sign-in is canceled
+ id delegate_;
+ SEL finishedSelector_;
+
+#if NS_BLOCKS_AVAILABLE
+ GTMOAuth2ViewControllerCompletionHandler completionBlock_;
+
+ void (^popViewBlock_)(void);
+#endif
+
+ NSString *keychainItemName_;
+ CFTypeRef keychainItemAccessibility_;
+
+ // if non-nil, the html string to be displayed immediately upon opening
+ // of the web view
+ NSString *initialHTMLString_;
+
+ // set to 1 or -1 if the user sets the showsInitialActivityIndicator
+ // property
+ int mustShowActivityIndicator_;
+
+ // if non-nil, the URL for which cookies will be deleted when the
+ // browser view is dismissed
+ NSURL *browserCookiesURL_;
+
+ id userData_;
+ NSMutableDictionary *properties_;
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
+ // We delegate the decision to our owning NavigationController (if any).
+ // But, the NavigationController will call us back, and ask us.
+ // BOOL keeps us from infinite looping.
+ BOOL isInsideShouldAutorotateToInterfaceOrientation_;
+#endif
+
+ // YES, when view first shown in this signIn session.
+ BOOL isViewShown_;
+
+ // YES, after the view has fully transitioned in.
+ BOOL didViewAppear_;
+
+ // YES between sends of start and stop notifications
+ BOOL hasNotifiedWebViewStartedLoading_;
+
+ // To prevent us from calling our delegate's selector more than once.
+ BOOL hasCalledFinished_;
+
+ // Set in a webView callback.
+ BOOL hasDoneFinalRedirect_;
+
+ // Set during the pop initiated by the sign-in object; otherwise,
+ // viewWillDisappear indicates that some external change of the view
+ // has stopped the sign-in.
+ BOOL didDismissSelf_;
+}
+
+// the application and service name to use for saving the auth tokens
+// to the keychain
+@property (nonatomic, copy) NSString *keychainItemName;
+
+// the keychain item accessibility is a system constant for use
+// with kSecAttrAccessible.
+//
+// Since it's a system constant, we do not need to retain it.
+@property (nonatomic, assign) CFTypeRef keychainItemAccessibility;
+
+// optional html string displayed immediately upon opening the web view
+//
+// This string is visible just until the sign-in web page loads, and
+// may be used for a "Loading..." type of message or to set the
+// initial view color
+@property (nonatomic, copy) NSString *initialHTMLString;
+
+// an activity indicator shows during initial webview load when no initial HTML
+// string is specified, but the activity indicator can be forced to be shown
+// with this property
+@property (nonatomic, assign) BOOL showsInitialActivityIndicator;
+
+// the underlying object to hold authentication tokens and authorize http
+// requests
+@property (nonatomic, retain, readonly) GTMOAuth2Authentication *authentication;
+
+// the underlying object which performs the sign-in networking sequence
+@property (nonatomic, retain, readonly) GTMOAuth2SignIn *signIn;
+
+// user interface elements
+@property (nonatomic, retain) IBOutlet UIButton *backButton;
+@property (nonatomic, retain) IBOutlet UIButton *forwardButton;
+@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *initialActivityIndicator;
+@property (nonatomic, retain) IBOutlet UIView *navButtonsView;
+@property (nonatomic, retain) IBOutlet UIBarButtonItem *rightBarButtonItem;
+@property (nonatomic, retain) IBOutlet UIWebView *webView;
+
+#if NS_BLOCKS_AVAILABLE
+// An optional block to be called when the view should be popped. If not set,
+// the view controller will use its navigation controller to pop the view.
+@property (nonatomic, copy) void (^popViewBlock)(void);
+#endif
+
+// 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 set, cookies are deleted for this URL when the view is hidden
+//
+// For Google sign-ins, this is set by default to https://google.com/accounts
+// but it may be explicitly set to nil to disable clearing of browser cookies
+@property (nonatomic, retain) NSURL *browserCookiesURL;
+
+// userData is retained for the convenience of the caller
+@property (nonatomic, retain) id userData;
+
+// Stored property values are retained for the convenience of the caller
+- (void)setProperty:(id)obj forKey:(NSString *)key;
+- (id)propertyForKey:(NSString *)key;
+
+@property (nonatomic, retain) NSDictionary *properties;
+
+// Method for creating a controller to authenticate to Google services
+//
+// scope is the requested scope of authorization
+// (like "http://www.google.com/m8/feeds")
+//
+// keychain item name is used for storing the token on the keychain,
+// keychainItemName should be like "My Application: Google Latitude"
+// (or set to nil if no persistent keychain storage is desired)
+//
+// the delegate is retained only until the finished selector is invoked
+// or the sign-in is canceled
+//
+// If you don't like the default nibName and bundle, you can change them
+// using the UIViewController properties once you've made one of these.
+//
+// finishedSelector is called after authentication completes. It should follow
+// this signature.
+//
+// - (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
+// finishedWithAuth:(GTMOAuth2Authentication *)auth
+// error:(NSError *)error;
+//
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+#if NS_BLOCKS_AVAILABLE
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler;
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler;
+#endif
+#endif
+
+// Create a controller for authenticating to non-Google services, taking
+// explicit endpoint URLs and an authentication object
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName // may be nil
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+// This is the designated initializer
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+#if NS_BLOCKS_AVAILABLE
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName // may be nil
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler;
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler;
+#endif
+
+// subclasses may override authNibName to specify a custom name
++ (NSString *)authNibName;
+
+// subclasses may override authNibBundle to specify a custom bundle
++ (NSBundle *)authNibBundle;
+
+// subclasses may override setUpNavigation to provide their own navigation
+// controls
+- (void)setUpNavigation;
+
+// apps may replace the sign-in class with their own subclass of it
++ (Class)signInClass;
++ (void)setSignInClass:(Class)theClass;
+
+- (void)cancelSigningIn;
+
+// revocation of an authorized token from Google
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth;
+#endif
+
+//
+// Keychain
+//
+
+// create an authentication object for Google services from the access
+// token and secret stored in the keychain; if no token is available, return
+// an unauthorized auth object. OK to pass NULL for the error parameter.
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ error:(NSError **)error;
+// Equivalent to calling the method above with a NULL error parameter.
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret;
+#endif
+
+// add tokens from the keychain, if available, to the authentication object
+//
+// returns YES if the authentication object was authorized from the keychain
++ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth
+ error:(NSError **)error;
+
+// method for deleting the stored access token and secret, useful for "signing
+// out"
++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName;
+
+// method for saving the stored access token and secret
+//
+// returns YES if the save was successful. OK to pass NULL for the error
+// parameter.
++ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
+ accessibility:(CFTypeRef)accessibility
+ authentication:(GTMOAuth2Authentication *)auth
+ error:(NSError **)error;
+
+// older version, defaults to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
++ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth;
+
+@end
+
+// To function, GTMOAuth2ViewControllerTouch needs a certain amount of access
+// to the iPhone's keychain. To keep things simple, its keychain access is
+// broken out into a helper class. We declare it here in case you'd like to use
+// it too, to store passwords.
+
+enum {
+ kGTMOAuth2KeychainErrorBadArguments = -1301,
+ kGTMOAuth2KeychainErrorNoPassword = -1302
+};
+
+
+@interface GTMOAuth2Keychain : NSObject
+
++ (GTMOAuth2Keychain *)defaultKeychain;
+
+// OK to pass nil for the error parameter.
+- (NSString *)passwordForService:(NSString *)service
+ account:(NSString *)account
+ error:(NSError **)error;
+
+// OK to pass nil for the error parameter.
+- (BOOL)removePasswordForService:(NSString *)service
+ account:(NSString *)account
+ error:(NSError **)error;
+
+// OK to pass nil for the error parameter.
+//
+// accessibility should be one of the constants for kSecAttrAccessible
+// such as kSecAttrAccessibleWhenUnlocked
+- (BOOL)setPassword:(NSString *)password
+ forService:(NSString *)service
+ accessibility:(CFTypeRef)accessibility
+ account:(NSString *)account
+ error:(NSError **)error;
+
+// For unit tests: allow setting a mock object
++ (void)setDefaultKeychain:(GTMOAuth2Keychain *)keychain;
+
+@end
+
+#endif // TARGET_OS_IPHONE
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.m b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.m
new file mode 100644
index 00000000..3a18104d
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.m
@@ -0,0 +1,1070 @@
+/* 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.
+ */
+
+//
+// GTMOAuth2ViewControllerTouch.m
+//
+
+#import <Foundation/Foundation.h>
+#import <Security/Security.h>
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#if TARGET_OS_IPHONE
+
+#define GTMOAUTH2VIEWCONTROLLERTOUCH_DEFINE_GLOBALS 1
+#import "GTMOAuth2ViewControllerTouch.h"
+
+#import "GTMOAuth2SignIn.h"
+#import "GTMOAuth2Authentication.h"
+
+static NSString * const kGTMOAuth2AccountName = @"OAuth";
+static GTMOAuth2Keychain* sDefaultKeychain = nil;
+
+@interface GTMOAuth2ViewControllerTouch()
+
+@property (nonatomic, copy) NSURLRequest *request;
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request;
+- (void)signIn:(GTMOAuth2SignIn *)signIn
+finishedWithAuth:(GTMOAuth2Authentication *)auth
+ error:(NSError *)error;
+- (BOOL)isNavigationBarTranslucent;
+- (void)moveWebViewFromUnderNavigationBar;
+- (void)popView;
+- (void)clearBrowserCookies;
+@end
+
+@implementation GTMOAuth2ViewControllerTouch
+
+// IBOutlets
+@synthesize request = request_,
+ backButton = backButton_,
+ forwardButton = forwardButton_,
+ navButtonsView = navButtonsView_,
+ rightBarButtonItem = rightBarButtonItem_,
+ webView = webView_,
+ initialActivityIndicator = initialActivityIndicator_;
+
+@synthesize keychainItemName = keychainItemName_,
+ keychainItemAccessibility = keychainItemAccessibility_,
+ initialHTMLString = initialHTMLString_,
+ browserCookiesURL = browserCookiesURL_,
+ signIn = signIn_,
+ userData = userData_,
+ properties = properties_;
+
+#if NS_BLOCKS_AVAILABLE
+@synthesize popViewBlock = popViewBlock_;
+#endif
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ return [[[self alloc] initWithScope:scope
+ clientID:clientID
+ clientSecret:clientSecret
+ keychainItemName:keychainItemName
+ delegate:delegate
+ finishedSelector:finishedSelector] autorelease];
+}
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ // convenient entry point for Google authentication
+
+ Class signInClass = [[self class] signInClass];
+
+ GTMOAuth2Authentication *auth;
+ auth = [signInClass standardGoogleAuthenticationForScope:scope
+ clientID:clientID
+ clientSecret:clientSecret];
+ NSURL *authorizationURL = [signInClass googleAuthorizationURL];
+ return [self initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ delegate:delegate
+ finishedSelector:finishedSelector];
+}
+
+#if NS_BLOCKS_AVAILABLE
+
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler {
+ return [[[self alloc] initWithScope:scope
+ clientID:clientID
+ clientSecret:clientSecret
+ keychainItemName:keychainItemName
+ completionHandler:handler] autorelease];
+}
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler {
+ // convenient entry point for Google authentication
+
+ Class signInClass = [[self class] signInClass];
+
+ GTMOAuth2Authentication *auth;
+ auth = [signInClass standardGoogleAuthenticationForScope:scope
+ clientID:clientID
+ clientSecret:clientSecret];
+ NSURL *authorizationURL = [signInClass googleAuthorizationURL];
+ self = [self initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ delegate:nil
+ finishedSelector:NULL];
+ if (self) {
+ completionBlock_ = [handler copy];
+ }
+ return self;
+}
+
+#endif // NS_BLOCKS_AVAILABLE
+#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ return [[[self alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ delegate:delegate
+ finishedSelector:finishedSelector] autorelease];
+}
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+
+ NSString *nibName = [[self class] authNibName];
+ NSBundle *nibBundle = [[self class] authNibBundle];
+
+ self = [super initWithNibName:nibName bundle:nibBundle];
+ if (self != nil) {
+ delegate_ = [delegate retain];
+ finishedSelector_ = finishedSelector;
+
+ Class signInClass = [[self class] signInClass];
+
+ // use the supplied auth and OAuth endpoint URLs
+ signIn_ = [[signInClass alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ delegate:self
+ webRequestSelector:@selector(signIn:displayRequest:)
+ finishedSelector:@selector(signIn:finishedWithAuth:error:)];
+
+ // if the user is signing in to a Google service, we'll delete the
+ // Google authentication browser cookies upon completion
+ //
+ // for other service domains, or to disable clearing of the cookies,
+ // set the browserCookiesURL property explicitly
+ NSString *authorizationHost = [signIn_.authorizationURL host];
+ if ([authorizationHost hasSuffix:@".google.com"]) {
+ NSString *urlStr = [NSString stringWithFormat:@"https://%@/",
+ authorizationHost];
+ NSURL *cookiesURL = [NSURL URLWithString:urlStr];
+ [self setBrowserCookiesURL:cookiesURL];
+ }
+
+ [self setKeychainItemName:keychainItemName];
+ }
+ return self;
+}
+
+#if NS_BLOCKS_AVAILABLE
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler {
+ return [[[self alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ completionHandler:handler] autorelease];
+}
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler {
+ // fall back to the non-blocks init
+ self = [self initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ delegate:nil
+ finishedSelector:NULL];
+ if (self) {
+ completionBlock_ = [handler copy];
+ }
+ return self;
+}
+#endif
+
+- (void)dealloc {
+ [webView_ setDelegate:nil];
+
+ [backButton_ release];
+ [forwardButton_ release];
+ [initialActivityIndicator_ release];
+ [navButtonsView_ release];
+ [rightBarButtonItem_ release];
+ [webView_ release];
+ [signIn_ release];
+ [request_ release];
+ [delegate_ release];
+#if NS_BLOCKS_AVAILABLE
+ [completionBlock_ release];
+ [popViewBlock_ release];
+#endif
+ [keychainItemName_ release];
+ [initialHTMLString_ release];
+ [browserCookiesURL_ release];
+ [userData_ release];
+ [properties_ release];
+
+ [super dealloc];
+}
+
++ (NSString *)authNibName {
+ // subclasses may override this to specify a custom nib name
+ return @"GTMOAuth2ViewTouch";
+}
+
++ (NSBundle *)authNibBundle {
+ // subclasses may override this to specify a custom nib bundle
+ return nil;
+}
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret {
+ return [self authForGoogleFromKeychainForName:keychainItemName
+ clientID:clientID
+ clientSecret:clientSecret
+ error:NULL];
+}
+
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ error:(NSError **)error {
+ Class signInClass = [self signInClass];
+ NSURL *tokenURL = [signInClass googleTokenURL];
+ NSString *redirectURI = [signInClass nativeClientRedirectURI];
+
+ GTMOAuth2Authentication *auth;
+ auth = [GTMOAuth2Authentication authenticationWithServiceProvider:kGTMOAuth2ServiceProviderGoogle
+ tokenURL:tokenURL
+ redirectURI:redirectURI
+ clientID:clientID
+ clientSecret:clientSecret];
+ [[self class] authorizeFromKeychainForName:keychainItemName
+ authentication:auth
+ error:error];
+ return auth;
+}
+
+#endif
+
++ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)newAuth
+ error:(NSError **)error {
+ newAuth.accessToken = nil;
+
+ BOOL didGetTokens = NO;
+ GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
+ NSString *password = [keychain passwordForService:keychainItemName
+ account:kGTMOAuth2AccountName
+ error:error];
+ if (password != nil) {
+ [newAuth setKeysForResponseString:password];
+ didGetTokens = YES;
+ }
+ return didGetTokens;
+}
+
++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName {
+ GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
+ return [keychain removePasswordForService:keychainItemName
+ account:kGTMOAuth2AccountName
+ error:nil];
+}
+
++ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth {
+ return [self saveParamsToKeychainForName:keychainItemName
+ accessibility:NULL
+ authentication:auth
+ error:NULL];
+}
+
++ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
+ accessibility:(CFTypeRef)accessibility
+ authentication:(GTMOAuth2Authentication *)auth
+ error:(NSError **)error {
+ [self removeAuthFromKeychainForName:keychainItemName];
+ // don't save unless we have a token that can really authorize requests
+ if (![auth canAuthorize]) {
+ if (error) {
+ *error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain
+ code:kGTMOAuth2ErrorTokenUnavailable
+ userInfo:nil];
+ }
+ return NO;
+ }
+
+ if (accessibility == NULL
+ && &kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly != NULL) {
+ accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
+ }
+
+ // make a response string containing the values we want to save
+ NSString *password = [auth persistenceResponseString];
+ GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
+ return [keychain setPassword:password
+ forService:keychainItemName
+ accessibility:accessibility
+ account:kGTMOAuth2AccountName
+ error:error];
+}
+
+- (void)loadView {
+ NSString *nibPath = nil;
+ NSBundle *nibBundle = [self nibBundle];
+ if (nibBundle == nil) {
+ nibBundle = [NSBundle mainBundle];
+ }
+ NSString *nibName = self.nibName;
+ if (nibName != nil) {
+ nibPath = [nibBundle pathForResource:nibName ofType:@"nib"];
+ }
+ if (nibPath != nil && [[NSFileManager defaultManager] fileExistsAtPath:nibPath]) {
+ [super loadView];
+ } else {
+ // One of the requirements of loadView is that a valid view object is set to
+ // self.view upon completion. Otherwise, subclasses that attempt to
+ // access self.view after calling [super loadView] will enter an infinite
+ // loop due to the fact that UIViewController's -view accessor calls
+ // loadView when self.view is nil.
+ self.view = [[[UIView alloc] init] autorelease];
+
+#if DEBUG
+ NSLog(@"missing %@.nib", nibName);
+#endif
+ }
+}
+
+
+- (void)viewDidLoad {
+ [self setUpNavigation];
+}
+
+- (void)setUpNavigation {
+ rightBarButtonItem_.customView = navButtonsView_;
+ self.navigationItem.rightBarButtonItem = rightBarButtonItem_;
+}
+
+- (void)popView {
+#if NS_BLOCKS_AVAILABLE
+ void (^popViewBlock)() = self.popViewBlock;
+#else
+ id popViewBlock = nil;
+#endif
+
+ if (popViewBlock || self.navigationController.topViewController == self) {
+ if (!self.view.hidden) {
+ // Set the flag to our viewWillDisappear method so it knows
+ // this is a disappearance initiated by the sign-in object,
+ // not the user cancelling via the navigation controller
+ didDismissSelf_ = YES;
+
+ if (popViewBlock) {
+#if NS_BLOCKS_AVAILABLE
+ popViewBlock();
+ self.popViewBlock = nil;
+#endif
+ } else {
+ [self.navigationController popViewControllerAnimated:YES];
+ }
+ self.view.hidden = YES;
+ }
+ }
+}
+
+- (void)notifyWithName:(NSString *)name
+ webView:(UIWebView *)webView
+ kind:(NSString *)kind {
+ BOOL isStarting = [name isEqual:kGTMOAuth2WebViewStartedLoading];
+ if (hasNotifiedWebViewStartedLoading_ == isStarting) {
+ // Duplicate notification
+ //
+ // UIWebView's delegate methods are so unbalanced that there's little
+ // point trying to keep a count, as it could easily end up stuck greater
+ // than zero.
+ //
+ // We don't really have a way to track the starts and stops of
+ // subframe loads, too, as the webView in the notification is always
+ // for the topmost request.
+ return;
+ }
+ hasNotifiedWebViewStartedLoading_ = isStarting;
+
+ // Notification for webview load starting and stopping
+ NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
+ webView, kGTMOAuth2WebViewKey,
+ kind, kGTMOAuth2WebViewStopKindKey, // kind may be nil
+ nil];
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:name
+ object:self
+ userInfo:dict];
+}
+
+- (void)cancelSigningIn {
+ // The application has explicitly asked us to cancel signing in
+ // (so no further callback is required)
+ hasCalledFinished_ = YES;
+
+ [delegate_ autorelease];
+ delegate_ = nil;
+
+#if NS_BLOCKS_AVAILABLE
+ [completionBlock_ autorelease];
+ completionBlock_ = nil;
+#endif
+
+ // The sign-in object's cancel method will close the window
+ [signIn_ cancelSigningIn];
+ hasDoneFinalRedirect_ = YES;
+}
+
+static Class gSignInClass = Nil;
+
++ (Class)signInClass {
+ if (gSignInClass == Nil) {
+ gSignInClass = [GTMOAuth2SignIn class];
+ }
+ return gSignInClass;
+}
+
++ (void)setSignInClass:(Class)theClass {
+ gSignInClass = theClass;
+}
+
+#pragma mark Token Revocation
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth {
+ [[self signInClass] revokeTokenForGoogleAuthentication:auth];
+}
+#endif
+
+#pragma mark Browser Cookies
+
+- (GTMOAuth2Authentication *)authentication {
+ return self.signIn.authentication;
+}
+
+- (void)clearBrowserCookies {
+ // if browserCookiesURL is non-nil, then get cookies for that URL
+ // and delete them from the common application cookie storage
+ NSURL *cookiesURL = [self browserCookiesURL];
+ if (cookiesURL) {
+ NSHTTPCookieStorage *cookieStorage;
+
+ cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ NSArray *cookies = [cookieStorage cookiesForURL:cookiesURL];
+
+ for (NSHTTPCookie *cookie in cookies) {
+ [cookieStorage deleteCookie:cookie];
+ }
+ }
+}
+
+#pragma mark Accessors
+
+- (void)setNetworkLossTimeoutInterval:(NSTimeInterval)val {
+ signIn_.networkLossTimeoutInterval = val;
+}
+
+- (NSTimeInterval)networkLossTimeoutInterval {
+ return signIn_.networkLossTimeoutInterval;
+}
+
+- (BOOL)shouldUseKeychain {
+ NSString *name = self.keychainItemName;
+ return ([name length] > 0);
+}
+
+- (BOOL)showsInitialActivityIndicator {
+ return (mustShowActivityIndicator_ == 1 || initialHTMLString_ == nil);
+}
+
+- (void)setShowsInitialActivityIndicator:(BOOL)flag {
+ mustShowActivityIndicator_ = (flag ? 1 : -1);
+}
+
+#pragma mark User Properties
+
+- (void)setProperty:(id)obj forKey:(NSString *)key {
+ if (obj == nil) {
+ // User passed in nil, so delete the property
+ [properties_ removeObjectForKey:key];
+ } else {
+ // Be sure the property dictionary exists
+ if (properties_ == nil) {
+ [self setProperties:[NSMutableDictionary dictionary]];
+ }
+ [properties_ setObject:obj forKey:key];
+ }
+}
+
+- (id)propertyForKey:(NSString *)key {
+ id obj = [properties_ objectForKey:key];
+
+ // Be sure the returned pointer has the life of the autorelease pool,
+ // in case self is released immediately
+ return [[obj retain] autorelease];
+}
+
+#pragma mark SignIn callbacks
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request {
+ // This is the signIn object's webRequest method, telling the controller
+ // to either display the request in the webview, or if the request is nil,
+ // to close the window.
+ //
+ // All web requests and all window closing goes through this routine
+
+#if DEBUG
+ if (self.navigationController) {
+ if (self.navigationController.topViewController != self && request != nil) {
+ NSLog(@"Unexpected: Request to show, when already on top. request %@", [request URL]);
+ } else if(self.navigationController.topViewController != self && request == nil) {
+ NSLog(@"Unexpected: Request to pop, when not on top. request nil");
+ }
+ }
+#endif
+
+ if (request != nil) {
+ const NSTimeInterval kJanuary2011 = 1293840000;
+ BOOL isDateValid = ([[NSDate date] timeIntervalSince1970] > kJanuary2011);
+ if (isDateValid) {
+ // Display the request.
+ self.request = request;
+ // The app may prefer some html other than blank white to be displayed
+ // before the sign-in web page loads.
+ // The first fetch might be slow, so the client programmer may want
+ // to show a local "loading" message.
+ // On iOS 5+, UIWebView will ignore loadHTMLString: if it's followed by
+ // a loadRequest: call, so if there is a "loading" message we defer
+ // the loadRequest: until after after we've drawn the "loading" message.
+ //
+ // If there is no initial html string, we show the activity indicator
+ // unless the user set showsInitialActivityIndicator to NO; if there
+ // is an initial html string, we hide the indicator unless the user set
+ // showsInitialActivityIndicator to YES.
+ NSString *html = self.initialHTMLString;
+ if ([html length] > 0) {
+ [initialActivityIndicator_ setHidden:(mustShowActivityIndicator_ < 1)];
+ [self.webView loadHTMLString:html baseURL:nil];
+ } else {
+ [initialActivityIndicator_ setHidden:(mustShowActivityIndicator_ < 0)];
+ [self.webView loadRequest:request];
+ }
+ } else {
+ // clock date is invalid, so signing in would fail with an unhelpful error
+ // from the server. Warn the user in an html string showing a watch icon,
+ // question mark, and the system date and time. Hopefully this will clue
+ // in brighter users, or at least give them a clue when they report the
+ // problem to developers.
+ //
+ // Even better is for apps to check the system clock and show some more
+ // helpful, localized instructions for users; this is really a fallback.
+ NSString *const html = @"<html><body><div align=center><font size='7'>"
+ @"&#x231A; ?<br><i>System Clock Incorrect</i><br>%@"
+ @"</font></div></body></html>";
+ NSString *errHTML = [NSString stringWithFormat:html, [NSDate date]];
+
+ [[self webView] loadHTMLString:errHTML baseURL:nil];
+ }
+ } else {
+ // request was nil.
+ [self popView];
+ }
+}
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn
+ finishedWithAuth:(GTMOAuth2Authentication *)auth
+ error:(NSError *)error {
+ if (!hasCalledFinished_) {
+ hasCalledFinished_ = YES;
+
+ if (error == nil) {
+ if (self.shouldUseKeychain) {
+ NSString *keychainItemName = self.keychainItemName;
+ if (auth.canAuthorize) {
+ // save the auth params in the keychain
+ CFTypeRef accessibility = self.keychainItemAccessibility;
+ [[self class] saveParamsToKeychainForName:keychainItemName
+ accessibility:accessibility
+ authentication:auth
+ error:NULL];
+ } else {
+ // remove the auth params from the keychain
+ [[self class] removeAuthFromKeychainForName:keychainItemName];
+ }
+ }
+ }
+
+ if (delegate_ && finishedSelector_) {
+ SEL sel = finishedSelector_;
+ NSMethodSignature *sig = [delegate_ methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:delegate_];
+ [invocation setArgument:&self atIndex:2];
+ [invocation setArgument:&auth atIndex:3];
+ [invocation setArgument:&error atIndex:4];
+ [invocation invoke];
+ }
+
+ [delegate_ autorelease];
+ delegate_ = nil;
+
+#if NS_BLOCKS_AVAILABLE
+ if (completionBlock_) {
+ completionBlock_(self, auth, error);
+
+ // release the block here to avoid a retain loop on the controller
+ [completionBlock_ autorelease];
+ completionBlock_ = nil;
+ }
+#endif
+ }
+}
+
+- (void)moveWebViewFromUnderNavigationBar {
+ CGRect dontCare;
+ CGRect webFrame = self.view.bounds;
+ UINavigationBar *navigationBar = self.navigationController.navigationBar;
+ CGRectDivide(webFrame, &dontCare, &webFrame,
+ navigationBar.frame.size.height, CGRectMinYEdge);
+ [self.webView setFrame:webFrame];
+}
+
+// isTranslucent is defined in iPhoneOS 3.0 on.
+- (BOOL)isNavigationBarTranslucent {
+ UINavigationBar *navigationBar = [[self navigationController] navigationBar];
+ BOOL isTranslucent =
+ ([navigationBar respondsToSelector:@selector(isTranslucent)] &&
+ [navigationBar isTranslucent]);
+ return isTranslucent;
+}
+
+#pragma mark -
+#pragma mark Protocol implementations
+
+- (void)viewWillAppear:(BOOL)animated {
+ // See the comment on clearBrowserCookies in viewDidDisappear.
+ [self clearBrowserCookies];
+
+ if (!isViewShown_) {
+ isViewShown_ = YES;
+ if ([self isNavigationBarTranslucent]) {
+ [self moveWebViewFromUnderNavigationBar];
+ }
+ if (![signIn_ startSigningIn]) {
+ // Can't start signing in. We must pop our view.
+ // UIWebview needs time to stabilize. Animations need time to complete.
+ // We remove ourself from the view stack after that.
+ [self performSelector:@selector(popView)
+ withObject:nil
+ afterDelay:0.5
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ }
+ }
+ [super viewWillAppear:animated];
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+ didViewAppear_ = YES;
+ [super viewDidAppear:animated];
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+ if (!didDismissSelf_) {
+ // We won't receive further webview delegate messages, so be sure the
+ // started loading notification is balanced, if necessary
+ [self notifyWithName:kGTMOAuth2WebViewStoppedLoading
+ webView:self.webView
+ kind:kGTMOAuth2WebViewCancelled];
+
+ // We are not popping ourselves, so presumably we are being popped by the
+ // navigation controller; tell the sign-in object to close up shop
+ //
+ // this will indirectly call our signIn:finishedWithAuth:error: method
+ // for us
+ [signIn_ windowWasClosed];
+
+#if NS_BLOCKS_AVAILABLE
+ self.popViewBlock = nil;
+#endif
+ }
+
+ [super viewWillDisappear:animated];
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+ [super viewDidDisappear:animated];
+
+ // prevent the next sign-in from showing in the WebView that the user is
+ // already signed in. It's possible for the WebView to set the cookies even
+ // after this, so we also clear them when the view first appears.
+ [self clearBrowserCookies];
+}
+
+- (void)viewDidLayoutSubviews {
+ // We don't call super's version of this method because
+ // -[UIViewController viewDidLayoutSubviews] is documented as a no-op, that
+ // didn't exist before iOS 5.
+ [initialActivityIndicator_ setCenter:[webView_ center]];
+}
+
+- (BOOL)webView:(UIWebView *)webView
+ shouldStartLoadWithRequest:(NSURLRequest *)request
+ navigationType:(UIWebViewNavigationType)navigationType {
+
+ if (!hasDoneFinalRedirect_) {
+ hasDoneFinalRedirect_ = [signIn_ requestRedirectedToRequest:request];
+ if (hasDoneFinalRedirect_) {
+ // signIn has told the view to close
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (void)updateUI {
+ [backButton_ setEnabled:[[self webView] canGoBack]];
+ [forwardButton_ setEnabled:[[self webView] canGoForward]];
+}
+
+- (void)webViewDidStartLoad:(UIWebView *)webView {
+ [self notifyWithName:kGTMOAuth2WebViewStartedLoading
+ webView:webView
+ kind:nil];
+ [self updateUI];
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [self notifyWithName:kGTMOAuth2WebViewStoppedLoading
+ webView:webView
+ kind:kGTMOAuth2WebViewFinished];
+
+ NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
+ if ([title length] > 0) {
+ [signIn_ titleChanged:title];
+ } else {
+#if DEBUG
+ // Verify that Javascript is enabled
+ NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"1+1"];
+ NSAssert([result integerValue] == 2, @"GTMOAuth2: Javascript is required");
+#endif
+ }
+
+ if (self.request && [self.initialHTMLString length] > 0) {
+ // The request was pending.
+ [self setInitialHTMLString:nil];
+ [self.webView loadRequest:self.request];
+ } else {
+ [initialActivityIndicator_ setHidden:YES];
+ [signIn_ cookiesChanged:[NSHTTPCookieStorage sharedHTTPCookieStorage]];
+
+ [self updateUI];
+ }
+}
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [self notifyWithName:kGTMOAuth2WebViewStoppedLoading
+ webView:webView
+ kind:kGTMOAuth2WebViewFailed];
+
+ // Tell the sign-in object that a load failed; if it was the authorization
+ // URL, it will pop the view and return an error to the delegate.
+ if (didViewAppear_) {
+ BOOL isUserInterruption = ([error code] == NSURLErrorCancelled
+ && [[error domain] isEqual:NSURLErrorDomain]);
+ if (isUserInterruption) {
+ // Ignore this error:
+ // Users report that this error occurs when clicking too quickly on the
+ // accept button, before the page has completely loaded. Ignoring
+ // this error seems to provide a better experience than does immediately
+ // cancelling sign-in.
+ //
+ // This error also occurs whenever UIWebView is sent the stopLoading
+ // message, so if we ever send that message intentionally, we need to
+ // revisit this bypass.
+ return;
+ }
+
+ [signIn_ loadFailedWithError:error];
+ } else {
+ // UIWebview needs time to stabilize. Animations need time to complete.
+ [signIn_ performSelector:@selector(loadFailedWithError:)
+ withObject:error
+ afterDelay:0.5
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ }
+}
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
+// When running on a device with an OS version < 6, this gets called.
+//
+// Since it is never called in iOS 6 or greater, if your min deployment
+// target is iOS6 or greater, then you don't need to have this method compiled
+// into your app.
+//
+// When running on a device with an OS version 6 or greater, this code is
+// not called. - (NSUInteger)supportedInterfaceOrientations; would be called,
+// if it existed. Since it is absent,
+// Allow the default orientations: All for iPad, all but upside down for iPhone.
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
+ BOOL value = YES;
+ if (!isInsideShouldAutorotateToInterfaceOrientation_) {
+ isInsideShouldAutorotateToInterfaceOrientation_ = YES;
+ UIViewController *navigationController = [self navigationController];
+ if (navigationController != nil) {
+ value = [navigationController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
+ } else {
+ value = [super shouldAutorotateToInterfaceOrientation:interfaceOrientation];
+ }
+ isInsideShouldAutorotateToInterfaceOrientation_ = NO;
+ }
+ return value;
+}
+#endif
+
+
+@end
+
+
+#pragma mark Common Code
+
+@implementation GTMOAuth2Keychain
+
++ (GTMOAuth2Keychain *)defaultKeychain {
+ if (sDefaultKeychain == nil) {
+ sDefaultKeychain = [[self alloc] init];
+ }
+ return sDefaultKeychain;
+}
+
+
+// For unit tests: allow setting a mock object
++ (void)setDefaultKeychain:(GTMOAuth2Keychain *)keychain {
+ if (sDefaultKeychain != keychain) {
+ [sDefaultKeychain release];
+ sDefaultKeychain = [keychain retain];
+ }
+}
+
+- (NSString *)keyForService:(NSString *)service account:(NSString *)account {
+ return [NSString stringWithFormat:@"com.google.GTMOAuth.%@%@", service, account];
+}
+
+// The Keychain API isn't available on the iPhone simulator in SDKs before 3.0,
+// so, on early simulators we use a fake API, that just writes, unencrypted, to
+// NSUserDefaults.
+#if TARGET_IPHONE_SIMULATOR && __IPHONE_OS_VERSION_MAX_ALLOWED < 30000
+#pragma mark Simulator
+
+// Simulator - just simulated, not secure.
+- (NSString *)passwordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
+ NSString *result = nil;
+ if (0 < [service length] && 0 < [account length]) {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSString *key = [self keyForService:service account:account];
+ result = [defaults stringForKey:key];
+ if (result == nil && error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:kGTMOAuth2KeychainErrorNoPassword
+ userInfo:nil];
+ }
+ } else if (error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:kGTMOAuth2KeychainErrorBadArguments
+ userInfo:nil];
+ }
+ return result;
+
+}
+
+
+// Simulator - just simulated, not secure.
+- (BOOL)removePasswordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
+ BOOL didSucceed = NO;
+ if (0 < [service length] && 0 < [account length]) {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSString *key = [self keyForService:service account:account];
+ [defaults removeObjectForKey:key];
+ [defaults synchronize];
+ } else if (error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:kGTMOAuth2KeychainErrorBadArguments
+ userInfo:nil];
+ }
+ return didSucceed;
+}
+
+// Simulator - just simulated, not secure.
+- (BOOL)setPassword:(NSString *)password
+ forService:(NSString *)service
+ accessibility:(CFTypeRef)accessibility
+ account:(NSString *)account
+ error:(NSError **)error {
+ BOOL didSucceed = NO;
+ if (0 < [password length] && 0 < [service length] && 0 < [account length]) {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSString *key = [self keyForService:service account:account];
+ [defaults setObject:password forKey:key];
+ [defaults synchronize];
+ didSucceed = YES;
+ } else if (error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:kGTMOAuth2KeychainErrorBadArguments
+ userInfo:nil];
+ }
+ return didSucceed;
+}
+
+#else // ! TARGET_IPHONE_SIMULATOR
+#pragma mark Device
+
++ (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
+ NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ (id)kSecClassGenericPassword, (id)kSecClass,
+ @"OAuth", (id)kSecAttrGeneric,
+ account, (id)kSecAttrAccount,
+ service, (id)kSecAttrService,
+ nil];
+ return query;
+}
+
+- (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
+ return [[self class] keychainQueryForService:service account:account];
+}
+
+
+
+// iPhone
+- (NSString *)passwordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
+ OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
+ NSString *result = nil;
+ if (0 < [service length] && 0 < [account length]) {
+ CFDataRef passwordData = NULL;
+ NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
+ [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
+ [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
+
+ status = SecItemCopyMatching((CFDictionaryRef)keychainQuery,
+ (CFTypeRef *)&passwordData);
+ if (status == noErr && 0 < [(NSData *)passwordData length]) {
+ result = [[[NSString alloc] initWithData:(NSData *)passwordData
+ encoding:NSUTF8StringEncoding] autorelease];
+ }
+ if (passwordData != NULL) {
+ CFRelease(passwordData);
+ }
+ }
+ if (status != noErr && error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:status
+ userInfo:nil];
+ }
+ return result;
+}
+
+
+// iPhone
+- (BOOL)removePasswordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
+ OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
+ if (0 < [service length] && 0 < [account length]) {
+ NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
+ status = SecItemDelete((CFDictionaryRef)keychainQuery);
+ }
+ if (status != noErr && error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:status
+ userInfo:nil];
+ }
+ return status == noErr;
+}
+
+// iPhone
+- (BOOL)setPassword:(NSString *)password
+ forService:(NSString *)service
+ accessibility:(CFTypeRef)accessibility
+ account:(NSString *)account
+ error:(NSError **)error {
+ OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
+ if (0 < [service length] && 0 < [account length]) {
+ [self removePasswordForService:service account:account error:nil];
+ if (0 < [password length]) {
+ NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
+ NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+ [keychainQuery setObject:passwordData forKey:(id)kSecValueData];
+
+ if (accessibility != NULL && &kSecAttrAccessible != NULL) {
+ [keychainQuery setObject:(id)accessibility
+ forKey:(id)kSecAttrAccessible];
+ }
+ status = SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
+ }
+ }
+ if (status != noErr && error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:status
+ userInfo:nil];
+ }
+ return status == noErr;
+}
+
+#endif // ! TARGET_IPHONE_SIMULATOR
+
+@end
+
+#endif // TARGET_OS_IPHONE
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewTouch.xib b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewTouch.xib
new file mode 100644
index 00000000..4f91fa4a
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewTouch.xib
@@ -0,0 +1,494 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">1024</int>
+ <string key="IBDocument.SystemVersion">12C60</string>
+ <string key="IBDocument.InterfaceBuilderVersion">2840</string>
+ <string key="IBDocument.AppKitVersion">1187.34</string>
+ <string key="IBDocument.HIToolboxVersion">625.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="NS.object.0">1926</string>
+ </object>
+ <object class="NSArray" key="IBDocument.IntegratedClassDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>IBProxyObject</string>
+ <string>IBUIActivityIndicatorView</string>
+ <string>IBUIBarButtonItem</string>
+ <string>IBUIButton</string>
+ <string>IBUINavigationItem</string>
+ <string>IBUIView</string>
+ <string>IBUIWebView</string>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+ <integer value="1" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBProxyObject" id="372490531">
+ <string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBProxyObject" id="975951072">
+ <string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUINavigationItem" id="1047805472">
+ <string key="IBUITitle">OAuth</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUIBarButtonItem" id="961671599">
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIStyle">1</int>
+ </object>
+ <object class="IBUIView" id="808907889">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">292</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBUIButton" id="453250804">
+ <reference key="NSNextResponder" ref="808907889"/>
+ <int key="NSvFlags">292</int>
+ <string key="NSFrameSize">{30, 30}</string>
+ <reference key="NSSuperview" ref="808907889"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="981703116"/>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIContentHorizontalAlignment">0</int>
+ <int key="IBUIContentVerticalAlignment">0</int>
+ <string key="IBUITitleShadowOffset">{0, -2}</string>
+ <string key="IBUINormalTitle">◀</string>
+ <object class="NSColor" key="IBUIHighlightedTitleColor" id="193465259">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MQA</bytes>
+ </object>
+ <object class="NSColor" key="IBUIDisabledTitleColor">
+ <int key="NSColorSpace">2</int>
+ <bytes key="NSRGB">MC41OTYwNzg0NiAwLjY4NjI3NDUzIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA</bytes>
+ </object>
+ <reference key="IBUINormalTitleColor" ref="193465259"/>
+ <object class="NSColor" key="IBUINormalTitleShadowColor" id="999379443">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MC41AA</bytes>
+ </object>
+ <object class="IBUIFontDescription" key="IBUIFontDescription" id="621440819">
+ <string key="name">Helvetica-Bold</string>
+ <string key="family">Helvetica</string>
+ <int key="traits">2</int>
+ <double key="pointSize">24</double>
+ </object>
+ <object class="NSFont" key="IBUIFont" id="530402572">
+ <string key="NSName">Helvetica-Bold</string>
+ <double key="NSSize">24</double>
+ <int key="NSfFlags">16</int>
+ </object>
+ </object>
+ <object class="IBUIButton" id="981703116">
+ <reference key="NSNextResponder" ref="808907889"/>
+ <int key="NSvFlags">292</int>
+ <string key="NSFrame">{{30, 0}, {30, 30}}</string>
+ <reference key="NSSuperview" ref="808907889"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIContentHorizontalAlignment">0</int>
+ <int key="IBUIContentVerticalAlignment">0</int>
+ <string key="IBUITitleShadowOffset">{0, -2}</string>
+ <string key="IBUINormalTitle">▶</string>
+ <reference key="IBUIHighlightedTitleColor" ref="193465259"/>
+ <object class="NSColor" key="IBUIDisabledTitleColor">
+ <int key="NSColorSpace">2</int>
+ <bytes key="NSRGB">MC41ODQzMTM3NSAwLjY3NDUwOTgyIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA</bytes>
+ </object>
+ <reference key="IBUINormalTitleColor" ref="193465259"/>
+ <reference key="IBUINormalTitleShadowColor" ref="999379443"/>
+ <reference key="IBUIFontDescription" ref="621440819"/>
+ <reference key="IBUIFont" ref="530402572"/>
+ </object>
+ </object>
+ <string key="NSFrameSize">{60, 30}</string>
+ <reference key="NSSuperview"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="453250804"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MSAwAA</bytes>
+ </object>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
+ <int key="IBUIInterfaceOrientation">3</int>
+ <int key="interfaceOrientation">3</int>
+ </object>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUIView" id="426018584">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">274</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBUIWebView" id="663477729">
+ <reference key="NSNextResponder" ref="426018584"/>
+ <int key="NSvFlags">274</int>
+ <string key="NSFrameSize">{320, 460}</string>
+ <reference key="NSSuperview" ref="426018584"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="268967673"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MSAxIDEAA</bytes>
+ </object>
+ <bool key="IBUIClipsSubviews">YES</bool>
+ <bool key="IBUIMultipleTouchEnabled">YES</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIDataDetectorTypes">1</int>
+ <bool key="IBUIDetectsPhoneNumbers">YES</bool>
+ </object>
+ <object class="IBUIActivityIndicatorView" id="268967673">
+ <reference key="NSNextResponder" ref="426018584"/>
+ <int key="NSvFlags">301</int>
+ <string key="NSFrame">{{150, 115}, {20, 20}}</string>
+ <reference key="NSSuperview" ref="426018584"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ <string key="NSReuseIdentifierKey">_NS:9</string>
+ <bool key="IBUIOpaque">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <bool key="IBUIHidesWhenStopped">NO</bool>
+ <bool key="IBUIAnimating">YES</bool>
+ <int key="IBUIStyle">2</int>
+ </object>
+ </object>
+ <string key="NSFrameSize">{320, 460}</string>
+ <reference key="NSSuperview"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="663477729"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MQA</bytes>
+ <object class="NSColorSpace" key="NSCustomColorSpace">
+ <int key="NSID">2</int>
+ </object>
+ </object>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">rightBarButtonItem</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="961671599"/>
+ </object>
+ <int key="connectionID">20</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">navButtonsView</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="808907889"/>
+ </object>
+ <int key="connectionID">22</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">backButton</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="453250804"/>
+ </object>
+ <int key="connectionID">25</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">forwardButton</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="981703116"/>
+ </object>
+ <int key="connectionID">26</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">view</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="426018584"/>
+ </object>
+ <int key="connectionID">28</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">webView</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="663477729"/>
+ </object>
+ <int key="connectionID">29</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">initialActivityIndicator</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="268967673"/>
+ </object>
+ <int key="connectionID">33</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="663477729"/>
+ <reference key="destination" ref="372490531"/>
+ </object>
+ <int key="connectionID">9</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">rightBarButtonItem</string>
+ <reference key="source" ref="1047805472"/>
+ <reference key="destination" ref="961671599"/>
+ </object>
+ <int key="connectionID">14</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchEventConnection" key="connection">
+ <string key="label">goBack</string>
+ <reference key="source" ref="453250804"/>
+ <reference key="destination" ref="663477729"/>
+ <int key="IBEventType">7</int>
+ </object>
+ <int key="connectionID">18</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchEventConnection" key="connection">
+ <string key="label">goForward</string>
+ <reference key="source" ref="981703116"/>
+ <reference key="destination" ref="663477729"/>
+ <int key="IBEventType">7</int>
+ </object>
+ <int key="connectionID">19</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <object class="NSArray" key="object" id="0">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="children" ref="1000"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="372490531"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="975951072"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">6</int>
+ <reference key="object" ref="1047805472"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">10</int>
+ <reference key="object" ref="961671599"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">15</int>
+ <reference key="object" ref="808907889"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="453250804"/>
+ <reference ref="981703116"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">16</int>
+ <reference key="object" ref="453250804"/>
+ <reference key="parent" ref="808907889"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">17</int>
+ <reference key="object" ref="981703116"/>
+ <reference key="parent" ref="808907889"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">27</int>
+ <reference key="object" ref="426018584"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="663477729"/>
+ <reference ref="268967673"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">4</int>
+ <reference key="object" ref="663477729"/>
+ <reference key="parent" ref="426018584"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">31</int>
+ <reference key="object" ref="268967673"/>
+ <reference key="parent" ref="426018584"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.CustomClassName</string>
+ <string>-1.IBPluginDependency</string>
+ <string>-2.CustomClassName</string>
+ <string>-2.IBPluginDependency</string>
+ <string>10.IBPluginDependency</string>
+ <string>15.IBPluginDependency</string>
+ <string>16.IBPluginDependency</string>
+ <string>17.IBPluginDependency</string>
+ <string>27.IBPluginDependency</string>
+ <string>31.IBPluginDependency</string>
+ <string>4.IBPluginDependency</string>
+ <string>6.IBPluginDependency</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>GTMOAuth2ViewControllerTouch</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>UIResponder</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">33</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes">
+ <object class="NSMutableArray" key="referencedPartialClassDescriptions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">GTMOAuth2ViewControllerTouch</string>
+ <string key="superclassName">UIViewController</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>backButton</string>
+ <string>forwardButton</string>
+ <string>initialActivityIndicator</string>
+ <string>navButtonsView</string>
+ <string>rightBarButtonItem</string>
+ <string>webView</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>UIButton</string>
+ <string>UIButton</string>
+ <string>UIActivityIndicatorView</string>
+ <string>UIView</string>
+ <string>UIBarButtonItem</string>
+ <string>UIWebView</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>backButton</string>
+ <string>forwardButton</string>
+ <string>initialActivityIndicator</string>
+ <string>navButtonsView</string>
+ <string>rightBarButtonItem</string>
+ <string>webView</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBToOneOutletInfo">
+ <string key="name">backButton</string>
+ <string key="candidateClassName">UIButton</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">forwardButton</string>
+ <string key="candidateClassName">UIButton</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">initialActivityIndicator</string>
+ <string key="candidateClassName">UIActivityIndicatorView</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">navButtonsView</string>
+ <string key="candidateClassName">UIView</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">rightBarButtonItem</string>
+ <string key="candidateClassName">UIBarButtonItem</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">webView</string>
+ <string key="candidateClassName">UIWebView</string>
+ </object>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">./Classes/GTMOAuth2ViewControllerTouch.h</string>
+ </object>
+ </object>
+ </object>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <real value="1024" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <real value="1536" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
+ <integer value="3000" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ <string key="IBCocoaTouchPluginVersion">1926</string>
+ </data>
+</archive>
diff --git a/example/ios/iOS UI Test/iOS UI Test.xcodeproj/project.pbxproj b/example/ios/iOS UI Test/iOS UI Test.xcodeproj/project.pbxproj
index db280fde..e9cd3797 100644
--- a/example/ios/iOS UI Test/iOS UI Test.xcodeproj/project.pbxproj
+++ b/example/ios/iOS UI Test/iOS UI Test.xcodeproj/project.pbxproj
@@ -28,6 +28,13 @@
ABE4026B173F3FCE007F1FB3 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABE4026A173F3FCE007F1FB3 /* ImageIO.framework */; };
B12AAA3417322654003551C7 /* MCOMessageView.mm in Sources */ = {isa = PBXBuildFile; fileRef = B12AAA3017322654003551C7 /* MCOMessageView.mm */; };
B12AAA3517322654003551C7 /* MCTMsgViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = B12AAA3217322654003551C7 /* MCTMsgViewController.mm */; };
+ C6D7194D178BB8B4008ED15F /* GTMHTTPFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D71939178BA812008ED15F /* GTMHTTPFetcher.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ C6D7194E178BB8B4008ED15F /* GTMHTTPFetchHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D7193B178BA812008ED15F /* GTMHTTPFetchHistory.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ C6D7194F178BB8B4008ED15F /* GTMOAuth2Authentication.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D7193E178BA812008ED15F /* GTMOAuth2Authentication.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ C6D71950178BB8B4008ED15F /* GTMOAuth2SignIn.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D71940178BA812008ED15F /* GTMOAuth2SignIn.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ C6D71951178BB8B4008ED15F /* GTMOAuth2ViewControllerTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D71947178BA812008ED15F /* GTMOAuth2ViewControllerTouch.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+ C6D71952178BB8B4008ED15F /* GTMOAuth2ViewTouch.xib in Resources */ = {isa = PBXBuildFile; fileRef = C6D71948178BA812008ED15F /* GTMOAuth2ViewTouch.xib */; };
+ C6D71954178BB91E008ED15F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D71953178BB91E008ED15F /* SystemConfiguration.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -107,6 +114,18 @@
B12AAA3017322654003551C7 /* MCOMessageView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MCOMessageView.mm; sourceTree = "<group>"; };
B12AAA3117322654003551C7 /* MCTMsgViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCTMsgViewController.h; sourceTree = "<group>"; };
B12AAA3217322654003551C7 /* MCTMsgViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MCTMsgViewController.mm; sourceTree = "<group>"; };
+ C6D71938178BA812008ED15F /* GTMHTTPFetcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcher.h; sourceTree = "<group>"; };
+ C6D71939178BA812008ED15F /* GTMHTTPFetcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcher.m; sourceTree = "<group>"; };
+ C6D7193A178BA812008ED15F /* GTMHTTPFetchHistory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetchHistory.h; sourceTree = "<group>"; };
+ C6D7193B178BA812008ED15F /* GTMHTTPFetchHistory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetchHistory.m; sourceTree = "<group>"; };
+ C6D7193D178BA812008ED15F /* GTMOAuth2Authentication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2Authentication.h; sourceTree = "<group>"; };
+ C6D7193E178BA812008ED15F /* GTMOAuth2Authentication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2Authentication.m; sourceTree = "<group>"; };
+ C6D7193F178BA812008ED15F /* GTMOAuth2SignIn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2SignIn.h; sourceTree = "<group>"; };
+ C6D71940178BA812008ED15F /* GTMOAuth2SignIn.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2SignIn.m; sourceTree = "<group>"; };
+ C6D71946178BA812008ED15F /* GTMOAuth2ViewControllerTouch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2ViewControllerTouch.h; sourceTree = "<group>"; };
+ C6D71947178BA812008ED15F /* GTMOAuth2ViewControllerTouch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2ViewControllerTouch.m; sourceTree = "<group>"; };
+ C6D71948178BA812008ED15F /* GTMOAuth2ViewTouch.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GTMOAuth2ViewTouch.xib; sourceTree = "<group>"; };
+ C6D71953178BB91E008ED15F /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -114,6 +133,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ C6D71954178BB91E008ED15F /* SystemConfiguration.framework in Frameworks */,
AB7BA446171389CC00104953 /* libMailCore-ios.a in Frameworks */,
ABE4026B173F3FCE007F1FB3 /* ImageIO.framework in Frameworks */,
AB9EAE40170374D900D750C7 /* Security.framework in Frameworks */,
@@ -170,6 +190,7 @@
AB9EAE04170368F000D750C7 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ C6D71953178BB91E008ED15F /* SystemConfiguration.framework */,
ABE4026A173F3FCE007F1FB3 /* ImageIO.framework */,
AB7BA4471713913F00104953 /* CFNetwork.framework */,
AB9EAE3F170374D900D750C7 /* Security.framework */,
@@ -183,6 +204,7 @@
AB9EAE0B170368F000D750C7 /* iOS UI Test */ = {
isa = PBXGroup;
children = (
+ C6D71936178BA812008ED15F /* gtm-oauth2 */,
AB7BA4321713898B00104953 /* mailcore2.xcodeproj */,
AB9EAE11170368F000D750C7 /* main.mm */,
AB9EAE14170368F000D750C7 /* AppDelegate.h */,
@@ -217,6 +239,49 @@
name = "Supporting Files";
sourceTree = "<group>";
};
+ C6D71936178BA812008ED15F /* gtm-oauth2 */ = {
+ isa = PBXGroup;
+ children = (
+ C6D71937178BA812008ED15F /* HTTPFetcher */,
+ C6D7193C178BA812008ED15F /* Source */,
+ );
+ name = "gtm-oauth2";
+ path = "../../../common/gtm-oauth2";
+ sourceTree = "<group>";
+ };
+ C6D71937178BA812008ED15F /* HTTPFetcher */ = {
+ isa = PBXGroup;
+ children = (
+ C6D71938178BA812008ED15F /* GTMHTTPFetcher.h */,
+ C6D71939178BA812008ED15F /* GTMHTTPFetcher.m */,
+ C6D7193A178BA812008ED15F /* GTMHTTPFetchHistory.h */,
+ C6D7193B178BA812008ED15F /* GTMHTTPFetchHistory.m */,
+ );
+ path = HTTPFetcher;
+ sourceTree = "<group>";
+ };
+ C6D7193C178BA812008ED15F /* Source */ = {
+ isa = PBXGroup;
+ children = (
+ C6D7193D178BA812008ED15F /* GTMOAuth2Authentication.h */,
+ C6D7193E178BA812008ED15F /* GTMOAuth2Authentication.m */,
+ C6D7193F178BA812008ED15F /* GTMOAuth2SignIn.h */,
+ C6D71940178BA812008ED15F /* GTMOAuth2SignIn.m */,
+ C6D71945178BA812008ED15F /* Touch */,
+ );
+ path = Source;
+ sourceTree = "<group>";
+ };
+ C6D71945178BA812008ED15F /* Touch */ = {
+ isa = PBXGroup;
+ children = (
+ C6D71946178BA812008ED15F /* GTMOAuth2ViewControllerTouch.h */,
+ C6D71947178BA812008ED15F /* GTMOAuth2ViewControllerTouch.m */,
+ C6D71948178BA812008ED15F /* GTMOAuth2ViewTouch.xib */,
+ );
+ path = Touch;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -313,6 +378,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ C6D71952178BB8B4008ED15F /* GTMOAuth2ViewTouch.xib in Resources */,
ABE40269173F0282007F1FB3 /* MCOMessageViewScript.js in Resources */,
AB9EAE10170368F000D750C7 /* InfoPlist.strings in Resources */,
AB9EAE18170368F000D750C7 /* Default.png in Resources */,
@@ -331,10 +397,15 @@
buildActionMask = 2147483647;
files = (
AB9EAE12170368F000D750C7 /* main.mm in Sources */,
+ C6D7194E178BB8B4008ED15F /* GTMHTTPFetchHistory.m in Sources */,
AB9EAE16170368F000D750C7 /* AppDelegate.m in Sources */,
AB9EAE22170368F000D750C7 /* MasterViewController.m in Sources */,
AB9EAE3617036FD700D750C7 /* SettingsViewController.m in Sources */,
+ C6D7194D178BB8B4008ED15F /* GTMHTTPFetcher.m in Sources */,
+ C6D71951178BB8B4008ED15F /* GTMOAuth2ViewControllerTouch.m in Sources */,
AB665BCD17134336007F2151 /* FXKeychain.m in Sources */,
+ C6D71950178BB8B4008ED15F /* GTMOAuth2SignIn.m in Sources */,
+ C6D7194F178BB8B4008ED15F /* GTMOAuth2Authentication.m in Sources */,
B12AAA3417322654003551C7 /* MCOMessageView.mm in Sources */,
B12AAA3517322654003551C7 /* MCTMsgViewController.mm in Sources */,
);
diff --git a/example/ios/iOS UI Test/iOS UI Test/MasterViewController.m b/example/ios/iOS UI Test/iOS UI Test/MasterViewController.m
index 3e5e4422..5bbc0d53 100644
--- a/example/ios/iOS UI Test/iOS UI Test/MasterViewController.m
+++ b/example/ios/iOS UI Test/iOS UI Test/MasterViewController.m
@@ -10,6 +10,11 @@
#import <MailCore/MailCore.h>
#import "FXKeychain.h"
#import "MCTMsgViewController.h"
+#import "GTMOAuth2ViewControllerTouch.h"
+
+#define CLIENT_ID @"the-client-id"
+#define CLIENT_SECRET @"the-client-secret"
+#define KEYCHAIN_ITEM_NAME @"MailCore OAuth 2.0 Token"
@interface MasterViewController ()
@property (nonatomic, strong) NSArray *messages;
@@ -26,24 +31,78 @@
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ HostnameKey: @"imap.gmail.com" }];
+ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"OAuth2Enabled"]) {
+ [self startOAuth2];
+ }
+ else {
+ [self startLogin];
+ }
+}
+
+- (void) startLogin
+{
NSString *username = [[NSUserDefaults standardUserDefaults] objectForKey:UsernameKey];
NSString *password = [[FXKeychain defaultKeychain] objectForKey:PasswordKey];
NSString *hostname = [[NSUserDefaults standardUserDefaults] objectForKey:HostnameKey];
-
- [self loadAccountWithUsername:username password:password hostname:hostname];
+
+ if (!username.length || !password.length) {
+ [self performSelector:@selector(showSettingsViewController:) withObject:nil afterDelay:0.5];
+ return;
+ }
+
+ [self loadAccountWithUsername:username password:password hostname:hostname oauth2Token:nil];
}
-- (void)loadAccountWithUsername:(NSString *)username password:(NSString *)password hostname:(NSString *)hostname {
- if (!username.length || !password.length) {
- [self performSelector:@selector(showSettingsViewController:) withObject:nil afterDelay:0.5];
- return;
- }
-
+- (void) startOAuth2
+{
+ GTMOAuth2Authentication * auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:KEYCHAIN_ITEM_NAME
+ clientID:CLIENT_ID
+ clientSecret:CLIENT_SECRET];
+
+ if ([auth refreshToken] == nil) {
+ MasterViewController * __weak weakSelf = self;
+ GTMOAuth2ViewControllerTouch *viewController = [GTMOAuth2ViewControllerTouch controllerWithScope:@"https://mail.google.com/"
+ clientID:CLIENT_ID
+ clientSecret:CLIENT_SECRET
+ keychainItemName:KEYCHAIN_ITEM_NAME
+ completionHandler:^(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *retrievedAuth, NSError *error) {
+ [weakSelf loadWithAuth:retrievedAuth];
+ }];
+ [self.navigationController pushViewController:viewController
+ animated:YES];
+ }
+ else {
+ [auth beginTokenFetchWithDelegate:self
+ didFinishSelector:@selector(auth:finishedRefreshWithFetcher:error:)];
+ }
+}
+
+- (void)auth:(GTMOAuth2Authentication *)auth
+finishedRefreshWithFetcher:(GTMHTTPFetcher *)fetcher
+ error:(NSError *)error {
+ [self loadWithAuth:auth];
+}
+
+- (void)loadWithAuth:(GTMOAuth2Authentication *)auth
+{
+ NSString *hostname = [[NSUserDefaults standardUserDefaults] objectForKey:HostnameKey];
+ [self loadAccountWithUsername:[auth userEmail] password:nil hostname:hostname oauth2Token:[auth accessToken]];
+}
+
+- (void)loadAccountWithUsername:(NSString *)username
+ password:(NSString *)password
+ hostname:(NSString *)hostname
+ oauth2Token:(NSString *)oauth2Token
+{
self.imapSession = [[MCOIMAPSession alloc] init];
self.imapSession.hostname = hostname;
self.imapSession.port = 993;
self.imapSession.username = username;
self.imapSession.password = password;
+ if (oauth2Token != nil) {
+ self.imapSession.OAuth2Token = oauth2Token;
+ self.imapSession.authType = MCOAuthTypeXOAuth2;
+ }
self.imapSession.connectionType = MCOConnectionTypeTLS;
MasterViewController * __weak weakSelf = self;
self.imapSession.connectionLogger = ^(void * connectionID, MCOConnectionLogType type, NSData * data) {
@@ -135,7 +194,7 @@
![password isEqualToString:self.imapSession.password] ||
![hostname isEqualToString:self.imapSession.hostname]) {
self.imapSession = nil;
- [self loadAccountWithUsername:username password:password hostname:hostname];
+ [self loadAccountWithUsername:username password:password hostname:hostname oauth2Token:nil];
}
}
diff --git a/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.h b/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.h
index e5f5da48..a1cb84a2 100644
--- a/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.h
+++ b/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.h
@@ -12,6 +12,7 @@ extern NSString * const UsernameKey;
extern NSString * const PasswordKey;
extern NSString * const HostnameKey;
extern NSString * const FetchFullMessageKey;
+extern NSString * const OAuthEnabledKey;
@protocol SettingsViewControllerDelegate;
@@ -21,6 +22,8 @@ extern NSString * const FetchFullMessageKey;
@property (weak, nonatomic) IBOutlet UITextField *passwordTextField;
@property (weak, nonatomic) IBOutlet UITextField *hostnameTextField;
@property (weak, nonatomic) IBOutlet UISwitch *fetchFullMessageSwitch;
+@property (weak, nonatomic) IBOutlet UISwitch *useOAuth2Switch;
+
@property (nonatomic, weak) id<SettingsViewControllerDelegate> delegate;
- (IBAction)done:(id)sender;
diff --git a/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.m b/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.m
index e2f0d2c5..d17b479a 100644
--- a/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.m
+++ b/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.m
@@ -13,6 +13,7 @@ NSString * const UsernameKey = @"username";
NSString * const PasswordKey = @"password";
NSString * const HostnameKey = @"hostname";
NSString * const FetchFullMessageKey = @"FetchFullMessageEnabled";
+NSString * const OAuthEnabledKey = @"OAuth2Enabled";
@implementation SettingsViewController
@@ -21,6 +22,7 @@ NSString * const FetchFullMessageKey = @"FetchFullMessageEnabled";
[[FXKeychain defaultKeychain] setObject:self.passwordTextField.text ?: @"" forKey:PasswordKey];
[[NSUserDefaults standardUserDefaults] setObject:self.hostnameTextField.text ?: @"" forKey:HostnameKey];
[[NSUserDefaults standardUserDefaults] setBool:[self.fetchFullMessageSwitch isOn] forKey:FetchFullMessageKey];
+ [[NSUserDefaults standardUserDefaults] setBool:[self.useOAuth2Switch isOn] forKey:OAuthEnabledKey];
[self.delegate settingsViewControllerFinished:self];
}
@@ -33,6 +35,7 @@ NSString * const FetchFullMessageKey = @"FetchFullMessageEnabled";
self.passwordTextField.text = [[FXKeychain defaultKeychain] objectForKey:PasswordKey];
self.hostnameTextField.text = [[NSUserDefaults standardUserDefaults] stringForKey:HostnameKey];
self.fetchFullMessageSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:FetchFullMessageKey];
+ self.useOAuth2Switch.on = [[NSUserDefaults standardUserDefaults] boolForKey:OAuthEnabledKey];
}
@end
diff --git a/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.xib b/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.xib
index 3de561bd..c3f4f089 100644
--- a/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.xib
+++ b/example/ios/iOS UI Test/iOS UI Test/SettingsViewController.xib
@@ -3,12 +3,12 @@
<data>
<int key="IBDocument.SystemTarget">1552</int>
<string key="IBDocument.SystemVersion">12E55</string>
- <string key="IBDocument.InterfaceBuilderVersion">3084</string>
+ <string key="IBDocument.InterfaceBuilderVersion">4457.6</string>
<string key="IBDocument.AppKitVersion">1187.39</string>
<string key="IBDocument.HIToolboxVersion">626.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
- <string key="NS.object.0">2083</string>
+ <string key="NS.object.0">3682.6</string>
</object>
<array key="IBDocument.IntegratedClassDependencies">
<string>IBNSLayoutConstraint</string>
@@ -96,8 +96,8 @@
<int key="type">1</int>
<double key="pointSize">14</double>
</object>
- <object class="NSFont" key="IBUIFont" id="649714166">
- <string key="NSName">Helvetica</string>
+ <object class="NSFont" key="IBUIFont" id="577736679">
+ <string key="NSName">HelveticaNeue</string>
<double key="NSSize">14</double>
<int key="NSfFlags">16</int>
</object>
@@ -130,7 +130,7 @@
</object>
<int key="IBUIClearButtonMode">1</int>
<reference key="IBUIFontDescription" ref="985700861"/>
- <reference key="IBUIFont" ref="649714166"/>
+ <reference key="IBUIFont" ref="577736679"/>
</object>
<object class="IBUITextField" id="394260873">
<reference key="NSNextResponder" ref="191373211"/>
@@ -159,7 +159,7 @@
</object>
<int key="IBUIClearButtonMode">1</int>
<reference key="IBUIFontDescription" ref="985700861"/>
- <reference key="IBUIFont" ref="649714166"/>
+ <reference key="IBUIFont" ref="577736679"/>
</object>
<object class="IBUISwitch" id="348875194">
<reference key="NSNextResponder" ref="191373211"/>
@@ -167,7 +167,7 @@
<string key="NSFrame">{{208, 180}, {94, 27}}</string>
<reference key="NSSuperview" ref="191373211"/>
<reference key="NSWindow"/>
- <reference key="NSNextKeyView"/>
+ <reference key="NSNextKeyView" ref="780270730"/>
<string key="NSReuseIdentifierKey">_NS:9</string>
<bool key="IBUIOpaque">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
@@ -188,7 +188,7 @@
<bool key="IBUIUserInteractionEnabled">NO</bool>
<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
<string key="IBUIText">Fetch full message</string>
- <object class="NSColor" key="IBUITextColor">
+ <object class="NSColor" key="IBUITextColor" id="471454649">
<int key="NSColorSpace">1</int>
<bytes key="NSRGB">MCAwIDAAA</bytes>
<string key="IBUIColorCocoaTouchKeyPath">darkTextColor</string>
@@ -196,7 +196,41 @@
<nil key="IBUIHighlightedColor"/>
<int key="IBUIBaselineAdjustment">0</int>
<reference key="IBUIFontDescription" ref="985700861"/>
- <reference key="IBUIFont" ref="649714166"/>
+ <reference key="IBUIFont" ref="577736679"/>
+ <bool key="IBUIAdjustsFontSizeToFit">NO</bool>
+ </object>
+ <object class="IBUISwitch" id="480060909">
+ <reference key="NSNextResponder" ref="191373211"/>
+ <int key="NSvFlags">292</int>
+ <string key="NSFrame">{{208, 215}, {94, 27}}</string>
+ <reference key="NSSuperview" ref="191373211"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ <string key="NSReuseIdentifierKey">_NS:9</string>
+ <bool key="IBUIOpaque">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIContentHorizontalAlignment">0</int>
+ <int key="IBUIContentVerticalAlignment">0</int>
+ </object>
+ <object class="IBUILabel" id="780270730">
+ <reference key="NSNextResponder" ref="191373211"/>
+ <int key="NSvFlags">292</int>
+ <string key="NSFrame">{{20, 218}, {143, 21}}</string>
+ <reference key="NSSuperview" ref="191373211"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="480060909"/>
+ <string key="NSReuseIdentifierKey">_NS:9</string>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClipsSubviews">YES</bool>
+ <int key="IBUIContentMode">7</int>
+ <bool key="IBUIUserInteractionEnabled">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <string key="IBUIText">Use OAuth 2.0</string>
+ <reference key="IBUITextColor" ref="471454649"/>
+ <nil key="IBUIHighlightedColor"/>
+ <int key="IBUIBaselineAdjustment">0</int>
+ <reference key="IBUIFontDescription" ref="985700861"/>
+ <reference key="IBUIFont" ref="577736679"/>
<bool key="IBUIAdjustsFontSizeToFit">NO</bool>
</object>
</array>
@@ -272,6 +306,14 @@
<int key="connectionID">69</int>
</object>
<object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">useOAuth2Switch</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="480060909"/>
+ </object>
+ <int key="connectionID">84</int>
+ </object>
+ <object class="IBConnectionRecord">
<object class="IBCocoaTouchEventConnection" key="connection">
<string key="label">done:</string>
<reference key="source" ref="38144743"/>
@@ -292,6 +334,57 @@
<int key="objectID">1</int>
<reference key="object" ref="191373211"/>
<array class="NSMutableArray" key="children">
+ <object class="IBNSLayoutConstraint" id="728173208">
+ <reference key="firstItem" ref="480060909"/>
+ <int key="firstAttribute">5</int>
+ <int key="relation">0</int>
+ <reference key="secondItem" ref="348875194"/>
+ <int key="secondAttribute">5</int>
+ <float key="multiplier">1</float>
+ <object class="IBLayoutConstant" key="constant">
+ <double key="value">0.0</double>
+ </object>
+ <float key="priority">1000</float>
+ <reference key="containingView" ref="191373211"/>
+ <int key="scoringType">6</int>
+ <float key="scoringTypeFloat">24</float>
+ <int key="contentType">2</int>
+ <bool key="placeholder">NO</bool>
+ </object>
+ <object class="IBNSLayoutConstraint" id="390655325">
+ <reference key="firstItem" ref="480060909"/>
+ <int key="firstAttribute">3</int>
+ <int key="relation">0</int>
+ <reference key="secondItem" ref="348875194"/>
+ <int key="secondAttribute">4</int>
+ <float key="multiplier">1</float>
+ <object class="IBNSLayoutSymbolicConstant" key="constant">
+ <double key="value">8</double>
+ </object>
+ <float key="priority">1000</float>
+ <reference key="containingView" ref="191373211"/>
+ <int key="scoringType">6</int>
+ <float key="scoringTypeFloat">24</float>
+ <int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
+ </object>
+ <object class="IBNSLayoutConstraint" id="999557578">
+ <reference key="firstItem" ref="480060909"/>
+ <int key="firstAttribute">10</int>
+ <int key="relation">0</int>
+ <reference key="secondItem" ref="780270730"/>
+ <int key="secondAttribute">10</int>
+ <float key="multiplier">1</float>
+ <object class="IBLayoutConstant" key="constant">
+ <double key="value">0.0</double>
+ </object>
+ <float key="priority">1000</float>
+ <reference key="containingView" ref="191373211"/>
+ <int key="scoringType">6</int>
+ <float key="scoringTypeFloat">24</float>
+ <int key="contentType">2</int>
+ <bool key="placeholder">NO</bool>
+ </object>
<object class="IBNSLayoutConstraint" id="148058045">
<reference key="firstItem" ref="348875194"/>
<int key="firstAttribute">3</int>
@@ -307,6 +400,7 @@
<int key="scoringType">6</int>
<float key="scoringTypeFloat">24</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="719571487">
<reference key="firstItem" ref="191373211"/>
@@ -320,9 +414,44 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
+ </object>
+ <object class="IBNSLayoutConstraint" id="247680247">
+ <reference key="firstItem" ref="780270730"/>
+ <int key="firstAttribute">6</int>
+ <int key="relation">0</int>
+ <reference key="secondItem" ref="484795553"/>
+ <int key="secondAttribute">6</int>
+ <float key="multiplier">1</float>
+ <object class="IBLayoutConstant" key="constant">
+ <double key="value">0.0</double>
+ </object>
+ <float key="priority">1000</float>
+ <reference key="containingView" ref="191373211"/>
+ <int key="scoringType">6</int>
+ <float key="scoringTypeFloat">24</float>
+ <int key="contentType">2</int>
+ <bool key="placeholder">NO</bool>
+ </object>
+ <object class="IBNSLayoutConstraint" id="986729151">
+ <reference key="firstItem" ref="780270730"/>
+ <int key="firstAttribute">5</int>
+ <int key="relation">0</int>
+ <reference key="secondItem" ref="484795553"/>
+ <int key="secondAttribute">5</int>
+ <float key="multiplier">1</float>
+ <object class="IBLayoutConstant" key="constant">
+ <double key="value">0.0</double>
+ </object>
+ <float key="priority">1000</float>
+ <reference key="containingView" ref="191373211"/>
+ <int key="scoringType">6</int>
+ <float key="scoringTypeFloat">24</float>
+ <int key="contentType">2</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="918242346">
<reference key="firstItem" ref="484795553"/>
@@ -339,6 +468,7 @@
<int key="scoringType">6</int>
<float key="scoringTypeFloat">24</float>
<int key="contentType">2</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="342906512">
<reference key="firstItem" ref="484795553"/>
@@ -352,9 +482,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="1036238214">
<reference key="firstItem" ref="191373211"/>
@@ -368,9 +499,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="662191761">
<reference key="firstItem" ref="394260873"/>
@@ -384,9 +516,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="457196062">
<reference key="firstItem" ref="394260873"/>
@@ -403,6 +536,7 @@
<int key="scoringType">6</int>
<float key="scoringTypeFloat">24</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="295413753">
<reference key="firstItem" ref="923634912"/>
@@ -419,6 +553,7 @@
<int key="scoringType">6</int>
<float key="scoringTypeFloat">24</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="144109937">
<reference key="firstItem" ref="191373211"/>
@@ -432,9 +567,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="620844127">
<reference key="firstItem" ref="923634912"/>
@@ -448,9 +584,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="321610222">
<reference key="firstItem" ref="191373211"/>
@@ -464,9 +601,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="126503603">
<reference key="firstItem" ref="339913914"/>
@@ -483,6 +621,7 @@
<int key="scoringType">3</int>
<float key="scoringTypeFloat">9</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="17620276">
<reference key="firstItem" ref="339913914"/>
@@ -496,9 +635,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="118803530">
<reference key="firstItem" ref="703582545"/>
@@ -512,9 +652,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="7756024">
<reference key="firstItem" ref="703582545"/>
@@ -528,9 +669,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="630587606">
<reference key="firstItem" ref="703582545"/>
@@ -544,9 +686,10 @@
</object>
<float key="priority">1000</float>
<reference key="containingView" ref="191373211"/>
- <int key="scoringType">8</int>
+ <int key="scoringType">0</int>
<float key="scoringTypeFloat">29</float>
<int key="contentType">3</int>
+ <bool key="placeholder">NO</bool>
</object>
<reference ref="703582545"/>
<reference ref="339913914"/>
@@ -554,6 +697,8 @@
<reference ref="394260873"/>
<reference ref="348875194"/>
<reference ref="484795553"/>
+ <reference ref="480060909"/>
+ <reference ref="780270730"/>
</array>
<reference key="parent" ref="0"/>
</object>
@@ -699,6 +844,7 @@
<int key="scoringType">3</int>
<float key="scoringTypeFloat">9</float>
<int key="contentType">1</int>
+ <bool key="placeholder">NO</bool>
</object>
<object class="IBNSLayoutConstraint" id="670227139">
<reference key="firstItem" ref="484795553"/>
@@ -715,6 +861,7 @@
<int key="scoringType">3</int>
<float key="scoringTypeFloat">9</float>
<int key="contentType">1</int>
+ <bool key="placeholder">NO</bool>
</object>
</array>
<reference key="parent" ref="191373211"/>
@@ -739,6 +886,65 @@
<reference key="object" ref="269062633"/>
<reference key="parent" ref="484795553"/>
</object>
+ <object class="IBObjectRecord">
+ <int key="objectID">72</int>
+ <reference key="object" ref="480060909"/>
+ <reference key="parent" ref="191373211"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">73</int>
+ <reference key="object" ref="780270730"/>
+ <array class="NSMutableArray" key="children">
+ <object class="IBNSLayoutConstraint" id="725298023">
+ <reference key="firstItem" ref="780270730"/>
+ <int key="firstAttribute">8</int>
+ <int key="relation">0</int>
+ <nil key="secondItem"/>
+ <int key="secondAttribute">0</int>
+ <float key="multiplier">1</float>
+ <object class="IBLayoutConstant" key="constant">
+ <double key="value">21</double>
+ </object>
+ <float key="priority">1000</float>
+ <reference key="containingView" ref="780270730"/>
+ <int key="scoringType">3</int>
+ <float key="scoringTypeFloat">9</float>
+ <int key="contentType">1</int>
+ <bool key="placeholder">NO</bool>
+ </object>
+ </array>
+ <reference key="parent" ref="191373211"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">75</int>
+ <reference key="object" ref="725298023"/>
+ <reference key="parent" ref="780270730"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">76</int>
+ <reference key="object" ref="999557578"/>
+ <reference key="parent" ref="191373211"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">80</int>
+ <reference key="object" ref="390655325"/>
+ <reference key="parent" ref="191373211"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">81</int>
+ <reference key="object" ref="986729151"/>
+ <reference key="parent" ref="191373211"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">82</int>
+ <reference key="object" ref="247680247"/>
+ <reference key="parent" ref="191373211"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">83</int>
+ <reference key="object" ref="728173208"/>
+ <reference key="parent" ref="191373211"/>
+ </object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
@@ -747,7 +953,7 @@
<string key="-2.CustomClassName">UIResponder</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
- <array key="1.IBViewMetadataConstraints">
+ <array class="NSMutableArray" key="1.IBViewMetadataConstraints">
<reference ref="630587606"/>
<reference ref="7756024"/>
<reference ref="118803530"/>
@@ -762,8 +968,13 @@
<reference ref="1036238214"/>
<reference ref="342906512"/>
<reference ref="918242346"/>
+ <reference ref="986729151"/>
+ <reference ref="247680247"/>
<reference ref="719571487"/>
<reference ref="148058045"/>
+ <reference ref="999557578"/>
+ <reference ref="390655325"/>
+ <reference ref="728173208"/>
</array>
<string key="10.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="11.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
@@ -800,13 +1011,26 @@
<string key="61.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="70.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="71.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="72.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <boolean value="NO" key="72.IBViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+ <string key="73.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <array class="NSMutableArray" key="73.IBViewMetadataConstraints">
+ <reference ref="725298023"/>
+ </array>
+ <boolean value="NO" key="73.IBViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+ <string key="75.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="76.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="80.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="81.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="82.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="83.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="9.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
- <int key="maxID">71</int>
+ <int key="maxID">84</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
@@ -837,6 +1061,7 @@
<string key="fetchFullMessageSwitch">UISwitch</string>
<string key="hostnameTextField">UITextField</string>
<string key="passwordTextField">UITextField</string>
+ <string key="useOAuth2Switch">UISwitch</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
<object class="IBToOneOutletInfo" key="emailTextField">
@@ -855,6 +1080,10 @@
<string key="name">passwordTextField</string>
<string key="candidateClassName">UITextField</string>
</object>
+ <object class="IBToOneOutletInfo" key="useOAuth2Switch">
+ <string key="name">useOAuth2Switch</string>
+ <string key="candidateClassName">UISwitch</string>
+ </object>
</dictionary>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
@@ -865,9 +1094,17 @@
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <real value="1552" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
+ <integer value="4600" key="NS.object.0"/>
+ </object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<bool key="IBDocument.UseAutolayout">YES</bool>
- <string key="IBCocoaTouchPluginVersion">2083</string>
+ <string key="IBCocoaTouchPluginVersion">3682.6</string>
</data>
</archive>
diff --git a/example/mac/macExample/macExample.xcodeproj/project.pbxproj b/example/mac/macExample/macExample.xcodeproj/project.pbxproj
index 4cfe53f6..c03d6be7 100644
--- a/example/mac/macExample/macExample.xcodeproj/project.pbxproj
+++ b/example/mac/macExample/macExample.xcodeproj/project.pbxproj
@@ -19,6 +19,14 @@
C6D42BE516ABB511002BB4F9 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D42BE416ABB511002BB4F9 /* WebKit.framework */; };
C6D42BE916ACF711002BB4F9 /* MCTMsgListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D42BE816ACF711002BB4F9 /* MCTMsgListViewController.m */; };
C6D42BEC16ACFE3F002BB4F9 /* MCTMsgViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D42BEB16ACFE3F002BB4F9 /* MCTMsgViewController.m */; };
+ C6D718CC178B7160008ED15F /* MailCore.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6BD28B3170BDFE500A91AC1 /* MailCore.framework */; };
+ C6D71912178B7942008ED15F /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D71911178B7942008ED15F /* SystemConfiguration.framework */; };
+ C6D7192F178B7D65008ED15F /* GTMOAuth2Authentication.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D71924178B7D55008ED15F /* GTMOAuth2Authentication.m */; };
+ C6D71930178B7D67008ED15F /* GTMOAuth2SignIn.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D71926178B7D55008ED15F /* GTMOAuth2SignIn.m */; };
+ C6D71931178B7D6C008ED15F /* GTMHTTPFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D7191F178B7D55008ED15F /* GTMHTTPFetcher.m */; };
+ C6D71932178B7D6E008ED15F /* GTMHTTPFetchHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D71921178B7D55008ED15F /* GTMHTTPFetchHistory.m */; };
+ C6D71933178B7D72008ED15F /* GTMOAuth2Window.xib in Resources */ = {isa = PBXBuildFile; fileRef = C6D71928178B7D55008ED15F /* GTMOAuth2Window.xib */; };
+ C6D71934178B7D72008ED15F /* GTMOAuth2WindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D7192A178B7D55008ED15F /* GTMOAuth2WindowController.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -66,6 +74,19 @@
};
/* End PBXContainerItemProxy section */
+/* Begin PBXCopyFilesBuildPhase section */
+ C6D718CB178B714A008ED15F /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ C6D718CC178B7160008ED15F /* MailCore.framework in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
AB666C5717484E6200545290 /* FXKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FXKeychain.h; path = ../../../common/FXKeychain.h; sourceTree = "<group>"; };
AB666C5817484E6200545290 /* FXKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FXKeychain.m; path = ../../../common/FXKeychain.m; sourceTree = "<group>"; };
@@ -90,6 +111,18 @@
C6D42BE816ACF711002BB4F9 /* MCTMsgListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCTMsgListViewController.m; sourceTree = "<group>"; };
C6D42BEA16ACFE3F002BB4F9 /* MCTMsgViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MCTMsgViewController.h; sourceTree = "<group>"; };
C6D42BEB16ACFE3F002BB4F9 /* MCTMsgViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MCTMsgViewController.m; sourceTree = "<group>"; };
+ C6D71911178B7942008ED15F /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
+ C6D7191E178B7D55008ED15F /* GTMHTTPFetcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcher.h; sourceTree = "<group>"; };
+ C6D7191F178B7D55008ED15F /* GTMHTTPFetcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcher.m; sourceTree = "<group>"; };
+ C6D71920178B7D55008ED15F /* GTMHTTPFetchHistory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetchHistory.h; sourceTree = "<group>"; };
+ C6D71921178B7D55008ED15F /* GTMHTTPFetchHistory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetchHistory.m; sourceTree = "<group>"; };
+ C6D71923178B7D55008ED15F /* GTMOAuth2Authentication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2Authentication.h; sourceTree = "<group>"; };
+ C6D71924178B7D55008ED15F /* GTMOAuth2Authentication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2Authentication.m; sourceTree = "<group>"; };
+ C6D71925178B7D55008ED15F /* GTMOAuth2SignIn.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2SignIn.h; sourceTree = "<group>"; };
+ C6D71926178B7D55008ED15F /* GTMOAuth2SignIn.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2SignIn.m; sourceTree = "<group>"; };
+ C6D71928178B7D55008ED15F /* GTMOAuth2Window.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GTMOAuth2Window.xib; sourceTree = "<group>"; };
+ C6D71929178B7D55008ED15F /* GTMOAuth2WindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GTMOAuth2WindowController.h; sourceTree = "<group>"; };
+ C6D7192A178B7D55008ED15F /* GTMOAuth2WindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GTMOAuth2WindowController.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -97,6 +130,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ C6D71912178B7942008ED15F /* SystemConfiguration.framework in Frameworks */,
AB666C5B1748558000545290 /* Security.framework in Frameworks */,
C6BD28B4170BDFE500A91AC1 /* MailCore.framework in Frameworks */,
C6D42BE516ABB511002BB4F9 /* WebKit.framework in Frameworks */,
@@ -186,6 +220,7 @@
C6D42BB416ABB39A002BB4F9 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ C6D71911178B7942008ED15F /* SystemConfiguration.framework */,
AB666C5A1748558000545290 /* Security.framework */,
C6D42BE416ABB511002BB4F9 /* WebKit.framework */,
C6D42BB516ABB39A002BB4F9 /* Cocoa.framework */,
@@ -207,6 +242,7 @@
C6D42BBB16ABB39A002BB4F9 /* macExample */ = {
isa = PBXGroup;
children = (
+ C6D7191C178B7D55008ED15F /* gtm-oauth2 */,
AB666C5217484E3100545290 /* External */,
C6D42BBC16ABB39A002BB4F9 /* Supporting Files */,
C6D42BC716ABB39A002BB4F9 /* AppDelegate.h */,
@@ -229,6 +265,49 @@
name = "Supporting Files";
sourceTree = "<group>";
};
+ C6D7191C178B7D55008ED15F /* gtm-oauth2 */ = {
+ isa = PBXGroup;
+ children = (
+ C6D7191D178B7D55008ED15F /* HTTPFetcher */,
+ C6D71922178B7D55008ED15F /* Source */,
+ );
+ name = "gtm-oauth2";
+ path = "../../../common/gtm-oauth2";
+ sourceTree = "<group>";
+ };
+ C6D7191D178B7D55008ED15F /* HTTPFetcher */ = {
+ isa = PBXGroup;
+ children = (
+ C6D7191E178B7D55008ED15F /* GTMHTTPFetcher.h */,
+ C6D7191F178B7D55008ED15F /* GTMHTTPFetcher.m */,
+ C6D71920178B7D55008ED15F /* GTMHTTPFetchHistory.h */,
+ C6D71921178B7D55008ED15F /* GTMHTTPFetchHistory.m */,
+ );
+ path = HTTPFetcher;
+ sourceTree = "<group>";
+ };
+ C6D71922178B7D55008ED15F /* Source */ = {
+ isa = PBXGroup;
+ children = (
+ C6D71923178B7D55008ED15F /* GTMOAuth2Authentication.h */,
+ C6D71924178B7D55008ED15F /* GTMOAuth2Authentication.m */,
+ C6D71925178B7D55008ED15F /* GTMOAuth2SignIn.h */,
+ C6D71926178B7D55008ED15F /* GTMOAuth2SignIn.m */,
+ C6D71927178B7D55008ED15F /* Mac */,
+ );
+ path = Source;
+ sourceTree = "<group>";
+ };
+ C6D71927178B7D55008ED15F /* Mac */ = {
+ isa = PBXGroup;
+ children = (
+ C6D71928178B7D55008ED15F /* GTMOAuth2Window.xib */,
+ C6D71929178B7D55008ED15F /* GTMOAuth2WindowController.h */,
+ C6D7192A178B7D55008ED15F /* GTMOAuth2WindowController.m */,
+ );
+ path = Mac;
+ sourceTree = "<group>";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -239,6 +318,7 @@
C6D42BAD16ABB39A002BB4F9 /* Sources */,
C6D42BAE16ABB39A002BB4F9 /* Frameworks */,
C6D42BAF16ABB39A002BB4F9 /* Resources */,
+ C6D718CB178B714A008ED15F /* CopyFiles */,
);
buildRules = (
);
@@ -326,6 +406,7 @@
buildActionMask = 2147483647;
files = (
C6D42BCC16ABB39A002BB4F9 /* MainMenu.xib in Resources */,
+ C6D71933178B7D72008ED15F /* GTMOAuth2Window.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -337,12 +418,17 @@
buildActionMask = 2147483647;
files = (
C6D42BC916ABB39A002BB4F9 /* AppDelegate.m in Sources */,
+ C6D71930178B7D67008ED15F /* GTMOAuth2SignIn.m in Sources */,
C6D42BE916ACF711002BB4F9 /* MCTMsgListViewController.m in Sources */,
C6D42BEC16ACFE3F002BB4F9 /* MCTMsgViewController.m in Sources */,
+ C6D71931178B7D6C008ED15F /* GTMHTTPFetcher.m in Sources */,
+ C6D71934178B7D72008ED15F /* GTMOAuth2WindowController.m in Sources */,
C64FF38416AF97F400F8C162 /* main.mm in Sources */,
C6BD2873170BC5C500A91AC1 /* MCOCIDURLProtocol.mm in Sources */,
C6BD2874170BC5C500A91AC1 /* MCOMessageView.mm in Sources */,
AB666C5917484E6200545290 /* FXKeychain.m in Sources */,
+ C6D7192F178B7D65008ED15F /* GTMOAuth2Authentication.m in Sources */,
+ C6D71932178B7D6E008ED15F /* GTMHTTPFetchHistory.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/example/mac/macExample/macExample/AppDelegate.m b/example/mac/macExample/macExample/AppDelegate.m
index 8ba89c37..8cf8e858 100644
--- a/example/mac/macExample/macExample/AppDelegate.m
+++ b/example/mac/macExample/macExample/AppDelegate.m
@@ -9,6 +9,8 @@
#import "AppDelegate.h"
#import <MailCore/MailCore.h>
+#import <GTMOAuth2/GTMOAuth2WindowController.h>
+#import <GTMOAuth2/GTMOAuth2SignIn.h>
#import "MCTMsgListViewController.h"
#import "FXKeychain.h"
@@ -17,6 +19,7 @@
@property (nonatomic, copy) NSString *login;
@property (nonatomic, copy) NSString *hostname;
@property (nonatomic, copy) NSString *password;
+@property (nonatomic, copy) NSString *oauth2Token;
@property (nonatomic, readonly) BOOL loginEnabled;
@property (nonatomic, readonly) BOOL loggingIn;
@@ -24,21 +27,89 @@
@property (nonatomic, retain) MCOIMAPOperation *checkOp;
@end
+#define CLIENT_ID @"the-client-id"
+#define CLIENT_SECRET @"the-client-secret"
+#define KEYCHAIN_ITEM_NAME @"MailCore OAuth 2.0 Token"
+
@implementation AppDelegate
- (void) dealloc
{
+ self.login = nil;
+ self.hostname = nil;
+ self.password = nil;
+ self.oauth2Token = nil;
+ self.session = nil;
+ self.checkOp = nil;
[super dealloc];
}
- (void) awakeFromNib
{
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"Hostname": @"imap.gmail.com" }];
-
- self.login = [[NSUserDefaults standardUserDefaults] stringForKey:@"Login"];
self.hostname = [[NSUserDefaults standardUserDefaults] stringForKey:@"Hostname"];
+
+ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"OAuth2Enabled"]) {
+ [self startOAuth2];
+ }
+ else {
+ [self startLogin];
+ }
+}
+
+- (void) startOAuth2
+{
+ GTMOAuth2Authentication * auth = [GTMOAuth2WindowController authForGoogleFromKeychainForName:KEYCHAIN_ITEM_NAME
+ clientID:CLIENT_ID
+ clientSecret:CLIENT_SECRET];
+
+ if ([auth refreshToken] == nil) {
+ GTMOAuth2WindowController *windowController = [[[GTMOAuth2WindowController alloc] initWithScope:@"https://mail.google.com/"
+ clientID:CLIENT_ID
+ clientSecret:CLIENT_SECRET
+ keychainItemName:KEYCHAIN_ITEM_NAME
+ resourceBundle:[NSBundle bundleForClass:[GTMOAuth2WindowController class]]] autorelease];
+ [windowController signInSheetModalForWindow:nil
+ delegate:self
+ finishedSelector:@selector(windowController:finishedWithAuth:error:)];
+ }
+ else {
+ [auth beginTokenFetchWithDelegate:self
+ didFinishSelector:@selector(auth:finishedRefreshWithFetcher:error:)];
+ }
+}
+
+- (void)auth:(GTMOAuth2Authentication *)auth
+finishedRefreshWithFetcher:(GTMHTTPFetcher *)fetcher
+ error:(NSError *)error {
+ [self windowController:nil finishedWithAuth:auth error:error];
+}
+
+- (void)windowController:(GTMOAuth2WindowController *)viewController
+ finishedWithAuth:(GTMOAuth2Authentication *)auth
+ error:(NSError *)error
+{
+ if (error != nil) {
+ NSLog(@"request failed");
+ // Authentication failed
+ } else {
+ // Authentication succeeded
+ [self retrieveAccessToken:auth];
+ }
+}
+
+- (void) retrieveAccessToken:(GTMOAuth2Authentication *)auth
+{
+ self.login = [auth userEmail];
+ self.oauth2Token = [auth accessToken];
+
+ [self accountLogin:nil];
+}
+
+- (void) startLogin
+{
+ self.login = [[NSUserDefaults standardUserDefaults] stringForKey:@"Login"];
self.password = [[FXKeychain defaultKeychain] objectForKey:@"Password"];
-
if (self.login.length && self.password.length) {
[self accountLogin:nil];
} else {
@@ -48,22 +119,36 @@
- (void) accountLogin:(id)sender
{
- NSLog(@"try login");
- [[NSUserDefaults standardUserDefaults] setObject:self.login forKey:@"Login"];
- [[NSUserDefaults standardUserDefaults] setObject:self.hostname forKey:@"Hostname"];
-
- if (![[[FXKeychain defaultKeychain] objectForKey:@"Password"] isEqualToString:self.password]) {
- [[FXKeychain defaultKeychain] removeObjectForKey:@"Password"];
- [[FXKeychain defaultKeychain] setObject:self.password forKey:@"Password"];
- }
-
- self.session = [[MCOIMAPSession alloc] init];
+ self.session = [[[MCOIMAPSession alloc] init] autorelease];
[self.session setHostname:self.hostname];
[self.session setPort:993];
- [self.session setUsername:self.login];
- [self.session setPassword:self.password];
+
+ NSLog(@"try login");
+ if (self.oauth2Token != nil) {
+ [self.session setUsername:self.login];
+ [self.session setOAuth2Token:self.oauth2Token];
+ [self.session setAuthType:MCOAuthTypeXOAuth2];
+ }
+ else {
+ [[NSUserDefaults standardUserDefaults] setObject:self.login forKey:@"Login"];
+ [[NSUserDefaults standardUserDefaults] setObject:self.hostname forKey:@"Hostname"];
+
+ if (![[[FXKeychain defaultKeychain] objectForKey:@"Password"] isEqualToString:self.password]) {
+ [[FXKeychain defaultKeychain] removeObjectForKey:@"Password"];
+ [[FXKeychain defaultKeychain] setObject:self.password forKey:@"Password"];
+ }
+
+ [self.session setUsername:self.login];
+ [self.session setPassword:self.password];
+ }
+
[self.session setConnectionType:MCOConnectionTypeTLS];
self.checkOp = [self.session checkAccountOperation];
+ self.session.connectionLogger = ^(void * connectionID, MCOConnectionLogType type, NSData * data) {
+ if (type != MCOConnectionLogTypeSentPrivate) {
+ NSLog(@"event logged:%p %i withData: %@", connectionID, type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
+ }
+ };
NSLog(@"start op");
[self.checkOp start:^(NSError * error) {
@@ -82,7 +167,7 @@
if (error != nil)
[_accountWindow makeKeyAndOrderFront:nil];
- [_msgListViewController connectWithHostname:self.hostname login:self.login password:self.password];
+ [_msgListViewController connectWithHostname:self.hostname login:self.login password:self.password oauth2Token:self.oauth2Token];
}];
}
diff --git a/example/mac/macExample/macExample/MCTMsgListViewController.h b/example/mac/macExample/macExample/MCTMsgListViewController.h
index 2998f1a2..0dee2422 100644
--- a/example/mac/macExample/macExample/MCTMsgListViewController.h
+++ b/example/mac/macExample/macExample/MCTMsgListViewController.h
@@ -19,6 +19,9 @@
NSArray * _messages;
}
-- (void) connectWithHostname:(NSString *)hostname login:(NSString *)login password:(NSString *)password;
+- (void) connectWithHostname:(NSString *)hostname
+ login:(NSString *)login
+ password:(NSString *)password
+ oauth2Token:(NSString *)oauth2Token;
@end
diff --git a/example/mac/macExample/macExample/MCTMsgListViewController.m b/example/mac/macExample/macExample/MCTMsgListViewController.m
index 498c66d1..0d7cee7a 100644
--- a/example/mac/macExample/macExample/MCTMsgListViewController.m
+++ b/example/mac/macExample/macExample/MCTMsgListViewController.m
@@ -21,20 +21,36 @@
@implementation MCTMsgListViewController
-- (void) connectWithHostname:(NSString *)hostname login:(NSString *)login password:(NSString *)password
+- (void) connectWithHostname:(NSString *)hostname
+ login:(NSString *)login
+ password:(NSString *)password
+ oauth2Token:(NSString *)oauth2Token
{
[_msgViewController setFolder:FOLDER];
- if (([login length] == 0) || ([password length] == 0))
- return;
+ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"OAuth2Enabled"]) {
+ if (([login length] == 0) || ([oauth2Token length] == 0))
+ return;
+ }
+ else {
+ if (([login length] == 0) || ([password length] == 0))
+ return;
+ }
self.loading = YES;
_session = [[MCOIMAPSession alloc] init];
[_session setHostname:hostname];
[_session setPort:993];
- [_session setUsername:login];
- [_session setPassword:password];
+ if ([[NSUserDefaults standardUserDefaults] boolForKey:@"OAuth2Enabled"]) {
+ [_session setUsername:login];
+ [_session setOAuth2Token:oauth2Token];
+ [_session setAuthType:MCOAuthTypeXOAuth2];
+ }
+ else {
+ [_session setUsername:login];
+ [_session setPassword:password];
+ }
[_session setConnectionType:MCOConnectionTypeTLS];
MCOIMAPMessagesRequestKind requestKind = (MCOIMAPMessagesRequestKind)
@@ -53,6 +69,7 @@
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"header.date" ascending:NO];
_messages = [[messages sortedArrayUsingDescriptors:@[sort]] retain];
+ NSLog(@"error: %@", error);
NSLog(@"%i messages", (int) [_messages count]);
//NSLog(@"%@", _messages);
[_tableView reloadData];
diff --git a/example/mac/macExample/macExample/en.lproj/MainMenu.xib b/example/mac/macExample/macExample/en.lproj/MainMenu.xib
index e49bd706..8470cb69 100644
--- a/example/mac/macExample/macExample/en.lproj/MainMenu.xib
+++ b/example/mac/macExample/macExample/en.lproj/MainMenu.xib
@@ -1,4004 +1,696 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
- <data>
- <int key="IBDocument.SystemTarget">1080</int>
- <string key="IBDocument.SystemVersion">12D78</string>
- <string key="IBDocument.InterfaceBuilderVersion">3084</string>
- <string key="IBDocument.AppKitVersion">1187.37</string>
- <string key="IBDocument.HIToolboxVersion">626.00</string>
- <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
- <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="NS.object.0">3084</string>
- </object>
- <array key="IBDocument.IntegratedClassDependencies">
- <string>NSButton</string>
- <string>NSButtonCell</string>
- <string>NSCustomObject</string>
- <string>NSCustomView</string>
- <string>NSMenu</string>
- <string>NSMenuItem</string>
- <string>NSProgressIndicator</string>
- <string>NSScrollView</string>
- <string>NSScroller</string>
- <string>NSSecureTextField</string>
- <string>NSSecureTextFieldCell</string>
- <string>NSTableColumn</string>
- <string>NSTableHeaderView</string>
- <string>NSTableView</string>
- <string>NSTextField</string>
- <string>NSTextFieldCell</string>
- <string>NSUserDefaultsController</string>
- <string>NSView</string>
- <string>NSViewController</string>
- <string>NSWindowTemplate</string>
- </array>
- <array key="IBDocument.PluginDependencies">
- <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- </array>
- <object class="NSMutableDictionary" key="IBDocument.Metadata">
- <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
- <integer value="1" key="NS.object.0"/>
- </object>
- <array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
- <object class="NSCustomObject" id="1021">
- <string key="NSClassName">NSApplication</string>
- </object>
- <object class="NSCustomObject" id="1014">
- <string key="NSClassName">FirstResponder</string>
- </object>
- <object class="NSCustomObject" id="1050">
- <string key="NSClassName">NSApplication</string>
- </object>
- <object class="NSMenu" id="649796088">
- <string key="NSTitle">AMainMenu</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="694149608">
- <reference key="NSMenu" ref="649796088"/>
- <string key="NSTitle">macExample</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <object class="NSCustomResource" key="NSOnImage" id="35465992">
- <string key="NSClassName">NSImage</string>
- <string key="NSResourceName">NSMenuCheckmark</string>
- </object>
- <object class="NSCustomResource" key="NSMixedImage" id="502551668">
- <string key="NSClassName">NSImage</string>
- <string key="NSResourceName">NSMenuMixedState</string>
- </object>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="110575045">
- <string key="NSTitle">macExample</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="238522557">
- <reference key="NSMenu" ref="110575045"/>
- <string key="NSTitle">About macExample</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="304266470">
- <reference key="NSMenu" ref="110575045"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="609285721">
- <reference key="NSMenu" ref="110575045"/>
- <string key="NSTitle">Preferences…</string>
- <string key="NSKeyEquiv">,</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="481834944">
- <reference key="NSMenu" ref="110575045"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="1046388886">
- <reference key="NSMenu" ref="110575045"/>
- <string key="NSTitle">Services</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="752062318">
- <string key="NSTitle">Services</string>
- <array class="NSMutableArray" key="NSMenuItems"/>
- <string key="NSName">_NSServicesMenu</string>
- </object>
- </object>
- <object class="NSMenuItem" id="646227648">
- <reference key="NSMenu" ref="110575045"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="755159360">
- <reference key="NSMenu" ref="110575045"/>
- <string key="NSTitle">Hide macExample</string>
- <string key="NSKeyEquiv">h</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="342932134">
- <reference key="NSMenu" ref="110575045"/>
- <string key="NSTitle">Hide Others</string>
- <string key="NSKeyEquiv">h</string>
- <int key="NSKeyEquivModMask">1572864</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="908899353">
- <reference key="NSMenu" ref="110575045"/>
- <string key="NSTitle">Show All</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="1056857174">
- <reference key="NSMenu" ref="110575045"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="632727374">
- <reference key="NSMenu" ref="110575045"/>
- <string key="NSTitle">Quit testUI</string>
- <string key="NSKeyEquiv">q</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- <string key="NSName">_NSAppleMenu</string>
- </object>
- </object>
- <object class="NSMenuItem" id="379814623">
- <reference key="NSMenu" ref="649796088"/>
- <string key="NSTitle">File</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="720053764">
- <string key="NSTitle">File</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="354256217">
- <reference key="NSMenu" ref="720053764"/>
- <string key="NSTitle">Account...</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="521030078">
- <reference key="NSMenu" ref="720053764"/>
- <string key="NSTitle">Fetch Full Message</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="425164168">
- <reference key="NSMenu" ref="720053764"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="776162233">
- <reference key="NSMenu" ref="720053764"/>
- <string key="NSTitle">Close</string>
- <string key="NSKeyEquiv">w</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="952259628">
- <reference key="NSMenu" ref="649796088"/>
- <string key="NSTitle">Edit</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="789758025">
- <string key="NSTitle">Edit</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="1058277027">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Undo</string>
- <string key="NSKeyEquiv">z</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="790794224">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Redo</string>
- <string key="NSKeyEquiv">Z</string>
- <int key="NSKeyEquivModMask">1179648</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="1040322652">
- <reference key="NSMenu" ref="789758025"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="296257095">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Cut</string>
- <string key="NSKeyEquiv">x</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="860595796">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Copy</string>
- <string key="NSKeyEquiv">c</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="29853731">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Paste</string>
- <string key="NSKeyEquiv">v</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="82994268">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Paste and Match Style</string>
- <string key="NSKeyEquiv">V</string>
- <int key="NSKeyEquivModMask">1572864</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="437104165">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Delete</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="583158037">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Select All</string>
- <string key="NSKeyEquiv">a</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="212016141">
- <reference key="NSMenu" ref="789758025"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="892235320">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Find</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="963351320">
- <string key="NSTitle">Find</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="447796847">
- <reference key="NSMenu" ref="963351320"/>
- <string key="NSTitle">Find…</string>
- <string key="NSKeyEquiv">f</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">1</int>
- </object>
- <object class="NSMenuItem" id="738670835">
- <reference key="NSMenu" ref="963351320"/>
- <string key="NSTitle">Find and Replace…</string>
- <string key="NSKeyEquiv">f</string>
- <int key="NSKeyEquivModMask">1572864</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">12</int>
- </object>
- <object class="NSMenuItem" id="326711663">
- <reference key="NSMenu" ref="963351320"/>
- <string key="NSTitle">Find Next</string>
- <string key="NSKeyEquiv">g</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">2</int>
- </object>
- <object class="NSMenuItem" id="270902937">
- <reference key="NSMenu" ref="963351320"/>
- <string key="NSTitle">Find Previous</string>
- <string key="NSKeyEquiv">G</string>
- <int key="NSKeyEquivModMask">1179648</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">3</int>
- </object>
- <object class="NSMenuItem" id="159080638">
- <reference key="NSMenu" ref="963351320"/>
- <string key="NSTitle">Use Selection for Find</string>
- <string key="NSKeyEquiv">e</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">7</int>
- </object>
- <object class="NSMenuItem" id="88285865">
- <reference key="NSMenu" ref="963351320"/>
- <string key="NSTitle">Jump to Selection</string>
- <string key="NSKeyEquiv">j</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="972420730">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Spelling and Grammar</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="769623530">
- <string key="NSTitle">Spelling and Grammar</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="679648819">
- <reference key="NSMenu" ref="769623530"/>
- <string key="NSTitle">Show Spelling and Grammar</string>
- <string key="NSKeyEquiv">:</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="96193923">
- <reference key="NSMenu" ref="769623530"/>
- <string key="NSTitle">Check Document Now</string>
- <string key="NSKeyEquiv">;</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="859480356">
- <reference key="NSMenu" ref="769623530"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="948374510">
- <reference key="NSMenu" ref="769623530"/>
- <string key="NSTitle">Check Spelling While Typing</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="967646866">
- <reference key="NSMenu" ref="769623530"/>
- <string key="NSTitle">Check Grammar With Spelling</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="795346622">
- <reference key="NSMenu" ref="769623530"/>
- <string key="NSTitle">Correct Spelling Automatically</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="507821607">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Substitutions</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="698887838">
- <string key="NSTitle">Substitutions</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="65139061">
- <reference key="NSMenu" ref="698887838"/>
- <string key="NSTitle">Show Substitutions</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="19036812">
- <reference key="NSMenu" ref="698887838"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="605118523">
- <reference key="NSMenu" ref="698887838"/>
- <string key="NSTitle">Smart Copy/Paste</string>
- <string key="NSKeyEquiv">f</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">1</int>
- </object>
- <object class="NSMenuItem" id="197661976">
- <reference key="NSMenu" ref="698887838"/>
- <string key="NSTitle">Smart Quotes</string>
- <string key="NSKeyEquiv">g</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">2</int>
- </object>
- <object class="NSMenuItem" id="672708820">
- <reference key="NSMenu" ref="698887838"/>
- <string key="NSTitle">Smart Dashes</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="708854459">
- <reference key="NSMenu" ref="698887838"/>
- <string key="NSTitle">Smart Links</string>
- <string key="NSKeyEquiv">G</string>
- <int key="NSKeyEquivModMask">1179648</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">3</int>
- </object>
- <object class="NSMenuItem" id="537092702">
- <reference key="NSMenu" ref="698887838"/>
- <string key="NSTitle">Text Replacement</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="288088188">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Transformations</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="579392910">
- <string key="NSTitle">Transformations</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="1060694897">
- <reference key="NSMenu" ref="579392910"/>
- <string key="NSTitle">Make Upper Case</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="879586729">
- <reference key="NSMenu" ref="579392910"/>
- <string key="NSTitle">Make Lower Case</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="56570060">
- <reference key="NSMenu" ref="579392910"/>
- <string key="NSTitle">Capitalize</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="676164635">
- <reference key="NSMenu" ref="789758025"/>
- <string key="NSTitle">Speech</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="785027613">
- <string key="NSTitle">Speech</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="731782645">
- <reference key="NSMenu" ref="785027613"/>
- <string key="NSTitle">Start Speaking</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="680220178">
- <reference key="NSMenu" ref="785027613"/>
- <string key="NSTitle">Stop Speaking</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="302598603">
- <reference key="NSMenu" ref="649796088"/>
- <string key="NSTitle">Format</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="941447902">
- <string key="NSTitle">Format</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="792887677">
- <reference key="NSMenu" ref="941447902"/>
- <string key="NSTitle">Font</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="786677654">
- <string key="NSTitle">Font</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="159677712">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Show Fonts</string>
- <string key="NSKeyEquiv">t</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="305399458">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Bold</string>
- <string key="NSKeyEquiv">b</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">2</int>
- </object>
- <object class="NSMenuItem" id="814362025">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Italic</string>
- <string key="NSKeyEquiv">i</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">1</int>
- </object>
- <object class="NSMenuItem" id="330926929">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Underline</string>
- <string key="NSKeyEquiv">u</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="533507878">
- <reference key="NSMenu" ref="786677654"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="158063935">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Bigger</string>
- <string key="NSKeyEquiv">+</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">3</int>
- </object>
- <object class="NSMenuItem" id="885547335">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Smaller</string>
- <string key="NSKeyEquiv">-</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <int key="NSTag">4</int>
- </object>
- <object class="NSMenuItem" id="901062459">
- <reference key="NSMenu" ref="786677654"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="767671776">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Kern</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="175441468">
- <string key="NSTitle">Kern</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="252969304">
- <reference key="NSMenu" ref="175441468"/>
- <string key="NSTitle">Use Default</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="766922938">
- <reference key="NSMenu" ref="175441468"/>
- <string key="NSTitle">Use None</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="677519740">
- <reference key="NSMenu" ref="175441468"/>
- <string key="NSTitle">Tighten</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="238351151">
- <reference key="NSMenu" ref="175441468"/>
- <string key="NSTitle">Loosen</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="691570813">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Ligatures</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="1058217995">
- <string key="NSTitle">Ligatures</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="706297211">
- <reference key="NSMenu" ref="1058217995"/>
- <string key="NSTitle">Use Default</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="568384683">
- <reference key="NSMenu" ref="1058217995"/>
- <string key="NSTitle">Use None</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="663508465">
- <reference key="NSMenu" ref="1058217995"/>
- <string key="NSTitle">Use All</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="769124883">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Baseline</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="18263474">
- <string key="NSTitle">Baseline</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="257962622">
- <reference key="NSMenu" ref="18263474"/>
- <string key="NSTitle">Use Default</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="644725453">
- <reference key="NSMenu" ref="18263474"/>
- <string key="NSTitle">Superscript</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="1037576581">
- <reference key="NSMenu" ref="18263474"/>
- <string key="NSTitle">Subscript</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="941806246">
- <reference key="NSMenu" ref="18263474"/>
- <string key="NSTitle">Raise</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="1045724900">
- <reference key="NSMenu" ref="18263474"/>
- <string key="NSTitle">Lower</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="739652853">
- <reference key="NSMenu" ref="786677654"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="1012600125">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Show Colors</string>
- <string key="NSKeyEquiv">C</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="214559597">
- <reference key="NSMenu" ref="786677654"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="596732606">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Copy Style</string>
- <string key="NSKeyEquiv">c</string>
- <int key="NSKeyEquivModMask">1572864</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="393423671">
- <reference key="NSMenu" ref="786677654"/>
- <string key="NSTitle">Paste Style</string>
- <string key="NSKeyEquiv">v</string>
- <int key="NSKeyEquivModMask">1572864</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- <string key="NSName">_NSFontMenu</string>
- </object>
- </object>
- <object class="NSMenuItem" id="215659978">
- <reference key="NSMenu" ref="941447902"/>
- <string key="NSTitle">Text</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="446991534">
- <string key="NSTitle">Text</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="875092757">
- <reference key="NSMenu" ref="446991534"/>
- <string key="NSTitle">Align Left</string>
- <string key="NSKeyEquiv">{</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="630155264">
- <reference key="NSMenu" ref="446991534"/>
- <string key="NSTitle">Center</string>
- <string key="NSKeyEquiv">|</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="945678886">
- <reference key="NSMenu" ref="446991534"/>
- <string key="NSTitle">Justify</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="512868991">
- <reference key="NSMenu" ref="446991534"/>
- <string key="NSTitle">Align Right</string>
- <string key="NSKeyEquiv">}</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="163117631">
- <reference key="NSMenu" ref="446991534"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="31516759">
- <reference key="NSMenu" ref="446991534"/>
- <string key="NSTitle">Writing Direction</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="956096989">
- <string key="NSTitle">Writing Direction</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="257099033">
- <reference key="NSMenu" ref="956096989"/>
- <bool key="NSIsDisabled">YES</bool>
- <string key="NSTitle">Paragraph</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="551969625">
- <reference key="NSMenu" ref="956096989"/>
- <string type="base64-UTF8" key="NSTitle">CURlZmF1bHQ</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="249532473">
- <reference key="NSMenu" ref="956096989"/>
- <string type="base64-UTF8" key="NSTitle">CUxlZnQgdG8gUmlnaHQ</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="607364498">
- <reference key="NSMenu" ref="956096989"/>
- <string type="base64-UTF8" key="NSTitle">CVJpZ2h0IHRvIExlZnQ</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="508151438">
- <reference key="NSMenu" ref="956096989"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="981751889">
- <reference key="NSMenu" ref="956096989"/>
- <bool key="NSIsDisabled">YES</bool>
- <string key="NSTitle">Selection</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="380031999">
- <reference key="NSMenu" ref="956096989"/>
- <string type="base64-UTF8" key="NSTitle">CURlZmF1bHQ</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="825984362">
- <reference key="NSMenu" ref="956096989"/>
- <string type="base64-UTF8" key="NSTitle">CUxlZnQgdG8gUmlnaHQ</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="560145579">
- <reference key="NSMenu" ref="956096989"/>
- <string type="base64-UTF8" key="NSTitle">CVJpZ2h0IHRvIExlZnQ</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="908105787">
- <reference key="NSMenu" ref="446991534"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="644046920">
- <reference key="NSMenu" ref="446991534"/>
- <string key="NSTitle">Show Ruler</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="231811626">
- <reference key="NSMenu" ref="446991534"/>
- <string key="NSTitle">Copy Ruler</string>
- <string key="NSKeyEquiv">c</string>
- <int key="NSKeyEquivModMask">1310720</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="883618387">
- <reference key="NSMenu" ref="446991534"/>
- <string key="NSTitle">Paste Ruler</string>
- <string key="NSKeyEquiv">v</string>
- <int key="NSKeyEquivModMask">1310720</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- </object>
- </object>
- </array>
- </object>
- </object>
- <object class="NSMenuItem" id="713487014">
- <reference key="NSMenu" ref="649796088"/>
- <string key="NSTitle">Window</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="835318025">
- <string key="NSTitle">Window</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="1011231497">
- <reference key="NSMenu" ref="835318025"/>
- <string key="NSTitle">Minimize</string>
- <string key="NSKeyEquiv">m</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="575023229">
- <reference key="NSMenu" ref="835318025"/>
- <string key="NSTitle">Zoom</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="299356726">
- <reference key="NSMenu" ref="835318025"/>
- <bool key="NSIsDisabled">YES</bool>
- <bool key="NSIsSeparator">YES</bool>
- <string key="NSTitle"/>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- <object class="NSMenuItem" id="625202149">
- <reference key="NSMenu" ref="835318025"/>
- <string key="NSTitle">Bring All to Front</string>
- <string key="NSKeyEquiv"/>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- <string key="NSName">_NSWindowsMenu</string>
- </object>
- </object>
- <object class="NSMenuItem" id="448692316">
- <reference key="NSMenu" ref="649796088"/>
- <string key="NSTitle">Help</string>
- <string key="NSKeyEquiv"/>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- <string key="NSAction">submenuAction:</string>
- <object class="NSMenu" key="NSSubmenu" id="992780483">
- <string key="NSTitle">Help</string>
- <array class="NSMutableArray" key="NSMenuItems">
- <object class="NSMenuItem" id="105068016">
- <reference key="NSMenu" ref="992780483"/>
- <string key="NSTitle">macExample Help</string>
- <string key="NSKeyEquiv">?</string>
- <int key="NSKeyEquivModMask">1048576</int>
- <int key="NSMnemonicLoc">2147483647</int>
- <reference key="NSOnImage" ref="35465992"/>
- <reference key="NSMixedImage" ref="502551668"/>
- </object>
- </array>
- <string key="NSName">_NSHelpMenu</string>
- </object>
- </object>
- </array>
- <string key="NSName">_NSMainMenu</string>
- </object>
- <object class="NSWindowTemplate" id="972006081">
- <int key="NSWindowStyleMask">15</int>
- <int key="NSWindowBacking">2</int>
- <string key="NSWindowRect">{{344, 127}, {768, 493}}</string>
- <int key="NSWTFlags">1948778496</int>
- <string key="NSWindowTitle">testUI</string>
- <string key="NSWindowClass">NSWindow</string>
- <nil key="NSViewClass"/>
- <nil key="NSUserInterfaceItemIdentifier"/>
- <object class="NSView" key="NSWindowView" id="439893737">
- <reference key="NSNextResponder"/>
- <int key="NSvFlags">256</int>
- <array class="NSMutableArray" key="NSSubviews">
- <object class="NSScrollView" id="665197104">
- <reference key="NSNextResponder" ref="439893737"/>
- <int key="NSvFlags">284</int>
- <array class="NSMutableArray" key="NSSubviews">
- <object class="NSClipView" id="1057040196">
- <reference key="NSNextResponder" ref="665197104"/>
- <int key="NSvFlags">2304</int>
- <array class="NSMutableArray" key="NSSubviews">
- <object class="NSTableView" id="818403152">
- <reference key="NSNextResponder" ref="1057040196"/>
- <int key="NSvFlags">256</int>
- <string key="NSFrameSize">{192, 477}</string>
- <reference key="NSSuperview" ref="1057040196"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="849854472"/>
- <string key="NSReuseIdentifierKey">_NS:13</string>
- <bool key="NSEnabled">YES</bool>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- <bool key="NSControlAllowsExpansionToolTips">YES</bool>
- <object class="NSTableHeaderView" key="NSHeaderView" id="8853202">
- <reference key="NSNextResponder" ref="958887582"/>
- <int key="NSvFlags">256</int>
- <string key="NSFrameSize">{192, 17}</string>
- <reference key="NSSuperview" ref="958887582"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="1057040196"/>
- <string key="NSReuseIdentifierKey">_NS:16</string>
- <reference key="NSTableView" ref="818403152"/>
- </object>
- <object class="_NSCornerView" key="NSCornerView">
- <nil key="NSNextResponder"/>
- <int key="NSvFlags">-2147483392</int>
- <string key="NSFrame">{{224, 0}, {16, 17}}</string>
- <reference key="NSNextKeyView" ref="1057040196"/>
- <string key="NSReuseIdentifierKey">_NS:19</string>
- </object>
- <array class="NSMutableArray" key="NSTableColumns">
- <object class="NSTableColumn" id="631488934">
- <double key="NSWidth">188.68359375</double>
- <double key="NSMinWidth">40</double>
- <double key="NSMaxWidth">1000</double>
- <object class="NSTableHeaderCell" key="NSHeaderCell">
- <int key="NSCellFlags">75497536</int>
- <int key="NSCellFlags2">2048</int>
- <string key="NSContents"/>
- <object class="NSFont" key="NSSupport">
- <string key="NSName">LucidaGrande</string>
- <double key="NSSize">11</double>
- <int key="NSfFlags">3100</int>
- </object>
- <object class="NSColor" key="NSBackgroundColor">
- <int key="NSColorSpace">3</int>
- <bytes key="NSWhite">MC4zMzMzMzI5ODU2AA</bytes>
- </object>
- <object class="NSColor" key="NSTextColor">
- <int key="NSColorSpace">6</int>
- <string key="NSCatalogName">System</string>
- <string key="NSColorName">headerTextColor</string>
- <object class="NSColor" key="NSColor" id="342765170">
- <int key="NSColorSpace">3</int>
- <bytes key="NSWhite">MAA</bytes>
- </object>
- </object>
- </object>
- <object class="NSTextFieldCell" key="NSDataCell" id="665052716">
- <int key="NSCellFlags">337641536</int>
- <int key="NSCellFlags2">2048</int>
- <string key="NSContents">Text Cell</string>
- <object class="NSFont" key="NSSupport" id="640234263">
- <string key="NSName">LucidaGrande</string>
- <double key="NSSize">13</double>
- <int key="NSfFlags">1044</int>
- </object>
- <reference key="NSControlView" ref="818403152"/>
- <object class="NSColor" key="NSBackgroundColor" id="866538965">
- <int key="NSColorSpace">6</int>
- <string key="NSCatalogName">System</string>
- <string key="NSColorName">controlBackgroundColor</string>
- <object class="NSColor" key="NSColor" id="428798902">
- <int key="NSColorSpace">3</int>
- <bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
- </object>
- </object>
- <object class="NSColor" key="NSTextColor" id="567109269">
- <int key="NSColorSpace">6</int>
- <string key="NSCatalogName">System</string>
- <string key="NSColorName">controlTextColor</string>
- <reference key="NSColor" ref="342765170"/>
- </object>
- </object>
- <int key="NSResizingMask">3</int>
- <bool key="NSIsResizeable">YES</bool>
- <bool key="NSIsEditable">YES</bool>
- <reference key="NSTableView" ref="818403152"/>
- </object>
- </array>
- <double key="NSIntercellSpacingWidth">3</double>
- <double key="NSIntercellSpacingHeight">2</double>
- <object class="NSColor" key="NSBackgroundColor" id="492204717">
- <int key="NSColorSpace">3</int>
- <bytes key="NSWhite">MQA</bytes>
- </object>
- <object class="NSColor" key="NSGridColor">
- <int key="NSColorSpace">6</int>
- <string key="NSCatalogName">System</string>
- <string key="NSColorName">gridColor</string>
- <object class="NSColor" key="NSColor">
- <int key="NSColorSpace">3</int>
- <bytes key="NSWhite">MC41AA</bytes>
- </object>
- </object>
- <double key="NSRowHeight">17</double>
- <int key="NSTvFlags">-700448768</int>
- <reference key="NSDelegate"/>
- <reference key="NSDataSource"/>
- <int key="NSColumnAutoresizingStyle">4</int>
- <int key="NSDraggingSourceMaskForLocal">15</int>
- <int key="NSDraggingSourceMaskForNonLocal">0</int>
- <bool key="NSAllowsTypeSelect">YES</bool>
- <int key="NSTableViewDraggingDestinationStyle">0</int>
- <int key="NSTableViewGroupRowStyle">1</int>
- </object>
- </array>
- <string key="NSFrame">{{1, 17}, {192, 477}}</string>
- <reference key="NSSuperview" ref="665197104"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="818403152"/>
- <string key="NSReuseIdentifierKey">_NS:11</string>
- <reference key="NSDocView" ref="818403152"/>
- <reference key="NSBGColor" ref="866538965"/>
- <int key="NScvFlags">4</int>
- </object>
- <object class="NSScroller" id="849854472">
- <reference key="NSNextResponder" ref="665197104"/>
- <int key="NSvFlags">-2147483392</int>
- <string key="NSFrame">{{224, 17}, {15, 102}}</string>
- <reference key="NSSuperview" ref="665197104"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="1027823325"/>
- <string key="NSReuseIdentifierKey">_NS:58</string>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- <reference key="NSTarget" ref="665197104"/>
- <string key="NSAction">_doScroller:</string>
- <double key="NSCurValue">37</double>
- <double key="NSPercent">0.1947367936372757</double>
- </object>
- <object class="NSScroller" id="1027823325">
- <reference key="NSNextResponder" ref="665197104"/>
- <int key="NSvFlags">-2147483392</int>
- <string key="NSFrame">{{1, 478}, {335, 16}}</string>
- <reference key="NSSuperview" ref="665197104"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="220665056"/>
- <string key="NSReuseIdentifierKey">_NS:60</string>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- <int key="NSsFlags">1</int>
- <reference key="NSTarget" ref="665197104"/>
- <string key="NSAction">_doScroller:</string>
- <double key="NSCurValue">1</double>
- <double key="NSPercent">0.97953216374269003</double>
- </object>
- <object class="NSClipView" id="958887582">
- <reference key="NSNextResponder" ref="665197104"/>
- <int key="NSvFlags">2304</int>
- <array class="NSMutableArray" key="NSSubviews">
- <reference ref="8853202"/>
- </array>
- <string key="NSFrame">{{1, 0}, {192, 17}}</string>
- <reference key="NSSuperview" ref="665197104"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="8853202"/>
- <string key="NSReuseIdentifierKey">_NS:15</string>
- <reference key="NSDocView" ref="8853202"/>
- <reference key="NSBGColor" ref="866538965"/>
- <int key="NScvFlags">4</int>
- </object>
- </array>
- <string key="NSFrame">{{-1, 0}, {194, 495}}</string>
- <reference key="NSSuperview" ref="439893737"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="958887582"/>
- <string key="NSReuseIdentifierKey">_NS:9</string>
- <int key="NSsFlags">133682</int>
- <reference key="NSVScroller" ref="849854472"/>
- <reference key="NSHScroller" ref="1027823325"/>
- <reference key="NSContentView" ref="1057040196"/>
- <reference key="NSHeaderClipView" ref="958887582"/>
- <bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
- <double key="NSMinMagnification">0.25</double>
- <double key="NSMaxMagnification">4</double>
- <double key="NSMagnification">1</double>
- </object>
- <object class="NSCustomView" id="220665056">
- <reference key="NSNextResponder" ref="439893737"/>
- <int key="NSvFlags">274</int>
- <array class="NSMutableArray" key="NSSubviews"/>
- <string key="NSFrame">{{201, 0}, {567, 493}}</string>
- <reference key="NSSuperview" ref="439893737"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="677395401"/>
- <string key="NSReuseIdentifierKey">_NS:9</string>
- <string key="NSClassName">MCOMessageView</string>
- </object>
- <object class="NSProgressIndicator" id="677395401">
- <reference key="NSNextResponder" ref="439893737"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{468, 230}, {32, 32}}</string>
- <reference key="NSSuperview" ref="439893737"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView"/>
- <string key="NSReuseIdentifierKey">_NS:945</string>
- <int key="NSpiFlags">20490</int>
- <double key="NSMaxValue">100</double>
- </object>
- </array>
- <string key="NSFrameSize">{768, 493}</string>
- <reference key="NSSuperview"/>
- <reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="665197104"/>
- <bool key="NSViewIsLayerTreeHost">YES</bool>
- </object>
- <string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
- <string key="NSMaxSize">{10000000000000, 10000000000000}</string>
- <bool key="NSWindowIsRestorable">YES</bool>
- </object>
- <object class="NSWindowTemplate" id="389393842">
- <int key="NSWindowStyleMask">3</int>
- <int key="NSWindowBacking">2</int>
- <string key="NSWindowRect">{{505, 474}, {409, 153}}</string>
- <int key="NSWTFlags">611845120</int>
- <string key="NSWindowTitle">Account</string>
- <string key="NSWindowClass">NSWindow</string>
- <nil key="NSViewClass"/>
- <nil key="NSUserInterfaceItemIdentifier"/>
- <object class="NSView" key="NSWindowView" id="370006470">
- <reference key="NSNextResponder"/>
- <int key="NSvFlags">256</int>
- <array class="NSMutableArray" key="NSSubviews">
- <object class="NSTextField" id="369746882">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{116, 111}, {249, 22}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView" ref="47969306"/>
- <string key="NSReuseIdentifierKey">_NS:9</string>
- <bool key="NSEnabled">YES</bool>
- <object class="NSTextFieldCell" key="NSCell" id="163183536">
- <int key="NSCellFlags">-1804599231</int>
- <int key="NSCellFlags2">272630784</int>
- <string key="NSContents"/>
- <reference key="NSSupport" ref="640234263"/>
- <string key="NSCellIdentifier">_NS:9</string>
- <reference key="NSControlView" ref="369746882"/>
- <bool key="NSDrawsBackground">YES</bool>
- <object class="NSColor" key="NSBackgroundColor" id="412315037">
- <int key="NSColorSpace">6</int>
- <string key="NSCatalogName">System</string>
- <string key="NSColorName">textBackgroundColor</string>
- <reference key="NSColor" ref="492204717"/>
- </object>
- <object class="NSColor" key="NSTextColor" id="906820664">
- <int key="NSColorSpace">6</int>
- <string key="NSCatalogName">System</string>
- <string key="NSColorName">textColor</string>
- <reference key="NSColor" ref="342765170"/>
- </object>
- </object>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- </object>
- <object class="NSTextField" id="148390580">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{72, 114}, {39, 17}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView" ref="369746882"/>
- <string key="NSReuseIdentifierKey">_NS:1535</string>
- <bool key="NSEnabled">YES</bool>
- <object class="NSTextFieldCell" key="NSCell" id="746004588">
- <int key="NSCellFlags">68157504</int>
- <int key="NSCellFlags2">272630784</int>
- <string key="NSContents">Login</string>
- <reference key="NSSupport" ref="640234263"/>
- <string key="NSCellIdentifier">_NS:1535</string>
- <reference key="NSControlView" ref="148390580"/>
- <object class="NSColor" key="NSBackgroundColor" id="809738508">
- <int key="NSColorSpace">6</int>
- <string key="NSCatalogName">System</string>
- <string key="NSColorName">controlColor</string>
- <reference key="NSColor" ref="428798902"/>
- </object>
- <reference key="NSTextColor" ref="567109269"/>
- </object>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- </object>
- <object class="NSTextField" id="47969306">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{47, 82}, {64, 17}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView" ref="217026742"/>
- <string key="NSReuseIdentifierKey">_NS:1535</string>
- <bool key="NSEnabled">YES</bool>
- <object class="NSTextFieldCell" key="NSCell" id="960551038">
- <int key="NSCellFlags">68157504</int>
- <int key="NSCellFlags2">272630784</int>
- <string key="NSContents">Password</string>
- <reference key="NSSupport" ref="640234263"/>
- <string key="NSCellIdentifier">_NS:1535</string>
- <reference key="NSControlView" ref="47969306"/>
- <reference key="NSBackgroundColor" ref="809738508"/>
- <reference key="NSTextColor" ref="567109269"/>
- </object>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- </object>
- <object class="NSSecureTextField" id="217026742">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{116, 79}, {249, 22}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView" ref="204654299"/>
- <string key="NSReuseIdentifierKey">_NS:9</string>
- <bool key="NSEnabled">YES</bool>
- <object class="NSSecureTextFieldCell" key="NSCell" id="338490228">
- <int key="NSCellFlags">342884416</int>
- <int key="NSCellFlags2">272630848</int>
- <string key="NSContents"/>
- <reference key="NSSupport" ref="640234263"/>
- <string key="NSCellIdentifier">_NS:9</string>
- <reference key="NSControlView" ref="217026742"/>
- <bool key="NSDrawsBackground">YES</bool>
- <reference key="NSBackgroundColor" ref="412315037"/>
- <reference key="NSTextColor" ref="906820664"/>
- <array key="NSAllowedInputLocales">
- <string>NSAllRomanInputSourcesLocaleIdentifier</string>
- </array>
- </object>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- </object>
- <object class="NSButton" id="372944324">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{296, 11}, {75, 32}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView" ref="758205814"/>
- <string key="NSReuseIdentifierKey">_NS:9</string>
- <bool key="NSEnabled">YES</bool>
- <object class="NSButtonCell" key="NSCell" id="496976666">
- <int key="NSCellFlags">67108864</int>
- <int key="NSCellFlags2">134217728</int>
- <string key="NSContents">Login</string>
- <reference key="NSSupport" ref="640234263"/>
- <string key="NSCellIdentifier">_NS:9</string>
- <reference key="NSControlView" ref="372944324"/>
- <int key="NSButtonFlags">-2038284288</int>
- <int key="NSButtonFlags2">129</int>
- <string key="NSAlternateContents"/>
- <string type="base64-UTF8" key="NSKeyEquivalent">DQ</string>
- <int key="NSPeriodicDelay">200</int>
- <int key="NSPeriodicInterval">25</int>
- </object>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- </object>
- <object class="NSButton" id="602401876">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{214, 11}, {82, 32}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView" ref="372944324"/>
- <string key="NSReuseIdentifierKey">_NS:9</string>
- <bool key="NSEnabled">YES</bool>
- <object class="NSButtonCell" key="NSCell" id="867739186">
- <int key="NSCellFlags">67108864</int>
- <int key="NSCellFlags2">134217728</int>
- <string key="NSContents">Cancel</string>
- <reference key="NSSupport" ref="640234263"/>
- <string key="NSCellIdentifier">_NS:9</string>
- <reference key="NSControlView" ref="602401876"/>
- <int key="NSButtonFlags">-2038284288</int>
- <int key="NSButtonFlags2">129</int>
- <string key="NSAlternateContents"/>
- <string type="base64-UTF8" key="NSKeyEquivalent">Gw</string>
- <int key="NSPeriodicDelay">200</int>
- <int key="NSPeriodicInterval">25</int>
- </object>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- </object>
- <object class="NSProgressIndicator" id="758205814">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{373, 20}, {16, 16}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView"/>
- <string key="NSReuseIdentifierKey">_NS:945</string>
- <int key="NSpiFlags">28938</int>
- <double key="NSMaxValue">100</double>
- </object>
- <object class="NSTextField" id="1021403255">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{116, 47}, {249, 22}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView" ref="602401876"/>
- <string key="NSReuseIdentifierKey">_NS:9</string>
- <bool key="NSEnabled">YES</bool>
- <object class="NSTextFieldCell" key="NSCell" id="828733144">
- <int key="NSCellFlags">-1804599231</int>
- <int key="NSCellFlags2">272630784</int>
- <string key="NSContents"/>
- <reference key="NSSupport" ref="640234263"/>
- <string key="NSCellIdentifier">_NS:9</string>
- <reference key="NSControlView" ref="1021403255"/>
- <bool key="NSDrawsBackground">YES</bool>
- <reference key="NSBackgroundColor" ref="412315037"/>
- <reference key="NSTextColor" ref="906820664"/>
- </object>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- </object>
- <object class="NSTextField" id="204654299">
- <reference key="NSNextResponder" ref="370006470"/>
- <int key="NSvFlags">268</int>
- <string key="NSFrame">{{43, 50}, {68, 17}}</string>
- <reference key="NSSuperview" ref="370006470"/>
- <reference key="NSNextKeyView" ref="1021403255"/>
- <string key="NSReuseIdentifierKey">_NS:1535</string>
- <bool key="NSEnabled">YES</bool>
- <object class="NSTextFieldCell" key="NSCell" id="670594070">
- <int key="NSCellFlags">68157504</int>
- <int key="NSCellFlags2">272630784</int>
- <string key="NSContents">Hostname</string>
- <reference key="NSSupport" ref="640234263"/>
- <string key="NSCellIdentifier">_NS:1535</string>
- <reference key="NSControlView" ref="204654299"/>
- <reference key="NSBackgroundColor" ref="809738508"/>
- <reference key="NSTextColor" ref="567109269"/>
- </object>
- <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
- </object>
- </array>
- <string key="NSFrameSize">{409, 153}</string>
- <reference key="NSSuperview"/>
- <reference key="NSNextKeyView" ref="148390580"/>
- <string key="NSReuseIdentifierKey">_NS:20</string>
- </object>
- <string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
- <string key="NSMaxSize">{10000000000000, 10000000000000}</string>
- <bool key="NSWindowIsRestorable">YES</bool>
- </object>
- <object class="NSCustomObject" id="976324537">
- <string key="NSClassName">AppDelegate</string>
- </object>
- <object class="NSCustomObject" id="755631768">
- <string key="NSClassName">NSFontManager</string>
- </object>
- <object class="NSViewController" id="409855171"/>
- <object class="NSViewController" id="477402609"/>
- <object class="NSUserDefaultsController" id="522483983">
- <bool key="NSSharedInstance">YES</bool>
- </object>
- </array>
- <object class="IBObjectContainer" key="IBDocument.Objects">
- <array class="NSMutableArray" key="connectionRecords">
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">terminate:</string>
- <reference key="source" ref="1050"/>
- <reference key="destination" ref="632727374"/>
- </object>
- <int key="connectionID">449</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">orderFrontStandardAboutPanel:</string>
- <reference key="source" ref="1021"/>
- <reference key="destination" ref="238522557"/>
- </object>
- <int key="connectionID">142</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">delegate</string>
- <reference key="source" ref="1021"/>
- <reference key="destination" ref="976324537"/>
- </object>
- <int key="connectionID">495</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">performMiniaturize:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="1011231497"/>
- </object>
- <int key="connectionID">37</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">arrangeInFront:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="625202149"/>
- </object>
- <int key="connectionID">39</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">performClose:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="776162233"/>
- </object>
- <int key="connectionID">193</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleContinuousSpellChecking:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="948374510"/>
- </object>
- <int key="connectionID">222</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">undo:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="1058277027"/>
- </object>
- <int key="connectionID">223</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">copy:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="860595796"/>
- </object>
- <int key="connectionID">224</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">checkSpelling:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="96193923"/>
- </object>
- <int key="connectionID">225</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">paste:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="29853731"/>
- </object>
- <int key="connectionID">226</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">stopSpeaking:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="680220178"/>
- </object>
- <int key="connectionID">227</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">cut:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="296257095"/>
- </object>
- <int key="connectionID">228</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">showGuessPanel:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="679648819"/>
- </object>
- <int key="connectionID">230</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">redo:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="790794224"/>
- </object>
- <int key="connectionID">231</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">selectAll:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="583158037"/>
- </object>
- <int key="connectionID">232</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">startSpeaking:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="731782645"/>
- </object>
- <int key="connectionID">233</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">delete:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="437104165"/>
- </object>
- <int key="connectionID">235</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">performZoom:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="575023229"/>
- </object>
- <int key="connectionID">240</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">performFindPanelAction:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="447796847"/>
- </object>
- <int key="connectionID">241</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">centerSelectionInVisibleArea:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="88285865"/>
- </object>
- <int key="connectionID">245</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleGrammarChecking:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="967646866"/>
- </object>
- <int key="connectionID">347</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleSmartInsertDelete:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="605118523"/>
- </object>
- <int key="connectionID">355</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleAutomaticQuoteSubstitution:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="197661976"/>
- </object>
- <int key="connectionID">356</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleAutomaticLinkDetection:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="708854459"/>
- </object>
- <int key="connectionID">357</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">hide:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="755159360"/>
- </object>
- <int key="connectionID">367</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">hideOtherApplications:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="342932134"/>
- </object>
- <int key="connectionID">368</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">unhideAllApplications:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="908899353"/>
- </object>
- <int key="connectionID">370</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">raiseBaseline:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="941806246"/>
- </object>
- <int key="connectionID">426</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">lowerBaseline:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="1045724900"/>
- </object>
- <int key="connectionID">427</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">copyFont:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="596732606"/>
- </object>
- <int key="connectionID">428</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">subscript:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="1037576581"/>
- </object>
- <int key="connectionID">429</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">superscript:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="644725453"/>
- </object>
- <int key="connectionID">430</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">tightenKerning:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="677519740"/>
- </object>
- <int key="connectionID">431</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">underline:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="330926929"/>
- </object>
- <int key="connectionID">432</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">orderFrontColorPanel:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="1012600125"/>
- </object>
- <int key="connectionID">433</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">useAllLigatures:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="663508465"/>
- </object>
- <int key="connectionID">434</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">loosenKerning:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="238351151"/>
- </object>
- <int key="connectionID">435</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">pasteFont:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="393423671"/>
- </object>
- <int key="connectionID">436</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">unscript:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="257962622"/>
- </object>
- <int key="connectionID">437</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">useStandardKerning:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="252969304"/>
- </object>
- <int key="connectionID">438</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">useStandardLigatures:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="706297211"/>
- </object>
- <int key="connectionID">439</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">turnOffLigatures:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="568384683"/>
- </object>
- <int key="connectionID">440</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">turnOffKerning:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="766922938"/>
- </object>
- <int key="connectionID">441</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleAutomaticSpellingCorrection:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="795346622"/>
- </object>
- <int key="connectionID">456</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">orderFrontSubstitutionsPanel:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="65139061"/>
- </object>
- <int key="connectionID">458</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleAutomaticDashSubstitution:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="672708820"/>
- </object>
- <int key="connectionID">461</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleAutomaticTextReplacement:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="537092702"/>
- </object>
- <int key="connectionID">463</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">uppercaseWord:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="1060694897"/>
- </object>
- <int key="connectionID">464</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">capitalizeWord:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="56570060"/>
- </object>
- <int key="connectionID">467</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">lowercaseWord:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="879586729"/>
- </object>
- <int key="connectionID">468</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">pasteAsPlainText:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="82994268"/>
- </object>
- <int key="connectionID">486</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">performFindPanelAction:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="326711663"/>
- </object>
- <int key="connectionID">487</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">performFindPanelAction:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="270902937"/>
- </object>
- <int key="connectionID">488</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">performFindPanelAction:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="159080638"/>
- </object>
- <int key="connectionID">489</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">showHelp:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="105068016"/>
- </object>
- <int key="connectionID">493</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">alignCenter:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="630155264"/>
- </object>
- <int key="connectionID">518</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">pasteRuler:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="883618387"/>
- </object>
- <int key="connectionID">519</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">toggleRuler:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="644046920"/>
- </object>
- <int key="connectionID">520</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">alignRight:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="512868991"/>
- </object>
- <int key="connectionID">521</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">copyRuler:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="231811626"/>
- </object>
- <int key="connectionID">522</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">alignJustified:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="945678886"/>
- </object>
- <int key="connectionID">523</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">alignLeft:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="875092757"/>
- </object>
- <int key="connectionID">524</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">makeBaseWritingDirectionNatural:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="551969625"/>
- </object>
- <int key="connectionID">525</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">makeBaseWritingDirectionLeftToRight:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="249532473"/>
- </object>
- <int key="connectionID">526</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">makeBaseWritingDirectionRightToLeft:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="607364498"/>
- </object>
- <int key="connectionID">527</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">makeTextWritingDirectionNatural:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="380031999"/>
- </object>
- <int key="connectionID">528</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">makeTextWritingDirectionLeftToRight:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="825984362"/>
- </object>
- <int key="connectionID">529</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">makeTextWritingDirectionRightToLeft:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="560145579"/>
- </object>
- <int key="connectionID">530</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">performFindPanelAction:</string>
- <reference key="source" ref="1014"/>
- <reference key="destination" ref="738670835"/>
- </object>
- <int key="connectionID">535</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">addFontTrait:</string>
- <reference key="source" ref="755631768"/>
- <reference key="destination" ref="305399458"/>
- </object>
- <int key="connectionID">421</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">addFontTrait:</string>
- <reference key="source" ref="755631768"/>
- <reference key="destination" ref="814362025"/>
- </object>
- <int key="connectionID">422</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">modifyFont:</string>
- <reference key="source" ref="755631768"/>
- <reference key="destination" ref="885547335"/>
- </object>
- <int key="connectionID">423</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">orderFrontFontPanel:</string>
- <reference key="source" ref="755631768"/>
- <reference key="destination" ref="159677712"/>
- </object>
- <int key="connectionID">424</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">modifyFont:</string>
- <reference key="source" ref="755631768"/>
- <reference key="destination" ref="158063935"/>
- </object>
- <int key="connectionID">425</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">window</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="972006081"/>
- </object>
- <int key="connectionID">532</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">cancelButton</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="602401876"/>
- </object>
- <int key="connectionID">636</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">loginButton</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="372944324"/>
- </object>
- <int key="connectionID">637</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">progressView</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="758205814"/>
- </object>
- <int key="connectionID">638</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">_loginButton</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="372944324"/>
- </object>
- <int key="connectionID">641</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">_cancelButton</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="602401876"/>
- </object>
- <int key="connectionID">642</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">_progressView</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="758205814"/>
- </object>
- <int key="connectionID">643</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">_msgListViewController</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="409855171"/>
- </object>
- <int key="connectionID">644</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">_accountWindow</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="389393842"/>
- </object>
- <int key="connectionID">645</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">accountCancel:</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="602401876"/>
- </object>
- <int key="connectionID">652</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">accountLogin:</string>
- <reference key="source" ref="976324537"/>
- <reference key="destination" ref="372944324"/>
- </object>
- <int key="connectionID">653</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">dataSource</string>
- <reference key="source" ref="818403152"/>
- <reference key="destination" ref="409855171"/>
- </object>
- <int key="connectionID">597</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">delegate</string>
- <reference key="source" ref="818403152"/>
- <reference key="destination" ref="409855171"/>
- </object>
- <int key="connectionID">598</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">hidden: self.loading</string>
- <reference key="source" ref="220665056"/>
- <reference key="destination" ref="409855171"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="220665056"/>
- <reference key="NSDestination" ref="409855171"/>
- <string key="NSLabel">hidden: self.loading</string>
- <string key="NSBinding">hidden</string>
- <string key="NSKeyPath">self.loading</string>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">694</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">_tableView</string>
- <reference key="source" ref="409855171"/>
- <reference key="destination" ref="818403152"/>
- </object>
- <int key="connectionID">596</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">_msgViewController</string>
- <reference key="source" ref="409855171"/>
- <reference key="destination" ref="477402609"/>
- </object>
- <int key="connectionID">608</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">_messageView</string>
- <reference key="source" ref="477402609"/>
- <reference key="destination" ref="220665056"/>
- </object>
- <int key="connectionID">607</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBActionConnection" key="connection">
- <string key="label">makeKeyAndOrderFront:</string>
- <reference key="source" ref="389393842"/>
- <reference key="destination" ref="354256217"/>
- </object>
- <int key="connectionID">622</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">initialFirstResponder</string>
- <reference key="source" ref="389393842"/>
- <reference key="destination" ref="369746882"/>
- </object>
- <int key="connectionID">654</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">value: self.login</string>
- <reference key="source" ref="369746882"/>
- <reference key="destination" ref="976324537"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="369746882"/>
- <reference key="NSDestination" ref="976324537"/>
- <string key="NSLabel">value: self.login</string>
- <string key="NSBinding">value</string>
- <string key="NSKeyPath">self.login</string>
- <object class="NSDictionary" key="NSOptions">
- <string key="NS.key.0">NSContinuouslyUpdatesValue</string>
- <boolean value="YES" key="NS.object.0"/>
- </object>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">680</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">value: self.password</string>
- <reference key="source" ref="217026742"/>
- <reference key="destination" ref="976324537"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="217026742"/>
- <reference key="NSDestination" ref="976324537"/>
- <string key="NSLabel">value: self.password</string>
- <string key="NSBinding">value</string>
- <string key="NSKeyPath">self.password</string>
- <object class="NSDictionary" key="NSOptions">
- <string key="NS.key.0">NSContinuouslyUpdatesValue</string>
- <boolean value="YES" key="NS.object.0"/>
- </object>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">679</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">enabled: self.loginEnabled</string>
- <reference key="source" ref="372944324"/>
- <reference key="destination" ref="976324537"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="372944324"/>
- <reference key="NSDestination" ref="976324537"/>
- <string key="NSLabel">enabled: self.loginEnabled</string>
- <string key="NSBinding">enabled</string>
- <string key="NSKeyPath">self.loginEnabled</string>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">678</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">enabled: self.loggingIn</string>
- <reference key="source" ref="602401876"/>
- <reference key="destination" ref="976324537"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="602401876"/>
- <reference key="NSDestination" ref="976324537"/>
- <string key="NSLabel">enabled: self.loggingIn</string>
- <string key="NSBinding">enabled</string>
- <string key="NSKeyPath">self.loggingIn</string>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">670</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">hidden: self.loggingIn</string>
- <reference key="source" ref="758205814"/>
- <reference key="destination" ref="976324537"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="758205814"/>
- <reference key="NSDestination" ref="976324537"/>
- <string key="NSLabel">hidden: self.loggingIn</string>
- <string key="NSBinding">hidden</string>
- <string key="NSKeyPath">self.loggingIn</string>
- <object class="NSDictionary" key="NSOptions">
- <string key="NS.key.0">NSValueTransformerName</string>
- <string key="NS.object.0">NSNegateBoolean</string>
- </object>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">672</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">animate: self.loggingIn</string>
- <reference key="source" ref="758205814"/>
- <reference key="destination" ref="976324537"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="758205814"/>
- <reference key="NSDestination" ref="976324537"/>
- <string key="NSLabel">animate: self.loggingIn</string>
- <string key="NSBinding">animate</string>
- <string key="NSKeyPath">self.loggingIn</string>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">674</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">value: values.FetchFullMessageEnabled</string>
- <reference key="source" ref="521030078"/>
- <reference key="destination" ref="522483983"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="521030078"/>
- <reference key="NSDestination" ref="522483983"/>
- <string key="NSLabel">value: values.FetchFullMessageEnabled</string>
- <string key="NSBinding">value</string>
- <string key="NSKeyPath">values.FetchFullMessageEnabled</string>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">648</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">value: self.hostname</string>
- <reference key="source" ref="1021403255"/>
- <reference key="destination" ref="976324537"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="1021403255"/>
- <reference key="NSDestination" ref="976324537"/>
- <string key="NSLabel">value: self.hostname</string>
- <string key="NSBinding">value</string>
- <string key="NSKeyPath">self.hostname</string>
- <object class="NSDictionary" key="NSOptions">
- <string key="NS.key.0">NSContinuouslyUpdatesValue</string>
- <boolean value="YES" key="NS.object.0"/>
- </object>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">686</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">hidden: self.loading</string>
- <reference key="source" ref="677395401"/>
- <reference key="destination" ref="409855171"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="677395401"/>
- <reference key="NSDestination" ref="409855171"/>
- <string key="NSLabel">hidden: self.loading</string>
- <string key="NSBinding">hidden</string>
- <string key="NSKeyPath">self.loading</string>
- <object class="NSDictionary" key="NSOptions">
- <string key="NS.key.0">NSValueTransformerName</string>
- <string key="NS.object.0">NSNegateBoolean</string>
- </object>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">689</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">animate: self.loading</string>
- <reference key="source" ref="677395401"/>
- <reference key="destination" ref="409855171"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="677395401"/>
- <reference key="NSDestination" ref="409855171"/>
- <string key="NSLabel">animate: self.loading</string>
- <string key="NSBinding">animate</string>
- <string key="NSKeyPath">self.loading</string>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">691</int>
- </object>
- </array>
- <object class="IBMutableOrderedSet" key="objectRecords">
- <array key="orderedObjects">
- <object class="IBObjectRecord">
- <int key="objectID">0</int>
- <array key="object" id="0"/>
- <reference key="children" ref="1048"/>
- <nil key="parent"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">-2</int>
- <reference key="object" ref="1021"/>
- <reference key="parent" ref="0"/>
- <string key="objectName">File's Owner</string>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">-1</int>
- <reference key="object" ref="1014"/>
- <reference key="parent" ref="0"/>
- <string key="objectName">First Responder</string>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">-3</int>
- <reference key="object" ref="1050"/>
- <reference key="parent" ref="0"/>
- <string key="objectName">Application</string>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">29</int>
- <reference key="object" ref="649796088"/>
- <array class="NSMutableArray" key="children">
- <reference ref="713487014"/>
- <reference ref="694149608"/>
- <reference ref="952259628"/>
- <reference ref="379814623"/>
- <reference ref="302598603"/>
- <reference ref="448692316"/>
- </array>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">19</int>
- <reference key="object" ref="713487014"/>
- <array class="NSMutableArray" key="children">
- <reference ref="835318025"/>
- </array>
- <reference key="parent" ref="649796088"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">56</int>
- <reference key="object" ref="694149608"/>
- <array class="NSMutableArray" key="children">
- <reference ref="110575045"/>
- </array>
- <reference key="parent" ref="649796088"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">217</int>
- <reference key="object" ref="952259628"/>
- <array class="NSMutableArray" key="children">
- <reference ref="789758025"/>
- </array>
- <reference key="parent" ref="649796088"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">83</int>
- <reference key="object" ref="379814623"/>
- <array class="NSMutableArray" key="children">
- <reference ref="720053764"/>
- </array>
- <reference key="parent" ref="649796088"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">81</int>
- <reference key="object" ref="720053764"/>
- <array class="NSMutableArray" key="children">
- <reference ref="776162233"/>
- <reference ref="425164168"/>
- <reference ref="354256217"/>
- <reference ref="521030078"/>
- </array>
- <reference key="parent" ref="379814623"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">73</int>
- <reference key="object" ref="776162233"/>
- <reference key="parent" ref="720053764"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">79</int>
- <reference key="object" ref="425164168"/>
- <reference key="parent" ref="720053764"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">205</int>
- <reference key="object" ref="789758025"/>
- <array class="NSMutableArray" key="children">
- <reference ref="437104165"/>
- <reference ref="583158037"/>
- <reference ref="1058277027"/>
- <reference ref="212016141"/>
- <reference ref="296257095"/>
- <reference ref="29853731"/>
- <reference ref="860595796"/>
- <reference ref="1040322652"/>
- <reference ref="790794224"/>
- <reference ref="892235320"/>
- <reference ref="972420730"/>
- <reference ref="676164635"/>
- <reference ref="507821607"/>
- <reference ref="288088188"/>
- <reference ref="82994268"/>
- </array>
- <reference key="parent" ref="952259628"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">202</int>
- <reference key="object" ref="437104165"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">198</int>
- <reference key="object" ref="583158037"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">207</int>
- <reference key="object" ref="1058277027"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">214</int>
- <reference key="object" ref="212016141"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">199</int>
- <reference key="object" ref="296257095"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">203</int>
- <reference key="object" ref="29853731"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">197</int>
- <reference key="object" ref="860595796"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">206</int>
- <reference key="object" ref="1040322652"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">215</int>
- <reference key="object" ref="790794224"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">218</int>
- <reference key="object" ref="892235320"/>
- <array class="NSMutableArray" key="children">
- <reference ref="963351320"/>
- </array>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">216</int>
- <reference key="object" ref="972420730"/>
- <array class="NSMutableArray" key="children">
- <reference ref="769623530"/>
- </array>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">200</int>
- <reference key="object" ref="769623530"/>
- <array class="NSMutableArray" key="children">
- <reference ref="948374510"/>
- <reference ref="96193923"/>
- <reference ref="679648819"/>
- <reference ref="967646866"/>
- <reference ref="859480356"/>
- <reference ref="795346622"/>
- </array>
- <reference key="parent" ref="972420730"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">219</int>
- <reference key="object" ref="948374510"/>
- <reference key="parent" ref="769623530"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">201</int>
- <reference key="object" ref="96193923"/>
- <reference key="parent" ref="769623530"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">204</int>
- <reference key="object" ref="679648819"/>
- <reference key="parent" ref="769623530"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">220</int>
- <reference key="object" ref="963351320"/>
- <array class="NSMutableArray" key="children">
- <reference ref="270902937"/>
- <reference ref="88285865"/>
- <reference ref="159080638"/>
- <reference ref="326711663"/>
- <reference ref="447796847"/>
- <reference ref="738670835"/>
- </array>
- <reference key="parent" ref="892235320"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">213</int>
- <reference key="object" ref="270902937"/>
- <reference key="parent" ref="963351320"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">210</int>
- <reference key="object" ref="88285865"/>
- <reference key="parent" ref="963351320"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">221</int>
- <reference key="object" ref="159080638"/>
- <reference key="parent" ref="963351320"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">208</int>
- <reference key="object" ref="326711663"/>
- <reference key="parent" ref="963351320"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">209</int>
- <reference key="object" ref="447796847"/>
- <reference key="parent" ref="963351320"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">57</int>
- <reference key="object" ref="110575045"/>
- <array class="NSMutableArray" key="children">
- <reference ref="238522557"/>
- <reference ref="755159360"/>
- <reference ref="908899353"/>
- <reference ref="632727374"/>
- <reference ref="646227648"/>
- <reference ref="609285721"/>
- <reference ref="481834944"/>
- <reference ref="304266470"/>
- <reference ref="1046388886"/>
- <reference ref="1056857174"/>
- <reference ref="342932134"/>
- </array>
- <reference key="parent" ref="694149608"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">58</int>
- <reference key="object" ref="238522557"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">134</int>
- <reference key="object" ref="755159360"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">150</int>
- <reference key="object" ref="908899353"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">136</int>
- <reference key="object" ref="632727374"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">144</int>
- <reference key="object" ref="646227648"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">129</int>
- <reference key="object" ref="609285721"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">143</int>
- <reference key="object" ref="481834944"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">236</int>
- <reference key="object" ref="304266470"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">131</int>
- <reference key="object" ref="1046388886"/>
- <array class="NSMutableArray" key="children">
- <reference ref="752062318"/>
- </array>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">149</int>
- <reference key="object" ref="1056857174"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">145</int>
- <reference key="object" ref="342932134"/>
- <reference key="parent" ref="110575045"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">130</int>
- <reference key="object" ref="752062318"/>
- <reference key="parent" ref="1046388886"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">24</int>
- <reference key="object" ref="835318025"/>
- <array class="NSMutableArray" key="children">
- <reference ref="299356726"/>
- <reference ref="625202149"/>
- <reference ref="575023229"/>
- <reference ref="1011231497"/>
- </array>
- <reference key="parent" ref="713487014"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">92</int>
- <reference key="object" ref="299356726"/>
- <reference key="parent" ref="835318025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">5</int>
- <reference key="object" ref="625202149"/>
- <reference key="parent" ref="835318025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">239</int>
- <reference key="object" ref="575023229"/>
- <reference key="parent" ref="835318025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">23</int>
- <reference key="object" ref="1011231497"/>
- <reference key="parent" ref="835318025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">211</int>
- <reference key="object" ref="676164635"/>
- <array class="NSMutableArray" key="children">
- <reference ref="785027613"/>
- </array>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">212</int>
- <reference key="object" ref="785027613"/>
- <array class="NSMutableArray" key="children">
- <reference ref="680220178"/>
- <reference ref="731782645"/>
- </array>
- <reference key="parent" ref="676164635"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">195</int>
- <reference key="object" ref="680220178"/>
- <reference key="parent" ref="785027613"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">196</int>
- <reference key="object" ref="731782645"/>
- <reference key="parent" ref="785027613"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">346</int>
- <reference key="object" ref="967646866"/>
- <reference key="parent" ref="769623530"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">348</int>
- <reference key="object" ref="507821607"/>
- <array class="NSMutableArray" key="children">
- <reference ref="698887838"/>
- </array>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">349</int>
- <reference key="object" ref="698887838"/>
- <array class="NSMutableArray" key="children">
- <reference ref="605118523"/>
- <reference ref="197661976"/>
- <reference ref="708854459"/>
- <reference ref="65139061"/>
- <reference ref="19036812"/>
- <reference ref="672708820"/>
- <reference ref="537092702"/>
- </array>
- <reference key="parent" ref="507821607"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">350</int>
- <reference key="object" ref="605118523"/>
- <reference key="parent" ref="698887838"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">351</int>
- <reference key="object" ref="197661976"/>
- <reference key="parent" ref="698887838"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">354</int>
- <reference key="object" ref="708854459"/>
- <reference key="parent" ref="698887838"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">371</int>
- <reference key="object" ref="972006081"/>
- <array class="NSMutableArray" key="children">
- <reference ref="439893737"/>
- </array>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">372</int>
- <reference key="object" ref="439893737"/>
- <array class="NSMutableArray" key="children">
- <reference ref="665197104"/>
- <reference ref="220665056"/>
- <reference ref="677395401"/>
- </array>
- <reference key="parent" ref="972006081"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">375</int>
- <reference key="object" ref="302598603"/>
- <array class="NSMutableArray" key="children">
- <reference ref="941447902"/>
- </array>
- <reference key="parent" ref="649796088"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">376</int>
- <reference key="object" ref="941447902"/>
- <array class="NSMutableArray" key="children">
- <reference ref="792887677"/>
- <reference ref="215659978"/>
- </array>
- <reference key="parent" ref="302598603"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">377</int>
- <reference key="object" ref="792887677"/>
- <array class="NSMutableArray" key="children">
- <reference ref="786677654"/>
- </array>
- <reference key="parent" ref="941447902"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">388</int>
- <reference key="object" ref="786677654"/>
- <array class="NSMutableArray" key="children">
- <reference ref="159677712"/>
- <reference ref="305399458"/>
- <reference ref="814362025"/>
- <reference ref="330926929"/>
- <reference ref="533507878"/>
- <reference ref="158063935"/>
- <reference ref="885547335"/>
- <reference ref="901062459"/>
- <reference ref="767671776"/>
- <reference ref="691570813"/>
- <reference ref="769124883"/>
- <reference ref="739652853"/>
- <reference ref="1012600125"/>
- <reference ref="214559597"/>
- <reference ref="596732606"/>
- <reference ref="393423671"/>
- </array>
- <reference key="parent" ref="792887677"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">389</int>
- <reference key="object" ref="159677712"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">390</int>
- <reference key="object" ref="305399458"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">391</int>
- <reference key="object" ref="814362025"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">392</int>
- <reference key="object" ref="330926929"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">393</int>
- <reference key="object" ref="533507878"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">394</int>
- <reference key="object" ref="158063935"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">395</int>
- <reference key="object" ref="885547335"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">396</int>
- <reference key="object" ref="901062459"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">397</int>
- <reference key="object" ref="767671776"/>
- <array class="NSMutableArray" key="children">
- <reference ref="175441468"/>
- </array>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">398</int>
- <reference key="object" ref="691570813"/>
- <array class="NSMutableArray" key="children">
- <reference ref="1058217995"/>
- </array>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">399</int>
- <reference key="object" ref="769124883"/>
- <array class="NSMutableArray" key="children">
- <reference ref="18263474"/>
- </array>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">400</int>
- <reference key="object" ref="739652853"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">401</int>
- <reference key="object" ref="1012600125"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">402</int>
- <reference key="object" ref="214559597"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">403</int>
- <reference key="object" ref="596732606"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">404</int>
- <reference key="object" ref="393423671"/>
- <reference key="parent" ref="786677654"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">405</int>
- <reference key="object" ref="18263474"/>
- <array class="NSMutableArray" key="children">
- <reference ref="257962622"/>
- <reference ref="644725453"/>
- <reference ref="1037576581"/>
- <reference ref="941806246"/>
- <reference ref="1045724900"/>
- </array>
- <reference key="parent" ref="769124883"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">406</int>
- <reference key="object" ref="257962622"/>
- <reference key="parent" ref="18263474"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">407</int>
- <reference key="object" ref="644725453"/>
- <reference key="parent" ref="18263474"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">408</int>
- <reference key="object" ref="1037576581"/>
- <reference key="parent" ref="18263474"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">409</int>
- <reference key="object" ref="941806246"/>
- <reference key="parent" ref="18263474"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">410</int>
- <reference key="object" ref="1045724900"/>
- <reference key="parent" ref="18263474"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">411</int>
- <reference key="object" ref="1058217995"/>
- <array class="NSMutableArray" key="children">
- <reference ref="706297211"/>
- <reference ref="568384683"/>
- <reference ref="663508465"/>
- </array>
- <reference key="parent" ref="691570813"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">412</int>
- <reference key="object" ref="706297211"/>
- <reference key="parent" ref="1058217995"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">413</int>
- <reference key="object" ref="568384683"/>
- <reference key="parent" ref="1058217995"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">414</int>
- <reference key="object" ref="663508465"/>
- <reference key="parent" ref="1058217995"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">415</int>
- <reference key="object" ref="175441468"/>
- <array class="NSMutableArray" key="children">
- <reference ref="252969304"/>
- <reference ref="766922938"/>
- <reference ref="677519740"/>
- <reference ref="238351151"/>
- </array>
- <reference key="parent" ref="767671776"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">416</int>
- <reference key="object" ref="252969304"/>
- <reference key="parent" ref="175441468"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">417</int>
- <reference key="object" ref="766922938"/>
- <reference key="parent" ref="175441468"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">418</int>
- <reference key="object" ref="677519740"/>
- <reference key="parent" ref="175441468"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">419</int>
- <reference key="object" ref="238351151"/>
- <reference key="parent" ref="175441468"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">420</int>
- <reference key="object" ref="755631768"/>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">450</int>
- <reference key="object" ref="288088188"/>
- <array class="NSMutableArray" key="children">
- <reference ref="579392910"/>
- </array>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">451</int>
- <reference key="object" ref="579392910"/>
- <array class="NSMutableArray" key="children">
- <reference ref="1060694897"/>
- <reference ref="879586729"/>
- <reference ref="56570060"/>
- </array>
- <reference key="parent" ref="288088188"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">452</int>
- <reference key="object" ref="1060694897"/>
- <reference key="parent" ref="579392910"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">453</int>
- <reference key="object" ref="859480356"/>
- <reference key="parent" ref="769623530"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">454</int>
- <reference key="object" ref="795346622"/>
- <reference key="parent" ref="769623530"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">457</int>
- <reference key="object" ref="65139061"/>
- <reference key="parent" ref="698887838"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">459</int>
- <reference key="object" ref="19036812"/>
- <reference key="parent" ref="698887838"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">460</int>
- <reference key="object" ref="672708820"/>
- <reference key="parent" ref="698887838"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">462</int>
- <reference key="object" ref="537092702"/>
- <reference key="parent" ref="698887838"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">465</int>
- <reference key="object" ref="879586729"/>
- <reference key="parent" ref="579392910"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">466</int>
- <reference key="object" ref="56570060"/>
- <reference key="parent" ref="579392910"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">485</int>
- <reference key="object" ref="82994268"/>
- <reference key="parent" ref="789758025"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">490</int>
- <reference key="object" ref="448692316"/>
- <array class="NSMutableArray" key="children">
- <reference ref="992780483"/>
- </array>
- <reference key="parent" ref="649796088"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">491</int>
- <reference key="object" ref="992780483"/>
- <array class="NSMutableArray" key="children">
- <reference ref="105068016"/>
- </array>
- <reference key="parent" ref="448692316"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">492</int>
- <reference key="object" ref="105068016"/>
- <reference key="parent" ref="992780483"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">494</int>
- <reference key="object" ref="976324537"/>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">496</int>
- <reference key="object" ref="215659978"/>
- <array class="NSMutableArray" key="children">
- <reference ref="446991534"/>
- </array>
- <reference key="parent" ref="941447902"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">497</int>
- <reference key="object" ref="446991534"/>
- <array class="NSMutableArray" key="children">
- <reference ref="875092757"/>
- <reference ref="630155264"/>
- <reference ref="945678886"/>
- <reference ref="512868991"/>
- <reference ref="163117631"/>
- <reference ref="31516759"/>
- <reference ref="908105787"/>
- <reference ref="644046920"/>
- <reference ref="231811626"/>
- <reference ref="883618387"/>
- </array>
- <reference key="parent" ref="215659978"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">498</int>
- <reference key="object" ref="875092757"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">499</int>
- <reference key="object" ref="630155264"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">500</int>
- <reference key="object" ref="945678886"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">501</int>
- <reference key="object" ref="512868991"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">502</int>
- <reference key="object" ref="163117631"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">503</int>
- <reference key="object" ref="31516759"/>
- <array class="NSMutableArray" key="children">
- <reference ref="956096989"/>
- </array>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">504</int>
- <reference key="object" ref="908105787"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">505</int>
- <reference key="object" ref="644046920"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">506</int>
- <reference key="object" ref="231811626"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">507</int>
- <reference key="object" ref="883618387"/>
- <reference key="parent" ref="446991534"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">508</int>
- <reference key="object" ref="956096989"/>
- <array class="NSMutableArray" key="children">
- <reference ref="257099033"/>
- <reference ref="551969625"/>
- <reference ref="249532473"/>
- <reference ref="607364498"/>
- <reference ref="508151438"/>
- <reference ref="981751889"/>
- <reference ref="380031999"/>
- <reference ref="825984362"/>
- <reference ref="560145579"/>
- </array>
- <reference key="parent" ref="31516759"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">509</int>
- <reference key="object" ref="257099033"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">510</int>
- <reference key="object" ref="551969625"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">511</int>
- <reference key="object" ref="249532473"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">512</int>
- <reference key="object" ref="607364498"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">513</int>
- <reference key="object" ref="508151438"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">514</int>
- <reference key="object" ref="981751889"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">515</int>
- <reference key="object" ref="380031999"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">516</int>
- <reference key="object" ref="825984362"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">517</int>
- <reference key="object" ref="560145579"/>
- <reference key="parent" ref="956096989"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">534</int>
- <reference key="object" ref="738670835"/>
- <reference key="parent" ref="963351320"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">536</int>
- <reference key="object" ref="665197104"/>
- <array class="NSMutableArray" key="children">
- <reference ref="818403152"/>
- <reference ref="1027823325"/>
- <reference ref="8853202"/>
- <reference ref="849854472"/>
- </array>
- <reference key="parent" ref="439893737"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">537</int>
- <reference key="object" ref="818403152"/>
- <array class="NSMutableArray" key="children">
- <reference ref="631488934"/>
- </array>
- <reference key="parent" ref="665197104"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">538</int>
- <reference key="object" ref="1027823325"/>
- <reference key="parent" ref="665197104"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">539</int>
- <reference key="object" ref="8853202"/>
- <reference key="parent" ref="665197104"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">540</int>
- <reference key="object" ref="849854472"/>
- <reference key="parent" ref="665197104"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">541</int>
- <reference key="object" ref="631488934"/>
- <array class="NSMutableArray" key="children">
- <reference ref="665052716"/>
- </array>
- <reference key="parent" ref="818403152"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">544</int>
- <reference key="object" ref="665052716"/>
- <reference key="parent" ref="631488934"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">573</int>
- <reference key="object" ref="220665056"/>
- <array class="NSMutableArray" key="children"/>
- <reference key="parent" ref="439893737"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">594</int>
- <reference key="object" ref="409855171"/>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">595</int>
- <reference key="object" ref="477402609"/>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">609</int>
- <reference key="object" ref="354256217"/>
- <reference key="parent" ref="720053764"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">610</int>
- <reference key="object" ref="389393842"/>
- <array class="NSMutableArray" key="children">
- <reference ref="370006470"/>
- </array>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">611</int>
- <reference key="object" ref="370006470"/>
- <array class="NSMutableArray" key="children">
- <reference ref="369746882"/>
- <reference ref="148390580"/>
- <reference ref="217026742"/>
- <reference ref="47969306"/>
- <reference ref="1021403255"/>
- <reference ref="204654299"/>
- <reference ref="372944324"/>
- <reference ref="602401876"/>
- <reference ref="758205814"/>
- </array>
- <reference key="parent" ref="389393842"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">612</int>
- <reference key="object" ref="369746882"/>
- <array class="NSMutableArray" key="children">
- <reference ref="163183536"/>
- </array>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">613</int>
- <reference key="object" ref="163183536"/>
- <reference key="parent" ref="369746882"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">616</int>
- <reference key="object" ref="148390580"/>
- <array class="NSMutableArray" key="children">
- <reference ref="746004588"/>
- </array>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">617</int>
- <reference key="object" ref="746004588"/>
- <reference key="parent" ref="148390580"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">618</int>
- <reference key="object" ref="47969306"/>
- <array class="NSMutableArray" key="children">
- <reference ref="960551038"/>
- </array>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">619</int>
- <reference key="object" ref="960551038"/>
- <reference key="parent" ref="47969306"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">620</int>
- <reference key="object" ref="217026742"/>
- <array class="NSMutableArray" key="children">
- <reference ref="338490228"/>
- </array>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">621</int>
- <reference key="object" ref="338490228"/>
- <reference key="parent" ref="217026742"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">623</int>
- <reference key="object" ref="522483983"/>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">627</int>
- <reference key="object" ref="372944324"/>
- <array class="NSMutableArray" key="children">
- <reference ref="496976666"/>
- </array>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">628</int>
- <reference key="object" ref="496976666"/>
- <reference key="parent" ref="372944324"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">629</int>
- <reference key="object" ref="602401876"/>
- <array class="NSMutableArray" key="children">
- <reference ref="867739186"/>
- </array>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">630</int>
- <reference key="object" ref="867739186"/>
- <reference key="parent" ref="602401876"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">631</int>
- <reference key="object" ref="758205814"/>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">646</int>
- <reference key="object" ref="521030078"/>
- <reference key="parent" ref="720053764"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">681</int>
- <reference key="object" ref="1021403255"/>
- <array class="NSMutableArray" key="children">
- <reference ref="828733144"/>
- </array>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">682</int>
- <reference key="object" ref="828733144"/>
- <reference key="parent" ref="1021403255"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">684</int>
- <reference key="object" ref="204654299"/>
- <array class="NSMutableArray" key="children">
- <reference ref="670594070"/>
- </array>
- <reference key="parent" ref="370006470"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">685</int>
- <reference key="object" ref="670594070"/>
- <reference key="parent" ref="204654299"/>
- </object>
- <object class="IBObjectRecord">
- <int key="objectID">687</int>
- <reference key="object" ref="677395401"/>
- <reference key="parent" ref="439893737"/>
- </object>
- </array>
- </object>
- <dictionary class="NSMutableDictionary" key="flattenedProperties">
- <string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="129.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="130.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="131.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="134.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="136.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="143.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="144.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="145.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="149.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="150.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="19.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="195.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="196.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="197.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="198.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="199.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="200.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="201.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="202.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="203.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="204.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="205.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="206.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="207.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="208.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="209.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="210.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="211.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="212.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="213.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="214.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="215.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="216.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="217.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="218.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="219.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="220.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="221.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="23.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="236.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="239.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="24.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="346.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="348.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="349.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="350.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="351.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="354.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <boolean value="NO" key="371.IBNSWindowAutoPositionCentersHorizontal"/>
- <boolean value="NO" key="371.IBNSWindowAutoPositionCentersVertical"/>
- <string key="371.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="371.IBWindowTemplateEditedContentRect">{{380, 496}, {480, 360}}</string>
- <integer value="1" key="371.NSWindowTemplate.visibleAtLaunch"/>
- <string key="372.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="375.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="376.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="377.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="388.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="389.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="390.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="391.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="392.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="393.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="394.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="395.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="396.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="397.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="398.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="399.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="400.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="401.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="402.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="403.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="404.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="405.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="406.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="407.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="408.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="409.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="410.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="411.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="412.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="413.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="414.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="415.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="416.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="417.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="418.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="419.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="420.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="450.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="451.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="452.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="453.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="454.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="457.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="459.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="460.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="462.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="465.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="466.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="485.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="490.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="491.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="492.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="494.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="496.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="497.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="498.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="499.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="500.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="501.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="502.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="503.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="504.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="505.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="506.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="507.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="508.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="509.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="510.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="511.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="512.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="513.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="514.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="515.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="516.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="517.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="534.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="536.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="537.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="538.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="539.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="540.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="541.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="544.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="56.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="57.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="573.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="58.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="594.CustomClassName">MCTMsgListViewController</string>
- <string key="594.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="595.CustomClassName">MCTMsgViewController</string>
- <string key="595.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="609.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <boolean value="YES" key="610.IBNSWindowAutoPositionCentersHorizontal"/>
- <boolean value="YES" key="610.IBNSWindowAutoPositionCentersVertical"/>
- <string key="610.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <boolean value="NO" key="610.NSWindowTemplate.visibleAtLaunch"/>
- <string key="611.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="612.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="613.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="616.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="617.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="618.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="619.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="620.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="621.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="623.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="627.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="628.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="629.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="630.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="631.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="646.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="681.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="682.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="684.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="685.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="687.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="73.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="79.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="81.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="83.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string key="92.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
- </dictionary>
- <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
- <nil key="activeLocalization"/>
- <dictionary class="NSMutableDictionary" key="localizations"/>
- <nil key="sourceID"/>
- <int key="maxID">694</int>
- </object>
- <object class="IBClassDescriber" key="IBDocument.Classes">
- <array class="NSMutableArray" key="referencedPartialClassDescriptions">
- <object class="IBPartialClassDescription">
- <string key="className">AppDelegate</string>
- <string key="superclassName">UIResponder</string>
- <object class="IBClassDescriptionSource" key="sourceIdentifier">
- <string key="majorKey">IBProjectSource</string>
- <string key="minorKey">./Classes/AppDelegate.h</string>
- </object>
- </object>
- <object class="IBPartialClassDescription">
- <string key="className">MCOMessageView</string>
- <string key="superclassName">NSView</string>
- <object class="IBClassDescriptionSource" key="sourceIdentifier">
- <string key="majorKey">IBProjectSource</string>
- <string key="minorKey">./Classes/MCOMessageView.h</string>
- </object>
- </object>
- <object class="IBPartialClassDescription">
- <string key="className">MCTMsgListViewController</string>
- <string key="superclassName">NSViewController</string>
- <dictionary class="NSMutableDictionary" key="outlets">
- <string key="_msgViewController">MCTMsgViewController</string>
- <string key="_tableView">NSTableView</string>
- </dictionary>
- <dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
- <object class="IBToOneOutletInfo" key="_msgViewController">
- <string key="name">_msgViewController</string>
- <string key="candidateClassName">MCTMsgViewController</string>
- </object>
- <object class="IBToOneOutletInfo" key="_tableView">
- <string key="name">_tableView</string>
- <string key="candidateClassName">NSTableView</string>
- </object>
- </dictionary>
- <object class="IBClassDescriptionSource" key="sourceIdentifier">
- <string key="majorKey">IBProjectSource</string>
- <string key="minorKey">./Classes/MCTMsgListViewController.h</string>
- </object>
- </object>
- <object class="IBPartialClassDescription">
- <string key="className">MCTMsgViewController</string>
- <string key="superclassName">NSViewController</string>
- <object class="NSMutableDictionary" key="outlets">
- <string key="NS.key.0">_messageView</string>
- <string key="NS.object.0">MCOMessageView</string>
- </object>
- <object class="NSMutableDictionary" key="toOneOutletInfosByName">
- <string key="NS.key.0">_messageView</string>
- <object class="IBToOneOutletInfo" key="NS.object.0">
- <string key="name">_messageView</string>
- <string key="candidateClassName">MCOMessageView</string>
- </object>
- </object>
- <object class="IBClassDescriptionSource" key="sourceIdentifier">
- <string key="majorKey">IBProjectSource</string>
- <string key="minorKey">./Classes/MCTMsgViewController.h</string>
- </object>
- </object>
- </array>
- </object>
- <int key="IBDocument.localizationMode">0</int>
- <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
- <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
- <int key="IBDocument.defaultPropertyAccessControl">3</int>
- <dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
- <string key="NSMenuCheckmark">{11, 11}</string>
- <string key="NSMenuMixedState">{10, 3}</string>
- </dictionary>
- </data>
-</archive>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4457.6" systemVersion="12E55" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
+ <dependencies>
+ <deployment defaultVersion="1080" identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4457.6"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+ <connections>
+ <action selector="orderFrontStandardAboutPanel:" destination="58" id="142"/>
+ <outlet property="delegate" destination="494" id="495"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder">
+ <connections>
+ <action selector="alignCenter:" destination="499" id="518"/>
+ <action selector="alignJustified:" destination="500" id="523"/>
+ <action selector="alignLeft:" destination="498" id="524"/>
+ <action selector="alignRight:" destination="501" id="521"/>
+ <action selector="arrangeInFront:" destination="5" id="39"/>
+ <action selector="capitalizeWord:" destination="466" id="467"/>
+ <action selector="centerSelectionInVisibleArea:" destination="210" id="245"/>
+ <action selector="checkSpelling:" destination="201" id="225"/>
+ <action selector="copy:" destination="197" id="224"/>
+ <action selector="copyFont:" destination="403" id="428"/>
+ <action selector="copyRuler:" destination="506" id="522"/>
+ <action selector="cut:" destination="199" id="228"/>
+ <action selector="delete:" destination="202" id="235"/>
+ <action selector="hide:" destination="134" id="367"/>
+ <action selector="hideOtherApplications:" destination="145" id="368"/>
+ <action selector="loosenKerning:" destination="419" id="435"/>
+ <action selector="lowerBaseline:" destination="410" id="427"/>
+ <action selector="lowercaseWord:" destination="465" id="468"/>
+ <action selector="makeBaseWritingDirectionLeftToRight:" destination="511" id="526"/>
+ <action selector="makeBaseWritingDirectionNatural:" destination="510" id="525"/>
+ <action selector="makeBaseWritingDirectionRightToLeft:" destination="512" id="527"/>
+ <action selector="makeTextWritingDirectionLeftToRight:" destination="516" id="529"/>
+ <action selector="makeTextWritingDirectionNatural:" destination="515" id="528"/>
+ <action selector="makeTextWritingDirectionRightToLeft:" destination="517" id="530"/>
+ <action selector="orderFrontColorPanel:" destination="401" id="433"/>
+ <action selector="orderFrontSubstitutionsPanel:" destination="457" id="458"/>
+ <action selector="paste:" destination="203" id="226"/>
+ <action selector="pasteAsPlainText:" destination="485" id="486"/>
+ <action selector="pasteFont:" destination="404" id="436"/>
+ <action selector="pasteRuler:" destination="507" id="519"/>
+ <action selector="performClose:" destination="73" id="193"/>
+ <action selector="performFindPanelAction:" destination="209" id="241"/>
+ <action selector="performFindPanelAction:" destination="208" id="487"/>
+ <action selector="performFindPanelAction:" destination="213" id="488"/>
+ <action selector="performFindPanelAction:" destination="221" id="489"/>
+ <action selector="performFindPanelAction:" destination="534" id="535"/>
+ <action selector="performMiniaturize:" destination="23" id="37"/>
+ <action selector="performZoom:" destination="239" id="240"/>
+ <action selector="raiseBaseline:" destination="409" id="426"/>
+ <action selector="redo:" destination="215" id="231"/>
+ <action selector="selectAll:" destination="198" id="232"/>
+ <action selector="showGuessPanel:" destination="204" id="230"/>
+ <action selector="showHelp:" destination="492" id="493"/>
+ <action selector="startSpeaking:" destination="196" id="233"/>
+ <action selector="stopSpeaking:" destination="195" id="227"/>
+ <action selector="subscript:" destination="408" id="429"/>
+ <action selector="superscript:" destination="407" id="430"/>
+ <action selector="tightenKerning:" destination="418" id="431"/>
+ <action selector="toggleAutomaticDashSubstitution:" destination="460" id="461"/>
+ <action selector="toggleAutomaticLinkDetection:" destination="354" id="357"/>
+ <action selector="toggleAutomaticQuoteSubstitution:" destination="351" id="356"/>
+ <action selector="toggleAutomaticSpellingCorrection:" destination="454" id="456"/>
+ <action selector="toggleAutomaticTextReplacement:" destination="462" id="463"/>
+ <action selector="toggleContinuousSpellChecking:" destination="219" id="222"/>
+ <action selector="toggleGrammarChecking:" destination="346" id="347"/>
+ <action selector="toggleRuler:" destination="505" id="520"/>
+ <action selector="toggleSmartInsertDelete:" destination="350" id="355"/>
+ <action selector="turnOffKerning:" destination="417" id="441"/>
+ <action selector="turnOffLigatures:" destination="413" id="440"/>
+ <action selector="underline:" destination="392" id="432"/>
+ <action selector="undo:" destination="207" id="223"/>
+ <action selector="unhideAllApplications:" destination="150" id="370"/>
+ <action selector="unscript:" destination="406" id="437"/>
+ <action selector="uppercaseWord:" destination="452" id="464"/>
+ <action selector="useAllLigatures:" destination="414" id="434"/>
+ <action selector="useStandardKerning:" destination="416" id="438"/>
+ <action selector="useStandardLigatures:" destination="412" id="439"/>
+ </connections>
+ </customObject>
+ <customObject id="-3" userLabel="Application">
+ <connections>
+ <action selector="terminate:" destination="136" id="449"/>
+ </connections>
+ </customObject>
+ <menu title="AMainMenu" systemMenu="main" id="29">
+ <items>
+ <menuItem title="macExample" id="56">
+ <menu key="submenu" title="macExample" systemMenu="apple" id="57">
+ <items>
+ <menuItem title="About macExample" id="58">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="236">
+ <modifierMask key="keyEquivalentModifierMask" command="YES"/>
+ </menuItem>
+ <menuItem title="Preferences…" keyEquivalent="," id="129"/>
+ <menuItem isSeparatorItem="YES" id="143">
+ <modifierMask key="keyEquivalentModifierMask" command="YES"/>
+ </menuItem>
+ <menuItem title="Services" id="131">
+ <menu key="submenu" title="Services" systemMenu="services" id="130"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="144">
+ <modifierMask key="keyEquivalentModifierMask" command="YES"/>
+ </menuItem>
+ <menuItem title="Hide macExample" keyEquivalent="h" id="134"/>
+ <menuItem title="Hide Others" keyEquivalent="h" id="145">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ </menuItem>
+ <menuItem title="Show All" id="150"/>
+ <menuItem isSeparatorItem="YES" id="149">
+ <modifierMask key="keyEquivalentModifierMask" command="YES"/>
+ </menuItem>
+ <menuItem title="Quit testUI" keyEquivalent="q" id="136"/>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="File" id="83">
+ <menu key="submenu" title="File" id="81">
+ <items>
+ <menuItem title="Account..." id="609">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Fetch Full Message" id="646">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <binding destination="623" name="value" keyPath="values.FetchFullMessageEnabled" id="648"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use OAuth 2.0" id="rf2-ay-8rn">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <binding destination="623" name="value" keyPath="values.OAuth2Enabled" id="aLG-X1-MaV"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="79">
+ <modifierMask key="keyEquivalentModifierMask" command="YES"/>
+ </menuItem>
+ <menuItem title="Close" keyEquivalent="w" id="73"/>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Edit" id="217">
+ <menu key="submenu" title="Edit" id="205">
+ <items>
+ <menuItem title="Undo" keyEquivalent="z" id="207"/>
+ <menuItem title="Redo" keyEquivalent="Z" id="215">
+ <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="206">
+ <modifierMask key="keyEquivalentModifierMask" command="YES"/>
+ </menuItem>
+ <menuItem title="Cut" keyEquivalent="x" id="199"/>
+ <menuItem title="Copy" keyEquivalent="c" id="197"/>
+ <menuItem title="Paste" keyEquivalent="v" id="203"/>
+ <menuItem title="Paste and Match Style" keyEquivalent="V" id="485">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ </menuItem>
+ <menuItem title="Delete" id="202"/>
+ <menuItem title="Select All" keyEquivalent="a" id="198"/>
+ <menuItem isSeparatorItem="YES" id="214">
+ <modifierMask key="keyEquivalentModifierMask" command="YES"/>
+ </menuItem>
+ <menuItem title="Find" id="218">
+ <menu key="submenu" title="Find" id="220">
+ <items>
+ <menuItem title="Find…" tag="1" keyEquivalent="f" id="209"/>
+ <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="534">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ </menuItem>
+ <menuItem title="Find Next" tag="2" keyEquivalent="g" id="208"/>
+ <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="213">
+ <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
+ </menuItem>
+ <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="221"/>
+ <menuItem title="Jump to Selection" keyEquivalent="j" id="210"/>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Spelling and Grammar" id="216">
+ <menu key="submenu" title="Spelling and Grammar" id="200">
+ <items>
+ <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="204"/>
+ <menuItem title="Check Document Now" keyEquivalent=";" id="201"/>
+ <menuItem isSeparatorItem="YES" id="453"/>
+ <menuItem title="Check Spelling While Typing" id="219"/>
+ <menuItem title="Check Grammar With Spelling" id="346"/>
+ <menuItem title="Correct Spelling Automatically" id="454">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Substitutions" id="348">
+ <menu key="submenu" title="Substitutions" id="349">
+ <items>
+ <menuItem title="Show Substitutions" id="457">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="459"/>
+ <menuItem title="Smart Copy/Paste" tag="1" keyEquivalent="f" id="350"/>
+ <menuItem title="Smart Quotes" tag="2" keyEquivalent="g" id="351"/>
+ <menuItem title="Smart Dashes" id="460">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Smart Links" tag="3" keyEquivalent="G" id="354">
+ <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
+ </menuItem>
+ <menuItem title="Text Replacement" id="462">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Transformations" id="450">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Transformations" id="451">
+ <items>
+ <menuItem title="Make Upper Case" id="452">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Make Lower Case" id="465">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Capitalize" id="466">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Speech" id="211">
+ <menu key="submenu" title="Speech" id="212">
+ <items>
+ <menuItem title="Start Speaking" id="196"/>
+ <menuItem title="Stop Speaking" id="195"/>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Format" id="375">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Format" id="376">
+ <items>
+ <menuItem title="Font" id="377">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Font" systemMenu="font" id="388">
+ <items>
+ <menuItem title="Show Fonts" keyEquivalent="t" id="389"/>
+ <menuItem title="Bold" tag="2" keyEquivalent="b" id="390"/>
+ <menuItem title="Italic" tag="1" keyEquivalent="i" id="391"/>
+ <menuItem title="Underline" keyEquivalent="u" id="392"/>
+ <menuItem isSeparatorItem="YES" id="393"/>
+ <menuItem title="Bigger" tag="3" keyEquivalent="+" id="394"/>
+ <menuItem title="Smaller" tag="4" keyEquivalent="-" id="395"/>
+ <menuItem isSeparatorItem="YES" id="396"/>
+ <menuItem title="Kern" id="397">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Kern" id="415">
+ <items>
+ <menuItem title="Use Default" id="416">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Use None" id="417">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Tighten" id="418">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Loosen" id="419">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Ligatures" id="398">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Ligatures" id="411">
+ <items>
+ <menuItem title="Use Default" id="412">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Use None" id="413">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Use All" id="414">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Baseline" id="399">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Baseline" id="405">
+ <items>
+ <menuItem title="Use Default" id="406">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Superscript" id="407">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Subscript" id="408">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Raise" id="409">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Lower" id="410">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="400"/>
+ <menuItem title="Show Colors" keyEquivalent="C" id="401"/>
+ <menuItem isSeparatorItem="YES" id="402"/>
+ <menuItem title="Copy Style" keyEquivalent="c" id="403">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ </menuItem>
+ <menuItem title="Paste Style" keyEquivalent="v" id="404">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Text" id="496">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Text" id="497">
+ <items>
+ <menuItem title="Align Left" keyEquivalent="{" id="498"/>
+ <menuItem title="Center" keyEquivalent="|" id="499"/>
+ <menuItem title="Justify" id="500">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Align Right" keyEquivalent="}" id="501"/>
+ <menuItem isSeparatorItem="YES" id="502"/>
+ <menuItem title="Writing Direction" id="503">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Writing Direction" id="508">
+ <items>
+ <menuItem title="Paragraph" enabled="NO" id="509">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="510">
+ <string key="title"> Default</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="511">
+ <string key="title"> Left to Right</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="512">
+ <string key="title"> Right to Left</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="513"/>
+ <menuItem title="Selection" enabled="NO" id="514">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="515">
+ <string key="title"> Default</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="516">
+ <string key="title"> Left to Right</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem id="517">
+ <string key="title"> Right to Left</string>
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="504"/>
+ <menuItem title="Show Ruler" id="505">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Copy Ruler" keyEquivalent="c" id="506">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ </menuItem>
+ <menuItem title="Paste Ruler" keyEquivalent="v" id="507">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Window" id="19">
+ <menu key="submenu" title="Window" systemMenu="window" id="24">
+ <items>
+ <menuItem title="Minimize" keyEquivalent="m" id="23"/>
+ <menuItem title="Zoom" id="239"/>
+ <menuItem isSeparatorItem="YES" id="92">
+ <modifierMask key="keyEquivalentModifierMask" command="YES"/>
+ </menuItem>
+ <menuItem title="Bring All to Front" id="5"/>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Help" id="490">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Help" systemMenu="help" id="491">
+ <items>
+ <menuItem title="macExample Help" keyEquivalent="?" id="492"/>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ <window title="testUI" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="371">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" topStrut="YES"/>
+ <rect key="contentRect" x="344" y="127" width="768" height="493"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1440" height="878"/>
+ <view key="contentView" wantsLayer="YES" id="372">
+ <rect key="frame" x="0.0" y="0.0" width="768" height="493"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="536">
+ <rect key="frame" x="-1" y="0.0" width="194" height="495"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
+ <clipView key="contentView" id="NhN-6Q-DSk">
+ <rect key="frame" x="1" y="17" width="192" height="477"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" headerView="539" id="537">
+ <rect key="frame" x="0.0" y="0.0" width="192" height="477"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <size key="intercellSpacing" width="3" height="2"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
+ <tableColumns>
+ <tableColumn width="188.68359375" minWidth="40" maxWidth="1000" id="541">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" white="0.33333298560000002" alpha="1" colorSpace="calibratedWhite"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="544">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ </tableColumn>
+ </tableColumns>
+ <connections>
+ <outlet property="dataSource" destination="594" id="597"/>
+ <outlet property="delegate" destination="594" id="598"/>
+ </connections>
+ </tableView>
+ </subviews>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </clipView>
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" id="538">
+ <rect key="frame" x="1" y="478" width="335" height="16"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" id="540">
+ <rect key="frame" x="224" y="17" width="15" height="102"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <tableHeaderView key="headerView" id="539">
+ <rect key="frame" x="0.0" y="0.0" width="192" height="17"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </tableHeaderView>
+ </scrollView>
+ <customView id="573" customClass="MCOMessageView">
+ <rect key="frame" x="201" y="0.0" width="567" height="493"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <connections>
+ <binding destination="594" name="hidden" keyPath="self.loading" id="694"/>
+ </connections>
+ </customView>
+ <progressIndicator horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" bezeled="NO" indeterminate="YES" style="spinning" id="687">
+ <rect key="frame" x="468" y="230" width="32" height="32"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <connections>
+ <binding destination="594" name="hidden" keyPath="self.loading" id="689">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">NSNegateBoolean</string>
+ </dictionary>
+ </binding>
+ <binding destination="594" name="animate" keyPath="self.loading" id="691"/>
+ </connections>
+ </progressIndicator>
+ </subviews>
+ </view>
+ </window>
+ <window title="Account" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="610">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES"/>
+ <rect key="contentRect" x="505" y="474" width="409" height="153"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1440" height="878"/>
+ <view key="contentView" id="611">
+ <rect key="frame" x="0.0" y="0.0" width="409" height="153"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <textField verticalHuggingPriority="750" id="612">
+ <rect key="frame" x="116" y="111" width="249" height="22"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="613">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <binding destination="494" name="value" keyPath="self.login" id="680">
+ <dictionary key="options">
+ <bool key="NSContinuouslyUpdatesValue" value="YES"/>
+ </dictionary>
+ </binding>
+ </connections>
+ </textField>
+ <textField verticalHuggingPriority="750" id="616">
+ <rect key="frame" x="72" y="114" width="39" height="17"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Login" id="617">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <textField verticalHuggingPriority="750" id="618">
+ <rect key="frame" x="47" y="82" width="64" height="17"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Password" id="619">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ <secureTextField verticalHuggingPriority="750" id="620">
+ <rect key="frame" x="116" y="79" width="249" height="22"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="621">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+ <allowedInputSourceLocales>
+ <string>NSAllRomanInputSourcesLocaleIdentifier</string>
+ </allowedInputSourceLocales>
+ </secureTextFieldCell>
+ <connections>
+ <binding destination="494" name="value" keyPath="self.password" id="679">
+ <dictionary key="options">
+ <bool key="NSContinuouslyUpdatesValue" value="YES"/>
+ </dictionary>
+ </binding>
+ </connections>
+ </secureTextField>
+ <button verticalHuggingPriority="750" id="627">
+ <rect key="frame" x="296" y="11" width="75" height="32"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <buttonCell key="cell" type="push" title="Login" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="628">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ <string key="keyEquivalent" base64-UTF8="YES">
+DQ
+</string>
+ </buttonCell>
+ <connections>
+ <binding destination="494" name="enabled" keyPath="self.loginEnabled" id="678"/>
+ </connections>
+ </button>
+ <button verticalHuggingPriority="750" id="629">
+ <rect key="frame" x="214" y="11" width="82" height="32"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="630">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ <string key="keyEquivalent" base64-UTF8="YES">
+Gw
+</string>
+ </buttonCell>
+ <connections>
+ <binding destination="494" name="enabled" keyPath="self.loggingIn" id="670"/>
+ </connections>
+ </button>
+ <progressIndicator horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" id="631">
+ <rect key="frame" x="373" y="20" width="16" height="16"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <connections>
+ <binding destination="494" name="hidden" keyPath="self.loggingIn" id="672">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">NSNegateBoolean</string>
+ </dictionary>
+ </binding>
+ <binding destination="494" name="animate" keyPath="self.loggingIn" id="674"/>
+ </connections>
+ </progressIndicator>
+ <textField verticalHuggingPriority="750" id="681">
+ <rect key="frame" x="116" y="47" width="249" height="22"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="682">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <binding destination="494" name="value" keyPath="self.hostname" id="686">
+ <dictionary key="options">
+ <bool key="NSContinuouslyUpdatesValue" value="YES"/>
+ </dictionary>
+ </binding>
+ </connections>
+ </textField>
+ <textField verticalHuggingPriority="750" id="684">
+ <rect key="frame" x="43" y="50" width="68" height="17"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Hostname" id="685">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ </textField>
+ </subviews>
+ </view>
+ <connections>
+ <action selector="makeKeyAndOrderFront:" destination="609" id="622"/>
+ <outlet property="initialFirstResponder" destination="612" id="654"/>
+ </connections>
+ </window>
+ <customObject id="494" customClass="AppDelegate">
+ <connections>
+ <action selector="accountCancel:" destination="629" id="652"/>
+ <action selector="accountLogin:" destination="627" id="653"/>
+ <outlet property="_accountWindow" destination="610" id="645"/>
+ <outlet property="_cancelButton" destination="629" id="642"/>
+ <outlet property="_loginButton" destination="627" id="641"/>
+ <outlet property="_msgListViewController" destination="594" id="644"/>
+ <outlet property="_progressView" destination="631" id="643"/>
+ <outlet property="cancelButton" destination="629" id="636"/>
+ <outlet property="loginButton" destination="627" id="637"/>
+ <outlet property="progressView" destination="631" id="638"/>
+ <outlet property="window" destination="371" id="532"/>
+ </connections>
+ </customObject>
+ <customObject id="420" customClass="NSFontManager">
+ <connections>
+ <action selector="addFontTrait:" destination="390" id="421"/>
+ <action selector="addFontTrait:" destination="391" id="422"/>
+ <action selector="modifyFont:" destination="395" id="423"/>
+ <action selector="modifyFont:" destination="394" id="425"/>
+ <action selector="orderFrontFontPanel:" destination="389" id="424"/>
+ </connections>
+ </customObject>
+ <viewController id="594" customClass="MCTMsgListViewController">
+ <connections>
+ <outlet property="_msgViewController" destination="595" id="608"/>
+ <outlet property="_tableView" destination="537" id="596"/>
+ </connections>
+ </viewController>
+ <viewController id="595" customClass="MCTMsgViewController">
+ <connections>
+ <outlet property="_messageView" destination="573" id="607"/>
+ </connections>
+ </viewController>
+ <userDefaultsController id="623"/>
+ </objects>
+ <classes>
+ <class className="AppDelegate" superclassName="NSObject">
+ <source key="sourceIdentifier" type="project" relativePath="./Classes/AppDelegate.h"/>
+ <relationships>
+ <relationship kind="action" name="accountCancel:"/>
+ <relationship kind="action" name="accountLogin:"/>
+ <relationship kind="outlet" name="_accountWindow" candidateClass="NSWindow"/>
+ <relationship kind="outlet" name="_msgListViewController" candidateClass="MCTMsgListViewController"/>
+ </relationships>
+ </class>
+ <class className="MCOMessageView" superclassName="NSView">
+ <source key="sourceIdentifier" type="project" relativePath="./Classes/MCOMessageView.h"/>
+ </class>
+ <class className="MCTMsgListViewController" superclassName="NSViewController">
+ <source key="sourceIdentifier" type="project" relativePath="./Classes/MCTMsgListViewController.h"/>
+ <relationships>
+ <relationship kind="outlet" name="_msgViewController" candidateClass="MCTMsgViewController"/>
+ <relationship kind="outlet" name="_tableView" candidateClass="NSTableView"/>
+ </relationships>
+ </class>
+ <class className="MCTMsgViewController" superclassName="NSViewController">
+ <source key="sourceIdentifier" type="project" relativePath="./Classes/MCTMsgViewController.h"/>
+ <relationships>
+ <relationship kind="outlet" name="_messageView" candidateClass="MCOMessageView"/>
+ </relationships>
+ </class>
+ </classes>
+</document> \ No newline at end of file