diff --git a/Podfile.lock b/Podfile.lock index eb91726f..537c3f7b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -35,4 +35,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 78a8cb73e37727fdec10b33d5e8d32bbbad8444f -COCOAPODS: 1.9.3 +COCOAPODS: 1.9.2 diff --git a/Pods/AppAuth/LICENSE b/Pods/AppAuth/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/Pods/AppAuth/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Pods/AppAuth/README.md b/Pods/AppAuth/README.md new file mode 100644 index 00000000..5f54c2c3 --- /dev/null +++ b/Pods/AppAuth/README.md @@ -0,0 +1,574 @@ +![AppAuth for iOS and macOS](https://rawgit.com/openid/AppAuth-iOS/master/appauth_lockup.svg) +[![Build Status](https://travis-ci.org/openid/AppAuth-iOS.svg?branch=master)](https://travis-ci.org/openid/AppAuth-iOS) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + +AppAuth for iOS and macOS is a client SDK for communicating with +[OAuth 2.0](https://tools.ietf.org/html/rfc6749) and +[OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html) providers. +It strives to +directly map the requests and responses of those specifications, while following +the idiomatic style of the implementation language. In addition to mapping the +raw protocol flows, convenience methods are available to assist with common +tasks like performing an action with fresh tokens. + +It follows the best practices set out in +[RFC 8252 - OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252) +including using `SFAuthenticationSession` and `SFSafariViewController` on iOS +for the auth request. `UIWebView` and `WKWebView` are explicitly *not* +supported due to the security and usability reasons explained in +[Section 8.12 of RFC 8252](https://tools.ietf.org/html/rfc8252#section-8.12). + +It also supports the [PKCE](https://tools.ietf.org/html/rfc7636) extension to +OAuth, which was created to secure authorization codes in public clients when +custom URI scheme redirects are used. The library is friendly to other +extensions (standard or otherwise), with the ability to handle additional params +in all protocol requests and responses. + +## Specification + +### iOS + +#### Supported Versions + +AppAuth supports iOS 7 and above. + +iOS 9+ uses the in-app browser tab pattern +(via `SFSafariViewController`), and falls back to the system browser (mobile +Safari) on earlier versions. + +#### Authorization Server Requirements + +Both Custom URI Schemes (all supported versions of iOS) and Universal Links +(iOS 9+) can be used with the library. + +In general, AppAuth can work with any authorization server that supports +native apps, as documented in [RFC 8252](https://tools.ietf.org/html/rfc8252), +either through custom URI scheme redirects, or universal links. +Authorization servers that assume all clients are web-based, or require clients to maintain +confidentiality of the client secrets may not work well. + +### macOS + +#### Supported Versions + +AppAuth supports macOS (OS X) 10.9 and above. + +#### Authorization Server Requirements + +AppAuth for macOS supports both custom schemes; a loopback HTTP redirects +via a small embedded server. + +In general, AppAuth can work with any authorization server that supports +native apps, as documented in [RFC 8252](https://tools.ietf.org/html/rfc8252); +either through custom URI schemes, or loopback HTTP redirects. +Authorization servers that assume all clients are web-based, or require clients to maintain +confidentiality of the client secrets may not work well. + +## Try + +Want to try out AppAuth? Just run: + + pod try AppAuth + +Follow the instructions in [Examples/README.md](Examples/README.md) to configure +with your own OAuth client (you need to update three configuration points with your +client info to try the demo). + +## Setup + +AppAuth supports four options for dependency management. + +### Swift Package Manager + +With [Swift Package Manager](https://swift.org/package-manager), +add the following `dependency` to your `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/openid/AppAuth-iOS.git", .upToNextMajor(from: "1.3.0")) +] +``` + +### CocoaPods + +With [CocoaPods](https://guides.cocoapods.org/using/getting-started.html), +add the following line to your `Podfile`: + + pod 'AppAuth' + +Then, run `pod install`. + +### Carthage + +With [Carthage](https://github.com/Carthage/Carthage), add the following +line to your `Cartfile`: + + github "openid/AppAuth-iOS" "master" + +Then, run `carthage bootstrap`. + +### Static Library + +You can also use AppAuth as a static library. This requires linking the library +and your project, and including the headers. Here is a suggested configuration: + +1. Create an Xcode Workspace. +2. Add `AppAuth.xcodeproj` to your Workspace. +3. Include libAppAuth as a linked library for your target (in the "General -> +Linked Framework and Libraries" section of your target). +4. Add `AppAuth-iOS/Source` to your search paths of your target ("Build Settings -> +"Header Search Paths"). + +## Auth Flow + +AppAuth supports both manual interaction with the authorization server +where you need to perform your own token exchanges, as well as convenience +methods that perform some of this logic for you. This example uses the +convenience method, which returns either an `OIDAuthState` object, or an error. + +`OIDAuthState` is a class that keeps track of the authorization and token +requests and responses, and provides a convenience method to call an API with +fresh tokens. This is the only object that you need to serialize to retain the +authorization state of the session. + +### Configuration + +You can configure AppAuth by specifying the endpoints directly: + +Objective-C +```objc +NSURL *authorizationEndpoint = + [NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"]; +NSURL *tokenEndpoint = + [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"]; + +OIDServiceConfiguration *configuration = + [[OIDServiceConfiguration alloc] + initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint]; + +// perform the auth request... +``` + +Swift +```swift +let authorizationEndpoint = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")! +let tokenEndpoint = URL(string: "https://www.googleapis.com/oauth2/v4/token")! +let configuration = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint, + tokenEndpoint: tokenEndpoint) + +// perform the auth request... +``` + +Or through discovery: + +Objective-C +```objc +NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"]; + +[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer + completion:^(OIDServiceConfiguration *_Nullable configuration, + NSError *_Nullable error) { + + if (!configuration) { + NSLog(@"Error retrieving discovery document: %@", + [error localizedDescription]); + return; + } + + // perform the auth request... +}]; +``` + +Swift +```swift +let issuer = URL(string: "https://accounts.google.com")! + +// discovers endpoints +OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in + guard let config = configuration else { + print("Error retrieving discovery document: \(error?.localizedDescription ?? "Unknown error")") + return + } + + // perform the auth request... +} +``` + +### Authorizing – iOS + +First, you need to have a property in your `UIApplicationDelegate` +implementation to hold the session, in order to continue the authorization flow +from the redirect. In this example, the implementation of this delegate is +a class named `AppDelegate`, if your app's application delegate has a different +name, please update the class name in samples below accordingly. + +Objective-C +```objc +@interface AppDelegate : UIResponder +// property of the app's AppDelegate +@property(nonatomic, strong, nullable) id currentAuthorizationFlow; +@end +``` + +Swift +```swift +class AppDelegate: UIResponder, UIApplicationDelegate { + // property of the app's AppDelegate + var currentAuthorizationFlow: OIDExternalUserAgentSession? +} +``` + + +And your main class, a property to store the auth state: + +Objective-C +```objc +// property of the containing class +@property(nonatomic, strong, nullable) OIDAuthState *authState; +``` +Swift +```swift +// property of the containing class +private var authState: OIDAuthState? +``` + + +Then, initiate the authorization request. By using the +`authStateByPresentingAuthorizationRequest` convenience method, the token +exchange will be performed automatically, and everything will be protected with +PKCE (if the server supports it). AppAuth also lets you perform these +requests manually. See the `authNoCodeExchange` method in the included Example +app for a demonstration: + +Objective-C +```objc +// builds authentication request +OIDAuthorizationRequest *request = + [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:kClientID + scopes:@[OIDScopeOpenID, + OIDScopeProfile] + redirectURL:kRedirectURI + responseType:OIDResponseTypeCode + additionalParameters:nil]; + +// performs authentication request +AppDelegate *appDelegate = + (AppDelegate *)[UIApplication sharedApplication].delegate; +appDelegate.currentAuthorizationFlow = + [OIDAuthState authStateByPresentingAuthorizationRequest:request + presentingViewController:self + callback:^(OIDAuthState *_Nullable authState, + NSError *_Nullable error) { + if (authState) { + NSLog(@"Got authorization tokens. Access token: %@", + authState.lastTokenResponse.accessToken); + [self setAuthState:authState]; + } else { + NSLog(@"Authorization error: %@", [error localizedDescription]); + [self setAuthState:nil]; + } +}]; +``` + +Swift +```swift +// builds authentication request +let request = OIDAuthorizationRequest(configuration: configuration, + clientId: clientID, + clientSecret: clientSecret, + scopes: [OIDScopeOpenID, OIDScopeProfile], + redirectURL: redirectURI, + responseType: OIDResponseTypeCode, + additionalParameters: nil) + +// performs authentication request +print("Initiating authorization request with scope: \(request.scope ?? "nil")") + +let appDelegate = UIApplication.shared.delegate as! AppDelegate + +appDelegate.currentAuthorizationFlow = + OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in + if let authState = authState { + self.setAuthState(authState) + print("Got authorization tokens. Access token: " + + "\(authState.lastTokenResponse?.accessToken ?? "nil")") + } else { + print("Authorization error: \(error?.localizedDescription ?? "Unknown error")") + self.setAuthState(nil) + } +} +``` + +*Handling the Redirect* + +The authorization response URL is returned to the app via the iOS openURL +app delegate method, so you need to pipe this through to the current +authorization session (created in the previous session): + +Objective-C +```objc +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + // Sends the URL to the current authorization flow (if any) which will + // process it if it relates to an authorization response. + if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { + _currentAuthorizationFlow = nil; + return YES; + } + + // Your additional URL handling (if any) goes here. + + return NO; +} +``` + +Swift +```swift +func application(_ app: UIApplication, + open url: URL, + options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { + // Sends the URL to the current authorization flow (if any) which will + // process it if it relates to an authorization response. + if let authorizationFlow = self.currentAuthorizationFlow, + authorizationFlow.resumeExternalUserAgentFlow(with: url) { + self.currentAuthorizationFlow = nil + return true + } + + // Your additional URL handling (if any) + + return false +} +``` + +### Authorizing – MacOS + +On macOS, the most popular way to get the authorization response redirect is to +start a local HTTP server on the loopback interface (limited to incoming +requests from the user's machine only). When the authorization is complete, the +user is redirected to that local server, and the authorization response can be +processed by the app. AppAuth takes care of managing the local HTTP server +lifecycle for you. + +> #### :bulb: Alternative: Custom URI Schemes +> Custom URI schemes are also supported on macOS, but some browsers display +> an interstitial, which reduces the usability. For an example on using custom +> URI schemes with macOS, See `Example-Mac`. + +To receive the authorization response using a local HTTP server, first you need +to have an instance variable in your main class to retain the HTTP redirect +handler: + +Objective-C +```objc +OIDRedirectHTTPHandler *_redirectHTTPHandler; +``` + +Then, as the port used by the local HTTP server varies, you need to start it +before building the authorization request, in order to get the exact redirect +URI to use: + +Objective-C +```objc +static NSString *const kSuccessURLString = + @"http://openid.github.io/AppAuth-iOS/redirect/"; +NSURL *successURL = [NSURL URLWithString:kSuccessURLString]; + +// Starts a loopback HTTP redirect listener to receive the code. This needs to be started first, +// as the exact redirect URI (including port) must be passed in the authorization request. +_redirectHTTPHandler = [[OIDRedirectHTTPHandler alloc] initWithSuccessURL:successURL]; +NSURL *redirectURI = [_redirectHTTPHandler startHTTPListener:nil]; +``` + +Then, initiate the authorization request. By using the +`authStateByPresentingAuthorizationRequest` convenience method, the token +exchange will be performed automatically, and everything will be protected with +PKCE (if the server supports it). By assigning the return value to the +`OIDRedirectHTTPHandler`'s `currentAuthorizationFlow`, the authorization will +continue automatically once the user makes their choice: + +```objc +// builds authentication request +OIDAuthorizationRequest *request = + [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:kClientID + clientSecret:kClientSecret + scopes:@[ OIDScopeOpenID ] + redirectURL:redirectURI + responseType:OIDResponseTypeCode + additionalParameters:nil]; +// performs authentication request +__weak __typeof(self) weakSelf = self; +_redirectHTTPHandler.currentAuthorizationFlow = + [OIDAuthState authStateByPresentingAuthorizationRequest:request + callback:^(OIDAuthState *_Nullable authState, + NSError *_Nullable error) { + // Brings this app to the foreground. + [[NSRunningApplication currentApplication] + activateWithOptions:(NSApplicationActivateAllWindows | + NSApplicationActivateIgnoringOtherApps)]; + + // Processes the authorization response. + if (authState) { + NSLog(@"Got authorization tokens. Access token: %@", + authState.lastTokenResponse.accessToken); + } else { + NSLog(@"Authorization error: %@", error.localizedDescription); + } + [weakSelf setAuthState:authState]; +}]; +``` + +### Making API Calls + +AppAuth gives you the raw token information, if you need it. However, we +recommend that users of the `OIDAuthState` convenience wrapper use the provided +`performActionWithFreshTokens:` method to perform their API calls to avoid +needing to worry about token freshness: + +Objective-C +```objc +[_authState performActionWithFreshTokens:^(NSString *_Nonnull accessToken, + NSString *_Nonnull idToken, + NSError *_Nullable error) { + if (error) { + NSLog(@"Error fetching fresh tokens: %@", [error localizedDescription]); + return; + } + + // perform your API request using the tokens +}]; +``` + +Swift +```swift +let userinfoEndpoint = URL(string:"https://openidconnect.googleapis.com/v1/userinfo")! +self.authState?.performAction() { (accessToken, idToken, error) in + + if error != nil { + print("Error fetching fresh tokens: \(error?.localizedDescription ?? "Unknown error")") + return + } + guard let accessToken = accessToken else { + return + } + + // Add Bearer token to request + var urlRequest = URLRequest(url: userinfoEndpoint) + urlRequest.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"] + + // Perform request... +} +``` + +### Custom User-Agents + +Each OAuth flow involves presenting an external user-agent to the user, that +allows them to interact with the OAuth authorization server. Typical examples +of a user-agent are the user's browser, or an in-app browser tab incarnation +like `ASWebAuthenticationSession` on iOS. + +AppAuth ships with several implementations of an external user-agent out of the +box, including defaults for iOS and macOS suitable for most cases. The default +user-agents typically share persistent cookies with the system default browser, +to improve the chance that the user doesn't need to sign-in all over again. + +It is possible to change the user-agent that AppAuth uses, and even write your +own - all without needing to fork the library. + +All implementations of the external user-agent, be they included or created by +you need to conform to the +[`OIDExternalUserAgent`](http://openid.github.io/AppAuth-iOS/docs/latest/protocol_o_i_d_external_user_agent-p.html) +protocol. + +Instances of the `OIDExternalUserAgent`are passed into +[`OIDAuthState.authStateByPresentingAuthorizationRequest:externalUserAgent:callback`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_auth_state.html#ac762fe2bf95c116f0b437419be211fa1) +and/or +[`OIDAuthorizationService.presentAuthorizationRequest:externalUserAgent:callback:`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_authorization_service.html#ae551f8e6887366a46e49b09b37389b8f) +rather than using the platform-specific convenience methods (which use the +default user-agents for their respective platforms), like +[`OIDAuthState.authStateByPresentingAuthorizationRequest:presentingViewController:callback:`](http://openid.github.io/AppAuth-iOS/docs/latest/category_o_i_d_auth_state_07_i_o_s_08.html#ae32fd0732cd3192cd5219f2655a4c85c). + +Popular use-cases for writing your own user-agent implementation include needing +to style the user-agent in ways not supported by AppAuth, and implementing a +fully custom flow with your own business logic. You can take one of the existing +implementations as a starting point to copy, rename, and customize to your +needs. + +#### Custom Browser User-Agent + +AppAuth for iOS includes a few extra user-agent implementations which you can +try, or use as a reference for your own implementation. One of them, +[`OIDExternalUserAgentIOSCustomBrowser`](http://openid.github.io/AppAuth-iOS/docs/latest/interface_o_i_d_external_user_agent_i_o_s_custom_browser.html) +enables you to use a different browser for authentication, like Chrome for iOS +or Firefox for iOS. + +Here's how to configure AppAuth to use a custom browser using the +`OIDExternalUserAgentIOSCustomBrowser` user agent: + +First, add the following array to your +[Info.plist](https://github.com/openid/AppAuth-iOS/blob/135f99d2cb4e9d18d310ac2588b905e612461561/Examples/Example-iOS_ObjC/Source/Info.plist#L34) +(in XCode, right click -> Open As -> Source Code) + +``` + LSApplicationQueriesSchemes + + googlechromes + opera-https + firefox + +``` + +This is required so that AppAuth can test for the browser and open the app store +if it's not installed (the default behavior of this user-agent). You only need +to include the URL scheme of the actual browser you intend to use. + +Objective-C +```objc +// performs authentication request +AppDelegate *appDelegate = + (AppDelegate *)[UIApplication sharedApplication].delegate; +id userAgent = + [OIDExternalUserAgentIOSCustomBrowser CustomBrowserChrome]; +appDelegate.currentAuthorizationFlow = + [OIDAuthState authStateByPresentingAuthorizationRequest:request + externalUserAgent:self + callback:^(OIDAuthState *_Nullable authState, + NSError *_Nullable error) { + if (authState) { + NSLog(@"Got authorization tokens. Access token: %@", + authState.lastTokenResponse.accessToken); + [self setAuthState:authState]; + } else { + NSLog(@"Authorization error: %@", [error localizedDescription]); + [self setAuthState:nil]; + } +}]; +``` + +That's it! With those two changes (which you can try on the included sample), +AppAuth will use Chrome iOS for the authorization request (and open Chrome in +the App Store if it's not installed). + +⚠️**Note: the `OIDExternalUserAgentIOSCustomBrowser` user-agent is not intended for consumer apps**. It is designed for +advanced enterprise use-cases where the app developers have greater control over +the operating environment and have special requirements that require a custom +browser like Chrome. + +You don't need to stop with the included external user agents either! Since the +[`OIDExternalUserAgent`](http://openid.github.io/AppAuth-iOS/docs/latest/protocol_o_i_d_external_user_agent-p.html) +protocol is part of AppAuth's public API, you can implement your own versions of +it. In the above example, +`userAgent = [OIDExternalUserAgentIOSCustomBrowser CustomBrowserChrome]` would +be replaced with an instantiation of your user-agent implementation. + +## API Documentation + +Browse the [API documentation](http://openid.github.io/AppAuth-iOS/docs/latest/annotated.html). + +## Included Samples + +Sample apps that explore core AppAuth features are available for iOS and macOS; follow the instructions in [Examples/README.md](Examples/README.md) to get started. diff --git a/Pods/AppAuth/Source/AppAuth.h b/Pods/AppAuth/Source/AppAuth.h new file mode 100644 index 00000000..4f779df3 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth.h @@ -0,0 +1,92 @@ +/*! @file AppAuth.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDAuthState.h" +#import "OIDAuthStateChangeDelegate.h" +#import "OIDAuthStateErrorDelegate.h" +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDAuthorizationService.h" +#import "OIDError.h" +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentRequest.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDGrantTypes.h" +#import "OIDIDToken.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDResponseTypes.h" +#import "OIDScopes.h" +#import "OIDScopeUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDTokenUtilities.h" +#import "OIDURLSessionProvider.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" + +#if TARGET_OS_TV +#elif TARGET_OS_WATCH +#elif TARGET_OS_IOS || TARGET_OS_MACCATALYST +#import "OIDAuthState+IOS.h" +#import "OIDAuthorizationService+IOS.h" +#import "OIDExternalUserAgentIOS.h" +#import "OIDExternalUserAgentIOSCustomBrowser.h" +#import "OIDExternalUserAgentCatalyst.h" +#elif TARGET_OS_MAC +#import "OIDAuthState+Mac.h" +#import "OIDAuthorizationService+Mac.h" +#import "OIDExternalUserAgentMac.h" +#import "OIDRedirectHTTPHandler.h" +#else +#error "Platform Undefined" +#endif + +/*! @mainpage AppAuth for iOS and macOS + + @section introduction Introduction + + AppAuth for iOS and macOS is a client SDK for communicating with [OAuth 2.0] + (https://tools.ietf.org/html/rfc6749) and [OpenID Connect] + (http://openid.net/specs/openid-connect-core-1_0.html) providers. It strives to + directly map the requests and responses of those specifications, while following + the idiomatic style of the implementation language. In addition to mapping the + raw protocol flows, convenience methods are available to assist with common + tasks like performing an action with fresh tokens. + + It follows the best practices set out in + [RFC 8252 - OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252) + including using `SFAuthenticationSession` and `SFSafariViewController` on iOS + for the auth request. Web view and `WKWebView` are explicitly *not* + supported due to the security and usability reasons explained in + [Section 8.12 of RFC 8252](https://tools.ietf.org/html/rfc8252#section-8.12). + + It also supports the [PKCE](https://tools.ietf.org/html/rfc7636) extension to + OAuth which was created to secure authorization codes in public clients when + custom URI scheme redirects are used. The library is friendly to other + extensions (standard or otherwise) with the ability to handle additional params + in all protocol requests and responses. + + Homepage: http://openid.github.io/AppAuth-iOS/
+ API Documentation: http://openid.github.io/AppAuth-iOS/docs/latest
+ Git Repository: https://github.com/openid/AppAuth-iOS
+ + */ diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthState+IOS.h b/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthState+IOS.h new file mode 100644 index 00000000..99ca4573 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthState+IOS.h @@ -0,0 +1,63 @@ +/*! @file OIDAuthState+IOS.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDAuthState.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief iOS specific convenience methods for @c OIDAuthState. + */ +@interface OIDAuthState (IOS) + +/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request + and performing the authorization code exchange in the case of code flow requests. For + the hybrid flow, the caller should validate the id_token and c_hash, then perform the token + request (@c OIDAuthorizationService.performTokenRequest:callback:) + and update the OIDAuthState with the results (@c + OIDAuthState.updateWithTokenResponse:error:). + @param authorizationRequest The authorization request to present. + @param presentingViewController The view controller from which to present the + @c SFSafariViewController. On iOS 13, the window of this UIViewController + is used as the ASPresentationAnchor. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingViewController:(UIViewController *)presentingViewController + callback:(OIDAuthStateAuthorizationCallback)callback; + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + callback:(OIDAuthStateAuthorizationCallback)callback API_AVAILABLE(ios(11)) API_UNAVAILABLE(macCatalyst) + __deprecated_msg("This method will not work on iOS 13. Use " + "authStateByPresentingAuthorizationRequest:presentingViewController:callback:"); + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthState+IOS.m b/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthState+IOS.m new file mode 100644 index 00000000..9f3a4e8c --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthState+IOS.m @@ -0,0 +1,58 @@ +/*! @file OIDAuthState+IOS.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDAuthState+IOS.h" +#import "OIDExternalUserAgentIOS.h" +#import "OIDExternalUserAgentCatalyst.h" + +@implementation OIDAuthState (IOS) + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + presentingViewController:(UIViewController *)presentingViewController + callback:(OIDAuthStateAuthorizationCallback)callback { + id externalUserAgent; +#if TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentCatalyst alloc] + initWithPresentingViewController:presentingViewController]; +#else // TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentIOS alloc] initWithPresentingViewController:presentingViewController]; +#endif // TARGET_OS_MACCATALYST + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:callback]; +} + +#if !TARGET_OS_MACCATALYST ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + callback:(OIDAuthStateAuthorizationCallback)callback { + OIDExternalUserAgentIOS *externalUserAgent = [[OIDExternalUserAgentIOS alloc] init]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:callback]; +} +#endif // !TARGET_OS_MACCATALYST + +@end + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthorizationService+IOS.h b/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthorizationService+IOS.h new file mode 100644 index 00000000..c6e14f19 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthorizationService+IOS.h @@ -0,0 +1,50 @@ +/*! @file OIDAuthorizationService+IOS.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDAuthorizationService.h" +#import "OIDExternalUserAgentSession.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Provides iOS specific authorization request handling. + */ +@interface OIDAuthorizationService (IOS) + +/*! @brief Perform an authorization flow using \SFSafariViewController. + @param request The authorization request. + @param presentingViewController The view controller from which to present the + \SFSafariViewController. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingViewController:(UIViewController *)presentingViewController + callback:(OIDAuthorizationCallback)callback; +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthorizationService+IOS.m b/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthorizationService+IOS.m new file mode 100644 index 00000000..05ccff5d --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDAuthorizationService+IOS.m @@ -0,0 +1,48 @@ +/*! @file OIDAuthorizationService+IOS.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDAuthorizationService+IOS.h" +#import "OIDExternalUserAgentIOS.h" +#import "OIDExternalUserAgentCatalyst.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDAuthorizationService (IOS) + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + presentingViewController:(UIViewController *)presentingViewController + callback:(OIDAuthorizationCallback)callback { + id externalUserAgent; +#if TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentCatalyst alloc] + initWithPresentingViewController:presentingViewController]; +#else // TARGET_OS_MACCATALYST + externalUserAgent = [[OIDExternalUserAgentIOS alloc] initWithPresentingViewController:presentingViewController]; +#endif // TARGET_OS_MACCATALYST + return [self presentAuthorizationRequest:request externalUserAgent:externalUserAgent callback:callback]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentCatalyst.h b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentCatalyst.h new file mode 100644 index 00000000..d98d4413 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentCatalyst.h @@ -0,0 +1,52 @@ +/*! @file OIDExternalUserAgentCatalyst.h + @brief AppAuth iOS SDK + @copyright + Copyright 2019 The AppAuth Authors. All Rights Reserved. + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDExternalUserAgent.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A Catalyst specific external user-agent that uses `ASWebAuthenticationSession` to + present the request. +*/ +API_AVAILABLE(macCatalyst(13)) API_UNAVAILABLE(ios) +@interface OIDExternalUserAgentCatalyst : NSObject + +/*! @internal + @brief Unavailable. Please use @c initWithPresentingViewController: + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +/*! @brief The designated initializer. + @param presentingViewController The view controller from which to present the + \SFSafariViewController. + */ +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentCatalyst.m b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentCatalyst.m new file mode 100644 index 00000000..fc9cef5c --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentCatalyst.m @@ -0,0 +1,145 @@ +/*! @file OIDExternalUserAgentCatalyst.m + @brief AppAuth iOS SDK + @copyright + Copyright 2019 The AppAuth Authors. All Rights Reserved. + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDExternalUserAgentCatalyst.h" + +#import +#import + +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDExternalUserAgentRequest.h" + +#if TARGET_OS_MACCATALYST + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDExternalUserAgentCatalyst () +@end + +@implementation OIDExternalUserAgentCatalyst { + UIViewController *_presentingViewController; + + BOOL _externalUserAgentFlowInProgress; + __weak id _session; + ASWebAuthenticationSession *_webAuthenticationVC; +} + +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController { + self = [super init]; + if (self) { + _presentingViewController = presentingViewController; + } + return self; +} + +- (BOOL)presentExternalUserAgentRequest:(id)request + session:(id)session { + if (_externalUserAgentFlowInProgress) { + // TODO: Handle errors as authorization is already in progress. + return NO; + } + + _externalUserAgentFlowInProgress = YES; + _session = session; + BOOL openedUserAgent = NO; + NSURL *requestURL = [request externalUserAgentRequestURL]; + + __weak OIDExternalUserAgentCatalyst *weakSelf = self; + NSString *redirectScheme = request.redirectScheme; + ASWebAuthenticationSession *authenticationVC = + [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentCatalyst *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_webAuthenticationVC = nil; + if (callbackURL) { + [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; + } + }]; + + authenticationVC.presentationContextProvider = self; + _webAuthenticationVC = authenticationVC; + openedUserAgent = [authenticationVC start]; + + if (!openedUserAgent) { + [self cleanUp]; + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open ASWebAuthenticationSession view controller."]; + [session failExternalUserAgentFlowWithError:safariError]; + } + return openedUserAgent; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { + if (!_externalUserAgentFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + if (completion) completion(); + return; + } + + ASWebAuthenticationSession *webAuthenticationVC = _webAuthenticationVC; + + [self cleanUp]; + + if (webAuthenticationVC) { + // dismiss the ASWebAuthenticationSession + [webAuthenticationVC cancel]; + if (completion) completion(); + } else { + if (completion) completion(); + } +} + +- (void)cleanUp { + // The weak reference to |_session| is set to nil to avoid accidentally using + // it while not in an authorization flow. + _webAuthenticationVC = nil; + _session = nil; + _externalUserAgentFlowInProgress = NO; +} + +#pragma mark - ASWebAuthenticationPresentationContextProviding + +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session { + return _presentingViewController.view.window; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_MACCATALYST + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOS.h b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOS.h new file mode 100644 index 00000000..7261c050 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOS.h @@ -0,0 +1,53 @@ +/*! @file OIDExternalUserAgentIOS.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDExternalUserAgent.h" + +@class SFSafariViewController; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief An iOS specific external user-agent that uses the best possible user-agent available + depending on the version of iOS to present the request. + */ +API_UNAVAILABLE(macCatalyst) +@interface OIDExternalUserAgentIOS : NSObject + +- (nullable instancetype)init API_AVAILABLE(ios(11)) + __deprecated_msg("This method will not work on iOS 13, use " + "initWithPresentingViewController:presentingViewController"); + +/*! @brief The designated initializer. + @param presentingViewController The view controller from which to present the + \SFSafariViewController. + */ +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOS.m b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOS.m new file mode 100644 index 00000000..728f0868 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOS.m @@ -0,0 +1,256 @@ +/*! @file OIDExternalUserAgentIOS.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDExternalUserAgentIOS.h" + +#import +#import + +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDExternalUserAgentRequest.h" + +#if !TARGET_OS_MACCATALYST + +NS_ASSUME_NONNULL_BEGIN + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 +@interface OIDExternalUserAgentIOS () +@end +#else +@interface OIDExternalUserAgentIOS () +@end +#endif + +@implementation OIDExternalUserAgentIOS { + UIViewController *_presentingViewController; + + BOOL _externalUserAgentFlowInProgress; + __weak id _session; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + __weak SFSafariViewController *_safariVC; + SFAuthenticationSession *_authenticationVC; + ASWebAuthenticationSession *_webAuthenticationVC; +#pragma clang diagnostic pop +} + +- (nullable instancetype)init { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + return [self initWithPresentingViewController:nil]; +#pragma clang diagnostic pop +} + +- (nullable instancetype)initWithPresentingViewController: + (UIViewController *)presentingViewController { + self = [super init]; + if (self) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + NSAssert(presentingViewController != nil, + @"presentingViewController cannot be nil on iOS 13"); +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + + _presentingViewController = presentingViewController; + } + return self; +} + +- (BOOL)presentExternalUserAgentRequest:(id)request + session:(id)session { + if (_externalUserAgentFlowInProgress) { + // TODO: Handle errors as authorization is already in progress. + return NO; + } + + _externalUserAgentFlowInProgress = YES; + _session = session; + BOOL openedUserAgent = NO; + NSURL *requestURL = [request externalUserAgentRequestURL]; + + // iOS 12 and later, use ASWebAuthenticationSession + if (@available(iOS 12.0, *)) { + // ASWebAuthenticationSession doesn't work with guided access (rdar://40809553) + if (!UIAccessibilityIsGuidedAccessEnabled()) { + __weak OIDExternalUserAgentIOS *weakSelf = self; + NSString *redirectScheme = request.redirectScheme; + ASWebAuthenticationSession *authenticationVC = + [[ASWebAuthenticationSession alloc] initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentIOS *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_webAuthenticationVC = nil; + if (callbackURL) { + [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:nil]; + [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; + } + }]; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + if (@available(iOS 13.0, *)) { + authenticationVC.presentationContextProvider = self; + } +#endif + _webAuthenticationVC = authenticationVC; + openedUserAgent = [authenticationVC start]; + } + } + // iOS 11, use SFAuthenticationSession + if (@available(iOS 11.0, *)) { + // SFAuthenticationSession doesn't work with guided access (rdar://40809553) + if (!openedUserAgent && !UIAccessibilityIsGuidedAccessEnabled()) { + __weak OIDExternalUserAgentIOS *weakSelf = self; + NSString *redirectScheme = request.redirectScheme; + SFAuthenticationSession *authenticationVC = + [[SFAuthenticationSession alloc] initWithURL:requestURL + callbackURLScheme:redirectScheme + completionHandler:^(NSURL * _Nullable callbackURL, + NSError * _Nullable error) { + __strong OIDExternalUserAgentIOS *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + strongSelf->_authenticationVC = nil; + if (callbackURL) { + [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL]; + } else { + NSError *safariError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:error + description:@"User cancelled."]; + [strongSelf->_session failExternalUserAgentFlowWithError:safariError]; + } + }]; + _authenticationVC = authenticationVC; + openedUserAgent = [authenticationVC start]; + } + } + // iOS 9 and 10, use SFSafariViewController + if (@available(iOS 9.0, *)) { + if (!openedUserAgent && _presentingViewController) { + SFSafariViewController *safariVC = + [[SFSafariViewController alloc] initWithURL:requestURL]; + safariVC.delegate = self; + _safariVC = safariVC; + [_presentingViewController presentViewController:safariVC animated:YES completion:nil]; + openedUserAgent = YES; + } + } + // iOS 8 and earlier, use mobile Safari + if (!openedUserAgent){ + openedUserAgent = [[UIApplication sharedApplication] openURL:requestURL]; + } + + if (!openedUserAgent) { + [self cleanUp]; + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open Safari."]; + [session failExternalUserAgentFlowWithError:safariError]; + } + return openedUserAgent; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion { + if (!_externalUserAgentFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + if (completion) completion(); + return; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + SFSafariViewController *safariVC = _safariVC; + SFAuthenticationSession *authenticationVC = _authenticationVC; + ASWebAuthenticationSession *webAuthenticationVC = _webAuthenticationVC; +#pragma clang diagnostic pop + + [self cleanUp]; + + if (webAuthenticationVC) { + // dismiss the ASWebAuthenticationSession + [webAuthenticationVC cancel]; + if (completion) completion(); + } else if (authenticationVC) { + // dismiss the SFAuthenticationSession + [authenticationVC cancel]; + if (completion) completion(); + } else if (safariVC) { + // dismiss the SFSafariViewController + [safariVC dismissViewControllerAnimated:YES completion:completion]; + } else { + if (completion) completion(); + } +} + +- (void)cleanUp { + // The weak references to |_safariVC| and |_session| are set to nil to avoid accidentally using + // them while not in an authorization flow. + _safariVC = nil; + _authenticationVC = nil; + _webAuthenticationVC = nil; + _session = nil; + _externalUserAgentFlowInProgress = NO; +} + +#pragma mark - SFSafariViewControllerDelegate + +- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller NS_AVAILABLE_IOS(9.0) { + if (controller != _safariVC) { + // Ignore this call if the safari view controller do not match. + return; + } + if (!_externalUserAgentFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + return; + } + id session = _session; + [self cleanUp]; + NSError *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:@"No external user agent flow in progress."]; + [session failExternalUserAgentFlowWithError:error]; +} + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 +#pragma mark - ASWebAuthenticationPresentationContextProviding + +- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session API_AVAILABLE(ios(13.0)){ + return _presentingViewController.view.window; +} +#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + +@end + +NS_ASSUME_NONNULL_END + +#endif // !TARGET_OS_MACCATALYST + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h new file mode 100644 index 00000000..2032e8c9 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h @@ -0,0 +1,113 @@ +/*! @file OIDExternalUserAgentIOSCustomBrowser.h + @brief AppAuth iOS SDK + @copyright + Copyright 2018 Google LLC + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import + +#import "OIDExternalUserAgent.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A block that transforms a regular http/https URL into one that will open in an + alternative browser. + @param requestURL the http/https request URL to be transformed. + @return transformed URL. + */ +typedef NSURL *_Nullable (^OIDCustomBrowserURLTransformation)(NSURL *_Nullable requestURL); + +/*! @brief An implementation of the OIDExternalUserAgent protocol for iOS that uses + a custom browser (i.e. not Safari) for external requests. It is suitable for browsers that + offer a custom url scheme that simply replaces the "https" scheme. It is not designed + for browsers that require other modifications to the URL. If the browser is not installed + the user will be prompted to install it. + */ +API_UNAVAILABLE(macCatalyst) +@interface OIDExternalUserAgentIOSCustomBrowser : NSObject + +/*! @brief URL transformation block for the browser. + */ +@property(nonatomic, readonly) OIDCustomBrowserURLTransformation URLTransformation; + +/*! @brief URL Scheme used to test for whether the browser is installed. + */ +@property(nonatomic, readonly, nullable) NSString *canOpenURLScheme; + +/*! @brief URL of the browser's App Store listing. + */ +@property(nonatomic, readonly, nullable) NSURL *appStoreURL; + +/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Chrome. + */ ++ (instancetype)CustomBrowserChrome; + +/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Firefox. + */ ++ (instancetype)CustomBrowserFirefox; + +/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Opera. + */ ++ (instancetype)CustomBrowserOpera; + +/*! @brief An instance of @c OIDExternalUserAgentIOSCustomBrowser for Safari. + */ ++ (instancetype)CustomBrowserSafari; + +/*! @brief Creates a @c OIDCustomBrowserURLTransformation using the scheme substitution method used + iOS browsers like Chrome and Firefox. + */ ++ (OIDCustomBrowserURLTransformation) + URLTransformationSchemeSubstitutionHTTPS:(NSString *)browserSchemeHTTPS + HTTP:(nullable NSString *)browserSchemeHTTP; + +/*! @brief Creates a @c OIDCustomBrowserURLTransformation with the URL prefix method used by + iOS browsers like Firefox. + */ ++ (OIDCustomBrowserURLTransformation) URLTransformationSchemeConcatPrefix:(NSString*)URLprefix; + +/*! @internal + @brief Unavailable. Please use @c initWithURLTransformation:canOpenURLScheme:appStoreURL: + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +/*! @brief OIDExternalUserAgent for a custom browser. @c presentExternalUserAgentRequest:session method + will return NO if the browser isn't installed. + */ +- (nullable instancetype)initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation; + +/*! @brief The designated initializer. + @param URLTransformation the transformation block to translate the URL into one that will open + in the desired custom browser. + @param canOpenURLScheme any scheme supported by the browser used to check if the browser is + installed. + @param appStoreURL URL of the browser in the app store. When this and @c canOpenURLScheme + are non-nil, @c presentExternalUserAgentRequest:session will redirect the user to the app store + if the browser is not installed. + */ +- (nullable instancetype)initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation + canOpenURLScheme:(nullable NSString *)canOpenURLScheme + appStoreURL:(nullable NSURL *)appStoreURL + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m new file mode 100644 index 00000000..51d2e56c --- /dev/null +++ b/Pods/AppAuth/Source/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m @@ -0,0 +1,171 @@ +/*! @file OIDExternalUserAgentIOSCustomBrowser.m + @brief AppAuth iOS SDK + @copyright + Copyright 2018 Google LLC + @copydetails + 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 + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + +#import "OIDExternalUserAgentIOSCustomBrowser.h" + +#import + +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationService.h" +#import "OIDErrorUtilities.h" +#import "OIDURLQueryComponent.h" + +#if !TARGET_OS_MACCATALYST + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDExternalUserAgentIOSCustomBrowser + ++ (instancetype)CustomBrowserChrome { + // Chrome iOS documentation: https://developer.chrome.com/multidevice/ios/links + OIDCustomBrowserURLTransformation transform = [[self class] URLTransformationSchemeSubstitutionHTTPS:@"googlechromes" HTTP:@"googlechrome"]; + NSURL *appStoreURL = + [NSURL URLWithString:@"itms-apps://itunes.apple.com/us/app/chrome/id535886823"]; + return [[[self class] alloc] initWithURLTransformation:transform + canOpenURLScheme:@"googlechromes" + appStoreURL:appStoreURL]; +} + ++ (instancetype)CustomBrowserFirefox { + // Firefox iOS documentation: https://github.com/mozilla-mobile/firefox-ios-open-in-client + OIDCustomBrowserURLTransformation transform = + [[self class] URLTransformationSchemeConcatPrefix:@"firefox://open-url?url="]; + NSURL *appStoreURL = + [NSURL URLWithString:@"itms-apps://itunes.apple.com/us/app/firefox-web-browser/id989804926"]; + return [[[self class] alloc] initWithURLTransformation:transform + canOpenURLScheme:@"firefox" + appStoreURL:appStoreURL]; +} + ++ (instancetype)CustomBrowserOpera { + OIDCustomBrowserURLTransformation transform = + [[self class] URLTransformationSchemeSubstitutionHTTPS:@"opera-https" HTTP:@"opera-http"]; + NSURL *appStoreURL = + [NSURL URLWithString:@"itms-apps://itunes.apple.com/us/app/opera-mini-web-browser/id363729560"]; + return [[[self class] alloc] initWithURLTransformation:transform + canOpenURLScheme:@"opera-https" + appStoreURL:appStoreURL]; +} + ++ (instancetype)CustomBrowserSafari { + OIDCustomBrowserURLTransformation transformNOP = ^NSURL *(NSURL *requestURL) { + return requestURL; + }; + OIDExternalUserAgentIOSCustomBrowser *transform = + [[[self class] alloc] initWithURLTransformation:transformNOP]; + return transform; +} + ++ (OIDCustomBrowserURLTransformation) + URLTransformationSchemeSubstitutionHTTPS:(NSString *)browserSchemeHTTPS + HTTP:(nullable NSString *)browserSchemeHTTP { + OIDCustomBrowserURLTransformation transform = ^NSURL *(NSURL *requestURL) { + // Replace the URL Scheme with the Chrome equivalent. + NSString *newScheme = nil; + if ([requestURL.scheme isEqualToString:@"https"]) { + newScheme = browserSchemeHTTPS; + } else if ([requestURL.scheme isEqualToString:@"http"]) { + if (!browserSchemeHTTP) { + NSAssert(false, @"No HTTP scheme registered for browser"); + return nil; + } + newScheme = browserSchemeHTTP; + } + + // Replaces the URI scheme with the custom scheme + NSURLComponents *components = [NSURLComponents componentsWithURL:requestURL + resolvingAgainstBaseURL:YES]; + components.scheme = newScheme; + return components.URL; + }; + return transform; +} + ++ (OIDCustomBrowserURLTransformation)URLTransformationSchemeConcatPrefix:(NSString *)URLprefix { + OIDCustomBrowserURLTransformation transform = ^NSURL *(NSURL *requestURL) { + NSString *requestURLString = [requestURL absoluteString]; + NSMutableCharacterSet *allowedParamCharacters = + [OIDURLQueryComponent URLParamValueAllowedCharacters]; + NSString *encodedUrl = [requestURLString stringByAddingPercentEncodingWithAllowedCharacters:allowedParamCharacters]; + NSString *newURL = [NSString stringWithFormat:@"%@%@", URLprefix, encodedUrl]; + return [NSURL URLWithString:newURL]; + }; + return transform; +} + +- (nullable instancetype)initWithURLTransformation: + (OIDCustomBrowserURLTransformation)URLTransformation { + return [self initWithURLTransformation:URLTransformation canOpenURLScheme:nil appStoreURL:nil]; +} + +- (nullable instancetype) + initWithURLTransformation:(OIDCustomBrowserURLTransformation)URLTransformation + canOpenURLScheme:(nullable NSString *)canOpenURLScheme + appStoreURL:(nullable NSURL *)appStoreURL { + self = [super init]; + if (self) { + _URLTransformation = URLTransformation; + _canOpenURLScheme = canOpenURLScheme; + _appStoreURL = appStoreURL; + } + return self; +} + +- (BOOL)presentExternalUserAgentRequest:(nonnull id)request + session:(nonnull id)session { + // If the app store URL is set, checks if the app is installed and if not opens the app store. + if (_appStoreURL && _canOpenURLScheme) { + // Verifies existence of LSApplicationQueriesSchemes Info.plist key. + NSArray __unused* canOpenURLs = + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"LSApplicationQueriesSchemes"]; + NSAssert(canOpenURLs, @"plist missing LSApplicationQueriesSchemes key"); + NSAssert1([canOpenURLs containsObject:_canOpenURLScheme], + @"plist missing LSApplicationQueriesSchemes entry for '%@'", _canOpenURLScheme); + + // Opens AppStore if app isn't installed + NSString *testURLString = [NSString stringWithFormat:@"%@://example.com", _canOpenURLScheme]; + NSURL *testURL = [NSURL URLWithString:testURLString]; + if (![[UIApplication sharedApplication] canOpenURL:testURL]) { + [[UIApplication sharedApplication] openURL:_appStoreURL]; + return NO; + } + } + + // Transforms the request URL and opens it. + NSURL *requestURL = [request externalUserAgentRequestURL]; + requestURL = _URLTransformation(requestURL); + BOOL openedInBrowser = [[UIApplication sharedApplication] openURL:requestURL]; + return openedInBrowser; +} + +- (void)dismissExternalUserAgentAnimated:(BOOL)animated + completion:(nonnull void (^)(void))completion { + completion(); +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // !TARGET_OS_MACCATALYST + +#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST diff --git a/Pods/AppAuth/Source/AppAuthCore.h b/Pods/AppAuth/Source/AppAuthCore.h new file mode 100644 index 00000000..c30af464 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore.h @@ -0,0 +1,44 @@ +/*! @file AppAuthCore.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDAuthState.h" +#import "OIDAuthStateChangeDelegate.h" +#import "OIDAuthStateErrorDelegate.h" +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDAuthorizationService.h" +#import "OIDError.h" +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentRequest.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDGrantTypes.h" +#import "OIDIDToken.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDResponseTypes.h" +#import "OIDScopes.h" +#import "OIDScopeUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDTokenUtilities.h" +#import "OIDURLSessionProvider.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthState.h b/Pods/AppAuth/Source/AppAuthCore/OIDAuthState.h new file mode 100644 index 00000000..68697d2c --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthState.h @@ -0,0 +1,272 @@ +/*! @file OIDAuthState.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +@class OIDAuthorizationRequest; +@class OIDAuthorizationResponse; +@class OIDAuthState; +@class OIDRegistrationResponse; +@class OIDTokenResponse; +@class OIDTokenRequest; +@protocol OIDAuthStateChangeDelegate; +@protocol OIDAuthStateErrorDelegate; +@protocol OIDExternalUserAgent; +@protocol OIDExternalUserAgentSession; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents a block used to call an action with a fresh access token. + @param accessToken A valid access token if available. + @param idToken A valid ID token if available. + @param error The error if an error occurred. + */ +typedef void (^OIDAuthStateAction)(NSString *_Nullable accessToken, + NSString *_Nullable idToken, + NSError *_Nullable error); + +/*! @brief The method called when the @c + OIDAuthState.authStateByPresentingAuthorizationRequest:presentingViewController:callback: + method has completed or failed. + @param authState The auth state, if the authorization request succeeded. + @param error The error if an error occurred. + */ +typedef void (^OIDAuthStateAuthorizationCallback)(OIDAuthState *_Nullable authState, + NSError *_Nullable error); + +/*! @brief A convenience class that retains the auth state between @c OIDAuthorizationResponse%s + and @c OIDTokenResponse%s. + */ +@interface OIDAuthState : NSObject + +/*! @brief The most recent refresh token received from the server. + @discussion Rather than using this property directly, you should call + @c OIDAuthState.performActionWithFreshTokens:. + @remarks refresh_token + @see https://tools.ietf.org/html/rfc6749#section-5.1 + */ +@property(nonatomic, readonly, nullable) NSString *refreshToken; + +/*! @brief The scope of the current authorization grant. + @discussion This represents the latest scope returned by the server and may be a subset of the + scope that was initially granted. + @remarks scope + */ +@property(nonatomic, readonly, nullable) NSString *scope; + +/*! @brief The most recent authorization response used to update the authorization state. For the + implicit flow, this will contain the latest access token. + */ +@property(nonatomic, readonly) OIDAuthorizationResponse *lastAuthorizationResponse; + +/*! @brief The most recent token response used to update this authorization state. This will + contain the latest access token. + */ +@property(nonatomic, readonly, nullable) OIDTokenResponse *lastTokenResponse; + +/*! @brief The most recent registration response used to update this authorization state. This will + contain the latest client credentials. + */ +@property(nonatomic, readonly, nullable) OIDRegistrationResponse *lastRegistrationResponse; + +/*! @brief The authorization error that invalidated this @c OIDAuthState. + @discussion The authorization error encountered by @c OIDAuthState or set by the user via + @c OIDAuthState.updateWithAuthorizationError: that invalidated this @c OIDAuthState. + Authorization errors from @c OIDAuthState will always have a domain of + @c ::OIDOAuthAuthorizationErrorDomain or @c ::OIDOAuthTokenErrorDomain. Note: that after + unarchiving the @c OIDAuthState object, the \NSError_userInfo property of this error will + be nil. + */ +@property(nonatomic, readonly, nullable) NSError *authorizationError; + +/*! @brief Returns YES if the authorization state is not known to be invalid. + @discussion Returns YES if no OAuth errors have been received, and the last call resulted in a + successful access token or id token. This does not mean that the access is fresh - just + that it was valid the last time it was used. Note that network and other transient errors + do not invalidate the authorized state. If NO, you should authenticate the user again, + using a fresh authorization request. Invalid @c OIDAuthState objects may still be useful in + that case, to hint at the previously authorized user and streamline the re-authentication + experience. + */ +@property(nonatomic, readonly) BOOL isAuthorized; + +/*! @brief The @c OIDAuthStateChangeDelegate delegate. + @discussion Use the delegate to observe state changes (and update storage) as well as error + states. + */ +@property(nonatomic, weak, nullable) id stateChangeDelegate; + +/*! @brief The @c OIDAuthStateErrorDelegate delegate. + @discussion Use the delegate to observe state changes (and update storage) as well as error + states. + */ +@property(nonatomic, weak, nullable) id errorDelegate; + +/*! @brief Convenience method to create a @c OIDAuthState by presenting an authorization request + and performing the authorization code exchange in the case of code flow requests. For + the hybrid flow, the caller should validate the id_token and c_hash, then perform the token + request (@c OIDAuthorizationService.performTokenRequest:callback:) + and update the OIDAuthState with the results (@c + OIDAuthState.updateWithTokenResponse:error:). + @param authorizationRequest The authorization request to present. + @param externalUserAgent A external user agent that can present an external user-agent request. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + externalUserAgent:(id)externalUserAgent + callback:(OIDAuthStateAuthorizationCallback)callback; + +/*! @internal + @brief Unavailable. Please use @c initWithAuthorizationResponse:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Creates an auth state from an authorization response. + @param authorizationResponse The authorization response. + */ +- (instancetype)initWithAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse; + +/*! @brief Creates an auth state from an authorization and token response. + @param authorizationResponse The authorization response. + @param tokenResponse The token response. + */ +- (instancetype)initWithAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse + tokenResponse:(nullable OIDTokenResponse *)tokenResponse; + +/*! @brief Creates an auth state from an registration response. + @param registrationResponse The registration response. + */ +- (instancetype)initWithRegistrationResponse:(OIDRegistrationResponse *)registrationResponse; + +/*! @brief Creates an auth state from an authorization, token and registration response. + @param authorizationResponse The authorization response. + @param tokenResponse The token response. + @param registrationResponse The registration response. + */ +- (instancetype)initWithAuthorizationResponse: + (nullable OIDAuthorizationResponse *)authorizationResponse + tokenResponse:(nullable OIDTokenResponse *)tokenResponse + registrationResponse:(nullable OIDRegistrationResponse *)registrationResponse + NS_DESIGNATED_INITIALIZER; + +/*! @brief Updates the authorization state based on a new authorization response. + @param authorizationResponse The new authorization response to update the state with. + @param error Any error encountered when performing the authorization request. Errors in the + domain @c ::OIDOAuthAuthorizationErrorDomain are reflected in the auth state, other errors + are assumed to be transient, and ignored. + @discussion Typically called with the response from an incremental authorization request, + or if using the implicit flow. Will clear the @c #lastTokenResponse property. + */ +- (void)updateWithAuthorizationResponse:(nullable OIDAuthorizationResponse *)authorizationResponse + error:(nullable NSError *)error; + +/*! @brief Updates the authorization state based on a new token response. + @param tokenResponse The new token response to update the state from. + @param error Any error encountered when performing the authorization request. Errors in the + domain @c ::OIDOAuthTokenErrorDomain are reflected in the auth state, other errors + are assumed to be transient, and ignored. + @discussion Typically called with the response from an authorization code exchange, or a token + refresh. + */ +- (void)updateWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse + error:(nullable NSError *)error; + +/*! @brief Updates the authorization state based on a new registration response. + @param registrationResponse The new registration response to update the state with. + @discussion Typically called with the response from a successful client registration + request. Will reset the auth state. + */ +- (void)updateWithRegistrationResponse:(nullable OIDRegistrationResponse *)registrationResponse; + +/*! @brief Updates the authorization state based on an authorization error. + @param authorizationError The authorization error. + @discussion Call this method if you receive an authorization error during an API call to + invalidate the authentication state of this @c OIDAuthState. Don't call with errors + unrelated to authorization, such as transient network errors. + The OIDAuthStateErrorDelegate.authState:didEncounterAuthorizationError: method of + @c #errorDelegate will be called with the error. + You may optionally use the convenience method + OIDErrorUtilities.resourceServerAuthorizationErrorWithCode:errorResponse:underlyingError: + to create \NSError objects for use here. + The latest error received is stored in @c #authorizationError. Note: that after unarchiving + this object, the \NSError_userInfo property of this error will be nil. + */ +- (void)updateWithAuthorizationError:(NSError *)authorizationError; + +/*! @brief Calls the block with a valid access token (refreshing it first, if needed), or if a + refresh was needed and failed, with the error that caused it to fail. + @param action The block to execute with a fresh token. This block will be executed on the main + thread. + */ +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action; + +/*! @brief Calls the block with a valid access token (refreshing it first, if needed), or if a + refresh was needed and failed, with the error that caused it to fail. + @param action The block to execute with a fresh token. This block will be executed on the main + thread. + @param additionalParameters Additional parameters for the token request if token is + refreshed. + */ +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action + additionalRefreshParameters: + (nullable NSDictionary *)additionalParameters; + +/*! @brief Calls the block with a valid access token (refreshing it first, if needed), or if a + refresh was needed and failed, with the error that caused it to fail. + @param action The block to execute with a fresh token. This block will be executed on the main + thread. + @param additionalParameters Additional parameters for the token request if token is + refreshed. + @param dispatchQueue The dispatchQueue on which to dispatch the action block. + */ +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action + additionalRefreshParameters: + (nullable NSDictionary *)additionalParameters + dispatchQueue:(dispatch_queue_t)dispatchQueue; + +/*! @brief Forces a token refresh the next time @c OIDAuthState.performActionWithFreshTokens: is + called, even if the current tokens are considered valid. + */ +- (void)setNeedsTokenRefresh; + +/*! @brief Creates a token request suitable for refreshing an access token. + @return A @c OIDTokenRequest suitable for using a refresh token to obtain a new access token. + @discussion After performing the refresh, call @c OIDAuthState.updateWithTokenResponse:error: + to update the authorization state based on the response. Rather than doing the token refresh + yourself, you should use @c OIDAuthState.performActionWithFreshTokens:. + @see https://tools.ietf.org/html/rfc6749#section-1.5 + */ +- (nullable OIDTokenRequest *)tokenRefreshRequest; + +/*! @brief Creates a token request suitable for refreshing an access token. + @param additionalParameters Additional parameters for the token request. + @return A @c OIDTokenRequest suitable for using a refresh token to obtain a new access token. + @discussion After performing the refresh, call @c OIDAuthState.updateWithTokenResponse:error: + to update the authorization state based on the response. Rather than doing the token refresh + yourself, you should use @c OIDAuthState.performActionWithFreshTokens:. + @see https://tools.ietf.org/html/rfc6749#section-1.5 + */ +- (nullable OIDTokenRequest *)tokenRefreshRequestWithAdditionalParameters: + (nullable NSDictionary *)additionalParameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthState.m b/Pods/AppAuth/Source/AppAuthCore/OIDAuthState.m new file mode 100644 index 00000000..fe8a1622 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthState.m @@ -0,0 +1,570 @@ +/*! @file OIDAuthState.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDAuthState.h" + +#import "OIDAuthStateChangeDelegate.h" +#import "OIDAuthStateErrorDelegate.h" +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDAuthorizationService.h" +#import "OIDDefines.h" +#import "OIDError.h" +#import "OIDErrorUtilities.h" +#import "OIDRegistrationResponse.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDTokenUtilities.h" + +/*! @brief Key used to encode the @c refreshToken property for @c NSSecureCoding. + */ +static NSString *const kRefreshTokenKey = @"refreshToken"; + +/*! @brief Key used to encode the @c needsTokenRefresh property for @c NSSecureCoding. + */ +static NSString *const kNeedsTokenRefreshKey = @"needsTokenRefresh"; + +/*! @brief Key used to encode the @c scope property for @c NSSecureCoding. + */ +static NSString *const kScopeKey = @"scope"; + +/*! @brief Key used to encode the @c lastAuthorizationResponse property for @c NSSecureCoding. + */ +static NSString *const kLastAuthorizationResponseKey = @"lastAuthorizationResponse"; + +/*! @brief Key used to encode the @c lastTokenResponse property for @c NSSecureCoding. + */ +static NSString *const kLastTokenResponseKey = @"lastTokenResponse"; + +/*! @brief Key used to encode the @c lastOAuthError property for @c NSSecureCoding. + */ +static NSString *const kAuthorizationErrorKey = @"authorizationError"; + +/*! @brief The exception thrown when a developer tries to create a refresh request from an + authorization request with no authorization code. + */ +static NSString *const kRefreshTokenRequestException = + @"Attempted to create a token refresh request from a token response with no refresh token."; + +/*! @brief Number of seconds the access token is refreshed before it actually expires. + */ +static const NSUInteger kExpiryTimeTolerance = 60; + +/*! @brief Object to hold OIDAuthState pending actions. + */ +@interface OIDAuthStatePendingAction : NSObject +@property(nonatomic, readonly, nullable) OIDAuthStateAction action; +@property(nonatomic, readonly, nullable) dispatch_queue_t dispatchQueue; +@end +@implementation OIDAuthStatePendingAction +- (id)initWithAction:(OIDAuthStateAction)action andDispatchQueue:(dispatch_queue_t)dispatchQueue { + self = [super init]; + if (self) { + _action = action; + _dispatchQueue = dispatchQueue; + } + return self; +} +@end + +@interface OIDAuthState () + +/*! @brief The access token generated by the authorization server. + @discussion Rather than using this property directly, you should call + @c OIDAuthState.withFreshTokenPerformAction:. + */ +@property(nonatomic, readonly, nullable) NSString *accessToken; + +/*! @brief The approximate expiration date & time of the access token. + @discussion Rather than using this property directly, you should call + @c OIDAuthState.withFreshTokenPerformAction:. + */ +@property(nonatomic, readonly, nullable) NSDate *accessTokenExpirationDate; + +/*! @brief ID Token value associated with the authenticated session. + @discussion Rather than using this property directly, you should call + OIDAuthState.withFreshTokenPerformAction:. + */ +@property(nonatomic, readonly, nullable) NSString *idToken; + +/*! @brief Private method, called when the internal state changes. + */ +- (void)didChangeState; + +@end + + +@implementation OIDAuthState { + /*! @brief Array of pending actions (use @c _pendingActionsSyncObject to synchronize access). + */ + NSMutableArray *_pendingActions; + + /*! @brief Object for synchronizing access to @c pendingActions. + */ + id _pendingActionsSyncObject; + + /*! @brief If YES, tokens will be refreshed on the next API call regardless of expiry. + */ + BOOL _needsTokenRefresh; +} + +#pragma mark - Convenience initializers + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + externalUserAgent:(id)externalUserAgent + callback:(OIDAuthStateAuthorizationCallback)callback { + // presents the authorization request + id authFlowSession = [OIDAuthorizationService + presentAuthorizationRequest:authorizationRequest + externalUserAgent:externalUserAgent + callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, + NSError *_Nullable authorizationError) { + // inspects response and processes further if needed (e.g. authorization + // code exchange) + if (authorizationResponse) { + if ([authorizationRequest.responseType + isEqualToString:OIDResponseTypeCode]) { + // if the request is for the code flow (NB. not hybrid), assumes the + // code is intended for this client, and performs the authorization + // code exchange + OIDTokenRequest *tokenExchangeRequest = + [authorizationResponse tokenExchangeRequest]; + [OIDAuthorizationService performTokenRequest:tokenExchangeRequest + originalAuthorizationResponse:authorizationResponse + callback:^(OIDTokenResponse *_Nullable tokenResponse, + NSError *_Nullable tokenError) { + OIDAuthState *authState; + if (tokenResponse) { + authState = [[OIDAuthState alloc] + initWithAuthorizationResponse: + authorizationResponse + tokenResponse:tokenResponse]; + } + callback(authState, tokenError); + }]; + } else { + // hybrid flow (code id_token). Two possible cases: + // 1. The code is not for this client, ie. will be sent to a + // webservice that performs the id token verification and token + // exchange + // 2. The code is for this client and, for security reasons, the + // application developer must verify the id_token signature and + // c_hash before calling the token endpoint + OIDAuthState *authState = [[OIDAuthState alloc] + initWithAuthorizationResponse:authorizationResponse]; + callback(authState, authorizationError); + } + } else { + callback(nil, authorizationError); + } + }]; + return authFlowSession; +} + +#pragma mark - Initializers + +- (nonnull instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithAuthorizationResponse:tokenResponse:)) + +/*! @brief Creates an auth state from an authorization response. + @param authorizationResponse The authorization response. + */ +- (instancetype)initWithAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse { + return [self initWithAuthorizationResponse:authorizationResponse tokenResponse:nil]; +} + + +/*! @brief Designated initializer. + @param authorizationResponse The authorization response. + @discussion Creates an auth state from an authorization response and token response. + */ +- (instancetype)initWithAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse + tokenResponse:(nullable OIDTokenResponse *)tokenResponse { + return [self initWithAuthorizationResponse:authorizationResponse + tokenResponse:tokenResponse + registrationResponse:nil]; +} + +/*! @brief Creates an auth state from an registration response. + @param registrationResponse The registration response. + */ +- (instancetype)initWithRegistrationResponse:(OIDRegistrationResponse *)registrationResponse { + return [self initWithAuthorizationResponse:nil + tokenResponse:nil + registrationResponse:registrationResponse]; +} + +- (instancetype)initWithAuthorizationResponse: + (nullable OIDAuthorizationResponse *)authorizationResponse + tokenResponse:(nullable OIDTokenResponse *)tokenResponse + registrationResponse:(nullable OIDRegistrationResponse *)registrationResponse { + self = [super init]; + if (self) { + _pendingActionsSyncObject = [[NSObject alloc] init]; + + if (registrationResponse) { + [self updateWithRegistrationResponse:registrationResponse]; + } + + if (authorizationResponse) { + [self updateWithAuthorizationResponse:authorizationResponse error:nil]; + } + + if (tokenResponse) { + [self updateWithTokenResponse:tokenResponse error:nil]; + } + } + return self; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, isAuthorized: %@, refreshToken: \"%@\", " + "scope: \"%@\", accessToken: \"%@\", " + "accessTokenExpirationDate: %@, idToken: \"%@\", " + "lastAuthorizationResponse: %@, lastTokenResponse: %@, " + "lastRegistrationResponse: %@, authorizationError: %@>", + NSStringFromClass([self class]), + (void *)self, + (self.isAuthorized) ? @"YES" : @"NO", + [OIDTokenUtilities redact:_refreshToken], + _scope, + [OIDTokenUtilities redact:self.accessToken], + self.accessTokenExpirationDate, + [OIDTokenUtilities redact:self.idToken], + _lastAuthorizationResponse, + _lastTokenResponse, + _lastRegistrationResponse, + _authorizationError]; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + _lastAuthorizationResponse = [aDecoder decodeObjectOfClass:[OIDAuthorizationResponse class] + forKey:kLastAuthorizationResponseKey]; + _lastTokenResponse = [aDecoder decodeObjectOfClass:[OIDTokenResponse class] + forKey:kLastTokenResponseKey]; + self = [self initWithAuthorizationResponse:_lastAuthorizationResponse + tokenResponse:_lastTokenResponse]; + if (self) { + _authorizationError = + [aDecoder decodeObjectOfClass:[NSError class] forKey:kAuthorizationErrorKey]; + _scope = [aDecoder decodeObjectOfClass:[NSString class] forKey:kScopeKey]; + _refreshToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:kRefreshTokenKey]; + _needsTokenRefresh = [aDecoder decodeBoolForKey:kNeedsTokenRefreshKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_lastAuthorizationResponse forKey:kLastAuthorizationResponseKey]; + [aCoder encodeObject:_lastTokenResponse forKey:kLastTokenResponseKey]; + if (_authorizationError) { + NSError *codingSafeAuthorizationError = [NSError errorWithDomain:_authorizationError.domain + code:_authorizationError.code + userInfo:nil]; + [aCoder encodeObject:codingSafeAuthorizationError forKey:kAuthorizationErrorKey]; + } + [aCoder encodeObject:_scope forKey:kScopeKey]; + [aCoder encodeObject:_refreshToken forKey:kRefreshTokenKey]; + [aCoder encodeBool:_needsTokenRefresh forKey:kNeedsTokenRefreshKey]; +} + +#pragma mark - Private convenience getters + +- (NSString *)accessToken { + if (_authorizationError) { + return nil; + } + return _lastTokenResponse ? _lastTokenResponse.accessToken + : _lastAuthorizationResponse.accessToken; +} + +- (NSString *)tokenType { + if (_authorizationError) { + return nil; + } + return _lastTokenResponse ? _lastTokenResponse.tokenType + : _lastAuthorizationResponse.tokenType; +} + +- (NSDate *)accessTokenExpirationDate { + if (_authorizationError) { + return nil; + } + return _lastTokenResponse ? _lastTokenResponse.accessTokenExpirationDate + : _lastAuthorizationResponse.accessTokenExpirationDate; +} + +- (NSString *)idToken { + if (_authorizationError) { + return nil; + } + return _lastTokenResponse ? _lastTokenResponse.idToken + : _lastAuthorizationResponse.idToken; +} + +#pragma mark - Getters + +- (BOOL)isAuthorized { + return !self.authorizationError && (self.accessToken || self.idToken || self.refreshToken); +} + +#pragma mark - Updating the state + +- (void)updateWithRegistrationResponse:(OIDRegistrationResponse *)registrationResponse { + _lastRegistrationResponse = registrationResponse; + _refreshToken = nil; + _scope = nil; + _lastAuthorizationResponse = nil; + _lastTokenResponse = nil; + _authorizationError = nil; + [self didChangeState]; +} + +- (void)updateWithAuthorizationResponse:(nullable OIDAuthorizationResponse *)authorizationResponse + error:(nullable NSError *)error { + // If the error is an OAuth authorization error, updates the state. Other errors are ignored. + if (error.domain == OIDOAuthAuthorizationErrorDomain) { + [self updateWithAuthorizationError:error]; + return; + } + if (!authorizationResponse) { + return; + } + + _lastAuthorizationResponse = authorizationResponse; + + // clears the last token response and refresh token as these now relate to an old authorization + // that is no longer relevant + _lastTokenResponse = nil; + _refreshToken = nil; + _authorizationError = nil; + + // if the response's scope is nil, it means that it equals that of the request + // see: https://tools.ietf.org/html/rfc6749#section-5.1 + _scope = (authorizationResponse.scope) ? authorizationResponse.scope + : authorizationResponse.request.scope; + + [self didChangeState]; +} + +- (void)updateWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse + error:(nullable NSError *)error { + if (_authorizationError) { + // Calling updateWithTokenResponse while in an error state probably means the developer obtained + // a new token and did the exchange without also calling updateWithAuthorizationResponse. + // Attempts to handle gracefully, but warns the developer that this is unexpected. + NSLog(@"OIDAuthState:updateWithTokenResponse should not be called in an error state [%@] call" + "updateWithAuthorizationResponse with the result of the fresh authorization response" + "first", + _authorizationError); + + _authorizationError = nil; + } + + // If the error is an OAuth authorization error, updates the state. Other errors are ignored. + if (error.domain == OIDOAuthTokenErrorDomain) { + [self updateWithAuthorizationError:error]; + return; + } + if (!tokenResponse) { + return; + } + + _lastTokenResponse = tokenResponse; + + // updates the scope and refresh token if they are present on the TokenResponse. + // according to the spec, these may be changed by the server, including when refreshing the + // access token. See: https://tools.ietf.org/html/rfc6749#section-5.1 and + // https://tools.ietf.org/html/rfc6749#section-6 + if (tokenResponse.scope) { + _scope = tokenResponse.scope; + } + if (tokenResponse.refreshToken) { + _refreshToken = tokenResponse.refreshToken; + } + + [self didChangeState]; +} + +- (void)updateWithAuthorizationError:(NSError *)oauthError { + _authorizationError = oauthError; + + [self didChangeState]; + + [_errorDelegate authState:self didEncounterAuthorizationError:oauthError]; +} + +#pragma mark - OAuth Requests + +- (OIDTokenRequest *)tokenRefreshRequest { + return [self tokenRefreshRequestWithAdditionalParameters:nil]; +} + +- (OIDTokenRequest *)tokenRefreshRequestWithAdditionalParameters: + (NSDictionary *)additionalParameters { + + // TODO: Add unit test to confirm exception is thrown when expected + + if (!_refreshToken) { + [OIDErrorUtilities raiseException:kRefreshTokenRequestException]; + } + return [[OIDTokenRequest alloc] + initWithConfiguration:_lastAuthorizationResponse.request.configuration + grantType:OIDGrantTypeRefreshToken + authorizationCode:nil + redirectURL:nil + clientID:_lastAuthorizationResponse.request.clientID + clientSecret:_lastAuthorizationResponse.request.clientSecret + scope:nil + refreshToken:_refreshToken + codeVerifier:nil + additionalParameters:additionalParameters]; +} + +#pragma mark - Stateful Actions + +- (void)didChangeState { + [_stateChangeDelegate didChangeState:self]; +} + +- (void)setNeedsTokenRefresh { + _needsTokenRefresh = YES; +} + +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action { + [self performActionWithFreshTokens:action additionalRefreshParameters:nil]; +} + +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action + additionalRefreshParameters: + (nullable NSDictionary *)additionalParameters { + [self performActionWithFreshTokens:action + additionalRefreshParameters:additionalParameters + dispatchQueue:dispatch_get_main_queue()]; +} + +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action + additionalRefreshParameters: + (nullable NSDictionary *)additionalParameters + dispatchQueue:(dispatch_queue_t)dispatchQueue { + + if ([self isTokenFresh]) { + // access token is valid within tolerance levels, perform action + dispatch_async(dispatchQueue, ^{ + action(self.accessToken, self.idToken, nil); + }); + return; + } + + if (!_refreshToken) { + // no refresh token available and token has expired + NSError *tokenRefreshError = [ + OIDErrorUtilities errorWithCode:OIDErrorCodeTokenRefreshError + underlyingError:nil + description:@"Unable to refresh expired token without a refresh token."]; + dispatch_async(dispatchQueue, ^{ + action(nil, nil, tokenRefreshError); + }); + return; + } + + // access token is expired, first refresh the token, then perform action + NSAssert(_pendingActionsSyncObject, @"_pendingActionsSyncObject cannot be nil", @""); + OIDAuthStatePendingAction* pendingAction = + [[OIDAuthStatePendingAction alloc] initWithAction:action andDispatchQueue:dispatchQueue]; + @synchronized(_pendingActionsSyncObject) { + // if a token is already in the process of being refreshed, adds to pending actions + if (_pendingActions) { + [_pendingActions addObject:pendingAction]; + return; + } + + // creates a list of pending actions, starting with this one + _pendingActions = [NSMutableArray arrayWithObject:pendingAction]; + } + + // refresh the tokens + OIDTokenRequest *tokenRefreshRequest = + [self tokenRefreshRequestWithAdditionalParameters:additionalParameters]; + [OIDAuthorizationService performTokenRequest:tokenRefreshRequest + originalAuthorizationResponse:_lastAuthorizationResponse + callback:^(OIDTokenResponse *_Nullable response, + NSError *_Nullable error) { + // update OIDAuthState based on response + if (response) { + self->_needsTokenRefresh = NO; + [self updateWithTokenResponse:response error:nil]; + } else { + if (error.domain == OIDOAuthTokenErrorDomain) { + self->_needsTokenRefresh = NO; + [self updateWithAuthorizationError:error]; + } else { + if ([self->_errorDelegate respondsToSelector: + @selector(authState:didEncounterTransientError:)]) { + [self->_errorDelegate authState:self didEncounterTransientError:error]; + } + } + } + + // nil the pending queue and process everything that was queued up + NSArray *actionsToProcess; + @synchronized(self->_pendingActionsSyncObject) { + actionsToProcess = self->_pendingActions; + self->_pendingActions = nil; + } + for (OIDAuthStatePendingAction* actionToProcess in actionsToProcess) { + dispatch_async(actionToProcess.dispatchQueue, ^{ + actionToProcess.action(self.accessToken, self.idToken, error); + }); + } + }]; +} + +#pragma mark - + +/*! @fn isTokenFresh + @brief Determines whether a token refresh request must be made to refresh the tokens. + */ +- (BOOL)isTokenFresh { + if (_needsTokenRefresh) { + // forced refresh + return NO; + } + + if (!self.accessTokenExpirationDate) { + // if there is no expiration time but we have an access token, it is assumed to never expire + return !!self.accessToken; + } + + // has the token expired? + BOOL tokenFresh = [self.accessTokenExpirationDate timeIntervalSinceNow] > kExpiryTimeTolerance; + return tokenFresh; +} + +@end + + diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthStateChangeDelegate.h b/Pods/AppAuth/Source/AppAuthCore/OIDAuthStateChangeDelegate.h new file mode 100644 index 00000000..2570df13 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthStateChangeDelegate.h @@ -0,0 +1,39 @@ +/*! @file OIDAuthStateChangeDelegate.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +@class OIDAuthState; + +NS_ASSUME_NONNULL_BEGIN + +/*! @protocol OIDAuthStateChangeDelegate + @brief Delegate of the OIDAuthState used to monitor various changes in state. + */ +@protocol OIDAuthStateChangeDelegate + +/*! @brief Called when the authorization state changes and any backing storage needs to be updated. + @param state The @c OIDAuthState that changed. + @discussion If you are storing the authorization state, you should update the storage when the + state changes. + */ +- (void)didChangeState:(OIDAuthState *)state; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthStateErrorDelegate.h b/Pods/AppAuth/Source/AppAuthCore/OIDAuthStateErrorDelegate.h new file mode 100644 index 00000000..91a9b1cd --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthStateErrorDelegate.h @@ -0,0 +1,62 @@ +/*! @file OIDAuthStateErrorDelegate.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +@class OIDAuthState; + +NS_ASSUME_NONNULL_BEGIN + +/*! @protocol OIDAuthStateErrorDelegate + @brief Delegate of the OIDAuthState used to monitor errors. + */ +@protocol OIDAuthStateErrorDelegate + +/*! @brief Called when an authentication occurs, which indicates the auth session is invalid. + @param state The @c OIDAuthState on which the error occurred. + @param error The authorization error. + @discussion This is a hard error (not a transient network issue) that indicates a problem with + the authorization. You should stop using the @c OIDAuthState when such an error is + encountered. If the \NSError_code is @c ::OIDErrorCodeOAuthInvalidGrant then + the session may be recoverable with user interaction (i.e. re-authentication). In all cases + you should consider the user unauthorized, and remove locally cached resources that require + that authorization. @c OIDAuthState will call this method automatically if it encounters + an OAuth error (that is, an HTTP 400 response with a valid OAuth error response) during + authorization or token refresh (such as performed automatically when using + @c OIDAuthState.performActionWithFreshTokens:). You can signal authorization errors with + @c OIDAuthState.updateWithAuthorizationError:. + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ +- (void)authState:(OIDAuthState *)state didEncounterAuthorizationError:(NSError *)error; + +@optional + +/*! @brief Called when a network or other transient error occurs. + @param state The @c OIDAuthState on which the error occurred. + @param error The transient error. + @discussion This is a soft error, typically network related. The @c OIDAuthState is likely + still valid, and should not be discarded. Retry the request using an incremental backoff + strategy. This is only called when using the @c OIDAuthState convenience methods such as + @c OIDAuthState.performActionWithFreshTokens:. If you are refreshing the tokens yourself + outside of @c OIDAuthState class, it will never be called. + */ +- (void)authState:(OIDAuthState *)state didEncounterTransientError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationRequest.h b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationRequest.h new file mode 100644 index 00000000..594f01d8 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationRequest.h @@ -0,0 +1,250 @@ +/*! @file OIDAuthorizationRequest.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +// These files only declare string constants useful for constructing a @c OIDAuthorizationRequest, +// so they are imported here for convenience. +#import "OIDExternalUserAgentRequest.h" +#import "OIDResponseTypes.h" +#import "OIDScopes.h" + +@class OIDServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief The @c code_challenge_method value for the S256 code challenge. + @see https://tools.ietf.org/html/rfc7636#section-4.3 + */ +extern NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256; + + +/*! @brief Represents an authorization request. + @see https://tools.ietf.org/html/rfc6749#section-4 + @see https://tools.ietf.org/html/rfc6749#section-4.1.1 + */ +@interface OIDAuthorizationRequest : + NSObject + +/*! @brief The service's configuration. + @remarks This configuration specifies how to connect to a particular OAuth provider. + Configurations may be created manually, or via an OpenID Connect Discovery Document. + */ +@property(nonatomic, readonly) OIDServiceConfiguration *configuration; + +/*! @brief The expected response type. + @remarks response_type + @discussion Generally 'code' if pure OAuth, otherwise a space-delimited list of of response + types including 'code', 'token', and 'id_token' for OpenID Connect. + @see https://tools.ietf.org/html/rfc6749#section-3.1.1 + @see http://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3 + */ +@property(nonatomic, readonly) NSString *responseType; + +/*! @brief The client identifier. + @remarks client_id + @see https://tools.ietf.org/html/rfc6749#section-2.2 + */ +@property(nonatomic, readonly) NSString *clientID; + +/*! @brief The client secret. + @remarks client_secret + @discussion The client secret is used to prove that identity of the client when exchaning an + authorization code for an access token. + The client secret is not passed in the authorizationRequestURL. It is only used when + exchanging the authorization code for an access token. + @see https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ +@property(nonatomic, readonly, nullable) NSString *clientSecret; + +/*! @brief The value of the scope parameter is expressed as a list of space-delimited, + case-sensitive strings. + @remarks scope + @see https://tools.ietf.org/html/rfc6749#section-3.3 + */ +@property(nonatomic, readonly, nullable) NSString *scope; + +/*! @brief The client's redirect URI. + @remarks redirect_uri + @see https://tools.ietf.org/html/rfc6749#section-3.1.2 + */ +@property(nonatomic, readonly, nullable) NSURL *redirectURL; + +/*! @brief An opaque value used by the client to maintain state between the request and callback. + @remarks state + @discussion If this value is not explicitly set, this library will automatically add state and + perform appropriate validation of the state in the authorization response. It is recommended + that the default implementation of this parameter be used wherever possible. Typically used + to prevent CSRF attacks, as recommended in RFC6819 Section 5.3.5. + @see https://tools.ietf.org/html/rfc6749#section-4.1.1 + @see https://tools.ietf.org/html/rfc6819#section-5.3.5 + */ +@property(nonatomic, readonly, nullable) NSString *state; + +/*! @brief String value used to associate a Client session with an ID Token, and to mitigate replay + attacks. The value is passed through unmodified from the Authentication Request to the ID + Token. Sufficient entropy MUST be present in the nonce values used to prevent attackers from + guessing values. + @remarks nonce + @discussion If this value is not explicitly set, this library will automatically add nonce and + perform appropriate validation of the nonce in the ID Token. + @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest + */ +@property(nonatomic, readonly, nullable) NSString *nonce; + +/*! @brief The PKCE code verifier. + @remarks code_verifier + @discussion The code verifier itself is not included in the authorization request that is sent + on the wire, but needs to be in the token exchange request. + @c OIDAuthorizationResponse.tokenExchangeRequest will create a @c OIDTokenRequest that + includes this parameter automatically. + @see https://tools.ietf.org/html/rfc7636#section-4.1 + */ +@property(nonatomic, readonly, nullable) NSString *codeVerifier; + +/*! @brief The PKCE code challenge, derived from #codeVerifier. + @remarks code_challenge + @see https://tools.ietf.org/html/rfc7636#section-4.2 + */ +@property(nonatomic, readonly, nullable) NSString *codeChallenge; + +/*! @brief The method used to compute the @c #codeChallenge + @remarks code_challenge_method + @see https://tools.ietf.org/html/rfc7636#section-4.3 + */ +@property(nonatomic, readonly, nullable) NSString *codeChallengeMethod; + +/*! @brief The client's additional authorization parameters. + @see https://tools.ietf.org/html/rfc6749#section-3.1 + */ +@property(nonatomic, readonly, nullable) NSDictionary *additionalParameters; + +/*! @internal + @brief Unavailable. Please use + @c initWithConfiguration:clientId:scopes:redirectURL:responseType:additionalParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Creates an authorization request with opinionated defaults (a secure @c state, and + PKCE with S256 as the @c code_challenge_method). + @param configuration The service's configuration. + @param clientID The client identifier. + @param scopes An array of scopes to combine into a single scope string per the OAuth2 spec. + @param redirectURL The client's redirect URI. + @param responseType The expected response type. + @param additionalParameters The client's additional authorization parameters. + @remarks This convenience initializer generates a state parameter and PKCE challenges + automatically. + */ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + clientId:(NSString *)clientID + scopes:(nullable NSArray *)scopes + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Creates an authorization request with opinionated defaults (a secure @c state, @c nonce, + and PKCE with S256 as the @c code_challenge_method). + @param configuration The service's configuration. + @param clientID The client identifier. + @param clientSecret The client secret. + @param scopes An array of scopes to combine into a single scope string per the OAuth2 spec. + @param redirectURL The client's redirect URI. + @param responseType The expected response type. + @param additionalParameters The client's additional authorization parameters. + @remarks This convenience initializer generates a state parameter and PKCE challenges + automatically. + */ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scopes:(nullable NSArray *)scopes + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Designated initializer. + @param configuration The service's configuration. + @param clientID The client identifier. + @param scope A scope string per the OAuth2 spec (a space-delimited set of scopes). + @param redirectURL The client's redirect URI. + @param responseType The expected response type. + @param state An opaque value used by the client to maintain state between the request and + callback. + @param nonce String value used to associate a Client session with an ID Token. Can be set to nil + if not using OpenID Connect, although pure OAuth servers should ignore params they don't + understand anyway. + @param codeVerifier The PKCE code verifier. See @c OIDAuthorizationRequest.generateCodeVerifier. + @param codeChallenge The PKCE code challenge, calculated from the code verifier such as with + @c OIDAuthorizationRequest.codeChallengeS256ForVerifier:. + @param codeChallengeMethod The PKCE code challenge method. + ::OIDOAuthorizationRequestCodeChallengeMethodS256 when + @c OIDAuthorizationRequest.codeChallengeS256ForVerifier: is used to create the code + challenge. + @param additionalParameters The client's additional authorization parameters. + */ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + redirectURL:(nullable NSURL *)redirectURL + responseType:(NSString *)responseType + state:(nullable NSString *)state + nonce:(nullable NSString *)nonce + codeVerifier:(nullable NSString *)codeVerifier + codeChallenge:(nullable NSString *)codeChallenge + codeChallengeMethod:(nullable NSString *)codeChallengeMethod + additionalParameters:(nullable NSDictionary *)additionalParameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Constructs the request URI by adding the request parameters to the query component of the + authorization endpoint URI using the "application/x-www-form-urlencoded" format. + @return A URL representing the authorization request. + @see https://tools.ietf.org/html/rfc6749#section-4.1.1 + */ +- (NSURL *)authorizationRequestURL; + +/*! @brief Generates an OAuth state param using a random source. + @return The generated state. + @see https://tools.ietf.org/html/rfc6819#section-5.3.5 + */ ++ (nullable NSString *)generateState; + +/*! @brief Constructs a PKCE-compliant code verifier. + @return The generated code verifier. + @see https://tools.ietf.org/html/rfc7636#section-4.1 + */ ++ (nullable NSString *)generateCodeVerifier; + +/*! @brief Creates a PKCE S256 codeChallenge from the codeVerifier. + @param codeVerifier The code verifier from which the code challenge will be derived. + @return The generated code challenge. + @details Generate a secure code verifier to pass into this method with + @c OIDAuthorizationRequest.generateCodeVerifier. The matching @c #codeChallengeMethod for + @c #codeChallenge%s created by this method is + ::OIDOAuthorizationRequestCodeChallengeMethodS256. + @see https://tools.ietf.org/html/rfc7636#section-4.1 + */ ++ (nullable NSString *)codeChallengeS256ForVerifier:(nullable NSString *)codeVerifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationRequest.m b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationRequest.m new file mode 100644 index 00000000..ccfacda0 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationRequest.m @@ -0,0 +1,351 @@ +/*! @file OIDAuthorizationRequest.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDAuthorizationRequest.h" + +#import "OIDDefines.h" +#import "OIDScopeUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDTokenUtilities.h" +#import "OIDURLQueryComponent.h" + +/*! @brief The key for the @c configuration property for @c NSSecureCoding + */ +static NSString *const kConfigurationKey = @"configuration"; + +/*! @brief Key used to encode the @c responseType property for @c NSSecureCoding, and on the URL + request. + */ +static NSString *const kResponseTypeKey = @"response_type"; + +/*! @brief Key used to encode the @c clientID property for @c NSSecureCoding, and on the URL + request. + */ +static NSString *const kClientIDKey = @"client_id"; + +/*! @brief Key used to encode the @c clientSecret property for @c NSSecureCoding. + */ +static NSString *const kClientSecretKey = @"client_secret"; + +/*! @brief Key used to encode the @c scope property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kScopeKey = @"scope"; + +/*! @brief Key used to encode the @c redirectURL property for @c NSSecureCoding, and on the URL + request. + */ +static NSString *const kRedirectURLKey = @"redirect_uri"; + +/*! @brief Key used to encode the @c state property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kStateKey = @"state"; + +/*! @brief Key used to encode the @c nonce property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kNonceKey = @"nonce"; + +/*! @brief Key used to encode the @c codeVerifier property for @c NSSecureCoding. + */ +static NSString *const kCodeVerifierKey = @"code_verifier"; + +/*! @brief Key used to send the @c codeChallenge on the URL request. + */ +static NSString *const kCodeChallengeKey = @"code_challenge"; + +/*! @brief Key used to send the @c codeChallengeMethod on the URL request. + */ +static NSString *const kCodeChallengeMethodKey = @"code_challenge_method"; + +/*! @brief Key used to encode the @c additionalParameters property for + @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +/*! @brief Number of random bytes generated for the @ state. + */ +static NSUInteger const kStateSizeBytes = 32; + +/*! @brief Number of random bytes generated for the @ codeVerifier. + */ +static NSUInteger const kCodeVerifierBytes = 32; + +/*! @brief Assertion text for unsupported response types. + */ +static NSString *const OIDOAuthUnsupportedResponseTypeMessage = + @"The response_type \"%@\" isn't supported. AppAuth only supports the \"code\" or \"code id_token\" response_type."; + +/*! @brief Code challenge request method. + */ +NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256 = @"S256"; + +@implementation OIDAuthorizationRequest + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithConfiguration: + clientId: + scopes: + redirectURL: + responseType: + additionalParameters:) + ) + +/*! @brief Check if the response type is one AppAuth supports + @remarks AppAuth only supports the `code` and `code id_token` response types. + @see https://github.com/openid/AppAuth-iOS/issues/98 + @see https://github.com/openid/AppAuth-iOS/issues/292 + */ ++ (BOOL)isSupportedResponseType:(NSString *)responseType +{ + NSString *codeIdToken = [@[OIDResponseTypeCode, OIDResponseTypeIDToken] + componentsJoinedByString:@" "]; + NSString *idTokenCode = [@[OIDResponseTypeIDToken, OIDResponseTypeCode] + componentsJoinedByString:@" "]; + + return [responseType isEqualToString:OIDResponseTypeCode] + || [responseType isEqualToString:codeIdToken] + || [responseType isEqualToString:idTokenCode]; +} + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + state:(nullable NSString *)state + nonce:(nullable NSString *)nonce + codeVerifier:(nullable NSString *)codeVerifier + codeChallenge:(nullable NSString *)codeChallenge + codeChallengeMethod:(nullable NSString *)codeChallengeMethod + additionalParameters:(nullable NSDictionary *)additionalParameters +{ + self = [super init]; + if (self) { + _configuration = [configuration copy]; + _clientID = [clientID copy]; + _clientSecret = [clientSecret copy]; + _scope = [scope copy]; + _redirectURL = [redirectURL copy]; + _responseType = [responseType copy]; + if (![[self class] isSupportedResponseType:_responseType]) { + NSAssert(NO, OIDOAuthUnsupportedResponseTypeMessage, _responseType); + return nil; + } + _state = [state copy]; + _nonce = [nonce copy]; + _codeVerifier = [codeVerifier copy]; + _codeChallenge = [codeChallenge copy]; + _codeChallengeMethod = [codeChallengeMethod copy]; + + _additionalParameters = + [[NSDictionary alloc] initWithDictionary:additionalParameters copyItems:YES]; + } + return self; +} + +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(NSString *)clientSecret + scopes:(nullable NSArray *)scopes + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + additionalParameters:(nullable NSDictionary *)additionalParameters { + + // generates PKCE code verifier and challenge + NSString *codeVerifier = [[self class] generateCodeVerifier]; + NSString *codeChallenge = [[self class] codeChallengeS256ForVerifier:codeVerifier]; + + return [self initWithConfiguration:configuration + clientId:clientID + clientSecret:clientSecret + scope:[OIDScopeUtilities scopesWithArray:scopes] + redirectURL:redirectURL + responseType:responseType + state:[[self class] generateState] + nonce:[[self class] generateState] + codeVerifier:codeVerifier + codeChallenge:codeChallenge + codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256 + additionalParameters:additionalParameters]; +} + +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + clientId:(NSString *)clientID + scopes:(nullable NSArray *)scopes + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + additionalParameters:(nullable NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + clientId:clientID + clientSecret:nil + scopes:scopes + redirectURL:redirectURL + responseType:responseType + additionalParameters:additionalParameters]; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDServiceConfiguration *configuration = + [aDecoder decodeObjectOfClass:[OIDServiceConfiguration class] + forKey:kConfigurationKey]; + NSString *responseType = [aDecoder decodeObjectOfClass:[NSString class] forKey:kResponseTypeKey]; + NSString *clientID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kClientIDKey]; + NSString *clientSecret = [aDecoder decodeObjectOfClass:[NSString class] forKey:kClientSecretKey]; + NSString *scope = [aDecoder decodeObjectOfClass:[NSString class] forKey:kScopeKey]; + NSURL *redirectURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kRedirectURLKey]; + NSString *state = [aDecoder decodeObjectOfClass:[NSString class] forKey:kStateKey]; + NSString *nonce = [aDecoder decodeObjectOfClass:[NSString class] forKey:kNonceKey]; + NSString *codeVerifier = [aDecoder decodeObjectOfClass:[NSString class] forKey:kCodeVerifierKey]; + NSString *codeChallenge = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kCodeChallengeKey]; + NSString *codeChallengeMethod = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kCodeChallengeMethodKey]; + NSSet *additionalParameterCodingClasses = [NSSet setWithArray:@[ + [NSDictionary class], + [NSString class] + ]]; + NSDictionary *additionalParameters = + [aDecoder decodeObjectOfClasses:additionalParameterCodingClasses + forKey:kAdditionalParametersKey]; + + self = [self initWithConfiguration:configuration + clientId:clientID + clientSecret:clientSecret + scope:scope + redirectURL:redirectURL + responseType:responseType + state:state + nonce:nonce + codeVerifier:codeVerifier + codeChallenge:codeChallenge + codeChallengeMethod:codeChallengeMethod + additionalParameters:additionalParameters]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_configuration forKey:kConfigurationKey]; + [aCoder encodeObject:_responseType forKey:kResponseTypeKey]; + [aCoder encodeObject:_clientID forKey:kClientIDKey]; + [aCoder encodeObject:_clientSecret forKey:kClientSecretKey]; + [aCoder encodeObject:_scope forKey:kScopeKey]; + [aCoder encodeObject:_redirectURL forKey:kRedirectURLKey]; + [aCoder encodeObject:_state forKey:kStateKey]; + [aCoder encodeObject:_nonce forKey:kNonceKey]; + [aCoder encodeObject:_codeVerifier forKey:kCodeVerifierKey]; + [aCoder encodeObject:_codeChallenge forKey:kCodeChallengeKey]; + [aCoder encodeObject:_codeChallengeMethod forKey:kCodeChallengeMethodKey]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, request: %@>", + NSStringFromClass([self class]), + (void *)self, + self.authorizationRequestURL]; +} + +#pragma mark - State and PKCE verifier/challenge generation Methods + ++ (nullable NSString *)generateCodeVerifier { + return [OIDTokenUtilities randomURLSafeStringWithSize:kCodeVerifierBytes]; +} + ++ (nullable NSString *)generateState { + return [OIDTokenUtilities randomURLSafeStringWithSize:kStateSizeBytes]; +} + ++ (nullable NSString *)codeChallengeS256ForVerifier:(NSString *)codeVerifier { + if (!codeVerifier) { + return nil; + } + // generates the code_challenge per spec https://tools.ietf.org/html/rfc7636#section-4.2 + // code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) + // NB. the ASCII conversion on the code_verifier entropy was done at time of generation. + NSData *sha256Verifier = [OIDTokenUtilities sha256:codeVerifier]; + return [OIDTokenUtilities encodeBase64urlNoPadding:sha256Verifier]; +} + +#pragma mark - + +- (NSURL *)authorizationRequestURL { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; + + // Required parameters. + [query addParameter:kResponseTypeKey value:_responseType]; + [query addParameter:kClientIDKey value:_clientID]; + + // Add any additional parameters the client has specified. + [query addParameters:_additionalParameters]; + + // Add optional parameters, as applicable. + if (_redirectURL) { + [query addParameter:kRedirectURLKey value:_redirectURL.absoluteString]; + } + if (_scope) { + [query addParameter:kScopeKey value:_scope]; + } + if (_state) { + [query addParameter:kStateKey value:_state]; + } + if (_nonce) { + [query addParameter:kNonceKey value:_nonce]; + } + if (_codeChallenge) { + [query addParameter:kCodeChallengeKey value:_codeChallenge]; + } + if (_codeChallengeMethod) { + [query addParameter:kCodeChallengeMethodKey value:_codeChallengeMethod]; + } + + // Construct the URL: + return [query URLByReplacingQueryInURL:_configuration.authorizationEndpoint]; +} + +#pragma mark - OIDExternalUserAgentRequest + +- (NSURL *)externalUserAgentRequestURL { + return [self authorizationRequestURL]; +} + +- (NSString *)redirectScheme { + return [[self redirectURL] scheme]; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationResponse.h b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationResponse.h new file mode 100644 index 00000000..e7552fe5 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationResponse.h @@ -0,0 +1,128 @@ +/*! @file OIDAuthorizationResponse.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +@class OIDAuthorizationRequest; +@class OIDTokenRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents the response to an authorization request. + @see https://tools.ietf.org/html/rfc6749#section-4.1.2 + @see https://tools.ietf.org/html/rfc6749#section-5.1 + @see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + */ +@interface OIDAuthorizationResponse : NSObject + +/*! @brief The request which was serviced. + */ +@property(nonatomic, readonly) OIDAuthorizationRequest *request; + +/*! @brief The authorization code generated by the authorization server. + @discussion Set when the response_type requested includes 'code'. + @remarks code + */ +@property(nonatomic, readonly, nullable) NSString *authorizationCode; + +/*! @brief REQUIRED if the "state" parameter was present in the client authorization request. The + exact value received from the client. + @remarks state + */ +@property(nonatomic, readonly, nullable) NSString *state; + +/*! @brief The access token generated by the authorization server. + @discussion Set when the response_type requested includes 'token'. + @remarks access_token + @see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + */ +@property(nonatomic, readonly, nullable) NSString *accessToken; + +/*! @brief The approximate expiration date & time of the access token. + @discussion Set when the response_type requested includes 'token'. + @remarks expires_in + @seealso OIDAuthorizationResponse.accessToken + @see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + */ +@property(nonatomic, readonly, nullable) NSDate *accessTokenExpirationDate; + +/*! @brief Typically "Bearer" when present. Otherwise, another token_type value that the Client has + negotiated with the Authorization Server. + @discussion Set when the response_type requested includes 'token'. + @remarks token_type + @see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + */ +@property(nonatomic, readonly, nullable) NSString *tokenType; + +/*! @brief ID Token value associated with the authenticated session. + @discussion Set when the response_type requested includes 'id_token'. + @remarks id_token + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + @see http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + */ +@property(nonatomic, readonly, nullable) NSString *idToken; + +/*! @brief The scope of the access token. OPTIONAL, if identical to the scopes requested, otherwise, + REQUIRED. + @remarks scope + @see https://tools.ietf.org/html/rfc6749#section-5.1 + */ +@property(nonatomic, readonly, nullable) NSString *scope; + +/*! @brief Additional parameters returned from the authorization server. + */ +@property(nonatomic, readonly, nullable) + NSDictionary *> *additionalParameters; + +/*! @internal + @brief Unavailable. Please use initWithRequest:parameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param request The serviced request. + @param parameters The decoded parameters returned from the Authorization Server. + @remarks Known parameters are extracted from the @c parameters parameter and the normative + properties are populated. Non-normative parameters are placed in the + @c #additionalParameters dictionary. + */ +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request + parameters:(NSDictionary *> *)parameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Creates a token request suitable for exchanging an authorization code for an access + token. + @return A @c OIDTokenRequest suitable for exchanging an authorization code for an access + token. + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +- (nullable OIDTokenRequest *)tokenExchangeRequest; + +/*! @brief Creates a token request suitable for exchanging an authorization code for an access + token. + @param additionalParameters Additional parameters for the token request. + @return A @c OIDTokenRequest suitable for exchanging an authorization code for an access + token. + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +- (nullable OIDTokenRequest *)tokenExchangeRequestWithAdditionalParameters: + (nullable NSDictionary *)additionalParameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationResponse.m b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationResponse.m new file mode 100644 index 00000000..a8f92c75 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationResponse.m @@ -0,0 +1,210 @@ +/*! @file OIDAuthorizationResponse.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDAuthorizationResponse.h" + +#import "OIDAuthorizationRequest.h" +#import "OIDDefines.h" +#import "OIDError.h" +#import "OIDFieldMapping.h" +#import "OIDTokenRequest.h" +#import "OIDTokenUtilities.h" + +/*! @brief The key for the @c authorizationCode property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kAuthorizationCodeKey = @"code"; + +/*! @brief The key for the @c state property in the incoming parameters and for @c NSSecureCoding. + */ +static NSString *const kStateKey = @"state"; + +/*! @brief The key for the @c accessToken property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kAccessTokenKey = @"access_token"; + +/*! @brief The key for the @c accessTokenExpirationDate property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kExpiresInKey = @"expires_in"; + +/*! @brief The key for the @c tokenType property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kTokenTypeKey = @"token_type"; + +/*! @brief The key for the @c idToken property in the incoming parameters and for @c NSSecureCoding. + */ +static NSString *const kIDTokenKey = @"id_token"; + +/*! @brief The key for the @c scope property in the incoming parameters and for @c NSSecureCoding. + */ +static NSString *const kScopeKey = @"scope"; + +/*! @brief Key used to encode the @c additionalParameters property for @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +/*! @brief Key used to encode the @c request property for @c NSSecureCoding + */ +static NSString *const kRequestKey = @"request"; + +/*! @brief The exception thrown when a developer tries to create a token exchange request from an + authorization request with no authorization code. + */ +static NSString *const kTokenExchangeRequestException = + @"Attempted to create a token exchange request from an authorization response with no " + "authorization code."; + +@implementation OIDAuthorizationResponse + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + fieldMap[kStateKey] = + [[OIDFieldMapping alloc] initWithName:@"_state" type:[NSString class]]; + fieldMap[kAuthorizationCodeKey] = + [[OIDFieldMapping alloc] initWithName:@"_authorizationCode" type:[NSString class]]; + fieldMap[kAccessTokenKey] = + [[OIDFieldMapping alloc] initWithName:@"_accessToken" type:[NSString class]]; + fieldMap[kExpiresInKey] = + [[OIDFieldMapping alloc] initWithName:@"_accessTokenExpirationDate" + type:[NSDate class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSinceNow:[valueAsNumber longLongValue]]; + }]; + fieldMap[kTokenTypeKey] = + [[OIDFieldMapping alloc] initWithName:@"_tokenType" type:[NSString class]]; + fieldMap[kIDTokenKey] = + [[OIDFieldMapping alloc] initWithName:@"_idToken" type:[NSString class]]; + fieldMap[kScopeKey] = + [[OIDFieldMapping alloc] initWithName:@"_scope" type:[NSString class]]; + }); + return fieldMap; +} + +#pragma mark - Initializers + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) + +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request + parameters:(NSDictionary *> *)parameters { + self = [super init]; + if (self) { + _request = [request copy]; + NSDictionary *> *additionalParameters = + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:parameters + instance:self]; + _additionalParameters = additionalParameters; + } + return self; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDAuthorizationRequest *request = + [aDecoder decodeObjectOfClass:[OIDAuthorizationRequest class] forKey:kRequestKey]; + self = [self initWithRequest:request parameters:@{ }]; + if (self) { + [OIDFieldMapping decodeWithCoder:aDecoder map:[[self class] fieldMap] instance:self]; + _additionalParameters = [aDecoder decodeObjectOfClasses:[OIDFieldMapping JSONTypes] + forKey:kAdditionalParametersKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_request forKey:kRequestKey]; + [OIDFieldMapping encodeWithCoder:aCoder map:[[self class] fieldMap] instance:self]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, authorizationCode: %@, state: \"%@\", accessToken: " + "\"%@\", accessTokenExpirationDate: %@, tokenType: %@, " + "idToken: \"%@\", scope: \"%@\", additionalParameters: %@, " + "request: %@>", + NSStringFromClass([self class]), + (void *)self, + _authorizationCode, + _state, + [OIDTokenUtilities redact:_accessToken], + _accessTokenExpirationDate, + _tokenType, + [OIDTokenUtilities redact:_idToken], + _scope, + _additionalParameters, + _request]; +} + +#pragma mark - + +- (OIDTokenRequest *)tokenExchangeRequest { + return [self tokenExchangeRequestWithAdditionalParameters:nil]; +} + +- (OIDTokenRequest *)tokenExchangeRequestWithAdditionalParameters: + (NSDictionary *)additionalParameters { + // TODO: add a unit test to confirm exception is thrown when expected and the request is created + // with the correct parameters. + if (!_authorizationCode) { + [NSException raise:kTokenExchangeRequestException + format:kTokenExchangeRequestException]; + } + return [[OIDTokenRequest alloc] initWithConfiguration:_request.configuration + grantType:OIDGrantTypeAuthorizationCode + authorizationCode:_authorizationCode + redirectURL:_request.redirectURL + clientID:_request.clientID + clientSecret:_request.clientSecret + scope:nil + refreshToken:nil + codeVerifier:_request.codeVerifier + additionalParameters:additionalParameters]; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationService.h b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationService.h new file mode 100644 index 00000000..c8fee535 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationService.h @@ -0,0 +1,170 @@ +/*! @file OIDAuthorizationService.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +@class OIDAuthorization; +@class OIDAuthorizationRequest; +@class OIDAuthorizationResponse; +@class OIDEndSessionRequest; +@class OIDEndSessionResponse; +@class OIDRegistrationRequest; +@class OIDRegistrationResponse; +@class OIDServiceConfiguration; +@class OIDTokenRequest; +@class OIDTokenResponse; +@protocol OIDExternalUserAgent; +@protocol OIDExternalUserAgentSession; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents the type of block used as a callback for creating a service configuration from + a remote OpenID Connect Discovery document. + @param configuration The service configuration, if available. + @param error The error if an error occurred. + */ +typedef void (^OIDDiscoveryCallback)(OIDServiceConfiguration *_Nullable configuration, + NSError *_Nullable error); + +/*! @brief Represents the type of block used as a callback for various methods of + @c OIDAuthorizationService. + @param authorizationResponse The authorization response, if available. + @param error The error if an error occurred. + */ +typedef void (^OIDAuthorizationCallback)(OIDAuthorizationResponse *_Nullable authorizationResponse, + NSError *_Nullable error); + +/*! @brief Block used as a callback for the end-session request of @c OIDAuthorizationService. + @param endSessionResponse The end-session response, if available. + @param error The error if an error occurred. + */ +typedef void (^OIDEndSessionCallback)(OIDEndSessionResponse *_Nullable endSessionResponse, + NSError *_Nullable error); + +/*! @brief Represents the type of block used as a callback for various methods of + @c OIDAuthorizationService. + @param tokenResponse The token response, if available. + @param error The error if an error occurred. + */ +typedef void (^OIDTokenCallback)(OIDTokenResponse *_Nullable tokenResponse, + NSError *_Nullable error); + +/*! @brief Represents the type of dictionary used to specify additional querystring parameters + when making authorization or token endpoint requests. + */ +typedef NSDictionary *_Nullable OIDTokenEndpointParameters; + +/*! @brief Represents the type of block used as a callback for various methods of + @c OIDAuthorizationService. + @param registrationResponse The registration response, if available. + @param error The error if an error occurred. +*/ +typedef void (^OIDRegistrationCompletion)(OIDRegistrationResponse *_Nullable registrationResponse, + NSError *_Nullable error); + +/*! @brief Performs various OAuth and OpenID Connect related calls via the user agent or + \NSURLSession. + */ +@interface OIDAuthorizationService : NSObject + +/*! @brief The service's configuration. + @remarks Each authorization service is initialized with a configuration. This configuration + specifies how to connect to a particular OAuth provider. Clients should use separate + authorization service instances for each provider they wish to integrate with. + Configurations may be created manually, or via an OpenID Connect Discovery Document. + */ +@property(nonatomic, readonly) OIDServiceConfiguration *configuration; + +/*! @internal + @brief Unavailable. This class should not be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Convenience method for creating an authorization service configuration from an OpenID + Connect compliant issuer URL. + @param issuerURL The service provider's OpenID Connect issuer. + @param completion A block which will be invoked when the authorization service configuration has + been created, or when an error has occurred. + @see https://openid.net/specs/openid-connect-discovery-1_0.html + */ ++ (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL + completion:(OIDDiscoveryCallback)completion; + + +/*! @brief Convenience method for creating an authorization service configuration from an OpenID + Connect compliant identity provider's discovery document. + @param discoveryURL The URL of the service provider's OpenID Connect discovery document. + @param completion A block which will be invoked when the authorization service configuration has + been created, or when an error has occurred. + @see https://openid.net/specs/openid-connect-discovery-1_0.html + */ ++ (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL + completion:(OIDDiscoveryCallback)completion; + +/*! @brief Perform an authorization flow using a generic flow shim. + @param request The authorization request. + @param externalUserAgent Generic external user-agent that can present an authorization + request. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + */ ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDAuthorizationCallback)callback; + +/*! @brief Perform a logout request. + @param request The end-session logout request. + @param externalUserAgent Generic external user-agent that can present user-agent requests. + @param callback The method called when the request has completed or failed. + @return A @c OIDExternalUserAgentSession instance which will terminate when it + receives a @c OIDExternalUserAgentSession.cancel message, or after processing a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ ++ (id) + presentEndSessionRequest:(OIDEndSessionRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)callback; + +/*! @brief Performs a token request. + @param request The token request. + @param callback The method called when the request has completed or failed. + */ ++ (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback; + +/*! @brief Performs a token request. + @param request The token request. + @param authorizationResponse The original authorization response related to this token request. + @param callback The method called when the request has completed or failed. + */ ++ (void)performTokenRequest:(OIDTokenRequest *)request + originalAuthorizationResponse:(OIDAuthorizationResponse *_Nullable)authorizationResponse + callback:(OIDTokenCallback)callback; + +/*! @brief Performs a registration request. + @param request The registration request. + @param completion The method called when the request has completed or failed. + */ ++ (void)performRegistrationRequest:(OIDRegistrationRequest *)request + completion:(OIDRegistrationCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationService.m b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationService.m new file mode 100644 index 00000000..cc749a3f --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDAuthorizationService.m @@ -0,0 +1,790 @@ +/*! @file OIDAuthorizationService.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDAuthorizationService.h" + +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDDefines.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDIDToken.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDURLQueryComponent.h" +#import "OIDURLSessionProvider.h" + +/*! @brief Path appended to an OpenID Connect issuer for discovery + @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig + */ +static NSString *const kOpenIDConfigurationWellKnownPath = @".well-known/openid-configuration"; + +/*! @brief Max allowable iat (Issued At) time skew + @see https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + */ +static int const kOIDAuthorizationSessionIATMaxSkew = 600; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDAuthorizationSession : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request + NS_DESIGNATED_INITIALIZER; + +@end + +@implementation OIDAuthorizationSession { + OIDAuthorizationRequest *_request; + id _externalUserAgent; + OIDAuthorizationCallback _pendingauthorizationFlowCallback; +} + +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request { + self = [super init]; + if (self) { + _request = [request copy]; + } + return self; +} + +- (void)presentAuthorizationWithExternalUserAgent:(id)externalUserAgent + callback:(OIDAuthorizationCallback)authorizationFlowCallback { + _externalUserAgent = externalUserAgent; + _pendingauthorizationFlowCallback = authorizationFlowCallback; + BOOL authorizationFlowStarted = + [_externalUserAgent presentExternalUserAgentRequest:_request session:self]; + if (!authorizationFlowStarted) { + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open Safari."]; + [self didFinishWithResponse:nil error:safariError]; + } +} + +- (void)cancel { + [self cancelWithCompletion:nil]; +} + +- (void)cancelWithCompletion:(nullable void (^)(void))completion { + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + NSError *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:@"Authorization flow was cancelled."]; + [self didFinishWithResponse:nil error:error]; + if (completion) completion(); + }]; +} + +/*! @brief Does the redirection URL equal another URL down to the path component? + @param URL The first redirect URI to compare. + @param redirectionURL The second redirect URI to compare. + @return YES if the URLs match down to the path level (query params are ignored). + */ ++ (BOOL)URL:(NSURL *)URL matchesRedirectionURL:(NSURL *)redirectionURL { + NSURL *standardizedURL = [URL standardizedURL]; + NSURL *standardizedRedirectURL = [redirectionURL standardizedURL]; + + return [standardizedURL.scheme caseInsensitiveCompare:standardizedRedirectURL.scheme] == NSOrderedSame + && OIDIsEqualIncludingNil(standardizedURL.user, standardizedRedirectURL.user) + && OIDIsEqualIncludingNil(standardizedURL.password, standardizedRedirectURL.password) + && OIDIsEqualIncludingNil(standardizedURL.host, standardizedRedirectURL.host) + && OIDIsEqualIncludingNil(standardizedURL.port, standardizedRedirectURL.port) + && OIDIsEqualIncludingNil(standardizedURL.path, standardizedRedirectURL.path); +} + +- (BOOL)shouldHandleURL:(NSURL *)URL { + return [[self class] URL:URL matchesRedirectionURL:_request.redirectURL]; +} + +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { + // rejects URLs that don't match redirect (these may be completely unrelated to the authorization) + if (![self shouldHandleURL:URL]) { + return NO; + } + + AppAuthRequestTrace(@"Authorization Response: %@", URL); + + // checks for an invalid state + if (!_pendingauthorizationFlowCallback) { + [NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow + format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil]; + } + + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:URL]; + + NSError *error; + OIDAuthorizationResponse *response = nil; + + // checks for an OAuth error response as per RFC6749 Section 4.1.2.1 + if (query.dictionaryValue[OIDOAuthErrorFieldError]) { + error = [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthAuthorizationErrorDomain + OAuthResponse:query.dictionaryValue + underlyingError:nil]; + } + + // no error, should be a valid OAuth 2.0 response + if (!error) { + response = [[OIDAuthorizationResponse alloc] initWithRequest:_request + parameters:query.dictionaryValue]; + + // verifies that the state in the response matches the state in the request, or both are nil + if (!OIDIsEqualIncludingNil(_request.state, response.state)) { + NSMutableDictionary *userInfo = [query.dictionaryValue mutableCopy]; + userInfo[NSLocalizedDescriptionKey] = + [NSString stringWithFormat:@"State mismatch, expecting %@ but got %@ in authorization " + "response %@", + _request.state, + response.state, + response]; + response = nil; + error = [NSError errorWithDomain:OIDOAuthAuthorizationErrorDomain + code:OIDErrorCodeOAuthAuthorizationClientError + userInfo:userInfo]; + } + } + + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + [self didFinishWithResponse:response error:error]; + }]; + + return YES; +} + +- (void)failExternalUserAgentFlowWithError:(NSError *)error { + [self didFinishWithResponse:nil error:error]; +} + +/*! @brief Invokes the pending callback and performs cleanup. + @param response The authorization response, if any to return to the callback. + @param error The error, if any, to return to the callback. + */ +- (void)didFinishWithResponse:(nullable OIDAuthorizationResponse *)response + error:(nullable NSError *)error { + OIDAuthorizationCallback callback = _pendingauthorizationFlowCallback; + _pendingauthorizationFlowCallback = nil; + _externalUserAgent = nil; + if (callback) { + callback(response, error); + } +} + +@end + +@interface OIDEndSessionImplementation : NSObject { + // private variables + OIDEndSessionRequest *_request; + id _externalUserAgent; + OIDEndSessionCallback _pendingEndSessionCallback; +} +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + NS_DESIGNATED_INITIALIZER; +@end + + +@implementation OIDEndSessionImplementation + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request { + self = [super init]; + if (self) { + _request = [request copy]; + } + return self; +} + +- (void)presentAuthorizationWithExternalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)authorizationFlowCallback { + _externalUserAgent = externalUserAgent; + _pendingEndSessionCallback = authorizationFlowCallback; + BOOL authorizationFlowStarted = + [_externalUserAgent presentExternalUserAgentRequest:_request session:self]; + if (!authorizationFlowStarted) { + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open Safari."]; + [self didFinishWithResponse:nil error:safariError]; + } +} + +- (void)cancel { + [self cancelWithCompletion:nil]; +} + +- (void)cancelWithCompletion:(nullable void (^)(void))completion { + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + NSError *error = [OIDErrorUtilities + errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:nil]; + [self didFinishWithResponse:nil error:error]; + if (completion) completion(); + }]; +} + +- (BOOL)shouldHandleURL:(NSURL *)URL { + // The logic of when to handle the URL is the same as for authorization requests: should match + // down to the path component. + return [[OIDAuthorizationSession class] URL:URL + matchesRedirectionURL:_request.postLogoutRedirectURL]; +} + +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL { + // rejects URLs that don't match redirect (these may be completely unrelated to the authorization) + if (![self shouldHandleURL:URL]) { + return NO; + } + // checks for an invalid state + if (!_pendingEndSessionCallback) { + [NSException raise:OIDOAuthExceptionInvalidAuthorizationFlow + format:@"%@", OIDOAuthExceptionInvalidAuthorizationFlow, nil]; + } + + + NSError *error; + OIDEndSessionResponse *response = nil; + + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] initWithURL:URL]; + response = [[OIDEndSessionResponse alloc] initWithRequest:_request + parameters:query.dictionaryValue]; + + // verifies that the state in the response matches the state in the request, or both are nil + if (!OIDIsEqualIncludingNil(_request.state, response.state)) { + NSMutableDictionary *userInfo = [query.dictionaryValue mutableCopy]; + userInfo[NSLocalizedDescriptionKey] = + [NSString stringWithFormat:@"State mismatch, expecting %@ but got %@ in authorization " + "response %@", + _request.state, + response.state, + response]; + response = nil; + error = [NSError errorWithDomain:OIDOAuthAuthorizationErrorDomain + code:OIDErrorCodeOAuthAuthorizationClientError + userInfo:userInfo]; + } + + [_externalUserAgent dismissExternalUserAgentAnimated:YES completion:^{ + [self didFinishWithResponse:response error:error]; + }]; + + return YES; +} + +- (void)failExternalUserAgentFlowWithError:(NSError *)error { + [self didFinishWithResponse:nil error:error]; +} + +/*! @brief Invokes the pending callback and performs cleanup. + @param response The authorization response, if any to return to the callback. + @param error The error, if any, to return to the callback. + */ +- (void)didFinishWithResponse:(nullable OIDEndSessionResponse *)response + error:(nullable NSError *)error { + OIDEndSessionCallback callback = _pendingEndSessionCallback; + _pendingEndSessionCallback = nil; + _externalUserAgent = nil; + if (callback) { + callback(response, error); + } +} + +@end + +@implementation OIDAuthorizationService + ++ (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL + completion:(OIDDiscoveryCallback)completion { + NSURL *fullDiscoveryURL = + [issuerURL URLByAppendingPathComponent:kOpenIDConfigurationWellKnownPath]; + + [[self class] discoverServiceConfigurationForDiscoveryURL:fullDiscoveryURL + completion:completion]; +} + ++ (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL + completion:(OIDDiscoveryCallback)completion { + + NSURLSession *session = [OIDURLSessionProvider session]; + NSURLSessionDataTask *task = + [session dataTaskWithURL:discoveryURL + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + // If we got any sort of error, just report it. + if (error || !data) { + NSString *errorDescription = + [NSString stringWithFormat:@"Connection error fetching discovery document '%@': %@.", + discoveryURL, + error.localizedDescription]; + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, error); + }); + return; + } + + NSHTTPURLResponse *urlResponse = (NSHTTPURLResponse *)response; + + // Check for non-200 status codes. + // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse + if (urlResponse.statusCode != 200) { + NSError *URLResponseError = [OIDErrorUtilities HTTPErrorWithHTTPResponse:urlResponse + data:data]; + NSString *errorDescription = + [NSString stringWithFormat:@"Non-200 HTTP response (%d) fetching discovery document " + "'%@'.", + (int)urlResponse.statusCode, + discoveryURL]; + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:URLResponseError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, error); + }); + return; + } + + // Construct an OIDServiceDiscovery with the received JSON. + OIDServiceDiscovery *discovery = + [[OIDServiceDiscovery alloc] initWithJSONData:data error:&error]; + if (error || !discovery) { + NSString *errorDescription = + [NSString stringWithFormat:@"JSON error parsing document at '%@': %@", + discoveryURL, + error.localizedDescription]; + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, error); + }); + return; + } + + // Create our service configuration with the discovery document and return it. + OIDServiceConfiguration *configuration = + [[OIDServiceConfiguration alloc] initWithDiscoveryDocument:discovery]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(configuration, nil); + }); + }]; + [task resume]; +} + +#pragma mark - Authorization Endpoint + ++ (id) presentAuthorizationRequest:(OIDAuthorizationRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDAuthorizationCallback)callback { + + AppAuthRequestTrace(@"Authorization Request: %@", request); + + OIDAuthorizationSession *flowSession = [[OIDAuthorizationSession alloc] initWithRequest:request]; + [flowSession presentAuthorizationWithExternalUserAgent:externalUserAgent callback:callback]; + return flowSession; +} + ++ (id) + presentEndSessionRequest:(OIDEndSessionRequest *)request + externalUserAgent:(id)externalUserAgent + callback:(OIDEndSessionCallback)callback { + OIDEndSessionImplementation *flowSession = + [[OIDEndSessionImplementation alloc] initWithRequest:request]; + [flowSession presentAuthorizationWithExternalUserAgent:externalUserAgent callback:callback]; + return flowSession; +} + +#pragma mark - Token Endpoint + ++ (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback { + [[self class] performTokenRequest:request + originalAuthorizationResponse:nil + callback:callback]; +} + ++ (void)performTokenRequest:(OIDTokenRequest *)request + originalAuthorizationResponse:(OIDAuthorizationResponse *_Nullable)authorizationResponse + callback:(OIDTokenCallback)callback { + + NSURLRequest *URLRequest = [request URLRequest]; + + AppAuthRequestTrace(@"Token Request: %@\nHeaders:%@\nHTTPBody: %@", + URLRequest.URL, + URLRequest.allHTTPHeaderFields, + [[NSString alloc] initWithData:URLRequest.HTTPBody + encoding:NSUTF8StringEncoding]); + + NSURLSession *session = [OIDURLSessionProvider session]; + [[session dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // A network error or server error occurred. + NSString *errorDescription = + [NSString stringWithFormat:@"Connection error making token request to '%@': %@.", + URLRequest.URL, + error.localizedDescription]; + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response; + NSInteger statusCode = HTTPURLResponse.statusCode; + AppAuthRequestTrace(@"Token Response: HTTP Status %d\nHTTPBody: %@", + (int)statusCode, + [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); + if (statusCode != 200) { + // A server error occurred. + NSError *serverError = + [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse data:data]; + + // HTTP 4xx may indicate an RFC6749 Section 5.2 error response, attempts to parse as such. + if (statusCode >= 400 && statusCode < 500) { + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; + + // If the HTTP 4xx response parses as JSON and has an 'error' key, it's an OAuth error. + // These errors are special as they indicate a problem with the authorization grant. + if (json[OIDOAuthErrorFieldError]) { + NSError *oauthError = + [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthTokenErrorDomain + OAuthResponse:json + underlyingError:serverError]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, oauthError); + }); + return; + } + } + + // Status code indicates this is an error, but not an RFC6749 Section 5.2 error. + NSString *errorDescription = + [NSString stringWithFormat:@"Non-200 HTTP response (%d) making token request to '%@'.", + (int)statusCode, + URLRequest.URL]; + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError + underlyingError:serverError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; + if (jsonDeserializationError) { + // A problem occurred deserializing the response/JSON. + NSString *errorDescription = + [NSString stringWithFormat:@"JSON error parsing token response: %@", + jsonDeserializationError.localizedDescription]; + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonDeserializationError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + OIDTokenResponse *tokenResponse = + [[OIDTokenResponse alloc] initWithRequest:request parameters:json]; + if (!tokenResponse) { + // A problem occurred constructing the token response from the JSON. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeTokenResponseConstructionError + underlyingError:jsonDeserializationError + description:@"Token response invalid."]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + // If an ID Token is included in the response, validates the ID Token following the rules + // in OpenID Connect Core Section 3.1.3.7 for features that AppAuth directly supports + // (which excludes rules #1, #4, #5, #7, #8, #12, and #13). Regarding rule #6, ID Tokens + // received by this class are received via direct communication between the Client and the Token + // Endpoint, thus we are exercising the option to rely only on the TLS validation. AppAuth + // has a zero dependencies policy, and verifying the JWT signature would add a dependency. + // Users of the library are welcome to perform the JWT signature verification themselves should + // they wish. + if (tokenResponse.idToken) { + OIDIDToken *idToken = [[OIDIDToken alloc] initWithIDTokenString:tokenResponse.idToken]; + if (!idToken) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenParsingError + underlyingError:nil + description:@"ID Token parsing failed"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // OpenID Connect Core Section 3.1.3.7. rule #1 + // Not supported: AppAuth does not support JWT encryption. + + // OpenID Connect Core Section 3.1.3.7. rule #2 + // Validates that the issuer in the ID Token matches that of the discovery document. + NSURL *issuer = tokenResponse.request.configuration.issuer; + if (issuer && ![idToken.issuer isEqual:issuer]) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Issuer mismatch"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // OpenID Connect Core Section 3.1.3.7. rule #3 & Section 2 azp Claim + // Validates that the aud (audience) Claim contains the client ID, or that the azp + // (authorized party) Claim matches the client ID. + NSString *clientID = tokenResponse.request.clientID; + if (![idToken.audience containsObject:clientID] && + ![idToken.claims[@"azp"] isEqualToString:clientID]) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Audience mismatch"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // OpenID Connect Core Section 3.1.3.7. rules #4 & #5 + // Not supported. + + // OpenID Connect Core Section 3.1.3.7. rule #6 + // As noted above, AppAuth only supports the code flow which results in direct communication + // of the ID Token from the Token Endpoint to the Client, and we are exercising the option to + // use TSL server validation instead of checking the token signature. Users may additionally + // check the token signature should they wish. + + // OpenID Connect Core Section 3.1.3.7. rules #7 & #8 + // Not applicable. See rule #6. + + // OpenID Connect Core Section 3.1.3.7. rule #9 + // Validates that the current time is before the expiry time. + NSTimeInterval expiresAtDifference = [idToken.expiresAt timeIntervalSinceNow]; + if (expiresAtDifference < 0) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"ID Token expired"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // OpenID Connect Core Section 3.1.3.7. rule #10 + // Validates that the issued at time is not more than +/- 10 minutes on the current time. + NSTimeInterval issuedAtDifference = [idToken.issuedAt timeIntervalSinceNow]; + if (fabs(issuedAtDifference) > kOIDAuthorizationSessionIATMaxSkew) { + NSString *message = + [NSString stringWithFormat:@"Issued at time is more than %d seconds before or after " + "the current time", + kOIDAuthorizationSessionIATMaxSkew]; + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:message]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + + // Only relevant for the authorization_code response type + if ([tokenResponse.request.grantType isEqual:OIDGrantTypeAuthorizationCode]) { + // OpenID Connect Core Section 3.1.3.7. rule #11 + // Validates the nonce. + NSString *nonce = authorizationResponse.request.nonce; + if (nonce && ![idToken.nonce isEqual:nonce]) { + NSError *invalidIDToken = + [OIDErrorUtilities errorWithCode:OIDErrorCodeIDTokenFailedValidationError + underlyingError:nil + description:@"Nonce mismatch"]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, invalidIDToken); + }); + return; + } + } + + // OpenID Connect Core Section 3.1.3.7. rules #12 + // ACR is not directly supported by AppAuth. + + // OpenID Connect Core Section 3.1.3.7. rules #12 + // max_age is not directly supported by AppAuth. + } + + // Success + dispatch_async(dispatch_get_main_queue(), ^{ + callback(tokenResponse, nil); + }); + }] resume]; +} + + +#pragma mark - Registration Endpoint + ++ (void)performRegistrationRequest:(OIDRegistrationRequest *)request + completion:(OIDRegistrationCompletion)completion { + NSURLRequest *URLRequest = [request URLRequest]; + if (!URLRequest) { + // A problem occurred deserializing the response/JSON. + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONSerializationError + underlyingError:nil + description:@"The registration request could not " + "be serialized as JSON."]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + NSURLSession *session = [OIDURLSessionProvider session]; + [[session dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // A network error or server error occurred. + NSString *errorDescription = + [NSString stringWithFormat:@"Connection error making registration request to '%@': %@.", + URLRequest.URL, + error.localizedDescription]; + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *) response; + + if (HTTPURLResponse.statusCode != 201 && HTTPURLResponse.statusCode != 200) { + // A server error occurred. + NSError *serverError = [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse + data:data]; + + // HTTP 400 may indicate an OpenID Connect Dynamic Client Registration 1.0 Section 3.3 error + // response, checks for that + if (HTTPURLResponse.statusCode == 400) { + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; + + // if the HTTP 400 response parses as JSON and has an 'error' key, it's an OAuth error + // these errors are special as they indicate a problem with the authorization grant + if (json[OIDOAuthErrorFieldError]) { + NSError *oauthError = + [OIDErrorUtilities OAuthErrorWithDomain:OIDOAuthRegistrationErrorDomain + OAuthResponse:json + underlyingError:serverError]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, oauthError); + }); + return; + } + } + + // not an OAuth error, just a generic server error + NSString *errorDescription = + [NSString stringWithFormat:@"Non-200/201 HTTP response (%d) making registration request " + "to '%@'.", + (int)HTTPURLResponse.statusCode, + URLRequest.URL]; + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError + underlyingError:serverError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonDeserializationError]; + if (jsonDeserializationError) { + // A problem occurred deserializing the response/JSON. + NSString *errorDescription = + [NSString stringWithFormat:@"JSON error parsing registration response: %@", + jsonDeserializationError.localizedDescription]; + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonDeserializationError + description:errorDescription]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + OIDRegistrationResponse *registrationResponse = + [[OIDRegistrationResponse alloc] initWithRequest:request + parameters:json]; + if (!registrationResponse) { + // A problem occurred constructing the registration response from the JSON. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeRegistrationResponseConstructionError + underlyingError:nil + description:@"Registration response invalid."]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + // Success + dispatch_async(dispatch_get_main_queue(), ^{ + completion(registrationResponse, nil); + }); + }] resume]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDClientMetadataParameters.h b/Pods/AppAuth/Source/AppAuthCore/OIDClientMetadataParameters.h new file mode 100644 index 00000000..39ea2d62 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDClientMetadataParameters.h @@ -0,0 +1,51 @@ +/*! @file OIDClientMetadataParameters.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 The AppAuth for iOS Authors. All Rights Reserved. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Parameter name for the token endpoint authentication method. + */ +extern NSString *const OIDTokenEndpointAuthenticationMethodParam; + +/*! @brief Parameter name for the application type. + */ +extern NSString *const OIDApplicationTypeParam; + +/*! @brief Parameter name for the redirect URI values. + */ +extern NSString *const OIDRedirectURIsParam; + +/*! @brief Parameter name for the response type values. + */ +extern NSString *const OIDResponseTypesParam; + +/*! @brief Parameter name for the grant type values. + */ +extern NSString *const OIDGrantTypesParam; + +/*! @brief Parameter name for the subject type. + */ +extern NSString *const OIDSubjectTypeParam; + +/*! @brief Application type that indicates this client is a native (not a web) application. + */ +extern NSString *const OIDApplicationTypeNative; + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDClientMetadataParameters.m b/Pods/AppAuth/Source/AppAuthCore/OIDClientMetadataParameters.m new file mode 100644 index 00000000..79ad4676 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDClientMetadataParameters.m @@ -0,0 +1,33 @@ +/*! @file OIDClientMetadataParameters.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 The AppAuth for iOS Authors. All Rights Reserved. + @copydetails + 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 "OIDClientMetadataParameters.h" + +NSString *const OIDTokenEndpointAuthenticationMethodParam = @"token_endpoint_auth_method"; + +NSString *const OIDApplicationTypeParam = @"application_type"; + +NSString *const OIDRedirectURIsParam = @"redirect_uris"; + +NSString *const OIDResponseTypesParam = @"response_types"; + +NSString *const OIDGrantTypesParam = @"grant_types"; + +NSString *const OIDSubjectTypeParam = @"subject_type"; + +NSString *const OIDApplicationTypeNative = @"native"; diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDDefines.h b/Pods/AppAuth/Source/AppAuthCore/OIDDefines.h new file mode 100644 index 00000000..8ff4f19b --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDDefines.h @@ -0,0 +1,51 @@ +/*! @file OIDDefines.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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. + */ + +/*! @def OIDIsEqualIncludingNil(x, y) + @brief Returns YES if x and y are equal by reference or value. + @discussion NOTE: parameters may be evaluated multiple times. Be careful if using this check + with expressions - especially if the expressions have side effects. + @param x An object. + @param y An object. + */ +#define OIDIsEqualIncludingNil(x, y) (((x) == (y)) || [(x) isEqual:(y)]) + +/*! @def OID_UNAVAILABLE_USE_INITIALIZER(designatedInitializer) + @brief Provides a template implementation for init-family methods which have been marked as + NS_UNAVILABLE. Stops the compiler from giving a warning when it's the super class' + designated initializer, and gives callers useful feedback telling them what the + new designated initializer is. + @remarks Takes a SEL as a parameter instead of a string so that we get compiler warnings if the + designated intializer's signature changes. + @param designatedInitializer A SEL referencing the designated initializer. + */ +#define OID_UNAVAILABLE_USE_INITIALIZER(designatedInitializer) { \ + NSString *reason = [NSString stringWithFormat:@"Called: %@\nDesignated Initializer:%@", \ + NSStringFromSelector(_cmd), \ + NSStringFromSelector(designatedInitializer)]; \ + @throw [NSException exceptionWithName:@"Attempt to call unavailable initializer." \ + reason:reason \ + userInfo:nil]; \ +} + +#ifdef _APPAUTHTRACE +# define AppAuthRequestTrace(fmt, ...) NSLog(fmt, ##__VA_ARGS__); +#else // _APPAUTHTRACE +# define AppAuthRequestTrace(...) +#endif // _APPAUTHTRACE + diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionRequest.h b/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionRequest.h new file mode 100644 index 00000000..4087e9fa --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionRequest.h @@ -0,0 +1,107 @@ +/*! @file OIDEndSessionRequest.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + 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 + +#import "OIDExternalUserAgentRequest.h" + +@class OIDServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDEndSessionRequest : NSObject + + +/*! @brief The service's configuration. + @remarks This configuration specifies how to connect to a particular OAuth provider. + Configurations may be created manually, or via an OpenID Connect Discovery Document. + */ +@property(nonatomic, readonly) OIDServiceConfiguration *configuration; + +/*! @brief The client's redirect URI. + @remarks post_logout_redirect_uri + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSURL *postLogoutRedirectURL; + +/*! @brief Previously issued ID Token passed to the end session endpoint as a hint about the End-User's current authenticated + session with the Client + @remarks id_token_hint + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSString *idTokenHint; + +/*! @brief An opaque value used by the client to maintain state between the request and callback. + @remarks state + @discussion If this value is not explicitly set, this library will automatically add state and + perform appropriate validation of the state in the authorization response. It is recommended + that the default implementation of this parameter be used wherever possible. Typically used + to prevent CSRF attacks, as recommended in RFC6819 Section 5.3.5. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +@property(nonatomic, readonly, nullable) NSString *state; + +/*! @brief The client's additional authorization parameters. + @see https://tools.ietf.org/html/rfc6749#section-3.1 + */ +@property(nonatomic, readonly, nullable) NSDictionary *additionalParameters; + +/*! @internal + @brief Unavailable. Please use @c initWithConfiguration:clientId:scopes:redirectURL:additionalParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Creates an authorization request with opinionated defaults (a secure @c state). + @param configuration The service's configuration. + @param idTokenHint The previously issued ID Token + @param postLogoutRedirectURL The client's post-logout redirect URI. + callback. + @param additionalParameters The client's additional authorization parameters. +*/ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Designated initializer. + @param configuration The service's configuration. + @param idTokenHint The previously issued ID Token + @param postLogoutRedirectURL The client's post-logout redirect URI. + @param state An opaque value used by the client to maintain state between the request and + callback. + @param additionalParameters The client's additional authorization parameters. + */ +- (instancetype) + initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + state:(NSString *)state + additionalParameters:(nullable NSDictionary *)additionalParameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Constructs the request URI by adding the request parameters to the query component of the + authorization endpoint URI using the "application/x-www-form-urlencoded" format. + @return A URL representing the authorization request. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ +- (NSURL *)endSessionRequestURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionRequest.m b/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionRequest.m new file mode 100644 index 00000000..1e9eb0e2 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionRequest.m @@ -0,0 +1,190 @@ +/*! @file OIDEndSessionRequest.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + 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 "OIDEndSessionRequest.h" + +#import "OIDDefines.h" +#import "OIDTokenUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDURLQueryComponent.h" + +/*! @brief The key for the @c configuration property for @c NSSecureCoding + */ +static NSString *const kConfigurationKey = @"configuration"; + +/*! @brief Key used to encode the @c state property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kStateKey = @"state"; + +/*! @brief Key used to encode the @c postLogoutRedirectURL property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kPostLogoutRedirectURLKey = @"post_logout_redirect_uri"; + +/*! @brief Key used to encode the @c idTokenHint property for @c NSSecureCoding, and on the URL request. + */ +static NSString *const kIdTokenHintKey = @"id_token_hint"; + +/*! @brief Key used to encode the @c additionalParameters property for @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +/*! @brief Number of random bytes generated for the @state. + */ +static NSUInteger const kStateSizeBytes = 32; + +/*! @brief Assertion text for missing end_session_endpoint. + */ +static NSString *const OIDMissingEndSessionEndpointMessage = +@"The service configuration is missing an end_session_endpoint."; + +@implementation OIDEndSessionRequest + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithConfiguration: + idTokenHint: + postLogoutRedirectURL: + additionalParameters:) + ) + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + state:(NSString *)state + additionalParameters:(NSDictionary *)additionalParameters +{ + self = [super init]; + if (self) { + _configuration = [configuration copy]; + _idTokenHint = [idTokenHint copy]; + _postLogoutRedirectURL = [postLogoutRedirectURL copy]; + _state = [state copy]; + _additionalParameters = + [[NSDictionary alloc] initWithDictionary:additionalParameters copyItems:YES]; + } + return self; +} + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + idTokenHint:(NSString *)idTokenHint + postLogoutRedirectURL:(NSURL *)postLogoutRedirectURL + additionalParameters:(NSDictionary *)additionalParameters +{ + return [self initWithConfiguration:configuration + idTokenHint:idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + state:[[self class] generateState] + additionalParameters:additionalParameters]; +} +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDServiceConfiguration *configuration = [aDecoder decodeObjectOfClass:[OIDServiceConfiguration class] forKey:kConfigurationKey]; + + NSString *idTokenHint = [aDecoder decodeObjectOfClass:[NSString class] forKey:kIdTokenHintKey]; + NSURL *postLogoutRedirectURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kPostLogoutRedirectURLKey]; + NSString *state = [aDecoder decodeObjectOfClass:[NSString class] forKey:kStateKey]; + NSSet *additionalParameterCodingClasses = [NSSet setWithArray:@[ + [NSDictionary class], + [NSString class] + ]]; + NSDictionary *additionalParameters = [aDecoder decodeObjectOfClasses:additionalParameterCodingClasses + forKey:kAdditionalParametersKey]; + + self = [self initWithConfiguration:configuration + idTokenHint:idTokenHint + postLogoutRedirectURL:postLogoutRedirectURL + state:state + additionalParameters:additionalParameters]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_configuration forKey:kConfigurationKey]; + [aCoder encodeObject:_idTokenHint forKey:kIdTokenHintKey]; + [aCoder encodeObject:_postLogoutRedirectURL forKey:kPostLogoutRedirectURLKey]; + [aCoder encodeObject:_state forKey:kStateKey]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, request: %@>", + NSStringFromClass([self class]), + (void *)self, + self.endSessionRequestURL]; +} + ++ (nullable NSString *)generateState { + return [OIDTokenUtilities randomURLSafeStringWithSize:kStateSizeBytes]; +} + +#pragma mark - OIDExternalUserAgentRequest + +- (NSURL*)externalUserAgentRequestURL { + return [self endSessionRequestURL]; +} + +- (NSString *)redirectScheme { + return [_postLogoutRedirectURL scheme]; +} + +#pragma mark - + +- (NSURL *)endSessionRequestURL { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; + + // Add any additional parameters the client has specified. + [query addParameters:_additionalParameters]; + + // Add optional parameters, as applicable. + if (_idTokenHint) { + [query addParameter:kIdTokenHintKey value:_idTokenHint]; + } + + if (_postLogoutRedirectURL) { + [query addParameter:kPostLogoutRedirectURLKey value:_postLogoutRedirectURL.absoluteString]; + } + + if (_state) { + [query addParameter:kStateKey value:_state]; + } + + NSAssert(_configuration.endSessionEndpoint, OIDMissingEndSessionEndpointMessage); + + // Construct the URL + return [query URLByReplacingQueryInURL:_configuration.endSessionEndpoint]; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionResponse.h b/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionResponse.h new file mode 100644 index 00000000..ab69b930 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionResponse.h @@ -0,0 +1,64 @@ +/*! @file OIDEndSessionResponse.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + 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 + +@class OIDEndSessionRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents the response to an End Session request. + @see http://openid.net/specs/openid-connect-session-1_0.html#RPLogout + */ + +@interface OIDEndSessionResponse : NSObject + +/*! @brief The request which was serviced. + */ +@property(nonatomic, readonly) OIDEndSessionRequest *request; + +/*! @brief REQUIRED if the "state" parameter was present in the client end-session request. The + exact value received from the client. + @remarks state + */ +@property(nonatomic, readonly, nullable) NSString *state; + +/*! @brief Additional parameters returned from the end session endpoint. + */ +@property(nonatomic, readonly, nullable) + NSDictionary *> *additionalParameters; + +/*! @internal + @brief Unavailable. Please use initWithParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param request The serviced request. + @param parameters The decoded parameters returned from the End Session Endpoint. + @remarks Known parameters are extracted from the @c parameters parameter and the normative + properties are populated. Non-normative parameters are placed in the + @c #additionalParameters dictionary. + */ +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + parameters:(NSDictionary *> *)parameters + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionResponse.m b/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionResponse.m new file mode 100644 index 00000000..bedf0cd9 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDEndSessionResponse.m @@ -0,0 +1,118 @@ +/*! @file OIDEndSessionResponse.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + 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 "OIDEndSessionResponse.h" + +#import "OIDDefines.h" +#import "OIDEndSessionRequest.h" +#import "OIDFieldMapping.h" + +/*! @brief The key for the @c state property in the incoming parameters and for @c NSSecureCoding. + */ +static NSString *const kStateKey = @"state"; + +/*! @brief Key used to encode the @c request property for @c NSSecureCoding + */ +static NSString *const kRequestKey = @"request"; + +/*! @brief Key used to encode the @c additionalParameters property for + @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +@implementation OIDEndSessionResponse + +#pragma mark - Initializers + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) + +- (instancetype)initWithRequest:(OIDEndSessionRequest *)request + parameters:(NSDictionary *> *)parameters { + self = [super init]; + if (self) { + _request = [request copy]; + NSDictionary *> *additionalParameters = + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:parameters + instance:self]; + _additionalParameters = additionalParameters; + } + return self; +} + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + fieldMap[kStateKey] = + [[OIDFieldMapping alloc] initWithName:@"_state" type:[NSString class]]; + }); + return fieldMap; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDEndSessionRequest *request = + [aDecoder decodeObjectOfClass:[OIDEndSessionRequest class] forKey:kRequestKey]; + self = [self initWithRequest:request parameters:@{ }]; + if (self) { + [OIDFieldMapping decodeWithCoder:aDecoder map:[[self class] fieldMap] instance:self]; + _additionalParameters = [aDecoder decodeObjectOfClasses:[OIDFieldMapping JSONTypes] + forKey:kAdditionalParametersKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_request forKey:kRequestKey]; + [OIDFieldMapping encodeWithCoder:aCoder map:[[self class] fieldMap] instance:self]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, state: \"%@\", " + "additionalParameters: %@, request: %@>", + NSStringFromClass([self class]), + (void *)self, + _state, + _additionalParameters, + _request]; +} +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDError.h b/Pods/AppAuth/Source/AppAuthCore/OIDError.h new file mode 100644 index 00000000..5131f0ad --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDError.h @@ -0,0 +1,393 @@ +/*! @file OIDError.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief The error domain for all NSErrors returned from the AppAuth library. + */ +extern NSString *const OIDGeneralErrorDomain; + +/*! @brief The error domain for OAuth specific errors on the authorization endpoint. + @discussion This error domain is used when the server responds to an authorization request + with an explicit OAuth error, as defined by RFC6749 Section 4.1.2.1. If the authorization + response is invalid and not explicitly an error response, another error domain will be used. + The error response parameter dictionary is available in the + \NSError_userInfo dictionary using the @c ::OIDOAuthErrorResponseErrorKey key. + The \NSError_code will be one of the @c ::OIDErrorCodeOAuthAuthorization enum values. + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ +extern NSString *const OIDOAuthAuthorizationErrorDomain; + +/*! @brief The error domain for OAuth specific errors on the token endpoint. + @discussion This error domain is used when the server responds with HTTP 400 and an OAuth error, + as defined RFC6749 Section 5.2. If an HTTP 400 response does not parse as an OAuth error + (i.e. no 'error' field is present or the JSON is invalid), another error domain will be + used. The entire OAuth error response dictionary is available in the \NSError_userInfo + dictionary using the @c ::OIDOAuthErrorResponseErrorKey key. Unlike transient network + errors, errors in this domain invalidate the authentication state, and either indicate a + client error or require user interaction (i.e. reauthentication) to resolve. + The \NSError_code will be one of the @c ::OIDErrorCodeOAuthToken enum values. + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ +extern NSString *const OIDOAuthTokenErrorDomain; + +/*! @brief The error domain for dynamic client registration errors. + @discussion This error domain is used when the server responds with HTTP 400 and an OAuth error, + as defined in OpenID Connect Dynamic Client Registration 1.0 Section 3.3. If an HTTP 400 + response does not parse as an OAuth error (i.e. no 'error' field is present or the JSON is + invalid), another error domain will be used. The entire OAuth error response dictionary is + available in the \NSError_userInfo dictionary using the @c ::OIDOAuthErrorResponseErrorKey + key. Unlike transient network errors, errors in this domain invalidate the authentication + state, and indicates a client error. + The \NSError_code will be one of the @c ::OIDErrorCodeOAuthToken enum values. + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError + */ +extern NSString *const OIDOAuthRegistrationErrorDomain; + +/*! @brief The error domain for authorization errors encountered out of band on the resource server. + */ +extern NSString *const OIDResourceServerAuthorizationErrorDomain; + +/*! @brief An error domain representing received HTTP errors. + */ +extern NSString *const OIDHTTPErrorDomain; + +/*! @brief An error key for the original OAuth error response (if any). + */ +extern NSString *const OIDOAuthErrorResponseErrorKey; + +/*! @brief The key of the 'error' response field in a RFC6749 Section 5.2 response. + @remark error + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ +extern NSString *const OIDOAuthErrorFieldError; + +/*! @brief The key of the 'error_description' response field in a RFC6749 Section 5.2 response. + @remark error_description + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ +extern NSString *const OIDOAuthErrorFieldErrorDescription; + +/*! @brief The key of the 'error_uri' response field in a RFC6749 Section 5.2 response. + @remark error_uri + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ +extern NSString *const OIDOAuthErrorFieldErrorURI; + +/*! @brief The various error codes returned from the AppAuth library. + */ +typedef NS_ENUM(NSInteger, OIDErrorCode) { + /*! @brief Indicates a problem parsing an OpenID Connect Service Discovery document. + */ + OIDErrorCodeInvalidDiscoveryDocument = -2, + + /*! @brief Indicates the user manually canceled the OAuth authorization code flow. + */ + OIDErrorCodeUserCanceledAuthorizationFlow = -3, + + /*! @brief Indicates an OAuth authorization flow was programmatically cancelled. + */ + OIDErrorCodeProgramCanceledAuthorizationFlow = -4, + + /*! @brief Indicates a network error or server error occurred. + */ + OIDErrorCodeNetworkError = -5, + + /*! @brief Indicates a server error occurred. + */ + OIDErrorCodeServerError = -6, + + /*! @brief Indicates a problem occurred deserializing the response/JSON. + */ + OIDErrorCodeJSONDeserializationError = -7, + + /*! @brief Indicates a problem occurred constructing the token response from the JSON. + */ + OIDErrorCodeTokenResponseConstructionError = -8, + + /*! @brief @c UIApplication.openURL: returned NO when attempting to open the authorization + request in mobile Safari. + */ + OIDErrorCodeSafariOpenError = -9, + + /*! @brief @c NSWorkspace.openURL returned NO when attempting to open the authorization + request in the default browser. + */ + OIDErrorCodeBrowserOpenError = -10, + + /*! @brief Indicates a problem when trying to refresh the tokens. + */ + OIDErrorCodeTokenRefreshError = -11, + + /*! @brief Indicates a problem occurred constructing the registration response from the JSON. + */ + OIDErrorCodeRegistrationResponseConstructionError = -12, + + /*! @brief Indicates a problem occurred deserializing the response/JSON. + */ + OIDErrorCodeJSONSerializationError = -13, + + /*! @brief The ID Token did not parse. + */ + OIDErrorCodeIDTokenParsingError = -14, + + /*! @brief The ID Token did not pass validation (e.g. issuer, audience checks). + */ + OIDErrorCodeIDTokenFailedValidationError = -15, +}; + +/*! @brief Enum of all possible OAuth error codes as defined by RFC6749 + @discussion Used by @c ::OIDErrorCodeOAuthAuthorization and @c ::OIDErrorCodeOAuthToken + which define endpoint-specific subsets of OAuth codes. Those enum types are down-castable + to this one. + @see https://tools.ietf.org/html/rfc6749#section-11.4 + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ +typedef NS_ENUM(NSInteger, OIDErrorCodeOAuth) { + + /*! @remarks invalid_request + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthInvalidRequest = -2, + + /*! @remarks unauthorized_client + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthUnauthorizedClient = -3, + + /*! @remarks access_denied + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAccessDenied = -4, + + /*! @remarks unsupported_response_type + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthUnsupportedResponseType = -5, + + /*! @remarks invalid_scope + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthInvalidScope = -6, + + /*! @remarks server_error + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthServerError = -7, + + /*! @remarks temporarily_unavailable + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthTemporarilyUnavailable = -8, + + /*! @remarks invalid_client + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthInvalidClient = -9, + + /*! @remarks invalid_grant + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthInvalidGrant = -10, + + /*! @remarks unsupported_grant_type + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthUnsupportedGrantType = -11, + + /*! @remarks invalid_redirect_uri + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError + */ + OIDErrorCodeOAuthInvalidRedirectURI = -12, + + /*! @remarks invalid_client_metadata + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError + */ + OIDErrorCodeOAuthInvalidClientMetadata = -13, + + /*! @brief An authorization error occurring on the client rather than the server. For example, + due to a state mismatch or misconfiguration. Should be treated as an unrecoverable + authorization error. + */ + OIDErrorCodeOAuthClientError = -0xEFFF, + + /*! @brief An OAuth error not known to this library + @discussion Indicates an OAuth error as per RFC6749, but the error code was not in our + list. It could be a custom error code, or one from an OAuth extension. See the "error" key + of the \NSError_userInfo property. Such errors are assumed to invalidate the + authentication state + */ + OIDErrorCodeOAuthOther = -0xF000, +}; + +/*! @brief The error codes for the @c ::OIDOAuthAuthorizationErrorDomain error domain + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ +typedef NS_ENUM(NSInteger, OIDErrorCodeOAuthAuthorization) { + /*! @remarks invalid_request + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAuthorizationInvalidRequest = OIDErrorCodeOAuthInvalidRequest, + + /*! @remarks unauthorized_client + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAuthorizationUnauthorizedClient = OIDErrorCodeOAuthUnauthorizedClient, + + /*! @remarks access_denied + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAuthorizationAccessDenied = + OIDErrorCodeOAuthAccessDenied, + + /*! @remarks unsupported_response_type + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAuthorizationUnsupportedResponseType = + OIDErrorCodeOAuthUnsupportedResponseType, + + /*! @brief Indicates a network error or server error occurred. + @remarks invalid_scope + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAuthorizationAuthorizationInvalidScope = OIDErrorCodeOAuthInvalidScope, + + /*! @brief Indicates a server error occurred. + @remarks server_error + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAuthorizationServerError = OIDErrorCodeOAuthServerError, + + /*! @remarks temporarily_unavailable + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAuthorizationTemporarilyUnavailable = OIDErrorCodeOAuthTemporarilyUnavailable, + + /*! @brief An authorization error occurring on the client rather than the server. For example, + due to a state mismatch or client misconfiguration. Should be treated as an unrecoverable + authorization error. + */ + OIDErrorCodeOAuthAuthorizationClientError = OIDErrorCodeOAuthClientError, + + /*! @brief An authorization OAuth error not known to this library + @discussion this indicates an OAuth error as per RFC6749, but the error code was not in our + list. It could be a custom error code, or one from an OAuth extension. See the "error" key + of the \NSError_userInfo property. We assume such errors are not transient. + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + */ + OIDErrorCodeOAuthAuthorizationOther = OIDErrorCodeOAuthOther, +}; + + +/*! @brief The error codes for the @c ::OIDOAuthTokenErrorDomain error domain + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ +typedef NS_ENUM(NSInteger, OIDErrorCodeOAuthToken) { + /*! @remarks invalid_request + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthTokenInvalidRequest = OIDErrorCodeOAuthInvalidRequest, + + /*! @remarks invalid_client + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthTokenInvalidClient = OIDErrorCodeOAuthInvalidClient, + + /*! @remarks invalid_grant + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthTokenInvalidGrant = OIDErrorCodeOAuthInvalidGrant, + + /*! @remarks unauthorized_client + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthTokenUnauthorizedClient = OIDErrorCodeOAuthUnauthorizedClient, + + /*! @remarks unsupported_grant_type + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthTokenUnsupportedGrantType = OIDErrorCodeOAuthUnsupportedGrantType, + + /*! @remarks invalid_scope + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthTokenInvalidScope = OIDErrorCodeOAuthInvalidScope, + + /*! @brief An unrecoverable token error occurring on the client rather than the server. + */ + OIDErrorCodeOAuthTokenClientError = OIDErrorCodeOAuthClientError, + + /*! @brief A token endpoint OAuth error not known to this library + @discussion this indicates an OAuth error as per RFC6749, but the error code was not in our + list. It could be a custom error code, or one from an OAuth extension. See the "error" key + of the \NSError_userInfo property. We assume such errors are not transient. + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthTokenOther = OIDErrorCodeOAuthOther, +}; + +/*! @brief The error codes for the @c ::OIDOAuthRegistrationErrorDomain error domain + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError + */ +typedef NS_ENUM(NSInteger, OIDErrorCodeOAuthRegistration) { + /*! @remarks invalid_request + @see http://tools.ietf.org/html/rfc6750#section-3.1 + */ + OIDErrorCodeOAuthRegistrationInvalidRequest = OIDErrorCodeOAuthInvalidRequest, + + /*! @remarks invalid_redirect_uri + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError + */ + OIDErrorCodeOAuthRegistrationInvalidRedirectURI = OIDErrorCodeOAuthInvalidRedirectURI, + + /*! @remarks invalid_client_metadata + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError + */ + OIDErrorCodeOAuthRegistrationInvalidClientMetadata = OIDErrorCodeOAuthInvalidClientMetadata, + + /*! @brief An unrecoverable token error occurring on the client rather than the server. + */ + OIDErrorCodeOAuthRegistrationClientError = OIDErrorCodeOAuthClientError, + + /*! @brief A registration endpoint OAuth error not known to this library + @discussion this indicates an OAuth error, but the error code was not in our + list. It could be a custom error code, or one from an OAuth extension. See the "error" key + of the \NSError_userInfo property. We assume such errors are not transient. + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ + OIDErrorCodeOAuthRegistrationOther = OIDErrorCodeOAuthOther, +}; + + +/*! @brief The exception text for the exception which occurs when a + @c OIDExternalUserAgentSession receives a message after it has already completed. + */ +extern NSString *const OIDOAuthExceptionInvalidAuthorizationFlow; + +/*! @brief The text for the exception which occurs when a Token Request is constructed + with a null redirectURL for a grant_type that requires a nonnull Redirect + */ +extern NSString *const OIDOAuthExceptionInvalidTokenRequestNullRedirectURL; + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDError.m b/Pods/AppAuth/Source/AppAuthCore/OIDError.m new file mode 100644 index 00000000..87c8623e --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDError.m @@ -0,0 +1,45 @@ +/*! @file OIDError.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDError.h" + +NSString *const OIDGeneralErrorDomain = @"org.openid.appauth.general"; + +NSString *const OIDOAuthTokenErrorDomain = @"org.openid.appauth.oauth_token"; + +NSString *const OIDOAuthAuthorizationErrorDomain = @"org.openid.appauth.oauth_authorization"; + +NSString *const OIDOAuthRegistrationErrorDomain = @"org.openid.appauth.oauth_registration"; + +NSString *const OIDResourceServerAuthorizationErrorDomain = @"org.openid.appauth.resourceserver"; + +NSString *const OIDHTTPErrorDomain = @"org.openid.appauth.remote-http"; + +NSString *const OIDOAuthExceptionInvalidAuthorizationFlow = @"An OAuth redirect was sent to a " + "OIDExternalUserAgentSession after it already completed."; + +NSString *const OIDOAuthExceptionInvalidTokenRequestNullRedirectURL = @"A OIDTokenRequest was " + "created with a grant_type that requires a redirectURL, but a null redirectURL was given"; + +NSString *const OIDOAuthErrorResponseErrorKey = @"OIDOAuthErrorResponseErrorKey"; + +NSString *const OIDOAuthErrorFieldError = @"error"; + +NSString *const OIDOAuthErrorFieldErrorDescription = @"error_description"; + +NSString *const OIDOAuthErrorFieldErrorURI = @"error_uri"; diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDErrorUtilities.h b/Pods/AppAuth/Source/AppAuthCore/OIDErrorUtilities.h new file mode 100644 index 00000000..3380f6fe --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDErrorUtilities.h @@ -0,0 +1,107 @@ +/*! @file OIDErrorUtilities.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +#import "OIDError.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Convenience methods for creating standardized \NSError instances. + */ +@interface OIDErrorUtilities : NSObject + +/*! @brief Creates a standard \NSError from an @c ::OIDErrorCode and custom user info. + Automatically populates the localized error description. + @param code The error code. + @param underlyingError The underlying error which occurred, if applicable. + @param description A custom description, if applicable. + @return An \NSError representing the error code. + */ ++ (NSError *)errorWithCode:(OIDErrorCode)code + underlyingError:(nullable NSError *)underlyingError + description:(nullable NSString *)description; + +/*! @brief Creates a standard \NSError from an @c ::OIDErrorCode and custom user info. + Automatically populates the localized error description. + @param OAuthErrorDomain The OAuth error domain. Must be @c ::OIDOAuthAuthorizationErrorDomain or + @c ::OIDOAuthTokenErrorDomain. + @param errorResponse The dictionary from an OAuth error response (as per RFC6749 Section 5.2). + @param underlyingError The underlying error which occurred, if applicable. + @return An \NSError representing the OAuth error. + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ ++ (NSError *)OAuthErrorWithDomain:(NSString *)OAuthErrorDomain + OAuthResponse:(NSDictionary *)errorResponse + underlyingError:(nullable NSError *)underlyingError; + +/*! @brief Creates a \NSError indicating that the resource server responded with an authorization + error. + @param code Your error code. + @param errorResponse The resource server error response, if any. + @param underlyingError The underlying error which occurred, if applicable. + @return An \NSError representing the authorization error from the resource server. + */ ++ (NSError *)resourceServerAuthorizationErrorWithCode:(NSInteger)code + errorResponse:(nullable NSDictionary *)errorResponse + underlyingError:(nullable NSError *)underlyingError; + + +/*! @brief Creates a standard \NSError from an \NSHTTPURLResponse. Automatically + populates the localized error description with the response data associated with the + \NSHTTPURLResponse, if available. + @param HTTPURLResponse The response which indicates an error occurred. + @param data The response data associated with the response which should be converted to an + @c NSString assuming a UTF-8 encoding, if available. + @return An \NSError representing the error. + */ ++ (NSError *)HTTPErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPURLResponse + data:(nullable NSData *)data; + +/*! @brief Raises an exception with the given name as both the name, and the message. + @param name The name of the exception. + */ ++ (void)raiseException:(NSString *)name; + +/*! @brief Raises an exception with the given name and message. + @param name The name of the exception. + @param message The message of the exception. + */ ++ (void)raiseException:(NSString *)name message:(NSString *)message; + +/*! @brief Converts an OAuth error code into an @c ::OIDErrorCodeOAuth error code. + @param errorCode The OAuth error code. + @discussion Returns @c ::OIDErrorCodeOAuthOther if the string is not in AppAuth's list. + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ ++ (OIDErrorCodeOAuth)OAuthErrorCodeFromString:(NSString *)errorCode; + +/*! @brief Returns true if the given error domain is an OAuth error domain. + @param errorDomain The error domain to test. + @discussion An OAuth error domain is used for errors returned per RFC6749 sections 4.1.2.1 and + 5.2. Other errors, such as network errors can also occur but they will not have an OAuth + error domain. + @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + @see https://tools.ietf.org/html/rfc6749#section-5.2 + */ ++ (BOOL)isOAuthErrorDomain:(NSString*)errorDomain; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDErrorUtilities.m b/Pods/AppAuth/Source/AppAuthCore/OIDErrorUtilities.m new file mode 100644 index 00000000..3b3c0607 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDErrorUtilities.m @@ -0,0 +1,172 @@ +/*! @file OIDErrorUtilities.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDErrorUtilities.h" + +@implementation OIDErrorUtilities + ++ (NSError *)errorWithCode:(OIDErrorCode)code + underlyingError:(NSError *)underlyingError + description:(NSString *)description { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + if (description) { + userInfo[NSLocalizedDescriptionKey] = description; + } + // TODO: Populate localized description based on code. + NSError *error = [NSError errorWithDomain:OIDGeneralErrorDomain + code:code + userInfo:userInfo]; + return error; +} + ++ (BOOL)isOAuthErrorDomain:(NSString *)errorDomain { + return errorDomain == OIDOAuthRegistrationErrorDomain + || errorDomain == OIDOAuthAuthorizationErrorDomain + || errorDomain == OIDOAuthTokenErrorDomain; +} + ++ (NSError *)resourceServerAuthorizationErrorWithCode:(NSInteger)code + errorResponse:(nullable NSDictionary *)errorResponse + underlyingError:(nullable NSError *)underlyingError { + // builds the userInfo dictionary with the full OAuth response and other information + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (errorResponse) { + userInfo[OIDOAuthErrorResponseErrorKey] = errorResponse; + } + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + NSError *error = [NSError errorWithDomain:OIDResourceServerAuthorizationErrorDomain + code:code + userInfo:userInfo]; + return error; +} + ++ (NSError *)OAuthErrorWithDomain:(NSString *)oAuthErrorDomain + OAuthResponse:(NSDictionary *)errorResponse + underlyingError:(NSError *)underlyingError { + // not a valid OAuth error + if (![self isOAuthErrorDomain:oAuthErrorDomain] + || !errorResponse + || !errorResponse[OIDOAuthErrorFieldError] + || ![errorResponse[OIDOAuthErrorFieldError] isKindOfClass:[NSString class]]) { + return [[self class] errorWithCode:OIDErrorCodeNetworkError + underlyingError:underlyingError + description:underlyingError.localizedDescription]; + } + + // builds the userInfo dictionary with the full OAuth response and other information + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + userInfo[OIDOAuthErrorResponseErrorKey] = errorResponse; + if (underlyingError) { + userInfo[NSUnderlyingErrorKey] = underlyingError; + } + + NSString *oauthErrorCodeString = errorResponse[OIDOAuthErrorFieldError]; + NSString *oauthErrorMessage = nil; + if ([errorResponse[OIDOAuthErrorFieldErrorDescription] isKindOfClass:[NSString class]]) { + oauthErrorMessage = errorResponse[OIDOAuthErrorFieldErrorDescription]; + } else { + oauthErrorMessage = [errorResponse[OIDOAuthErrorFieldErrorDescription] description]; + } + NSString *oauthErrorURI = nil; + if ([errorResponse[OIDOAuthErrorFieldErrorURI] isKindOfClass:[NSString class]]) { + oauthErrorURI = errorResponse[OIDOAuthErrorFieldErrorURI]; + } else { + oauthErrorURI = [errorResponse[OIDOAuthErrorFieldErrorURI] description]; + } + + // builds the error description, using the information supplied by the server if possible + NSMutableString *description = [NSMutableString string]; + [description appendString:oauthErrorCodeString]; + if (oauthErrorMessage) { + [description appendString:@": "]; + [description appendString:oauthErrorMessage]; + } + if (oauthErrorURI) { + if ([description length] > 0) { + [description appendString:@" - "]; + } + [description appendString:oauthErrorURI]; + } + if ([description length] == 0) { + // backup description + [description appendFormat:@"OAuth error: %@ - https://tools.ietf.org/html/rfc6749#section-5.2", + oauthErrorCodeString]; + } + userInfo[NSLocalizedDescriptionKey] = description; + + // looks up the error code based on the "error" response param + OIDErrorCodeOAuth code = [[self class] OAuthErrorCodeFromString:oauthErrorCodeString]; + + NSError *error = [NSError errorWithDomain:oAuthErrorDomain + code:code + userInfo:userInfo]; + return error; +} + ++ (NSError *)HTTPErrorWithHTTPResponse:(NSHTTPURLResponse *)HTTPURLResponse + data:(nullable NSData *)data { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (data) { + NSString *serverResponse = + [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (serverResponse) { + userInfo[NSLocalizedDescriptionKey] = serverResponse; + } + } + NSError *serverError = + [NSError errorWithDomain:OIDHTTPErrorDomain + code:HTTPURLResponse.statusCode + userInfo:userInfo]; + return serverError; +} + ++ (OIDErrorCodeOAuth)OAuthErrorCodeFromString:(NSString *)errorCode { + NSDictionary *errorCodes = @{ + @"invalid_request": @(OIDErrorCodeOAuthInvalidRequest), + @"unauthorized_client": @(OIDErrorCodeOAuthUnauthorizedClient), + @"access_denied": @(OIDErrorCodeOAuthAccessDenied), + @"unsupported_response_type": @(OIDErrorCodeOAuthUnsupportedResponseType), + @"invalid_scope": @(OIDErrorCodeOAuthInvalidScope), + @"server_error": @(OIDErrorCodeOAuthServerError), + @"temporarily_unavailable": @(OIDErrorCodeOAuthTemporarilyUnavailable), + @"invalid_client": @(OIDErrorCodeOAuthInvalidClient), + @"invalid_grant": @(OIDErrorCodeOAuthInvalidGrant), + @"unsupported_grant_type": @(OIDErrorCodeOAuthUnsupportedGrantType), + }; + NSNumber *code = errorCodes[errorCode]; + if (code) { + return [code integerValue]; + } else { + return OIDErrorCodeOAuthOther; + } +} + ++ (void)raiseException:(NSString *)name { + [[self class] raiseException:name message:name]; +} + ++ (void)raiseException:(NSString *)name message:(NSString *)message { + [NSException raise:name format:@"%@", message]; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgent.h b/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgent.h new file mode 100644 index 00000000..c4eb0a90 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgent.h @@ -0,0 +1,53 @@ +/*! @file OIDExternalUserAgent.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 Google Inc. All Rights Reserved. + @copydetails + 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 + +@protocol OIDExternalUserAgentSession; +@protocol OIDExternalUserAgentRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @protocol OIDExternalUserAgent + @brief An external user-agent UI that presents displays the request to the user. Clients may + provide custom implementations of an external user-agent to customize the way the requests + are presented to the end user. + */ +@protocol OIDExternalUserAgent + +/*! @brief Presents the request in the external user-agent. + @param request The request to be presented in the external user-agent. + @param session The @c OIDExternalUserAgentSession instance that initiates presenting the UI. + Concrete implementations of a @c OIDExternalUserAgent may call + resumeExternalUserAgentFlowWithURL or failExternalUserAgentFlowWithError on session to either + resume or fail the request. + @return YES If the request UI was successfully presented to the user. + */ +- (BOOL)presentExternalUserAgentRequest:(id )request + session:(id)session; + +/*! @brief Dimisses the external user-agent and calls completion when the dismiss operation ends. + @param animated Whether or not the dismiss operation should be animated. + @remarks Has no effect if no UI is presented. + @param completion The block to be called when the dismiss operations ends + */ +- (void)dismissExternalUserAgentAnimated:(BOOL)animated completion:(void (^)(void))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgentRequest.h b/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgentRequest.h new file mode 100644 index 00000000..8ea40cb6 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgentRequest.h @@ -0,0 +1,37 @@ +/*! @file OIDExternalUserAgent.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + 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. + */ + +/*! @protocol OIDExternalUserAgent + @brief An interface that any external user-agent request may implement to use the + @c OIDExternalUserAgent flow. + */ +@protocol OIDExternalUserAgentRequest + +/*! @brief Method to create and return the complete request URL instance. + @return A @c NSURL instance which contains the URL to be opened in an external UI (i.e. browser) + */ +- (NSURL*)externalUserAgentRequestURL; + +/*! @brief If this external user-agent request has a redirect URL, this should return its scheme. + Since some external requests have optional callbacks (such as the end session endpoint), the + return value of this method is nullable. + @return A @c NSString instance that contains the scheme of a callback url, or nil if there is + no callback url for this request. + */ +- (NSString*)redirectScheme; +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgentSession.h b/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgentSession.h new file mode 100644 index 00000000..3b886a6c --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDExternalUserAgentSession.h @@ -0,0 +1,65 @@ +/*! @file OIDExternalUserAgentSession.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 The AppAuth Authors. All Rights Reserved. + @copydetails + 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. + */ + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents an in-flight external user-agent session. + */ +@protocol OIDExternalUserAgentSession + +/*! @brief Cancels the code flow session, invoking the request's callback with a cancelled error. + @remarks Has no effect if called more than once, or after a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message was received. + Will cause an error with code: @c ::OIDErrorCodeProgramCanceledAuthorizationFlow to be + passed to the @c callback block passed to + @c OIDAuthorizationService.presentAuthorizationRequest:presentingViewController:callback: + */ +- (void)cancel; + +/*! @brief Cancels the code flow session, invoking the request's callback with a cancelled error. + @remarks Has no effect if called more than once, or after a + @c OIDExternalUserAgentSession.resumeExternalUserAgentFlowWithURL: message was received. + Will cause an error with code: @c ::OIDErrorCodeProgramCanceledAuthorizationFlow to be + passed to the @c callback block passed to + @c OIDAuthorizationService.presentAuthorizationRequest:presentingViewController:callback: + @param completion The block to be called when the cancel operation ends + */ +- (void)cancelWithCompletion:(nullable void (^)(void))completion; + +/*! @brief Clients should call this method with the result of the external user-agent code flow if + it becomes available. + @param URL The redirect URL invoked by the server. + @discussion When the URL represented a valid response, implementations should clean up any + left-over UI state from the request, for example by closing the + \SFSafariViewController or loopback HTTP listener if those were used. The completion block + of the pending request should then be invoked. + @remarks Has no effect if called more than once, or after a @c cancel message was received. + @return YES if the passed URL matches the expected redirect URL and was consumed, NO otherwise. + */ +- (BOOL)resumeExternalUserAgentFlowWithURL:(NSURL *)URL; + +/*! @brief @c OIDExternalUserAgent or clients should call this method when the + external user-agent flow failed with a non-OAuth error. + @param error The error that is the reason for the failure of this external flow. + @remarks Has no effect if called more than once, or after a @c cancel message was received. + */ +- (void)failExternalUserAgentFlowWithError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDFieldMapping.h b/Pods/AppAuth/Source/AppAuthCore/OIDFieldMapping.h new file mode 100644 index 00000000..f0a56fef --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDFieldMapping.h @@ -0,0 +1,126 @@ +/*! @file OIDFieldMapping.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents a function which transforms incoming source values into instance variable + values. + */ +typedef _Nullable id(^OIDFieldMappingConversionFunction)(NSObject *_Nullable value); + +/*! @brief Describes the mapping of a key/value pair to an iVar with an optional conversion + function. + */ +@interface OIDFieldMapping : NSObject + +/*! @brief The name of the instance variable the field should be mapped to. + */ +@property(nonatomic, readonly) NSString *name; + +/*! @brief The type of the instance variable. + */ +@property(nonatomic, readonly) Class expectedType; + +/*! @brief An optional conversion function which specifies a transform from the incoming data to the + instance variable value. + */ +@property(nonatomic, readonly, nullable) OIDFieldMappingConversionFunction conversion; + +/*! @internal + @brief Unavailable. Please use initWithName:type:conversion:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief The designated initializer. + @param name The name of the instance variable the field should be mapped to. + @param type The type of the instance variable. + @param conversion An optional conversion function which specifies a transform from the incoming + data to the instance variable value. Used during the process performed by + @c OIDFieldMapping.remainingParametersWithMap:parameters:instance: but not during + encoding/decoding, since the encoded and decoded values should already be of the type + specified by the @c type parameter. + */ +- (instancetype)initWithName:(NSString *)name + type:(Class)type + conversion:(nullable OIDFieldMappingConversionFunction)conversion + NS_DESIGNATED_INITIALIZER; + +/*! @brief A convenience initializer. + @param name The name of the instance variable the field should be mapped to. + @param type The type of the instance variable. + */ +- (instancetype)initWithName:(NSString *)name + type:(Class)type; + +/*! @brief Performs a mapping of key/value pairs in an incoming parameters dictionary to instance + variables, returning a dictionary of parameter key/values which didn't map to instance + variables. + @param map A mapping of incoming keys to instance variables. + @param parameters Incoming key value pairs to map to an instance's variables. + @param instance The instance whose variables should be set based on the mapping. + @return A dictionary of parameter key/values which didn't map to instance variables. + */ ++ (NSDictionary *> *)remainingParametersWithMap: + (NSDictionary *)map + parameters:(NSDictionary *> *)parameters + instance:(id)instance; + +/*! @brief This helper method for @c NSCoding implementations performs a serialization of fields + defined in a field mapping. + @param aCoder An @c NSCoder instance to serialize instance variable values to. + @param map A mapping of keys to instance variables. + @param instance The instance whose variables should be serialized based on the mapping. + */ ++ (void)encodeWithCoder:(NSCoder *)aCoder + map:(NSDictionary *)map + instance:(id)instance; + +/*! @brief This helper method for @c NSCoding implementations performs a deserialization of + fields defined in a field mapping. + @param aCoder An @c NSCoder instance from which to deserialize instance variable values from. + @param map A mapping of keys to instance variables. + @param instance The instance whose variables should be deserialized based on the mapping. + */ ++ (void)decodeWithCoder:(NSCoder *)aCoder + map:(NSDictionary *)map + instance:(id)instance; + +/*! @brief Returns an @c NSSet of classes suitable for deserializing JSON content in an + @c NSSecureCoding context. + */ ++ (NSSet *)JSONTypes; + +/*! @brief Returns a function for converting an @c NSString to an @c NSURL. + */ ++ (OIDFieldMappingConversionFunction)URLConversion; + +/*! @brief Returns a function for converting an @c NSNumber number of seconds from now to an + @c NSDate. + */ ++ (OIDFieldMappingConversionFunction)dateSinceNowConversion; + +/*! @brief Returns a function for converting an @c NSNumber representing a unix time stamp to an + @c NSDate. + */ ++ (OIDFieldMappingConversionFunction)dateEpochConversion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDFieldMapping.m b/Pods/AppAuth/Source/AppAuthCore/OIDFieldMapping.m new file mode 100644 index 00000000..f8436560 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDFieldMapping.m @@ -0,0 +1,132 @@ +/*! @file OIDFieldMapping.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDFieldMapping.h" + +#import "OIDDefines.h" + +@implementation OIDFieldMapping + +- (nonnull instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithName:type:conversion:)) + +- (instancetype)initWithName:(NSString *)name + type:(Class)type { + return [self initWithName:name type:type conversion:nil]; +} + +- (instancetype)initWithName:(NSString *)name + type:(Class)type + conversion:(nullable OIDFieldMappingConversionFunction)conversion { + self = [super init]; + if (self) { + _name = [name copy]; + _expectedType = type; + _conversion = conversion; + } + return self; +} + ++ (NSDictionary *> *)remainingParametersWithMap: + (NSDictionary *)map + parameters:(NSDictionary *> *)parameters + instance:(id)instance { + NSMutableDictionary *additionalParameters = [NSMutableDictionary dictionary]; + for (NSString *key in parameters) { + NSObject *value = [parameters[key] copy]; + OIDFieldMapping *mapping = map[key]; + // If the field doesn't appear in the mapping, we add it to the additional parameters + // dictionary. + if (!mapping) { + additionalParameters[key] = value; + continue; + } + // If the field mapping specifies a conversion function, apply the conversion to the value. + if (mapping.conversion) { + value = mapping.conversion(value); + } + // Check the type of the value and make sure it matches the type we expected. If it doesn't we + // add the value to the additional parameters dictionary but don't assign the instance variable. + if (![value isKindOfClass:mapping.expectedType]) { + additionalParameters[key] = value; + continue; + } + // Assign the instance variable. + [instance setValue:value forKey:mapping.name]; + } + return additionalParameters; +} + ++ (void)encodeWithCoder:(NSCoder *)aCoder + map:(NSDictionary *)map + instance:(id)instance { + for (NSString *key in map) { + id value = [instance valueForKey:map[key].name]; + [aCoder encodeObject:value forKey:key]; + } +} + ++ (void)decodeWithCoder:(NSCoder *)aCoder + map:(NSDictionary *)map + instance:(id)instance { + for (NSString *key in map) { + OIDFieldMapping *mapping = map[key]; + id value = [aCoder decodeObjectOfClass:mapping.expectedType forKey:key]; + [instance setValue:value forKey:mapping.name]; + } +} + ++ (NSSet *)JSONTypes { + return [NSSet setWithArray:@[ + [NSDictionary class], + [NSArray class], + [NSString class], + [NSNumber class] + ]]; +} + ++ (OIDFieldMappingConversionFunction)URLConversion { + return ^id _Nullable(NSObject *_Nullable value) { + if ([value isKindOfClass:[NSString class]]) { + return [NSURL URLWithString:(NSString *)value]; + } + return value; + }; +} + ++ (OIDFieldMappingConversionFunction)dateSinceNowConversion { + return ^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSinceNow:[valueAsNumber longLongValue]]; + }; +} + ++ (OIDFieldMappingConversionFunction)dateEpochConversion { + return ^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *) value; + return [NSDate dateWithTimeIntervalSince1970:[valueAsNumber longLongValue]]; + }; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDGrantTypes.h b/Pods/AppAuth/Source/AppAuthCore/OIDGrantTypes.h new file mode 100644 index 00000000..6e650047 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDGrantTypes.h @@ -0,0 +1,40 @@ +/*! @file OIDGrantTypes.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +/*! @brief For exchanging an authorization code for an access token. + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +extern NSString *const OIDGrantTypeAuthorizationCode; + +/*! @brief For refreshing an access token with a refresh token. + @see https://tools.ietf.org/html/rfc6749#section-6 + */ +extern NSString *const OIDGrantTypeRefreshToken; + +/*! @brief For obtaining an access token with a username and password. + @see https://tools.ietf.org/html/rfc6749#section-4.3.2 + */ +extern NSString *const OIDGrantTypePassword; + +/*! @brief For obtaining an access token from the token endpoint using client credentials. + @see https://tools.ietf.org/html/rfc6749#section-3.2.1 + @see https://tools.ietf.org/html/rfc6749#section-4.4.2 + */ +extern NSString *const OIDGrantTypeClientCredentials; diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDGrantTypes.m b/Pods/AppAuth/Source/AppAuthCore/OIDGrantTypes.m new file mode 100644 index 00000000..2b193437 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDGrantTypes.m @@ -0,0 +1,27 @@ +/*! @file OIDGrantTypes.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDGrantTypes.h" + +NSString *const OIDGrantTypeAuthorizationCode = @"authorization_code"; + +NSString *const OIDGrantTypeRefreshToken = @"refresh_token"; + +NSString *const OIDGrantTypePassword = @"password"; + +NSString *const OIDGrantTypeClientCredentials = @"client_credentials"; diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDIDToken.h b/Pods/AppAuth/Source/AppAuthCore/OIDIDToken.h new file mode 100644 index 00000000..6fe84d7f --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDIDToken.h @@ -0,0 +1,91 @@ +/*! @file OIDIDToken.h + @brief AppAuth iOS SDK + @copyright + Copyright 2017 Google Inc. All Rights Reserved. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A convenience class that parses an ID Token and extracts the claims _but does not_ + verify its signature. AppAuth only supports the OpenID Code flow, meaning ID Tokens + received by AppAuth are sent from the token endpoint on a TLS protected channel, + offering some assurances as to the origin of the token. You may wish to additionally + verify the ID Token signature using a JWT signature verification library of your + choosing. + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + @see https://tools.ietf.org/html/rfc7519 + @see https://jwt.io/ + */ +@interface OIDIDToken : NSObject + +/*! @internal + @brief Unavailable. Please use @c initWithAuthorizationResponse:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Parses the given ID Token string. + @param idToken The ID Token spring. + */ +- (nullable instancetype)initWithIDTokenString:(NSString *)idToken; + +/*! @brief The header JWT values. + */ +@property(nonatomic, readonly) NSDictionary *header; + +/*! @brief All ID Token claims. + */ +@property(nonatomic, readonly) NSDictionary *claims; + +/*! @brief Issuer Identifier for the Issuer of the response. + @remarks iss + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSURL *issuer; + +/*! @brief Subject Identifier. + @remarks sub + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSString *subject; + +/*! @brief Audience(s) that this ID Token is intended for. + @remarks aud + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSArray *audience; + +/*! @brief Expiration time on or after which the ID Token MUST NOT be accepted for processing. + @remarks exp + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSDate *expiresAt; + +/*! @brief Time at which the JWT was issued. + @remarks iat + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly) NSDate *issuedAt; + +/*! @brief String value used to associate a Client session with an ID Token, and to mitigate replay + attacks. + @remarks nonce + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + */ +@property(nonatomic, readonly, nullable) NSString *nonce; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDIDToken.m b/Pods/AppAuth/Source/AppAuthCore/OIDIDToken.m new file mode 100644 index 00000000..57a7324e --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDIDToken.m @@ -0,0 +1,149 @@ +/*! @file OIDIDToken.m + @brief AppAuth iOS SDK + @copyright + Copyright 2017 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDIDToken.h" + +/*! Field keys associated with an ID Token. */ +static NSString *const kIssKey = @"iss"; +static NSString *const kSubKey = @"sub"; +static NSString *const kAudKey = @"aud"; +static NSString *const kExpKey = @"exp"; +static NSString *const kIatKey = @"iat"; +static NSString *const kNonceKey = @"nonce"; + +#import "OIDFieldMapping.h" + +@implementation OIDIDToken + +- (instancetype)initWithIDTokenString:(NSString *)idToken { + self = [super init]; + NSArray *sections = [idToken componentsSeparatedByString:@"."]; + + // The header and claims sections are required. + if (sections.count <= 1) { + return nil; + } + + _header = [[self class] parseJWTSection:sections[0]]; + _claims = [[self class] parseJWTSection:sections[1]]; + if (!_header || !_claims) { + return nil; + } + + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:_claims + instance:self]; + + // Required fields. + if (!_issuer || !_audience || !_subject || !_expiresAt || !_issuedAt) { + return nil; + } + + return self; +} + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + + fieldMap[kIssKey] = + [[OIDFieldMapping alloc] initWithName:@"_issuer" + type:[NSURL class] + conversion:[OIDFieldMapping URLConversion]]; + fieldMap[kSubKey] = + [[OIDFieldMapping alloc] initWithName:@"_subject" type:[NSString class]]; + fieldMap[kAudKey] = + [[OIDFieldMapping alloc] initWithName:@"_audience" + type:[NSArray class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if ([value isKindOfClass:[NSArray class]]) { + return value; + } + if ([value isKindOfClass:[NSString class]]) { + return @[value]; + } + return nil; + }]; + fieldMap[kExpKey] = + [[OIDFieldMapping alloc] initWithName:@"_expiresAt" + type:[NSDate class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSince1970:valueAsNumber.longLongValue]; + }]; + fieldMap[kIatKey] = + [[OIDFieldMapping alloc] initWithName:@"_issuedAt" + type:[NSDate class] + conversion:^id _Nullable(NSObject *_Nullable value) { + if (![value isKindOfClass:[NSNumber class]]) { + return value; + } + NSNumber *valueAsNumber = (NSNumber *)value; + return [NSDate dateWithTimeIntervalSince1970:valueAsNumber.longLongValue]; + }]; + fieldMap[kNonceKey] = + [[OIDFieldMapping alloc] initWithName:@"_nonce" type:[NSString class]]; + }); + return fieldMap; +} + ++ (NSDictionary *)parseJWTSection:(NSString *)sectionString { + NSData *decodedData = [[self class] base64urlNoPaddingDecode:sectionString]; + + // Parses JSON. + NSError *error; + id object = [NSJSONSerialization JSONObjectWithData:decodedData options:0 error:&error]; + if (error) { + NSLog(@"Error %@ parsing token payload %@", error, sectionString); + } + if ([object isKindOfClass:[NSDictionary class]]) { + return (NSDictionary *)object; + } + + return nil; +} + ++ (NSData *)base64urlNoPaddingDecode:(NSString *)base64urlNoPaddingString { + NSMutableString *body = [base64urlNoPaddingString mutableCopy]; + + // Converts base64url to base64. + NSRange range = NSMakeRange(0, base64urlNoPaddingString.length); + [body replaceOccurrencesOfString:@"-" withString:@"+" options:NSLiteralSearch range:range]; + [body replaceOccurrencesOfString:@"_" withString:@"/" options:NSLiteralSearch range:range]; + + // Converts base64 no padding to base64 with padding + while (body.length % 4 != 0) { + [body appendString:@"="]; + } + + // Decodes base64 string. + NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:body options:0]; + return decodedData; +} + +@end + + diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationRequest.h b/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationRequest.h new file mode 100644 index 00000000..e509c60a --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationRequest.h @@ -0,0 +1,141 @@ +/*! @file OIDRegistrationRequest.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 The AppAuth for iOS Authors. All Rights Reserved. + @copydetails + 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 + +@class OIDAuthorizationResponse; +@class OIDServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents a registration request. + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest + */ +@interface OIDRegistrationRequest : NSObject + +/*! @brief The service's configuration. + @remarks This configuration specifies how to connect to a particular OAuth provider. + Configurations may be created manually, or via an OpenID Connect Discovery Document. + */ +@property(nonatomic, readonly) OIDServiceConfiguration *configuration; + +/*! @brief The initial access token to access the Client Registration Endpoint + (if required by the OpenID Provider). + @remarks OAuth 2.0 Access Token optionally issued by an Authorization Server granting + access to its Client Registration Endpoint. This token (if required) is + provisioned out of band. + @see Section 3 of OpenID Connect Dynamic Client Registration 1.0 + https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration + */ +@property(nonatomic, readonly) NSString *initialAccessToken; + +/*! @brief The application type to register, will always be 'native'. + @remarks application_type + @see https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata + */ +@property(nonatomic, readonly) NSString *applicationType; + +/*! @brief The client's redirect URI's. + @remarks redirect_uris + @see https://tools.ietf.org/html/rfc6749#section-3.1.2 + */ +@property(nonatomic, readonly) NSArray *redirectURIs; + +/*! @brief The response types to register for usage by this client. + @remarks response_types + @see http://openid.net/specs/openid-connect-core-1_0.html#Authentication + */ +@property(nonatomic, readonly, nullable) NSArray *responseTypes; + +/*! @brief The grant types to register for usage by this client. + @remarks grant_types + @see https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata + */ +@property(nonatomic, readonly, nullable) NSArray *grantTypes; + +/*! @brief The subject type to to request. + @remarks subject_type + @see http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes + */ +@property(nonatomic, readonly, nullable) NSString *subjectType; + +/*! @brief The client authentication method to use at the token endpoint. + @remarks token_endpoint_auth_method + @see http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + */ +@property(nonatomic, readonly, nullable) NSString *tokenEndpointAuthenticationMethod; + +/*! @brief The client's additional token request parameters. + */ +@property(nonatomic, readonly, nullable) NSDictionary *additionalParameters; + +/*! @internal + @brief Unavailable. Please use initWithConfiguration + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Create a Client Registration Request to an OpenID Provider that supports open Dynamic + Registration. + @param configuration The service's configuration. + @param redirectURIs The redirect URIs to register for the client. + @param responseTypes The response types to register for the client. + @param grantTypes The grant types to register for the client. + @param subjectType The subject type to register for the client. + @param tokenEndpointAuthMethod The token endpoint authentication method to register for the + client. + @param additionalParameters The client's additional registration request parameters. + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + redirectURIs:(NSArray *)redirectURIs + responseTypes:(nullable NSArray *)responseTypes + grantTypes:(nullable NSArray *)grantTypes + subjectType:(nullable NSString *)subjectType + tokenEndpointAuthMethod:(nullable NSString *)tokenEndpointAuthMethod + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Designated initializer. + @param configuration The service's configuration. + @param redirectURIs The redirect URIs to register for the client. + @param responseTypes The response types to register for the client. + @param grantTypes The grant types to register for the client. + @param subjectType The subject type to register for the client. + @param tokenEndpointAuthMethod The token endpoint authentication method to register for the + client. + @param initialAccessToken The initial access token to access the Client Registration Endpoint + (if required by the OpenID Provider). + @param additionalParameters The client's additional registration request parameters. + @see https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + redirectURIs:(NSArray *)redirectURIs + responseTypes:(nullable NSArray *)responseTypes + grantTypes:(nullable NSArray *)grantTypes + subjectType:(nullable NSString *)subjectType + tokenEndpointAuthMethod:(nullable NSString *)tokenEndpointAuthMethod + initialAccessToken:(nullable NSString *)initialAccessToken + additionalParameters:(nullable NSDictionary *)additionalParameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Constructs an @c NSURLRequest representing the registration request. + @return An @c NSURLRequest representing the registration request. + */ +- (NSURLRequest *)URLRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationRequest.m b/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationRequest.m new file mode 100644 index 00000000..9efd18fd --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationRequest.m @@ -0,0 +1,248 @@ +/*! @file OIDRegistrationRequest.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 The AppAuth for iOS Authors. All Rights Reserved. + @copydetails + 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 "OIDRegistrationRequest.h" + +#import "OIDClientMetadataParameters.h" +#import "OIDDefines.h" +#import "OIDServiceConfiguration.h" + +/*! @brief The key for the @c configuration property for @c NSSecureCoding + */ +static NSString *const kConfigurationKey = @"configuration"; + +/*! @brief The key for the @c initialAccessToken property for @c NSSecureCoding + */ +static NSString *const kInitialAccessToken = @"initial_access_token"; + +/*! @brief Key used to encode the @c redirectURIs property for @c NSSecureCoding + */ +static NSString *const kRedirectURIsKey = @"redirect_uris"; + +/*! @brief The key for the @c responseTypes property for @c NSSecureCoding. + */ +static NSString *const kResponseTypesKey = @"response_types"; + +/*! @brief Key used to encode the @c grantType property for @c NSSecureCoding + */ +static NSString *const kGrantTypesKey = @"grant_types"; + +/*! @brief Key used to encode the @c subjectType property for @c NSSecureCoding + */ +static NSString *const kSubjectTypeKey = @"subject_type"; + +/*! @brief Key used to encode the @c additionalParameters property for + @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +@implementation OIDRegistrationRequest + +#pragma mark - Initializers + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithConfiguration: + redirectURIs: + responseTypes: + grantTypes: + subjectType: + tokenEndpointAuthMethod: + additionalParameters:) + ) + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + redirectURIs:(NSArray *)redirectURIs + responseTypes:(nullable NSArray *)responseTypes + grantTypes:(nullable NSArray *)grantTypes + subjectType:(nullable NSString *)subjectType + tokenEndpointAuthMethod:(nullable NSString *)tokenEndpointAuthenticationMethod + additionalParameters:(nullable NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + redirectURIs:redirectURIs + responseTypes:responseTypes + grantTypes:grantTypes + subjectType:subjectType + tokenEndpointAuthMethod:tokenEndpointAuthenticationMethod + initialAccessToken:nil + additionalParameters:additionalParameters]; +} + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + redirectURIs:(NSArray *)redirectURIs + responseTypes:(nullable NSArray *)responseTypes + grantTypes:(nullable NSArray *)grantTypes + subjectType:(nullable NSString *)subjectType + tokenEndpointAuthMethod:(nullable NSString *)tokenEndpointAuthenticationMethod + initialAccessToken:(nullable NSString *)initialAccessToken + additionalParameters:(nullable NSDictionary *)additionalParameters { + self = [super init]; + if (self) { + _configuration = [configuration copy]; + _initialAccessToken = [initialAccessToken copy]; + _redirectURIs = [redirectURIs copy]; + _responseTypes = [responseTypes copy]; + _grantTypes = [grantTypes copy]; + _subjectType = [subjectType copy]; + _tokenEndpointAuthenticationMethod = [tokenEndpointAuthenticationMethod copy]; + _additionalParameters = + [[NSDictionary alloc] initWithDictionary:additionalParameters copyItems:YES]; + + _applicationType = OIDApplicationTypeNative; + } + return self; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDServiceConfiguration *configuration = + [aDecoder decodeObjectOfClass:[OIDServiceConfiguration class] + forKey:kConfigurationKey]; + NSString *initialAccessToken = [aDecoder decodeObjectOfClass:[NSString class] + forKey:kInitialAccessToken]; + NSArray *redirectURIs = [aDecoder decodeObjectOfClass:[NSArray class] + forKey:kRedirectURIsKey]; + NSArray *responseTypes = [aDecoder decodeObjectOfClass:[NSArray class] + forKey:kResponseTypesKey]; + NSArray *grantTypes = [aDecoder decodeObjectOfClass:[NSArray class] + forKey:kGrantTypesKey]; + NSString *subjectType = [aDecoder decodeObjectOfClass:[NSString class] + forKey:kSubjectTypeKey]; + NSString *tokenEndpointAuthenticationMethod = + [aDecoder decodeObjectOfClass:[NSString class] + forKey:OIDTokenEndpointAuthenticationMethodParam]; + NSSet *additionalParameterCodingClasses = [NSSet setWithArray:@[ [NSDictionary class], + [NSString class] ]]; + NSDictionary *additionalParameters = + [aDecoder decodeObjectOfClasses:additionalParameterCodingClasses + forKey:kAdditionalParametersKey]; + self = [self initWithConfiguration:configuration + redirectURIs:redirectURIs + responseTypes:responseTypes + grantTypes:grantTypes + subjectType:subjectType + tokenEndpointAuthMethod:tokenEndpointAuthenticationMethod + initialAccessToken:initialAccessToken + additionalParameters:additionalParameters]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_configuration forKey:kConfigurationKey]; + [aCoder encodeObject:_initialAccessToken forKey:kInitialAccessToken]; + [aCoder encodeObject:_redirectURIs forKey:kRedirectURIsKey]; + [aCoder encodeObject:_responseTypes forKey:kResponseTypesKey]; + [aCoder encodeObject:_grantTypes forKey:kGrantTypesKey]; + [aCoder encodeObject:_subjectType forKey:kSubjectTypeKey]; + [aCoder encodeObject:_tokenEndpointAuthenticationMethod + forKey:OIDTokenEndpointAuthenticationMethodParam]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + NSURLRequest *request = [self URLRequest]; + NSString *requestBody = [[NSString alloc] initWithData:request.HTTPBody + encoding:NSUTF8StringEncoding]; + return [NSString stringWithFormat:@"<%@: %p, request: >", + NSStringFromClass([self class]), + (void *)self, + request.URL, + requestBody]; +} + +- (NSURLRequest *)URLRequest { + static NSString *const kHTTPPost = @"POST"; + static NSString *const kBearer = @"Bearer"; + static NSString *const kHTTPContentTypeHeaderKey = @"Content-Type"; + static NSString *const kHTTPContentTypeHeaderValue = @"application/json"; + static NSString *const kHTTPAuthorizationHeaderKey = @"Authorization"; + + NSData *postBody = [self JSONString]; + if (!postBody) { + return nil; + } + + NSURL *registrationRequestURL = _configuration.registrationEndpoint; + NSMutableURLRequest *URLRequest = + [[NSURLRequest requestWithURL:registrationRequestURL] mutableCopy]; + URLRequest.HTTPMethod = kHTTPPost; + [URLRequest setValue:kHTTPContentTypeHeaderValue forHTTPHeaderField:kHTTPContentTypeHeaderKey]; + if (_initialAccessToken) { + NSString *value = [NSString stringWithFormat:@"%@ %@", kBearer, _initialAccessToken]; + [URLRequest setValue:value forHTTPHeaderField:kHTTPAuthorizationHeaderKey]; + } + URLRequest.HTTPBody = postBody; + return URLRequest; +} + +- (NSData *)JSONString { + // Dictionary with several kay/value pairs and the above array of arrays + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + NSMutableArray *redirectURIStrings = + [NSMutableArray arrayWithCapacity:[_redirectURIs count]]; + for (id obj in _redirectURIs) { + [redirectURIStrings addObject:[obj absoluteString]]; + } + dict[OIDRedirectURIsParam] = redirectURIStrings; + dict[OIDApplicationTypeParam] = _applicationType; + + if (_additionalParameters) { + // Add any additional parameters first to allow them + // to be overwritten by instance values + [dict addEntriesFromDictionary:_additionalParameters]; + } + if (_responseTypes) { + dict[OIDResponseTypesParam] = _responseTypes; + } + if (_grantTypes) { + dict[OIDGrantTypesParam] = _grantTypes; + } + if (_subjectType) { + dict[OIDSubjectTypeParam] = _subjectType; + } + if (_tokenEndpointAuthenticationMethod) { + dict[OIDTokenEndpointAuthenticationMethodParam] = _tokenEndpointAuthenticationMethod; + } + + NSError *error; + NSData *json = [NSJSONSerialization dataWithJSONObject:dict options:kNilOptions error:&error]; + if (json == nil || error != nil) { + return nil; + } + + return json; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationResponse.h b/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationResponse.h new file mode 100644 index 00000000..df623906 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationResponse.h @@ -0,0 +1,126 @@ +/*! @file OIDRegistrationResponse.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 The AppAuth for iOS Authors. All Rights Reserved. + @copydetails + 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 + +@class OIDRegistrationRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Parameter name for the client id. + */ +extern NSString *const OIDClientIDParam; + +/*! @brief Parameter name for the client id issuance timestamp. + */ +extern NSString *const OIDClientIDIssuedAtParam; + +/*! @brief Parameter name for the client secret. + */ +extern NSString *const OIDClientSecretParam; + +/*! @brief Parameter name for the client secret expiration time. + */ +extern NSString *const OIDClientSecretExpirestAtParam; + +/*! @brief Parameter name for the registration access token. + */ +extern NSString *const OIDRegistrationAccessTokenParam; + +/*! @brief Parameter name for the client configuration URI. + */ +extern NSString *const OIDRegistrationClientURIParam; + +/*! @brief Represents a registration response. + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse + */ +@interface OIDRegistrationResponse : NSObject + +/*! @brief The request which was serviced. + */ +@property(nonatomic, readonly) OIDRegistrationRequest *request; + +/*! @brief The registered client identifier. + @remarks client_id + @see https://tools.ietf.org/html/rfc6749#section-4 + @see https://tools.ietf.org/html/rfc6749#section-4.1.1 + */ +@property(nonatomic, readonly) NSString *clientID; + +/*! @brief Timestamp of when the client identifier was issued, if provided. + @remarks client_id_issued_at + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse + */ +@property(nonatomic, readonly, nullable) NSDate *clientIDIssuedAt; + +/*! @brief TThe client secret, which is part of the client credentials, if provided. + @remarks client_secret + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse + */ +@property(nonatomic, readonly, nullable) NSString *clientSecret; + +/*! @brief Timestamp of when the client credentials expires, if provided. + @remarks client_secret_expires_at + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse + */ +@property(nonatomic, readonly, nullable) NSDate *clientSecretExpiresAt; + +/*! @brief Client registration access token that can be used for subsequent operations upon the + client registration. + @remarks registration_access_token + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse + */ +@property(nonatomic, readonly, nullable) NSString *registrationAccessToken; + +/*! @brief Location of the client configuration endpoint, if provided. + @remarks registration_client_uri + @see https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse + */ +@property(nonatomic, readonly, nullable) NSURL *registrationClientURI; + +/*! @brief Client authentication method to use at the token endpoint, if provided. + @remarks token_endpoint_auth_method + @see http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication + */ +@property(nonatomic, readonly, nullable) NSString *tokenEndpointAuthenticationMethod; + +/*! @brief Additional parameters returned from the token server. + */ +@property(nonatomic, readonly, nullable) NSDictionary *> + *additionalParameters; + +/*! @internal + @brief Unavailable. Please use initWithRequest + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param request The serviced request. + @param parameters The decoded parameters returned from the Authorization Server. + @remarks Known parameters are extracted from the @c parameters parameter and the normative + properties are populated. Non-normative parameters are placed in the + @c #additionalParameters dictionary. + */ +- (instancetype)initWithRequest:(OIDRegistrationRequest *)request + parameters:(NSDictionary *> *)parameters + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationResponse.m b/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationResponse.m new file mode 100644 index 00000000..ec0411b7 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDRegistrationResponse.m @@ -0,0 +1,164 @@ +/*! @file OIDRegistrationResponse.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 The AppAuth for iOS Authors. All Rights Reserved. + @copydetails + 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 "OIDRegistrationResponse.h" + +#import "OIDClientMetadataParameters.h" +#import "OIDDefines.h" +#import "OIDFieldMapping.h" +#import "OIDRegistrationRequest.h" +#import "OIDTokenUtilities.h" + +NSString *const OIDClientIDParam = @"client_id"; +NSString *const OIDClientIDIssuedAtParam = @"client_id_issued_at"; +NSString *const OIDClientSecretParam = @"client_secret"; +NSString *const OIDClientSecretExpirestAtParam = @"client_secret_expires_at"; +NSString *const OIDRegistrationAccessTokenParam = @"registration_access_token"; +NSString *const OIDRegistrationClientURIParam = @"registration_client_uri"; + +/*! @brief Key used to encode the @c request property for @c NSSecureCoding + */ +static NSString *const kRequestKey = @"request"; + +/*! @brief Key used to encode the @c additionalParameters property for @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +@implementation OIDRegistrationResponse + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + fieldMap[OIDClientIDParam] = [[OIDFieldMapping alloc] initWithName:@"_clientID" + type:[NSString class]]; + fieldMap[OIDClientIDIssuedAtParam] = + [[OIDFieldMapping alloc] initWithName:@"_clientIDIssuedAt" + type:[NSDate class] + conversion:[OIDFieldMapping dateEpochConversion]]; + fieldMap[OIDClientSecretParam] = + [[OIDFieldMapping alloc] initWithName:@"_clientSecret" + type:[NSString class]]; + fieldMap[OIDClientSecretExpirestAtParam] = + [[OIDFieldMapping alloc] initWithName:@"_clientSecretExpiresAt" + type:[NSDate class] + conversion:[OIDFieldMapping dateEpochConversion]]; + fieldMap[OIDRegistrationAccessTokenParam] = + [[OIDFieldMapping alloc] initWithName:@"_registrationAccessToken" + type:[NSString class]]; + fieldMap[OIDRegistrationClientURIParam] = + [[OIDFieldMapping alloc] initWithName:@"_registrationClientURI" + type:[NSURL class] + conversion:[OIDFieldMapping URLConversion]]; + fieldMap[OIDTokenEndpointAuthenticationMethodParam] = + [[OIDFieldMapping alloc] initWithName:@"_tokenEndpointAuthenticationMethod" + type:[NSString class]]; + }); + return fieldMap; +} + + +#pragma mark - Initializers + +- (nonnull instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) + +- (instancetype)initWithRequest:(OIDRegistrationRequest *)request + parameters:(NSDictionary *> *)parameters { + self = [super init]; + if (self) { + _request = [request copy]; + NSDictionary *> *additionalParameters = + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:parameters + instance:self]; + _additionalParameters = additionalParameters; + + if ((_clientSecret && !_clientSecretExpiresAt) + || (!!_registrationClientURI != !!_registrationAccessToken)) { + // If client_secret is issued, client_secret_expires_at is REQUIRED, + // and the response MUST contain "[...] both a Client Configuration Endpoint + // and a Registration Access Token or neither of them" + return nil; + } + } + return self; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDRegistrationRequest *request = [aDecoder decodeObjectOfClass:[OIDRegistrationRequest class] + forKey:kRequestKey]; + self = [self initWithRequest:request + parameters:@{}]; + if (self) { + [OIDFieldMapping decodeWithCoder:aDecoder + map:[[self class] fieldMap] + instance:self]; + _additionalParameters = [aDecoder decodeObjectOfClasses:[OIDFieldMapping JSONTypes] + forKey:kAdditionalParametersKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [OIDFieldMapping encodeWithCoder:aCoder map:[[self class] fieldMap] instance:self]; + [aCoder encodeObject:_request forKey:kRequestKey]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, clientID: \"%@\", clientIDIssuedAt: %@, " + "clientSecret: %@, clientSecretExpiresAt: \"%@\", " + "registrationAccessToken: \"%@\", " + "registrationClientURI: \"%@\", " + "additionalParameters: %@, request: %@>", + NSStringFromClass([self class]), + (void *)self, + _clientID, + _clientIDIssuedAt, + [OIDTokenUtilities redact:_clientSecret], + _clientSecretExpiresAt, + [OIDTokenUtilities redact:_registrationAccessToken], + _registrationClientURI, + _additionalParameters, + _request]; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDResponseTypes.h b/Pods/AppAuth/Source/AppAuthCore/OIDResponseTypes.h new file mode 100644 index 00000000..405ef938 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDResponseTypes.h @@ -0,0 +1,31 @@ +/*! @file OIDResponseTypes.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +/*! @brief A constant for the standard OAuth2 Response Type of 'code'. + */ +extern NSString *const OIDResponseTypeCode; + +/*! @brief A constant for the standard OAuth2 Response Type of 'token'. + */ +extern NSString *const OIDResponseTypeToken; + +/*! @brief A constant for the standard OAuth2 Response Type of 'id_token'. + */ +extern NSString *const OIDResponseTypeIDToken; diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDResponseTypes.m b/Pods/AppAuth/Source/AppAuthCore/OIDResponseTypes.m new file mode 100644 index 00000000..78eaf187 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDResponseTypes.m @@ -0,0 +1,25 @@ +/*! @file OIDResponseTypes.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDResponseTypes.h" + +NSString *const OIDResponseTypeCode = @"code"; + +NSString *const OIDResponseTypeToken = @"token"; + +NSString *const OIDResponseTypeIDToken = @"id_token"; diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDScopeUtilities.h b/Pods/AppAuth/Source/AppAuthCore/OIDScopeUtilities.h new file mode 100644 index 00000000..c0a5190c --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDScopeUtilities.h @@ -0,0 +1,48 @@ +/*! @file OIDScopeUtilities.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Provides convenience methods for dealing with scope strings. + */ +@interface OIDScopeUtilities : NSObject + +/*! @internal + @brief Unavailable. This class should not be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Converts an array of scope strings to a single scope string per the OAuth 2 spec. + @param scopes An array of scope strings. + @return A space-delimited string of scopes. + @see https://tools.ietf.org/html/rfc6749#section-3.3 + */ ++ (NSString *)scopesWithArray:(NSArray *)scopes; + +/*! @brief Converts an OAuth 2 spec-compliant scope string to an array of scopes. + @param scopes An OAuth 2 spec-compliant scope string. + @return An array of scope strings. + @see https://tools.ietf.org/html/rfc6749#section-3.3 + */ ++ (NSArray *)scopesArrayWithString:(NSString *)scopes; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDScopeUtilities.m b/Pods/AppAuth/Source/AppAuthCore/OIDScopeUtilities.m new file mode 100644 index 00000000..a0bcb8c8 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDScopeUtilities.m @@ -0,0 +1,58 @@ +/*! @file OIDScopeUtilities.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDScopeUtilities.h" + +@implementation OIDScopeUtilities + +/*! @brief A character set with the characters NOT allowed in a scope name. + @see https://tools.ietf.org/html/rfc6749#section-3.3 + */ ++ (NSCharacterSet *)disallowedScopeCharacters { + static NSCharacterSet *disallowedCharacters; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableCharacterSet *allowedCharacters; + allowedCharacters = + [NSMutableCharacterSet characterSetWithRange:NSMakeRange(0x23, 0x5B - 0x23 + 1)]; + [allowedCharacters addCharactersInRange:NSMakeRange(0x5D, 0x7E - 0x5D + 1)]; + [allowedCharacters addCharactersInString:@"\x21"]; + disallowedCharacters = [allowedCharacters invertedSet]; + }); + return disallowedCharacters; +} + ++ (NSString *)scopesWithArray:(NSArray *)scopes { +#if !defined(NS_BLOCK_ASSERTIONS) + NSCharacterSet *disallowedCharacters = [self disallowedScopeCharacters]; + for (NSString *scope in scopes) { + NSAssert(scope.length, @"Found illegal empty scope string."); + NSAssert([scope rangeOfCharacterFromSet:disallowedCharacters].location == NSNotFound, + @"Found illegal character in scope string."); + } +#endif // !defined(NS_BLOCK_ASSERTIONS) + + NSString *scopeString = [scopes componentsJoinedByString:@" "]; + return scopeString; +} + ++ (NSArray *)scopesArrayWithString:(NSString *)scopes { + return [scopes componentsSeparatedByString:@" "]; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDScopes.h b/Pods/AppAuth/Source/AppAuthCore/OIDScopes.h new file mode 100644 index 00000000..da8bb189 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDScopes.h @@ -0,0 +1,46 @@ +/*! @file OIDScopes.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +/*! @brief Scope that indicates this request is an OpenID Connect request. + @see http://openid.net/specs/openid-connect-core-1_0.html#AuthRequestValidation + */ +extern NSString *const OIDScopeOpenID; + +/*! @brief This scope value requests access to the End-User's default profile Claims, which are: + name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, + website, gender, birthdate, zoneinfo, locale, and updated_at. + @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims + */ +extern NSString *const OIDScopeProfile; + +/*! @brief This scope value requests access to the email and email_verified Claims. + @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims + */ +extern NSString *const OIDScopeEmail; + +/*! @brief This scope value requests access to the address Claim. + @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims + */ +extern NSString *const OIDScopeAddress; + +/*! @brief This scope value requests access to the phone_number and phone_number_verified Claims. + @see http://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims + */ +extern NSString *const OIDScopePhone; diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDScopes.m b/Pods/AppAuth/Source/AppAuthCore/OIDScopes.m new file mode 100644 index 00000000..62dd707e --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDScopes.m @@ -0,0 +1,29 @@ +/*! @file OIDScopes.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDScopes.h" + +NSString *const OIDScopeOpenID = @"openid"; + +NSString *const OIDScopeProfile = @"profile"; + +NSString *const OIDScopeEmail = @"email"; + +NSString *const OIDScopeAddress = @"address"; + +NSString *const OIDScopePhone = @"phone"; diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDServiceConfiguration.h b/Pods/AppAuth/Source/AppAuthCore/OIDServiceConfiguration.h new file mode 100644 index 00000000..a072a478 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDServiceConfiguration.h @@ -0,0 +1,118 @@ +/*! @file OIDServiceConfiguration.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +@class OIDServiceConfiguration; +@class OIDServiceDiscovery; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief The type of block called when a @c OIDServiceConfiguration has been created + by loading a @c OIDServiceDiscovery from an @c NSURL. + */ +typedef void (^OIDServiceConfigurationCreated) + (OIDServiceConfiguration *_Nullable serviceConfiguration, + NSError *_Nullable error); + +/*! @brief Represents the information needed to construct a @c OIDAuthorizationService. + */ +@interface OIDServiceConfiguration : NSObject + +/*! @brief The authorization endpoint URI. + */ +@property(nonatomic, readonly) NSURL *authorizationEndpoint; + +/*! @brief The token exchange and refresh endpoint URI. + */ +@property(nonatomic, readonly) NSURL *tokenEndpoint; + +/*! @brief The OpenID Connect issuer. + */ +@property(nonatomic, readonly, nullable) NSURL *issuer; + +/*! @brief The dynamic client registration endpoint URI. + */ +@property(nonatomic, readonly, nullable) NSURL *registrationEndpoint; + +/*! @brief The end session logout endpoint URI. + */ +@property(nonatomic, readonly, nullable) NSURL *endSessionEndpoint; + +/*! @brief The discovery document. + */ +@property(nonatomic, readonly, nullable) OIDServiceDiscovery *discoveryDocument; + +/*! @internal + @brief Unavailable. Please use @c initWithAuthorizationEndpoint:tokenEndpoint: or + @c initWithDiscoveryDocument:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint; + +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + @param registrationEndpoint The dynamic client registration endpoint URI. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + registrationEndpoint:(nullable NSURL *)registrationEndpoint; + +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + @param issuer The OpenID Connect issuer. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer; + +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + @param issuer The OpenID Connect issuer. + @param registrationEndpoint The dynamic client registration endpoint URI. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint; + +/*! @param authorizationEndpoint The authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + @param issuer The OpenID Connect issuer. + @param registrationEndpoint The dynamic client registration endpoint URI. + @param endSessionEndpoint The end session endpoint (logout) URI. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint; + +/*! @param discoveryDocument The discovery document from which to extract the required OAuth + configuration. + */ +- (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *)discoveryDocument; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDServiceConfiguration.m b/Pods/AppAuth/Source/AppAuthCore/OIDServiceConfiguration.m new file mode 100644 index 00000000..ca48a8c3 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDServiceConfiguration.m @@ -0,0 +1,223 @@ +/*! @file OIDServiceConfiguration.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDServiceConfiguration.h" + +#import "OIDDefines.h" +#import "OIDErrorUtilities.h" +#import "OIDServiceDiscovery.h" + +/*! @brief The key for the @c authorizationEndpoint property. + */ +static NSString *const kAuthorizationEndpointKey = @"authorizationEndpoint"; + +/*! @brief The key for the @c tokenEndpoint property. + */ +static NSString *const kTokenEndpointKey = @"tokenEndpoint"; + +/*! @brief The key for the @c issuer property. + */ +static NSString *const kIssuerKey = @"issuer"; + +/*! @brief The key for the @c registrationEndpoint property. + */ +static NSString *const kRegistrationEndpointKey = @"registrationEndpoint"; + +/*! @brief The key for the @c endSessionEndpoint property. + */ +static NSString *const kEndSessionEndpointKey = @"endSessionEndpoint"; + +/*! @brief The key for the @c discoveryDocument property. + */ +static NSString *const kDiscoveryDocumentKey = @"discoveryDocument"; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDServiceConfiguration () + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint + discoveryDocument:(nullable OIDServiceDiscovery *)discoveryDocument + NS_DESIGNATED_INITIALIZER; + +@end + +@implementation OIDServiceConfiguration + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector( + initWithAuthorizationEndpoint: + tokenEndpoint:) + ) + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable OIDServiceDiscovery *)endSessionEndpoint + discoveryDocument:(nullable OIDServiceDiscovery *)discoveryDocument { + + self = [super init]; + if (self) { + _authorizationEndpoint = [authorizationEndpoint copy]; + _tokenEndpoint = [tokenEndpoint copy]; + _issuer = [issuer copy]; + _registrationEndpoint = [registrationEndpoint copy]; + _endSessionEndpoint = [endSessionEndpoint copy]; + _discoveryDocument = [discoveryDocument copy]; + } + return self; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:nil + registrationEndpoint:nil + endSessionEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + registrationEndpoint:(nullable NSURL *)registrationEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:nil + registrationEndpoint:registrationEndpoint + endSessionEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:issuer + registrationEndpoint:nil + endSessionEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:issuer + registrationEndpoint:registrationEndpoint + endSessionEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + issuer:(nullable NSURL *)issuer + registrationEndpoint:(nullable NSURL *)registrationEndpoint + endSessionEndpoint:(nullable NSURL *)endSessionEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:issuer + registrationEndpoint:registrationEndpoint + endSessionEndpoint:endSessionEndpoint + discoveryDocument:nil]; +} + +- (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *) discoveryDocument { + return [self initWithAuthorizationEndpoint:discoveryDocument.authorizationEndpoint + tokenEndpoint:discoveryDocument.tokenEndpoint + issuer:discoveryDocument.issuer + registrationEndpoint:discoveryDocument.registrationEndpoint + endSessionEndpoint:discoveryDocument.endSessionEndpoint + discoveryDocument:discoveryDocument]; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSURL *authorizationEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kAuthorizationEndpointKey]; + NSURL *tokenEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kTokenEndpointKey]; + NSURL *issuer = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kIssuerKey]; + NSURL *registrationEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kRegistrationEndpointKey]; + NSURL *endSessionEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kEndSessionEndpointKey]; + // We don't accept nil authorizationEndpoints or tokenEndpoints. + if (!authorizationEndpoint || !tokenEndpoint) { + return nil; + } + + OIDServiceDiscovery *discoveryDocument = [aDecoder decodeObjectOfClass:[OIDServiceDiscovery class] + forKey:kDiscoveryDocumentKey]; + + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + issuer:issuer + registrationEndpoint:registrationEndpoint + endSessionEndpoint:endSessionEndpoint + discoveryDocument:discoveryDocument]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_authorizationEndpoint forKey:kAuthorizationEndpointKey]; + [aCoder encodeObject:_tokenEndpoint forKey:kTokenEndpointKey]; + [aCoder encodeObject:_issuer forKey:kIssuerKey]; + [aCoder encodeObject:_registrationEndpoint forKey:kRegistrationEndpointKey]; + [aCoder encodeObject:_discoveryDocument forKey:kDiscoveryDocumentKey]; + [aCoder encodeObject:_endSessionEndpoint forKey:kEndSessionEndpointKey]; +} + +#pragma mark - description + +- (NSString *)description { + return [NSString stringWithFormat: + @"OIDServiceConfiguration authorizationEndpoint: %@, tokenEndpoint: %@, " + "registrationEndpoint: %@, endSessionEndpoint: %@, discoveryDocument: [%@]", + _authorizationEndpoint, + _tokenEndpoint, + _registrationEndpoint, + _endSessionEndpoint, + _discoveryDocument]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDServiceDiscovery.h b/Pods/AppAuth/Source/AppAuthCore/OIDServiceDiscovery.h new file mode 100644 index 00000000..57770083 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDServiceDiscovery.h @@ -0,0 +1,358 @@ +/*! @file OIDServiceDiscovery.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents an OpenID Connect 1.0 Discovery Document + @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata + */ +@interface OIDServiceDiscovery : NSObject + +/*! @brief The decoded OpenID Connect 1.0 Discovery Document as a dictionary. + */ +@property(nonatomic, readonly) NSDictionary *discoveryDictionary; + +/*! @brief REQUIRED. URL using the @c https scheme with no query or fragment component that the OP + asserts as its Issuer Identifier. If Issuer discovery is supported, this value MUST be + identical to the issuer value returned by WebFinger. This also MUST be identical to the + @c iss Claim value in ID Tokens issued from this Issuer. + @remarks issuer + @seealso https://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery + */ +@property(nonatomic, readonly) NSURL *issuer; + +/*! @brief REQUIRED. URL of the OP's OAuth 2.0 Authorization Endpoint. + @remarks authorization_endpoint + @seealso http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint + */ +@property(nonatomic, readonly) NSURL *authorizationEndpoint; + +/*! @brief URL of the OP's OAuth 2.0 Token Endpoint. This is REQUIRED unless only the Implicit Flow + is used. + @remarks token_endpoint + @seealso http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint + */ +@property(nonatomic, readonly) NSURL *tokenEndpoint; + +/*! @brief RECOMMENDED. URL of the OP's UserInfo Endpoint. This URL MUST use the https scheme and + MAY contain port, path, and query parameter components. + @remarks userinfo_endpoint + @seealso http://openid.net/specs/openid-connect-core-1_0.html#UserInfo + */ +@property(nonatomic, readonly, nullable) NSURL *userinfoEndpoint; + +/*! @brief REQUIRED. URL of the OP's JSON Web Key Set document. This contains the signing key(s) the + RP uses to validate signatures from the OP. The JWK Set MAY also contain the Server's + encryption key(s), which are used by RPs to encrypt requests to the Server. When both + signing and encryption keys are made available, a use (Key Use) parameter value is REQUIRED + for all keys in the referenced JWK Set to indicate each key's intended usage. Although some + algorithms allow the same key to be used for both signatures and encryption, doing so is NOT + RECOMMENDED, as it is less secure. The JWK x5c parameter MAY be used to provide X.509 + representations of keys provided. When used, the bare key values MUST still be present and + MUST match those in the certificate. + @remarks jwks_uri + @seealso http://tools.ietf.org/html/rfc7517 + */ +@property(nonatomic, readonly) NSURL *jwksURL; + +/*! @brief RECOMMENDED. URL of the OP's Dynamic Client Registration Endpoint. + @remarks registration_endpoint + @seealso http://openid.net/specs/openid-connect-registration-1_0.html + */ +@property(nonatomic, readonly, nullable) NSURL *registrationEndpoint; + +/* @brief OPTIONAL. URL of the OP's RP-Initiated Logout endpoint. + @remarks end_session_endpoint + @seealso http://openid.net/specs/openid-connect-session-1_0.html#OPMetadata + */ +@property(nonatomic, readonly, nullable) NSURL *endSessionEndpoint; + +/*! @brief RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that + this server supports. The server MUST support the openid scope value. Servers MAY choose not + to advertise some supported scope values even when this parameter is used, although those + defined in [OpenID.Core] SHOULD be listed, if supported. + @remarks scopes_supported + @seealso http://tools.ietf.org/html/rfc6749#section-3.3 + */ +@property(nonatomic, readonly, nullable) NSArray *scopesSupported; + +/*! @brief REQUIRED. JSON array containing a list of the OAuth 2.0 @c response_type values that this + OP supports. Dynamic OpenID Providers MUST support the @c code, @c id_token, and the token + @c id_token Response Type values. + @remarks response_types_supported + */ +@property(nonatomic, readonly) NSArray *responseTypesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the OAuth 2.0 @c response_mode values that this + OP supports, as specified in OAuth 2.0 Multiple Response Type Encoding Practices. If + omitted, the default for Dynamic OpenID Providers is @c ["query", "fragment"]. + @remarks response_modes_supported + @seealso http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html + */ +@property(nonatomic, readonly, nullable) NSArray *responseModesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the OAuth 2.0 Grant Type values that this OP + supports. Dynamic OpenID Providers MUST support the @c authorization_code and @c implicit + Grant Type values and MAY support other Grant Types. If omitted, the default value is + @c ["authorization_code", "implicit"]. + @remarks grant_types_supported + */ +@property(nonatomic, readonly, nullable) NSArray *grantTypesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the Authentication Context Class References + that this OP supports. + @remarks acr_values_supported + */ +@property(nonatomic, readonly, nullable) NSArray *acrValuesSupported; + +/*! @brief REQUIRED. JSON array containing a list of the Subject Identifier types that this OP + supports. Valid types include @c pairwise and @c public. + @remarks subject_types_supported + */ +@property(nonatomic, readonly) NSArray *subjectTypesSupported; + +/*! @brief REQUIRED. JSON array containing a list of the JWS signing algorithms (@c alg values) + supported by the OP for the ID Token to encode the Claims in a JWT. The algorithm @c RS256 + MUST be included. The value @c none MAY be supported, but MUST NOT be used unless the + Response Type used returns no ID Token from the Authorization Endpoint (such as when using + the Authorization Code Flow). + @remarks id_token_signing_alg_values_supported + @seealso https://tools.ietf.org/html/rfc7519 + */ +@property(nonatomic, readonly) NSArray *IDTokenSigningAlgorithmValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWE encryption algorithms (@c alg values) + supported by the OP for the ID Token to encode the Claims in a JWT. + @remarks id_token_encryption_alg_values_supported + @seealso https://tools.ietf.org/html/rfc7519 + */ +@property(nonatomic, readonly, nullable) + NSArray *IDTokenEncryptionAlgorithmValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWE encryption algorithms (@c enc values) + supported by the OP for the ID Token to encode the Claims in a JWT. + @remarks id_token_encryption_enc_values_supported + @seealso https://tools.ietf.org/html/rfc7519 + */ +@property(nonatomic, readonly, nullable) + NSArray *IDTokenEncryptionEncodingValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWS signing algorithms (@c alg values) + supported by the UserInfo Endpoint to encode the Claims in a JWT. The value none MAY be + included. + @remarks userinfo_signing_alg_values_supported + @seealso https://tools.ietf.org/html/rfc7515 + @seealso https://tools.ietf.org/html/rfc7518 + @seealso https://tools.ietf.org/html/rfc7519 + */ +@property(nonatomic, readonly, nullable) + NSArray *userinfoSigningAlgorithmValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWE encryption algorithms (alg values) + supported by the UserInfo Endpoint to encode the Claims in a JWT. + @remarks userinfo_encryption_alg_values_supported + @seealso https://tools.ietf.org/html/rfc7516 + @seealso https://tools.ietf.org/html/rfc7518 + @seealso https://tools.ietf.org/html/rfc7519 + */ +@property(nonatomic, readonly, nullable) + NSArray *userinfoEncryptionAlgorithmValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWE encryption algorithms (@c enc values) + supported by the UserInfo Endpoint to encode the Claims in a JWT. + @remarks userinfo_encryption_enc_values_supported + @seealso https://tools.ietf.org/html/rfc7519 + */ +@property(nonatomic, readonly, nullable) + NSArray *userinfoEncryptionEncodingValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWS signing algorithms (@c alg values) + supported by the OP for Request Objects, which are described in Section 6.1 of OpenID + Connect Core 1.0. These algorithms are used both when the Request Object is passed by value + (using the request parameter) and when it is passed by reference (using the @c request_uri + parameter). Servers SHOULD support @c none and @c RS256. + @remarks request_object_signing_alg_values_supported + @seealso http://openid.net/specs/openid-connect-core-1_0.html + */ +@property(nonatomic, readonly, nullable) + NSArray *requestObjectSigningAlgorithmValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWE encryption algorithms (@c alg values) + supported by the OP for Request Objects. These algorithms are used both when the Request + Object is passed by value and when it is passed by reference. + @remarks request_object_encryption_alg_values_supported + */ +@property(nonatomic, readonly, nullable) + NSArray *requestObjectEncryptionAlgorithmValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWE encryption algorithms (@c enc values) + supported by the OP for Request Objects. These algorithms are used both when the Request + Object is passed by value and when it is passed by reference. + @remarks request_object_encryption_enc_values_supported + */ +@property(nonatomic, readonly, nullable) + NSArray *requestObjectEncryptionEncodingValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of Client Authentication methods supported by this + Token Endpoint. The options are @c client_secret_post, @c client_secret_basic, + @c client_secret_jwt, and @c private_key_jwt, as described in Section 9 of OpenID Connect + Core 1.0. Other authentication methods MAY be defined by extensions. If omitted, the default + is @c client_secret_basic -- the HTTP Basic Authentication Scheme specified in Section 2.3.1 + of OAuth 2.0. + @remarks token_endpoint_auth_methods_supported + @seealso http://openid.net/specs/openid-connect-core-1_0.html + @seealso http://tools.ietf.org/html/rfc6749#section-2.3.1 + */ +@property(nonatomic, readonly, nullable) NSArray *tokenEndpointAuthMethodsSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the JWS signing algorithms (@c alg values) + supported by the Token Endpoint for the signature on the JWT used to authenticate the Client + at the Token Endpoint for the @c private_key_jwt and @c client_secret_jwt authentication + methods. Servers SHOULD support @c RS256. The value @c none MUST NOT be used. + @remarks token_endpoint_auth_signing_alg_values_supported + @seealso https://tools.ietf.org/html/rfc7519 + */ +@property(nonatomic, readonly, nullable) + NSArray *tokenEndpointAuthSigningAlgorithmValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the @c display parameter values that the OpenID + Provider supports. These values are described in Section 3.1.2.1 of OpenID Connect Core 1.0. + @remarks display_values_supported + @seealso http://openid.net/specs/openid-connect-core-1_0.html + */ +@property(nonatomic, readonly, nullable) NSArray *displayValuesSupported; + +/*! @brief OPTIONAL. JSON array containing a list of the Claim Types that the OpenID Provider + supports. These Claim Types are described in Section 5.6 of OpenID Connect Core 1.0. Values + defined by this specification are @c normal, @c aggregated, and @c distributed. If omitted, + the implementation supports only @c normal Claims. + @remarks claim_types_supported + @seealso http://openid.net/specs/openid-connect-core-1_0.html + */ +@property(nonatomic, readonly, nullable) NSArray *claimTypesSupported; + +/*! @brief RECOMMENDED. JSON array containing a list of the Claim Names of the Claims that the + OpenID Provider MAY be able to supply values for. Note that for privacy or other reasons, + this might not be an exhaustive list. + @remarks claims_supported + */ +@property(nonatomic, readonly, nullable) NSArray *claimsSupported; + +/*! @brief OPTIONAL. URL of a page containing human-readable information that developers might want + or need to know when using the OpenID Provider. In particular, if the OpenID Provider does + not support Dynamic Client Registration, then information on how to register Clients needs + to be provided in this documentation. + @remarks service_documentation + */ +@property(nonatomic, readonly, nullable) NSURL *serviceDocumentation; + +/*! @brief OPTIONAL. Languages and scripts supported for values in Claims being returned, + represented as a JSON array of BCP47 language tag values. Not all languages and scripts are + necessarily supported for all Claim values. + @remarks claims_locales_supported + @seealso http://tools.ietf.org/html/rfc5646 + */ +@property(nonatomic, readonly, nullable) NSArray *claimsLocalesSupported; + +/*! @brief OPTIONAL. Languages and scripts supported for the user interface, represented as a JSON + array of BCP47 language tag values. + @remarks ui_locales_supported + @seealso http://tools.ietf.org/html/rfc5646 + */ +@property(nonatomic, readonly, nullable) NSArray *UILocalesSupported; + +/*! @brief OPTIONAL. Boolean value specifying whether the OP supports use of the claims parameter, + with @c true indicating support. If omitted, the default value is @c false. + @remarks claims_parameter_supported + */ +@property(nonatomic, readonly) BOOL claimsParameterSupported; + +/*! @brief OPTIONAL. Boolean value specifying whether the OP supports use of the request parameter, + with @c true indicating support. If omitted, the default value is @c false. + @remarks request_parameter_supported + */ +@property(nonatomic, readonly) BOOL requestParameterSupported; + +/*! @brief OPTIONAL. Boolean value specifying whether the OP supports use of the @c request_uri + parameter, with true indicating support. If omitted, the default value is @c true. + @remarks request_uri_parameter_supported + */ +@property(nonatomic, readonly) BOOL requestURIParameterSupported; + +/*! @brief OPTIONAL. Boolean value specifying whether the OP requires any @c request_uri values used + to be pre-registered using the @c request_uris registration parameter. Pre-registration is + REQUIRED when the value is @c true. If omitted, the default value is @c false. + @remarks require_request_uri_registration + */ +@property(nonatomic, readonly) BOOL requireRequestURIRegistration; + +/*! @brief OPTIONAL. URL that the OpenID Provider provides to the person registering the Client to + read about the OP's requirements on how the Relying Party can use the data provided by the + OP. The registration process SHOULD display this URL to the person registering the Client if + it is given. + @remarks op_policy_uri + */ +@property(nonatomic, readonly, nullable) NSURL *OPPolicyURI; + +/*! @brief OPTIONAL. URL that the OpenID Provider provides to the person registering the Client to + read about OpenID Provider's terms of service. The registration process SHOULD display this + URL to the person registering the Client if it is given. + @remarks op_tos_uri + */ +@property(nonatomic, readonly, nullable) NSURL *OPTosURI; + +/*! @internal + @brief Unavailable. Please use @c initWithDictionary:error:, @c initWithJSON:error, or the + @c serviceDiscoveryWithURL:callback: factory method. + */ +- (nonnull instancetype)init NS_UNAVAILABLE; + +/*! @brief Decodes a OpenID Connect Discovery 1.0 JSON document. + @param serviceDiscoveryJSON An OpenID Connect Service Discovery document. + @param error If a required field is missing from the dictionary, an error with domain + @c ::OIDGeneralErrorDomain and code @c ::OIDErrorCodeInvalidDiscoveryDocument will be + returned. + */ +- (nullable instancetype)initWithJSON:(NSString *)serviceDiscoveryJSON + error:(NSError **_Nullable)error; + +/*! @brief Decodes a OpenID Connect Discovery 1.0 JSON document. + @param serviceDiscoveryJSONData An OpenID Connect Service Discovery document. + @param error If a required field is missing from the dictionary, an error with domain + @c ::OIDGeneralErrorDomain and code @c ::OIDErrorCodeInvalidDiscoveryDocument will be + returned. + */ +- (nullable instancetype)initWithJSONData:(NSData *)serviceDiscoveryJSONData + error:(NSError **_Nullable)error; + +/*! @brief Designated initializer. The dictionary keys should match the keys defined in the OpenID + Connect Discovery 1.0 standard for OpenID Provider Metadata. + @param serviceDiscoveryDictionary A dictionary representing an OpenID Connect Service Discovery + document. + @param error If a required field is missing from the dictionary, an error with domain + @c ::OIDGeneralErrorDomain and code @c ::OIDErrorCodeInvalidDiscoveryDocument will be + returned. + */ +- (nullable instancetype)initWithDictionary:(NSDictionary *)serviceDiscoveryDictionary + error:(NSError **_Nullable)error NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDServiceDiscovery.m b/Pods/AppAuth/Source/AppAuthCore/OIDServiceDiscovery.m new file mode 100644 index 00000000..ca81108a --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDServiceDiscovery.m @@ -0,0 +1,362 @@ +/*! @file OIDServiceDiscovery.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDServiceDiscovery.h" + +#import "OIDDefines.h" +#import "OIDErrorUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! Field keys associated with an OpenID Connect Discovery Document. */ +static NSString *const kIssuerKey = @"issuer"; +static NSString *const kAuthorizationEndpointKey = @"authorization_endpoint"; +static NSString *const kTokenEndpointKey = @"token_endpoint"; +static NSString *const kUserinfoEndpointKey = @"userinfo_endpoint"; +static NSString *const kJWKSURLKey = @"jwks_uri"; +static NSString *const kRegistrationEndpointKey = @"registration_endpoint"; +static NSString *const kEndSessionEndpointKey = @"end_session_endpoint"; +static NSString *const kScopesSupportedKey = @"scopes_supported"; +static NSString *const kResponseTypesSupportedKey = @"response_types_supported"; +static NSString *const kResponseModesSupportedKey = @"response_modes_supported"; +static NSString *const kGrantTypesSupportedKey = @"grant_types_supported"; +static NSString *const kACRValuesSupportedKey = @"acr_values_supported"; +static NSString *const kSubjectTypesSupportedKey = @"subject_types_supported"; +static NSString *const kIDTokenSigningAlgorithmValuesSupportedKey = + @"id_token_signing_alg_values_supported"; +static NSString *const kIDTokenEncryptionAlgorithmValuesSupportedKey = + @"id_token_encryption_alg_values_supported"; +static NSString *const kIDTokenEncryptionEncodingValuesSupportedKey = + @"id_token_encryption_enc_values_supported"; +static NSString *const kUserinfoSigningAlgorithmValuesSupportedKey = + @"userinfo_signing_alg_values_supported"; +static NSString *const kUserinfoEncryptionAlgorithmValuesSupportedKey = + @"userinfo_encryption_alg_values_supported"; +static NSString *const kUserinfoEncryptionEncodingValuesSupportedKey = + @"userinfo_encryption_enc_values_supported"; +static NSString *const kRequestObjectSigningAlgorithmValuesSupportedKey = + @"request_object_signing_alg_values_supported"; +static NSString *const kRequestObjectEncryptionAlgorithmValuesSupportedKey = + @"request_object_encryption_alg_values_supported"; +static NSString *const kRequestObjectEncryptionEncodingValuesSupported = + @"request_object_encryption_enc_values_supported"; +static NSString *const kTokenEndpointAuthMethodsSupportedKey = + @"token_endpoint_auth_methods_supported"; +static NSString *const kTokenEndpointAuthSigningAlgorithmValuesSupportedKey = + @"token_endpoint_auth_signing_alg_values_supported"; +static NSString *const kDisplayValuesSupportedKey = @"display_values_supported"; +static NSString *const kClaimTypesSupportedKey = @"claim_types_supported"; +static NSString *const kClaimsSupportedKey = @"claims_supported"; +static NSString *const kServiceDocumentationKey = @"service_documentation"; +static NSString *const kClaimsLocalesSupportedKey = @"claims_locales_supported"; +static NSString *const kUILocalesSupportedKey = @"ui_locales_supported"; +static NSString *const kClaimsParameterSupportedKey = @"claims_parameter_supported"; +static NSString *const kRequestParameterSupportedKey = @"request_parameter_supported"; +static NSString *const kRequestURIParameterSupportedKey = @"request_uri_parameter_supported"; +static NSString *const kRequireRequestURIRegistrationKey = @"require_request_uri_registration"; +static NSString *const kOPPolicyURIKey = @"op_policy_uri"; +static NSString *const kOPTosURIKey = @"op_tos_uri"; + +@implementation OIDServiceDiscovery { + NSDictionary *_discoveryDictionary; +} + +- (nonnull instancetype)init OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithDictionary:error:)) + +- (nullable instancetype)initWithJSON:(NSString *)serviceDiscoveryJSON error:(NSError **)error { + NSData *jsonData = [serviceDiscoveryJSON dataUsingEncoding:NSUTF8StringEncoding]; + return [self initWithJSONData:jsonData error:error]; +} + +- (nullable instancetype)initWithJSONData:(NSData *)serviceDiscoveryJSONData + error:(NSError **_Nullable)error { + NSError *jsonError; + NSDictionary *json = + [NSJSONSerialization JSONObjectWithData:serviceDiscoveryJSONData options:0 error:&jsonError]; + if (!json || jsonError) { + *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonError + description:jsonError.localizedDescription]; + return nil; + } + if (![json isKindOfClass:[NSDictionary class]]) { + *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeInvalidDiscoveryDocument + underlyingError:nil + description:@"Discovery document isn't a dictionary"]; + return nil; + } + + return [self initWithDictionary:json error:error]; +} + +- (nullable instancetype)initWithDictionary:(NSDictionary *)serviceDiscoveryDictionary + error:(NSError **_Nullable)error { + if (![[self class] dictionaryHasRequiredFields:serviceDiscoveryDictionary error:error]) { + return nil; + } + self = [super init]; + if (self) { + _discoveryDictionary = [serviceDiscoveryDictionary copy]; + } + return self; +} + +#pragma mark - + +/*! @brief Checks to see if the specified dictionary contains the required fields. + @discussion This test is not meant to provide semantic analysis of the document (eg. fields + where the value @c none is not an allowed option would not cause this method to fail if + their value was @c none.) We are just testing to make sure we can meet the nullability + contract we promised in the header. + */ ++ (BOOL)dictionaryHasRequiredFields:(NSDictionary *)dictionary + error:(NSError **_Nullable)error { + static NSString *const kMissingFieldErrorText = @"Missing field: %@"; + static NSString *const kInvalidURLFieldErrorText = @"Invalid URL: %@"; + + NSArray *requiredFields = @[ + kIssuerKey, + kAuthorizationEndpointKey, + kTokenEndpointKey, + kJWKSURLKey, + kResponseTypesSupportedKey, + kSubjectTypesSupportedKey, + kIDTokenSigningAlgorithmValuesSupportedKey + ]; + + for (NSString *field in requiredFields) { + if (!dictionary[field]) { + if (error) { + NSString *errorText = [NSString stringWithFormat:kMissingFieldErrorText, field]; + *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeInvalidDiscoveryDocument + underlyingError:nil + description:errorText]; + } + return NO; + } + } + + // Check required URL fields are valid URLs. + NSArray *requiredURLFields = @[ + kIssuerKey, + kTokenEndpointKey, + kJWKSURLKey + ]; + + for (NSString *field in requiredURLFields) { + if (![NSURL URLWithString:dictionary[field]]) { + if (error) { + NSString *errorText = [NSString stringWithFormat:kInvalidURLFieldErrorText, field]; + *error = [OIDErrorUtilities errorWithCode:OIDErrorCodeInvalidDiscoveryDocument + underlyingError:nil + description:errorText]; + } + return NO; + } + } + + return YES; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + NSError *error; + NSDictionary *dictionary = [[NSDictionary alloc] initWithCoder:aDecoder]; + self = [self initWithDictionary:dictionary error:&error]; + if (error) { + return nil; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [_discoveryDictionary encodeWithCoder:aCoder]; +} + +#pragma mark - Properties + +- (NSDictionary *)discoveryDictionary { + return _discoveryDictionary; +} + +- (NSURL *)issuer { + return [NSURL URLWithString:_discoveryDictionary[kIssuerKey]]; +} + +- (NSURL *)authorizationEndpoint { + return [NSURL URLWithString:_discoveryDictionary[kAuthorizationEndpointKey]]; +} + +- (NSURL *)tokenEndpoint { + return [NSURL URLWithString:_discoveryDictionary[kTokenEndpointKey]]; +} + +- (nullable NSURL *)userinfoEndpoint { + return [NSURL URLWithString:_discoveryDictionary[kUserinfoEndpointKey]]; +} + +- (NSURL *)jwksURL { + return [NSURL URLWithString:_discoveryDictionary[kJWKSURLKey]]; +} + +- (nullable NSURL *)registrationEndpoint { + return [NSURL URLWithString:_discoveryDictionary[kRegistrationEndpointKey]]; +} + +- (nullable NSURL *)endSessionEndpoint { + return [NSURL URLWithString:_discoveryDictionary[kEndSessionEndpointKey]]; +} + +- (nullable NSArray *)scopesSupported { + return _discoveryDictionary[kScopesSupportedKey]; +} + +- (NSArray *)responseTypesSupported { + return _discoveryDictionary[kResponseTypesSupportedKey]; +} + +- (nullable NSArray *)responseModesSupported { + return _discoveryDictionary[kResponseModesSupportedKey]; +} + +- (nullable NSArray *)grantTypesSupported { + return _discoveryDictionary[kGrantTypesSupportedKey]; +} + +- (nullable NSArray *)acrValuesSupported { + return _discoveryDictionary[kACRValuesSupportedKey]; +} + +- (NSArray *)subjectTypesSupported { + return _discoveryDictionary[kSubjectTypesSupportedKey]; +} + +- (NSArray *) IDTokenSigningAlgorithmValuesSupported { + return _discoveryDictionary[kIDTokenSigningAlgorithmValuesSupportedKey]; +} + +- (nullable NSArray *)IDTokenEncryptionAlgorithmValuesSupported { + return _discoveryDictionary[kIDTokenEncryptionAlgorithmValuesSupportedKey]; +} + +- (nullable NSArray *)IDTokenEncryptionEncodingValuesSupported { + return _discoveryDictionary[kIDTokenEncryptionEncodingValuesSupportedKey]; +} + +- (nullable NSArray *)userinfoSigningAlgorithmValuesSupported { + return _discoveryDictionary[kUserinfoSigningAlgorithmValuesSupportedKey]; +} + +- (nullable NSArray *)userinfoEncryptionAlgorithmValuesSupported { + return _discoveryDictionary[kUserinfoEncryptionAlgorithmValuesSupportedKey]; +} + +- (nullable NSArray *)userinfoEncryptionEncodingValuesSupported { + return _discoveryDictionary[kUserinfoEncryptionEncodingValuesSupportedKey]; +} + +- (nullable NSArray *)requestObjectSigningAlgorithmValuesSupported { + return _discoveryDictionary[kRequestObjectSigningAlgorithmValuesSupportedKey]; +} + +- (nullable NSArray *) requestObjectEncryptionAlgorithmValuesSupported { + return _discoveryDictionary[kRequestObjectEncryptionAlgorithmValuesSupportedKey]; +} + +- (nullable NSArray *) requestObjectEncryptionEncodingValuesSupported { + return _discoveryDictionary[kRequestObjectEncryptionEncodingValuesSupported]; +} + +- (nullable NSArray *)tokenEndpointAuthMethodsSupported { + return _discoveryDictionary[kTokenEndpointAuthMethodsSupportedKey]; +} + +- (nullable NSArray *)tokenEndpointAuthSigningAlgorithmValuesSupported { + return _discoveryDictionary[kTokenEndpointAuthSigningAlgorithmValuesSupportedKey]; +} + +- (nullable NSArray *)displayValuesSupported { + return _discoveryDictionary[kDisplayValuesSupportedKey]; +} + +- (nullable NSArray *)claimTypesSupported { + return _discoveryDictionary[kClaimTypesSupportedKey]; +} + +- (nullable NSArray *)claimsSupported { + return _discoveryDictionary[kClaimsSupportedKey]; +} + +- (nullable NSURL *)serviceDocumentation { + return [NSURL URLWithString:_discoveryDictionary[kServiceDocumentationKey]]; +} + +- (nullable NSArray *)claimsLocalesSupported { + return _discoveryDictionary[kClaimsLocalesSupportedKey]; +} + +- (nullable NSArray *)UILocalesSupported { + return _discoveryDictionary[kUILocalesSupportedKey]; +} + +- (BOOL)claimsParameterSupported { + return [_discoveryDictionary[kClaimsParameterSupportedKey] boolValue]; +} + +- (BOOL)requestParameterSupported { + return [_discoveryDictionary[kRequestParameterSupportedKey] boolValue]; +} + +- (BOOL)requestURIParameterSupported { + // Default is true/YES. + if (!_discoveryDictionary[kRequestURIParameterSupportedKey]) { + return YES; + } + return [_discoveryDictionary[kRequestURIParameterSupportedKey] boolValue]; +} + +- (BOOL)requireRequestURIRegistration { + return [_discoveryDictionary[kRequireRequestURIRegistrationKey] boolValue]; +} + +- (nullable NSURL *)OPPolicyURI { + return [NSURL URLWithString:_discoveryDictionary[kOPPolicyURIKey]]; +} + +- (nullable NSURL *)OPTosURI { + return [NSURL URLWithString:_discoveryDictionary[kOPTosURIKey]]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDTokenRequest.h b/Pods/AppAuth/Source/AppAuthCore/OIDTokenRequest.h new file mode 100644 index 00000000..00e0c6e2 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDTokenRequest.h @@ -0,0 +1,162 @@ +/*! @file OIDTokenRequest.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +// This file only declares string constants useful for constructing a @c OIDTokenRequest, so it is +// imported here for convenience. +#import "OIDGrantTypes.h" + +@class OIDAuthorizationResponse; +@class OIDServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents a token request. + @see https://tools.ietf.org/html/rfc6749#section-3.2 + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +@interface OIDTokenRequest : NSObject + +/*! @brief The service's configuration. + @remarks This configuration specifies how to connect to a particular OAuth provider. + Configurations may be created manually, or via an OpenID Connect Discovery Document. + */ +@property(nonatomic, readonly) OIDServiceConfiguration *configuration; + +/*! @brief The type of token being sent to the token endpoint, i.e. "authorization_code" for the + authorization code exchange, or "refresh_token" for an access token refresh request. + @remarks grant_type + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + @see https://www.google.com/url?sa=D&q=https%3A%2F%2Ftools.ietf.org%2Fhtml%2Frfc6749%23section-6 + */ +@property(nonatomic, readonly) NSString *grantType; + +/*! @brief The authorization code received from the authorization server. + @remarks code + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +@property(nonatomic, readonly, nullable) NSString *authorizationCode; + +/*! @brief The client's redirect URI. + @remarks redirect_uri + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +@property(nonatomic, readonly, nullable) NSURL *redirectURL; + +/*! @brief The client identifier. + @remarks client_id + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +@property(nonatomic, readonly) NSString *clientID; + +/*! @brief The client secret. + @remarks client_secret + @see https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ +@property(nonatomic, readonly, nullable) NSString *clientSecret; + +/*! @brief The value of the scope parameter is expressed as a list of space-delimited, + case-sensitive strings. + @remarks scope + @see https://tools.ietf.org/html/rfc6749#section-3.3 + */ +@property(nonatomic, readonly, nullable) NSString *scope; + +/*! @brief The refresh token, which can be used to obtain new access tokens using the same + authorization grant. + @remarks refresh_token + @see https://tools.ietf.org/html/rfc6749#section-5.1 + */ +@property(nonatomic, readonly, nullable) NSString *refreshToken; + +/*! @brief The PKCE code verifier used to redeem the authorization code. + @remarks code_verifier + @see https://tools.ietf.org/html/rfc7636#section-4.3 + */ +@property(nonatomic, readonly, nullable) NSString *codeVerifier; + +/*! @brief The client's additional token request parameters. + */ +@property(nonatomic, readonly, nullable) NSDictionary *additionalParameters; + +/*! @internal + @brief Unavailable. Please use + initWithConfiguration:grantType:code:redirectURL:clientID:additionalParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @param configuration The service's configuration. + @param grantType the type of token being sent to the token endpoint, i.e. "authorization_code" + for the authorization code exchange, or "refresh_token" for an access token refresh request. + @see OIDGrantTypes.h + @param code The authorization code received from the authorization server. + @param redirectURL The client's redirect URI. + @param clientID The client identifier. + @param clientSecret The client secret. + @param scopes An array of scopes to combine into a single scope string per the OAuth2 spec. + @param refreshToken The refresh token. + @param codeVerifier The PKCE code verifier. + @param additionalParameters The client's additional token request parameters. + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scopes:(nullable NSArray *)scopes + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Designated initializer. + @param configuration The service's configuration. + @param grantType the type of token being sent to the token endpoint, i.e. "authorization_code" + for the authorization code exchange, or "refresh_token" for an access token refresh request. + @see OIDGrantTypes.h + @param code The authorization code received from the authorization server. + @param redirectURL The client's redirect URI. + @param clientID The client identifier. + @param clientSecret The client secret. + @param scope The value of the scope parameter is expressed as a list of space-delimited, + case-sensitive strings. + @param refreshToken The refresh token. + @param codeVerifier The PKCE code verifier. + @param additionalParameters The client's additional token request parameters. + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters:(nullable NSDictionary *)additionalParameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Constructs an @c NSURLRequest representing the token request. + @return An @c NSURLRequest representing the token request. + */ +- (NSURLRequest *)URLRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDTokenRequest.m b/Pods/AppAuth/Source/AppAuthCore/OIDTokenRequest.m new file mode 100644 index 00000000..bd27dd48 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDTokenRequest.m @@ -0,0 +1,307 @@ +/*! @file OIDTokenRequest.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDTokenRequest.h" + +#import "OIDDefines.h" +#import "OIDError.h" +#import "OIDScopeUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDURLQueryComponent.h" +#import "OIDTokenUtilities.h" + +/*! @brief The key for the @c configuration property for @c NSSecureCoding + */ +static NSString *const kConfigurationKey = @"configuration"; + +/*! @brief Key used to encode the @c grantType property for @c NSSecureCoding + */ +static NSString *const kGrantTypeKey = @"grant_type"; + +/*! @brief The key for the @c authorizationCode property for @c NSSecureCoding. + */ +static NSString *const kAuthorizationCodeKey = @"code"; + +/*! @brief Key used to encode the @c clientID property for @c NSSecureCoding + */ +static NSString *const kClientIDKey = @"client_id"; + +/*! @brief Key used to encode the @c clientSecret property for @c NSSecureCoding + */ +static NSString *const kClientSecretKey = @"client_secret"; + +/*! @brief Key used to encode the @c redirectURL property for @c NSSecureCoding + */ +static NSString *const kRedirectURLKey = @"redirect_uri"; + +/*! @brief Key used to encode the @c scopes property for @c NSSecureCoding + */ +static NSString *const kScopeKey = @"scope"; + +/*! @brief Key used to encode the @c refreshToken property for @c NSSecureCoding + */ +static NSString *const kRefreshTokenKey = @"refresh_token"; + +/*! @brief Key used to encode the @c codeVerifier property for @c NSSecureCoding and to build the + request URL. + */ +static NSString *const kCodeVerifierKey = @"code_verifier"; + +/*! @brief Key used to encode the @c additionalParameters property for + @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +@implementation OIDTokenRequest + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithConfiguration: + grantType: + authorizationCode: + redirectURL: + clientID: + clientSecret: + scope: + refreshToken: + codeVerifier: + additionalParameters:) + ) + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scopes:(nullable NSArray *)scopes + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters:(nullable NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + grantType:grantType + authorizationCode:code + redirectURL:redirectURL + clientID:clientID + clientSecret:clientSecret + scope:[OIDScopeUtilities scopesWithArray:scopes] + refreshToken:refreshToken + codeVerifier:(NSString *)codeVerifier + additionalParameters:additionalParameters]; +} + +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(nullable NSURL *)redirectURL + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + refreshToken:(nullable NSString *)refreshToken + codeVerifier:(nullable NSString *)codeVerifier + additionalParameters:(nullable NSDictionary *)additionalParameters { + self = [super init]; + if (self) { + _configuration = [configuration copy]; + _grantType = [grantType copy]; + _authorizationCode = [code copy]; + _redirectURL = [redirectURL copy]; + _clientID = [clientID copy]; + _clientSecret = [clientSecret copy]; + _scope = [scope copy]; + _refreshToken = [refreshToken copy]; + _codeVerifier = [codeVerifier copy]; + _additionalParameters = + [[NSDictionary alloc] initWithDictionary:additionalParameters copyItems:YES]; + + // Additional validation for the authorization_code grant type + if ([_grantType isEqual:OIDGrantTypeAuthorizationCode]) { + // redirect URI must not be nil + if (!_redirectURL) { + [NSException raise:OIDOAuthExceptionInvalidTokenRequestNullRedirectURL + format:@"%@", OIDOAuthExceptionInvalidTokenRequestNullRedirectURL, nil]; + + } + } + } + return self; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDServiceConfiguration *configuration = + [aDecoder decodeObjectOfClass:[OIDServiceConfiguration class] + forKey:kConfigurationKey]; + NSString *grantType = [aDecoder decodeObjectOfClass:[NSString class] forKey:kGrantTypeKey]; + NSString *code = [aDecoder decodeObjectOfClass:[NSString class] forKey:kAuthorizationCodeKey]; + NSString *clientID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kClientIDKey]; + NSString *clientSecret = [aDecoder decodeObjectOfClass:[NSString class] forKey:kClientSecretKey]; + NSString *scope = [aDecoder decodeObjectOfClass:[NSString class] forKey:kScopeKey]; + NSString *refreshToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:kRefreshTokenKey]; + NSString *codeVerifier = [aDecoder decodeObjectOfClass:[NSString class] forKey:kCodeVerifierKey]; + NSURL *redirectURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kRedirectURLKey]; + NSSet *additionalParameterCodingClasses = [NSSet setWithArray:@[ + [NSDictionary class], + [NSString class] + ]]; + NSDictionary *additionalParameters = + [aDecoder decodeObjectOfClasses:additionalParameterCodingClasses + forKey:kAdditionalParametersKey]; + self = [self initWithConfiguration:configuration + grantType:grantType + authorizationCode:code + redirectURL:redirectURL + clientID:clientID + clientSecret:clientSecret + scope:scope + refreshToken:refreshToken + codeVerifier:codeVerifier + additionalParameters:additionalParameters]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_configuration forKey:kConfigurationKey]; + [aCoder encodeObject:_grantType forKey:kGrantTypeKey]; + [aCoder encodeObject:_authorizationCode forKey:kAuthorizationCodeKey]; + [aCoder encodeObject:_clientID forKey:kClientIDKey]; + [aCoder encodeObject:_clientSecret forKey:kClientSecretKey]; + [aCoder encodeObject:_redirectURL forKey:kRedirectURLKey]; + [aCoder encodeObject:_scope forKey:kScopeKey]; + [aCoder encodeObject:_refreshToken forKey:kRefreshTokenKey]; + [aCoder encodeObject:_codeVerifier forKey:kCodeVerifierKey]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + NSURLRequest *request = self.URLRequest; + NSString *requestBody = + [[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]; + return [NSString stringWithFormat:@"<%@: %p, request: >", + NSStringFromClass([self class]), + (void *)self, + request.URL, + requestBody]; +} + +#pragma mark - + +/*! @brief Constructs the request URI. + @return A URL representing the token request. + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +- (NSURL *)tokenRequestURL { + return _configuration.tokenEndpoint; +} + +/*! @brief Constructs the request body data by combining the request parameters using the + "application/x-www-form-urlencoded" format. + @return The data to pass to the token request URL. + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +- (OIDURLQueryComponent *)tokenRequestBody { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; + + // Add parameters, as applicable. + if (_grantType) { + [query addParameter:kGrantTypeKey value:_grantType]; + } + if (_scope) { + [query addParameter:kScopeKey value:_scope]; + } + if (_redirectURL) { + [query addParameter:kRedirectURLKey value:_redirectURL.absoluteString]; + } + if (_refreshToken) { + [query addParameter:kRefreshTokenKey value:_refreshToken]; + } + if (_authorizationCode) { + [query addParameter:kAuthorizationCodeKey value:_authorizationCode]; + } + if (_codeVerifier) { + [query addParameter:kCodeVerifierKey value:_codeVerifier]; + } + + // Add any additional parameters the client has specified. + [query addParameters:_additionalParameters]; + + return query; +} + +- (NSURLRequest *)URLRequest { + static NSString *const kHTTPPost = @"POST"; + static NSString *const kHTTPContentTypeHeaderKey = @"Content-Type"; + static NSString *const kHTTPContentTypeHeaderValue = + @"application/x-www-form-urlencoded; charset=UTF-8"; + + NSURL *tokenRequestURL = [self tokenRequestURL]; + NSMutableURLRequest *URLRequest = [[NSURLRequest requestWithURL:tokenRequestURL] mutableCopy]; + URLRequest.HTTPMethod = kHTTPPost; + [URLRequest setValue:kHTTPContentTypeHeaderValue forHTTPHeaderField:kHTTPContentTypeHeaderKey]; + + OIDURLQueryComponent *bodyParameters = [self tokenRequestBody]; + NSMutableDictionary *httpHeaders = [[NSMutableDictionary alloc] init]; + + if (_clientSecret) { + // The client id and secret are encoded using the "application/x-www-form-urlencoded" + // encoding algorithm per RFC 6749 Section 2.3.1. + // https://tools.ietf.org/html/rfc6749#section-2.3.1 + NSString *encodedClientID = [OIDTokenUtilities formUrlEncode:_clientID]; + NSString *encodedClientSecret = [OIDTokenUtilities formUrlEncode:_clientSecret]; + + NSString *credentials = + [NSString stringWithFormat:@"%@:%@", encodedClientID, encodedClientSecret]; + NSData *plainData = [credentials dataUsingEncoding:NSUTF8StringEncoding]; + NSString *basicAuth = [plainData base64EncodedStringWithOptions:kNilOptions]; + + NSString *authValue = [NSString stringWithFormat:@"Basic %@", basicAuth]; + [httpHeaders setObject:authValue forKey:@"Authorization"]; + } else { + [bodyParameters addParameter:kClientIDKey value:_clientID]; + } + + // Constructs request with the body string and headers. + NSString *bodyString = [bodyParameters URLEncodedParameters]; + NSData *body = [bodyString dataUsingEncoding:NSUTF8StringEncoding]; + URLRequest.HTTPBody = body; + + for (id header in httpHeaders) { + [URLRequest setValue:httpHeaders[header] forHTTPHeaderField:header]; + } + + return URLRequest; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDTokenResponse.h b/Pods/AppAuth/Source/AppAuthCore/OIDTokenResponse.h new file mode 100644 index 00000000..b446e944 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDTokenResponse.h @@ -0,0 +1,110 @@ +/*! @file OIDTokenResponse.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +@class OIDTokenRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents the response to an token request. + @see https://tools.ietf.org/html/rfc6749#section-3.2 + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ +@interface OIDTokenResponse : NSObject + +/*! @brief The request which was serviced. + */ +@property(nonatomic, readonly) OIDTokenRequest *request; + +/*! @brief The access token generated by the authorization server. + @remarks access_token + @see https://tools.ietf.org/html/rfc6749#section-4.1.4 + @see https://tools.ietf.org/html/rfc6749#section-5.1 + */ +@property(nonatomic, readonly, nullable) NSString *accessToken; + +/*! @brief The approximate expiration date & time of the access token. + @remarks expires_in + @seealso OIDTokenResponse.accessToken + @see https://tools.ietf.org/html/rfc6749#section-4.1.4 + @see https://tools.ietf.org/html/rfc6749#section-5.1 + */ +@property(nonatomic, readonly, nullable) NSDate *accessTokenExpirationDate; + +/*! @brief Typically "Bearer" when present. Otherwise, another token_type value that the Client has + negotiated with the Authorization Server. + @remarks token_type + @see https://tools.ietf.org/html/rfc6749#section-4.1.4 + @see https://tools.ietf.org/html/rfc6749#section-5.1 + */ +@property(nonatomic, readonly, nullable) NSString *tokenType; + +/*! @brief ID Token value associated with the authenticated session. Always present for the + authorization code grant exchange when OpenID Connect is used, optional for responses to + access token refresh requests. Note that AppAuth does NOT verify the JWT signature. Users + of AppAuth are encouraged to verifying the JWT signature using the validation library of + their choosing. + @remarks id_token + @see http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse + @see http://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse + @see http://openid.net/specs/openid-connect-core-1_0.html#IDToken + @see https://jwt.io + @discussion @c OIDIDToken can be used to parse the ID Token and extract the claims. As noted, + this class does not verify the JWT signature. +*/ +@property(nonatomic, readonly, nullable) NSString *idToken; + +/*! @brief The refresh token, which can be used to obtain new access tokens using the same + authorization grant + @remarks refresh_token + @see https://tools.ietf.org/html/rfc6749#section-5.1 + */ +@property(nonatomic, readonly, nullable) NSString *refreshToken; + +/*! @brief The scope of the access token. OPTIONAL, if identical to the scopes requested, otherwise, + REQUIRED. + @remarks scope + @see https://tools.ietf.org/html/rfc6749#section-5.1 + */ +@property(nonatomic, readonly, nullable) NSString *scope; + +/*! @brief Additional parameters returned from the token server. + */ +@property(nonatomic, readonly, nullable) + NSDictionary *> *additionalParameters; + +/*! @internal + @brief Unavailable. Please use initWithParameters:. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param request The serviced request. + @param parameters The decoded parameters returned from the Authorization Server. + @remarks Known parameters are extracted from the @c parameters parameter and the normative + properties are populated. Non-normative parameters are placed in the + @c #additionalParameters dictionary. + */ +- (instancetype)initWithRequest:(OIDTokenRequest *)request + parameters:(NSDictionary *> *)parameters + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDTokenResponse.m b/Pods/AppAuth/Source/AppAuthCore/OIDTokenResponse.m new file mode 100644 index 00000000..6995fb91 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDTokenResponse.m @@ -0,0 +1,163 @@ +/*! @file OIDTokenResponse.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDTokenResponse.h" + +#import "OIDDefines.h" +#import "OIDFieldMapping.h" +#import "OIDTokenRequest.h" +#import "OIDTokenUtilities.h" + +/*! @brief Key used to encode the @c request property for @c NSSecureCoding + */ +static NSString *const kRequestKey = @"request"; + +/*! @brief The key for the @c accessToken property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kAccessTokenKey = @"access_token"; + +/*! @brief The key for the @c accessTokenExpirationDate property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kExpiresInKey = @"expires_in"; + +/*! @brief The key for the @c tokenType property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kTokenTypeKey = @"token_type"; + +/*! @brief The key for the @c idToken property in the incoming parameters and for @c NSSecureCoding. + */ +static NSString *const kIDTokenKey = @"id_token"; + +/*! @brief The key for the @c refreshToken property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kRefreshTokenKey = @"refresh_token"; + +/*! @brief The key for the @c scope property in the incoming parameters and for @c NSSecureCoding. + */ +static NSString *const kScopeKey = @"scope"; + +/*! @brief Key used to encode the @c additionalParameters property for @c NSSecureCoding + */ +static NSString *const kAdditionalParametersKey = @"additionalParameters"; + +@implementation OIDTokenResponse + +/*! @brief Returns a mapping of incoming parameters to instance variables. + @return A mapping of incoming parameters to instance variables. + */ ++ (NSDictionary *)fieldMap { + static NSMutableDictionary *fieldMap; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + fieldMap = [NSMutableDictionary dictionary]; + fieldMap[kAccessTokenKey] = + [[OIDFieldMapping alloc] initWithName:@"_accessToken" type:[NSString class]]; + fieldMap[kExpiresInKey] = + [[OIDFieldMapping alloc] initWithName:@"_accessTokenExpirationDate" + type:[NSDate class] + conversion:[OIDFieldMapping dateSinceNowConversion]]; + fieldMap[kTokenTypeKey] = + [[OIDFieldMapping alloc] initWithName:@"_tokenType" type:[NSString class]]; + fieldMap[kIDTokenKey] = + [[OIDFieldMapping alloc] initWithName:@"_idToken" type:[NSString class]]; + fieldMap[kRefreshTokenKey] = + [[OIDFieldMapping alloc] initWithName:@"_refreshToken" type:[NSString class]]; + fieldMap[kScopeKey] = + [[OIDFieldMapping alloc] initWithName:@"_scope" type:[NSString class]]; + }); + return fieldMap; +} + +#pragma mark - Initializers + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)) + +- (instancetype)initWithRequest:(OIDTokenRequest *)request + parameters:(NSDictionary *> *)parameters { + self = [super init]; + if (self) { + _request = [request copy]; + NSDictionary *> *additionalParameters = + [OIDFieldMapping remainingParametersWithMap:[[self class] fieldMap] + parameters:parameters + instance:self]; + _additionalParameters = additionalParameters; + } + return self; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + // The documentation for NSCopying specifically advises us to return a reference to the original + // instance in the case where instances are immutable (as ours is): + // "Implement NSCopying by retaining the original instead of creating a new copy when the class + // and its contents are immutable." + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDTokenRequest *request = + [aDecoder decodeObjectOfClass:[OIDTokenRequest class] forKey:kRequestKey]; + self = [self initWithRequest:request parameters:@{ }]; + if (self) { + [OIDFieldMapping decodeWithCoder:aDecoder map:[[self class] fieldMap] instance:self]; + _additionalParameters = [aDecoder decodeObjectOfClasses:[OIDFieldMapping JSONTypes] + forKey:kAdditionalParametersKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [OIDFieldMapping encodeWithCoder:aCoder map:[[self class] fieldMap] instance:self]; + [aCoder encodeObject:_request forKey:kRequestKey]; + [aCoder encodeObject:_additionalParameters forKey:kAdditionalParametersKey]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, accessToken: \"%@\", accessTokenExpirationDate: %@, " + "tokenType: %@, idToken: \"%@\", refreshToken: \"%@\", " + "scope: \"%@\", additionalParameters: %@, request: %@>", + NSStringFromClass([self class]), + (void *)self, + [OIDTokenUtilities redact:_accessToken], + _accessTokenExpirationDate, + _tokenType, + [OIDTokenUtilities redact:_idToken], + [OIDTokenUtilities redact:_refreshToken], + _scope, + _additionalParameters, + _request]; +} + +#pragma mark - + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDTokenUtilities.h b/Pods/AppAuth/Source/AppAuthCore/OIDTokenUtilities.h new file mode 100644 index 00000000..fda89854 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDTokenUtilities.h @@ -0,0 +1,67 @@ +/*! @file OIDTokenUtilities.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Provides data encoding/decoding methods, random string generators, etc. + */ +@interface OIDTokenUtilities : NSObject + +/*! @internal + @brief Unavailable. This class should not be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @brief Base64url-nopadding encodes the given data. + @param data The input data. + @return The base64url encoded data as a NSString. + @discussion Base64url-nopadding is used in several identity specs such as PKCE and + OpenID Connect. + */ ++ (NSString *)encodeBase64urlNoPadding:(NSData *)data; + +/*! @brief Generates a URL-safe string of random data. + @param size The number of random bytes to encode. NB. the length of the output string will be + greater than the number of random bytes, due to the URL-safe encoding. + @return Random data encoded with base64url. + */ ++ (nullable NSString *)randomURLSafeStringWithSize:(NSUInteger)size; + +/*! @brief SHA256 hashes the input string. + @param inputString The input string. + @return The SHA256 data. + */ ++ (NSData *)sha256:(NSString *)inputString; + +/*! @brief Truncated intput string after first 6 characters followed by ellipses + @param inputString The input string. + @return Truncated string. + */ ++ (nullable NSString *)redact:(nullable NSString *)inputString; + +/*! @brief Form url encode the input string by applying application/x-www-form-urlencoded algorithm + @param inputString The input string. + @return The encoded string. + */ ++ (NSString*)formUrlEncode:(NSString*)inputString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDTokenUtilities.m b/Pods/AppAuth/Source/AppAuthCore/OIDTokenUtilities.m new file mode 100644 index 00000000..3280c856 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDTokenUtilities.m @@ -0,0 +1,89 @@ +/*! @file OIDTokenUtilities.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDTokenUtilities.h" + +#import + +/*! @brief String representing the set of characters that are allowed as is for the + application/x-www-form-urlencoded encoding algorithm. + */ +static NSString *const kFormUrlEncodedAllowedCharacters = + @" *-._0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +@implementation OIDTokenUtilities + ++ (NSString *)encodeBase64urlNoPadding:(NSData *)data { + NSString *base64string = [data base64EncodedStringWithOptions:0]; + // converts base64 to base64url + base64string = [base64string stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; + base64string = [base64string stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; + // strips padding + base64string = [base64string stringByReplacingOccurrencesOfString:@"=" withString:@""]; + return base64string; +} + ++ (nullable NSString *)randomURLSafeStringWithSize:(NSUInteger)size { + NSMutableData *randomData = [NSMutableData dataWithLength:size]; + int result = SecRandomCopyBytes(kSecRandomDefault, randomData.length, randomData.mutableBytes); + if (result != 0) { + return nil; + } + return [[self class] encodeBase64urlNoPadding:randomData]; +} + ++ (NSData *)sha256:(NSString *)inputString { + NSData *verifierData = [inputString dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *sha256Verifier = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(verifierData.bytes, (CC_LONG)verifierData.length, sha256Verifier.mutableBytes); + return sha256Verifier; +} + ++ (NSString *)redact:(NSString *)inputString { + if (inputString == nil) { + return nil; + } + switch(inputString.length){ + case 0: + return @""; + case 1 ... 8: + return @"[redacted]"; + case 9: + default: + return [[inputString substringToIndex:6] stringByAppendingString:@"...[redacted]"]; + } +} + ++ (NSString*)formUrlEncode:(NSString*)inputString { + // https://www.w3.org/TR/html5/sec-forms.html#application-x-www-form-urlencoded-encoding-algorithm + // Following the spec from the above link, application/x-www-form-urlencoded percent encode all + // the characters except *-._A-Za-z0-9 + // Space character is replaced by + in the resulting bytes sequence + if (inputString.length == 0) { + return inputString; + } + NSCharacterSet *allowedCharacters = + [NSCharacterSet characterSetWithCharactersInString:kFormUrlEncodedAllowedCharacters]; + // Percent encode all characters not present in the provided set. + NSString *encodedString = + [inputString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; + // Replace occurences of space by '+' character + return [encodedString stringByReplacingOccurrencesOfString:@" " withString:@"+"]; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDURLQueryComponent.h b/Pods/AppAuth/Source/AppAuthCore/OIDURLQueryComponent.h new file mode 100644 index 00000000..054b11ea --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDURLQueryComponent.h @@ -0,0 +1,93 @@ +/*! @file OIDURLQueryComponent.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +@class OIDAuthorizationRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief If set to YES, will force the iOS 7-only code for @c OIDURLQueryComponent to be used, + even on non-iOS 7 devices and simulators. Useful for testing the iOS 7 code paths on the + simulator. Defaults to NO. + */ +extern BOOL gOIDURLQueryComponentForceIOS7Handling; + +/*! @brief A utility class for creating and parsing URL query components encoded with the + application/x-www-form-urlencoded format. + @description Supports application/x-www-form-urlencoded encoding and decoding, specifically + '+' is replaced with space before percent decoding. For encoding, simply percent encodes + space, as this is valid application/x-www-form-urlencoded. + @see https://tools.ietf.org/html/rfc6749#section-4.1.2 + @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + @see https://tools.ietf.org/html/rfc6749#appendix-B + @see https://url.spec.whatwg.org/#urlencoded-parsing + */ +@interface OIDURLQueryComponent : NSObject + +/*! @brief The parameter names in the query. + */ +@property(nonatomic, readonly) NSArray *parameters; + +/*! @brief The parameters represented as a dictionary. + @remarks All values are @c NSString except for parameters which contain multiple values, in + which case the value is an @c NSArray *. + */ +@property(nonatomic, readonly) NSDictionary *> *dictionaryValue; + +/*! @brief Creates an @c OIDURLQueryComponent by parsing the query string in a URL. + @param URL The URL from which to extract a query component. + */ +- (nullable instancetype)initWithURL:(NSURL *)URL; + +/*! @brief The value (or values) for a named parameter in the query. + @param parameter The parameter name. Case sensitive. + @return The value (or values) for a named parameter in the query. + */ +- (NSArray *)valuesForParameter:(NSString *)parameter; + +/*! @brief Adds a parameter value to the query. + @param parameter The name of the parameter. Case sensitive. + @param value The value to add. + */ +- (void)addParameter:(NSString *)parameter value:(NSString *)value; + +/*! @brief Adds multiple parameters with associated values to the query. + @param parameters The parameter name value pairs to add to the query. + */ +- (void)addParameters:(NSDictionary *)parameters; + +/*! @param URL The URL to add the query component to. + @return The original URL with the query component replaced by the parameters from this query. + */ +- (NSURL *)URLByReplacingQueryInURL:(NSURL *)URL; + +/*! @brief Builds an x-www-form-urlencoded string representing the parameters. + @return The x-www-form-urlencoded string representing the parameters. + */ +- (NSString *)URLEncodedParameters; + +/*! @brief A NSMutableCharacterSet containing allowed characters in URL parameter values (that is + the "value" part of "?key=value"). This has less allowed characters than + @c URLQueryAllowedCharacterSet, as the query component includes both the key & value. + */ ++ (NSMutableCharacterSet *)URLParamValueAllowedCharacters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDURLQueryComponent.m b/Pods/AppAuth/Source/AppAuthCore/OIDURLQueryComponent.m new file mode 100644 index 00000000..07050c90 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDURLQueryComponent.m @@ -0,0 +1,219 @@ +/*! @file OIDURLQueryComponent.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDURLQueryComponent.h" + +BOOL gOIDURLQueryComponentForceIOS7Handling = NO; + +/*! @brief String representing the set of characters that are valid for the URL query + (per @ NSCharacterSet.URLQueryAllowedCharacterSet), but are disallowed in URL query + parameters and values. + */ +static NSString *const kQueryStringParamAdditionalDisallowedCharacters = @"=&+"; + +@implementation OIDURLQueryComponent { + /*! @brief A dictionary of parameter names and values representing the contents of the query. + */ + NSMutableDictionary *> *_parameters; +} + +- (nullable instancetype)init { + self = [super init]; + if (self) { + _parameters = [NSMutableDictionary dictionary]; + } + return self; +} + +- (nullable instancetype)initWithURL:(NSURL *)URL { + self = [self init]; + if (self) { + if (@available(iOS 8.0, macOS 10.10, *)) { + // If NSURLQueryItem is available, use it for deconstructing the new URL. (iOS 8+) + if (!gOIDURLQueryComponentForceIOS7Handling) { + NSURLComponents *components = + [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + // As OAuth uses application/x-www-form-urlencoded encoding, interprets '+' as a space + // in addition to regular percent decoding. https://url.spec.whatwg.org/#urlencoded-parsing + components.percentEncodedQuery = + [components.percentEncodedQuery stringByReplacingOccurrencesOfString:@"+" + withString:@"%20"]; + // NB. @c queryItems are already percent decoded + NSArray *queryItems = components.queryItems; + for (NSURLQueryItem *queryItem in queryItems) { + [self addParameter:queryItem.name value:queryItem.value]; + } + return self; + } + } + + // Fallback for iOS 7 + NSString *query = URL.query; + // As OAuth uses application/x-www-form-urlencoded encoding, interprets '+' as a space + // in addition to regular percent decoding. https://url.spec.whatwg.org/#urlencoded-parsing + query = [query stringByReplacingOccurrencesOfString:@"+" withString:@"%20"]; + + NSArray *queryParts = [query componentsSeparatedByString:@"&"]; + for (NSString *queryPart in queryParts) { + NSRange equalsRange = [queryPart rangeOfString:@"="]; + if (equalsRange.location == NSNotFound) { + continue; + } + NSString *name = [queryPart substringToIndex:equalsRange.location]; + name = name.stringByRemovingPercentEncoding; + NSString *value = [queryPart substringFromIndex:equalsRange.location + equalsRange.length]; + value = value.stringByRemovingPercentEncoding; + [self addParameter:name value:value]; + } + return self; + } + return self; +} + +- (NSArray *)parameters { + return _parameters.allKeys; +} + +- (NSDictionary *> *)dictionaryValue { + // This method will flatten arrays in our @c _parameters' values if only one value exists. + NSMutableDictionary *> *values = [NSMutableDictionary dictionary]; + for (NSString *parameter in _parameters.allKeys) { + NSArray *value = _parameters[parameter]; + if (value.count == 1) { + values[parameter] = [value.firstObject copy]; + } else { + values[parameter] = [value copy]; + } + } + return values; +} + +- (NSArray *)valuesForParameter:(NSString *)parameter { + return _parameters[parameter]; +} + +- (void)addParameter:(NSString *)parameter value:(NSString *)value { + NSMutableArray *parameterValues = _parameters[parameter]; + if (!parameterValues) { + parameterValues = [NSMutableArray array]; + _parameters[parameter] = parameterValues; + } + [parameterValues addObject:value]; +} + +- (void)addParameters:(NSDictionary *)parameters { + for (NSString *parameterName in parameters.allKeys) { + [self addParameter:parameterName value:parameters[parameterName]]; + } +} + +/*! @brief Builds a query items array that can be set to @c NSURLComponents.queryItems + @discussion The parameter names and values are NOT URL encoded. + @return An array of unencoded @c NSURLQueryItem objects. + */ +- (NSMutableArray *)queryItems NS_AVAILABLE(10.10, 8.0) { + NSMutableArray *queryParameters = [NSMutableArray array]; + for (NSString *parameterName in _parameters.allKeys) { + NSArray *values = _parameters[parameterName]; + for (NSString *value in values) { + NSURLQueryItem *item = [NSURLQueryItem queryItemWithName:parameterName value:value]; + [queryParameters addObject:item]; + } + } + return queryParameters; +} + ++ (NSMutableCharacterSet *)URLParamValueAllowedCharacters { + // Starts with the standard URL-allowed character set. + NSMutableCharacterSet *allowedParamCharacters = + [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; + // Removes additional characters we don't want to see in the query component. + [allowedParamCharacters removeCharactersInString:kQueryStringParamAdditionalDisallowedCharacters]; + return allowedParamCharacters; +} + +/*! @brief Builds a query string that can be set to @c NSURLComponents.percentEncodedQuery + @discussion This string is percent encoded, and shouldn't be used with + @c NSURLComponents.query. + @return An percentage encoded query string. + */ +- (NSString *)percentEncodedQueryString { + NSMutableArray *parameterizedValues = [NSMutableArray array]; + + // Starts with the standard URL-allowed character set. + NSMutableCharacterSet *allowedParamCharacters = [[self class] URLParamValueAllowedCharacters]; + + for (NSString *parameterName in _parameters.allKeys) { + NSString *encodedParameterName = + [parameterName stringByAddingPercentEncodingWithAllowedCharacters:allowedParamCharacters]; + + NSArray *values = _parameters[parameterName]; + for (NSString *value in values) { + NSString *encodedValue = + [value stringByAddingPercentEncodingWithAllowedCharacters:allowedParamCharacters]; + NSString *parameterizedValue = + [NSString stringWithFormat:@"%@=%@", encodedParameterName, encodedValue]; + [parameterizedValues addObject:parameterizedValue]; + } + } + + NSString *queryString = [parameterizedValues componentsJoinedByString:@"&"]; + return queryString; +} + +- (NSString *)URLEncodedParameters { + // If NSURLQueryItem is available, uses it for constructing the encoded parameters. (iOS 8+) + if (@available(iOS 8.0, macOS 10.10, *)) { + if (!gOIDURLQueryComponentForceIOS7Handling) { + NSURLComponents *components = [[NSURLComponents alloc] init]; + components.queryItems = [self queryItems]; + NSString *encodedQuery = components.percentEncodedQuery; + // NSURLComponents.percentEncodedQuery creates a validly escaped URL query component, but + // doesn't encode the '+' leading to potential ambiguity with application/x-www-form-urlencoded + // encoding. Percent encodes '+' to avoid this ambiguity. + encodedQuery = [encodedQuery stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"]; + return encodedQuery; + } + } + + // else, falls back to building query string manually (iOS 7) + return [self percentEncodedQueryString]; +} + +- (NSURL *)URLByReplacingQueryInURL:(NSURL *)URL { + NSURLComponents *components = + [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + + // Replaces encodedQuery component + NSString *queryString = [self URLEncodedParameters]; + components.percentEncodedQuery = queryString; + + NSURL *URLWithParameters = components.URL; + return URLWithParameters; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, parameters: %@>", + NSStringFromClass([self class]), + (void *)self, + _parameters]; +} + +@end diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDURLSessionProvider.h b/Pods/AppAuth/Source/AppAuthCore/OIDURLSessionProvider.h new file mode 100644 index 00000000..28e91169 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDURLSessionProvider.h @@ -0,0 +1,40 @@ +/*! @file OIDURLSessionProvider.h + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief A NSURLSession provider that allows clients to provide custom implementation + for NSURLSession + */ +@interface OIDURLSessionProvider : NSObject + +/*! @brief Obtains the current @c NSURLSession; using the +[NSURLSession sharedSession] if + no custom implementation is provided. + @return NSURLSession object to be used for making network requests. + */ ++ (NSURLSession *)session; + +/*! @brief Allows library consumers to change the @c NSURLSession instance used to make + network requests. + @param session The @c NSURLSession instance that should be used for making network requests. + */ ++ (void)setSession:(NSURLSession *)session; +@end +NS_ASSUME_NONNULL_END diff --git a/Pods/AppAuth/Source/AppAuthCore/OIDURLSessionProvider.m b/Pods/AppAuth/Source/AppAuthCore/OIDURLSessionProvider.m new file mode 100644 index 00000000..fca17fe7 --- /dev/null +++ b/Pods/AppAuth/Source/AppAuthCore/OIDURLSessionProvider.m @@ -0,0 +1,39 @@ +/*! @file OIDURLSessionProvider.m + @brief AppAuth iOS SDK + @copyright + Copyright 2015 Google Inc. All Rights Reserved. + @copydetails + 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 "OIDURLSessionProvider.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSURLSession *__nullable gURLSession; + +@implementation OIDURLSessionProvider + ++ (NSURLSession *)session { + if (!gURLSession) { + gURLSession = [NSURLSession sharedSession]; + } + return gURLSession; +} + ++ (void)setSession:(NSURLSession *)session { + NSAssert(session, @"Parameter: |session| must be non-nil."); + gURLSession = session; +} +@end +NS_ASSUME_NONNULL_END diff --git a/Pods/GTMAppAuth/LICENSE b/Pods/GTMAppAuth/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/Pods/GTMAppAuth/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Pods/GTMAppAuth/README.md b/Pods/GTMAppAuth/README.md new file mode 100644 index 00000000..e2c02fd0 --- /dev/null +++ b/Pods/GTMAppAuth/README.md @@ -0,0 +1,385 @@ +# GTMAppAuth for iOS and macOS + +GTMAppAuth enables you to use [AppAuth](http://openid.github.io/AppAuth-iOS) +with the +[Google Toolbox for Mac - Session Fetcher](https://github.com/google/gtm-session-fetcher) +and +[Google APIs Client Library for Objective-C For REST](https://github.com/google/google-api-objectivec-client-for-rest) +libraries by providing an implementation of `GTMFetcherAuthorizationProtocol` +for authorizing requests with AppAuth. + +GTMAppAuth is an alternative authorizer to GTMOAuth2. The key differentiator is +the use of the user's default browser for the authorization, which is more +secure, more usable (the user's session can be reused) and follows modern OAuth +[best practices for native apps](https://tools.ietf.org/html/draft-ietf-oauth-native-apps). +Compatibility methods for GTMOAuth2 are offered allowing you to migrate +from GTMOAuth2 to GTMAppAuth preserving previously serialized authorizations +(so users shouldn't need to re-authenticate). + +## Setup + +If you use [CocoaPods](https://guides.cocoapods.org/using/getting-started.html), +simply add: + + pod 'GTMAppAuth' + +To your `Podfile` and run `pod install`. + +## Usage + +### Configuration + +To configure GTMAppAuth with the OAuth endpoints for Google, you can use the +convenience method: + +```objc +OIDServiceConfiguration *configuration = + [GTMAppAuthFetcherAuthorization configurationForGoogle]; +``` + +Alternatively, you can configure GTMAppAuth by specifying the endpoints +directly: + +```objc +NSURL *authorizationEndpoint = + [NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"]; +NSURL *tokenEndpoint = + [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"]; + +OIDServiceConfiguration *configuration = + [[OIDServiceConfiguration alloc] + initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint]; + +// perform the auth request... +``` + +Or through discovery: + +```objc +NSURL *issuer = [NSURL URLWithString:@"https://accounts.google.com"]; + +[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer + completion:^(OIDServiceConfiguration *_Nullable configuration, + NSError *_Nullable error) { + if (!configuration) { + NSLog(@"Error retrieving discovery document: %@", + [error localizedDescription]); + return; + } + + // perform the auth request... +}]; +``` + +### Authorizing + +First, you need to have a way for your UIApplicationDelegate to continue the +authorization flow session from the incoming redirect URI. Typically you could +store the in-progress OIDAuthorizationFlowSession instance in a property: + +```objc +// property of the app's UIApplicationDelegate +@property(nonatomic, nullable) + id currentAuthorizationFlow; +``` + +And in a location accessible by all controllers that need authorization, a +property to store the authorization state: + +```objc +// property of the containing class +@property(nonatomic, nullable) GTMAppAuthFetcherAuthorization *authorization; +``` + +Then, initiate the authorization request. By using the +`authStateByPresentingAuthorizationRequest` method, the OAuth token +exchange will be performed automatically, and everything will be protected with +PKCE (if the server supports it). + +```objc +// builds authentication request +OIDAuthorizationRequest *request = + [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration + clientId:kClientID + clientSecret:kClientSecret + scopes:@[OIDScopeOpenID, OIDScopeProfile] + redirectURL:redirectURI + responseType:OIDResponseTypeCode + additionalParameters:nil]; +// performs authentication request +self.appDelegate.currentAuthorizationFlow = + [OIDAuthState authStateByPresentingAuthorizationRequest:request + callback:^(OIDAuthState *_Nullable authState, + NSError *_Nullable error) { + if (authState) { + // Creates the GTMAppAuthFetcherAuthorization from the OIDAuthState. + GTMAppAuthFetcherAuthorization *authorization = + [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; + + self.authorization = authorization; + NSLog(@"Got authorization tokens. Access token: %@", + authState.lastTokenResponse.accessToken); + } else { + NSLog(@"Authorization error: %@", [error localizedDescription]); + self.authorization = nil; + } +}]; +``` + +### Handling the Redirect + +The authorization response URL is returned to the app via the platform-specific +application delegate method, so you need to pipe this through to the current +authorization session (created in the previous session). + +#### macOS Custom URI Scheme Redirect Example + +```objc +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // Other app initialization code ... + + // Register for GetURL events. + NSAppleEventManager *appleEventManager = + [NSAppleEventManager sharedAppleEventManager]; + [appleEventManager setEventHandler:self + andSelector:@selector(handleGetURLEvent:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; +} + +- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event + withReplyEvent:(NSAppleEventDescriptor *)replyEvent { + NSString *URLString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + NSURL *URL = [NSURL URLWithString:URLString]; + [_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:URL]; +} +``` + +#### iOS Custom URI Scheme Redirect Example + +```objc +- (BOOL)application:(UIApplication *)app + openURL:(NSURL *)url + options:(NSDictionary *)options { + // Sends the URL to the current authorization flow (if any) which will + // process it if it relates to an authorization response. + if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) { + _currentAuthorizationFlow = nil; + return YES; + } + + // Your additional URL handling (if any) goes here. + + return NO; +} +``` + +### Making API Calls + +The goal of GTMAppAuth is to enable you to authorize HTTP requests with fresh +tokens following the Session Fetcher pattern, which you can do like so: + +```objc +// Creates a GTMSessionFetcherService with the authorization. +// Normally you would save this service object and re-use it for all REST API calls. +GTMSessionFetcherService *fetcherService = [[GTMSessionFetcherService alloc] init]; +fetcherService.authorizer = self.authorization; + +// Creates a fetcher for the API call. +NSURL *userinfoEndpoint = [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v3/userinfo"]; +GTMSessionFetcher *fetcher = [fetcherService fetcherWithURL:userinfoEndpoint]; +[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { + // Checks for an error. + if (error) { + // OIDOAuthTokenErrorDomain indicates an issue with the authorization. + if ([error.domain isEqual:OIDOAuthTokenErrorDomain]) { + self.authorization = nil; + NSLog(@"Authorization error during token refresh, clearing state. %@", + error); + // Other errors are assumed transient. + } else { + NSLog(@"Transient error during token refresh. %@", error); + } + return; + } + + // Parses the JSON response. + NSError *jsonError = nil; + id jsonDictionaryOrArray = + [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + // JSON error. + if (jsonError) { + NSLog(@"JSON decoding error %@", jsonError); + return; + } + + // Success response! + NSLog(@"Success: %@", jsonDictionaryOrArray); +}]; +``` + +### Serialization + +You can easily serialize `GTMAppAuthFetcherAuthorization` objects using the +included Keychain category. + +```objc +// Serialize to Keychain +[GTMAppAuthFetcherAuthorization saveAuthorization:_authorization + toKeychainForName:kGTMAppAuthExampleAuthorizerKey]; + +// Deserialize from Keychain +GTMAppAuthFetcherAuthorization* authorization = + [GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kGTMAppAuthExampleAuthorizerKey]; + +// Remove from Keychain +[GTMAppAuthFetcherAuthorization + removeAuthorizationFromKeychainForName:kGTMAppAuthExampleAuthorizerKey]; +``` + +### GTMOAuth2-compatible Serialization + +To assist the migration from GTMOAuth2 to GTMAppAuth, GTMOAuth2-compatible +serialization methods are provided in `GTMOAuth2KeychainCompatibility`. + +```objc +// Deserialize from Keychain +GTMAppAuthFetcherAuthorization *auth = + [GTMOAuth2KeychainCompatibility authForGoogleFromKeychainForName:kKeychainItemName + clientID:clientID + clientSecret:clientSecret]; + +// Remove from Keychain +[GTMOAuth2KeychainCompatibility removeAuthFromKeychainForName:kKeychainItemName]; +``` + +You can also serialize to GTMOAuth2 format, though this is discouraged (you +should serialize in GTMAppAuth format as described above). + +```objc +// Serialize to Keychain +[GTMOAuth2KeychainCompatibility saveAuthToKeychainForName:kKeychainItemName + authentication:authorization]; +``` + +## Included Samples + +Try out one of the included sample apps under [Examples](Examples). In the +apps folder run `pod install`, then open the resulting `xcworkspace` file. + +Be sure to follow the instructions in +[Example-iOS/README.md](Examples/Example-iOS/README.md) or +[Example-macOS/README.md](Examples/Example-macOS/README.md) to configure +your own OAuth client ID for use with the example. + +## Differences with GTMOAuth2 + +### Authorization Method + +GTMAppAuth uses the browser to present the authorization request, while +GTMOAuth2 uses an embedded web-view. Migrating to GTMAppAuth will require you +to change how you authorize the user. Follow the instructions above to get the +authorization. You can then create a `GTMAppAuthFetcherAuthorization` object +with the `initWithAuthState:authState` initializer. + +Once you have the `GTMAppAuthFetcherAuthorization` you can continue to make REST +calls as before. + +### Error Handling + +GTMAppAuth's error handling is also different. There are no notifications, +instead you need to inspect NSError in the callback. If the error domain is +`OIDOAuthTokenErrorDomain`, it indicates an authorization error, you should +clear your authorization state and consider prompting the user to authorize +again. Other errors are generally considered transient, meaning that you should +retry the request after a delay. + +### Serialization + +The serialization format is different between GTMOAuth2 and GTMAppAuth, though +we have methods to help you migrate from one to the other without losing any +data. + +## Migrating from GTMOAuth2 + +### OAuth Client Registration + +Typically, GTMOAuth2 clients are registered with Google as type "Other". This is +correct for macOS, but on iOS clients should be registered with the type "iOS". + +If you're migrating an iOS client, in the *same project as your existing client*, +[register a new iOS client](https://console.developers.google.com/apis/credentials?project=_) +to be used with GTMAppAuth. + +### Changing your Authorization Flows + +Both GTMOAuth2 and GTMAppAuth support the `GTMFetcherAuthorizationProtocol` +allowing you to use the authorization with the session fetcher. Where you +previously had a property like `GTMOAuth2Authentication *authorization` change the +type to reference the protocol instead, i.e.: +`id authorization`. This allows you to switch +the authorization implementation under the hood to GTMAppAuth. + +Then, follow the instructions above to replace authorization request +(where you ask the user to grant access) with the GTMAppAuth approach. If you +created a new OAuth client, use that for these requests. + +### Serialization & Migrating Existing Grants + +GTMAppAuth has a new data format and APIs for serialization. Unlike +GTMOAuth2, GTMAppAuth serializes the configuration and history of the +authorization, including the client id, and a record of the authorization +request that resulted in the authorization grant. + +The client ID used for GTMAppAuth is [different](#oauth-client-registration) to +the one used for GTMOAuth2. In order to keep track of the different client ids +used for new and old grants, it's recommended to migrate to the new +serialization format, which will store that for you. +[GTMOAuth2-compatible serialization](#gtmoauth2-compatible-serialization) is +also offered, but not fully supported. + +Change how you serialize your `authorization` object using the new methods +using the following example. + +```objc +// Serialize to Keychain +[GTMAppAuthFetcherAuthorization saveAuthorization:(GTMAppAuthFetcherAuthorization *)authorization + toKeychainForName:kNewKeychainName]; +``` + +Be sure to use a *new* name for the keychain. Don't reuse your old one! + +For deserializing, we can preserve all existing grants (so users who authorized +your app in GTMOAuth2 don't have to authorize it again). Remember that when +deserializing the *old* data you need to use your *old* keychain name, and +the old client id and client secret (if those changed), and that when +serializing to the *new* format, use the *new* keychain name. +Once again, pay particular care to use the old details when deserializing the +GTMOAuth2 keychain, and the new details for all other GTMAppAuth calls. + +Keychain migration example: + +```objc +// Attempt to deserialize from Keychain in GTMAppAuth format. +id authorization = + [GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kNewKeychainName]; + +// If no data found in the new format, try to deserialize data from GTMOAuth2 +if (!authorization) { + // Tries to load the data serialized by GTMOAuth2 using old keychain name. + // If you created a new client id, be sure to use the *previous* client id and secret here. + authorization = + [GTMOAuth2KeychainCompatibility authForGoogleFromKeychainForName:kPreviousKeychainName + clientID:kPreviousClientID + clientSecret:kPreviousClientSecret]; + if (authorization) { + // Remove previously stored GTMOAuth2-formatted data. + [GTMOAuth2KeychainCompatibility removeAuthFromKeychainForName:kPreviousKeychainName]; + // Serialize to Keychain in GTMAppAuth format. + [GTMAppAuthFetcherAuthorization saveAuthorization:(GTMAppAuthFetcherAuthorization *)authorization + toKeychainForName:kNewKeychainName]; + } +} +``` diff --git a/Pods/GTMAppAuth/Source/GTMAppAuth.h b/Pods/GTMAppAuth/Source/GTMAppAuth.h new file mode 100644 index 00000000..ecbdad60 --- /dev/null +++ b/Pods/GTMAppAuth/Source/GTMAppAuth.h @@ -0,0 +1,30 @@ +/*! @file GTMAppAuth.h + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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 "GTMAppAuthFetcherAuthorization.h" +#import "GTMAppAuthFetcherAuthorization+Keychain.h" + +#if TARGET_OS_TV +#elif TARGET_OS_WATCH +#elif TARGET_OS_IOS +#import "GTMOAuth2KeychainCompatibility.h" +#elif TARGET_OS_MAC +#import "GTMOAuth2KeychainCompatibility.h" +#else +#warn "Platform Undefined" +#endif diff --git a/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.h b/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.h new file mode 100644 index 00000000..f0b9ffeb --- /dev/null +++ b/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.h @@ -0,0 +1,52 @@ +/*! @file GTMAppAuthFetcherAuthorization+Keychain.h + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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 "GTMAppAuthFetcherAuthorization.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Category to support serialization and deserialization of + @c GTMAppAuthFetcherAuthorization in the format used by GTMAppAuth. + */ +@interface GTMAppAuthFetcherAuthorization (Keychain) + +/*! @brief Attempts to create a @c GTMAppAuthFetcherAuthorization from data stored in the keychain + in GTMAppAuth format. + @param keychainItemName The keychain name. + @return A @c GTMAppAuthFetcherAuthorization object, or nil. + */ ++ (nullable GTMAppAuthFetcherAuthorization *) + authorizationFromKeychainForName:(NSString *)keychainItemName; + +/*! @brief Removes a stored authorization state. + @param keychainItemName The keychain name. + @return YES the tokens were removed successfully (or didn't exist). + */ ++ (BOOL)removeAuthorizationFromKeychainForName:(NSString *)keychainItemName; + +/*! @brief Saves the authorization state to the keychain, in GTMAppAuth format. + @param auth The authorization to save. + @param keychainItemName The keychain name. + @return YES when the state was saved successfully. + */ ++ (BOOL)saveAuthorization:(GTMAppAuthFetcherAuthorization *)auth + toKeychainForName:(NSString *)keychainItemName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.m b/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.m new file mode 100644 index 00000000..e4910a81 --- /dev/null +++ b/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.m @@ -0,0 +1,46 @@ +/*! @file GTMAppAuthFetcherAuthorization+Keychain.m + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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 "GTMAppAuthFetcherAuthorization+Keychain.h" + +#import "GTMKeychain.h" + +@implementation GTMAppAuthFetcherAuthorization (Keychain) + ++ (GTMAppAuthFetcherAuthorization *)authorizationFromKeychainForName:(NSString *)keychainItemName { + NSData *passwordData = [GTMKeychain passwordDataFromKeychainForName:keychainItemName]; + if (!passwordData) { + return nil; + } + GTMAppAuthFetcherAuthorization *authorization = (GTMAppAuthFetcherAuthorization *) + [NSKeyedUnarchiver unarchiveObjectWithData:passwordData]; + return authorization; +} + ++ (BOOL)removeAuthorizationFromKeychainForName:(NSString *)keychainItemName { + return [GTMKeychain removePasswordFromKeychainForName:keychainItemName]; +} + ++ (BOOL)saveAuthorization:(GTMAppAuthFetcherAuthorization *)auth + toKeychainForName:(NSString *)keychainItemName { + NSData *authorizationData = [NSKeyedArchiver archivedDataWithRootObject:auth]; + return [GTMKeychain savePasswordDataToKeychainForName:keychainItemName + passwordData:authorizationData]; +} + +@end diff --git a/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.h b/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.h new file mode 100644 index 00000000..248223c8 --- /dev/null +++ b/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.h @@ -0,0 +1,153 @@ +/*! @file GTMAppAuthFetcherAuthorization.h + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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. + */ + +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "GTMSessionFetcher.h" +#endif // GTMAPPAUTH_USER_IMPORTS + +@class OIDAuthState; +@class OIDServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief The userInfo key for the @c NSURLRequest. + */ +extern NSString *const GTMAppAuthFetcherAuthorizationErrorRequestKey; + +/*! @brief The error domain for errors specific to the session fetcher authorization. + */ +extern NSString *const GTMAppAuthFetcherAuthorizationErrorDomain; + +/*! @brief Enum of all possible error codes in the @c ::GTMAppAuthFetcherAuthorizationErrorDomain + domain. + @discussion Note that these are GTMAppAuth-specific errors. When AppAuth errors are encountered, + those are returned instead. + */ +typedef NS_ENUM(NSInteger, GTMAppAuthFetcherAuthorizationError) { + GTMAppAuthFetcherAuthorizationErrorUnauthorizableRequest = -1004 +}; + +typedef void (^GTMAppAuthFetcherAuthorizationCompletion)(NSError *_Nullable error); + +@class GTMAppAuthFetcherAuthorization; + +/*! @protocol GTMAppAuthFetcherAuthorizationTokenRefreshDelegate + @brief Delegate of the GTMAppAuthFetcherAuthorization used to supply additional parameters on + token refresh. + */ +@protocol GTMAppAuthFetcherAuthorizationTokenRefreshDelegate + +/*! @brief Called before a token refresh request is performed. + @param authorization The @c GTMFetcherAuthorization performing the token refresh. + @return A dictionary of parameters to be added to the token refresh request. + */ +- (nullable NSDictionary *)additionalRefreshParameters: + (GTMAppAuthFetcherAuthorization *)authorization; + +@end + +/*! @brief An implementation of the @c GTMFetcherAuthorizationProtocol protocol for the AppAuth + library. + @discussion Enables you to use AppAuth with the GTM Session Fetcher library. + */ +@interface GTMAppAuthFetcherAuthorization : NSObject + +/*! @brief The AppAuth authentication state. + */ +@property(nonatomic, readonly) OIDAuthState *authState; + +/*! @brief Service identifier, for example "Google"; not used for authentication. + @discussion The provider name is just for allowing stored authorization to be associated + with the authorizing service. + */ +@property(nullable, nonatomic, readonly) NSString *serviceProvider; + +/*! @brief User ID from the ID Token. + * @discussion Note: Never send this value to your backend as an authentication token, rather send + * an ID Token and validate it. + */ +@property(nullable, nonatomic, readonly) NSString *userID; + +/*! @brief Email verified status; not used for authentication. + @discussion 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(nullable, nonatomic, readonly) NSString *userEmailIsVerified; + +@property(nullable, nonatomic, weak) id + tokenRefreshDelegate; + +/*! @brief Creates a new @c GTMAppAuthFetcherAuthorization using the given @c OIDAuthState from + AppAuth. + @param authState The authorization state. + */ +- (instancetype)initWithAuthState:(OIDAuthState *)authState; + +/*! @brief Creates a new @c GTMAppAuthFetcherAuthorization using the given @c OIDAuthState from + AppAuth. + @param authState The authorization state. + @param serviceProvider An optional string to describe the service. + @param userID An optional string of the user ID. + @param userEmail An optional string of the user's email address. + @param userEmailIsVerified An optional string representation of a boolean to indicate that the + email address has been verified. Pass @"true" for @c YES, or @"false" for @c NO. + @discussion Designated initializer. + */ +- (instancetype)initWithAuthState:(OIDAuthState *)authState + serviceProvider:(nullable NSString *)serviceProvider + userID:(nullable NSString *)userID + userEmail:(nullable NSString *)userEmail + userEmailIsVerified:(nullable NSString *)userEmailIsVerified + NS_DESIGNATED_INITIALIZER; + +#if !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT +/*! @brief Convenience method to return an @c OIDServiceConfiguration for Google. + @return A @c OIDServiceConfiguration object setup with Google OAuth endpoints. + */ ++ (OIDServiceConfiguration *)configurationForGoogle; +#endif // !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT + +/*! @brief Adds an authorization header to the given request, using the authorization state. + Refreshes the access token if needed. + @param request The request to authorize. + @param handler The block that is called after authorizing the request is attempted. If @c error + is non-nil, the authorization failed. Errors in the domain @c ::OIDOAuthTokenErrorDomain + indicate that the authorization itself is invalid, and will need to be re-obtained from the + user. Errors in the @c GTMAppAuthFetcherAuthorizationErrorDomain indicate another + unrecoverable errors. Errors in other domains may indicate a transitive error condition such + as a network error, and typically you do not need to reauthenticate the user on such errors. + @discussion The completion handler is scheduled on the main thread, unless the @c callbackQueue + property is set on the @c fetcherService in which case the handler is scheduled on that + queue. + */ +- (void)authorizeRequest:(nullable NSMutableURLRequest *)request + completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)handler; + +/*! @brief Returns YES if the authorization state is currently valid. + @discussion Note that this doesn't guarantee that a request will get a valid authorization, as + the authorization state could become invalid on on the next token refresh. + */ +- (BOOL)canAuthorize; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.m b/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.m new file mode 100644 index 00000000..988a87b3 --- /dev/null +++ b/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.m @@ -0,0 +1,492 @@ +/*! @file GTMAppAuthFetcherAuthorization.m + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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 "GTMAppAuthFetcherAuthorization.h" + +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "AppAuthCore.h" +#endif // GTMAPPAUTH_USER_IMPORTS + +#define GTMOAuth2AssertValidSelector GTMBridgeAssertValidSelector + +/*! @brief Provides a template implementation for init-family methods which have been marked as + NS_UNAVILABLE. Stops the compiler from giving a warning when it's the super class' + designated initializer, and gives callers useful feedback telling them what the + new designated initializer is. + @remarks Takes a SEL as a parameter instead of a string so that we get compiler warnings if the + designated initializer's signature changes. + @param designatedInitializer A SEL referencing the designated initializer. + */ +#define GTM_UNAVAILABLE_USE_INITIALIZER(designatedInitializer) { \ + NSString *reason = [NSString stringWithFormat:@"Called: %@\nDesignated Initializer:%@", \ + NSStringFromSelector(_cmd), \ + NSStringFromSelector(designatedInitializer)]; \ + @throw [NSException exceptionWithName:@"Attempt to call unavailable initializer." \ + reason:reason \ + userInfo:nil]; \ +} + +/*! @brief Key used to encode the @c authState property for @c NSSecureCoding. + */ +static NSString *const kAuthStateKey = @"authState"; + +/*! @brief Key used to encode the @c serviceProvider property for @c NSSecureCoding. + */ +static NSString *const kServiceProviderKey = @"serviceProvider"; + +/*! @brief Key used to encode the @c userID property for @c NSSecureCoding. + */ +static NSString *const kUserIDKey = @"userID"; + +/*! @brief Key used to encode the @c userEmail property for @c NSSecureCoding. + */ +static NSString *const kUserEmailKey = @"userEmail"; + +/*! @brief Key used to encode the @c userEmailIsVerified property for @c NSSecureCoding. + */ +static NSString *const kUserEmailIsVerifiedKey = @"userEmailIsVerified"; + +NSString *const GTMAppAuthFetcherAuthorizationErrorDomain = + @"kGTMAppAuthFetcherAuthorizationErrorDomain"; +NSString *const GTMAppAuthFetcherAuthorizationErrorRequestKey = @"request"; + +/*! @brief Internal wrapper class for requests needing authorization and their callbacks. + @discusssion Used to abstract away the detail of whether a callback or block is used. + */ +@interface GTMAppAuthFetcherAuthorizationArgs : NSObject + +/*! @brief The request to authorize. + * @discussion Not copied, as we are mutating the request. + */ +@property (nonatomic, strong) NSMutableURLRequest *request; + +/*! @brief The delegate on which @c selector is called on completion. + */ +@property (nonatomic, weak) id delegate; + +/*! @brief The selector called on the @c delegate object on completion. + */ +@property (nonatomic) SEL selector; + +/*! @brief The completion block when the block option was used. + */ +@property (nonatomic, strong) GTMAppAuthFetcherAuthorizationCompletion completionHandler; + +/*! @brief The error that happened during token refresh (if any). + */ +@property (nonatomic, strong) NSError *error; + ++ (GTMAppAuthFetcherAuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req + delegate:(id)delegate + selector:(SEL)selector + completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)completionHandler; + +@end + +@implementation GTMAppAuthFetcherAuthorizationArgs + +@synthesize request = _request; +@synthesize delegate = _delegate; +@synthesize selector = _selector; +@synthesize completionHandler = _completionHandler; +@synthesize error = _error; + ++ (GTMAppAuthFetcherAuthorizationArgs *)argsWithRequest:(NSMutableURLRequest *)req + delegate:(id)delegate + selector:(SEL)selector + completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)completionHandler { + GTMAppAuthFetcherAuthorizationArgs *obj; + obj = [[GTMAppAuthFetcherAuthorizationArgs alloc] init]; + obj.request = req; + obj.delegate = delegate; + obj.selector = selector; + obj.completionHandler = completionHandler; + return obj; +} + +@end + +@implementation GTMAppAuthFetcherAuthorization { + /*! @brief Array of requests pending authorization headers. + */ + NSMutableArray *_authorizationQueue; +} + +@synthesize authState = _authState; +@synthesize serviceProvider = _serviceProvider; +@synthesize userID = _userID; +@synthesize userEmailIsVerified = _userEmailIsVerified; + +// GTMFetcherAuthorizationProtocol doesn't specify atomic/nonatomic for these properties. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wimplicit-atomic-properties" +@synthesize userEmail = _userEmail; +@synthesize shouldAuthorizeAllRequests = _shouldAuthorizeAllRequests; +@synthesize fetcherService = _fetcherService; +#pragma clang diagnostic pop + +#pragma mark - Initializers + +// Ignore warning about not calling the designated initializer. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" +- (instancetype)init + GTM_UNAVAILABLE_USE_INITIALIZER(@selector(initWithAuthState:)); +#pragma clang diagnostic pop + +- (instancetype)initWithAuthState:(OIDAuthState *)authState { + return [self initWithAuthState:authState + serviceProvider:nil + userID:nil + userEmail:nil + userEmailIsVerified:nil]; +} + +- (instancetype)initWithAuthState:(OIDAuthState *)authState + serviceProvider:(nullable NSString *)serviceProvider + userID:(nullable NSString *)userID + userEmail:(nullable NSString *)userEmail + userEmailIsVerified:(nullable NSString *)userEmailIsVerified { + self = [super init]; + if (self) { + _authState = authState; + _authorizationQueue = [[NSMutableArray alloc] init]; + + _serviceProvider = [serviceProvider copy]; + _userID = [userID copy]; + _userEmail = [userEmail copy]; + _userEmailIsVerified = [userEmailIsVerified copy]; + + // Decodes the ID Token locally to extract the email address. + NSString *idToken = _authState.lastTokenResponse.idToken + ? : _authState.lastAuthorizationResponse.idToken; + if (idToken) { + NSDictionary *claimsDictionary = [[OIDIDToken alloc] initWithIDTokenString:idToken].claims; + if (claimsDictionary) { + _userEmail = (NSString *)[claimsDictionary[@"email"] copy]; + _userEmailIsVerified = [(NSNumber *)claimsDictionary[@"email_verified"] stringValue]; + _userID = [claimsDictionary[@"sub"] copy]; + } + } + } + return self; +} + +# pragma mark - Convenience + +#if !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT ++ (OIDServiceConfiguration *)configurationForGoogle { + NSURL *authorizationEndpoint = + [NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"]; + NSURL *tokenEndpoint = + [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"]; + + OIDServiceConfiguration *configuration = + [[OIDServiceConfiguration alloc] initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint]; + return configuration; +} +#endif // !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT + +#pragma mark - Authorizing Requests + +/*! @brief Internal routine common to delegate and block invocations to queue requests while + fresh tokens are obtained. + */ +- (void)authorizeRequestArgs:(GTMAppAuthFetcherAuthorizationArgs *)args { + // Adds requests to queue. + @synchronized(_authorizationQueue) { + [_authorizationQueue addObject:args]; + } + + NSDictionary *additionalRefreshParameters = _tokenRefreshDelegate ? + [_tokenRefreshDelegate additionalRefreshParameters:self] : nil; + + // Obtains fresh tokens from AppAuth. + [_authState performActionWithFreshTokens:^(NSString *_Nullable accessToken, + NSString *_Nullable idToken, + NSError *_Nullable error) { + // Processes queue. + @synchronized(self->_authorizationQueue) { + for (GTMAppAuthFetcherAuthorizationArgs *fetcherArgs in self->_authorizationQueue) { + [self authorizeRequestImmediateArgs:fetcherArgs accessToken:accessToken error:error]; + } + [self->_authorizationQueue removeAllObjects]; + } + } + additionalRefreshParameters:additionalRefreshParameters]; +} + +/*! @brief Adds authorization headers to the given request, using the supplied access token, or + handles the error. + @param args The request argument group to authorize. + @param accessToken A currently valid access token. + @param error If accessToken is nil, the error which caused the token to be unavailable. + @return YES if the request was authorized with a valid access token. + */ +- (BOOL)authorizeRequestImmediateArgs:(GTMAppAuthFetcherAuthorizationArgs *)args + accessToken:(NSString *)accessToken + error:(NSError *)error { + // This authorization entry point never attempts to refresh the access token, + // but does call the completion routine + + NSMutableURLRequest *request = args.request; + + NSURL *requestURL = [request URL]; + NSString *scheme = [requestURL scheme]; + BOOL isAuthorizableRequest = + !requestURL + || (scheme && [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame) + || [requestURL isFileURL] + || self.shouldAuthorizeAllRequests; + if (!isAuthorizableRequest) { + // Request is not https, a local file, or nil, 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. + if (isAuthorizableRequest && accessToken && accessToken.length > 0) { + if (request) { + // Adds the authorization header to the request. + NSString *value = [NSString stringWithFormat:@"%@ %@", @"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 { + NSMutableDictionary *userInfo = [error.userInfo mutableCopy]; + if (!userInfo) { + userInfo = [[NSMutableDictionary alloc] init]; + } + if (request) { + userInfo[GTMAppAuthFetcherAuthorizationErrorRequestKey] = request; + } + + if (!isAuthorizableRequest || !error) { + args.error = [NSError errorWithDomain:GTMAppAuthFetcherAuthorizationErrorDomain + code:GTMAppAuthFetcherAuthorizationErrorUnauthorizableRequest + userInfo:userInfo]; + } else { + // Passes through error domain & code from AppAuth, with additional userInfo args. + args.error = [NSError errorWithDomain:error.domain + code:error.code + userInfo:userInfo]; + } + } + + // Invoke any callbacks on the proper thread + if (args.delegate || args.completionHandler) { + // If the fetcher service provides a callback queue, we'll use that + // (or if it's nil, we'll use the main thread) for callbacks. + dispatch_queue_t callbackQueue = self.fetcherService.callbackQueue; + if (!callbackQueue) { + callbackQueue = dispatch_get_main_queue(); + } + dispatch_async(callbackQueue, ^{ + [self invokeCallbackArgs:args]; + }); + } + + BOOL didAuth = (args.error == nil); + return didAuth; +} + +/*! @brief Invokes the callback for the given authorization argument group. + @param args The request argument group to invoke following authorization or error. + */ +- (void)invokeCallbackArgs:(GTMAppAuthFetcherAuthorizationArgs *)args { + NSError *error = args.error; + id delegate = args.delegate; + SEL sel = args.selector; + + // If the selector callback method exists, invokes the selector. + if (delegate && sel) { + NSMutableURLRequest *request = args.request; + + NSMethodSignature *sig = [delegate methodSignatureForSelector:sel]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:sel]; + [invocation setTarget:delegate]; + GTMAppAuthFetcherAuthorization *authorization = self; + [invocation setArgument:&authorization atIndex:2]; + [invocation setArgument:&request atIndex:3]; + [invocation setArgument:&error atIndex:4]; + [invocation invoke]; + } + + // If a callback block exists, executes the block. + id handler = args.completionHandler; + if (handler) { + void (^authCompletionBlock)(NSError *) = handler; + authCompletionBlock(error); + } +} + +#pragma mark - GTMFetcherAuthorizationProtocol + +/*! @brief Authorizing with a callback selector. + @discussion Selector has the signature: + - (void)authentication:(GTMAppAuthFetcherAuthorization *)auth + request:(NSMutableURLRequest *)request + finishedWithError:(NSError *)error; + */ +- (void)authorizeRequest:(NSMutableURLRequest *)request + delegate:(id)delegate + didFinishSelector:(SEL)sel { + GTMOAuth2AssertValidSelector(delegate, sel, + @encode(GTMAppAuthFetcherAuthorization *), + @encode(NSMutableURLRequest *), + @encode(NSError *), 0); + + GTMAppAuthFetcherAuthorizationArgs *args; + args = [GTMAppAuthFetcherAuthorizationArgs argsWithRequest:request + delegate:delegate + selector:sel + completionHandler:nil]; + [self authorizeRequestArgs:args]; +} + +/*! @brief Removes all pending requests from the authorization queue. + */ +- (void)stopAuthorization { + @synchronized(_authorizationQueue) { + [_authorizationQueue removeAllObjects]; + } +} + +/*! @brief Attempts to remove a specific pending requests from the authorization queue. + @discussion Has no effect if the authorization already occurred. + */ +- (void)stopAuthorizationForRequest:(NSURLRequest *)request { + @synchronized(_authorizationQueue) { + NSUInteger argIndex = 0; + BOOL found = NO; + for (GTMAppAuthFetcherAuthorizationArgs *args in _authorizationQueue) { + // Checks pointer equality with given request, don't want to match equivalent requests. + 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]; + } + } + } +} + +/*! @brief Returns YES if the given requests is in the pending authorization queue. + */ +- (BOOL)isAuthorizingRequest:(NSURLRequest *)request { + BOOL wasFound = NO; + @synchronized(_authorizationQueue) { + for (GTMAppAuthFetcherAuthorizationArgs *args in _authorizationQueue) { + // Checks pointer equality with given request, don't want to match equivalent requests. + if ([args request] == request) { + wasFound = YES; + break; + } + } + } + return wasFound; +} + +/*! @brief Returns YES if given request has an Authorization header. + */ +- (BOOL)isAuthorizedRequest:(NSURLRequest *)request { + NSString *authStr = [request valueForHTTPHeaderField:@"Authorization"]; + return (authStr.length > 0); +} + +/*! @brief Returns YES if the authorization state is currently valid. + @discussion Note that the state can become invalid immediately due to an error on token refresh. + */ +- (BOOL)canAuthorize { + return [_authState isAuthorized]; +} + +/*! @brief Authorizing with a completion block. + */ +- (void)authorizeRequest:(NSMutableURLRequest *)request + completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)handler { + GTMAppAuthFetcherAuthorizationArgs *args = + [GTMAppAuthFetcherAuthorizationArgs argsWithRequest:request + delegate:nil + selector:NULL + completionHandler:handler]; + [self authorizeRequestArgs:args]; +} + +/*! @brief Forces a token refresh the next time a request is queued for authorization. + */ +- (BOOL)primeForRefresh { + if (_authState.refreshToken == nil) { + // Cannot refresh without a refresh token + return NO; + } + [_authState setNeedsTokenRefresh]; + return YES; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + OIDAuthState *authState = + [aDecoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; + NSString *serviceProvider = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kServiceProviderKey]; + NSString *userID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserIDKey]; + NSString *userEmail = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserEmailKey]; + NSString *userEmailIsVerified = + [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserEmailIsVerifiedKey]; + + self = [self initWithAuthState:authState + serviceProvider:serviceProvider + userID:userID + userEmail:userEmail + userEmailIsVerified:userEmailIsVerified]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_authState forKey:kAuthStateKey]; + [aCoder encodeObject:_serviceProvider forKey:kServiceProviderKey]; + [aCoder encodeObject:_userID forKey:kUserIDKey]; + [aCoder encodeObject:_userEmail forKey:kUserEmailKey]; + [aCoder encodeObject:_userEmailIsVerified forKey:kUserEmailIsVerifiedKey]; +} + +@end diff --git a/Pods/GTMAppAuth/Source/GTMKeychain.h b/Pods/GTMAppAuth/Source/GTMKeychain.h new file mode 100644 index 00000000..b4d5d062 --- /dev/null +++ b/Pods/GTMAppAuth/Source/GTMKeychain.h @@ -0,0 +1,62 @@ +/*! @file GTMKeychain.h + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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 + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Utility for saving and loading data to the keychain. + */ +@interface GTMKeychain : NSObject + +/*! @brief Saves the password string to the keychain with the given identifier. + @param keychainItemName Keychain name of the item. + @param password Password string to save. + @return YES when the password string was saved successfully. + */ ++ (BOOL)savePasswordToKeychainForName:(NSString *)keychainItemName password:(NSString *)password; + +/*! @brief Loads the password string from the keychain with the given identifier. + @param keychainItemName Keychain name of the item. + @return The password string at the given identifier, or nil. + */ ++ (nullable NSString *)passwordFromKeychainForName:(NSString *)keychainItemName; + +/*! @brief Saves the password data to the keychain with the given identifier. + @param keychainItemName Keychain name of the item. + @param passwordData Password data to save. + @return YES when the password data was saved successfully. + */ ++ (BOOL)savePasswordDataToKeychainForName:(NSString *)keychainItemName + passwordData:(NSData *)passwordData; + +/*! @brief Loads the password data from the keychain with the given identifier. + @param keychainItemName Keychain name of the item. + @return The password data at the given identifier, or nil. + */ ++ (nullable NSData *)passwordDataFromKeychainForName:(NSString *)keychainItemName; + +/*! @brief Removes stored password string, such as when the user signs out. + @param keychainItemName Keychain name of the item. + @return YES if the password string was removed successfully (or didn't exist). + */ ++ (BOOL)removePasswordFromKeychainForName:(NSString *)keychainItemName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.h b/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.h new file mode 100644 index 00000000..9c0c5bf1 --- /dev/null +++ b/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.h @@ -0,0 +1,131 @@ +/*! @file GTMOAuth2Compatibility.h + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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 "GTMAppAuthFetcherAuthorization.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Class to support serialization and deserialization of @c GTMAppAuthFetcherAuthorization + in the format used by GTMOAuth2. + @discussion The methods of this class are capable of serializing and deserializing auth + objects in a way compatible with the serialization in @c GTMOAuth2ViewControllerTouch and + @c GTMOAuth2WindowController in GTMOAuth2. + */ +@interface GTMOAuth2KeychainCompatibility : NSObject + +/*! @brief Encodes the given @c GTMAppAuthFetcherAuthorization in a GTMOAuth2 compatible persistence + string using URL param key/value encoding. + @param authorization The @c GTMAppAuthFetcherAuthorization to serialize in GTMOAuth2 format. + @return The GTMOAuth2 persistence representation of this object. + */ ++ (NSString *)persistenceResponseStringForAuthorization: + (GTMAppAuthFetcherAuthorization *)authorization; + +/*! @brief Attempts to create a @c GTMAppAuthFetcherAuthorization from data stored in the keychain + in GTMOAuth2 format, at the supplied keychain identifier. + @param keychainItemName The keychain name. + @param tokenURL The OAuth token endpoint URL. + @param redirectURI The OAuth redirect URI used when obtaining the original authorization. + @param clientID The OAuth client id. + @param clientSecret The OAuth client secret. + @return A @c GTMAppAuthFetcherAuthorization object, or nil. + */ ++ (nullable GTMAppAuthFetcherAuthorization *) + authorizeFromKeychainForName:(NSString *)keychainItemName + tokenURL:(NSURL *)tokenURL + redirectURI:(NSString *)redirectURI + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret; + +/*! @brief Attempts to create a @c GTMAppAuthFetcherAuthorization from a @c NSString + representation of the GTMOAuth2 keychain data. + @param persistenceString String representation of the GTMOAuth2 keychain data. + @param tokenURL The OAuth token endpoint URL. + @param redirectURI The OAuth redirect URI used when obtaining the original authorization. + @param clientID The OAuth client id. + @param clientSecret The OAuth client secret. + @return A @c GTMAppAuthFetcherAuthorization object, or nil. + */ ++ (nullable GTMAppAuthFetcherAuthorization *) + authorizeFromPersistenceString:(NSString *)persistenceString + tokenURL:(NSURL *)tokenURL + redirectURI:(NSString *)redirectURI + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret; + +/*! @brief Removes stored tokens, such as when the user signs out. + @param keychainItemName The keychain name. + @return YES the tokens were removed successfully (or didn't exist). + */ ++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName; + +/*! @brief Saves the authorization state to the keychain, in a GTMOAuth2 compatible manner. + @param keychainItemName The keychain name. + @return YES when the state was saved successfully. + */ ++ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName + authentication:(GTMAppAuthFetcherAuthorization *)auth + __attribute__((deprecated( + "Use GTMAppAuthFetcherAuthorization::saveAuthorization:toKeychainForName:"))); + +#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT + +/*! @brief Attempts to create a @c GTMAppAuthFetcherAuthorization from data stored in the keychain + in GTMOAuth2 format, at the supplied keychain identifier. Uses Google OAuth provider + information. + @param keychainItemName The keychain name. + @param clientID The OAuth client id. + @param clientSecret The OAuth client secret. + @return A @c GTMAppAuthFetcherAuthorization object, or nil. + */ ++ (nullable GTMAppAuthFetcherAuthorization *) + authForGoogleFromKeychainForName:(NSString *)keychainItemName + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret; + +/*! @brief Returns Google's OAuth 2.0 authorization endpoint. + @return Returns Google's OAuth 2.0 authorization endpoint. + */ ++ (NSURL *)googleAuthorizationURL; + +/*! @brief Returns Google's OAuth 2.0 token endpoint. + @return Returns Google's OAuth 2.0 token endpoint. + */ ++ (NSURL *)googleTokenURL; + +/*! @brief Returns Google's OAuth 2.0 revocation endpoint. + @return Returns Google's OAuth 2.0 revocation endpoint. + */ ++ (NSURL *)googleRevocationURL; + +/*! @brief Returns Google's OAuth 2.0 userinfo endpoint. + @return Returns Google's OAuth 2.0 userinfo endpoint. + */ ++ (NSURL *)googleUserInfoURL; + +/*! @brief Returns Google's native OOB redirect URI. + @discussion This is a legacy redirect URI that was used with WebViews. + @return Returns Google's native OOB redirect URI. + */ ++ (NSString *)nativeClientRedirectURI; + +#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT + +@end + +NS_ASSUME_NONNULL_END diff --git a/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.m b/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.m new file mode 100644 index 00000000..5e56341b --- /dev/null +++ b/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.m @@ -0,0 +1,325 @@ +/*! @file GTMOAuth2Compatibility.m + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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 "GTMOAuth2KeychainCompatibility.h" + +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "AppAuthCore.h" +#endif // GTMAPPAUTH_USER_IMPORTS + +#import "GTMKeychain.h" + +// standard OAuth keys +static NSString *const kOAuth2AccessTokenKey = @"access_token"; +static NSString *const kOAuth2RefreshTokenKey = @"refresh_token"; +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"; + +// URI indicating an installed app is signing in. This is described at +// +// https://developers.google.com/identity/protocols/OAuth2InstalledApp#formingtheurl +// +static NSString *const kOOBString = @"urn:ietf:wg:oauth:2.0:oob"; + +@implementation GTMOAuth2KeychainCompatibility + +// This returns a "response string" that can be passed later to +// setKeysForResponseString: to reuse an old access token in a new auth object ++ (NSString *)persistenceResponseStringForAuthorization: + (GTMAppAuthFetcherAuthorization *)authorization { + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + NSString *refreshToken = authorization.authState.refreshToken; + NSString *accessToken = authorization.authState.lastTokenResponse.accessToken; + + // Any nil values will not set a dictionary entry + [dict setValue:refreshToken forKey:kOAuth2RefreshTokenKey]; + [dict setValue:accessToken forKey:kOAuth2AccessTokenKey]; + [dict setValue:authorization.serviceProvider forKey:kServiceProviderKey]; + [dict setValue:authorization.userID forKey:kUserIDKey]; + [dict setValue:authorization.userEmail forKey:kUserEmailKey]; + [dict setValue:authorization.userEmailIsVerified forKey:kUserEmailIsVerifiedKey]; + [dict setValue:authorization.authState.scope forKey:kOAuth2ScopeKey]; + + NSString *result = [self encodedQueryParametersForDictionary:dict]; + return result; +} + ++ (GTMAppAuthFetcherAuthorization *)authorizeFromKeychainForName:(NSString *)keychainItemName + tokenURL:(NSURL *)tokenURL + redirectURI:(NSString *)redirectURI + clientID:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret { + // Loads password string from keychain. + NSString *password = [GTMKeychain passwordFromKeychainForName:keychainItemName]; + + if (!password) { + return nil; + } + + GTMAppAuthFetcherAuthorization *authorization = + [self authorizeFromPersistenceString:password + tokenURL:tokenURL + redirectURI:redirectURI + clientID:clientID + clientSecret:clientSecret]; + return authorization; +} + ++ (GTMAppAuthFetcherAuthorization *)authorizeFromPersistenceString:(NSString *)persistenceString + tokenURL:(NSURL *)tokenURL + redirectURI:(NSString *)redirectURIString + clientID:(NSString *)clientID + clientSecret:(NSString *)clientSecret { + // Parses persistence data into NSDictionary. + NSDictionary *dict = [self dictionaryWithResponseString:persistenceString]; + + NSURL *redirectURI = (NSURL *)[NSURL URLWithString:redirectURIString]; + + // OIDAuthState is based on the request/response history. + // Creates history based on the data from the keychain, and client details passed in. + OIDServiceConfiguration *authConfig = + [[OIDServiceConfiguration alloc] initWithAuthorizationEndpoint:tokenURL tokenEndpoint:tokenURL]; + OIDAuthorizationRequest *authRequest = + [[OIDAuthorizationRequest alloc] initWithConfiguration:authConfig + clientId:clientID + clientSecret:clientSecret + scope:dict[kOAuth2ScopeKey] + redirectURL:redirectURI + responseType:OIDResponseTypeCode + state:nil + nonce:nil + codeVerifier:nil + codeChallenge:nil + codeChallengeMethod:nil + additionalParameters:nil]; + OIDAuthorizationResponse *authResponse = + [[OIDAuthorizationResponse alloc] initWithRequest:authRequest parameters:dict]; + // Exclude scope and refresh token parameters from additionalParameters. + NSMutableDictionary *additionalParameters = [dict mutableCopy]; + [additionalParameters removeObjectForKey:kOAuth2ScopeKey]; + [additionalParameters removeObjectForKey:kOAuth2RefreshTokenKey]; + OIDTokenRequest *tokenRequest = + [[OIDTokenRequest alloc] initWithConfiguration:authConfig + grantType:@"token" + authorizationCode:nil + redirectURL:redirectURI + clientID:clientID + clientSecret:clientSecret + scope:dict[kOAuth2ScopeKey] + refreshToken:dict[kOAuth2RefreshTokenKey] + codeVerifier:nil + additionalParameters:additionalParameters]; + OIDTokenResponse *tokenResponse = + [[OIDTokenResponse alloc] initWithRequest:tokenRequest parameters:dict]; + OIDAuthState *authState = [[OIDAuthState alloc] initWithAuthorizationResponse:authResponse + tokenResponse:tokenResponse]; + // We're not serializing the token expiry date, so the first refresh needs to be forced. + [authState setNeedsTokenRefresh]; + + GTMAppAuthFetcherAuthorization *authorizer = + [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState + serviceProvider:dict[kServiceProviderKey] + userID:dict[kUserIDKey] + userEmail:dict[kUserEmailKey] + userEmailIsVerified:dict[kUserEmailIsVerifiedKey]]; + return authorizer; +} + +#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT + ++ (GTMAppAuthFetcherAuthorization *)authForGoogleFromKeychainForName:(NSString *)keychainItemName + clientID:(NSString *)clientID + clientSecret:(NSString *)clientSecret { + Class signInClass = self; + NSURL *tokenURL = [signInClass googleTokenURL]; + NSString *redirectURI = [signInClass nativeClientRedirectURI]; + + GTMAppAuthFetcherAuthorization *auth; + auth = [self authorizeFromKeychainForName:keychainItemName + tokenURL:tokenURL + redirectURI:redirectURI + clientID:clientID + clientSecret:clientSecret]; + return auth; +} + +#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT + +/*! @brief Removes stored tokens, such as when the user signs out. + @return YES the tokens were removed successfully (or didn't exist). + */ ++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName { + return [GTMKeychain removePasswordFromKeychainForName:keychainItemName]; +} + +/*! @brief Saves the authorization state to the keychain, in a GTMOAuth2 compatible manner. + @return YES when the state was saved successfully. + */ ++ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName + authentication:(GTMAppAuthFetcherAuthorization *)auth { + [self removeAuthFromKeychainForName:keychainItemName]; + NSString *password = [self persistenceResponseStringForAuthorization:auth]; + + return [GTMKeychain savePasswordToKeychainForName:keychainItemName password:password]; +} + +#pragma mark Utility Routines + ++ (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; +} + ++ (NSString *)encodedOAuthValueForString:(NSString *)originalString { + // For parameters, we'll explicitly leave spaces unescaped now, and replace + // them with +'s + NSString *const kForceEscape = @"!*'();:@&=+$,/?%#[]"; + +#if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_7_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) + // Builds targeting iOS 7/OS X 10.9 and higher only. + NSMutableCharacterSet *cs = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; + [cs removeCharactersInString:kForceEscape]; + + return [originalString stringByAddingPercentEncodingWithAllowedCharacters:cs]; +#else + // Builds targeting iOS 6/OS X 10.8. + CFStringRef escapedStr = NULL; + if (originalString) { + escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)originalString, + NULL, + (CFStringRef)kForceEscape, + kCFStringEncodingUTF8); + } + + return (__bridge NSString *)escapedStr; +#endif +} + ++ (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; + 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 '=' + if (equalsRange.location + 1 < item.length) { + value = [item substringFromIndex:(equalsRange.location + 1)]; + } + } else { + // The parameter has no '=' + key = item; + } + + NSString *plainKey = [self unencodedOAuthParameterForString:key]; + NSString *plainValue = [self unencodedOAuthParameterForString:value]; + + [responseDict setObject:plainValue forKey:plainKey]; + } + + return responseDict; +} + ++ (NSString *)unencodedOAuthParameterForString:(NSString *)str { +#if (!TARGET_OS_IPHONE \ + && defined(MAC_OS_X_VERSION_10_9) \ + && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9) \ + || (TARGET_OS_IPHONE \ + && defined(__IPHONE_7_0) \ + && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) + // On iOS 7, -stringByRemovingPercentEncoding incorrectly returns nil for an empty string. + if (str != nil && [str length] == 0) return @""; + + NSString *plainStr = [str stringByRemovingPercentEncoding]; + return plainStr; +#else + NSString *plainStr = [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + return plainStr; +#endif +} + +#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT + +// Endpoint URLs are available at https://accounts.google.com/.well-known/openid-configuration + ++ (NSURL *)googleAuthorizationURL { + NSString *str = @"https://accounts.google.com/o/oauth2/v2/auth"; + return (NSURL *)[NSURL URLWithString:str]; +} + ++ (NSURL *)googleTokenURL { + NSString *str = @"https://www.googleapis.com/oauth2/v4/token"; + return (NSURL *)[NSURL URLWithString:str]; +} + ++ (NSURL *)googleRevocationURL { + NSString *urlStr = @"https://accounts.google.com/o/oauth2/revoke"; + return (NSURL *)[NSURL URLWithString:urlStr]; +} + ++ (NSURL *)googleUserInfoURL { + NSString *urlStr = @"https://www.googleapis.com/oauth2/v3/userinfo"; + return (NSURL *)[NSURL URLWithString:urlStr]; +} + ++ (NSString *)nativeClientRedirectURI { + return kOOBString; +} + +#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT + +@end diff --git a/Pods/GTMAppAuth/Source/iOS/GTMKeychain_iOS.m b/Pods/GTMAppAuth/Source/iOS/GTMKeychain_iOS.m new file mode 100644 index 00000000..ab64882e --- /dev/null +++ b/Pods/GTMAppAuth/Source/iOS/GTMKeychain_iOS.m @@ -0,0 +1,287 @@ +/*! @file GTMKeychain_iOS.m + @brief GTMAppAuth SDK + @copyright + Copyright 2016 Google Inc. + @copydetails + 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 "GTMKeychain.h" + +#import + +typedef NS_ENUM(NSInteger, GTMAppAuthFetcherAuthorizationGTMAppAuthGTMOAuth2KeychainError) { + GTMAppAuthGTMOAuth2KeychainErrorBadArguments = -1301, + GTMAppAuthGTMOAuth2KeychainErrorNoPassword = -1302 +}; + +/*! @brief Keychain helper class. + */ +@interface GTMAppAuthGTMOAuth2Keychain : NSObject + ++ (GTMAppAuthGTMOAuth2Keychain *)defaultKeychain; + +// OK to pass nil for the error parameter. +- (NSString *)passwordForService:(NSString *)service + account:(NSString *)account + error:(NSError **)error; + +- (NSData *)passwordDataForService:(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; + +- (BOOL)setPasswordData:(NSData *)passwordData + forService:(NSString *)service + accessibility:(CFTypeRef)accessibility + account:(NSString *)account + error:(NSError **)error; + +// For unit tests: allow setting a mock object ++ (void)setDefaultKeychain:(GTMAppAuthGTMOAuth2Keychain *)keychain; + +@end + +NSString *const kGTMAppAuthFetcherAuthorizationGTMOAuth2ErrorDomain = @"com.google.GTMOAuth2"; +NSString *const kGTMAppAuthFetcherAuthorizationGTMOAuth2KeychainErrorDomain = + @"com.google.GTMOAuthKeychain"; +static NSString *const kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName = @"OAuth"; +static GTMAppAuthGTMOAuth2Keychain* gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain = nil; + +@implementation GTMKeychain + ++ (BOOL)removePasswordFromKeychainForName:(NSString *)keychainItemName { + GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain]; + return [keychain removePasswordForService:keychainItemName + account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName + error:nil]; +} + ++ (NSString *)passwordFromKeychainForName:(NSString *)keychainItemName { + GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain]; + NSError *error; + NSString *password = + [keychain passwordForService:keychainItemName + account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName + error:&error]; + return password; +} + ++ (BOOL)savePasswordToKeychainForName:(NSString *)keychainItemName password:(NSString *)password { + return [self savePasswordToKeychainForName:keychainItemName + password:password + accessibility:NULL + error:NULL]; +} + ++ (BOOL)savePasswordToKeychainForName:(NSString *)keychainItemName password:(NSString *)password + accessibility:(CFTypeRef)accessibility + error:(NSError **)error { + if (accessibility == NULL) { + accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + } + // make a response string containing the values we want to save + GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain]; + return [keychain setPassword:password + forService:keychainItemName + accessibility:accessibility + account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName + error:error]; +} + +/*! @brief Saves the password string to the keychain with the given identifier. + @param keychainItemName Keychain name of the item. + @param password Password string to save. + @return YES when the password string was saved successfully. + */ ++ (BOOL)savePasswordDataToKeychainForName:(NSString *)keychainItemName + passwordData:(NSData *)password { + CFTypeRef accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + // make a response string containing the values we want to save + GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain]; + return [keychain setPasswordData:password + forService:keychainItemName + accessibility:accessibility + account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName + error:NULL]; +} + +/*! @brief Loads the password string from the keychain with the given identifier. + @param keychainItemName Keychain name of the item. + @return The password string at the given identifier, or nil. + */ ++ (NSData *)passwordDataFromKeychainForName:(NSString *)keychainItemName { + GTMAppAuthGTMOAuth2Keychain *keychain = [GTMAppAuthGTMOAuth2Keychain defaultKeychain]; + NSError *error; + NSData *password = + [keychain passwordDataForService:keychainItemName + account:kGTMAppAuthFetcherAuthorizationGTMOAuth2AccountName + error:&error]; + return password; +} + +@end + +#pragma mark GTMAppAuthGTMOAuth2Keychain + +@implementation GTMAppAuthGTMOAuth2Keychain + ++ (GTMAppAuthGTMOAuth2Keychain *)defaultKeychain { + static dispatch_once_t onceToken; + dispatch_once (&onceToken, ^{ + gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain = [[self alloc] init]; + }); + return gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain; +} + + +// For unit tests: allow setting a mock object ++ (void)setDefaultKeychain:(GTMAppAuthGTMOAuth2Keychain *)keychain { + if (gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain != keychain) { + gGTMAppAuthFetcherAuthorizationGTMOAuth2DefaultKeychain = keychain; + } +} + +- (NSString *)keyForService:(NSString *)service account:(NSString *)account { + return [NSString stringWithFormat:@"com.google.GTMOAuth.%@%@", service, account]; +} + ++ (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 { + NSData *passwordData = [self passwordDataForService:service account:account error:error]; + if (!passwordData) { + return nil; + } + NSString *result = [[NSString alloc] initWithData:passwordData + encoding:NSUTF8StringEncoding]; + return result; +} + +// iPhone +- (NSData *)passwordDataForService:(NSString *)service + account:(NSString *)account + error:(NSError **)error { + OSStatus status = GTMAppAuthGTMOAuth2KeychainErrorBadArguments; + NSData *result = nil; + if (service.length > 0 && account.length > 0) { + 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 < [(__bridge NSData *)passwordData length]) { + result = [(__bridge NSData *)passwordData copy]; + } + if (passwordData != NULL) { + CFRelease(passwordData); + } + } + if (status != noErr && error != NULL) { + *error = [NSError errorWithDomain:kGTMAppAuthFetcherAuthorizationGTMOAuth2KeychainErrorDomain + code:status + userInfo:nil]; + } + return result; +} + +// iPhone +- (BOOL)removePasswordForService:(NSString *)service + account:(NSString *)account + error:(NSError **)error { + OSStatus status = GTMAppAuthGTMOAuth2KeychainErrorBadArguments; + 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:kGTMAppAuthFetcherAuthorizationGTMOAuth2KeychainErrorDomain + code:status + userInfo:nil]; + } + return status == noErr; +} + +// iPhone +- (BOOL)setPassword:(NSString *)password + forService:(NSString *)service + accessibility:(CFTypeRef)accessibility + account:(NSString *)account + error:(NSError **)error { + NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; + return [self setPasswordData:passwordData + forService:service + accessibility:accessibility + account:account + error:error]; +} + +- (BOOL)setPasswordData:(NSData *)passwordData + forService:(NSString *)service + accessibility:(CFTypeRef)accessibility + account:(NSString *)account + error:(NSError **)error { + OSStatus status = GTMAppAuthGTMOAuth2KeychainErrorBadArguments; + if (0 < [service length] && 0 < [account length]) { + [self removePasswordForService:service account:account error:nil]; + if (0 < [passwordData length]) { + NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account]; + [keychainQuery setObject:passwordData forKey:(id)kSecValueData]; + + if (accessibility != NULL) { + [keychainQuery setObject:(__bridge id)accessibility + forKey:(id)kSecAttrAccessible]; + } + status = SecItemAdd((CFDictionaryRef)keychainQuery, NULL); + } + } + if (status != noErr && error != NULL) { + *error = [NSError errorWithDomain:kGTMAppAuthFetcherAuthorizationGTMOAuth2KeychainErrorDomain + code:status + userInfo:nil]; + } + return status == noErr; +} + +@end diff --git a/Pods/GTMSessionFetcher/LICENSE b/Pods/GTMSessionFetcher/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/Pods/GTMSessionFetcher/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Pods/GTMSessionFetcher/README.md b/Pods/GTMSessionFetcher/README.md new file mode 100644 index 00000000..478efde9 --- /dev/null +++ b/Pods/GTMSessionFetcher/README.md @@ -0,0 +1,23 @@ +# Google Toolbox for Mac - Session Fetcher # + +**Project site**
+**Discussion group** + +[![Build Status](https://travis-ci.org/google/gtm-session-fetcher.svg?branch=master)](https://travis-ci.org/google/gtm-session-fetcher) + +`GTMSessionFetcher` makes it easy for Cocoa applications to perform http +operations. The fetcher is implemented as a wrapper on `NSURLSession`, so its +behavior is asynchronous and uses operating-system settings on iOS and Mac OS X. + +Features include: +- Simple to build; only one source/header file pair is required +- Simple to use: takes just two lines of code to fetch a request +- Supports upload and download sessions +- Flexible cookie storage +- Automatic retry on errors, with exponential backoff +- Support for generating multipart MIME upload streams +- Easy, convenient logging of http requests and responses +- Supports plug-in authentication such as with GTMAppAuth +- Easily testable; self-mocking +- Automatic rate limiting when created by the `GTMSessionFetcherService` factory class +- Fully independent of other projects diff --git a/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h b/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h new file mode 100644 index 00000000..ec3c0125 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h @@ -0,0 +1,52 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +// The GTMGatherInput stream is an input stream implementation that is to be +// instantiated with an NSArray of NSData objects. It works in the traditional +// scatter/gather vector I/O model. Rather than allocating a big NSData object +// to hold all of the data and performing a copy into that object, the +// GTMGatherInputStream will maintain a reference to the NSArray and read from +// each NSData in turn as the read method is called. You should not alter the +// underlying set of NSData objects until all read operations on this input +// stream have completed. + +#import + +#ifndef GTM_NONNULL + #if defined(__has_attribute) + #if __has_attribute(nonnull) + #define GTM_NONNULL(x) __attribute__((nonnull x)) + #else + #define GTM_NONNULL(x) + #endif + #else + #define GTM_NONNULL(x) + #endif +#endif + +// Avoid multiple declaration of this class. +// +// Note: This should match the declaration of GTMGatherInputStream in GTMMIMEDocument.m + +#ifndef GTM_GATHERINPUTSTREAM_DECLARED +#define GTM_GATHERINPUTSTREAM_DECLARED + +@interface GTMGatherInputStream : NSInputStream + ++ (NSInputStream *)streamWithArray:(NSArray *)dataArray GTM_NONNULL((1)); + +@end + +#endif // GTM_GATHERINPUTSTREAM_DECLARED diff --git a/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m b/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m new file mode 100644 index 00000000..0f65310f --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m @@ -0,0 +1,185 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMGatherInputStream.h" + +@implementation GTMGatherInputStream { + NSArray *_dataArray; // NSDatas that should be "gathered" and streamed. + NSUInteger _arrayIndex; // Index in the array of the current NSData. + long long _dataOffset; // Offset in the current NSData we are processing. + NSStreamStatus _streamStatus; + id __weak _delegate; // Stream delegate, defaults to self. +} + ++ (NSInputStream *)streamWithArray:(NSArray *)dataArray { + return [(GTMGatherInputStream *)[self alloc] initWithArray:dataArray]; +} + +- (instancetype)initWithArray:(NSArray *)dataArray { + self = [super init]; + if (self) { + _dataArray = dataArray; + _delegate = self; // An NSStream's default delegate should be self. + } + return self; +} + +#pragma mark - NSStream + +- (void)open { + _arrayIndex = 0; + _dataOffset = 0; + _streamStatus = NSStreamStatusOpen; +} + +- (void)close { + _streamStatus = NSStreamStatusClosed; +} + +- (id)delegate { + return _delegate; +} + +- (void)setDelegate:(id)delegate { + if (delegate == nil) { + _delegate = self; + } else { + _delegate = delegate; + } +} + +- (id)propertyForKey:(NSString *)key { + if ([key isEqual:NSStreamFileCurrentOffsetKey]) { + return @([self absoluteOffset]); + } + return nil; +} + +- (BOOL)setProperty:(id)property forKey:(NSString *)key { + if ([key isEqual:NSStreamFileCurrentOffsetKey]) { + NSNumber *absoluteOffsetNumber = property; + [self setAbsoluteOffset:absoluteOffsetNumber.longLongValue]; + return YES; + } + return NO; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { +} + +- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { +} + +- (NSStreamStatus)streamStatus { + return _streamStatus; +} + +- (NSError *)streamError { + return nil; +} + +#pragma mark - NSInputStream + +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { + NSInteger bytesRead = 0; + NSUInteger bytesRemaining = len; + + // Read bytes from the currently-indexed array. + while ((bytesRemaining > 0) && (_arrayIndex < _dataArray.count)) { + NSData *data = [_dataArray objectAtIndex:_arrayIndex]; + + NSUInteger dataLen = data.length; + NSUInteger dataBytesLeft = dataLen - (NSUInteger)_dataOffset; + + NSUInteger bytesToCopy = MIN(bytesRemaining, dataBytesLeft); + NSRange range = NSMakeRange((NSUInteger) _dataOffset, bytesToCopy); + + [data getBytes:(buffer + bytesRead) range:range]; + + bytesRead += bytesToCopy; + _dataOffset += bytesToCopy; + bytesRemaining -= bytesToCopy; + + if (_dataOffset == (long long)dataLen) { + _dataOffset = 0; + _arrayIndex++; + } + } + if (_arrayIndex >= _dataArray.count) { + _streamStatus = NSStreamStatusAtEnd; + } + return bytesRead; +} + +- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len { + return NO; // We don't support this style of reading. +} + +- (BOOL)hasBytesAvailable { + // If we return no, the read never finishes, even if we've already delivered all the bytes. + return YES; +} + +#pragma mark - NSStreamDelegate + +- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { + id delegate = _delegate; + if (delegate != self) { + [delegate stream:self handleEvent:streamEvent]; + } +} + +#pragma mark - Private + +- (long long)absoluteOffset { + long long absoluteOffset = 0; + NSUInteger index = 0; + for (NSData *data in _dataArray) { + if (index >= _arrayIndex) { + break; + } + absoluteOffset += data.length; + ++index; + } + absoluteOffset += _dataOffset; + return absoluteOffset; +} + +- (void)setAbsoluteOffset:(long long)absoluteOffset { + if (absoluteOffset < 0) { + absoluteOffset = 0; + } + _arrayIndex = 0; + _dataOffset = absoluteOffset; + for (NSData *data in _dataArray) { + long long dataLen = (long long) data.length; + if (dataLen > _dataOffset) { + break; + } + _arrayIndex++; + _dataOffset -= dataLen; + } + if (_arrayIndex == _dataArray.count) { + if (_dataOffset > 0) { + _dataOffset = 0; + } + } +} + +@end diff --git a/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h b/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h new file mode 100644 index 00000000..451e1323 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h @@ -0,0 +1,148 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 is a simple class to create or parse a MIME document. + +// To create a MIME document, allocate a new GTMMIMEDocument and start adding parts. +// When you are done adding parts, call generateInputStream or generateDispatchData. +// +// A good reference for MIME is http://en.wikipedia.org/wiki/MIME + +#import + +#ifndef GTM_NONNULL + #if defined(__has_attribute) + #if __has_attribute(nonnull) + #define GTM_NONNULL(x) __attribute__((nonnull x)) + #else + #define GTM_NONNULL(x) + #endif + #else + #define GTM_NONNULL(x) + #endif +#endif + +#ifndef GTM_DECLARE_GENERICS + #if __has_feature(objc_generics) + #define GTM_DECLARE_GENERICS 1 + #else + #define GTM_DECLARE_GENERICS 0 + #endif +#endif + +#ifndef GTM_NSArrayOf + #if GTM_DECLARE_GENERICS + #define GTM_NSArrayOf(value) NSArray + #define GTM_NSDictionaryOf(key, value) NSDictionary + #else + #define GTM_NSArrayOf(value) NSArray + #define GTM_NSDictionaryOf(key, value) NSDictionary + #endif // GTM_DECLARE_GENERICS +#endif // GTM_NSArrayOf + + +// GTMMIMEDocumentPart represents a part of a MIME document. +// +// +[GTMMIMEDocument MIMEPartsWithBoundary:data:] returns an array of these. +@interface GTMMIMEDocumentPart : NSObject + +@property(nonatomic, readonly) GTM_NSDictionaryOf(NSString *, NSString *) *headers; +@property(nonatomic, readonly) NSData *headerData; +@property(nonatomic, readonly) NSData *body; +@property(nonatomic, readonly) NSUInteger length; + ++ (instancetype)partWithHeaders:(NSDictionary *)headers body:(NSData *)body; + +@end + +@interface GTMMIMEDocument : NSObject + +// Get or set the unique boundary for the parts that have been added. +// +// When creating a MIME document from parts, this is typically calculated +// automatically after all parts have been added. +@property(nonatomic, copy) NSString *boundary; + +#pragma mark - Methods for Creating a MIME Document + ++ (instancetype)MIMEDocument; + +// Adds a new part to this mime document with the given headers and body. +// The headers keys and values should be NSStrings. +// Adding a part may cause the boundary string to change. +- (void)addPartWithHeaders:(GTM_NSDictionaryOf(NSString *, NSString *) *)headers + body:(NSData *)body GTM_NONNULL((1,2)); + +// An inputstream that can be used to efficiently read the contents of the MIME document. +// +// Any parameter may be null if the result is not wanted. +- (void)generateInputStream:(NSInputStream **)outStream + length:(unsigned long long *)outLength + boundary:(NSString **)outBoundary; + +// A dispatch_data_t with the contents of the MIME document. +// +// Note: dispatch_data_t is one-way toll-free bridged so the result +// may be cast directly to NSData *. +// +// Any parameter may be null if the result is not wanted. +- (void)generateDispatchData:(dispatch_data_t *)outDispatchData + length:(unsigned long long *)outLength + boundary:(NSString **)outBoundary; + +// Utility method for making a header section, including trailing newlines. ++ (NSData *)dataWithHeaders:(GTM_NSDictionaryOf(NSString *, NSString *) *)headers; + +#pragma mark - Methods for Parsing a MIME Document + +// Method for parsing out an array of MIME parts from a MIME document. +// +// Returns an array of GTMMIMEDocumentParts. Returns nil if no part can +// be found. ++ (GTM_NSArrayOf(GTMMIMEDocumentPart *) *)MIMEPartsWithBoundary:(NSString *)boundary + data:(NSData *)fullDocumentData; + +// Utility method for efficiently searching possibly discontiguous NSData +// for occurrences of target byte. This method does not "flatten" an NSData +// that is composed of discontiguous blocks. +// +// The byte offsets of non-overlapping occurrences of the target are returned as +// NSNumbers in the array. ++ (void)searchData:(NSData *)data + targetBytes:(const void *)targetBytes + targetLength:(NSUInteger)targetLength + foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets; + +// Utility method to parse header bytes into an NSDictionary. ++ (GTM_NSDictionaryOf(NSString *, NSString *) *)headersWithData:(NSData *)data; + +// ------ UNIT TESTING ONLY BELOW ------ + +// Internal methods, exposed for unit testing only. +- (void)seedRandomWith:(u_int32_t)seed; + ++ (NSUInteger)findBytesWithNeedle:(const unsigned char *)needle + needleLength:(NSUInteger)needleLength + haystack:(const unsigned char *)haystack + haystackLength:(NSUInteger)haystackLength + foundOffset:(NSUInteger *)foundOffset; + ++ (void)searchData:(NSData *)data + targetBytes:(const void *)targetBytes + targetLength:(NSUInteger)targetLength + foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets + foundBlockNumbers:(GTM_NSArrayOf(NSNumber *) **)outFoundBlockNumbers; + +@end diff --git a/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m b/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m new file mode 100644 index 00000000..f4460c5d --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m @@ -0,0 +1,631 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMMIMEDocument.h" + +// Avoid a hard dependency on GTMGatherInputStream. +#ifndef GTM_GATHERINPUTSTREAM_DECLARED +#define GTM_GATHERINPUTSTREAM_DECLARED + +@interface GTMGatherInputStream : NSInputStream + ++ (NSInputStream *)streamWithArray:(NSArray *)dataArray GTM_NONNULL((1)); + +@end +#endif // GTM_GATHERINPUTSTREAM_DECLARED + +// FindBytes +// +// Helper routine to search for the existence of a set of bytes (needle) within +// a presumed larger set of bytes (haystack). Can find the first part of the +// needle at the very end of the haystack. +// +// Returns the needle length on complete success, the number of bytes matched +// if a partial needle was found at the end of the haystack, and 0 on failure. +static NSUInteger FindBytes(const unsigned char *needle, NSUInteger needleLen, + const unsigned char *haystack, NSUInteger haystackLen, + NSUInteger *foundOffset); + +// SearchDataForBytes +// +// This implements the functionality of the +searchData: methods below. See the documentation +// for those methods. +static void SearchDataForBytes(NSData *data, const void *targetBytes, NSUInteger targetLength, + NSMutableArray *foundOffsets, NSMutableArray *foundBlockNumbers); + +@implementation GTMMIMEDocumentPart { + NSDictionary *_headers; + NSData *_headerData; // Header content including the ending "\r\n". + NSData *_bodyData; +} + +@synthesize headers = _headers, + headerData = _headerData, + body = _bodyData; + +@dynamic length; + ++ (instancetype)partWithHeaders:(NSDictionary *)headers body:(NSData *)body { + return [[self alloc] initWithHeaders:headers body:body]; +} + +- (instancetype)initWithHeaders:(NSDictionary *)headers body:(NSData *)body { + self = [super init]; + if (self) { + _bodyData = body; + _headers = headers; + } + return self; +} + +// Returns true if the part's header or data contain the given set of bytes. +// +// NOTE: We assume that the 'bytes' we are checking for do not contain "\r\n", +// so we don't need to check the concatenation of the header and body bytes. +- (BOOL)containsBytes:(const unsigned char *)bytes length:(NSUInteger)length { + // This uses custom search code rather than strcpy because the encoded data may contain + // null values. + NSData *headerData = self.headerData; + return (FindBytes(bytes, length, headerData.bytes, headerData.length, NULL) == length || + FindBytes(bytes, length, _bodyData.bytes, _bodyData.length, NULL) == length); +} + +- (NSData *)headerData { + if (!_headerData) { + _headerData = [GTMMIMEDocument dataWithHeaders:_headers]; + } + return _headerData; +} + +- (NSData *)body { + return _bodyData; +} + +- (NSUInteger)length { + return _headerData.length + _bodyData.length; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %p (headers %lu keys, body %lu bytes)", + [self class], self, (unsigned long)_headers.count, + (unsigned long)_bodyData.length]; +} + +- (BOOL)isEqual:(GTMMIMEDocumentPart *)other { + if (self == other) return YES; + if (![other isKindOfClass:[GTMMIMEDocumentPart class]]) return NO; + return ((_bodyData == other->_bodyData || [_bodyData isEqual:other->_bodyData]) + && (_headers == other->_headers || [_headers isEqual:other->_headers])); +} + +- (NSUInteger)hash { + return _bodyData.hash | _headers.hash; +} + +@end + +@implementation GTMMIMEDocument { + NSMutableArray *_parts; // Ordered array of GTMMIMEDocumentParts. + unsigned long long _length; // Length in bytes of the document. + NSString *_boundary; + u_int32_t _randomSeed; // For testing. +} + ++ (instancetype)MIMEDocument { + return [[self alloc] init]; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _parts = [[NSMutableArray alloc] init]; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %p (%lu parts)", + [self class], self, (unsigned long)_parts.count]; +} + +#pragma mark - Joining Parts + +// Adds a new part to this mime document with the given headers and body. +- (void)addPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { + GTMMIMEDocumentPart *part = [GTMMIMEDocumentPart partWithHeaders:headers body:body]; + [_parts addObject:part]; + _boundary = nil; +} + +// For unit testing only, seeds the random number generator so that we will +// have reproducible boundary strings. +- (void)seedRandomWith:(u_int32_t)seed { + _randomSeed = seed; + _boundary = nil; +} + +- (u_int32_t)random { + if (_randomSeed) { + // For testing only. + return _randomSeed++; + } else { + return arc4random(); + } +} + +// Computes the mime boundary to use. This should only be called +// after all the desired document parts have been added since it must compute +// a boundary that does not exist in the document data. +- (NSString *)boundary { + if (_boundary) { + return _boundary; + } + + // Use an easily-readable boundary string. + NSString *const kBaseBoundary = @"END_OF_PART"; + + _boundary = kBaseBoundary; + + // If the boundary isn't unique, append random numbers, up to 10 attempts; + // if that's still not unique, use a random number sequence instead, and call it good. + BOOL didCollide = NO; + + const int maxTries = 10; // Arbitrarily chosen maximum attempts. + for (int tries = 0; tries < maxTries; ++tries) { + + NSData *data = [_boundary dataUsingEncoding:NSUTF8StringEncoding]; + const void *dataBytes = data.bytes; + NSUInteger dataLen = data.length; + + for (GTMMIMEDocumentPart *part in _parts) { + didCollide = [part containsBytes:dataBytes length:dataLen]; + if (didCollide) break; + } + + if (!didCollide) break; // We're fine, no more attempts needed. + + // Try again with a random number appended. + _boundary = [NSString stringWithFormat:@"%@_%08x", kBaseBoundary, [self random]]; + } + + if (didCollide) { + // Fallback... two random numbers. + _boundary = [NSString stringWithFormat:@"%08x_tedborg_%08x", [self random], [self random]]; + } + return _boundary; +} + +- (void)setBoundary:(NSString *)str { + _boundary = [str copy]; +} + +// Internal method. +- (void)generateDataArray:(NSMutableArray *)dataArray + length:(unsigned long long *)outLength + boundary:(NSString **)outBoundary { + + // The input stream is of the form: + // --boundary + // [part_1_headers] + // [part_1_data] + // --boundary + // [part_2_headers] + // [part_2_data] + // --boundary-- + + // First we set up our boundary NSData objects. + NSString *boundary = self.boundary; + + NSString *mainBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n", boundary]; + NSString *endBoundary = [NSString stringWithFormat:@"\r\n--%@--\r\n", boundary]; + + NSData *mainBoundaryData = [mainBoundary dataUsingEncoding:NSUTF8StringEncoding]; + NSData *endBoundaryData = [endBoundary dataUsingEncoding:NSUTF8StringEncoding]; + + // Now we add them all in proper order to our dataArray. + unsigned long long length = 0; + + for (GTMMIMEDocumentPart *part in _parts) { + [dataArray addObject:mainBoundaryData]; + [dataArray addObject:part.headerData]; + [dataArray addObject:part.body]; + + length += part.length + mainBoundaryData.length; + } + + [dataArray addObject:endBoundaryData]; + length += endBoundaryData.length; + + if (outLength) *outLength = length; + if (outBoundary) *outBoundary = boundary; +} + +- (void)generateInputStream:(NSInputStream **)outStream + length:(unsigned long long *)outLength + boundary:(NSString **)outBoundary { + NSMutableArray *dataArray = outStream ? [NSMutableArray array] : nil; + [self generateDataArray:dataArray + length:outLength + boundary:outBoundary]; + + if (outStream) { + Class streamClass = NSClassFromString(@"GTMGatherInputStream"); + NSAssert(streamClass != nil, @"GTMGatherInputStream not available."); + + *outStream = [streamClass streamWithArray:dataArray]; + } +} + +- (void)generateDispatchData:(dispatch_data_t *)outDispatchData + length:(unsigned long long *)outLength + boundary:(NSString **)outBoundary { + NSMutableArray *dataArray = outDispatchData ? [NSMutableArray array] : nil; + [self generateDataArray:dataArray + length:outLength + boundary:outBoundary]; + + if (outDispatchData) { + // Create an empty data accumulator. + dispatch_data_t dataAccumulator; + + dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + for (NSData *partData in dataArray) { + __block NSData *immutablePartData = [partData copy]; + dispatch_data_t newDataPart = + dispatch_data_create(immutablePartData.bytes, immutablePartData.length, bgQueue, ^{ + // We want the data retained until this block executes. + immutablePartData = nil; + }); + + if (dataAccumulator == nil) { + // First part. + dataAccumulator = newDataPart; + } else { + // Append the additional part. + dataAccumulator = dispatch_data_create_concat(dataAccumulator, newDataPart); + } + } + *outDispatchData = dataAccumulator; + } +} + ++ (NSData *)dataWithHeaders:(NSDictionary *)headers { + // Generate the header data by coalescing the dictionary as lines of "key: value\r\n". + NSMutableString* headerString = [NSMutableString string]; + + // Sort the header keys so we have a deterministic order for unit testing. + SEL sortSel = @selector(caseInsensitiveCompare:); + NSArray *sortedKeys = [headers.allKeys sortedArrayUsingSelector:sortSel]; + + for (NSString *key in sortedKeys) { + NSString *value = [headers objectForKey:key]; + +#if DEBUG + // Look for troublesome characters in the header keys & values. + NSCharacterSet *badKeyChars = [NSCharacterSet characterSetWithCharactersInString:@":\r\n"]; + NSCharacterSet *badValueChars = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"]; + + NSRange badRange = [key rangeOfCharacterFromSet:badKeyChars]; + NSAssert(badRange.location == NSNotFound, @"invalid key: %@", key); + + badRange = [value rangeOfCharacterFromSet:badValueChars]; + NSAssert(badRange.location == NSNotFound, @"invalid value: %@", value); +#endif + + [headerString appendFormat:@"%@: %@\r\n", key, value]; + } + // Headers end with an extra blank line. + [headerString appendString:@"\r\n"]; + + NSData *result = [headerString dataUsingEncoding:NSUTF8StringEncoding]; + return result; +} + +#pragma mark - Separating Parts + ++ (NSArray *)MIMEPartsWithBoundary:(NSString *)boundary + data:(NSData *)fullDocumentData { + // In MIME documents, the boundary is preceded by CRLF and two dashes, and followed + // at the end by two dashes. + NSData *boundaryData = [boundary dataUsingEncoding:NSUTF8StringEncoding]; + NSUInteger boundaryLength = boundaryData.length; + + NSMutableArray *foundBoundaryOffsets; + [self searchData:fullDocumentData + targetBytes:boundaryData.bytes + targetLength:boundaryLength + foundOffsets:&foundBoundaryOffsets]; + + // According to rfc1341, ignore anything before the first boundary, or after the last, though two + // dashes are expected to follow the last boundary. + if (foundBoundaryOffsets.count < 2) { + return nil; + } + + // Wrap the full document data with a dispatch_data_t for more efficient slicing + // and dicing. + dispatch_data_t dataWrapper; + if ([fullDocumentData conformsToProtocol:@protocol(OS_dispatch_data)]) { + dataWrapper = (dispatch_data_t)fullDocumentData; + } else { + // A no-op self invocation on fullDocumentData will keep it retained until the block is invoked. + dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dataWrapper = dispatch_data_create(fullDocumentData.bytes, + fullDocumentData.length, + bgQueue, ^{ [fullDocumentData self]; }); + } + NSMutableArray *parts; + NSInteger previousBoundaryOffset = -1; + NSInteger partCounter = -1; + NSInteger numberOfPartsWithHeaders = 0; + for (NSNumber *currentBoundaryOffset in foundBoundaryOffsets) { + ++partCounter; + if (previousBoundaryOffset == -1) { + // This is the first boundary. + previousBoundaryOffset = currentBoundaryOffset.integerValue; + continue; + } else { + // Create a part data subrange between the previous boundary and this one. + // + // The last four bytes before a boundary are CRLF--. + // The first two bytes following a boundary are either CRLF or, for the last boundary, --. + NSInteger previousPartDataStartOffset = + previousBoundaryOffset + (NSInteger)boundaryLength + 2; + NSInteger previousPartDataEndOffset = currentBoundaryOffset.integerValue - 4; + NSInteger previousPartDataLength = previousPartDataEndOffset - previousPartDataStartOffset; + + if (previousPartDataLength < 2) { + // The preceding part was too short to be useful. +#if DEBUG + NSLog(@"MIME part %ld has %ld bytes", (long)partCounter - 1, + (long)previousPartDataLength); +#endif + } else { + if (!parts) parts = [NSMutableArray array]; + + dispatch_data_t partData = + dispatch_data_create_subrange(dataWrapper, + (size_t)previousPartDataStartOffset, (size_t)previousPartDataLength); + // Scan the part data for the separator between headers and body. After the CRLF, + // either the headers start immediately, or there's another CRLF and there are no headers. + // + // We need to map the part data to get the first two bytes. (Or we could cast it to + // NSData and get the bytes pointer of that.) If we're concerned that a single part + // data may be expensive to map, we could make a subrange here for just the first two bytes, + // and map that two-byte subrange. + const void *partDataBuffer; + size_t partDataBufferSize; + dispatch_data_t mappedPartData NS_VALID_UNTIL_END_OF_SCOPE = + dispatch_data_create_map(partData, &partDataBuffer, &partDataBufferSize); + dispatch_data_t bodyData; + NSDictionary *headers; + BOOL hasAnotherCRLF = (((char *)partDataBuffer)[0] == '\r' + && ((char *)partDataBuffer)[1] == '\n'); + mappedPartData = nil; + + if (hasAnotherCRLF) { + // There are no headers; skip the CRLF to get to the body, and leave headers nil. + bodyData = dispatch_data_create_subrange(partData, 2, (size_t)previousPartDataLength - 2); + } else { + // There are part headers. They are separated from body data by CRLFCRLF. + NSArray *crlfOffsets; + [self searchData:(NSData *)partData + targetBytes:"\r\n\r\n" + targetLength:4 + foundOffsets:&crlfOffsets]; + if (crlfOffsets.count == 0) { +#if DEBUG + // We could not distinguish body and headers. + NSLog(@"MIME part %ld lacks a header separator: %@", (long)partCounter - 1, + [[NSString alloc] initWithData:(NSData *)partData encoding:NSUTF8StringEncoding]); +#endif + } else { + NSInteger headerSeparatorOffset = ((NSNumber *)crlfOffsets.firstObject).integerValue; + dispatch_data_t headerData = + dispatch_data_create_subrange(partData, 0, (size_t)headerSeparatorOffset); + headers = [self headersWithData:(NSData *)headerData]; + + bodyData = dispatch_data_create_subrange(partData, (size_t)headerSeparatorOffset + 4, + (size_t)(previousPartDataLength - (headerSeparatorOffset + 4))); + + numberOfPartsWithHeaders++; + } // crlfOffsets.count == 0 + } // hasAnotherCRLF + GTMMIMEDocumentPart *part = [GTMMIMEDocumentPart partWithHeaders:headers + body:(NSData *)bodyData]; + [parts addObject:part]; + } // previousPartDataLength < 2 + previousBoundaryOffset = currentBoundaryOffset.integerValue; + } + } +#if DEBUG + // In debug builds, warn if a reasonably long document lacks any CRLF characters. + if (numberOfPartsWithHeaders == 0) { + NSUInteger length = fullDocumentData.length; + if (length > 20) { // Reasonably long. + NSMutableArray *foundCRLFs; + [self searchData:fullDocumentData + targetBytes:"\r\n" + targetLength:2 + foundOffsets:&foundCRLFs]; + if (foundCRLFs.count == 0) { + // Parts were logged above (due to lacking header separators.) + NSLog(@"Warning: MIME document lacks any headers (may have wrong line endings)"); + } + } + } +#endif // DEBUG + return parts; +} + +// Efficiently search the supplied data for the target bytes. +// +// This uses enumerateByteRangesUsingBlock: to scan for bytes. It can find +// the target even if it spans multiple separate byte ranges. +// +// Returns an array of found byte offset values, as NSNumbers. ++ (void)searchData:(NSData *)data + targetBytes:(const void *)targetBytes + targetLength:(NSUInteger)targetLength + foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets { + NSMutableArray *foundOffsets = [NSMutableArray array]; + SearchDataForBytes(data, targetBytes, targetLength, foundOffsets, NULL); + *outFoundOffsets = foundOffsets; +} + + +// This version of searchData: also returns the block numbers (0-based) where the +// target was found, used for testing that the supplied dispatch_data buffer +// has not been flattened. ++ (void)searchData:(NSData *)data + targetBytes:(const void *)targetBytes + targetLength:(NSUInteger)targetLength + foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets + foundBlockNumbers:(GTM_NSArrayOf(NSNumber *) **)outFoundBlockNumbers { + NSMutableArray *foundOffsets = [NSMutableArray array]; + NSMutableArray *foundBlockNumbers = [NSMutableArray array]; + + SearchDataForBytes(data, targetBytes, targetLength, foundOffsets, foundBlockNumbers); + *outFoundOffsets = foundOffsets; + *outFoundBlockNumbers = foundBlockNumbers; +} + +static void SearchDataForBytes(NSData *data, const void *targetBytes, NSUInteger targetLength, + NSMutableArray *foundOffsets, NSMutableArray *foundBlockNumbers) { + __block NSUInteger priorPartialMatchAmount = 0; + __block NSInteger priorPartialMatchStartingBlockNumber = -1; + __block NSInteger blockNumber = -1; + + [data enumerateByteRangesUsingBlock:^(const void *bytes, + NSRange byteRange, + BOOL *stop) { + // Search for the first character in the current range. + const void *ptr = bytes; + NSInteger remainingInCurrentRange = (NSInteger)byteRange.length; + ++blockNumber; + + if (priorPartialMatchAmount > 0) { + NSUInteger amountRemainingToBeMatched = targetLength - priorPartialMatchAmount; + NSUInteger remainingFoundOffset; + NSUInteger amountMatched = FindBytes(targetBytes + priorPartialMatchAmount, + amountRemainingToBeMatched, + ptr, (NSUInteger)remainingInCurrentRange, &remainingFoundOffset); + if (amountMatched == 0 || remainingFoundOffset > 0) { + // No match of the rest of the prior partial match in this range. + } else if (amountMatched < amountRemainingToBeMatched) { + // Another partial match; we're done with this range. + priorPartialMatchAmount = priorPartialMatchAmount + amountMatched; + return; + } else { + // The offset is in an earlier range. + NSUInteger offset = byteRange.location - priorPartialMatchAmount; + [foundOffsets addObject:@(offset)]; + [foundBlockNumbers addObject:@(priorPartialMatchStartingBlockNumber)]; + priorPartialMatchStartingBlockNumber = -1; + } + priorPartialMatchAmount = 0; + } + + while (remainingInCurrentRange > 0) { + NSUInteger offsetFromPtr; + NSUInteger amountMatched = FindBytes(targetBytes, targetLength, ptr, + (NSUInteger)remainingInCurrentRange, &offsetFromPtr); + if (amountMatched == 0) { + // No match in this range. + return; + } + if (amountMatched < targetLength) { + // Found a partial target. If there's another range, we'll check for the rest. + priorPartialMatchAmount = amountMatched; + priorPartialMatchStartingBlockNumber = blockNumber; + return; + } + // Found the full target. + NSUInteger globalOffset = byteRange.location + (NSUInteger)(ptr - bytes) + offsetFromPtr; + + [foundOffsets addObject:@(globalOffset)]; + [foundBlockNumbers addObject:@(blockNumber)]; + + ptr += targetLength + offsetFromPtr; + remainingInCurrentRange -= (targetLength + offsetFromPtr); + } + }]; +} + +// Internal method only for testing; this calls through the static method. ++ (NSUInteger)findBytesWithNeedle:(const unsigned char *)needle + needleLength:(NSUInteger)needleLength + haystack:(const unsigned char *)haystack + haystackLength:(NSUInteger)haystackLength + foundOffset:(NSUInteger *)foundOffset { + return FindBytes(needle, needleLength, haystack, haystackLength, foundOffset); +} + +// Utility method to parse header bytes into an NSDictionary. ++ (NSDictionary *)headersWithData:(NSData *)data { + NSString *headersString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (!headersString) return nil; + + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + NSScanner *scanner = [NSScanner scannerWithString:headersString]; + // The scanner is skipping leading whitespace and newline characters by default. + NSCharacterSet *newlineCharacters = [NSCharacterSet newlineCharacterSet]; + NSString *key; + NSString *value; + while ([scanner scanUpToString:@":" intoString:&key] + && [scanner scanString:@":" intoString:NULL] + && [scanner scanUpToCharactersFromSet:newlineCharacters intoString:&value]) { + [headers setObject:value forKey:key]; + // Discard the trailing newline. + [scanner scanCharactersFromSet:newlineCharacters intoString:NULL]; + } + return headers; +} + +@end + +// Return how much of the needle was found in the haystack. +// +// If the result is less than needleLen, then the beginning of the needle +// was found at the end of the haystack. +static NSUInteger FindBytes(const unsigned char* needle, NSUInteger needleLen, + const unsigned char* haystack, NSUInteger haystackLen, + NSUInteger *foundOffset) { + const unsigned char *ptr = haystack; + NSInteger remain = (NSInteger)haystackLen; + // Assume memchr is an efficient way to find a match for the first + // byte of the needle, and memcmp is an efficient way to compare a + // range of bytes. + while (remain > 0 && (ptr = memchr(ptr, needle[0], (size_t)remain)) != 0) { + // The first character is present. + NSUInteger offset = (NSUInteger)(ptr - haystack); + remain = (NSInteger)(haystackLen - offset); + + NSUInteger amountToCompare = MIN((NSUInteger)remain, needleLen); + if (memcmp(ptr, needle, amountToCompare) == 0) { + if (foundOffset) *foundOffset = offset; + return amountToCompare; + } + ptr++; + remain--; + } + if (foundOffset) *foundOffset = 0; + return 0; +} diff --git a/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h b/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h new file mode 100644 index 00000000..4e306428 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h @@ -0,0 +1,49 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 + +#ifndef GTM_NONNULL + #if defined(__has_attribute) + #if __has_attribute(nonnull) + #define GTM_NONNULL(x) __attribute__((nonnull x)) + #else + #define GTM_NONNULL(x) + #endif + #else + #define GTM_NONNULL(x) + #endif +#endif + + +@interface GTMReadMonitorInputStream : NSInputStream + ++ (instancetype)inputStreamWithStream:(NSInputStream *)input GTM_NONNULL((1)); + +- (instancetype)initWithStream:(NSInputStream *)input GTM_NONNULL((1)); + +// The read monitor selector is called when bytes have been read. It should have this signature: +// +// - (void)inputStream:(GTMReadMonitorInputStream *)stream +// readIntoBuffer:(uint8_t *)buffer +// length:(int64_t)length; + +@property(atomic, weak) id readDelegate; +@property(atomic, assign) SEL readSelector; + +// Modes for invoking callbacks, when necessary. +@property(atomic, strong) NSArray *runLoopModes; + +@end diff --git a/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m b/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m new file mode 100644 index 00000000..6f95dd54 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m @@ -0,0 +1,190 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMReadMonitorInputStream.h" + +@implementation GTMReadMonitorInputStream { + NSInputStream *_inputStream; // Encapsulated stream that does the work. + + NSThread *_thread; // Thread in which this object was created. + NSArray *_runLoopModes; // Modes for calling callbacks, when necessary. +} + + +@synthesize readDelegate = _readDelegate; +@synthesize readSelector = _readSelector; +@synthesize runLoopModes = _runLoopModes; + +// We'll forward all unhandled messages to the NSInputStream class or to the encapsulated input +// stream. This is needed for all messages sent to NSInputStream which aren't handled by our +// superclass; that includes various private run loop calls. ++ (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { + return [NSInputStream methodSignatureForSelector:selector]; +} + ++ (void)forwardInvocation:(NSInvocation*)invocation { + [invocation invokeWithTarget:[NSInputStream class]]; +} + +- (BOOL)respondsToSelector:(SEL)selector { + return [_inputStream respondsToSelector:selector]; +} + +- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { + return [_inputStream methodSignatureForSelector:selector]; +} + +- (void)forwardInvocation:(NSInvocation*)invocation { + [invocation invokeWithTarget:_inputStream]; +} + +#pragma mark - + ++ (instancetype)inputStreamWithStream:(NSInputStream *)input { + return [[self alloc] initWithStream:input]; +} + +- (instancetype)initWithStream:(NSInputStream *)input { + self = [super init]; + if (self) { + _inputStream = input; + _thread = [NSThread currentThread]; + } + return self; +} + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +#pragma mark - + +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { + // Read from the encapsulated stream. + NSInteger numRead = [_inputStream read:buffer maxLength:len]; + if (numRead > 0) { + if (_readDelegate && _readSelector) { + // Call the read selector with the buffer and number of bytes actually read into it. + BOOL isOnOriginalThread = [_thread isEqual:[NSThread currentThread]]; + if (isOnOriginalThread) { + // Invoke immediately. + NSData *data = [NSData dataWithBytesNoCopy:buffer + length:(NSUInteger)numRead + freeWhenDone:NO]; + [self invokeReadSelectorWithBuffer:data]; + } else { + // Copy the buffer into an NSData to be retained by the performSelector, + // and invoke on the proper thread. + SEL sel = @selector(invokeReadSelectorWithBuffer:); + NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numRead]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + if (_runLoopModes) { + [self performSelector:sel + onThread:_thread + withObject:data + waitUntilDone:NO + modes:_runLoopModes]; + } else { + [self performSelector:sel + onThread:_thread + withObject:data + waitUntilDone:NO]; + } +#pragma clang diagnostic pop + } + } + } + return numRead; +} + +- (void)invokeReadSelectorWithBuffer:(NSData *)data { + const void *buffer = data.bytes; + int64_t length = (int64_t)data.length; + + id argSelf = self; + id readDelegate = _readDelegate; + if (readDelegate) { + NSMethodSignature *signature = [readDelegate methodSignatureForSelector:_readSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setSelector:_readSelector]; + [invocation setTarget:readDelegate]; + [invocation setArgument:&argSelf atIndex:2]; + [invocation setArgument:&buffer atIndex:3]; + [invocation setArgument:&length atIndex:4]; + [invocation invoke]; + } +} + +- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len { + return [_inputStream getBuffer:buffer length:len]; +} + +- (BOOL)hasBytesAvailable { + return [_inputStream hasBytesAvailable]; +} + +#pragma mark Standard messages + +// Pass expected messages to our encapsulated stream. +// +// We want our encapsulated NSInputStream to handle the standard messages; +// we don't want the superclass to handle them. +- (void)open { + [_inputStream open]; +} + +- (void)close { + [_inputStream close]; +} + +- (id)delegate { + return [_inputStream delegate]; +} + +- (void)setDelegate:(id)delegate { + [_inputStream setDelegate:delegate]; +} + +- (id)propertyForKey:(NSString *)key { + return [_inputStream propertyForKey:key]; +} + +- (BOOL)setProperty:(id)property forKey:(NSString *)key { + return [_inputStream setProperty:property forKey:key]; +} + +- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { + [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; +} + +- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { + [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; +} + +- (NSStreamStatus)streamStatus { + return [_inputStream streamStatus]; +} + +- (NSError *)streamError { + return [_inputStream streamError]; +} + +@end diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h new file mode 100644 index 00000000..0504aa75 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h @@ -0,0 +1,1332 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +// GTMSessionFetcher is a wrapper around NSURLSession for http operations. +// +// What does this offer on top of of NSURLSession? +// +// - Block-style callbacks for useful functionality like progress rather +// than delegate methods. +// - Out-of-process uploads and downloads using NSURLSession, including +// management of fetches after relaunch. +// - Integration with GTMAppAuth for invisible management and refresh of +// authorization tokens. +// - Pretty-printed http logging. +// - Cookies handling that does not interfere with or get interfered with +// by WebKit cookies or on Mac by Safari and other apps. +// - Credentials handling for the http operation. +// - Rate-limiting and cookie grouping when fetchers are created with +// GTMSessionFetcherService. +// +// If the bodyData or bodyFileURL property is set, then a POST request is assumed. +// +// Each fetcher is assumed to be for a one-shot fetch request; don't reuse the object +// for a second fetch. +// +// The fetcher will be self-retained as long as a connection is pending. +// +// To keep user activity private, URLs must have an https scheme (unless the property +// allowedInsecureSchemes is set to permit the scheme.) +// +// Callbacks will be released when the fetch completes or is stopped, so there is no need +// to use weak self references in the callback blocks. +// +// Sample usage: +// +// _fetcherService = [[GTMSessionFetcherService alloc] init]; +// +// GTMSessionFetcher *myFetcher = [_fetcherService fetcherWithURLString:myURLString]; +// myFetcher.retryEnabled = YES; +// myFetcher.comment = @"First profile image"; +// +// // Optionally specify a file URL or NSData for the request body to upload. +// myFetcher.bodyData = [postString dataUsingEncoding:NSUTF8StringEncoding]; +// +// [myFetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { +// if (error != nil) { +// // Server status code or network error. +// // +// // If the domain is kGTMSessionFetcherStatusDomain then the error code +// // is a failure status from the server. +// } else { +// // Fetch succeeded. +// } +// }]; +// +// There is also a beginFetch call that takes a pointer and selector for the completion handler; +// a pointer and selector is a better style when the callback is a substantial, separate method. +// +// NOTE: Fetches may retrieve data from the server even though the server +// returned an error, so the criteria for success is a non-nil error. +// The completion handler is called when the server status is >= 300 with an NSError +// having domain kGTMSessionFetcherStatusDomain and code set to the server status. +// +// Status codes are at +// +// +// Background session support: +// +// Out-of-process uploads and downloads may be created by setting the fetcher's +// useBackgroundSession property. Data to be uploaded should be provided via +// the uploadFileURL property; the download destination should be specified with +// the destinationFileURL. NOTE: Background upload files should be in a location +// that will be valid even after the device is restarted, so the file should not +// be uploaded from a system temporary or cache directory. +// +// Background session transfers are slower, and should typically be used only +// for very large downloads or uploads (hundreds of megabytes). +// +// When background sessions are used in iOS apps, the application delegate must +// pass through the parameters from UIApplicationDelegate's +// application:handleEventsForBackgroundURLSession:completionHandler: to the +// fetcher class. +// +// When the application has been relaunched, it may also create a new fetcher +// instance to handle completion of the transfers. +// +// - (void)application:(UIApplication *)application +// handleEventsForBackgroundURLSession:(NSString *)identifier +// completionHandler:(void (^)())completionHandler { +// // Application was re-launched on completing an out-of-process download. +// +// // Pass the URLSession info related to this re-launch to the fetcher class. +// [GTMSessionFetcher application:application +// handleEventsForBackgroundURLSession:identifier +// completionHandler:completionHandler]; +// +// // Get a fetcher related to this re-launch and re-hook up a completionHandler to it. +// GTMSessionFetcher *fetcher = [GTMSessionFetcher fetcherWithSessionIdentifier:identifier]; +// NSURL *destinationFileURL = fetcher.destinationFileURL; +// fetcher.completionHandler = ^(NSData *data, NSError *error) { +// [self downloadCompletedToFile:destinationFileURL error:error]; +// }; +// } +// +// +// Threading and queue support: +// +// Networking always happens on a background thread; there is no advantage to +// changing thread or queue to create or start a fetcher. +// +// Callbacks are run on the main thread; alternatively, the app may set the +// fetcher's callbackQueue to a dispatch queue. +// +// Once the fetcher's beginFetch method has been called, the fetcher's methods and +// properties may be accessed from any thread. +// +// Downloading to disk: +// +// To have downloaded data saved directly to disk, specify a file URL for the +// destinationFileURL property. +// +// HTTP methods and headers: +// +// Alternative HTTP methods, like PUT, and custom headers can be specified by +// creating the fetcher with an appropriate NSMutableURLRequest. +// +// +// Caching: +// +// The fetcher avoids caching. That is best for API requests, but may hurt +// repeat fetches of static data. Apps may enable a persistent disk cache by +// customizing the config: +// +// fetcher.configurationBlock = ^(GTMSessionFetcher *configFetcher, +// NSURLSessionConfiguration *config) { +// config.URLCache = [NSURLCache sharedURLCache]; +// }; +// +// Or use the standard system config to share cookie storage with web views +// and to enable disk caching: +// +// fetcher.configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; +// +// +// Cookies: +// +// There are three supported mechanisms for remembering cookies between fetches. +// +// By default, a standalone GTMSessionFetcher uses a mutable array held +// statically to track cookies for all instantiated fetchers. This avoids +// cookies being set by servers for the application from interfering with +// Safari and WebKit cookie settings, and vice versa. +// The fetcher cookies are lost when the application quits. +// +// To rely instead on WebKit's global NSHTTPCookieStorage, set the fetcher's +// cookieStorage property: +// myFetcher.cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; +// +// To share cookies with other apps, use the method introduced in iOS 9/OS X 10.11: +// myFetcher.cookieStorage = +// [NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:kMyCompanyContainedID]; +// +// To ignore existing cookies and only have cookies related to the single fetch +// be applied, make a temporary cookie storage object: +// myFetcher.cookieStorage = [[GTMSessionCookieStorage alloc] init]; +// +// Note: cookies set while following redirects will be sent to the server, as +// the redirects are followed by the fetcher. +// +// To completely disable cookies, similar to setting cookieStorageMethod to +// kGTMHTTPFetcherCookieStorageMethodNone, adjust the session configuration +// appropriately in the fetcher or fetcher service: +// fetcher.configurationBlock = ^(GTMSessionFetcher *configFetcher, +// NSURLSessionConfiguration *config) { +// config.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever; +// config.HTTPShouldSetCookies = NO; +// }; +// +// If the fetcher is created from a GTMSessionFetcherService object +// then the cookie storage mechanism is set to use the cookie storage in the +// service object rather than the static storage. Disabling cookies in the +// session configuration set on a service object will disable cookies for all +// fetchers created from that GTMSessionFetcherService object, since the session +// configuration is propagated to the fetcher. +// +// +// Monitoring data transfers. +// +// The fetcher supports a variety of properties for progress monitoring +// progress with callback blocks. +// GTMSessionFetcherSendProgressBlock sendProgressBlock +// GTMSessionFetcherReceivedProgressBlock receivedProgressBlock +// GTMSessionFetcherDownloadProgressBlock downloadProgressBlock +// +// 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; 502, gateway failure; +// 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.retryEnabled = YES; +// +// With retries enabled, the completion 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.maxRetryInterval = 60.0; // in seconds; default is 60 seconds +// // for downloads, 600 for uploads +// +// Servers should never send a 400 or 500 status for errors that are retryable +// by clients, as those values indicate permanent failures. In nearly all +// cases, the default standard retry behavior is correct for clients, and no +// custom client retry behavior is needed or appropriate. Servers that send +// non-retryable status codes and expect the client to retry the request are +// faulty. +// +// Still, the client may provide a block to determine if a status code or other +// error should be retried. The block returns 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 kGTMSessionFetcherStatusDomain. The +// user's method may look something like this: +// +// myFetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *error, +// GTMSessionFetcherRetryResponse response) { +// // Perhaps examine error.domain and error.code, or fetcher.retryCount +// // +// // Respond with 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. +// response(suggestedWillRetry); +// }; + + +#import + +#if TARGET_OS_IPHONE +#import +#endif +#if TARGET_OS_WATCH +#import +#endif + +// By default it is stripped from non DEBUG builds. Developers can override +// this in their project settings. +#ifndef STRIP_GTM_FETCH_LOGGING + #if !DEBUG + #define STRIP_GTM_FETCH_LOGGING 1 + #else + #define STRIP_GTM_FETCH_LOGGING 0 + #endif +#endif + +// Logs in debug builds. +#ifndef GTMSESSION_LOG_DEBUG + #if DEBUG + #define GTMSESSION_LOG_DEBUG(...) NSLog(__VA_ARGS__) + #else + #define GTMSESSION_LOG_DEBUG(...) do { } while (0) + #endif +#endif + +// Asserts in debug builds (or logs in debug builds if GTMSESSION_ASSERT_AS_LOG +// or NS_BLOCK_ASSERTIONS are defined.) +#ifndef GTMSESSION_ASSERT_DEBUG + #if DEBUG && !defined(NS_BLOCK_ASSERTIONS) && !GTMSESSION_ASSERT_AS_LOG + #undef GTMSESSION_ASSERT_AS_LOG + #define GTMSESSION_ASSERT_AS_LOG 1 + #endif + + #if DEBUG && !GTMSESSION_ASSERT_AS_LOG + #define GTMSESSION_ASSERT_DEBUG(...) NSAssert(__VA_ARGS__) + #elif DEBUG + #define GTMSESSION_ASSERT_DEBUG(pred, ...) if (!(pred)) { NSLog(__VA_ARGS__); } + #else + #define GTMSESSION_ASSERT_DEBUG(pred, ...) do { } while (0) + #endif +#endif + +// Asserts in debug builds, logs in release builds (or logs in debug builds if +// GTMSESSION_ASSERT_AS_LOG is defined.) +#ifndef GTMSESSION_ASSERT_DEBUG_OR_LOG + #if DEBUG && !GTMSESSION_ASSERT_AS_LOG + #define GTMSESSION_ASSERT_DEBUG_OR_LOG(...) NSAssert(__VA_ARGS__) + #else + #define GTMSESSION_ASSERT_DEBUG_OR_LOG(pred, ...) if (!(pred)) { NSLog(__VA_ARGS__); } + #endif +#endif + +// Macro useful for examining messages from NSURLSession during debugging. +#if 0 +#define GTM_LOG_SESSION_DELEGATE(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__) +#else +#define GTM_LOG_SESSION_DELEGATE(...) +#endif + +#ifndef GTM_NULLABLE + #if __has_feature(nullability) // Available starting in Xcode 6.3 + #define GTM_NULLABLE_TYPE __nullable + #define GTM_NONNULL_TYPE __nonnull + #define GTM_NULLABLE nullable + #define GTM_NONNULL_DECL nonnull // GTM_NONNULL is used by GTMDefines.h + #define GTM_NULL_RESETTABLE null_resettable + + #define GTM_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN + #define GTM_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END + #else + #define GTM_NULLABLE_TYPE + #define GTM_NONNULL_TYPE + #define GTM_NULLABLE + #define GTM_NONNULL_DECL + #define GTM_NULL_RESETTABLE + #define GTM_ASSUME_NONNULL_BEGIN + #define GTM_ASSUME_NONNULL_END + #endif // __has_feature(nullability) +#endif // GTM_NULLABLE + +#ifndef GTM_DECLARE_GENERICS + #if __has_feature(objc_generics) + #define GTM_DECLARE_GENERICS 1 + #else + #define GTM_DECLARE_GENERICS 0 + #endif +#endif + +#ifndef GTM_NSArrayOf + #if GTM_DECLARE_GENERICS + #define GTM_NSArrayOf(value) NSArray + #define GTM_NSDictionaryOf(key, value) NSDictionary + #else + #define GTM_NSArrayOf(value) NSArray + #define GTM_NSDictionaryOf(key, value) NSDictionary + #endif // __has_feature(objc_generics) +#endif // GTM_NSArrayOf + +// For iOS, the fetcher can declare itself a background task to allow fetches +// to finish when the app leaves the foreground. +// +// (This is unrelated to providing a background configuration, which allows +// out-of-process uploads and downloads.) +// +// To disallow use of background tasks during fetches, the target should define +// GTM_BACKGROUND_TASK_FETCHING to 0, or alternatively may set the +// skipBackgroundTask property to YES. +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !defined(GTM_BACKGROUND_TASK_FETCHING) + #define GTM_BACKGROUND_TASK_FETCHING 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if (TARGET_OS_TV \ + || TARGET_OS_WATCH \ + || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)) + #ifndef GTM_USE_SESSION_FETCHER + #define GTM_USE_SESSION_FETCHER 1 + #endif +#endif + +#if !defined(GTMBridgeFetcher) + // These bridge macros should be identical in GTMHTTPFetcher.h and GTMSessionFetcher.h + #if GTM_USE_SESSION_FETCHER + // Macros to new fetcher class. + #define GTMBridgeFetcher GTMSessionFetcher + #define GTMBridgeFetcherService GTMSessionFetcherService + #define GTMBridgeFetcherServiceProtocol GTMSessionFetcherServiceProtocol + #define GTMBridgeAssertValidSelector GTMSessionFetcherAssertValidSelector + #define GTMBridgeCookieStorage GTMSessionCookieStorage + #define GTMBridgeCleanedUserAgentString GTMFetcherCleanedUserAgentString + #define GTMBridgeSystemVersionString GTMFetcherSystemVersionString + #define GTMBridgeApplicationIdentifier GTMFetcherApplicationIdentifier + #define kGTMBridgeFetcherStatusDomain kGTMSessionFetcherStatusDomain + #define kGTMBridgeFetcherStatusBadRequest GTMSessionFetcherStatusBadRequest + #else + // Macros to old fetcher class. + #define GTMBridgeFetcher GTMHTTPFetcher + #define GTMBridgeFetcherService GTMHTTPFetcherService + #define GTMBridgeFetcherServiceProtocol GTMHTTPFetcherServiceProtocol + #define GTMBridgeAssertValidSelector GTMAssertSelectorNilOrImplementedWithArgs + #define GTMBridgeCookieStorage GTMCookieStorage + #define GTMBridgeCleanedUserAgentString GTMCleanedUserAgentString + #define GTMBridgeSystemVersionString GTMSystemVersionString + #define GTMBridgeApplicationIdentifier GTMApplicationIdentifier + #define kGTMBridgeFetcherStatusDomain kGTMHTTPFetcherStatusDomain + #define kGTMBridgeFetcherStatusBadRequest kGTMHTTPFetcherStatusBadRequest + #endif // GTM_USE_SESSION_FETCHER +#endif + +// When creating background sessions to perform out-of-process uploads and +// downloads, on app launch any background sessions must be reconnected in +// order to receive events that occurred while the app was not running. +// +// The fetcher will automatically attempt to recreate the sessions on app +// start, but doing so reads from NSUserDefaults. This may have launch-time +// performance impacts. +// +// To avoid launch performance impacts, on iPhone/iPad with iOS 13+ the +// GTMSessionFetcher class will register for the app launch notification and +// perform the reconnect then. +// +// Apps targeting Mac or older iOS SDKs can opt into the new behavior by defining +// GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH=1. +// +// Apps targeting new SDKs can force the old behavior by defining +// GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH = 0. +#ifndef GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH + // Default to the on-launch behavior for iOS 13+. + #if TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 + #define GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH 1 + #else + #define GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH 0 + #endif +#endif + +GTM_ASSUME_NONNULL_BEGIN + +// Notifications +// +// Fetch started and stopped, and fetch retry delay started and stopped. +extern NSString *const kGTMSessionFetcherStartedNotification; +extern NSString *const kGTMSessionFetcherStoppedNotification; +extern NSString *const kGTMSessionFetcherRetryDelayStartedNotification; +extern NSString *const kGTMSessionFetcherRetryDelayStoppedNotification; + +// Completion handler notification. This is intended for use by code capturing +// and replaying fetch requests and results for testing. For fetches where +// destinationFileURL or accumulateDataBlock is set for the fetcher, the data +// will be nil for successful fetches. +// +// This notification is posted on the main thread. +extern NSString *const kGTMSessionFetcherCompletionInvokedNotification; +extern NSString *const kGTMSessionFetcherCompletionDataKey; +extern NSString *const kGTMSessionFetcherCompletionErrorKey; + +// Constants for NSErrors created by the fetcher (excluding server status errors, +// and error objects originating in the OS.) +extern NSString *const kGTMSessionFetcherErrorDomain; + +// The fetcher turns server error status values (3XX, 4XX, 5XX) into NSErrors +// with domain kGTMSessionFetcherStatusDomain. +// +// Any server response body data accompanying the status error is added to the +// userInfo dictionary with key kGTMSessionFetcherStatusDataKey. +extern NSString *const kGTMSessionFetcherStatusDomain; +extern NSString *const kGTMSessionFetcherStatusDataKey; +extern NSString *const kGTMSessionFetcherStatusDataContentTypeKey; + +// When a fetch fails with an error, these keys are included in the error userInfo +// dictionary if retries were attempted. +extern NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey; +extern NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey; + +// Background session support requires access to NSUserDefaults. +// If [NSUserDefaults standardUserDefaults] doesn't yield the correct NSUserDefaults for your usage, +// ie for an App Extension, then implement this class/method to return the correct NSUserDefaults. +// https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW6 +@interface GTMSessionFetcherUserDefaultsFactory : NSObject + ++ (NSUserDefaults *)fetcherUserDefaults; + +@end + +#ifdef __cplusplus +} +#endif + +typedef NS_ENUM(NSInteger, GTMSessionFetcherError) { + GTMSessionFetcherErrorDownloadFailed = -1, + GTMSessionFetcherErrorUploadChunkUnavailable = -2, + GTMSessionFetcherErrorBackgroundExpiration = -3, + GTMSessionFetcherErrorBackgroundFetchFailed = -4, + GTMSessionFetcherErrorInsecureRequest = -5, + GTMSessionFetcherErrorTaskCreationFailed = -6, +}; + +typedef NS_ENUM(NSInteger, GTMSessionFetcherStatus) { + // Standard http status codes. + GTMSessionFetcherStatusNotModified = 304, + GTMSessionFetcherStatusBadRequest = 400, + GTMSessionFetcherStatusUnauthorized = 401, + GTMSessionFetcherStatusForbidden = 403, + GTMSessionFetcherStatusPreconditionFailed = 412 +}; + +#ifdef __cplusplus +extern "C" { +#endif + +@class GTMSessionCookieStorage; +@class GTMSessionFetcher; + +// The configuration block is for modifying the NSURLSessionConfiguration only. +// DO NOT change any fetcher properties in the configuration block. +typedef void (^GTMSessionFetcherConfigurationBlock)(GTMSessionFetcher *fetcher, + NSURLSessionConfiguration *configuration); +typedef void (^GTMSessionFetcherSystemCompletionHandler)(void); +typedef void (^GTMSessionFetcherCompletionHandler)(NSData * GTM_NULLABLE_TYPE data, + NSError * GTM_NULLABLE_TYPE error); +typedef void (^GTMSessionFetcherBodyStreamProviderResponse)(NSInputStream *bodyStream); +typedef void (^GTMSessionFetcherBodyStreamProvider)(GTMSessionFetcherBodyStreamProviderResponse response); +typedef void (^GTMSessionFetcherDidReceiveResponseDispositionBlock)(NSURLSessionResponseDisposition disposition); +typedef void (^GTMSessionFetcherDidReceiveResponseBlock)(NSURLResponse *response, + GTMSessionFetcherDidReceiveResponseDispositionBlock dispositionBlock); +typedef void (^GTMSessionFetcherChallengeDispositionBlock)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * GTM_NULLABLE_TYPE credential); +typedef void (^GTMSessionFetcherChallengeBlock)(GTMSessionFetcher *fetcher, + NSURLAuthenticationChallenge *challenge, + GTMSessionFetcherChallengeDispositionBlock dispositionBlock); +typedef void (^GTMSessionFetcherWillRedirectResponse)(NSURLRequest * GTM_NULLABLE_TYPE redirectedRequest); +typedef void (^GTMSessionFetcherWillRedirectBlock)(NSHTTPURLResponse *redirectResponse, + NSURLRequest *redirectRequest, + GTMSessionFetcherWillRedirectResponse response); +typedef void (^GTMSessionFetcherAccumulateDataBlock)(NSData * GTM_NULLABLE_TYPE buffer); +typedef void (^GTMSessionFetcherSimulateByteTransferBlock)(NSData * GTM_NULLABLE_TYPE buffer, + int64_t bytesWritten, + int64_t totalBytesWritten, + int64_t totalBytesExpectedToWrite); +typedef void (^GTMSessionFetcherReceivedProgressBlock)(int64_t bytesWritten, + int64_t totalBytesWritten); +typedef void (^GTMSessionFetcherDownloadProgressBlock)(int64_t bytesWritten, + int64_t totalBytesWritten, + int64_t totalBytesExpectedToWrite); +typedef void (^GTMSessionFetcherSendProgressBlock)(int64_t bytesSent, + int64_t totalBytesSent, + int64_t totalBytesExpectedToSend); +typedef void (^GTMSessionFetcherWillCacheURLResponseResponse)(NSCachedURLResponse * GTM_NULLABLE_TYPE cachedResponse); +typedef void (^GTMSessionFetcherWillCacheURLResponseBlock)(NSCachedURLResponse *proposedResponse, + GTMSessionFetcherWillCacheURLResponseResponse responseBlock); +typedef void (^GTMSessionFetcherRetryResponse)(BOOL shouldRetry); +typedef void (^GTMSessionFetcherRetryBlock)(BOOL suggestedWillRetry, + NSError * GTM_NULLABLE_TYPE error, + GTMSessionFetcherRetryResponse response); + +API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) +typedef void (^GTMSessionFetcherMetricsCollectionBlock)(NSURLSessionTaskMetrics *metrics); + +typedef void (^GTMSessionFetcherTestResponse)(NSHTTPURLResponse * GTM_NULLABLE_TYPE response, + NSData * GTM_NULLABLE_TYPE data, + NSError * GTM_NULLABLE_TYPE error); +typedef void (^GTMSessionFetcherTestBlock)(GTMSessionFetcher *fetcherToTest, + GTMSessionFetcherTestResponse testResponse); + +void GTMSessionFetcherAssertValidSelector(id GTM_NULLABLE_TYPE obj, SEL GTM_NULLABLE_TYPE sel, ...); + +// Utility functions for applications self-identifying to servers via a +// user-agent header + +// The "standard" user agent includes the application identifier, taken from the bundle, +// followed by a space and the system version string. Pass nil to use +mainBundle as the source +// of the bundle identifier. +// +// Applications may use this as a starting point for their own user agent strings, perhaps +// with additional sections appended. Use GTMFetcherCleanedUserAgentString() below to +// clean up any string being added to the user agent. +NSString *GTMFetcherStandardUserAgentString(NSBundle * GTM_NULLABLE_TYPE bundle); + +// 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. +// +// The bundle ID may be overridden as the base identifier string by +// adding to the bundle's Info.plist a "GTMUserAgentID" key. +// +// If no bundle ID or override is available, the process name preceded +// by "proc_" is used. +NSString *GTMFetcherApplicationIdentifier(NSBundle * GTM_NULLABLE_TYPE bundle); + +// Make an identifier like "MacOSX/10.7.1" or "iPod_Touch/4.1 hw/iPod1_1" +NSString *GTMFetcherSystemVersionString(void); + +// Make a parseable user-agent identifier from the given string, replacing whitespace +// and commas with underscores, and removing other characters that may interfere +// with parsing of the full user-agent string. +// +// For example, @"[My App]" would become @"My_App" +NSString *GTMFetcherCleanedUserAgentString(NSString *str); + +// Grab the data from an input stream. Since streams cannot be assumed to be rewindable, +// this may be destructive; the caller can try to rewind the stream (by setting the +// NSStreamFileCurrentOffsetKey property) or can just use the NSData to make a new +// NSInputStream. This function is intended to facilitate testing rather than be used in +// production. +// +// This function operates synchronously on the current thread. Depending on how the +// input stream is implemented, it may be appropriate to dispatch to a different +// queue before calling this function. +// +// Failure is indicated by a returned data value of nil. +NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NSError **outError); + +#ifdef __cplusplus +} // extern "C" +#endif + + +#if !GTM_USE_SESSION_FETCHER +@protocol GTMHTTPFetcherServiceProtocol; +#endif + +// This protocol allows abstract references to the fetcher service, primarily for +// fetchers (which may be compiled without the fetcher service class present.) +// +// Apps should not need to use this protocol. +@protocol GTMSessionFetcherServiceProtocol +// This protocol allows us to call into the service without requiring +// GTMSessionFetcherService sources in this project + +@property(atomic, strong) dispatch_queue_t callbackQueue; + +- (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher; +- (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher; +- (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher; +- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher; + +- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request; +- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher; + +@property(atomic, assign) BOOL reuseSession; +- (GTM_NULLABLE NSURLSession *)session; +- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation; +- (GTM_NULLABLE id)sessionDelegate; +- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate; + +// Methods for compatibility with the old GTMHTTPFetcher. +@property(atomic, readonly, strong, GTM_NULLABLE) NSOperationQueue *delegateQueue; + +@end // @protocol GTMSessionFetcherServiceProtocol + +#ifndef GTM_FETCHER_AUTHORIZATION_PROTOCOL +#define GTM_FETCHER_AUTHORIZATION_PROTOCOL 1 +@protocol GTMFetcherAuthorizationProtocol +@required +// This protocol allows us to call the authorizer without requiring its sources +// in this project. +- (void)authorizeRequest:(GTM_NULLABLE NSMutableURLRequest *)request + delegate:(id)delegate + didFinishSelector:(SEL)sel; + +- (void)stopAuthorization; + +- (void)stopAuthorizationForRequest:(NSURLRequest *)request; + +- (BOOL)isAuthorizingRequest:(NSURLRequest *)request; + +- (BOOL)isAuthorizedRequest:(NSURLRequest *)request; + +@property(atomic, strong, readonly, GTM_NULLABLE) 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(atomic, readonly) BOOL canAuthorize; + +// For development only, allow authorization of non-SSL requests, allowing +// transmission of the bearer token unencrypted. +@property(atomic, assign) BOOL shouldAuthorizeAllRequests; + +- (void)authorizeRequest:(GTM_NULLABLE NSMutableURLRequest *)request + completionHandler:(void (^)(NSError * GTM_NULLABLE_TYPE error))handler; + +#if GTM_USE_SESSION_FETCHER +@property(atomic, weak, GTM_NULLABLE) id fetcherService; +#else +@property(atomic, weak, GTM_NULLABLE) id fetcherService; +#endif + +- (BOOL)primeForRefresh; + +@end +#endif // GTM_FETCHER_AUTHORIZATION_PROTOCOL + +#if GTM_BACKGROUND_TASK_FETCHING +// A protocol for an alternative target for messages from GTMSessionFetcher to UIApplication. +// Set the target using +[GTMSessionFetcher setSubstituteUIApplication:] +@protocol GTMUIApplicationProtocol +- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithName:(nullable NSString *)taskName + expirationHandler:(void(^ __nullable)(void))handler; +- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier; +@end +#endif + +#pragma mark - + +// GTMSessionFetcher objects are used for async retrieval of an http get or post +// +// See additional comments at the beginning of this file +@interface GTMSessionFetcher : NSObject + +// 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. ++ (instancetype)fetcherWithRequest:(GTM_NULLABLE NSURLRequest *)request; + +// Convenience methods that make a request, like +fetcherWithRequest ++ (instancetype)fetcherWithURL:(NSURL *)requestURL; ++ (instancetype)fetcherWithURLString:(NSString *)requestURLString; + +// Methods for creating fetchers to continue previous fetches. ++ (instancetype)fetcherWithDownloadResumeData:(NSData *)resumeData; ++ (GTM_NULLABLE instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier; + +// Returns an array of currently active fetchers for background sessions, +// both restarted and newly created ones. ++ (GTM_NSArrayOf(GTMSessionFetcher *) *)fetchersForBackgroundSessions; + +// Designated initializer. +// +// Applications should create fetchers with a "fetcherWith..." method on a fetcher +// service or a class method, not with this initializer. +// +// The configuration should typically be nil. Applications needing to customize +// the configuration may do so by setting the configurationBlock property. +- (instancetype)initWithRequest:(GTM_NULLABLE NSURLRequest *)request + configuration:(GTM_NULLABLE NSURLSessionConfiguration *)configuration; + +// The fetcher's request. This may not be set after beginFetch has been invoked. The request +// may change due to redirects. +@property(atomic, strong, GTM_NULLABLE) NSURLRequest *request; + +// Set a header field value on the request. Header field value changes will not +// affect a fetch after the fetch has begun. +- (void)setRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field; + +// Data used for resuming a download task. +@property(atomic, readonly, GTM_NULLABLE) NSData *downloadResumeData; + +// The configuration; this must be set before the fetch begins. If no configuration is +// set or inherited from the fetcher service, then the fetcher uses an ephemeral config. +// +// NOTE: This property should typically be nil. Applications needing to customize +// the configuration should do so by setting the configurationBlock property. +// That allows the fetcher to pick an appropriate base configuration, with the +// application setting only the configuration properties it needs to customize. +@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration; + +// A block the client may use to customize the configuration used to create the session. +// +// This is called synchronously, either on the thread that begins the fetch or, during a retry, +// on the main thread. The configuration block may be called repeatedly if multiple fetchers are +// created. +// +// The configuration block is for modifying the NSURLSessionConfiguration only. +// DO NOT change any fetcher properties in the configuration block. Fetcher properties +// may be set in the fetcher service prior to fetcher creation, or on the fetcher prior +// to invoking beginFetch. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock; + +// A session is created as needed by the fetcher. A fetcher service object +// may maintain sessions for multiple fetches to the same host. +@property(atomic, strong, GTM_NULLABLE) NSURLSession *session; + +// The task in flight. +@property(atomic, readonly, GTM_NULLABLE) NSURLSessionTask *sessionTask; + +// The background session identifier. +@property(atomic, readonly, GTM_NULLABLE) NSString *sessionIdentifier; + +// Indicates a fetcher created to finish a background session task. +@property(atomic, readonly) BOOL wasCreatedFromBackgroundSession; + +// Additional user-supplied data to encode into the session identifier. Since session identifier +// length limits are unspecified, this should be kept small. Key names beginning with an underscore +// are reserved for use by the fetcher. +@property(atomic, strong, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSString *) *sessionUserInfo; + +// The human-readable description to be assigned to the task. +@property(atomic, copy, GTM_NULLABLE) NSString *taskDescription; + +// The priority assigned to the task, if any. Use NSURLSessionTaskPriorityLow, +// NSURLSessionTaskPriorityDefault, or NSURLSessionTaskPriorityHigh. +@property(atomic, assign) float taskPriority; + +// The fetcher encodes information used to resume a session in the session identifier. +// This method, intended for internal use returns the encoded information. The sessionUserInfo +// dictionary is stored as identifier metadata. +- (GTM_NULLABLE GTM_NSDictionaryOf(NSString *, NSString *) *)sessionIdentifierMetadata; + +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH +// The app should pass to this method the completion handler passed in the app delegate method +// application:handleEventsForBackgroundURLSession:completionHandler: ++ (void)application:(UIApplication *)application + handleEventsForBackgroundURLSession:(NSString *)identifier + completionHandler:(GTMSessionFetcherSystemCompletionHandler)completionHandler; +#endif + +// Indicate that a newly created session should be a background session. +// A new session identifier will be created by the fetcher. +// +// Warning: The only thing background sessions are for is rare download +// of huge, batched files of data. And even just for those, there's a lot +// of pain and hackery needed to get transfers to actually happen reliably +// with background sessions. +// +// Don't try to upload or download in many background sessions, since the system +// will impose an exponentially increasing time penalty to prevent the app from +// getting too much background execution time. +// +// References: +// +// "Moving to Fewer, Larger Transfers" +// https://forums.developer.apple.com/thread/14853 +// +// "NSURLSession’s Resume Rate Limiter" +// https://forums.developer.apple.com/thread/14854 +// +// "Background Session Task state persistence" +// https://forums.developer.apple.com/thread/11554 +// +@property(atomic, assign) BOOL useBackgroundSession; + +// Indicates if the fetcher was started using a background session. +@property(atomic, readonly, getter=isUsingBackgroundSession) BOOL usingBackgroundSession; + +// Indicates if uploads should use an upload task. This is always set for file or stream-provider +// bodies, but may be set explicitly for NSData bodies. +@property(atomic, assign) BOOL useUploadTask; + +// Indicates that the fetcher is using a session that may be shared with other fetchers. +@property(atomic, readonly) BOOL canShareSession; + +// By default, the fetcher allows only secure (https) schemes unless this +// property is set, or the GTM_ALLOW_INSECURE_REQUESTS build flag is set. +// +// For example, during debugging when fetching from a development server that lacks SSL support, +// this may be set to @[ @"http" ], or when the fetcher is used to retrieve local files, +// this may be set to @[ @"file" ]. +// +// This should be left as nil for release builds to avoid creating the opportunity for +// leaking private user behavior and data. If a server is providing insecure URLs +// for fetching by the client app, report the problem as server security & privacy bug. +// +// For builds with the iOS 9/OS X 10.11 and later SDKs, this property is required only when +// the app specifies NSAppTransportSecurity/NSAllowsArbitraryLoads in the main bundle's Info.plist. +@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes; + +// By default, the fetcher prohibits localhost requests unless this property is set, +// or the GTM_ALLOW_INSECURE_REQUESTS build flag is set. +// +// For localhost requests, the URL scheme is not checked when this property is set. +// +// For builds with the iOS 9/OS X 10.11 and later SDKs, this property is required only when +// the app specifies NSAppTransportSecurity/NSAllowsArbitraryLoads in the main bundle's Info.plist. +@property(atomic, assign) BOOL allowLocalhostRequest; + +// By default, the fetcher requires valid server certs. This may be bypassed +// temporarily for development against a test server with an invalid cert. +@property(atomic, assign) BOOL allowInvalidServerCertificates; + +// Cookie storage object for this fetcher. If nil, the fetcher will use a static cookie +// storage instance shared among fetchers. If this fetcher was created by a fetcher service +// object, it will be set to use the service object's cookie storage. See Cookies section above for +// the full discussion. +// +// Because as of Jan 2014 standalone instances of NSHTTPCookieStorage do not actually +// store any cookies (Radar 15735276) we use our own subclass, GTMSessionCookieStorage, +// to hold cookies in memory. +@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage; + +// Setting the credential is optional; it is used if the connection receives +// an authentication challenge. +@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential; + +// Setting the proxy credential is optional; it is used if the connection +// receives an authentication challenge from a proxy. +@property(atomic, strong, GTM_NULLABLE) NSURLCredential *proxyCredential; + +// If body data, body file URL, or body stream provider is not set, then a GET request +// method is assumed. +@property(atomic, strong, GTM_NULLABLE) NSData *bodyData; + +// File to use as the request body. This forces use of an upload task. +@property(atomic, strong, GTM_NULLABLE) NSURL *bodyFileURL; + +// Length of body to send, expected or actual. +@property(atomic, readonly) int64_t bodyLength; + +// The body stream provider may be called repeatedly to provide a body. +// Setting a body stream provider forces use of an upload task. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherBodyStreamProvider bodyStreamProvider; + +// Object to add authorization to the request, if needed. +// +// This may not be changed once beginFetch has been invoked. +@property(atomic, strong, GTM_NULLABLE) id authorizer; + +// The service object that created and monitors this fetcher, if any. +@property(atomic, strong) id service; + +// The host, if any, used to classify this fetcher in the fetcher service. +@property(atomic, copy, GTM_NULLABLE) 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 when the running fetchers +// exceeds the service's maxRunningFetchersPerHost. A priority of NSIntegerMin will +// exempt this fetcher from delay. +@property(atomic, assign) NSInteger servicePriority; + +// The delegate's optional didReceiveResponse block may be used to inspect or alter +// the session task response. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherDidReceiveResponseBlock didReceiveResponseBlock; + +// The delegate's optional challenge block may be used to inspect or alter +// the session task challenge. +// +// If this block is not set, the fetcher's default behavior for the NSURLSessionTask +// didReceiveChallenge: delegate method is to use the fetcher's respondToChallenge: method +// which relies on the fetcher's credential and proxyCredential properties. +// +// Warning: This may be called repeatedly if the challenge fails. Check +// challenge.previousFailureCount to identify repeated invocations. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock; + +// The delegate's optional willRedirect block may be used to inspect or alter +// the redirection. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherWillRedirectBlock willRedirectBlock; + +// The optional send progress block reports body bytes uploaded. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherSendProgressBlock sendProgressBlock; + +// The optional accumulate block may be set by clients wishing to accumulate data +// themselves rather than let the fetcher append each buffer to an NSData. +// +// When this is called with nil data (such as on redirect) the client +// should empty its accumulation buffer. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherAccumulateDataBlock accumulateDataBlock; + +// The optional received progress block may be used to monitor data +// received from a data task. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherReceivedProgressBlock receivedProgressBlock; + +// The delegate's optional downloadProgress block may be used to monitor download +// progress in writing to disk. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherDownloadProgressBlock downloadProgressBlock; + +// The delegate's optional willCacheURLResponse block may be used to alter the cached +// NSURLResponse. The user may prevent caching by passing nil to the block's response. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherWillCacheURLResponseBlock willCacheURLResponseBlock; + +// Enable retrying; see comments at the top of this file. Setting +// retryEnabled=YES resets the min and max retry intervals. +@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled; + +// Retry block is optional for retries. +// +// If present, this block should call the response block with YES to cause a retry or NO to end the +// fetch. +// See comments at the top of this file. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock; + +// The optional block for collecting the metrics of the present session. +// +// This is called on the callback queue. +@property(atomic, copy, GTM_NULLABLE) + GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE( + ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)); + +// 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, 60 seconds for downloads and 600 seconds for uploads. +@property(atomic, 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 +// set this except for unit testing. +@property(atomic, assign) NSTimeInterval minRetryInterval; + +// Multiplier used to increase the interval between retries, typically 2.0. +// Clients should not need to set this. +@property(atomic, assign) double retryFactor; + +// Number of retries attempted. +@property(atomic, readonly) NSUInteger retryCount; + +// Interval delay to precede next retry. +@property(atomic, readonly) NSTimeInterval nextRetryInterval; + +#if GTM_BACKGROUND_TASK_FETCHING +// Skip use of a UIBackgroundTask, thus requiring fetches to complete when the app is in the +// foreground. +// +// Targets should define GTM_BACKGROUND_TASK_FETCHING to 0 to avoid use of a UIBackgroundTask +// on iOS to allow fetches to complete in the background. This property is available when +// it's not practical to set the preprocessor define. +@property(atomic, assign) BOOL skipBackgroundTask; +#endif // GTM_BACKGROUND_TASK_FETCHING + +// Begin fetching the request +// +// The delegate may optionally implement the callback or pass nil for the selector or handler. +// +// The delegate and all callback blocks are retained between the beginFetch call until after the +// finish callback, or until the fetch is stopped. +// +// 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:(GTMSessionFetcher *)fetcher +// finishedWithData:(NSData *)data +// error:(NSError *)error; +// +// If the application has specified a destinationFileURL or an accumulateDataBlock +// for the fetcher, the data parameter passed to the callback will be nil. + +- (void)beginFetchWithDelegate:(GTM_NULLABLE id)delegate + didFinishSelector:(GTM_NULLABLE SEL)finishedSEL; + +- (void)beginFetchWithCompletionHandler:(GTM_NULLABLE GTMSessionFetcherCompletionHandler)handler; + +// Returns YES if this fetcher is in the process of fetching a URL. +@property(atomic, readonly, getter=isFetching) BOOL fetching; + +// Cancel the fetch of the request that's currently in progress. The completion handler +// will not be called. +- (void)stopFetching; + +// A block to be called when the fetch completes. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherCompletionHandler completionHandler; + +// A block to be called if download resume data becomes available. +@property(atomic, strong, GTM_NULLABLE) void (^resumeDataBlock)(NSData *); + +// Return the status code from the server response. +@property(atomic, readonly) NSInteger statusCode; + +// Return the http headers from the response. +@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSString *) *responseHeaders; + +// The response, once it's been received. +@property(atomic, strong, readonly, GTM_NULLABLE) NSURLResponse *response; + +// Bytes downloaded so far. +@property(atomic, readonly) int64_t downloadedLength; + +// Buffer of currently-downloaded data, if available. +@property(atomic, readonly, strong, GTM_NULLABLE) NSData *downloadedData; + +// Local path to which the downloaded file will be moved. +// +// If a file already exists at the path, it will be overwritten. +// Will create the enclosing folders if they are not present. +@property(atomic, strong, GTM_NULLABLE) NSURL *destinationFileURL; + +// The time this fetcher originally began fetching. This is useful as a time +// barrier for ignoring irrelevant fetch notifications or callbacks. +@property(atomic, strong, readonly, GTM_NULLABLE) NSDate *initialBeginFetchDate; + +// userData is retained solely for the convenience of the client. +@property(atomic, strong, GTM_NULLABLE) id userData; + +// Stored property values are retained solely for the convenience of the client. +@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties; + +- (void)setProperty:(GTM_NULLABLE id)obj forKey:(NSString *)key; // Pass nil for obj to remove the property. +- (GTM_NULLABLE id)propertyForKey:(NSString *)key; + +- (void)addPropertiesFromDictionary:(GTM_NSDictionaryOf(NSString *, id) *)dict; + +// Comments are useful for logging, so are strongly recommended for each fetcher. +@property(atomic, copy, GTM_NULLABLE) NSString *comment; + +- (void)setCommentWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); + +// Log of request and response, if logging is enabled +@property(atomic, copy, GTM_NULLABLE) NSString *log; + +// Callbacks are run on this queue. If none is supplied, the main queue is used. +@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue; + +// The queue used internally by the session to invoke its delegate methods in the fetcher. +// +// Application callbacks are always called by the fetcher on the callbackQueue above, +// not on this queue. Apps should generally not change this queue. +// +// The default delegate queue is the main queue. +// +// This value is ignored after the session has been created, so this +// property should be set in the fetcher service rather in the fetcher as it applies +// to a shared session. +@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue; + +// Spin the run loop or sleep the thread, discarding events, until the fetch has completed. +// +// This is only for use in testing or in tools without a user interface. +// +// Note: Synchronous fetches should never be used by shipping apps; they are +// sufficient reason for rejection from the app store. +// +// Returns NO if timed out. +- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds; + +// Test block is optional for testing. +// +// If present, this block will cause the fetcher to skip starting the session, and instead +// use the test block response values when calling the completion handler and delegate code. +// +// Test code can set this on the fetcher or on the fetcher service. For testing libraries +// that use a fetcher without exposing either the fetcher or the fetcher service, the global +// method setGlobalTestBlock: will set the block for all fetchers that do not have a test +// block set. +// +// The test code can pass nil for all response parameters to indicate that the fetch +// should proceed. +// +// Applications can exclude test block support by setting GTM_DISABLE_FETCHER_TEST_BLOCK. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock; + ++ (void)setGlobalTestBlock:(GTM_NULLABLE GTMSessionFetcherTestBlock)block; + +// When using the testBlock, |testBlockAccumulateDataChunkCount| is the desired number of chunks to +// divide the response data into if the client has streaming enabled. The data will be divided up to +// |testBlockAccumulateDataChunkCount| chunks; however, the exact amount may vary depending on the +// size of the response data (e.g. a 1-byte response can only be divided into one chunk). +@property(atomic, readwrite) NSUInteger testBlockAccumulateDataChunkCount; + +#if GTM_BACKGROUND_TASK_FETCHING +// For testing or to override UIApplication invocations, apps may specify an alternative +// target for messages to UIApplication. ++ (void)setSubstituteUIApplication:(nullable id)substituteUIApplication; ++ (nullable id)substituteUIApplication; +#endif // GTM_BACKGROUND_TASK_FETCHING + +// Exposed for testing. ++ (GTMSessionCookieStorage *)staticCookieStorage; ++ (BOOL)appAllowsInsecureRequests; + +#if STRIP_GTM_FETCH_LOGGING +// If logging is stripped, provide a stub for the main method +// for controlling logging. ++ (void)setLoggingEnabled:(BOOL)flag; ++ (BOOL)isLoggingEnabled; + +#else + +// These methods let an application log specific body text, such as the text description of a binary +// request or response. The application should set the fetcher to defer response body logging until +// the response has been received and the log response body has been set by the app. For example: +// +// fetcher.logRequestBody = [binaryObject stringDescription]; +// fetcher.deferResponseBodyLogging = YES; +// [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { +// if (error == nil) { +// fetcher.logResponseBody = [[[MyThing alloc] initWithData:data] stringDescription]; +// } +// fetcher.deferResponseBodyLogging = NO; +// }]; + +@property(atomic, copy, GTM_NULLABLE) NSString *logRequestBody; +@property(atomic, assign) BOOL deferResponseBodyLogging; +@property(atomic, copy, GTM_NULLABLE) NSString *logResponseBody; + +// Internal logging support. +@property(atomic, readonly) NSData *loggedStreamData; +@property(atomic, assign) BOOL hasLoggedError; +@property(atomic, strong, GTM_NULLABLE) NSURL *redirectedFromURL; +- (void)appendLoggedStreamData:(NSData *)dataToAdd; +- (void)clearLoggedStreamData; + +#endif // STRIP_GTM_FETCH_LOGGING + +@end + +@interface GTMSessionFetcher (BackwardsCompatibilityOnly) +// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves. +// This method is just for compatibility with the old GTMHTTPFetcher class. +- (void)setCookieStorageMethod:(NSInteger)method; +@end + +// Until we can just instantiate NSHTTPCookieStorage for local use, we'll +// implement all the public methods ourselves. This stores cookies only in +// memory. Additional methods are provided for testing. +// +// iOS 9/OS X 10.11 added +[NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:] +// which may also be used to create cookie storage. +@interface GTMSessionCookieStorage : NSHTTPCookieStorage + +// Add the array off cookies to the storage, replacing duplicates. +// Also removes expired cookies from the storage. +- (void)setCookies:(GTM_NULLABLE GTM_NSArrayOf(NSHTTPCookie *) *)cookies; + +- (void)removeAllCookies; + +@end + +// Macros to monitor synchronization blocks in debug builds. +// These report problems using GTMSessionCheckDebug. +// +// GTMSessionMonitorSynchronized Start monitoring a top-level-only +// @sync scope. +// GTMSessionMonitorRecursiveSynchronized Start monitoring a top-level or +// recursive @sync scope. +// GTMSessionCheckSynchronized Verify that the current execution +// is inside a @sync scope. +// GTMSessionCheckNotSynchronized Verify that the current execution +// is not inside a @sync scope. +// +// Example usage: +// +// - (void)myExternalMethod { +// @synchronized(self) { +// GTMSessionMonitorSynchronized(self) +// +// - (void)myInternalMethod { +// GTMSessionCheckSynchronized(self); +// +// - (void)callMyCallbacks { +// GTMSessionCheckNotSynchronized(self); +// +// GTMSessionCheckNotSynchronized is available for verifying the code isn't +// in a deadlockable @sync state when posting notifications and invoking +// callbacks. Don't use GTMSessionCheckNotSynchronized immediately before a +// @sync scope; the normal recursiveness check of GTMSessionMonitorSynchronized +// can catch those. + +#ifdef __OBJC__ +// If asserts are entirely no-ops, the synchronization monitor is just a bunch +// of counting code that doesn't report exceptional circumstances in any way. +// Only build the synchronization monitor code if NS_BLOCK_ASSERTIONS is not +// defined or asserts are being logged instead. +#if DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) + #define __GTMSessionMonitorSynchronizedVariableInner(varname, counter) \ + varname ## counter + #define __GTMSessionMonitorSynchronizedVariable(varname, counter) \ + __GTMSessionMonitorSynchronizedVariableInner(varname, counter) + + #define GTMSessionMonitorSynchronized(obj) \ + NS_VALID_UNTIL_END_OF_SCOPE id \ + __GTMSessionMonitorSynchronizedVariable(__monitor, __COUNTER__) = \ + [[GTMSessionSyncMonitorInternal alloc] initWithSynchronizationObject:obj \ + allowRecursive:NO \ + functionName:__func__] + + #define GTMSessionMonitorRecursiveSynchronized(obj) \ + NS_VALID_UNTIL_END_OF_SCOPE id \ + __GTMSessionMonitorSynchronizedVariable(__monitor, __COUNTER__) = \ + [[GTMSessionSyncMonitorInternal alloc] initWithSynchronizationObject:obj \ + allowRecursive:YES \ + functionName:__func__] + + #define GTMSessionCheckSynchronized(obj) { \ + GTMSESSION_ASSERT_DEBUG( \ + [GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ + @"GTMSessionCheckSynchronized(" #obj ") failed: not sync'd" \ + @" on " #obj " in %s. Call stack:\n%@", \ + __func__, [NSThread callStackSymbols]); \ + } + + #define GTMSessionCheckNotSynchronized(obj) { \ + GTMSESSION_ASSERT_DEBUG( \ + ![GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ + @"GTMSessionCheckNotSynchronized(" #obj ") failed: was sync'd" \ + @" on " #obj " in %s by %@. Call stack:\n%@", __func__, \ + [GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ + [NSThread callStackSymbols]); \ + } + +// GTMSessionSyncMonitorInternal is a private class that keeps track of the +// beginning and end of synchronized scopes. +// +// This class should not be used directly, but only via the +// GTMSessionMonitorSynchronized macro. +@interface GTMSessionSyncMonitorInternal : NSObject +- (instancetype)initWithSynchronizationObject:(id)object + allowRecursive:(BOOL)allowRecursive + functionName:(const char *)functionName; +// Return the names of the functions that hold sync on the object, or nil if none. ++ (NSArray *)functionsHoldingSynchronizationOnObject:(id)object; +@end + +#else + #define GTMSessionMonitorSynchronized(obj) do { } while (0) + #define GTMSessionMonitorRecursiveSynchronized(obj) do { } while (0) + #define GTMSessionCheckSynchronized(obj) do { } while (0) + #define GTMSessionCheckNotSynchronized(obj) do { } while (0) +#endif // !DEBUG +#endif // __OBJC__ + + +GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m new file mode 100644 index 00000000..3dc64597 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m @@ -0,0 +1,4670 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMSessionFetcher.h" +#if TARGET_OS_OSX && GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH +// To reconnect background sessions on Mac outside +load requires importing and linking +// AppKit to access the NSApplicationDidFinishLaunching symbol. +#import +#endif + +#import + +#ifndef STRIP_GTM_FETCH_LOGGING + #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. +#endif + +GTM_ASSUME_NONNULL_BEGIN + +NSString *const kGTMSessionFetcherStartedNotification = @"kGTMSessionFetcherStartedNotification"; +NSString *const kGTMSessionFetcherStoppedNotification = @"kGTMSessionFetcherStoppedNotification"; +NSString *const kGTMSessionFetcherRetryDelayStartedNotification = @"kGTMSessionFetcherRetryDelayStartedNotification"; +NSString *const kGTMSessionFetcherRetryDelayStoppedNotification = @"kGTMSessionFetcherRetryDelayStoppedNotification"; + +NSString *const kGTMSessionFetcherCompletionInvokedNotification = @"kGTMSessionFetcherCompletionInvokedNotification"; +NSString *const kGTMSessionFetcherCompletionDataKey = @"data"; +NSString *const kGTMSessionFetcherCompletionErrorKey = @"error"; + +NSString *const kGTMSessionFetcherErrorDomain = @"com.google.GTMSessionFetcher"; +NSString *const kGTMSessionFetcherStatusDomain = @"com.google.HTTPStatus"; +NSString *const kGTMSessionFetcherStatusDataKey = @"data"; // data returned with a kGTMSessionFetcherStatusDomain error +NSString *const kGTMSessionFetcherStatusDataContentTypeKey = @"data_content_type"; + +NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey = @"kGTMSessionFetcherNumberOfRetriesDoneKey"; +NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey = @"kGTMSessionFetcherElapsedIntervalWithRetriesKey"; + +static NSString *const kGTMSessionIdentifierPrefix = @"com.google.GTMSessionFetcher"; +static NSString *const kGTMSessionIdentifierDestinationFileURLMetadataKey = @"_destURL"; +static NSString *const kGTMSessionIdentifierBodyFileURLMetadataKey = @"_bodyURL"; + +// The default max retry interview is 10 minutes for uploads (POST/PUT/PATCH), +// 1 minute for downloads. +static const NSTimeInterval kUnsetMaxRetryInterval = -1.0; +static const NSTimeInterval kDefaultMaxDownloadRetryInterval = 60.0; +static const NSTimeInterval kDefaultMaxUploadRetryInterval = 60.0 * 10.; + +// The maximum data length that can be loaded to the error userInfo +static const int64_t kMaximumDownloadErrorDataLength = 20000; + +#ifdef GTMSESSION_PERSISTED_DESTINATION_KEY +// Projects using unique class names should also define a unique persisted destination key. +static NSString * const kGTMSessionFetcherPersistedDestinationKey = + GTMSESSION_PERSISTED_DESTINATION_KEY; +#else +static NSString * const kGTMSessionFetcherPersistedDestinationKey = + @"com.google.GTMSessionFetcher.downloads"; +#endif + +GTM_ASSUME_NONNULL_END + +// +// GTMSessionFetcher +// + +#if 0 +#define GTM_LOG_BACKGROUND_SESSION(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__) +#else +#define GTM_LOG_BACKGROUND_SESSION(...) +#endif + +#ifndef GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY + #if (TARGET_OS_TV \ + || TARGET_OS_WATCH \ + || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)) + #define GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY 1 + #endif +#endif + +#if ((defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST) || \ + (TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) || \ + (TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \ + (TARGET_OS_WATCH && defined(__WATCHOS_6_0) && __WATCHOS_VERSION_MIN_REQUIRED >= __WATCHOS_6_0) || \ + (TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MIN_REQUIRED >= __TVOS_13_0)) +#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1 +#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1 +#elif ((TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_15) || \ + (TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0) || \ + (TARGET_OS_WATCH && defined(__WATCHOS_6_0) && __WATCHOS_VERSION_MAX_ALLOWED >= __WATCHOS_6_0) || \ + (TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MAX_ALLOWED >= __TVOS_13_0)) +#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0 +#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 1 +#else +#define GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0 +#define GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION 0 +#endif + +#if ((defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST) || \ + (TARGET_OS_OSX && defined(__MAC_10_15) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_15) || \ + (TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_13_0) || \ + (TARGET_OS_WATCH && defined(__WATCHOS_6_0) && __WATCHOS_VERSION_MIN_REQUIRED >= __WATCHOS_6_0) || \ + (TARGET_OS_TV && defined(__TVOS_13_0) && __TVOS_VERSION_MIN_REQUIRED >= __TVOS_13_0)) +#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 1 +#else +#define GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR 0 +#endif + +@interface GTMSessionFetcher () + +@property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadedData; +@property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadResumeData; + +#if GTM_BACKGROUND_TASK_FETCHING +// Should always be accessed within an @synchronized(self). +@property(assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier; +#endif + +@property(atomic, readwrite, getter=isUsingBackgroundSession) BOOL usingBackgroundSession; + +@end + +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMSessionFetcher (GTMSessionFetcherLoggingInternal) +- (void)logFetchWithError:(NSError *)error; +- (void)logNowWithError:(GTM_NULLABLE NSError *)error; +- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream; +- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: + (GTMSessionFetcherBodyStreamProvider)streamProvider; +@end +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +GTM_ASSUME_NONNULL_BEGIN + +static NSTimeInterval InitialMinRetryInterval(void) { + return 1.0 + ((double)(arc4random_uniform(0x0FFFF)) / (double) 0x0FFFF); +} + +static BOOL IsLocalhost(NSString * GTM_NULLABLE_TYPE host) { + // We check if there's host, and then make the comparisons. + if (host == nil) return NO; + return ([host caseInsensitiveCompare:@"localhost"] == NSOrderedSame + || [host isEqual:@"::1"] + || [host isEqual:@"127.0.0.1"]); +} + +static NSDictionary *GTM_NULLABLE_TYPE GTMErrorUserInfoForData( + NSData *GTM_NULLABLE_TYPE data, NSDictionary *GTM_NULLABLE_TYPE responseHeaders) { + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + + if (data.length > 0) { + userInfo[kGTMSessionFetcherStatusDataKey] = data; + + NSString *contentType = responseHeaders[@"Content-Type"]; + if (contentType) { + userInfo[kGTMSessionFetcherStatusDataContentTypeKey] = contentType; + } + } + + return userInfo.count > 0 ? userInfo : nil; +} + +static GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE gGlobalTestBlock; + +@implementation GTMSessionFetcher { + NSMutableURLRequest *_request; // after beginFetch, changed only in delegate callbacks + BOOL _useUploadTask; // immutable after beginFetch + NSURL *_bodyFileURL; // immutable after beginFetch + GTMSessionFetcherBodyStreamProvider _bodyStreamProvider; // immutable after beginFetch + NSURLSession *_session; + BOOL _shouldInvalidateSession; // immutable after beginFetch + NSURLSession *_sessionNeedingInvalidation; + NSURLSessionConfiguration *_configuration; + NSURLSessionTask *_sessionTask; + NSString *_taskDescription; + float _taskPriority; + NSURLResponse *_response; + NSString *_sessionIdentifier; + BOOL _wasCreatedFromBackgroundSession; + BOOL _didCreateSessionIdentifier; + NSString *_sessionIdentifierUUID; + BOOL _userRequestedBackgroundSession; + BOOL _usingBackgroundSession; + NSMutableData * GTM_NULLABLE_TYPE _downloadedData; + NSError *_downloadFinishedError; + NSData *_downloadResumeData; // immutable after construction + NSData * GTM_NULLABLE_TYPE _downloadTaskErrorData; // Data for when download task fails + NSURL *_destinationFileURL; + int64_t _downloadedLength; + NSURLCredential *_credential; // username & password + NSURLCredential *_proxyCredential; // credential supplied to proxy servers + BOOL _isStopNotificationNeeded; // set when start notification has been sent + BOOL _isUsingTestBlock; // set when a test block was provided (remains set when the block is released) + id _userData; // retained, if set by caller + NSMutableDictionary *_properties; // more data retained for caller + dispatch_queue_t _callbackQueue; + dispatch_group_t _callbackGroup; // read-only after creation + NSOperationQueue *_delegateQueue; // immutable after beginFetch + + id _authorizer; // immutable after beginFetch + + // The service object that created and monitors this fetcher, if any. + id _service; // immutable; set by the fetcher service upon creation + NSString *_serviceHost; + NSInteger _servicePriority; // immutable after beginFetch + BOOL _hasStoppedFetching; // counterpart to _initialBeginFetchDate + BOOL _userStoppedFetching; + + BOOL _isRetryEnabled; // user wants auto-retry + NSTimer *_retryTimer; + NSUInteger _retryCount; + NSTimeInterval _maxRetryInterval; // default 60 (download) or 600 (upload) seconds + NSTimeInterval _minRetryInterval; // random between 1 and 2 seconds + NSTimeInterval _retryFactor; // default interval multiplier is 2 + NSTimeInterval _lastRetryInterval; + NSDate *_initialBeginFetchDate; // date that beginFetch was first invoked; immutable after initial beginFetch + NSDate *_initialRequestDate; // date of first request to the target server (ignoring auth) + BOOL _hasAttemptedAuthRefresh; // accessed only in shouldRetryNowForStatus: + + NSString *_comment; // comment for log + NSString *_log; +#if !STRIP_GTM_FETCH_LOGGING + NSMutableData *_loggedStreamData; + NSURL *_redirectedFromURL; + NSString *_logRequestBody; + NSString *_logResponseBody; + BOOL _hasLoggedError; + BOOL _deferResponseBodyLogging; +#endif +} + +#if !GTMSESSION_UNIT_TESTING ++ (void)load { +#if GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH && TARGET_OS_IPHONE + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(reconnectFetchersForBackgroundSessionsOnAppLaunch:) + name:UIApplicationDidFinishLaunchingNotification + object:nil]; +#elif GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH && TARGET_OS_OSX + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(reconnectFetchersForBackgroundSessionsOnAppLaunch:) + name:NSApplicationDidFinishLaunchingNotification + object:nil]; +#else + [self fetchersForBackgroundSessions]; +#endif +} + ++ (void)reconnectFetchersForBackgroundSessionsOnAppLaunch:(NSNotification *)notification { + // Give all other app-did-launch handlers a chance to complete before + // reconnecting the fetchers. Not doing this may lead to reconnecting + // before the app delegate has a chance to run. + dispatch_async(dispatch_get_main_queue(), ^{ + [self fetchersForBackgroundSessions]; + }); +} +#endif // !GTMSESSION_UNIT_TESTING + ++ (instancetype)fetcherWithRequest:(GTM_NULLABLE NSURLRequest *)request { + return [[self alloc] initWithRequest:request configuration:nil]; +} + ++ (instancetype)fetcherWithURL:(NSURL *)requestURL { + return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]]; +} + ++ (instancetype)fetcherWithURLString:(NSString *)requestURLString { + return [self fetcherWithURL:(NSURL *)[NSURL URLWithString:requestURLString]]; +} + ++ (instancetype)fetcherWithDownloadResumeData:(NSData *)resumeData { + GTMSessionFetcher *fetcher = [self fetcherWithRequest:nil]; + fetcher.comment = @"Resuming download"; + fetcher.downloadResumeData = resumeData; + return fetcher; +} + ++ (GTM_NULLABLE instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier { + GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); + NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; + GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; + if (!fetcher && [sessionIdentifier hasPrefix:kGTMSessionIdentifierPrefix]) { + fetcher = [self fetcherWithRequest:nil]; + [fetcher setSessionIdentifier:sessionIdentifier]; + [sessionIdentifierToFetcherMap setObject:fetcher forKey:sessionIdentifier]; + fetcher->_wasCreatedFromBackgroundSession = YES; + [fetcher setCommentWithFormat:@"Resuming %@", + fetcher && fetcher->_sessionIdentifierUUID ? fetcher->_sessionIdentifierUUID : @"?"]; + } + return fetcher; +} + ++ (NSMapTable *)sessionIdentifierToFetcherMap { + // TODO: What if a service is involved in creating the fetcher? Currently, when re-creating + // fetchers, if a service was involved, it is not re-created. Should the service maintain a map? + static NSMapTable *gSessionIdentifierToFetcherMap = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gSessionIdentifierToFetcherMap = [NSMapTable strongToWeakObjectsMapTable]; + }); + return gSessionIdentifierToFetcherMap; +} + +#if !GTM_ALLOW_INSECURE_REQUESTS ++ (BOOL)appAllowsInsecureRequests { + // If the main bundle Info.plist key NSAppTransportSecurity is present, and it specifies + // NSAllowsArbitraryLoads, then we need to explicitly enforce secure schemes. +#if GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY + static BOOL allowsInsecureRequests; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSBundle *mainBundle = [NSBundle mainBundle]; + NSDictionary *appTransportSecurity = + [mainBundle objectForInfoDictionaryKey:@"NSAppTransportSecurity"]; + allowsInsecureRequests = + [[appTransportSecurity objectForKey:@"NSAllowsArbitraryLoads"] boolValue]; + }); + return allowsInsecureRequests; +#else + // For builds targeting iOS 8 or 10.10 and earlier, we want to require fetcher + // security checks. + return YES; +#endif // GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY +} +#else // GTM_ALLOW_INSECURE_REQUESTS ++ (BOOL)appAllowsInsecureRequests { + return YES; +} +#endif // !GTM_ALLOW_INSECURE_REQUESTS + + +- (instancetype)init { + return [self initWithRequest:nil configuration:nil]; +} + +- (instancetype)initWithRequest:(NSURLRequest *)request { + return [self initWithRequest:request configuration:nil]; +} + +- (instancetype)initWithRequest:(GTM_NULLABLE NSURLRequest *)request + configuration:(GTM_NULLABLE NSURLSessionConfiguration *)configuration { + self = [super init]; + if (self) { +#if GTM_BACKGROUND_TASK_FETCHING + _backgroundTaskIdentifier = UIBackgroundTaskInvalid; +#endif + _request = [request mutableCopy]; + _configuration = configuration; + + NSData *bodyData = request.HTTPBody; + if (bodyData) { + _bodyLength = (int64_t)bodyData.length; + } else { + _bodyLength = NSURLSessionTransferSizeUnknown; + } + + _callbackQueue = dispatch_get_main_queue(); + _callbackGroup = dispatch_group_create(); + _delegateQueue = [NSOperationQueue mainQueue]; + + _minRetryInterval = InitialMinRetryInterval(); + _maxRetryInterval = kUnsetMaxRetryInterval; + + _taskPriority = -1.0f; // Valid values if set are 0.0...1.0. + + _testBlockAccumulateDataChunkCount = 1; + +#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 { + NSString *requestStr = self.request.URL.description; + if (requestStr.length == 0) { + if (self.downloadResumeData.length > 0) { + requestStr = @""; + } else if (_wasCreatedFromBackgroundSession) { + requestStr = @""; + } else { + requestStr = @""; + } + } + return [NSString stringWithFormat:@"%@ %p (%@)", [self class], self, requestStr]; +} + +- (void)dealloc { + GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded, + @"unbalanced fetcher notification for %@", _request.URL); + [self forgetSessionIdentifierForFetcherWithoutSyncCheck]; + + // Note: if a session task 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 +} + +#pragma mark - + +// Begin fetching the URL (or begin a retry fetch). The delegate is retained +// for the duration of the fetch connection. + +- (void)beginFetchWithCompletionHandler:(GTM_NULLABLE GTMSessionFetcherCompletionHandler)handler { + GTMSessionCheckNotSynchronized(self); + + _completionHandler = [handler copy]; + + // 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. + [self beginFetchMayDelay:YES mayAuthorize:YES]; +} + +// Begin fetching the URL for a retry fetch. The delegate and completion handler +// are already provided, and do not need to be copied. +- (void)beginFetchForRetry { + GTMSessionCheckNotSynchronized(self); + + [self beginFetchMayDelay:YES mayAuthorize:YES]; +} + +- (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(GTM_NULLABLE_TYPE id)target + didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector { + GTMSessionFetcherAssertValidSelector(target, finishedSelector, @encode(GTMSessionFetcher *), + @encode(NSData *), @encode(NSError *), 0); + GTMSessionFetcherCompletionHandler completionHandler = ^(NSData *data, NSError *error) { + if (target && finishedSelector) { + id selfArg = self; // Placate ARC. + NSMethodSignature *sig = [target methodSignatureForSelector:finishedSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:(SEL)finishedSelector]; + [invocation setTarget:target]; + [invocation setArgument:&selfArg atIndex:2]; + [invocation setArgument:&data atIndex:3]; + [invocation setArgument:&error atIndex:4]; + [invocation invoke]; + } + }; + return completionHandler; +} + +- (void)beginFetchWithDelegate:(GTM_NULLABLE_TYPE id)target + didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector { + GTMSessionCheckNotSynchronized(self); + + GTMSessionFetcherCompletionHandler handler = [self completionHandlerWithTarget:target + didFinishSelector:finishedSelector]; + [self beginFetchWithCompletionHandler:handler]; +} + +- (void)beginFetchMayDelay:(BOOL)mayDelay + mayAuthorize:(BOOL)mayAuthorize { + // This is the internal entry point for re-starting fetches. + GTMSessionCheckNotSynchronized(self); + + NSMutableURLRequest *fetchRequest = _request; // The request property is now externally immutable. + NSURL *fetchRequestURL = fetchRequest.URL; + NSString *priorSessionIdentifier = self.sessionIdentifier; + + // A utility block for creating error objects when we fail to start the fetch. + NSError *(^beginFailureError)(NSInteger) = ^(NSInteger code){ + NSString *urlString = fetchRequestURL.absoluteString; + NSDictionary *userInfo = @{ + NSURLErrorFailingURLStringErrorKey : (urlString ? urlString : @"(missing URL)") + }; + return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain + code:code + userInfo:userInfo]; + }; + + // Catch delegate queue maxConcurrentOperationCount values other than 1, particularly + // NSOperationQueueDefaultMaxConcurrentOperationCount (-1), to avoid the additional complexity + // of simultaneous or out-of-order delegate callbacks. + GTMSESSION_ASSERT_DEBUG(_delegateQueue.maxConcurrentOperationCount == 1, + @"delegate queue %@ should support one concurrent operation, not %ld", + _delegateQueue.name, + (long)_delegateQueue.maxConcurrentOperationCount); + + if (!_initialBeginFetchDate) { + // This ivar is set only here on the initial beginFetch so need not be synchronized. + _initialBeginFetchDate = [[NSDate alloc] init]; + } + + if (self.sessionTask != nil) { + // If cached fetcher returned through fetcherWithSessionIdentifier:, then it's + // already begun, but don't consider this a failure, since the user need not know this. + if (self.sessionIdentifier != nil) { + return; + } + GTMSESSION_ASSERT_DEBUG(NO, @"Fetch object %@ being reused; this should never happen", self); + [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)]; + return; + } + + if (fetchRequestURL == nil && !_downloadResumeData && !priorSessionIdentifier) { + GTMSESSION_ASSERT_DEBUG(NO, @"Beginning a fetch requires a request with a URL"); + [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)]; + return; + } + + // We'll respect the user's request for a background session (unless this is + // an upload fetcher, which does its initial request foreground.) + self.usingBackgroundSession = self.useBackgroundSession && [self canFetchWithBackgroundSession]; + + NSURL *bodyFileURL = self.bodyFileURL; + if (bodyFileURL) { + NSError *fileCheckError; + if (![bodyFileURL checkResourceIsReachableAndReturnError:&fileCheckError]) { + // This assert fires when the file being uploaded no longer exists once + // the fetcher is ready to start the upload. + GTMSESSION_ASSERT_DEBUG_OR_LOG(0, @"Body file is unreachable: %@\n %@", + bodyFileURL.path, fileCheckError); + [self failToBeginFetchWithError:fileCheckError]; + return; + } + } + + NSString *requestScheme = fetchRequestURL.scheme; + BOOL isDataRequest = [requestScheme isEqual:@"data"]; + if (isDataRequest) { + // NSURLSession does not support data URLs in background sessions. +#if DEBUG + if (priorSessionIdentifier || self.sessionIdentifier) { + GTMSESSION_LOG_DEBUG(@"Converting background to foreground session for %@", + fetchRequest); + } +#endif + // If priorSessionIdentifier is allowed to stay non-nil, a background session can + // still be created. + priorSessionIdentifier = nil; + [self setSessionIdentifierInternal:nil]; + self.usingBackgroundSession = NO; + } + +#if GTM_ALLOW_INSECURE_REQUESTS + BOOL shouldCheckSecurity = NO; +#else + BOOL shouldCheckSecurity = (fetchRequestURL != nil + && !isDataRequest + && [[self class] appAllowsInsecureRequests]); +#endif + + if (shouldCheckSecurity) { + // Allow https only for requests, unless overridden by the client. + // + // Non-https requests may too easily be snooped, so we disallow them by default. + // + // file: and data: schemes are usually safe if they are hardcoded in the client or provided + // by a trusted source, but since it's fairly rare to need them, it's safest to make clients + // explicitly whitelist them. + BOOL isSecure = + requestScheme != nil && [requestScheme caseInsensitiveCompare:@"https"] == NSOrderedSame; + if (!isSecure) { + BOOL allowRequest = NO; + NSString *host = fetchRequestURL.host; + + // Check schemes first. A file scheme request may be allowed here, or as a localhost request. + for (NSString *allowedScheme in _allowedInsecureSchemes) { + if (requestScheme != nil && + [requestScheme caseInsensitiveCompare:allowedScheme] == NSOrderedSame) { + allowRequest = YES; + break; + } + } + if (!allowRequest) { + // Check for localhost requests. Security checks only occur for non-https requests, so + // this check won't happen for an https request to localhost. + BOOL isLocalhostRequest = (host.length == 0 && [fetchRequestURL isFileURL]) || IsLocalhost(host); + if (isLocalhostRequest) { + if (self.allowLocalhostRequest) { + allowRequest = YES; + } else { + GTMSESSION_ASSERT_DEBUG(NO, @"Fetch request for localhost but fetcher" + @" allowLocalhostRequest is not set: %@", fetchRequestURL); + } + } else { + GTMSESSION_ASSERT_DEBUG(NO, @"Insecure fetch request has a scheme (%@)" + @" not found in fetcher allowedInsecureSchemes (%@): %@", + requestScheme, _allowedInsecureSchemes ?: @" @[] ", fetchRequestURL); + } + } + + if (!allowRequest) { +#if !DEBUG + NSLog(@"Insecure fetch disallowed for %@", fetchRequestURL.description ?: @"nil request URL"); +#endif + [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorInsecureRequest)]; + return; + } + } // !isSecure + } // (requestURL != nil) && !isDataRequest + + if (self.cookieStorage == nil) { + self.cookieStorage = [[self class] staticCookieStorage]; + } + + BOOL isRecreatingSession = (self.sessionIdentifier != nil) && (fetchRequest == nil); + + self.canShareSession = !isRecreatingSession && !self.usingBackgroundSession; + + if (!self.session && self.canShareSession) { + self.session = [_service sessionForFetcherCreation]; + // If _session is nil, then the service's session creation semaphore will block + // until this fetcher invokes fetcherDidCreateSession: below, so this *must* invoke + // that method, even if the session fails to be created. + } + + if (!self.session) { + // Create a session. + if (!_configuration) { + if (priorSessionIdentifier || self.usingBackgroundSession) { + NSString *sessionIdentifier = priorSessionIdentifier; + if (!sessionIdentifier) { + sessionIdentifier = [self createSessionIdentifierWithMetadata:nil]; + } + NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIdentifierToFetcherMap]; + [sessionIdentifierToFetcherMap setObject:self forKey:self.sessionIdentifier]; + + if (@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.10, *)) { + _configuration = + [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionIdentifier]; + } else { +#if ((!TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) \ + || (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0)) + // If building with support for iOS 7 or < macOS 10.10, allow using the older + // -backgroundSessionConfiguration: method, otherwise leave it out to avoid deprecated + // API warnings/errors. + _configuration = + [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier]; +#endif + } + self.usingBackgroundSession = YES; + self.canShareSession = NO; + } else { + _configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + } +#if !GTM_ALLOW_INSECURE_REQUESTS +#if GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION + _configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12; +#elif GTM_SDK_SUPPORTS_TLSMINIMUMSUPPORTEDPROTOCOLVERSION + if (@available(iOS 13, tvOS 13, watchOS 6, macOS 10.15, *)) { +#if TARGET_OS_IOS + // Early seeds of iOS 13 don't actually support the selector and several + // months later, those seeds are still in use, so validate if the selector + // is supported. + if ([_configuration respondsToSelector:@selector(setTLSMinimumSupportedProtocolVersion:)]) { + _configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12; + } else { + _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12; + } +#else + _configuration.TLSMinimumSupportedProtocolVersion = tls_protocol_version_TLSv12; +#endif // TARGET_OS_IOS + } else { + _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12; + } +#else + _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12; +#endif // GTM_SDK_REQUIRES_TLSMINIMUMSUPPORTEDPROTOCOLVERSION +#endif + } // !_configuration + _configuration.HTTPCookieStorage = self.cookieStorage; + + if (_configurationBlock) { + _configurationBlock(self, _configuration); + } + + id delegate = [_service sessionDelegate]; + if (!delegate || !self.canShareSession) { + delegate = self; + } + self.session = [NSURLSession sessionWithConfiguration:_configuration + delegate:delegate + delegateQueue:self.sessionDelegateQueue]; + GTMSESSION_ASSERT_DEBUG(self.session, @"Couldn't create session"); + + // Tell the service about the session created by this fetcher. This also signals the + // service's semaphore to allow other fetchers to request this session. + [_service fetcherDidCreateSession:self]; + + // If this assertion fires, the client probably tried to use a session identifier that was + // already used. The solution is to make the client use a unique identifier (or better yet let + // the session fetcher assign the identifier). + GTMSESSION_ASSERT_DEBUG(self.session.delegate == delegate, @"Couldn't assign delegate."); + + if (self.session) { + BOOL isUsingSharedDelegate = (delegate != self); + if (!isUsingSharedDelegate) { + _shouldInvalidateSession = YES; + } + } + } + + if (isRecreatingSession) { + _shouldInvalidateSession = YES; + + // Let's make sure there are tasks still running or if not that we get a callback from a + // completed one; otherwise, we assume the tasks failed. + // This is the observed behavior perhaps 25% of the time within the Simulator running 7.0.3 on + // exiting the app after starting an upload and relaunching the app if we manage to relaunch + // after the task has completed, but before the system relaunches us in the background. + [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, + NSArray *downloadTasks) { + if (dataTasks.count == 0 && uploadTasks.count == 0 && downloadTasks.count == 0) { + double const kDelayInSeconds = 1.0; // We should get progress indication or completion soon + dispatch_time_t checkForFeedbackDelay = + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDelayInSeconds * NSEC_PER_SEC)); + dispatch_after(checkForFeedbackDelay, dispatch_get_main_queue(), ^{ + if (!self.sessionTask && !fetchRequest) { + // If our task and/or request haven't been restored, then we assume task feedback lost. + [self removePersistedBackgroundSessionFromDefaults]; + NSError *sessionError = + [NSError errorWithDomain:kGTMSessionFetcherErrorDomain + code:GTMSessionFetcherErrorBackgroundFetchFailed + userInfo:nil]; + [self failToBeginFetchWithError:sessionError]; + } + }); + } + }]; + return; + } + + self.downloadedData = nil; + self.downloadedLength = 0; + + if (_servicePriority == NSIntegerMin) { + mayDelay = NO; + } + if (mayDelay && _service) { + BOOL shouldFetchNow = [_service fetcherShouldBeginFetching:self]; + if (!shouldFetchNow) { + // The fetch is deferred, but will happen later. + // + // If this session is held by the fetcher service, clear the session now so that we don't + // assume it's still valid after the fetcher is restarted. + if (self.canShareSession) { + self.session = nil; + } + return; + } + } + + NSString *effectiveHTTPMethod = [fetchRequest valueForHTTPHeaderField:@"X-HTTP-Method-Override"]; + if (effectiveHTTPMethod == nil) { + effectiveHTTPMethod = fetchRequest.HTTPMethod; + } + BOOL isEffectiveHTTPGet = (effectiveHTTPMethod == nil + || [effectiveHTTPMethod isEqual:@"GET"]); + + BOOL needsUploadTask = (self.useUploadTask || self.bodyFileURL || self.bodyStreamProvider); + if (_bodyData || self.bodyStreamProvider || fetchRequest.HTTPBodyStream) { + if (isEffectiveHTTPGet) { + fetchRequest.HTTPMethod = @"POST"; + isEffectiveHTTPGet = NO; + } + + if (_bodyData) { + if (!needsUploadTask) { + fetchRequest.HTTPBody = _bodyData; + } +#if !STRIP_GTM_FETCH_LOGGING + } else if (fetchRequest.HTTPBodyStream) { + if ([self respondsToSelector:@selector(loggedInputStreamForInputStream:)]) { + fetchRequest.HTTPBodyStream = + [self performSelector:@selector(loggedInputStreamForInputStream:) + withObject:fetchRequest.HTTPBodyStream]; + } +#endif + } + } + + // 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 && !isDataRequest) { + BOOL isAuthorized = [_authorizer isAuthorizedRequest:fetchRequest]; + if (!isAuthorized) { + // Authorization needed. + // + // If this session is held by the fetcher service, clear the session now so that we don't + // assume it's still valid after authorization completes. + if (self.canShareSession) { + self.session = nil; + } + + // Authorizing the request will recursively call this beginFetch:mayDelay: + // or failToBeginFetchWithError:. + [self authorizeRequest]; + return; + } + } + + // set the default upload or download retry interval, if necessary + if ([self isRetryEnabled] && self.maxRetryInterval <= 0) { + if (isEffectiveHTTPGet || [effectiveHTTPMethod isEqual:@"HEAD"]) { + [self setMaxRetryInterval:kDefaultMaxDownloadRetryInterval]; + } else { + [self setMaxRetryInterval:kDefaultMaxUploadRetryInterval]; + } + } + + // finally, start the connection + NSURLSessionTask *newSessionTask; + BOOL needsDataAccumulator = NO; + if (_downloadResumeData) { + newSessionTask = [_session downloadTaskWithResumeData:_downloadResumeData]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, + @"Failed downloadTaskWithResumeData for %@, resume data %lu bytes", + _session, (unsigned long)_downloadResumeData.length); + } else if (_destinationFileURL && !isDataRequest) { + newSessionTask = [_session downloadTaskWithRequest:fetchRequest]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed downloadTaskWithRequest for %@, %@", + _session, fetchRequest); + } else if (needsUploadTask) { + if (bodyFileURL) { + newSessionTask = [_session uploadTaskWithRequest:fetchRequest + fromFile:bodyFileURL]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, + @"Failed uploadTaskWithRequest for %@, %@, file %@", + _session, fetchRequest, bodyFileURL.path); + } else if (self.bodyStreamProvider) { + newSessionTask = [_session uploadTaskWithStreamedRequest:fetchRequest]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, + @"Failed uploadTaskWithStreamedRequest for %@, %@", + _session, fetchRequest); + } else { + GTMSESSION_ASSERT_DEBUG_OR_LOG(_bodyData != nil, + @"Upload task needs body data, %@", fetchRequest); + newSessionTask = [_session uploadTaskWithRequest:fetchRequest + fromData:(NSData * GTM_NONNULL_TYPE)_bodyData]; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, + @"Failed uploadTaskWithRequest for %@, %@, body data %lu bytes", + _session, fetchRequest, (unsigned long)_bodyData.length); + } + needsDataAccumulator = YES; + } else { + newSessionTask = [_session dataTaskWithRequest:fetchRequest]; + needsDataAccumulator = YES; + GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed dataTaskWithRequest for %@, %@", + _session, fetchRequest); + } + self.sessionTask = newSessionTask; + + if (!newSessionTask) { + // We shouldn't get here; if we're here, an earlier assertion should have fired to explain + // which session task creation failed. + [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorTaskCreationFailed)]; + return; + } + + if (needsDataAccumulator && _accumulateDataBlock == nil) { + self.downloadedData = [NSMutableData data]; + } + if (_taskDescription) { + newSessionTask.taskDescription = _taskDescription; + } + if (_taskPriority >= 0) { + if (@available(iOS 8.0, macOS 10.10, *)) { + newSessionTask.priority = _taskPriority; + } + } + +#if GTM_DISABLE_FETCHER_TEST_BLOCK + GTMSESSION_ASSERT_DEBUG(_testBlock == nil && gGlobalTestBlock == nil, @"test blocks disabled"); + _testBlock = nil; +#else + if (!_testBlock) { + if (gGlobalTestBlock) { + // Note that the test block may pass nil for all of its response parameters, + // indicating that the fetch should actually proceed. This is useful when the + // global test block has been set, and the app is only testing a specific + // fetcher. The block simulation code will then resume the task. + _testBlock = gGlobalTestBlock; + } + } + _isUsingTestBlock = (_testBlock != nil); +#endif // GTM_DISABLE_FETCHER_TEST_BLOCK + +#if GTM_BACKGROUND_TASK_FETCHING + id app = [[self class] fetcherUIApplication]; + // Background tasks seem to interfere with out-of-process uploads and downloads. + if (app && !self.skipBackgroundTask && !self.usingBackgroundSession) { + // Tell UIApplication that we want to continue even when the app is in the + // background. +#if DEBUG + NSString *bgTaskName = [NSString stringWithFormat:@"%@-%@", + [self class], fetchRequest.URL.host]; +#else + NSString *bgTaskName = @"GTMSessionFetcher"; +#endif + __block UIBackgroundTaskIdentifier bgTaskID = [app beginBackgroundTaskWithName:bgTaskName + expirationHandler:^{ + // Background task expiration callback - this block is always invoked by + // UIApplication on the main thread. + if (bgTaskID != UIBackgroundTaskInvalid) { + @synchronized(self) { + if (bgTaskID == self.backgroundTaskIdentifier) { + self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; + } + } + [app endBackgroundTask:bgTaskID]; + } + }]; + @synchronized(self) { + self.backgroundTaskIdentifier = bgTaskID; + } + } +#endif + + if (!_initialRequestDate) { + _initialRequestDate = [[NSDate alloc] init]; + } + + // We don't expect to reach here even on retry or auth until a stop notification has been sent + // for the previous task, but we should ensure that we don't unbalance that. + GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded, @"Start notification without a prior stop"); + [self sendStopNotificationIfNeeded]; + + [self addPersistedBackgroundSessionToDefaults]; + + [self setStopNotificationNeeded:YES]; + + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStartedNotification + userInfo:nil + requireAsync:NO]; + + // The service needs to know our task if it is serving as NSURLSession delegate. + [_service fetcherDidBeginFetching:self]; + + if (_testBlock) { +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + [self simulateFetchForTestBlock]; +#endif + } else { + // We resume the session task after posting the notification since the + // delegate callbacks may happen immediately if the fetch is started off + // the main thread or the session delegate queue is on a background thread, + // and we don't want to post a start notification after a premature finish + // of the session task. + [newSessionTask resume]; + } +} + +NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NSError **outError) { + NSMutableData *data = [NSMutableData data]; + + [inputStream open]; + NSInteger numberOfBytesRead = 0; + while ([inputStream hasBytesAvailable]) { + uint8_t buffer[512]; + numberOfBytesRead = [inputStream read:buffer maxLength:sizeof(buffer)]; + if (numberOfBytesRead > 0) { + [data appendBytes:buffer length:(NSUInteger)numberOfBytesRead]; + } else { + break; + } + } + [inputStream close]; + NSError *streamError = inputStream.streamError; + + if (streamError) { + data = nil; + } + if (outError) { + *outError = streamError; + } + return data; +} + +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + +- (void)simulateFetchForTestBlock { + // This is invoked on the same thread as the beginFetch method was. + // + // Callbacks will all occur on the callback queue. + _testBlock(self, ^(NSURLResponse *response, NSData *responseData, NSError *error) { + // Callback from test block. + if (response == nil && responseData == nil && error == nil) { + // Assume the fetcher should execute rather than be tested. + self->_testBlock = nil; + self->_isUsingTestBlock = NO; + [self->_sessionTask resume]; + return; + } + + GTMSessionFetcherBodyStreamProvider bodyStreamProvider = self.bodyStreamProvider; + if (bodyStreamProvider) { + bodyStreamProvider(^(NSInputStream *bodyStream){ + // Read from the input stream into an NSData buffer. We'll drain the stream + // explicitly on a background queue. + [self invokeOnCallbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) + afterUserStopped:NO + block:^{ + NSError *streamError; + NSData *streamedData = GTMDataFromInputStream(bodyStream, &streamError); + + dispatch_async(dispatch_get_main_queue(), ^{ + // Continue callbacks on the main thread, since serial behavior + // is more reliable for tests. + [self simulateDataCallbacksForTestBlockWithBodyData:streamedData + response:response + responseData:responseData + error:(error ?: streamError)]; + }); + }]; + }); + } else { + // No input stream; use the supplied data or file URL. + NSURL *bodyFileURL = self.bodyFileURL; + if (bodyFileURL) { + NSError *readError; + self->_bodyData = [NSData dataWithContentsOfURL:bodyFileURL + options:NSDataReadingMappedIfSafe + error:&readError]; + error = readError; + } + + // No stream provider. + + // In real fetches, nothing happens until the run loop spins, so apps have leeway to + // set callbacks after they call beginFetch. We'll mirror that fetcher behavior by + // delaying callbacks here at least to the next spin of the run loop. That keeps + // immediate, synchronous setting of callback blocks after beginFetch working in tests. + dispatch_async(dispatch_get_main_queue(), ^{ + [self simulateDataCallbacksForTestBlockWithBodyData:self->_bodyData + response:response + responseData:responseData + error:error]; + }); + } + }); +} + +- (void)simulateByteTransferReportWithDataLength:(int64_t)totalDataLength + block:(GTMSessionFetcherSendProgressBlock)block { + // This utility method simulates transfer progress with up to three callbacks. + // It is used to call back to any of the progress blocks. + int64_t sendReportSize = totalDataLength / 3 + 1; + int64_t totalSent = 0; + while (totalSent < totalDataLength) { + int64_t bytesRemaining = totalDataLength - totalSent; + sendReportSize = MIN(sendReportSize, bytesRemaining); + totalSent += sendReportSize; + [self invokeOnCallbackQueueUnlessStopped:^{ + block(sendReportSize, totalSent, totalDataLength); + }]; + } +} + +- (void)simulateDataCallbacksForTestBlockWithBodyData:(NSData * GTM_NULLABLE_TYPE)bodyData + response:(NSURLResponse *)response + responseData:(NSData *)suppliedData + error:(NSError *)suppliedError { + __block NSData *responseData = suppliedData; + __block NSError *responseError = suppliedError; + + // This method does the test simulation of callbacks once the upload + // and download data are known. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Get copies of ivars we'll access in async invocations. This simulation assumes + // they won't change during fetcher execution. + NSURL *destinationFileURL = _destinationFileURL; + GTMSessionFetcherWillRedirectBlock willRedirectBlock = _willRedirectBlock; + GTMSessionFetcherDidReceiveResponseBlock didReceiveResponseBlock = _didReceiveResponseBlock; + GTMSessionFetcherSendProgressBlock sendProgressBlock = _sendProgressBlock; + GTMSessionFetcherDownloadProgressBlock downloadProgressBlock = _downloadProgressBlock; + GTMSessionFetcherAccumulateDataBlock accumulateDataBlock = _accumulateDataBlock; + GTMSessionFetcherReceivedProgressBlock receivedProgressBlock = _receivedProgressBlock; + GTMSessionFetcherWillCacheURLResponseBlock willCacheURLResponseBlock = + _willCacheURLResponseBlock; + GTMSessionFetcherChallengeBlock challengeBlock = _challengeBlock; + + // Simulate receipt of redirection. + if (willRedirectBlock) { + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + willRedirectBlock((NSHTTPURLResponse *)response, self->_request, + ^(NSURLRequest *redirectRequest) { + // For simulation, we'll assume the app will just continue. + }); + }]; + } + + // If the fetcher has a challenge block, simulate a challenge. + // + // It might be nice to eventually let the user determine which testBlock + // fetches get challenged rather than always executing the supplied + // challenge block. + if (challengeBlock) { + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + NSURL *requestURL = self->_request.URL; + NSString *host = requestURL.host; + NSURLProtectionSpace *pspace = + [[NSURLProtectionSpace alloc] initWithHost:host + port:requestURL.port.integerValue + protocol:requestURL.scheme + realm:nil + authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; + id unusedSender = + (id)[NSNull null]; + NSURLAuthenticationChallenge *challenge = + [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:pspace + proposedCredential:nil + previousFailureCount:0 + failureResponse:nil + error:nil + sender:unusedSender]; + challengeBlock(self, challenge, ^(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * GTM_NULLABLE_TYPE credential){ + // We could change the responseData and responseError based on the disposition, + // but it's easier for apps to just supply the expected data and error + // directly to the test block. So this simulation ignores the disposition. + }); + }]; + } + + // Simulate receipt of an initial response. + if (response && didReceiveResponseBlock) { + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + didReceiveResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) { + // For simulation, we'll assume the disposition is to continue. + }); + }]; + } + + // Simulate reporting send progress. + if (sendProgressBlock) { + [self simulateByteTransferReportWithDataLength:(int64_t)bodyData.length + block:^(int64_t bytesSent, + int64_t totalBytesSent, + int64_t totalBytesExpectedToSend) { + // This is invoked on the callback queue unless stopped. + sendProgressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); + }]; + } + + if (destinationFileURL) { + // Simulate download to file progress. + if (downloadProgressBlock) { + [self simulateByteTransferReportWithDataLength:(int64_t)responseData.length + block:^(int64_t bytesDownloaded, + int64_t totalBytesDownloaded, + int64_t totalBytesExpectedToDownload) { + // This is invoked on the callback queue unless stopped. + downloadProgressBlock(bytesDownloaded, totalBytesDownloaded, + totalBytesExpectedToDownload); + }]; + } + + NSError *writeError; + [responseData writeToURL:destinationFileURL + options:NSDataWritingAtomic + error:&writeError]; + if (writeError) { + // Tell the test code that writing failed. + responseError = writeError; + } + } else { + // Simulate download to NSData progress. + if ((accumulateDataBlock || receivedProgressBlock) && responseData) { + [self simulateByteTransferWithData:responseData + block:^(NSData *data, + int64_t bytesReceived, + int64_t totalBytesReceived, + int64_t totalBytesExpectedToReceive) { + // This is invoked on the callback queue unless stopped. + if (accumulateDataBlock) { + accumulateDataBlock(data); + } + + if (receivedProgressBlock) { + receivedProgressBlock(bytesReceived, totalBytesReceived); + } + }]; + } + + if (!accumulateDataBlock) { + _downloadedData = [responseData mutableCopy]; + } + + if (willCacheURLResponseBlock) { + // Simulate letting the client inspect and alter the cached response. + NSData *cachedData = responseData ?: [[NSData alloc] init]; // Always have non-nil data. + NSCachedURLResponse *cachedResponse = + [[NSCachedURLResponse alloc] initWithResponse:response + data:cachedData]; + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + willCacheURLResponseBlock(cachedResponse, ^(NSCachedURLResponse *responseToCache){ + // The app may provide an alternative response, or nil to defeat caching. + }); + }]; + } + } + _response = response; + } // @synchronized(self) + + NSOperationQueue *queue = self.sessionDelegateQueue; + [queue addOperationWithBlock:^{ + // Rather than invoke failToBeginFetchWithError: we want to simulate completion of + // a connection that started and ended, so we'll call down to finishWithError: + NSInteger status = responseError ? responseError.code : 200; + if (status >= 200 && status <= 399) { + [self finishWithError:nil shouldRetry:NO]; + } else { + [self shouldRetryNowForStatus:status + error:responseError + forceAssumeRetry:NO + response:^(BOOL shouldRetry) { + [self finishWithError:responseError shouldRetry:shouldRetry]; + }]; + } + }]; +} + +- (void)simulateByteTransferWithData:(NSData *)responseData + block:(GTMSessionFetcherSimulateByteTransferBlock)transferBlock { + // This utility method simulates transfering data to the client. It divides the data into at most + // "chunkCount" chunks and then passes each chunk along with a progress update to transferBlock. + // This function can be used with accumulateDataBlock or receivedProgressBlock. + + NSUInteger chunkCount = MAX(self.testBlockAccumulateDataChunkCount, (NSUInteger) 1); + NSUInteger totalDataLength = responseData.length; + NSUInteger sendDataSize = totalDataLength / chunkCount + 1; + NSUInteger totalSent = 0; + while (totalSent < totalDataLength) { + NSUInteger bytesRemaining = totalDataLength - totalSent; + sendDataSize = MIN(sendDataSize, bytesRemaining); + NSData *chunkData = [responseData subdataWithRange:NSMakeRange(totalSent, sendDataSize)]; + totalSent += sendDataSize; + [self invokeOnCallbackQueueUnlessStopped:^{ + transferBlock(chunkData, + (int64_t)sendDataSize, + (int64_t)totalSent, + (int64_t)totalDataLength); + }]; + } +} + +#endif // !GTM_DISABLE_FETCHER_TEST_BLOCK + +- (void)setSessionTask:(NSURLSessionTask *)sessionTask { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_sessionTask != sessionTask) { + _sessionTask = sessionTask; + if (_sessionTask) { + // Request could be nil on restoring this fetcher from a background session. + if (!_request) { + _request = [_sessionTask.originalRequest mutableCopy]; + } + } + } + } // @synchronized(self) +} + +- (NSURLSessionTask * GTM_NULLABLE_TYPE)sessionTask { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _sessionTask; + } // @synchronized(self) +} + ++ (NSUserDefaults *)fetcherUserDefaults { + static NSUserDefaults *gFetcherUserDefaults = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class fetcherUserDefaultsClass = NSClassFromString(@"GTMSessionFetcherUserDefaultsFactory"); + if (fetcherUserDefaultsClass) { + gFetcherUserDefaults = [fetcherUserDefaultsClass fetcherUserDefaults]; + } else { + gFetcherUserDefaults = [NSUserDefaults standardUserDefaults]; + } + }); + return gFetcherUserDefaults; +} + +- (void)addPersistedBackgroundSessionToDefaults { + NSString *sessionIdentifier = self.sessionIdentifier; + if (!sessionIdentifier) { + return; + } + NSArray *oldBackgroundSessions = [[self class] activePersistedBackgroundSessions]; + if ([oldBackgroundSessions containsObject:_sessionIdentifier]) { + return; + } + NSMutableArray *newBackgroundSessions = + [NSMutableArray arrayWithArray:oldBackgroundSessions]; + [newBackgroundSessions addObject:sessionIdentifier]; + GTM_LOG_BACKGROUND_SESSION(@"Add to background sessions: %@", newBackgroundSessions); + + NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; + [userDefaults setObject:newBackgroundSessions + forKey:kGTMSessionFetcherPersistedDestinationKey]; + [userDefaults synchronize]; +} + +- (void)removePersistedBackgroundSessionFromDefaults { + NSString *sessionIdentifier = self.sessionIdentifier; + if (!sessionIdentifier) return; + + NSArray *oldBackgroundSessions = [[self class] activePersistedBackgroundSessions]; + if (!oldBackgroundSessions) { + return; + } + NSMutableArray *newBackgroundSessions = + [NSMutableArray arrayWithArray:oldBackgroundSessions]; + NSUInteger sessionIndex = [newBackgroundSessions indexOfObject:sessionIdentifier]; + if (sessionIndex == NSNotFound) { + return; + } + [newBackgroundSessions removeObjectAtIndex:sessionIndex]; + GTM_LOG_BACKGROUND_SESSION(@"Remove from background sessions: %@", newBackgroundSessions); + + NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; + if (newBackgroundSessions.count == 0) { + [userDefaults removeObjectForKey:kGTMSessionFetcherPersistedDestinationKey]; + } else { + [userDefaults setObject:newBackgroundSessions + forKey:kGTMSessionFetcherPersistedDestinationKey]; + } + [userDefaults synchronize]; +} + ++ (GTM_NULLABLE NSArray *)activePersistedBackgroundSessions { + NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; + NSArray *oldBackgroundSessions = + [userDefaults arrayForKey:kGTMSessionFetcherPersistedDestinationKey]; + if (oldBackgroundSessions.count == 0) { + return nil; + } + NSMutableArray *activeBackgroundSessions = nil; + NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; + for (NSString *sessionIdentifier in oldBackgroundSessions) { + GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; + if (fetcher) { + if (!activeBackgroundSessions) { + activeBackgroundSessions = [[NSMutableArray alloc] init]; + } + [activeBackgroundSessions addObject:sessionIdentifier]; + } + } + return activeBackgroundSessions; +} + ++ (NSArray *)fetchersForBackgroundSessions { + NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults]; + NSArray *backgroundSessions = + [userDefaults arrayForKey:kGTMSessionFetcherPersistedDestinationKey]; + NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap]; + NSMutableArray *fetchers = [NSMutableArray array]; + for (NSString *sessionIdentifier in backgroundSessions) { + GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; + if (!fetcher) { + fetcher = [self fetcherWithSessionIdentifier:sessionIdentifier]; + GTMSESSION_ASSERT_DEBUG(fetcher != nil, + @"Unexpected invalid session identifier: %@", sessionIdentifier); + [fetcher beginFetchWithCompletionHandler:nil]; + } + GTM_LOG_BACKGROUND_SESSION(@"%@ restoring session %@ by creating fetcher %@ %p", + [self class], sessionIdentifier, fetcher, fetcher); + if (fetcher != nil) { + [fetchers addObject:fetcher]; + } + } + return fetchers; +} + +#if TARGET_OS_IPHONE && !TARGET_OS_WATCH ++ (void)application:(UIApplication *)application + handleEventsForBackgroundURLSession:(NSString *)identifier + completionHandler:(GTMSessionFetcherSystemCompletionHandler)completionHandler { + GTMSessionFetcher *fetcher = [self fetcherWithSessionIdentifier:identifier]; + if (fetcher != nil) { + fetcher.systemCompletionHandler = completionHandler; + } else { + GTM_LOG_BACKGROUND_SESSION(@"%@ did not create background session identifier: %@", + [self class], identifier); + } +} +#endif + +- (NSString * GTM_NULLABLE_TYPE)sessionIdentifier { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _sessionIdentifier; + } // @synchronized(self) +} + +- (void)setSessionIdentifier:(NSString *)sessionIdentifier { + GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(!_session, @"Unable to set session identifier after session created"); + _sessionIdentifier = [sessionIdentifier copy]; + _usingBackgroundSession = YES; + _canShareSession = NO; + [self restoreDefaultStateForSessionIdentifierMetadata]; + } // @synchronized(self) +} + +- (void)setSessionIdentifierInternal:(GTM_NULLABLE NSString *)sessionIdentifier { + // This internal method only does a synchronized set of the session identifier. + // It does not have side effects on the background session, shared session, or + // session identifier metadata. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _sessionIdentifier = [sessionIdentifier copy]; + } // @synchronized(self) +} + +- (NSDictionary * GTM_NULLABLE_TYPE)sessionUserInfo { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_sessionUserInfo == nil) { + // We'll return the metadata dictionary with internal keys removed. This avoids the user + // re-using the userInfo dictionary later and accidentally including the internal keys. + NSMutableDictionary *metadata = [[self sessionIdentifierMetadataUnsynchronized] mutableCopy]; + NSSet *keysToRemove = [metadata keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { + return [key hasPrefix:@"_"]; + }]; + [metadata removeObjectsForKeys:[keysToRemove allObjects]]; + if (metadata.count > 0) { + _sessionUserInfo = metadata; + } + } + return _sessionUserInfo; + } // @synchronized(self) +} + +- (void)setSessionUserInfo:(NSDictionary * GTM_NULLABLE_TYPE)dictionary { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(_sessionIdentifier == nil, @"Too late to assign userInfo"); + _sessionUserInfo = dictionary; + } // @synchronized(self) +} + +- (GTM_NULLABLE NSDictionary *)sessionIdentifierDefaultMetadata { + GTMSessionCheckSynchronized(self); + + NSMutableDictionary *defaultUserInfo = [[NSMutableDictionary alloc] init]; + if (_destinationFileURL) { + defaultUserInfo[kGTMSessionIdentifierDestinationFileURLMetadataKey] = + [_destinationFileURL absoluteString]; + } + if (_bodyFileURL) { + defaultUserInfo[kGTMSessionIdentifierBodyFileURLMetadataKey] = [_bodyFileURL absoluteString]; + } + return (defaultUserInfo.count > 0) ? defaultUserInfo : nil; +} + +- (void)restoreDefaultStateForSessionIdentifierMetadata { + GTMSessionCheckSynchronized(self); + + NSDictionary *metadata = [self sessionIdentifierMetadataUnsynchronized]; + NSString *destinationFileURLString = metadata[kGTMSessionIdentifierDestinationFileURLMetadataKey]; + if (destinationFileURLString) { + _destinationFileURL = [NSURL URLWithString:destinationFileURLString]; + GTM_LOG_BACKGROUND_SESSION(@"Restoring destination file URL: %@", _destinationFileURL); + } + NSString *bodyFileURLString = metadata[kGTMSessionIdentifierBodyFileURLMetadataKey]; + if (bodyFileURLString) { + _bodyFileURL = [NSURL URLWithString:bodyFileURLString]; + GTM_LOG_BACKGROUND_SESSION(@"Restoring body file URL: %@", _bodyFileURL); + } +} + +- (NSDictionary * GTM_NULLABLE_TYPE)sessionIdentifierMetadata { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [self sessionIdentifierMetadataUnsynchronized]; + } +} + +- (NSDictionary * GTM_NULLABLE_TYPE)sessionIdentifierMetadataUnsynchronized { + GTMSessionCheckSynchronized(self); + + // Session Identifier format: "com.google.__ + if (!_sessionIdentifier) { + return nil; + } + NSScanner *metadataScanner = [NSScanner scannerWithString:_sessionIdentifier]; + [metadataScanner setCharactersToBeSkipped:nil]; + NSString *metadataString; + NSString *uuid; + if ([metadataScanner scanUpToString:@"_" intoString:NULL] && + [metadataScanner scanString:@"_" intoString:NULL] && + [metadataScanner scanUpToString:@"_" intoString:&uuid] && + [metadataScanner scanString:@"_" intoString:NULL] && + [metadataScanner scanUpToString:@"\n" intoString:&metadataString]) { + _sessionIdentifierUUID = uuid; + NSData *metadataData = [metadataString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *metadataDict = + [NSJSONSerialization JSONObjectWithData:metadataData + options:0 + error:&error]; + GTM_LOG_BACKGROUND_SESSION(@"User Info from session identifier: %@ %@", + metadataDict, error ? error : @""); + return metadataDict; + } + return nil; +} + +- (NSString *)createSessionIdentifierWithMetadata:(NSDictionary * GTM_NULLABLE_TYPE)metadataToInclude { + NSString *result; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Session Identifier format: "com.google.__ + GTMSESSION_ASSERT_DEBUG(!_sessionIdentifier, @"Session identifier already created"); + _sessionIdentifierUUID = [[NSUUID UUID] UUIDString]; + _sessionIdentifier = + [NSString stringWithFormat:@"%@_%@", kGTMSessionIdentifierPrefix, _sessionIdentifierUUID]; + // Start with user-supplied keys so they cannot accidentally override the fetcher's keys. + NSMutableDictionary *metadataDict = + [NSMutableDictionary dictionaryWithDictionary:(NSDictionary * GTM_NONNULL_TYPE)_sessionUserInfo]; + + if (metadataToInclude) { + [metadataDict addEntriesFromDictionary:(NSDictionary *)metadataToInclude]; + } + NSDictionary *defaultMetadataDict = [self sessionIdentifierDefaultMetadata]; + if (defaultMetadataDict) { + [metadataDict addEntriesFromDictionary:defaultMetadataDict]; + } + if (metadataDict.count > 0) { + NSData *metadataData = [NSJSONSerialization dataWithJSONObject:metadataDict + options:0 + error:NULL]; + GTMSESSION_ASSERT_DEBUG(metadataData != nil, + @"Session identifier user info failed to convert to JSON"); + if (metadataData.length > 0) { + NSString *metadataString = [[NSString alloc] initWithData:metadataData + encoding:NSUTF8StringEncoding]; + _sessionIdentifier = + [_sessionIdentifier stringByAppendingFormat:@"_%@", metadataString]; + } + } + _didCreateSessionIdentifier = YES; + result = _sessionIdentifier; + } // @synchronized(self) + return result; +} + +- (void)failToBeginFetchWithError:(NSError *)error { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _hasStoppedFetching = YES; + } + + if (error == nil) { + error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain + code:GTMSessionFetcherErrorDownloadFailed + userInfo:nil]; + } + + [self invokeFetchCallbacksOnCallbackQueueWithData:nil + error:error]; + [self releaseCallbacks]; + + [_service fetcherDidStop:self]; + + self.authorizer = nil; +} + ++ (GTMSessionCookieStorage *)staticCookieStorage { + static GTMSessionCookieStorage *gCookieStorage = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gCookieStorage = [[GTMSessionCookieStorage alloc] init]; + }); + return gCookieStorage; +} + +#if GTM_BACKGROUND_TASK_FETCHING + +- (void)endBackgroundTask { + // Whenever the connection stops or background execution expires, + // we need to tell UIApplication we're done. + UIBackgroundTaskIdentifier bgTaskID; + @synchronized(self) { + bgTaskID = self.backgroundTaskIdentifier; + if (bgTaskID != UIBackgroundTaskInvalid) { + self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; + } + } + + if (bgTaskID != UIBackgroundTaskInvalid) { + id app = [[self class] fetcherUIApplication]; + [app endBackgroundTask:bgTaskID]; + } +} + +#endif // GTM_BACKGROUND_TASK_FETCHING + +- (void)authorizeRequest { + GTMSessionCheckNotSynchronized(self); + + id authorizer = self.authorizer; + SEL asyncAuthSel = @selector(authorizeRequest:delegate:didFinishSelector:); + if ([authorizer respondsToSelector:asyncAuthSel]) { + SEL callbackSel = @selector(authorizer:request:finishedWithError:); + NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; + [authorizer authorizeRequest:mutableRequest + delegate:self + didFinishSelector:callbackSel]; + } else { + GTMSESSION_ASSERT_DEBUG(authorizer == nil, @"invalid authorizer for fetch"); + + // No authorizing possible, and authorizing happens only after any delay; + // just begin fetching + [self beginFetchMayDelay:NO + mayAuthorize:NO]; + } +} + +- (void)authorizer:(id)auth + request:(NSMutableURLRequest *)authorizedRequest + finishedWithError:(NSError *)error { + GTMSessionCheckNotSynchronized(self); + + if (error != nil) { + // We can't fetch without authorization + [self failToBeginFetchWithError:error]; + } else { + @synchronized(self) { + _request = authorizedRequest; + } + [self beginFetchMayDelay:NO + mayAuthorize:NO]; + } +} + + +- (BOOL)canFetchWithBackgroundSession { + // Subclasses may override. + return YES; +} + +// Returns YES if the fetcher has been started and has not yet stopped. +// +// Fetching includes waiting for authorization or for retry, waiting to be allowed by the +// service object to start the request, and actually fetching the request. +- (BOOL)isFetching { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [self isFetchingUnsynchronized]; + } +} + +- (BOOL)isFetchingUnsynchronized { + GTMSessionCheckSynchronized(self); + + BOOL hasBegun = (_initialBeginFetchDate != nil); + return hasBegun && !_hasStoppedFetching; +} + +- (NSURLResponse * GTM_NULLABLE_TYPE)response { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURLResponse *response = [self responseUnsynchronized]; + return response; + } // @synchronized(self) +} + +- (NSURLResponse * GTM_NULLABLE_TYPE)responseUnsynchronized { + GTMSessionCheckSynchronized(self); + + NSURLResponse *response = _sessionTask.response; + if (!response) response = _response; + return response; +} + +- (NSInteger)statusCode { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSInteger statusCode = [self statusCodeUnsynchronized]; + return statusCode; + } // @synchronized(self) +} + +- (NSInteger)statusCodeUnsynchronized { + GTMSessionCheckSynchronized(self); + + NSURLResponse *response = [self responseUnsynchronized]; + NSInteger statusCode; + + if ([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 * GTM_NULLABLE_TYPE)responseHeaders { + GTMSessionCheckNotSynchronized(self); + + NSURLResponse *response = self.response; + if ([response respondsToSelector:@selector(allHeaderFields)]) { + NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields]; + return headers; + } + return nil; +} + +- (NSDictionary * GTM_NULLABLE_TYPE)responseHeadersUnsynchronized { + GTMSessionCheckSynchronized(self); + + NSURLResponse *response = [self responseUnsynchronized]; + if ([response respondsToSelector:@selector(allHeaderFields)]) { + NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields]; + return headers; + } + return nil; +} + +- (void)releaseCallbacks { + // Avoid releasing blocks in the sync section since objects dealloc'd by + // the blocks being released may call back into the fetcher or fetcher + // service. + dispatch_queue_t NS_VALID_UNTIL_END_OF_SCOPE holdCallbackQueue; + GTMSessionFetcherCompletionHandler NS_VALID_UNTIL_END_OF_SCOPE holdCompletionHandler; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + holdCallbackQueue = _callbackQueue; + holdCompletionHandler = _completionHandler; + + _callbackQueue = nil; + _completionHandler = nil; // Setter overridden in upload. Setter assumed to be used externally. + } + + // Set local callback pointers to nil here rather than let them release at the end of the scope + // to make any problems due to the blocks being released be a bit more obvious in a stack trace. + holdCallbackQueue = nil; + holdCompletionHandler = nil; + + self.configurationBlock = nil; + self.didReceiveResponseBlock = nil; + self.challengeBlock = nil; + self.willRedirectBlock = nil; + self.sendProgressBlock = nil; + self.receivedProgressBlock = nil; + self.downloadProgressBlock = nil; + self.accumulateDataBlock = nil; + self.willCacheURLResponseBlock = nil; + self.retryBlock = nil; + self.testBlock = nil; + self.resumeDataBlock = nil; + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) { + self.metricsCollectionBlock = nil; + } +} + +- (void)forgetSessionIdentifierForFetcher { + GTMSessionCheckSynchronized(self); + [self forgetSessionIdentifierForFetcherWithoutSyncCheck]; +} + +- (void)forgetSessionIdentifierForFetcherWithoutSyncCheck { + // This should be called inside a @synchronized block (except during dealloc.) + if (_sessionIdentifier) { + NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIdentifierToFetcherMap]; + [sessionIdentifierToFetcherMap removeObjectForKey:_sessionIdentifier]; + _sessionIdentifier = nil; + _didCreateSessionIdentifier = NO; + } +} + +// External stop method +- (void)stopFetching { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Prevent enqueued callbacks from executing. + _userStoppedFetching = YES; + } // @synchronized(self) + [self stopFetchReleasingCallbacks:YES]; +} + +// Cancel the fetch of the URL that's currently in progress. +// +// If shouldReleaseCallbacks is NO then the fetch will be retried so the callbacks +// need to still be retained. +- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks { + [self removePersistedBackgroundSessionFromDefaults]; + + id service; + NSMutableURLRequest *request; + + // If the task 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. + __autoreleasing GTMSessionFetcher *holdSelf = self; + + BOOL hasCanceledTask = NO; + + [holdSelf destroyRetryTimer]; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _hasStoppedFetching = YES; + + service = _service; + request = _request; + + if (_sessionTask) { + // In case cancelling the task or session calls this recursively, we want + // to ensure that we'll only release the task and delegate once, + // so first set _sessionTask to nil + // + // This may be called in a callback from the task, so use autorelease to avoid + // releasing the task in its own callback. + __autoreleasing NSURLSessionTask *oldTask = _sessionTask; + if (!_isUsingTestBlock) { + _response = _sessionTask.response; + } + _sessionTask = nil; + + if ([oldTask state] != NSURLSessionTaskStateCompleted) { + // For download tasks, when the fetch is stopped, we may provide resume data that can + // be used to create a new session. + BOOL mayResume = (_resumeDataBlock + && [oldTask respondsToSelector:@selector(cancelByProducingResumeData:)]); + if (!mayResume) { + [oldTask cancel]; + // A side effect of stopping the task is that URLSession:task:didCompleteWithError: + // will be invoked asynchronously on the delegate queue. + } else { + void (^resumeBlock)(NSData *) = _resumeDataBlock; + _resumeDataBlock = nil; + + // Save callbackQueue since releaseCallbacks clears it. + dispatch_queue_t callbackQueue = _callbackQueue; + dispatch_group_enter(_callbackGroup); + [(NSURLSessionDownloadTask *)oldTask cancelByProducingResumeData:^(NSData *resumeData) { + [self invokeOnCallbackQueue:callbackQueue + afterUserStopped:YES + block:^{ + resumeBlock(resumeData); + dispatch_group_leave(self->_callbackGroup); + }]; + }]; + } + hasCanceledTask = YES; + } + } + + // If the task was canceled, wait until the URLSession:task:didCompleteWithError: to call + // finishTasksAndInvalidate, since calling it immediately tends to crash, see radar 18471901. + if (_session) { + BOOL shouldInvalidate = _shouldInvalidateSession; +#if TARGET_OS_IPHONE + // Don't invalidate if we've got a systemCompletionHandler, since + // URLSessionDidFinishEventsForBackgroundURLSession: won't be called if invalidated. + shouldInvalidate = shouldInvalidate && !self.systemCompletionHandler; +#endif + if (shouldInvalidate) { + __autoreleasing NSURLSession *oldSession = _session; + _session = nil; + + if (!hasCanceledTask) { + [oldSession finishTasksAndInvalidate]; + } else { + _sessionNeedingInvalidation = oldSession; + } + } + } + } // @synchronized(self) + + // send the stopped notification + [self sendStopNotificationIfNeeded]; + + [_authorizer stopAuthorizationForRequest:request]; + + if (shouldReleaseCallbacks) { + [self releaseCallbacks]; + + self.authorizer = nil; + } + + [service fetcherDidStop:self]; + +#if GTM_BACKGROUND_TASK_FETCHING + [self endBackgroundTask]; +#endif +} + +- (void)setStopNotificationNeeded:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _isStopNotificationNeeded = flag; + } // @synchronized(self) +} + +- (void)sendStopNotificationIfNeeded { + BOOL sendNow = NO; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_isStopNotificationNeeded) { + _isStopNotificationNeeded = NO; + sendNow = YES; + } + } // @synchronized(self) + + if (sendNow) { + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification + userInfo:nil + requireAsync:NO]; + } +} + +- (void)retryFetch { + [self stopFetchReleasingCallbacks:NO]; + + // A retry will need a configuration with a fresh session identifier. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_sessionIdentifier && _didCreateSessionIdentifier) { + [self forgetSessionIdentifierForFetcher]; + _configuration = nil; + } + + if (_canShareSession) { + // Force a grab of the current session from the fetcher service in case + // the service's old one has become invalid. + _session = nil; + } + } // @synchronized(self) + + [self beginFetchForRetry]; +} + +- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { + // Uncovered in upload fetcher testing, because the chunk fetcher is being waited on, and gets + // released by the upload code. The uploader just holds onto it with an ivar, and that gets + // nilled in the chunk fetcher callback. + // Used once in while loop just to avoid unused variable compiler warning. + __autoreleasing GTMSessionFetcher *holdSelf = self; + + NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + + BOOL shouldSpinRunLoop = ([NSThread isMainThread] && + (!self.callbackQueue + || self.callbackQueue == dispatch_get_main_queue())); + BOOL expired = NO; + + // Loop until the callbacks have been called and released, and until + // the connection is no longer pending, until there are no callback dispatches + // in flight, or until the timeout has expired. + int64_t delta = (int64_t)(100 * NSEC_PER_MSEC); // 100 ms + while (1) { + BOOL isTaskInProgress = (holdSelf->_sessionTask + && [_sessionTask state] != NSURLSessionTaskStateCompleted); + BOOL needsToCallCompletion = (_completionHandler != nil); + BOOL isCallbackInProgress = (_callbackGroup + && dispatch_group_wait(_callbackGroup, dispatch_time(DISPATCH_TIME_NOW, delta))); + + if (!isTaskInProgress && !needsToCallCompletion && !isCallbackInProgress) break; + + expired = ([giveUpDate timeIntervalSinceNow] < 0); + if (expired) { + GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher waitForCompletionWithTimeout:%0.1f expired -- " + @"%@%@%@", timeoutInSeconds, + isTaskInProgress ? @"taskInProgress " : @"", + needsToCallCompletion ? @"needsToCallCompletion " : @"", + isCallbackInProgress ? @"isCallbackInProgress" : @""); + break; + } + + // Run the current run loop 1/1000 of a second to give the networking + // code a chance to work + const NSTimeInterval kSpinInterval = 0.001; + if (shouldSpinRunLoop) { + NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; + } else { + [NSThread sleepForTimeInterval:kSpinInterval]; + } + } + return !expired; +} + ++ (void)setGlobalTestBlock:(GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE)block { +#if GTM_DISABLE_FETCHER_TEST_BLOCK + GTMSESSION_ASSERT_DEBUG(block == nil, @"test blocks disabled"); +#endif + gGlobalTestBlock = [block copy]; +} + +#if GTM_BACKGROUND_TASK_FETCHING + +static GTM_NULLABLE_TYPE id gSubstituteUIApp; + ++ (void)setSubstituteUIApplication:(nullable id)app { + gSubstituteUIApp = app; +} + ++ (nullable id)substituteUIApplication { + return gSubstituteUIApp; +} + ++ (nullable id)fetcherUIApplication { + id app = gSubstituteUIApp; + if (app) return app; + + // iOS App extensions should not call [UIApplication sharedApplication], even + // if UIApplication responds to it. + + static Class applicationClass = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + BOOL isAppExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; + if (!isAppExtension) { + Class cls = NSClassFromString(@"UIApplication"); + if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { + applicationClass = cls; + } + } + }); + + if (applicationClass) { + app = (id)[applicationClass sharedApplication]; + } + return app; +} +#endif // GTM_BACKGROUND_TASK_FETCHING + +#pragma mark NSURLSession Delegate Methods + +// NSURLSession documentation indicates that redirectRequest can be passed to the handler +// but empirically redirectRequest lacks the HTTP body, so passing it will break POSTs. +// Instead, we construct a new request, a copy of the original, with overrides from the +// redirect. + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +willPerformHTTPRedirection:(NSHTTPURLResponse *)redirectResponse + newRequest:(NSURLRequest *)redirectRequest + completionHandler:(void (^)(NSURLRequest * GTM_NULLABLE_TYPE))handler { + [self setSessionTask:task]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ willPerformHTTPRedirection:%@ newRequest:%@", + [self class], self, session, task, redirectResponse, redirectRequest); + + if ([self userStoppedFetching]) { + handler(nil); + return; + } + if (redirectRequest && redirectResponse) { + // Copy the original request, including the body. + NSURLRequest *originalRequest = self.request; + NSMutableURLRequest *newRequest = [originalRequest mutableCopy]; + + // The new requests's URL overrides the original's URL. + [newRequest setURL:[GTMSessionFetcher redirectURLWithOriginalRequestURL:originalRequest.URL + redirectRequestURL:redirectRequest.URL]]; + + // 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]; + } + + redirectRequest = newRequest; + + // Log the response we just received + [self setResponse:redirectResponse]; + [self logNowWithError:nil]; + + GTMSessionFetcherWillRedirectBlock willRedirectBlock = self.willRedirectBlock; + if (willRedirectBlock) { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + [self invokeOnCallbackQueueAfterUserStopped:YES + block:^{ + willRedirectBlock(redirectResponse, redirectRequest, ^(NSURLRequest *clientRequest) { + + // Update the request for future logging. + [self updateMutableRequest:[clientRequest mutableCopy]]; + + handler(clientRequest); + }); + }]; + } // @synchronized(self) + return; + } + // Continues here if the client did not provide a redirect block. + + // Update the request for future logging. + [self updateMutableRequest:[redirectRequest mutableCopy]]; + } + handler(redirectRequest); +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))handler { + [self setSessionTask:dataTask]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveResponse:%@", + [self class], self, session, dataTask, response); + void (^accumulateAndFinish)(NSURLSessionResponseDisposition) = + ^(NSURLSessionResponseDisposition dispositionValue) { + // 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. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + BOOL hadPreviousData = self->_downloadedLength > 0; + + [self->_downloadedData setLength:0]; + self->_downloadedLength = 0; + + if (hadPreviousData && (dispositionValue != NSURLSessionResponseCancel)) { + // Tell the accumulate block to discard prior data. + GTMSessionFetcherAccumulateDataBlock accumulateBlock = self->_accumulateDataBlock; + if (accumulateBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + accumulateBlock(nil); + }]; + } + } + } // @synchronized(self) + handler(dispositionValue); + }; + + GTMSessionFetcherDidReceiveResponseBlock receivedResponseBlock; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + receivedResponseBlock = _didReceiveResponseBlock; + if (receivedResponseBlock) { + // We will ultimately need to call back to NSURLSession's handler with the disposition value + // for this delegate method even if the user has stopped the fetcher. + [self invokeOnCallbackQueueAfterUserStopped:YES + block:^{ + receivedResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) { + accumulateAndFinish(desiredDisposition); + }); + }]; + } + } // @synchronized(self) + + if (receivedResponseBlock == nil) { + accumulateAndFinish(NSURLSessionResponseAllow); + } +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didBecomeDownloadTask:%@", + [self class], self, session, dataTask, downloadTask); + [self setSessionTask:downloadTask]; +} + + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * GTM_NULLABLE_TYPE credential))handler { + [self setSessionTask:task]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didReceiveChallenge:%@", + [self class], self, session, task, challenge); + + GTMSessionFetcherChallengeBlock challengeBlock = self.challengeBlock; + if (challengeBlock) { + // The fetcher user has provided custom challenge handling. + // + // We will ultimately need to call back to NSURLSession's handler with the disposition value + // for this delegate method even if the user has stopped the fetcher. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self invokeOnCallbackQueueAfterUserStopped:YES + block:^{ + challengeBlock(self, challenge, handler); + }]; + } + } else { + // No challenge block was provided by the client. + [self respondToChallenge:challenge + completionHandler:handler]; + } +} + +- (void)respondToChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential * GTM_NULLABLE_TYPE credential))handler { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSInteger previousFailureCount = [challenge previousFailureCount]; + if (previousFailureCount <= 2) { + NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; + NSString *authenticationMethod = [protectionSpace authenticationMethod]; + if ([authenticationMethod isEqual:NSURLAuthenticationMethodServerTrust]) { + // SSL. + // + // Background sessions seem to require an explicit check of the server trust object + // rather than default handling. + SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; + if (serverTrust == NULL) { + // No server trust information is available. + handler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + } else { + // Server trust information is available. + void (^callback)(SecTrustRef, BOOL) = ^(SecTrustRef trustRef, BOOL allow){ + if (allow) { + NSURLCredential *trustCredential = [NSURLCredential credentialForTrust:trustRef]; + handler(NSURLSessionAuthChallengeUseCredential, trustCredential); + } else { + GTMSESSION_LOG_DEBUG(@"Cancelling authentication challenge for %@", self->_request.URL); + handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + } + }; + if (_allowInvalidServerCertificates) { + callback(serverTrust, YES); + } else { + [[self class] evaluateServerTrust:serverTrust + forRequest:_request + completionHandler:callback]; + } + } + return; + } + + NSURLCredential *credential = _credential; + + if ([[challenge protectionSpace] isProxy] && _proxyCredential != nil) { + credential = _proxyCredential; + } + + if (credential) { + handler(NSURLSessionAuthChallengeUseCredential, credential); + } else { + // The credential is still nil; tell the OS to use the default handling. This is needed + // for things that can come out of the keychain (proxies, client certificates, etc.). + // + // Note: Looking up a credential with NSURLCredentialStorage's + // defaultCredentialForProtectionSpace: is *not* the same invoking the handler with + // NSURLSessionAuthChallengePerformDefaultHandling. In the case of + // NSURLAuthenticationMethodClientCertificate, you can get nil back from + // NSURLCredentialStorage, while using this code path instead works. + handler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + } + + } else { + // We've failed auth 3 times. The completion handler will be called with code + // NSURLErrorCancelled. + handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + } + } // @synchronized(self) +} + +// Return redirect URL based on the original request URL and redirect request URL. +// +// Method disallows any scheme changes between the original request URL and redirect request URL +// aside from "http" to "https". If a change in scheme is detected the redirect URL inherits the +// scheme from the original request URL. ++ (GTM_NULLABLE NSURL *)redirectURLWithOriginalRequestURL:(GTM_NULLABLE NSURL *)originalRequestURL + redirectRequestURL:(GTM_NULLABLE NSURL *)redirectRequestURL { + // In the case of an NSURLSession redirect, neither URL should ever be nil; as a sanity check + // if either is nil return the other URL. + if (!redirectRequestURL) return originalRequestURL; + if (!originalRequestURL) return redirectRequestURL; + + NSString *originalScheme = originalRequestURL.scheme; + NSString *redirectScheme = redirectRequestURL.scheme; + BOOL insecureToSecureRedirect = + (originalScheme != nil && [originalScheme caseInsensitiveCompare:@"http"] == NSOrderedSame && + redirectScheme != nil && [redirectScheme caseInsensitiveCompare:@"https"] == NSOrderedSame); + + // This can't really be nil for the inputs, but to keep the analyzer happy + // for the -caseInsensitiveCompare: call below, give it a value if it were. + if (!originalScheme) originalScheme = @"https"; + + // Check for changes to the scheme and disallow any changes except for http to https. + if (!insecureToSecureRedirect && + (redirectScheme.length != originalScheme.length || + [redirectScheme caseInsensitiveCompare:originalScheme] != NSOrderedSame)) { + NSURLComponents *components = + [NSURLComponents componentsWithURL:(NSURL * _Nonnull)redirectRequestURL + resolvingAgainstBaseURL:NO]; + components.scheme = originalScheme; + return components.URL; + } + + return redirectRequestURL; +} + +// Validate the certificate chain. +// +// This may become a public method if it appears to be useful to users. ++ (void)evaluateServerTrust:(SecTrustRef)serverTrust + forRequest:(NSURLRequest *)request + completionHandler:(void (^)(SecTrustRef trustRef, BOOL allow))handler { + // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7. + CFRetain(serverTrust); + + // Evaluate the certificate chain. + // + // The delegate queue may be the main thread. Trust evaluation could cause some + // blocking network activity, so we must evaluate async, as documented at + // https://developer.apple.com/library/ios/technotes/tn2232/ + // + // We must also avoid multiple uses of the trust object, per docs: + // "It is not safe to call this function concurrently with any other function that uses + // the same trust management object, or to re-enter this function for the same trust + // management object." + // + // SecTrustEvaluateAsync both does sync execution of Evaluate and calls back on the + // queue passed to it, according to at sources in + // http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55050.9/lib/SecTrust.cpp + // It would require a global serial queue to ensure the evaluate happens only on a + // single thread at a time, so we'll stick with using SecTrustEvaluate on a background + // thread. + dispatch_queue_t evaluateBackgroundQueue = + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(evaluateBackgroundQueue, ^{ + // It looks like the implementation of SecTrustEvaluate() on Mac grabs a global lock, + // so it may be redundant for us to also lock, but it's easy to synchronize here + // anyway. + BOOL shouldAllow; +#if GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR + CFErrorRef errorRef = NULL; + @synchronized ([GTMSessionFetcher class]) { + GTMSessionMonitorSynchronized([GTMSessionFetcher class]); + + // SecTrustEvaluateWithError handles both the "proceed" and "unspecified" cases, + // so it is not necessary to check the trust result the evaluation returns true. + shouldAllow = SecTrustEvaluateWithError(serverTrust, &errorRef); + } + + if (errorRef) { + GTMSESSION_LOG_DEBUG(@"Error %d evaluating trust for %@", + (int)CFErrorGetCode(errorRef), request); + CFRelease(errorRef); + } +#else + SecTrustResultType trustEval = kSecTrustResultInvalid; + OSStatus trustError; + @synchronized([GTMSessionFetcher class]) { + GTMSessionMonitorSynchronized([GTMSessionFetcher class]); + + trustError = SecTrustEvaluate(serverTrust, &trustEval); + } + if (trustError != errSecSuccess) { + GTMSESSION_LOG_DEBUG(@"Error %d evaluating trust for %@", + (int)trustError, request); + shouldAllow = NO; + } else { + // Having a trust level "unspecified" by the user is the usual result, described at + // https://developer.apple.com/library/mac/qa/qa1360 + if (trustEval == kSecTrustResultUnspecified + || trustEval == kSecTrustResultProceed) { + shouldAllow = YES; + } else { + shouldAllow = NO; + GTMSESSION_LOG_DEBUG(@"Challenge SecTrustResultType %u for %@, properties: %@", + trustEval, request.URL.host, + CFBridgingRelease(SecTrustCopyProperties(serverTrust))); + } + } +#endif // GTM_SDK_REQUIRES_SECTRUSTEVALUATEWITHERROR + handler(serverTrust, shouldAllow); + + CFRelease(serverTrust); + }); +} + +- (void)invokeOnCallbackQueueUnlessStopped:(void (^)(void))block { + [self invokeOnCallbackQueueAfterUserStopped:NO + block:block]; +} + +- (void)invokeOnCallbackQueueAfterUserStopped:(BOOL)afterStopped + block:(void (^)(void))block { + GTMSessionCheckSynchronized(self); + + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:afterStopped + block:block]; +} + +- (void)invokeOnCallbackUnsynchronizedQueueAfterUserStopped:(BOOL)afterStopped + block:(void (^)(void))block { + // testBlock simulation code may not be synchronizing when this is invoked. + [self invokeOnCallbackQueue:_callbackQueue + afterUserStopped:afterStopped + block:block]; +} + +- (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue + afterUserStopped:(BOOL)afterStopped + block:(void (^)(void))block { + if (callbackQueue) { + dispatch_group_async(_callbackGroup, callbackQueue, ^{ + if (!afterStopped) { + NSDate *serviceStoppedAllDate = [self->_service stoppedAllFetchersDate]; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Avoid a race between stopFetching and the callback. + if (self->_userStoppedFetching) { + return; + } + + // Also avoid calling back if the service has stopped all fetchers + // since this one was created. The fetcher may have stopped before + // stopAllFetchers was invoked, so _userStoppedFetching wasn't set, + // but the app still won't expect the callback to fire after + // the service's stopAllFetchers was invoked. + if (serviceStoppedAllDate + && [self->_initialBeginFetchDate compare:serviceStoppedAllDate] != NSOrderedDescending) { + // stopAllFetchers was called after this fetcher began. + return; + } + } // @synchronized(self) + } + block(); + }); + } +} + +- (void)invokeFetchCallbacksOnCallbackQueueWithData:(GTM_NULLABLE NSData *)data + error:(GTM_NULLABLE NSError *)error { + // Callbacks will be released in the method stopFetchReleasingCallbacks: + GTMSessionFetcherCompletionHandler handler; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + handler = _completionHandler; + + if (handler) { + [self invokeOnCallbackQueueUnlessStopped:^{ + handler(data, error); + + // Post a notification, primarily to allow code to collect responses for + // testing. + // + // The observing code is not likely on the fetcher's callback + // queue, so this posts explicitly to the main queue. + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + if (data) { + userInfo[kGTMSessionFetcherCompletionDataKey] = data; + } + if (error) { + userInfo[kGTMSessionFetcherCompletionErrorKey] = error; + } + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherCompletionInvokedNotification + userInfo:userInfo + requireAsync:NO]; + }]; + } + } // @synchronized(self) +} + +- (void)postNotificationOnMainThreadWithName:(NSString *)noteName + userInfo:(GTM_NULLABLE NSDictionary *)userInfo + requireAsync:(BOOL)requireAsync { + dispatch_block_t postBlock = ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:noteName + object:self + userInfo:userInfo]; + }; + + if ([NSThread isMainThread] && !requireAsync) { + // Post synchronously for compatibility with older code using the fetcher. + + // Avoid calling out to other code from inside a sync block to avoid risk + // of a deadlock or of recursive sync. + GTMSessionCheckNotSynchronized(self); + + postBlock(); + } else { + dispatch_async(dispatch_get_main_queue(), postBlock); + } +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)uploadTask + needNewBodyStream:(void (^)(NSInputStream * GTM_NULLABLE_TYPE bodyStream))completionHandler { + [self setSessionTask:uploadTask]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ needNewBodyStream:", + [self class], self, session, uploadTask); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSessionFetcherBodyStreamProvider provider = _bodyStreamProvider; +#if !STRIP_GTM_FETCH_LOGGING + if ([self respondsToSelector:@selector(loggedStreamProviderForStreamProvider:)]) { + provider = [self performSelector:@selector(loggedStreamProviderForStreamProvider:) + withObject:provider]; + } +#endif + if (provider) { + [self invokeOnCallbackQueueUnlessStopped:^{ + provider(completionHandler); + }]; + } else { + GTMSESSION_ASSERT_DEBUG(NO, @"NSURLSession expects a stream provider"); + + completionHandler(nil); + } + } // @synchronized(self) +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent +totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + [self setSessionTask:task]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didSendBodyData:%lld" + @" totalBytesSent:%lld totalBytesExpectedToSend:%lld", + [self class], self, session, task, bytesSent, totalBytesSent, + totalBytesExpectedToSend); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (!_sendProgressBlock) { + return; + } + // We won't hold on to send progress block; it's ok to not send it if the upload finishes. + [self invokeOnCallbackQueueUnlessStopped:^{ + GTMSessionFetcherSendProgressBlock progressBlock; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + progressBlock = self->_sendProgressBlock; + } + if (progressBlock) { + progressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); + } + }]; + } // @synchronized(self) +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + [self setSessionTask:dataTask]; + NSUInteger bufferLength = data.length; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveData:%p (%llu bytes)", + [self class], self, session, dataTask, data, + (unsigned long long)bufferLength); + if (bufferLength == 0) { + // Observed on completing an out-of-process upload. + return; + } + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSessionFetcherAccumulateDataBlock accumulateBlock = _accumulateDataBlock; + if (accumulateBlock) { + // Let the client accumulate the data. + _downloadedLength += bufferLength; + [self invokeOnCallbackQueueUnlessStopped:^{ + accumulateBlock(data); + }]; + } else if (!_userStoppedFetching) { + // Append to the mutable data buffer unless the fetch has been cancelled. + + // Resumed upload tasks may not yet have a data buffer. + if (_downloadedData == nil) { + // Using NSClassFromString for iOS 6 compatibility. + GTMSESSION_ASSERT_DEBUG( + ![dataTask isKindOfClass:NSClassFromString(@"NSURLSessionDownloadTask")], + @"Resumed download tasks should not receive data bytes"); + _downloadedData = [[NSMutableData alloc] init]; + } + + [_downloadedData appendData:data]; + _downloadedLength = (int64_t)_downloadedData.length; + + // We won't hold on to receivedProgressBlock here; it's ok to not send + // it if the transfer finishes. + if (_receivedProgressBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + GTMSessionFetcherReceivedProgressBlock progressBlock; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + progressBlock = self->_receivedProgressBlock; + } + if (progressBlock) { + progressBlock((int64_t)bufferLength, self->_downloadedLength); + } + }]; + } + } + } // @synchronized(self) +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + willCacheResponse:(NSCachedURLResponse *)proposedResponse + completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ willCacheResponse:%@ %@", + [self class], self, session, dataTask, + proposedResponse, proposedResponse.response); + GTMSessionFetcherWillCacheURLResponseBlock callback; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + callback = _willCacheURLResponseBlock; + + if (callback) { + [self invokeOnCallbackQueueAfterUserStopped:YES + block:^{ + callback(proposedResponse, completionHandler); + }]; + } + } // @synchronized(self) + if (!callback) { + completionHandler(proposedResponse); + } +} + + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten + totalBytesWritten:(int64_t)totalBytesWritten +totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didWriteData:%lld" + @" bytesWritten:%lld totalBytesExpectedToWrite:%lld", + [self class], self, session, downloadTask, bytesWritten, + totalBytesWritten, totalBytesExpectedToWrite); + [self setSessionTask:downloadTask]; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if ((totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown) && + (totalBytesExpectedToWrite < totalBytesWritten)) { + // Have observed cases were bytesWritten == totalBytesExpectedToWrite, + // but totalBytesWritten > totalBytesExpectedToWrite, so setting to unkown in these cases. + totalBytesExpectedToWrite = NSURLSessionTransferSizeUnknown; + } + + GTMSessionFetcherDownloadProgressBlock progressBlock; + progressBlock = self->_downloadProgressBlock; + if (progressBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + progressBlock(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + }]; + } + } // @synchronized(self) +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didResumeAtOffset:(int64_t)fileOffset +expectedTotalBytes:(int64_t)expectedTotalBytes { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didResumeAtOffset:%lld" + @" expectedTotalBytes:%lld", + [self class], self, session, downloadTask, fileOffset, + expectedTotalBytes); + [self setSessionTask:downloadTask]; +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask +didFinishDownloadingToURL:(NSURL *)downloadLocationURL { + // Download may have relaunched app, so update _sessionTask. + [self setSessionTask:downloadTask]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didFinishDownloadingToURL:%@", + [self class], self, session, downloadTask, downloadLocationURL); + NSNumber *fileSizeNum; + [downloadLocationURL getResourceValue:&fileSizeNum + forKey:NSURLFileSizeKey + error:NULL]; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURL *destinationURL = _destinationFileURL; + + _downloadedLength = fileSizeNum.longLongValue; + + // Overwrite any previous file at the destination URL. + NSFileManager *fileMgr = [NSFileManager defaultManager]; + NSError *removeError; + if (![fileMgr removeItemAtURL:destinationURL error:&removeError] + && removeError.code != NSFileNoSuchFileError) { + GTMSESSION_LOG_DEBUG(@"Could not remove previous file at %@ due to %@", + downloadLocationURL.path, removeError); + } + + NSInteger statusCode = [self statusCodeUnsynchronized]; + if (statusCode < 200 || statusCode > 399) { + // In OS X 10.11, the response body is written to a file even on a server + // status error. For convenience of the fetcher client, we'll skip saving the + // downloaded body to the destination URL so that clients do not need to know + // to delete the file following fetch errors. + GTMSESSION_LOG_DEBUG(@"Abandoning download due to status %ld, file %@", + (long)statusCode, downloadLocationURL.path); + + // On error code, add the contents of the temporary file to _downloadTaskErrorData + // This way fetcher clients have access to error details possibly passed by the server. + if (_downloadedLength > 0 && _downloadedLength <= kMaximumDownloadErrorDataLength) { + _downloadTaskErrorData = [NSData dataWithContentsOfURL:downloadLocationURL]; + } else if (_downloadedLength > kMaximumDownloadErrorDataLength) { + GTMSESSION_LOG_DEBUG(@"Download error data for file %@ not passed to userInfo due to size " + @"%lld", downloadLocationURL.path, _downloadedLength); + } + } else { + NSError *moveError; + NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent]; + BOOL didMoveDownload = NO; + if ([fileMgr createDirectoryAtURL:destinationFolderURL + withIntermediateDirectories:YES + attributes:nil + error:&moveError]) { + didMoveDownload = [fileMgr moveItemAtURL:downloadLocationURL + toURL:destinationURL + error:&moveError]; + } + if (!didMoveDownload) { + _downloadFinishedError = moveError; + } + GTM_LOG_BACKGROUND_SESSION(@"%@ %p Moved download from \"%@\" to \"%@\" %@", + [self class], self, + downloadLocationURL.path, destinationURL.path, + error ? error : @""); + } + } // @synchronized(self) +} + +/* Sent as the last message related to a specific task. Error may be + * nil, which implies that no error occurred and this task is complete. + */ +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error { + [self setSessionTask:task]; + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didCompleteWithError:%@", + [self class], self, session, task, error); + + NSInteger status = self.statusCode; + BOOL forceAssumeRetry = NO; + BOOL succeeded = NO; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + // The task is never resumed when a testBlock is used. When the session is destroyed, + // we should ignore the callback, since the testBlock support code itself invokes + // shouldRetryNowForStatus: and finishWithError:shouldRetry: + if (_isUsingTestBlock) return; +#endif + + if (error == nil) { + error = _downloadFinishedError; + } + succeeded = (error == nil && status >= 0 && status < 300); + if (succeeded) { + // Succeeded. + _bodyLength = task.countOfBytesSent; + } + } // @synchronized(self) + + if (succeeded) { + [self finishWithError:nil shouldRetry:NO]; + return; + } + // For background redirects, no delegate method is called, so we cannot restore a stripped + // Authorization header, so if a 403 ("Forbidden") was generated due to a missing OAuth 2 header, + // set the current request's URL to the redirected URL, so we in effect restore the Authorization + // header. + if ((status == 403) && self.usingBackgroundSession) { + NSURL *redirectURL = self.response.URL; + NSURLRequest *request = self.request; + if (![request.URL isEqual:redirectURL]) { + NSString *authorizationHeader = [request.allHTTPHeaderFields objectForKey:@"Authorization"]; + if (authorizationHeader != nil) { + NSMutableURLRequest *mutableRequest = [request mutableCopy]; + mutableRequest.URL = redirectURL; + [self updateMutableRequest:mutableRequest]; + // Avoid assuming the session is still valid. + self.session = nil; + forceAssumeRetry = YES; + } + } + } + + // If invalidating the session was deferred in stopFetchReleasingCallbacks: then do it now. + NSURLSession *oldSession = self.sessionNeedingInvalidation; + if (oldSession) { + [self setSessionNeedingInvalidation:NULL]; + [oldSession finishTasksAndInvalidate]; + } + + // Failed. + [self shouldRetryNowForStatus:status + error:error + forceAssumeRetry:forceAssumeRetry + response:^(BOOL shouldRetry) { + [self finishWithError:error shouldRetry:shouldRetry]; + }]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics + API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock = _metricsCollectionBlock; + if (metricsCollectionBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + metricsCollectionBlock(metrics); + }]; + } + } +} + +#if TARGET_OS_IPHONE +- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSessionDidFinishEventsForBackgroundURLSession:%@", + [self class], self, session); + [self removePersistedBackgroundSessionFromDefaults]; + + GTMSessionFetcherSystemCompletionHandler handler; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + handler = self.systemCompletionHandler; + self.systemCompletionHandler = nil; + } // @synchronized(self) + if (handler) { + GTM_LOG_BACKGROUND_SESSION(@"%@ %p Calling system completionHandler", [self class], self); + handler(); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURLSession *oldSession = _session; + _session = nil; + if (_shouldInvalidateSession) { + [oldSession finishTasksAndInvalidate]; + } + } // @synchronized(self) + } +} +#endif + +- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(GTM_NULLABLE NSError *)error { + // This may happen repeatedly for retries. On authentication callbacks, the retry + // may begin before the prior session sends the didBecomeInvalid delegate message. + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", + [self class], self, session, error); + if (session == (NSURLSession *)self.session) { + GTM_LOG_SESSION_DELEGATE(@" Unexpected retained invalid session: %@", session); + self.session = nil; + } +} + +- (void)finishWithError:(GTM_NULLABLE NSError *)error shouldRetry:(BOOL)shouldRetry { + [self removePersistedBackgroundSessionFromDefaults]; + + BOOL shouldStopFetching = YES; + NSData *downloadedData = nil; +#if !STRIP_GTM_FETCH_LOGGING + BOOL shouldDeferLogging = NO; +#endif + BOOL shouldBeginRetryTimer = NO; + NSInteger status = [self statusCode]; + NSURL *destinationURL = self.destinationFileURL; + + BOOL fetchSucceeded = (error == nil && status >= 0 && status < 300); + +#if !STRIP_GTM_FETCH_LOGGING + if (!fetchSucceeded) { + if (!shouldDeferLogging && !self.hasLoggedError) { + [self logNowWithError:error]; + self.hasLoggedError = YES; + } + } +#endif // !STRIP_GTM_FETCH_LOGGING + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + +#if !STRIP_GTM_FETCH_LOGGING + shouldDeferLogging = _deferResponseBodyLogging; +#endif + if (fetchSucceeded) { + // Success + if ((_downloadedData.length > 0) && (destinationURL != nil)) { + // Overwrite any previous file at the destination URL. + NSFileManager *fileMgr = [NSFileManager defaultManager]; + [fileMgr removeItemAtURL:destinationURL + error:NULL]; + NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent]; + BOOL didMoveDownload = NO; + if ([fileMgr createDirectoryAtURL:destinationFolderURL + withIntermediateDirectories:YES + attributes:nil + error:&error]) { + didMoveDownload = [_downloadedData writeToURL:destinationURL + options:NSDataWritingAtomic + error:&error]; + } + if (didMoveDownload) { + _downloadedData = nil; + } else { + _downloadFinishedError = error; + } + } + downloadedData = _downloadedData; + } else { + // Unsuccessful with error or status over 300. Retry or notify the delegate of failure + if (shouldRetry) { + // Retrying. + shouldBeginRetryTimer = YES; + shouldStopFetching = NO; + } else { + if (error == nil) { + // Create an error. + NSDictionary *userInfo = GTMErrorUserInfoForData( + _downloadedData.length > 0 ? _downloadedData : _downloadTaskErrorData, + [self responseHeadersUnsynchronized]); + + error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain + code:status + userInfo:userInfo]; + } else { + // If the error had resume data, and the client supplied a resume block, pass the + // data to the client. + void (^resumeBlock)(NSData *) = _resumeDataBlock; + _resumeDataBlock = nil; + if (resumeBlock) { + NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]; + if (resumeData) { + [self invokeOnCallbackQueueAfterUserStopped:YES block:^{ + resumeBlock(resumeData); + }]; + } + } + } + if (_downloadedData.length > 0) { + downloadedData = _downloadedData; + } + // If the error occurred after retries, report the number and duration of the + // retries. This provides a clue to a developer looking at the error description + // that the fetcher did retry before failing with this error. + if (_retryCount > 0) { + NSMutableDictionary *userInfoWithRetries = + [NSMutableDictionary dictionaryWithDictionary:(NSDictionary *)error.userInfo]; + NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow]; + [userInfoWithRetries setObject:@(timeSinceInitialRequest) + forKey:kGTMSessionFetcherElapsedIntervalWithRetriesKey]; + [userInfoWithRetries setObject:@(_retryCount) + forKey:kGTMSessionFetcherNumberOfRetriesDoneKey]; + error = [NSError errorWithDomain:(NSString *)error.domain + code:error.code + userInfo:userInfoWithRetries]; + } + } + } + } // @synchronized(self) + + if (shouldBeginRetryTimer) { + [self beginRetryTimer]; + } + + // 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) { + [self invokeFetchCallbacksOnCallbackQueueWithData:downloadedData + error:error]; + // The upload subclass doesn't want to release callbacks until upload chunks have completed. + BOOL shouldRelease = [self shouldReleaseCallbacksUponCompletion]; + [self stopFetchReleasingCallbacks:shouldRelease]; + } + +#if !STRIP_GTM_FETCH_LOGGING + // _hasLoggedError is only set by this method + if (!shouldDeferLogging && !_hasLoggedError) { + [self logNowWithError:error]; + } +#endif +} + +- (BOOL)shouldReleaseCallbacksUponCompletion { + // A subclass can override this to keep callbacks around after the + // connection has finished successfully + return YES; +} + +- (void)logNowWithError:(GTM_NULLABLE NSError *)error { + GTMSessionCheckNotSynchronized(self); + + // 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 { + __unsafe_unretained NSString *const domain; + NSInteger code; + }; + + struct RetryRecord retries[] = { + { kGTMSessionFetcherStatusDomain, 408 }, // request timeout + { kGTMSessionFetcherStatusDomain, 502 }, // failure gatewaying to another server + { kGTMSessionFetcherStatusDomain, 503 }, // service unavailable + { kGTMSessionFetcherStatusDomain, 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 + NSString *domain = error.domain; + NSInteger code = error.code; + for (int idx = 0; retries[idx].domain != nil; idx++) { + if (code == retries[idx].code && [domain isEqual:retries[idx].domain]) { + return YES; + } + } + return NO; +} + +// shouldRetryNowForStatus:error: responds with 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 retry block is present and returns YES when called, or the +// authorizer may be able to fix. +- (void)shouldRetryNowForStatus:(NSInteger)status + error:(NSError *)error + forceAssumeRetry:(BOOL)forceAssumeRetry + response:(GTMSessionFetcherRetryResponse)response { + // Determine if a refreshed authorizer may avoid an authorization error + BOOL willRetry = NO; + + // We assume _authorizer is immutable after beginFetch, and _hasAttemptedAuthRefresh is modified + // only in this method, and this method is invoked on the serial delegate queue. + // + // We want to avoid calling the authorizer from inside a sync block. + BOOL isFirstAuthError = (_authorizer != nil + && !_hasAttemptedAuthRefresh + && status == GTMSessionFetcherStatusUnauthorized); // 401 + + BOOL hasPrimed = NO; + if (isFirstAuthError) { + if ([_authorizer respondsToSelector:@selector(primeForRefresh)]) { + hasPrimed = [_authorizer primeForRefresh]; + } + } + + BOOL shouldRetryForAuthRefresh = NO; + if (hasPrimed) { + shouldRetryForAuthRefresh = YES; + _hasAttemptedAuthRefresh = YES; + [self updateRequestValue:nil forHTTPHeaderField:@"Authorization"]; + } + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + BOOL shouldDoRetry = [self isRetryEnabledUnsynchronized]; + if (shouldDoRetry && ![self hasRetryAfterInterval]) { + + // Determine if we're doing exponential backoff retries + shouldDoRetry = [self nextRetryIntervalUnsynchronized] < _maxRetryInterval; + + if (shouldDoRetry) { + // If an explicit max retry interval was set, we expect repeated backoffs to take + // up to roughly twice that for repeated fast failures. If the initial attempt is + // already more than 3 times the max retry interval, then failures have taken a long time + // (such as from network timeouts) so don't retry again to avoid the app becoming + // unexpectedly unresponsive. + if (_maxRetryInterval > 0) { + NSTimeInterval maxAllowedIntervalBeforeRetry = _maxRetryInterval * 3; + NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow]; + if (timeSinceInitialRequest > maxAllowedIntervalBeforeRetry) { + shouldDoRetry = NO; + } + } + } + } + BOOL canRetry = shouldRetryForAuthRefresh || forceAssumeRetry || shouldDoRetry; + if (canRetry) { + NSDictionary *userInfo = + GTMErrorUserInfoForData(_downloadedData, [self responseHeadersUnsynchronized]); + NSError *statusError = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain + code:status + userInfo:userInfo]; + if (error == nil) { + error = statusError; + } + willRetry = shouldRetryForAuthRefresh || + forceAssumeRetry || + [self isRetryError:error] || + ((error != statusError) && [self isRetryError:statusError]); + + // If the user has installed a retry callback, consult that. + GTMSessionFetcherRetryBlock retryBlock = _retryBlock; + if (retryBlock) { + [self invokeOnCallbackQueueUnlessStopped:^{ + retryBlock(willRetry, error, response); + }]; + return; + } + } + } // @synchronized(self) + response(willRetry); +} + +- (BOOL)hasRetryAfterInterval { + GTMSessionCheckSynchronized(self); + + NSDictionary *responseHeaders = [self responseHeadersUnsynchronized]; + NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"]; + return (retryAfterValue != nil); +} + +- (NSTimeInterval)retryAfterInterval { + GTMSessionCheckSynchronized(self); + + NSDictionary *responseHeaders = [self responseHeadersUnsynchronized]; + NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"]; + if (retryAfterValue == nil) { + return 0; + } + // Retry-After formatted as HTTP-date | delta-seconds + // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + NSDateFormatter *rfc1123DateFormatter = [[NSDateFormatter alloc] init]; + rfc1123DateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + rfc1123DateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + rfc1123DateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss z"; + NSDate *retryAfterDate = [rfc1123DateFormatter dateFromString:retryAfterValue]; + NSTimeInterval retryAfterInterval = (retryAfterDate != nil) ? + retryAfterDate.timeIntervalSinceNow : retryAfterValue.intValue; + retryAfterInterval = MAX(0, retryAfterInterval); + return retryAfterInterval; +} + +- (void)beginRetryTimer { + if (![NSThread isMainThread]) { + // Defer creating and starting the timer until we're on the main thread to ensure it has + // a run loop. + dispatch_group_async(_callbackGroup, dispatch_get_main_queue(), ^{ + [self beginRetryTimer]; + }); + return; + } + + [self destroyRetryTimer]; + +#if GTM_BACKGROUND_TASK_FETCHING + // Don't keep a background task active while awaiting retry, which can lead to the + // app exceeding the allotted time for keeping the background task open, causing the + // system to terminate the app. When the retry starts, a new background task will + // be created. + [self endBackgroundTask]; +#endif // GTM_BACKGROUND_TASK_FETCHING + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSTimeInterval nextInterval = [self nextRetryIntervalUnsynchronized]; + NSTimeInterval maxInterval = _maxRetryInterval; + NSTimeInterval newInterval = MIN(nextInterval, (maxInterval > 0 ? maxInterval : DBL_MAX)); + NSTimeInterval newIntervalTolerance = (newInterval / 10) > 1.0 ?: 1.0; + + _lastRetryInterval = newInterval; + + _retryTimer = [NSTimer timerWithTimeInterval:newInterval + target:self + selector:@selector(retryTimerFired:) + userInfo:nil + repeats:NO]; + _retryTimer.tolerance = newIntervalTolerance; + [[NSRunLoop mainRunLoop] addTimer:_retryTimer + forMode:NSDefaultRunLoopMode]; + } // @synchronized(self) + + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStartedNotification + userInfo:nil + requireAsync:NO]; +} + +- (void)retryTimerFired:(NSTimer *)timer { + [self destroyRetryTimer]; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _retryCount++; + } // @synchronized(self) + + NSOperationQueue *queue = self.sessionDelegateQueue; + [queue addOperationWithBlock:^{ + [self retryFetch]; + }]; +} + +- (void)destroyRetryTimer { + BOOL shouldNotify = NO; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_retryTimer) { + [_retryTimer invalidate]; + _retryTimer = nil; + shouldNotify = YES; + } + } + + if (shouldNotify) { + [self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStoppedNotification + userInfo:nil + requireAsync:NO]; + } +} + +- (NSUInteger)retryCount { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _retryCount; + } // @synchronized(self) +} + +- (NSTimeInterval)nextRetryInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSTimeInterval interval = [self nextRetryIntervalUnsynchronized]; + return interval; + } // @synchronized(self) +} + +- (NSTimeInterval)nextRetryIntervalUnsynchronized { + GTMSessionCheckSynchronized(self); + + NSInteger statusCode = [self statusCodeUnsynchronized]; + if ((statusCode == 503) && [self hasRetryAfterInterval]) { + NSTimeInterval secs = [self retryAfterInterval]; + return secs; + } + // The next wait interval is the factor (2.0) times the last interval, + // but never less than the minimum interval. + NSTimeInterval secs = _lastRetryInterval * _retryFactor; + if (_maxRetryInterval > 0) { + secs = MIN(secs, _maxRetryInterval); + } + secs = MAX(secs, _minRetryInterval); + + return secs; +} + +- (NSTimer *)retryTimer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _retryTimer; + } // @synchronized(self) +} + +- (BOOL)isRetryEnabled { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _isRetryEnabled; + } // @synchronized(self) +} + +- (BOOL)isRetryEnabledUnsynchronized { + GTMSessionCheckSynchronized(self); + + return _isRetryEnabled; +} + +- (void)setRetryEnabled:(BOOL)flag { + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + 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 + _minRetryInterval = InitialMinRetryInterval(); + _maxRetryInterval = kUnsetMaxRetryInterval; + _retryFactor = 2.0; + _lastRetryInterval = 0.0; + } + _isRetryEnabled = flag; + } // @synchronized(self) +}; + +- (NSTimeInterval)maxRetryInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _maxRetryInterval; + } // @synchronized(self) +} + +- (void)setMaxRetryInterval:(NSTimeInterval)secs { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (secs > 0) { + _maxRetryInterval = secs; + } else { + _maxRetryInterval = kUnsetMaxRetryInterval; + } + } // @synchronized(self) +} + +- (double)minRetryInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _minRetryInterval; + } // @synchronized(self) +} + +- (void)setMinRetryInterval:(NSTimeInterval)secs { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + 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 = InitialMinRetryInterval(); + } + } // @synchronized(self) + +} + +#pragma mark iOS System Completion Handlers + +#if TARGET_OS_IPHONE +static NSMutableDictionary *gSystemCompletionHandlers = nil; + +- (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler { + return [[self class] systemCompletionHandlerForSessionIdentifier:_sessionIdentifier]; +} + +- (void)setSystemCompletionHandler:(GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler { + [[self class] setSystemCompletionHandler:systemCompletionHandler + forSessionIdentifier:_sessionIdentifier]; +} + ++ (void)setSystemCompletionHandler:(GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler + forSessionIdentifier:(NSString *)sessionIdentifier { + if (!sessionIdentifier) { + NSLog(@"%s with nil identifier", __PRETTY_FUNCTION__); + return; + } + + @synchronized([GTMSessionFetcher class]) { + if (gSystemCompletionHandlers == nil && systemCompletionHandler != nil) { + gSystemCompletionHandlers = [[NSMutableDictionary alloc] init]; + } + // Use setValue: to remove the object if completionHandler is nil. + [gSystemCompletionHandlers setValue:systemCompletionHandler + forKey:sessionIdentifier]; + } +} + ++ (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandlerForSessionIdentifier:(NSString *)sessionIdentifier { + if (!sessionIdentifier) { + return nil; + } + @synchronized([GTMSessionFetcher class]) { + return [gSystemCompletionHandlers objectForKey:sessionIdentifier]; + } +} +#endif // TARGET_OS_IPHONE + +#pragma mark Getters and Setters + +@synthesize downloadResumeData = _downloadResumeData, + configuration = _configuration, + configurationBlock = _configurationBlock, + sessionTask = _sessionTask, + wasCreatedFromBackgroundSession = _wasCreatedFromBackgroundSession, + sessionUserInfo = _sessionUserInfo, + taskDescription = _taskDescription, + taskPriority = _taskPriority, + usingBackgroundSession = _usingBackgroundSession, + canShareSession = _canShareSession, + completionHandler = _completionHandler, + credential = _credential, + proxyCredential = _proxyCredential, + bodyData = _bodyData, + bodyLength = _bodyLength, + service = _service, + serviceHost = _serviceHost, + accumulateDataBlock = _accumulateDataBlock, + receivedProgressBlock = _receivedProgressBlock, + downloadProgressBlock = _downloadProgressBlock, + resumeDataBlock = _resumeDataBlock, + didReceiveResponseBlock = _didReceiveResponseBlock, + challengeBlock = _challengeBlock, + willRedirectBlock = _willRedirectBlock, + sendProgressBlock = _sendProgressBlock, + willCacheURLResponseBlock = _willCacheURLResponseBlock, + retryBlock = _retryBlock, + metricsCollectionBlock = _metricsCollectionBlock, + retryFactor = _retryFactor, + allowedInsecureSchemes = _allowedInsecureSchemes, + allowLocalhostRequest = _allowLocalhostRequest, + allowInvalidServerCertificates = _allowInvalidServerCertificates, + cookieStorage = _cookieStorage, + callbackQueue = _callbackQueue, + initialBeginFetchDate = _initialBeginFetchDate, + testBlock = _testBlock, + testBlockAccumulateDataChunkCount = _testBlockAccumulateDataChunkCount, + comment = _comment, + log = _log; + +#if !STRIP_GTM_FETCH_LOGGING +@synthesize redirectedFromURL = _redirectedFromURL, + logRequestBody = _logRequestBody, + logResponseBody = _logResponseBody, + hasLoggedError = _hasLoggedError; +#endif + +#if GTM_BACKGROUND_TASK_FETCHING +@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier, + skipBackgroundTask = _skipBackgroundTask; +#endif + +- (GTM_NULLABLE NSURLRequest *)request { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_request copy]; + } // @synchronized(self) +} + +- (void)setRequest:(GTM_NULLABLE NSURLRequest *)request { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (![self isFetchingUnsynchronized]) { + _request = [request mutableCopy]; + } else { + GTMSESSION_ASSERT_DEBUG(0, @"request may not be set after beginFetch has been invoked"); + } + } // @synchronized(self) +} + +- (GTM_NULLABLE NSMutableURLRequest *)mutableRequestForTesting { + // Allow tests only to modify the request, useful during retries. + return _request; +} + +// Internal method for updating the request property such as on redirects. +- (void)updateMutableRequest:(GTM_NULLABLE NSMutableURLRequest *)request { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _request = request; + } // @synchronized(self) +} + +// Set a header field value on the request. Header field value changes will not +// affect a fetch after the fetch has begun. +- (void)setRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field { + if (![self isFetching]) { + [self updateRequestValue:value forHTTPHeaderField:field]; + } else { + GTMSESSION_ASSERT_DEBUG(0, @"request may not be set after beginFetch has been invoked"); + } +} + +// Internal method for updating request headers. +- (void)updateRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [_request setValue:value forHTTPHeaderField:field]; + } // @synchronized(self) +} + +- (void)setResponse:(GTM_NULLABLE NSURLResponse *)response { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _response = response; + } // @synchronized(self) +} + +- (int64_t)bodyLength { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_bodyLength == NSURLSessionTransferSizeUnknown) { + if (_bodyData) { + _bodyLength = (int64_t)_bodyData.length; + } else if (_bodyFileURL) { + NSNumber *fileSizeNum = nil; + NSError *fileSizeError = nil; + if ([_bodyFileURL getResourceValue:&fileSizeNum + forKey:NSURLFileSizeKey + error:&fileSizeError]) { + _bodyLength = [fileSizeNum longLongValue]; + } + } + } + return _bodyLength; + } // @synchronized(self) +} + +- (BOOL)useUploadTask { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _useUploadTask; + } // @synchronized(self) +} + +- (void)setUseUploadTask:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (flag != _useUploadTask) { + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"useUploadTask should not change after beginFetch has been invoked"); + _useUploadTask = flag; + } + } // @synchronized(self) +} + +- (GTM_NULLABLE NSURL *)bodyFileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _bodyFileURL; + } // @synchronized(self) +} + +- (void)setBodyFileURL:(GTM_NULLABLE NSURL *)fileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // The comparison here is a trivial optimization and forgiveness for any client that + // repeatedly sets the property, so it just uses pointer comparison rather than isEqual:. + if (fileURL != _bodyFileURL) { + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"fileURL should not change after beginFetch has been invoked"); + + _bodyFileURL = fileURL; + } + } // @synchronized(self) +} + +- (GTM_NULLABLE GTMSessionFetcherBodyStreamProvider)bodyStreamProvider { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _bodyStreamProvider; + } // @synchronized(self) +} + +- (void)setBodyStreamProvider:(GTM_NULLABLE GTMSessionFetcherBodyStreamProvider)block { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"stream provider should not change after beginFetch has been invoked"); + + _bodyStreamProvider = [block copy]; + } // @synchronized(self) +} + +- (GTM_NULLABLE id)authorizer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _authorizer; + } // @synchronized(self) +} + +- (void)setAuthorizer:(GTM_NULLABLE id)authorizer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (authorizer != _authorizer) { + if ([self isFetchingUnsynchronized]) { + GTMSESSION_ASSERT_DEBUG(0, @"authorizer should not change after beginFetch has been invoked"); + } else { + _authorizer = authorizer; + } + } + } // @synchronized(self) +} + +- (GTM_NULLABLE NSData *)downloadedData { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _downloadedData; + } // @synchronized(self) +} + +- (void)setDownloadedData:(GTM_NULLABLE NSData *)data { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _downloadedData = [data mutableCopy]; + } // @synchronized(self) +} + +- (int64_t)downloadedLength { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _downloadedLength; + } // @synchronized(self) +} + +- (void)setDownloadedLength:(int64_t)length { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _downloadedLength = length; + } // @synchronized(self) +} + +- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _callbackQueue; + } // @synchronized(self) +} + +- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _callbackQueue = queue ?: dispatch_get_main_queue(); + } // @synchronized(self) +} + +- (GTM_NULLABLE NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _session; + } // @synchronized(self) +} + +- (NSInteger)servicePriority { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _servicePriority; + } // @synchronized(self) +} + +- (void)setServicePriority:(NSInteger)value { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (value != _servicePriority) { + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"servicePriority should not change after beginFetch has been invoked"); + + _servicePriority = value; + } + } // @synchronized(self) +} + + +- (void)setSession:(GTM_NULLABLE NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _session = session; + } // @synchronized(self) +} + +- (BOOL)canShareSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _canShareSession; + } // @synchronized(self) +} + +- (void)setCanShareSession:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _canShareSession = flag; + } // @synchronized(self) +} + +- (BOOL)useBackgroundSession { + // This reflects if the user requested a background session, not necessarily + // if one was created. That is tracked with _usingBackgroundSession. + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _userRequestedBackgroundSession; + } // @synchronized(self) +} + +- (void)setUseBackgroundSession:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (flag != _userRequestedBackgroundSession) { + GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], + @"useBackgroundSession should not change after beginFetch has been invoked"); + + _userRequestedBackgroundSession = flag; + } + } // @synchronized(self) +} + +- (BOOL)isUsingBackgroundSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _usingBackgroundSession; + } // @synchronized(self) +} + +- (void)setUsingBackgroundSession:(BOOL)flag { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _usingBackgroundSession = flag; + } // @synchronized(self) +} + +- (GTM_NULLABLE NSURLSession *)sessionNeedingInvalidation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _sessionNeedingInvalidation; + } // @synchronized(self) +} + +- (void)setSessionNeedingInvalidation:(GTM_NULLABLE NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _sessionNeedingInvalidation = session; + } // @synchronized(self) +} + +- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateQueue; + } // @synchronized(self) +} + +- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (queue != _delegateQueue) { + if ([self isFetchingUnsynchronized]) { + GTMSESSION_ASSERT_DEBUG(0, @"sessionDelegateQueue should not change after fetch begins"); + } else { + _delegateQueue = queue ?: [NSOperationQueue mainQueue]; + } + } + } // @synchronized(self) +} + +- (BOOL)userStoppedFetching { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _userStoppedFetching; + } // @synchronized(self) +} + +- (GTM_NULLABLE id)userData { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _userData; + } // @synchronized(self) +} + +- (void)setUserData:(GTM_NULLABLE id)theObj { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _userData = theObj; + } // @synchronized(self) +} + +- (GTM_NULLABLE NSURL *)destinationFileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _destinationFileURL; + } // @synchronized(self) +} + +- (void)setDestinationFileURL:(GTM_NULLABLE NSURL *)destinationFileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (((_destinationFileURL == nil) && (destinationFileURL == nil)) || + [_destinationFileURL isEqual:destinationFileURL]) { + return; + } + if (_sessionIdentifier) { + // This is something we don't expect to happen in production. + // However if it ever happen, leave a system log. + NSLog(@"%@: Destination File URL changed from (%@) to (%@) after session identifier has " + @"been created.", + [self class], _destinationFileURL, destinationFileURL); +#if DEBUG + // On both the simulator and devices, the path can change to the download file, but the name + // shouldn't change. Technically, this isn't supported in the fetcher, but the change of + // URL is expected to happen only across development runs through Xcode. + NSString *oldFilename = [_destinationFileURL lastPathComponent]; + NSString *newFilename = [destinationFileURL lastPathComponent]; + #pragma unused(oldFilename) + #pragma unused(newFilename) + GTMSESSION_ASSERT_DEBUG([oldFilename isEqualToString:newFilename], + @"Destination File URL cannot be changed after session identifier has been created"); +#endif + } + _destinationFileURL = destinationFileURL; + } // @synchronized(self) +} + +- (void)setProperties:(GTM_NULLABLE NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _properties = [dict mutableCopy]; + } // @synchronized(self) +} + +- (GTM_NULLABLE NSDictionary *)properties { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _properties; + } // @synchronized(self) +} + +- (void)setProperty:(GTM_NULLABLE id)obj forKey:(NSString *)key { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_properties == nil && obj != nil) { + _properties = [[NSMutableDictionary alloc] init]; + } + [_properties setValue:obj forKey:key]; + } // @synchronized(self) +} + +- (GTM_NULLABLE id)propertyForKey:(NSString *)key { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_properties objectForKey:key]; + } // @synchronized(self) +} + +- (void)addPropertiesFromDictionary:(NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_properties == nil && dict != nil) { + [self setProperties:[dict mutableCopy]]; + } else { + [_properties addEntriesFromDictionary:dict]; + } + } // @synchronized(self) +} + +- (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]; + va_end(argList); + } + [self setComment:result]; +#endif +} + +#if !STRIP_GTM_FETCH_LOGGING +- (NSData *)loggedStreamData { + return _loggedStreamData; +} + +- (void)appendLoggedStreamData:dataToAdd { + if (!_loggedStreamData) { + _loggedStreamData = [NSMutableData data]; + } + [_loggedStreamData appendData:dataToAdd]; +} + +- (void)clearLoggedStreamData { + _loggedStreamData = nil; +} + +- (void)setDeferResponseBodyLogging:(BOOL)deferResponseBodyLogging { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (deferResponseBodyLogging != _deferResponseBodyLogging) { + _deferResponseBodyLogging = deferResponseBodyLogging; + if (!deferResponseBodyLogging && !self.hasLoggedError) { + [_delegateQueue addOperationWithBlock:^{ + [self logNowWithError:nil]; + }]; + } + } + } // @synchronized(self) +} + +- (BOOL)deferResponseBodyLogging { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _deferResponseBodyLogging; + } // @synchronized(self) +} + +#else ++ (void)setLoggingEnabled:(BOOL)flag { +} + ++ (BOOL)isLoggingEnabled { + return NO; +} +#endif // STRIP_GTM_FETCH_LOGGING + +@end + +@implementation GTMSessionFetcher (BackwardsCompatibilityOnly) + +- (void)setCookieStorageMethod:(NSInteger)method { + // For backwards compatibility with the old fetcher, we'll support the old constants. + // + // Clients using the GTMSessionFetcher class should set the cookie storage explicitly + // themselves. + NSHTTPCookieStorage *storage = nil; + switch(method) { + case 0: // kGTMHTTPFetcherCookieStorageMethodStatic + // nil storage will use [[self class] staticCookieStorage] when the fetch begins. + break; + case 1: // kGTMHTTPFetcherCookieStorageMethodFetchHistory + // Do nothing; use whatever was set by the fetcher service. + return; + case 2: // kGTMHTTPFetcherCookieStorageMethodSystemDefault + storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + break; + case 3: // kGTMHTTPFetcherCookieStorageMethodNone + // Create temporary storage for this fetcher only. + storage = [[GTMSessionCookieStorage alloc] init]; + break; + default: + GTMSESSION_ASSERT_DEBUG(0, @"Invalid cookie storage method: %d", (int)method); + } + self.cookieStorage = storage; +} + +@end + +@implementation GTMSessionCookieStorage { + NSMutableArray *_cookies; + NSHTTPCookieAcceptPolicy _policy; +} + +- (id)init { + self = [super init]; + if (self != nil) { + _cookies = [[NSMutableArray alloc] init]; + } + return self; +} + +- (GTM_NULLABLE NSArray *)cookies { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_cookies copy]; + } // @synchronized(self) +} + +- (void)setCookie:(NSHTTPCookie *)cookie { + if (!cookie) return; + if (_policy == NSHTTPCookieAcceptPolicyNever) return; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self internalSetCookie:cookie]; + } // @synchronized(self) +} + +// Note: this should only be called from inside a @synchronized(self) block. +- (void)internalSetCookie:(NSHTTPCookie *)newCookie { + GTMSessionCheckSynchronized(self); + + if (_policy == NSHTTPCookieAcceptPolicyNever) return; + + BOOL isValidCookie = (newCookie.name.length > 0 + && newCookie.domain.length > 0 + && newCookie.path.length > 0); + GTMSESSION_ASSERT_DEBUG(isValidCookie, @"invalid cookie: %@", newCookie); + + if (isValidCookie) { + // Remove the cookie if it's currently in the array. + NSHTTPCookie *oldCookie = [self cookieMatchingCookie:newCookie]; + if (oldCookie) { + [_cookies removeObjectIdenticalTo:oldCookie]; + } + + if (![[self class] hasCookieExpired:newCookie]) { + [_cookies addObject:newCookie]; + } + } +} + +// 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:(GTM_NULLABLE NSArray *)newCookies { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self removeExpiredCookies]; + + for (NSHTTPCookie *newCookie in newCookies) { + [self internalSetCookie:newCookie]; + } + } // @synchronized(self) +} + +- (void)setCookies:(NSArray *)cookies forURL:(GTM_NULLABLE NSURL *)URL mainDocumentURL:(GTM_NULLABLE NSURL *)mainDocumentURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_policy == NSHTTPCookieAcceptPolicyNever) { + return; + } + + if (_policy == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain) { + NSString *mainHost = mainDocumentURL.host; + NSString *associatedHost = URL.host; + if (!mainHost || ![associatedHost hasSuffix:mainHost]) { + return; + } + } + } // @synchronized(self) + [self setCookies:cookies]; +} + +- (void)deleteCookie:(NSHTTPCookie *)cookie { + if (!cookie) return; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSHTTPCookie *foundCookie = [self cookieMatchingCookie:cookie]; + if (foundCookie) { + [_cookies removeObjectIdenticalTo:foundCookie]; + } + } // @synchronized(self) +} + +// 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. +- (GTM_NULLABLE NSArray *)cookiesForURL:(NSURL *)theURL { + NSMutableArray *foundCookies = nil; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [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 *requestingDomain = nil; + BOOL isLocalhostRetrieval = NO; + + if (IsLocalhost(host)) { + isLocalhostRetrieval = YES; + } else { + if (host.length > 0) { + requestingDomain = [@"." stringByAppendingString:host]; + } + } + + for (NSHTTPCookie *storedCookie in _cookies) { + 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 = (IsLocalhost(cookieDomain) + || [cookieDomain isEqual:@"localhost.local"]); + } else { + // Ensure we're matching exact domain names. We prepended a dot to the + // requesting domain, so we can also prepend one here if needed before + // checking if the request contains the cookie domain. + if (![cookieDomain hasPrefix:@"."]) { + cookieDomain = [@"." stringByAppendingString:cookieDomain]; + } + isDomainOK = [requestingDomain hasSuffix:cookieDomain]; + } + + BOOL isPathOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath]; + BOOL isSecureOK = (!cookieIsSecure + || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame); + + if (isDomainOK && isPathOK && isSecureOK) { + if (foundCookies == nil) { + foundCookies = [NSMutableArray array]; + } + [foundCookies addObject:storedCookie]; + } + } + } // @synchronized(self) + return foundCookies; +} + +// Override methods from the NSHTTPCookieStorage (NSURLSessionTaskAdditions) category. +- (void)storeCookies:(NSArray *)cookies forTask:(NSURLSessionTask *)task { + NSURLRequest *currentRequest = task.currentRequest; + [self setCookies:cookies forURL:currentRequest.URL mainDocumentURL:nil]; +} + +- (void)getCookiesForTask:(NSURLSessionTask *)task + completionHandler:(void (^)(GTM_NSArrayOf(NSHTTPCookie *) *))completionHandler { + if (completionHandler) { + NSURLRequest *currentRequest = task.currentRequest; + NSURL *currentRequestURL = currentRequest.URL; + NSArray *cookies = [self cookiesForURL:currentRequestURL]; + completionHandler(cookies); + } +} + +// 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(self) block +- (GTM_NULLABLE NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie { + GTMSessionCheckSynchronized(self); + + NSString *name = cookie.name; + NSString *domain = cookie.domain; + NSString *path = cookie.path; + + GTMSESSION_ASSERT_DEBUG(name && domain && path, + @"Invalid stored cookie (name:%@ domain:%@ path:%@)", name, domain, path); + + for (NSHTTPCookie *storedCookie in _cookies) { + 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(self) block +- (void)removeExpiredCookies { + GTMSessionCheckSynchronized(self); + + // 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]; + if ([[self class] hasCookieExpired:storedCookie]) { + [_cookies removeObjectAtIndex:(NSUInteger)idx]; + } + } +} + ++ (BOOL)hasCookieExpired:(NSHTTPCookie *)cookie { + NSDate *expiresDate = [cookie expiresDate]; + if (expiresDate == nil) { + // Cookies seem to have a Expires property even when the expiresDate method returns nil. + id expiresVal = [[cookie properties] objectForKey:NSHTTPCookieExpires]; + if ([expiresVal isKindOfClass:[NSDate class]]) { + expiresDate = expiresVal; + } + } + BOOL hasExpired = (expiresDate != nil && [expiresDate timeIntervalSinceNow] < 0); + return hasExpired; +} + +- (void)removeAllCookies { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [_cookies removeAllObjects]; + } // @synchronized(self) +} + +- (NSHTTPCookieAcceptPolicy)cookieAcceptPolicy { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _policy; + } // @synchronized(self) +} + +- (void)setCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)cookieAcceptPolicy { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _policy = cookieAcceptPolicy; + } // @synchronized(self) +} + +@end + +void GTMSessionFetcherAssertValidSelector(id GTM_NULLABLE_TYPE obj, SEL GTM_NULLABLE_TYPE 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([(id)obj class]), + NSStringFromSelector((SEL)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([(id)obj class]), + NSStringFromSelector((SEL)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([(id)obj class]), + NSStringFromSelector((SEL)sel), (argCount - 2)); + NSCAssert(0, @"callback selector arguments incorrect"); + } + } + } + + va_end(argList); +#endif +} + +NSString *GTMFetcherCleanedUserAgentString(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 @""; + + NSMutableString *result = [NSMutableString stringWithString:str]; + + // Replace spaces and commas with underscores + [result replaceOccurrencesOfString:@" " + withString:@"_" + options:0 + range:NSMakeRange(0, result.length)]; + [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 = + [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy]; + [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 *GTMFetcherSystemVersionString(void) { + static NSString *sSavedSystemString; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // The Xcode 8 SDKs finally cleaned up this mess by providing TARGET_OS_OSX + // and TARGET_OS_IOS, but to build with older SDKs, those don't exist and + // instead one has to rely on TARGET_OS_MAC (which is true for iOS, watchOS, + // and tvOS) and TARGET_OS_IPHONE (which is true for iOS, watchOS, tvOS). So + // one has to order these carefully so you pick off the specific things + // first. + // If the code can ever assume Xcode 8 or higher (even when building for + // older OSes), then + // TARGET_OS_MAC -> TARGET_OS_OSX + // TARGET_OS_IPHONE -> TARGET_OS_IOS + // TARGET_IPHONE_SIMULATOR -> TARGET_OS_SIMULATOR +#if TARGET_OS_WATCH + // watchOS - WKInterfaceDevice + + WKInterfaceDevice *currentDevice = [WKInterfaceDevice currentDevice]; + + NSString *rawModel = [currentDevice model]; + NSString *model = GTMFetcherCleanedUserAgentString(rawModel); + + NSString *systemVersion = [currentDevice systemVersion]; + +#if TARGET_OS_SIMULATOR + NSString *hardwareModel = @"sim"; +#else + NSString *hardwareModel; + struct utsname unameRecord; + if (uname(&unameRecord) == 0) { + NSString *machineName = @(unameRecord.machine); + hardwareModel = GTMFetcherCleanedUserAgentString(machineName); + } + if (hardwareModel.length == 0) { + hardwareModel = @"unk"; + } +#endif + + sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@", + model, systemVersion, hardwareModel]; + // Example: Apple_Watch/3.0 hw/Watch1_2 +#elif TARGET_OS_TV || TARGET_OS_IPHONE + // iOS and tvOS have UIDevice, use that. + UIDevice *currentDevice = [UIDevice currentDevice]; + + NSString *rawModel = [currentDevice model]; + NSString *model = GTMFetcherCleanedUserAgentString(rawModel); + + NSString *systemVersion = [currentDevice systemVersion]; + +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR + NSString *hardwareModel = @"sim"; +#else + NSString *hardwareModel; + struct utsname unameRecord; + if (uname(&unameRecord) == 0) { + NSString *machineName = @(unameRecord.machine); + hardwareModel = GTMFetcherCleanedUserAgentString(machineName); + } + if (hardwareModel.length == 0) { + hardwareModel = @"unk"; + } +#endif + + sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@", + model, systemVersion, hardwareModel]; + // Example: iPod_Touch/2.2 hw/iPod1_1 + // Example: Apple_TV/9.2 hw/AppleTV5,3 +#elif TARGET_OS_MAC + // Mac build + NSProcessInfo *procInfo = [NSProcessInfo processInfo]; +#if !defined(MAC_OS_X_VERSION_10_10) + BOOL hasOperatingSystemVersion = NO; +#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10 + BOOL hasOperatingSystemVersion = + [procInfo respondsToSelector:@selector(operatingSystemVersion)]; +#else + BOOL hasOperatingSystemVersion = YES; +#endif + NSString *versString; + if (hasOperatingSystemVersion) { +#if defined(MAC_OS_X_VERSION_10_10) + // A reference to NSOperatingSystemVersion requires the 10.10 SDK. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" +// Disable unguarded availability warning as we can't use the @availability macro until we require +// all clients to build with Xcode 9 or above. + NSOperatingSystemVersion version = procInfo.operatingSystemVersion; +#pragma clang diagnostic pop + versString = [NSString stringWithFormat:@"%ld.%ld.%ld", + (long)version.majorVersion, (long)version.minorVersion, + (long)version.patchVersion]; +#else +#pragma unused(procInfo) +#endif + } else { + // 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]; + versString = [plist objectForKey:@"ProductVersion"]; + if (versString.length == 0) { + versString = @"10.?.?"; + } + } + + sSavedSystemString = [[NSString alloc] initWithFormat:@"MacOSX/%@", versString]; +#elif defined(_SYS_UTSNAME_H) + // Foundation-only build + struct utsname unameRecord; + uname(&unameRecord); + + sSavedSystemString = [NSString stringWithFormat:@"%s/%s", + unameRecord.sysname, unameRecord.release]; // "Darwin/8.11.1" +#else +#error No branch taken for a default user agent +#endif + }); + return sSavedSystemString; +} + +NSString *GTMFetcherStandardUserAgentString(NSBundle * GTM_NULLABLE_TYPE bundle) { + NSString *result = [NSString stringWithFormat:@"%@ %@", + GTMFetcherApplicationIdentifier(bundle), + GTMFetcherSystemVersionString()]; + return result; +} + +NSString *GTMFetcherApplicationIdentifier(NSBundle * GTM_NULLABLE_TYPE bundle) { + @synchronized([GTMSessionFetcher class]) { + static NSMutableDictionary *sAppIDMap = nil; + + // If there's a bundle ID, use that; otherwise, use the process name + if (bundle == nil) { + bundle = [NSBundle mainBundle]; + } + NSString *bundleID = [bundle bundleIdentifier]; + if (bundleID == nil) { + bundleID = @""; + } + + NSString *identifier = [sAppIDMap objectForKey:bundleID]; + if (identifier) return identifier; + + // Apps may add a string to the info.plist to uniquely identify different builds. + identifier = [bundle objectForInfoDictionaryKey:@"GTMUserAgentID"]; + if (identifier.length == 0) { + 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 = GTMFetcherCleanedUserAgentString(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 = GTMFetcherCleanedUserAgentString(version); + + // Glue the two together (cleanup done above or else cleanup would strip the + // slash) + if (version.length > 0) { + identifier = [identifier stringByAppendingFormat:@"/%@", version]; + } + + if (sAppIDMap == nil) { + sAppIDMap = [[NSMutableDictionary alloc] init]; + } + [sAppIDMap setObject:identifier forKey:bundleID]; + return identifier; + } +} + +#if DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) +@implementation GTMSessionSyncMonitorInternal { + NSValue *_objectKey; // The synchronize target object. + const char *_functionName; // The function containing the monitored sync block. +} + +- (instancetype)initWithSynchronizationObject:(id)object + allowRecursive:(BOOL)allowRecursive + functionName:(const char *)functionName { + self = [super init]; + if (self) { + Class threadKey = [GTMSessionSyncMonitorInternal class]; + _objectKey = [NSValue valueWithNonretainedObject:object]; + _functionName = functionName; + + NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; + NSMutableDictionary *counters = threadDict[threadKey]; + if (counters == nil) { + counters = [NSMutableDictionary dictionary]; + threadDict[(id)threadKey] = counters; + } + NSCountedSet *functionNamesCounter = counters[_objectKey]; + NSUInteger numberOfSyncingFunctions = functionNamesCounter.count; + + if (!allowRecursive) { + BOOL isTopLevelSyncScope = (numberOfSyncingFunctions == 0); + NSArray *stack = [NSThread callStackSymbols]; + GTMSESSION_ASSERT_DEBUG(isTopLevelSyncScope, + @"*** Recursive sync on %@ at %s; previous sync at %@\n%@", + [object class], functionName, functionNamesCounter.allObjects, + [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]); + } + + if (!functionNamesCounter) { + functionNamesCounter = [NSCountedSet set]; + counters[_objectKey] = functionNamesCounter; + } + [functionNamesCounter addObject:(id _Nonnull)@(functionName)]; + } + return self; +} + +- (void)dealloc { + Class threadKey = [GTMSessionSyncMonitorInternal class]; + + NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; + NSMutableDictionary *counters = threadDict[threadKey]; + NSCountedSet *functionNamesCounter = counters[_objectKey]; + NSString *functionNameStr = @(_functionName); + NSUInteger numberOfSyncsByThisFunction = [functionNamesCounter countForObject:functionNameStr]; + NSArray *stack = [NSThread callStackSymbols]; + GTMSESSION_ASSERT_DEBUG(numberOfSyncsByThisFunction > 0, @"Sync not found on %@ at %s\n%@", + [_objectKey.nonretainedObjectValue class], _functionName, + [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]); + [functionNamesCounter removeObject:functionNameStr]; + if (functionNamesCounter.count == 0) { + [counters removeObjectForKey:_objectKey]; + } +} + ++ (NSArray *)functionsHoldingSynchronizationOnObject:(id)object { + Class threadKey = [GTMSessionSyncMonitorInternal class]; + NSValue *localObjectKey = [NSValue valueWithNonretainedObject:object]; + + NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; + NSMutableDictionary *counters = threadDict[threadKey]; + NSCountedSet *functionNamesCounter = counters[localObjectKey]; + return functionNamesCounter.count > 0 ? functionNamesCounter.allObjects : nil; +} +@end +#endif // DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) +GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h new file mode 100644 index 00000000..5ccea78e --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h @@ -0,0 +1,112 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 "GTMSessionFetcher.h" + +// GTM HTTP Logging +// +// All traffic using GTMSessionFetcher can be easily logged. Call +// +// [GTMSessionFetcher setLoggingEnabled:YES]; +// +// to begin generating log files. +// +// Unless explicitly set by the application using +setLoggingDirectory:, +// logs are put into a default directory, located at: +// * macOS: ~/Desktop/GTMHTTPDebugLogs +// * iOS simulator: ~/GTMHTTPDebugLogs (in application sandbox) +// * iOS device: ~/Documents/GTMHTTPDebugLogs (in application sandbox) +// +// Tip: use the Finder's "Sort By Date" to find the most recent logs. +// +// Each run of an application gets a separate set of log files. An html +// file is generated to simplify browsing the run's http transactions. +// The html file includes javascript links for inline viewing of uploaded +// and downloaded data. +// +// A symlink is created in the logs folder to simplify finding the html file +// for the latest run of the application; the symlink is called +// +// AppName_http_log_newest.html +// +// For better viewing of XML logs, use Camino or Firefox rather than Safari. +// +// Each fetcher may be given a comment to be inserted as a label in the logs, +// such as +// [fetcher setCommentWithFormat:@"retrieve item %@", itemName]; +// +// Projects may define STRIP_GTM_FETCH_LOGGING to remove logging code. + +#if !STRIP_GTM_FETCH_LOGGING + +@interface GTMSessionFetcher (GTMSessionFetcherLogging) + +// Note: on macOS the default logs directory is ~/Desktop/GTMHTTPDebugLogs; on +// iOS simulators it will be the ~/GTMHTTPDebugLogs (in the app sandbox); on +// iOS devices it will be in ~/Documents/GTMHTTPDebugLogs (in the app sandbox). +// These directories will be created as needed, and are excluded from backups +// to iCloud and iTunes. +// +// If a custom directory is set, the directory should already exist. It is +// the application's responsibility to exclude any custom directory from +// backups, if desired. ++ (void)setLoggingDirectory:(NSString *)path; ++ (NSString *)loggingDirectory; + +// client apps can turn logging on and off ++ (void)setLoggingEnabled:(BOOL)isLoggingEnabled; ++ (BOOL)isLoggingEnabled; + +// client apps can turn off logging to a file if they want to only check +// the fetcher's log property ++ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled; ++ (BOOL)isLoggingToFileEnabled; + +// client apps can optionally specify process name and date string used in +// log file names ++ (void)setLoggingProcessName:(NSString *)processName; ++ (NSString *)loggingProcessName; + ++ (void)setLoggingDateStamp:(NSString *)dateStamp; ++ (NSString *)loggingDateStamp; + +// client apps can specify the directory for the log for this specific run, +// typically to match the directory used by another fetcher class, like: +// +// [GTMSessionFetcher setLogDirectoryForCurrentRun:[GTMHTTPFetcher logDirectoryForCurrentRun]]; +// +// Setting this overrides the logging directory, process name, and date stamp when writing +// the log file. ++ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun; ++ (NSString *)logDirectoryForCurrentRun; + +// Prunes old log directories that have not been modified since the provided date. +// This will not delete the current run's log directory. ++ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)date; + +// internal; called by fetcher +- (void)logFetchWithError:(NSError *)error; +- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream; +- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: + (GTMSessionFetcherBodyStreamProvider)streamProvider; + +// internal; accessors useful for viewing logs ++ (NSString *)processNameLogPrefix; ++ (NSString *)symlinkNameSuffix; ++ (NSString *)htmlFileName; + +@end + +#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m new file mode 100644 index 00000000..cdf5c179 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m @@ -0,0 +1,982 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#include +#include + +#import "GTMSessionFetcherLogging.h" + +#ifndef STRIP_GTM_FETCH_LOGGING + #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. +#endif + +#if !STRIP_GTM_FETCH_LOGGING + +// Sensitive credential strings are replaced in logs with _snip_ +// +// Apps that must see the contents of sensitive tokens can set this to 1 +#ifndef SKIP_GTM_FETCH_LOGGING_SNIPPING +#define SKIP_GTM_FETCH_LOGGING_SNIPPING 0 +#endif + +// If GTMReadMonitorInputStream is available, it can be used for +// capturing uploaded streams of data +// +// We locally declare methods of GTMReadMonitorInputStream so we +// do not need to import the header, as some projects may not have it available +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMReadMonitorInputStream : NSInputStream + ++ (instancetype)inputStreamWithStream:(NSInputStream *)input; + +@property (assign) id readDelegate; +@property (assign) SEL readSelector; + +@end +#else +@class GTMReadMonitorInputStream; +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +@interface GTMSessionFetcher (GTMHTTPFetcherLoggingUtilities) + ++ (NSString *)headersStringForDictionary:(NSDictionary *)dict; ++ (NSString *)snipSubstringOfString:(NSString *)originalStr + betweenStartString:(NSString *)startStr + endString:(NSString *)endStr; +- (void)inputStream:(GTMReadMonitorInputStream *)stream + readIntoBuffer:(void *)buffer + length:(int64_t)length; + +@end + +@implementation GTMSessionFetcher (GTMSessionFetcherLogging) + +// fetchers come and fetchers go, but statics are forever +static BOOL gIsLoggingEnabled = NO; +static BOOL gIsLoggingToFile = YES; +static NSString *gLoggingDirectoryPath = nil; +static NSString *gLogDirectoryForCurrentRun = nil; +static NSString *gLoggingDateStamp = nil; +static NSString *gLoggingProcessName = nil; + ++ (void)setLoggingDirectory:(NSString *)path { + gLoggingDirectoryPath = [path copy]; +} + ++ (NSString *)loggingDirectory { + if (!gLoggingDirectoryPath) { + NSArray *paths = nil; +#if TARGET_IPHONE_SIMULATOR + // default to a directory called GTMHTTPDebugLogs into a sandbox-safe + // directory that a developer can find easily, the application home + paths = @[ NSHomeDirectory() ]; +#elif TARGET_OS_IPHONE + // Neither ~/Desktop nor ~/Home is writable on an actual iOS, watchOS, or tvOS device. + // Put it in ~/Documents. + paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); +#else + // default to a directory called GTMHTTPDebugLogs in the desktop folder + paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); +#endif + + NSString *desktopPath = paths.firstObject; + if (desktopPath) { + NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs"; + NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName]; + + NSFileManager *fileMgr = [NSFileManager defaultManager]; + BOOL isDir; + BOOL doesFolderExist = [fileMgr fileExistsAtPath:logsFolderPath isDirectory:&isDir]; + if (!doesFolderExist) { + // make the directory + doesFolderExist = [fileMgr createDirectoryAtPath:logsFolderPath + withIntermediateDirectories:YES + attributes:nil + error:NULL]; + if (doesFolderExist) { + // The directory has been created. Exclude it from backups. + NSURL *pathURL = [NSURL fileURLWithPath:logsFolderPath isDirectory:YES]; + [pathURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL]; + } + } + + if (doesFolderExist) { + // it's there; store it in the global + gLoggingDirectoryPath = [logsFolderPath copy]; + } + } + } + return gLoggingDirectoryPath; +} + ++ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun { + // Set the path for this run's logs. + gLogDirectoryForCurrentRun = [logDirectoryForCurrentRun copy]; +} + ++ (NSString *)logDirectoryForCurrentRun { + // make a directory for this run's logs, like SyncProto_logs_10-16_01-56-58PM + if (gLogDirectoryForCurrentRun) return gLogDirectoryForCurrentRun; + + NSString *parentDir = [self loggingDirectory]; + NSString *logNamePrefix = [self processNameLogPrefix]; + NSString *dateStamp = [self loggingDateStamp]; + NSString *dirName = [NSString stringWithFormat:@"%@%@", logNamePrefix, dateStamp]; + NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName]; + + if (gIsLoggingToFile) { + NSFileManager *fileMgr = [NSFileManager defaultManager]; + // Be sure that the first time this app runs, it's not writing to a preexisting folder + static BOOL gShouldReuseFolder = NO; + if (!gShouldReuseFolder) { + gShouldReuseFolder = YES; + NSString *origLogDir = logDirectory; + for (int ctr = 2; ctr < 20; ++ctr) { + if (![fileMgr fileExistsAtPath:logDirectory]) break; + + // append a digit + logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr]; + } + } + if (![fileMgr createDirectoryAtPath:logDirectory + withIntermediateDirectories:YES + attributes:nil + error:NULL]) return nil; + } + gLogDirectoryForCurrentRun = logDirectory; + + return gLogDirectoryForCurrentRun; +} + ++ (void)setLoggingEnabled:(BOOL)isLoggingEnabled { + gIsLoggingEnabled = isLoggingEnabled; +} + ++ (BOOL)isLoggingEnabled { + return gIsLoggingEnabled; +} + ++ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled { + gIsLoggingToFile = isLoggingToFileEnabled; +} + ++ (BOOL)isLoggingToFileEnabled { + return gIsLoggingToFile; +} + ++ (void)setLoggingProcessName:(NSString *)processName { + gLoggingProcessName = [processName copy]; +} + ++ (NSString *)loggingProcessName { + // get the process name (once per run) replacing spaces with underscores + if (!gLoggingProcessName) { + NSString *procName = [[NSProcessInfo processInfo] processName]; + gLoggingProcessName = [procName stringByReplacingOccurrencesOfString:@" " withString:@"_"]; + } + return gLoggingProcessName; +} + ++ (void)setLoggingDateStamp:(NSString *)dateStamp { + gLoggingDateStamp = [dateStamp copy]; +} + ++ (NSString *)loggingDateStamp { + // We'll pick one date stamp per run, so a run that starts at a later second + // will get a unique results html file + if (!gLoggingDateStamp) { + // produce a string like 08-21_01-41-23PM + + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [formatter setDateFormat:@"M-dd_hh-mm-ssa"]; + + gLoggingDateStamp = [formatter stringFromDate:[NSDate date]]; + } + return gLoggingDateStamp; +} + ++ (NSString *)processNameLogPrefix { + static NSString *gPrefix = nil; + if (!gPrefix) { + NSString *processName = [self loggingProcessName]; + gPrefix = [[NSString alloc] initWithFormat:@"%@_log_", processName]; + } + return gPrefix; +} + ++ (NSString *)symlinkNameSuffix { + return @"_log_newest.html"; +} + ++ (NSString *)htmlFileName { + return @"aperçu_http_log.html"; +} + ++ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)cutoffDate { + NSFileManager *fileMgr = [NSFileManager defaultManager]; + NSURL *parentDir = [NSURL fileURLWithPath:[[self class] loggingDirectory]]; + NSURL *logDirectoryForCurrentRun = + [NSURL fileURLWithPath:[[self class] logDirectoryForCurrentRun]]; + NSError *error; + NSArray *contents = [fileMgr contentsOfDirectoryAtURL:parentDir + includingPropertiesForKeys:@[ NSURLContentModificationDateKey ] + options:0 + error:&error]; + for (NSURL *itemURL in contents) { + if ([itemURL isEqual:logDirectoryForCurrentRun]) continue; + + NSDate *modDate; + if ([itemURL getResourceValue:&modDate + forKey:NSURLContentModificationDateKey + error:&error]) { + if ([modDate compare:cutoffDate] == NSOrderedAscending) { + if (![fileMgr removeItemAtURL:itemURL error:&error]) { + NSLog(@"deleteLogDirectoriesOlderThanDate failed to delete %@: %@", + itemURL.path, error); + } + } + } else { + NSLog(@"deleteLogDirectoriesOlderThanDate failed to get mod date of %@: %@", + itemURL.path, error); + } + } +} + +// formattedStringFromData returns a prettyprinted string for XML or JSON input, +// and a plain string for other input data +- (NSString *)formattedStringFromData:(NSData *)inputData + contentType:(NSString *)contentType + JSON:(NSDictionary **)outJSON { + if (!inputData) return nil; + + // if the content type is JSON and we have the parsing class available, use that + if ([contentType hasPrefix:@"application/json"] && inputData.length > 5) { + // convert from JSON string to NSObjects and back to a formatted string + NSMutableDictionary *obj = [NSJSONSerialization JSONObjectWithData:inputData + options:NSJSONReadingMutableContainers + error:NULL]; + if (obj) { + if (outJSON) *outJSON = obj; + if ([obj isKindOfClass:[NSMutableDictionary class]]) { + // for security and privacy, omit OAuth 2 response access and refresh tokens + if ([obj valueForKey:@"refresh_token"] != nil) { + [obj setObject:@"_snip_" forKey:@"refresh_token"]; + } + if ([obj valueForKey:@"access_token"] != nil) { + [obj setObject:@"_snip_" forKey:@"access_token"]; + } + } + NSData *data = [NSJSONSerialization dataWithJSONObject:obj + options:NSJSONWritingPrettyPrinted + error:NULL]; + if (data) { + NSString *jsonStr = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + return jsonStr; + } + } + } + +#if !TARGET_OS_IPHONE && !GTM_SKIP_LOG_XMLFORMAT + // verify that this data starts with the bytes indicating XML + + NSString *const kXMLLintPath = @"/usr/bin/xmllint"; + static BOOL gHasCheckedAvailability = NO; + static BOOL gIsXMLLintAvailable = NO; + + if (!gHasCheckedAvailability) { + gIsXMLLintAvailable = [[NSFileManager defaultManager] fileExistsAtPath:kXMLLintPath]; + gHasCheckedAvailability = YES; + } + if (gIsXMLLintAvailable + && inputData.length > 5 + && strncmp(inputData.bytes, " 0) { + // success + inputData = formattedData; + } + } +#else + // we can't call external tasks on the iPhone; leave the XML unformatted +#endif + + NSString *dataStr = [[NSString alloc] initWithData:inputData + encoding:NSUTF8StringEncoding]; + return dataStr; +} + +// stringFromStreamData creates a string given the supplied data +// +// If NSString can create a UTF-8 string from the data, then that is returned. +// +// Otherwise, this routine tries to find a MIME boundary at the beginning of the data block, and +// uses that to break up the data into parts. Each part will be used to try to make a UTF-8 string. +// For parts that fail, a replacement string showing the part header and <> is supplied +// in place of the binary data. + +- (NSString *)stringFromStreamData:(NSData *)data + contentType:(NSString *)contentType { + + if (!data) return nil; + + // optimistically, see if the whole data block is UTF-8 + NSString *streamDataStr = [self formattedStringFromData:data + contentType:contentType + JSON:NULL]; + if (streamDataStr) return streamDataStr; + + // Munge a buffer by replacing non-ASCII bytes with underscores, and turn that munged buffer an + // NSString. That gives us a string we can use with NSScanner. + NSMutableData *mutableData = [NSMutableData dataWithData:data]; + unsigned char *bytes = (unsigned char *)mutableData.mutableBytes; + + for (unsigned int idx = 0; idx < mutableData.length; ++idx) { + if (bytes[idx] > 0x7F || bytes[idx] == 0) { + bytes[idx] = '_'; + } + } + + NSString *mungedStr = [[NSString alloc] initWithData:mutableData + encoding:NSUTF8StringEncoding]; + if (mungedStr) { + + // scan for the boundary string + NSString *boundary = nil; + NSScanner *scanner = [NSScanner scannerWithString:mungedStr]; + + if ([scanner scanUpToString:@"\r\n" intoString:&boundary] + && [boundary hasPrefix:@"--"]) { + + // we found a boundary string; use it to divide the string into parts + NSArray *mungedParts = [mungedStr componentsSeparatedByString:boundary]; + + // look at each munged part in the original string, and try to convert those into UTF-8 + NSMutableArray *origParts = [NSMutableArray array]; + NSUInteger offset = 0; + for (NSString *mungedPart in mungedParts) { + NSUInteger partSize = mungedPart.length; + NSData *origPartData = [data subdataWithRange:NSMakeRange(offset, partSize)]; + NSString *origPartStr = [[NSString alloc] initWithData:origPartData + encoding:NSUTF8StringEncoding]; + if (origPartStr) { + // we could make this original part into UTF-8; use the string + [origParts addObject:origPartStr]; + } else { + // this part can't be made into UTF-8; scan the header, if we can + NSString *header = nil; + NSScanner *headerScanner = [NSScanner scannerWithString:mungedPart]; + if (![headerScanner scanUpToString:@"\r\n\r\n" intoString:&header]) { + // we couldn't find a header + header = @""; + } + // make a part string with the header and <> + NSString *binStr = [NSString stringWithFormat:@"\r%@\r<<%lu bytes>>\r", + header, (long)(partSize - header.length)]; + [origParts addObject:binStr]; + } + offset += partSize + boundary.length; + } + // rejoin the original parts + streamDataStr = [origParts componentsJoinedByString:boundary]; + } + } + if (!streamDataStr) { + // give up; just make a string showing the uploaded bytes + streamDataStr = [NSString stringWithFormat:@"<<%u bytes>>", (unsigned int)data.length]; + } + return streamDataStr; +} + +// logFetchWithError is called following a successful or failed fetch attempt +// +// This method does all the work for appending to and creating log files + +- (void)logFetchWithError:(NSError *)error { + if (![[self class] isLoggingEnabled]) return; + NSString *logDirectory = [[self class] logDirectoryForCurrentRun]; + if (!logDirectory) return; + NSString *processName = [[self class] loggingProcessName]; + + // TODO: add Javascript to display response data formatted in hex + + // each response's NSData goes into its own xml or txt file, though all responses for this run of + // the app share a main html file. This counter tracks all fetch responses for this app run. + // + // we'll use a local variable since this routine may be reentered while waiting for XML formatting + // to be completed by an external task + static int gResponseCounter = 0; + int responseCounter = ++gResponseCounter; + + NSURLResponse *response = [self response]; + NSDictionary *responseHeaders = [self responseHeaders]; + NSString *responseDataStr = nil; + NSDictionary *responseJSON = nil; + + // if there's response data, decide what kind of file to put it in based on the first bytes of the + // file or on the mime type supplied by the server + NSString *responseMIMEType = [response MIMEType]; + BOOL isResponseImage = NO; + + // file name for an image data file + NSString *responseDataFileName = nil; + + int64_t responseDataLength = self.downloadedLength; + if (responseDataLength > 0) { + NSData *downloadedData = self.downloadedData; + if (downloadedData == nil + && responseDataLength > 0 + && responseDataLength < 20000 + && self.destinationFileURL) { + // There's a download file that's not too big, so get the data to display from the downloaded + // file. + NSURL *destinationURL = self.destinationFileURL; + downloadedData = [NSData dataWithContentsOfURL:destinationURL]; + } + NSString *responseType = [responseHeaders valueForKey:@"Content-Type"]; + responseDataStr = [self formattedStringFromData:downloadedData + contentType:responseType + JSON:&responseJSON]; + NSString *responseDataExtn = nil; + NSData *dataToWrite = nil; + if (responseDataStr) { + // we were able to make a UTF-8 string from the response data + if ([responseMIMEType isEqual:@"application/atom+xml"] + || [responseMIMEType hasSuffix:@"/xml"]) { + responseDataExtn = @"xml"; + dataToWrite = [responseDataStr dataUsingEncoding:NSUTF8StringEncoding]; + } + } else if ([responseMIMEType isEqual:@"image/jpeg"]) { + responseDataExtn = @"jpg"; + dataToWrite = downloadedData; + isResponseImage = YES; + } else if ([responseMIMEType isEqual:@"image/gif"]) { + responseDataExtn = @"gif"; + dataToWrite = downloadedData; + isResponseImage = YES; + } else if ([responseMIMEType isEqual:@"image/png"]) { + responseDataExtn = @"png"; + dataToWrite = downloadedData; + isResponseImage = YES; + } else { + // add more non-text types here + } + // if we have an extension, save the raw data in a file with that extension + if (responseDataExtn && dataToWrite) { + // generate a response file base name like + NSString *responseBaseName = [NSString stringWithFormat:@"fetch_%d_response", responseCounter]; + responseDataFileName = [responseBaseName stringByAppendingPathExtension:responseDataExtn]; + NSString *responseDataFilePath = [logDirectory stringByAppendingPathComponent:responseDataFileName]; + + NSError *downloadedError = nil; + if (gIsLoggingToFile && ![dataToWrite writeToFile:responseDataFilePath + options:0 + error:&downloadedError]) { + NSLog(@"%@ logging write error:%@ (%@)", [self class], downloadedError, responseDataFileName); + } + } + } + // we'll have one main html file per run of the app + NSString *htmlName = [[self class] htmlFileName]; + NSString *htmlPath =[logDirectory stringByAppendingPathComponent:htmlName]; + + // if the html file exists (from logging previous fetches) we don't need + // to re-write the header or the scripts + NSFileManager *fileMgr = [NSFileManager defaultManager]; + BOOL didFileExist = [fileMgr fileExistsAtPath:htmlPath]; + + NSMutableString* outputHTML = [NSMutableString string]; + + // we need a header to say we'll have UTF-8 text + if (!didFileExist) { + [outputHTML appendFormat:@"%@ HTTP fetch log %@", + processName, [[self class] loggingDateStamp]]; + } + // now write the visible html elements + NSString *copyableFileName = [NSString stringWithFormat:@"fetch_%d.txt", responseCounter]; + + NSDate *now = [NSDate date]; + // write the date & time, the comment, and the link to the plain-text (copyable) log + [outputHTML appendFormat:@"%@      ", now]; + + NSString *comment = [self comment]; + if (comment.length > 0) { + [outputHTML appendFormat:@"%@      ", comment]; + } + [outputHTML appendFormat:@"request/response log
", copyableFileName]; + NSTimeInterval elapsed = -self.initialBeginFetchDate.timeIntervalSinceNow; + [outputHTML appendFormat:@"elapsed: %5.3fsec
", elapsed]; + + // write the request URL + NSURLRequest *request = self.request; + NSString *requestMethod = request.HTTPMethod; + NSURL *requestURL = request.URL; + + // Save the request URL for next time in case this redirects. + NSString *redirectedFromURLString = [self.redirectedFromURL absoluteString]; + self.redirectedFromURL = [requestURL copy]; + if (redirectedFromURLString) { + [outputHTML appendFormat:@"redirected from %@
", + redirectedFromURLString]; + } + [outputHTML appendFormat:@"request: %@ %@
\n", requestMethod, requestURL]; + + // write the request headers + NSDictionary *requestHeaders = request.allHTTPHeaderFields; + NSUInteger numberOfRequestHeaders = requestHeaders.count; + if (numberOfRequestHeaders > 0) { + // Indicate if the request is authorized; warn if the request is authorized but non-SSL + NSString *auth = [requestHeaders objectForKey:@"Authorization"]; + NSString *headerDetails = @""; + if (auth) { + BOOL isInsecure = [[requestURL scheme] isEqual:@"http"]; + if (isInsecure) { + // 26A0 = ⚠ + headerDetails = + @"   authorized, non-SSL "; + } else { + headerDetails = @"   authorized"; + } + } + NSString *cookiesHdr = [requestHeaders objectForKey:@"Cookie"]; + if (cookiesHdr) { + headerDetails = [headerDetails stringByAppendingString:@"   cookies"]; + } + NSString *matchHdr = [requestHeaders objectForKey:@"If-Match"]; + if (matchHdr) { + headerDetails = [headerDetails stringByAppendingString:@"   if-match"]; + } + matchHdr = [requestHeaders objectForKey:@"If-None-Match"]; + if (matchHdr) { + headerDetails = [headerDetails stringByAppendingString:@"   if-none-match"]; + } + [outputHTML appendFormat:@"   headers: %d %@
", + (int)numberOfRequestHeaders, headerDetails]; + } else { + [outputHTML appendFormat:@"   headers: none
"]; + } + // write the request post data + NSData *bodyData = nil; + NSData *loggedStreamData = self.loggedStreamData; + if (loggedStreamData) { + bodyData = loggedStreamData; + } else { + bodyData = self.bodyData; + if (bodyData == nil) { + bodyData = self.request.HTTPBody; + } + } + uint64_t bodyDataLength = bodyData.length; + + if (bodyData.length == 0) { + // If the data is in a body upload file URL, read that in if it's not huge. + NSURL *bodyFileURL = self.bodyFileURL; + if (bodyFileURL) { + NSNumber *fileSizeNum = nil; + NSError *fileSizeError = nil; + if ([bodyFileURL getResourceValue:&fileSizeNum + forKey:NSURLFileSizeKey + error:&fileSizeError]) { + bodyDataLength = [fileSizeNum unsignedLongLongValue]; + if (bodyDataLength > 0 && bodyDataLength < 50000) { + bodyData = [NSData dataWithContentsOfURL:bodyFileURL + options:NSDataReadingUncached + error:&fileSizeError]; + } + } + } + } + NSString *bodyDataStr = nil; + NSString *postType = [requestHeaders valueForKey:@"Content-Type"]; + + if (bodyDataLength > 0) { + [outputHTML appendFormat:@"   data: %llu bytes, %@
\n", + bodyDataLength, postType ? postType : @"(no type)"]; + NSString *logRequestBody = self.logRequestBody; + if (logRequestBody) { + bodyDataStr = [logRequestBody copy]; + self.logRequestBody = nil; + } else { + bodyDataStr = [self stringFromStreamData:bodyData + contentType:postType]; + if (bodyDataStr) { + // remove OAuth 2 client secret and refresh token + bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr + betweenStartString:@"client_secret=" + endString:@"&"]; + bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr + betweenStartString:@"refresh_token=" + endString:@"&"]; + // remove ClientLogin password + bodyDataStr = [[self class] snipSubstringOfString:bodyDataStr + betweenStartString:@"&Passwd=" + endString:@"&"]; + } + } + } else { + // no post data + } + // write the response status, MIME type, URL + NSInteger status = [self statusCode]; + if (response) { + NSString *statusString = @""; + if (status != 0) { + if (status == 200 || status == 201) { + statusString = [NSString stringWithFormat:@"%ld", (long)status]; + + // report any JSON-RPC error + if ([responseJSON isKindOfClass:[NSDictionary class]]) { + NSDictionary *jsonError = [responseJSON objectForKey:@"error"]; + if ([jsonError isKindOfClass:[NSDictionary class]]) { + NSString *jsonCode = [[jsonError valueForKey:@"code"] description]; + NSString *jsonMessage = [jsonError valueForKey:@"message"]; + if (jsonCode || jsonMessage) { + // 2691 = ⚑ + NSString *const jsonErrFmt = + @"   JSON error: %@ %@  ⚑"; + statusString = [statusString stringByAppendingFormat:jsonErrFmt, + jsonCode ? jsonCode : @"", + jsonMessage ? jsonMessage : @""]; + } + } + } + } else { + // purple for anything other than 200 or 201 + NSString *flag = status >= 400 ? @" ⚑" : @""; // 2691 = ⚑ + NSString *explanation = [NSHTTPURLResponse localizedStringForStatusCode:status]; + NSString *const statusFormat = @"%ld %@ %@"; + statusString = [NSString stringWithFormat:statusFormat, (long)status, explanation, flag]; + } + } + // show the response URL only if it's different from the request URL + NSString *responseURLStr = @""; + NSURL *responseURL = response.URL; + + if (responseURL && ![responseURL isEqual:request.URL]) { + NSString *const responseURLFormat = + @"response URL: %@
\n"; + responseURLStr = [NSString stringWithFormat:responseURLFormat, [responseURL absoluteString]]; + } + [outputHTML appendFormat:@"response:  status %@
\n%@", + statusString, responseURLStr]; + // Write the response headers + NSUInteger numberOfResponseHeaders = responseHeaders.count; + if (numberOfResponseHeaders > 0) { + // Indicate if the server is setting cookies + NSString *cookiesSet = [responseHeaders valueForKey:@"Set-Cookie"]; + NSString *cookiesStr = + cookiesSet ? @"  sets cookies" : @""; + // Indicate if the server is redirecting + NSString *location = [responseHeaders valueForKey:@"Location"]; + BOOL isRedirect = status >= 300 && status <= 399 && location != nil; + NSString *redirectsStr = + isRedirect ? @"  redirects" : @""; + [outputHTML appendFormat:@"   headers: %d %@ %@
\n", + (int)numberOfResponseHeaders, cookiesStr, redirectsStr]; + } else { + [outputHTML appendString:@"   headers: none
\n"]; + } + } + // error + if (error) { + [outputHTML appendFormat:@"Error: %@
\n", error.description]; + } + // Write the response data + if (responseDataFileName) { + if (isResponseImage) { + // Make a small inline image that links to the full image file + [outputHTML appendFormat:@"   data: %lld bytes, %@
", + responseDataLength, responseMIMEType]; + NSString *const fmt = + @"image\n"; + [outputHTML appendFormat:fmt, responseDataFileName, responseDataFileName]; + } else { + // The response data was XML; link to the xml file + NSString *const fmt = + @"   data: %lld bytes, %@   %@\n"; + [outputHTML appendFormat:fmt, responseDataLength, responseMIMEType, + responseDataFileName, [responseDataFileName pathExtension]]; + } + } else { + // The response data was not an image; just show the length and MIME type + [outputHTML appendFormat:@"   data: %lld bytes, %@\n", + responseDataLength, responseMIMEType ? responseMIMEType : @"(no response type)"]; + } + // Make a single string of the request and response, suitable for copying + // to the clipboard and pasting into a bug report + NSMutableString *copyable = [NSMutableString string]; + if (comment) { + [copyable appendFormat:@"%@\n\n", comment]; + } + [copyable appendFormat:@"%@ elapsed: %5.3fsec\n", now, elapsed]; + if (redirectedFromURLString) { + [copyable appendFormat:@"Redirected from %@\n", redirectedFromURLString]; + } + [copyable appendFormat:@"Request: %@ %@\n", requestMethod, requestURL]; + if (requestHeaders.count > 0) { + [copyable appendFormat:@"Request headers:\n%@\n", + [[self class] headersStringForDictionary:requestHeaders]]; + } + if (bodyDataLength > 0) { + [copyable appendFormat:@"Request body: (%llu bytes)\n", bodyDataLength]; + if (bodyDataStr) { + [copyable appendFormat:@"%@\n", bodyDataStr]; + } + [copyable appendString:@"\n"]; + } + if (response) { + [copyable appendFormat:@"Response: status %d\n", (int) status]; + [copyable appendFormat:@"Response headers:\n%@\n", + [[self class] headersStringForDictionary:responseHeaders]]; + [copyable appendFormat:@"Response body: (%lld bytes)\n", responseDataLength]; + if (responseDataLength > 0) { + NSString *logResponseBody = self.logResponseBody; + if (logResponseBody) { + // The user has provided the response body text. + responseDataStr = [logResponseBody copy]; + self.logResponseBody = nil; + } + if (responseDataStr != nil) { + [copyable appendFormat:@"%@\n", responseDataStr]; + } else { + // Even though it's redundant, we'll put in text to indicate that all the bytes are binary. + if (self.destinationFileURL) { + [copyable appendFormat:@"<<%lld bytes>> to file %@\n", + responseDataLength, self.destinationFileURL.path]; + } else { + [copyable appendFormat:@"<<%lld bytes>>\n", responseDataLength]; + } + } + } + } + if (error) { + [copyable appendFormat:@"Error: %@\n", error]; + } + // Save to log property before adding the separator + self.log = copyable; + + [copyable appendString:@"-----------------------------------------------------------\n"]; + + // Write the copyable version to another file (linked to at the top of the html file, above) + // + // Ideally, something to just copy this to the clipboard like + // Copy here." + // would work everywhere, but it only works in Safari as of 8/2010 + if (gIsLoggingToFile) { + NSString *parentDir = [[self class] loggingDirectory]; + NSString *copyablePath = [logDirectory stringByAppendingPathComponent:copyableFileName]; + NSError *copyableError = nil; + if (![copyable writeToFile:copyablePath + atomically:NO + encoding:NSUTF8StringEncoding + error:©ableError]) { + // Error writing to file + NSLog(@"%@ logging write error:%@ (%@)", [self class], copyableError, copyablePath); + } + [outputHTML appendString:@"

"]; + + // Append the HTML to the main output file + const char* htmlBytes = outputHTML.UTF8String; + NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:htmlPath + append:YES]; + [stream open]; + [stream write:(const uint8_t *) htmlBytes maxLength:strlen(htmlBytes)]; + [stream close]; + + // Make a symlink to the latest html + NSString *const symlinkNameSuffix = [[self class] symlinkNameSuffix]; + NSString *symlinkName = [processName stringByAppendingString:symlinkNameSuffix]; + NSString *symlinkPath = [parentDir stringByAppendingPathComponent:symlinkName]; + + [fileMgr removeItemAtPath:symlinkPath error:NULL]; + [fileMgr createSymbolicLinkAtPath:symlinkPath + withDestinationPath:htmlPath + error:NULL]; +#if TARGET_OS_IPHONE + static BOOL gReportedLoggingPath = NO; + if (!gReportedLoggingPath) { + gReportedLoggingPath = YES; + NSLog(@"GTMSessionFetcher logging to \"%@\"", parentDir); + } +#endif + } +} + +- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream { + if (!inputStream) return nil; + if (![GTMSessionFetcher isLoggingEnabled]) return inputStream; + + [self clearLoggedStreamData]; // Clear any previous data. + Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream"); + if (!monitorClass) { + NSString const *str = @"<>"; + NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding]; + [self appendLoggedStreamData:stringData]; + return inputStream; + } + inputStream = [monitorClass inputStreamWithStream:inputStream]; + + GTMReadMonitorInputStream *readMonitorInputStream = (GTMReadMonitorInputStream *)inputStream; + [readMonitorInputStream setReadDelegate:self]; + SEL readSel = @selector(inputStream:readIntoBuffer:length:); + [readMonitorInputStream setReadSelector:readSel]; + + return inputStream; +} + +- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: + (GTMSessionFetcherBodyStreamProvider)streamProvider { + if (!streamProvider) return nil; + if (![GTMSessionFetcher isLoggingEnabled]) return streamProvider; + + [self clearLoggedStreamData]; // Clear any previous data. + Class monitorClass = NSClassFromString(@"GTMReadMonitorInputStream"); + if (!monitorClass) { + NSString const *str = @"<>"; + NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding]; + [self appendLoggedStreamData:stringData]; + return streamProvider; + } + GTMSessionFetcherBodyStreamProvider loggedStreamProvider = + ^(GTMSessionFetcherBodyStreamProviderResponse response) { + streamProvider(^(NSInputStream *bodyStream) { + bodyStream = [self loggedInputStreamForInputStream:bodyStream]; + response(bodyStream); + }); + }; + return loggedStreamProvider; +} + +@end + +@implementation GTMSessionFetcher (GTMSessionFetcherLoggingUtilities) + +- (void)inputStream:(GTMReadMonitorInputStream *)stream + readIntoBuffer:(void *)buffer + length:(int64_t)length { + // append the captured data + NSData *data = [NSData dataWithBytesNoCopy:buffer + length:(NSUInteger)length + freeWhenDone:NO]; + [self appendLoggedStreamData:data]; +} + +#pragma mark Fomatting Utilities + ++ (NSString *)snipSubstringOfString:(NSString *)originalStr + betweenStartString:(NSString *)startStr + endString:(NSString *)endStr { +#if SKIP_GTM_FETCH_LOGGING_SNIPPING + return originalStr; +#else + if (!originalStr) return nil; + + // Find the start string, and replace everything between it + // and the end string (or the end of the original string) with "_snip_" + NSRange startRange = [originalStr rangeOfString:startStr]; + if (startRange.location == NSNotFound) return originalStr; + + // We found the start string + NSUInteger originalLength = originalStr.length; + NSUInteger startOfTarget = NSMaxRange(startRange); + NSRange targetAndRest = NSMakeRange(startOfTarget, originalLength - startOfTarget); + NSRange endRange = [originalStr rangeOfString:endStr + options:0 + range:targetAndRest]; + NSRange replaceRange; + if (endRange.location == NSNotFound) { + // Found no end marker so replace to end of string + replaceRange = targetAndRest; + } else { + // Replace up to the endStr + replaceRange = NSMakeRange(startOfTarget, endRange.location - startOfTarget); + } + NSString *result = [originalStr stringByReplacingCharactersInRange:replaceRange + withString:@"_snip_"]; + return result; +#endif // SKIP_GTM_FETCH_LOGGING_SNIPPING +} + ++ (NSString *)headersStringForDictionary:(NSDictionary *)dict { + // Format the dictionary in http header style, like + // Accept: application/json + // Cache-Control: no-cache + // Content-Type: application/json; charset=utf-8 + // + // Pad the key names, but not beyond 16 chars, since long custom header + // keys just create too much whitespace + NSArray *keys = [dict.allKeys sortedArrayUsingSelector:@selector(compare:)]; + + NSMutableString *str = [NSMutableString string]; + for (NSString *key in keys) { + NSString *value = [dict valueForKey:key]; + if ([key isEqual:@"Authorization"]) { + // Remove OAuth 1 token + value = [[self class] snipSubstringOfString:value + betweenStartString:@"oauth_token=\"" + endString:@"\""]; + + // Remove OAuth 2 bearer token (draft 16, and older form) + value = [[self class] snipSubstringOfString:value + betweenStartString:@"Bearer " + endString:@"\n"]; + value = [[self class] snipSubstringOfString:value + betweenStartString:@"OAuth " + endString:@"\n"]; + + // Remove Google ClientLogin + value = [[self class] snipSubstringOfString:value + betweenStartString:@"GoogleLogin auth=" + endString:@"\n"]; + } + [str appendFormat:@" %@: %@\n", key, value]; + } + return str; +} + +@end + +#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h new file mode 100644 index 00000000..312abaa7 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h @@ -0,0 +1,196 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +// For best performance and convenient usage, fetchers should be generated by a common +// GTMSessionFetcherService instance, like +// +// _fetcherService = [[GTMSessionFetcherService alloc] init]; +// GTMSessionFetcher* myFirstFetcher = [_fetcherService fetcherWithRequest:request1]; +// GTMSessionFetcher* mySecondFetcher = [_fetcherService fetcherWithRequest:request2]; + +#import "GTMSessionFetcher.h" + +GTM_ASSUME_NONNULL_BEGIN + +// Notifications. + +// This notification indicates a reusable session has become invalid. It is intended mainly for the +// service's unit tests. +// +// The notification object is the fetcher service. +// The invalid session is provided via the userInfo kGTMSessionFetcherServiceSessionKey key. +extern NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification; +extern NSString *const kGTMSessionFetcherServiceSessionKey; + +@interface GTMSessionFetcherService : NSObject + +// Queues of delayed and running fetchers. Each dictionary contains arrays +// of GTMSessionFetcher *fetchers, keyed by NSString *host +@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *delayedFetchersByHost; +@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *runningFetchersByHost; + +// A max value of 0 means no fetchers should be delayed. +// The default limit is 10 simultaneous fetchers targeting each host. +// This does not apply to fetchers whose useBackgroundSession property is YES. Since services are +// not resurrected on an app relaunch, delayed fetchers would effectively be abandoned. +@property(atomic, assign) NSUInteger maxRunningFetchersPerHost; + +// Properties to be applied to each fetcher; see GTMSessionFetcher.h for descriptions +@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration; +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock; +@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage; +@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue; +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock; +@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential; +@property(atomic, strong) NSURLCredential *proxyCredential; +@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes; +@property(atomic, assign) BOOL allowLocalhostRequest; +@property(atomic, assign) BOOL allowInvalidServerCertificates; +@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled; +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock; +@property(atomic, assign) NSTimeInterval maxRetryInterval; +@property(atomic, assign) NSTimeInterval minRetryInterval; +@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties; +@property(atomic, copy, GTM_NULLABLE) + GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE( + ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)); + +#if GTM_BACKGROUND_TASK_FETCHING +@property(atomic, assign) BOOL skipBackgroundTask; +#endif + +// A default useragent of GTMFetcherStandardUserAgentString(nil) will be given to each fetcher +// created by this service unless the request already has a user-agent header set. +// This default will be added starting with builds with the SDKs for OS X 10.11 and iOS 9. +// +// To use the configuration's default user agent, set this property to nil. +@property(atomic, copy, GTM_NULLABLE) NSString *userAgent; + +// The authorizer to attach to the created fetchers. If a specific fetcher should +// not authorize its requests, the fetcher's authorizer property may be set to nil +// before the fetch begins. +@property(atomic, strong, GTM_NULLABLE) id authorizer; + +// Delegate queue used by the session when calling back to the fetcher. The default +// is the main queue. Changing this does not affect the queue used to call back to the +// application; that is specified by the callbackQueue property above. +@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue; + +// When enabled, indicates the same session should be used by subsequent fetchers. +// +// This is enabled by default. +@property(atomic, assign) BOOL reuseSession; + +// Sets the delay until an unused session is invalidated. +// The default interval is 60 seconds. +// +// If the interval is set to 0, then any reused session is not invalidated except by +// explicitly invoking -resetSession. Be aware that setting the interval to 0 thus +// causes the session's delegate to be retained until the session is explicitly reset. +@property(atomic, assign) NSTimeInterval unusedSessionTimeout; + +// If shouldReuseSession is enabled, this will force creation of a new session when future +// fetchers begin. +- (void)resetSession; + +// Create a fetcher +// +// These methods will return a fetcher. If successfully created, the connection +// will hold a strong reference to it for the life of the connection as well. +// So the caller doesn't have to hold onto the fetcher explicitly unless they +// want to be able to monitor or cancel it. +- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request; +- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL; +- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString; + +// Common method for fetcher creation. +// +// -fetcherWithRequest:fetcherClass: may be overridden to customize creation of +// fetchers. This is the ONLY method in the GTMSessionFetcher library intended to +// be overridden. +- (id)fetcherWithRequest:(NSURLRequest *)request + fetcherClass:(Class)fetcherClass; + +- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher; + +- (NSUInteger)numberOfFetchers; // running + delayed fetchers +- (NSUInteger)numberOfRunningFetchers; +- (NSUInteger)numberOfDelayedFetchers; + +// Return a list of all running or delayed fetchers. This includes fetchers created +// by the service which have been started and have not yet stopped. +// +// Returns an array of fetcher objects, or nil if none. +- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchers; + +// Search for running or delayed fetchers with the specified URL. +// +// Returns an array of fetcher objects found, or nil if none found. +- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchersWithRequestURL:(NSURL *)requestURL; + +- (void)stopAllFetchers; + +// Methods for use by the fetcher class only. +- (GTM_NULLABLE NSURLSession *)session; +- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation; +- (GTM_NULLABLE id)sessionDelegate; +- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate; + +// The testBlock can inspect its fetcher parameter's request property to +// determine which fetcher is being faked. +@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock; + +@end + +@interface GTMSessionFetcherService (TestingSupport) + +// Convenience methods to create a fetcher service for testing. +// +// Fetchers generated by this mock fetcher service will not perform any +// network operation, but will invoke callbacks and provide the supplied data +// or error to the completion handler. +// +// You can make more customized mocks by setting the test block property of the service +// or fetcher; the test block can inspect the fetcher's request or other properties. +// +// See the description of the testBlock property below. ++ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil + fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil; ++ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil + fakedResponse:(NSHTTPURLResponse *)fakedResponse + fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil; + +// Spin the run loop and discard events (or, if not on the main thread, just sleep the thread) +// until all running and delayed fetchers have 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. +// +// Returns NO if timed out. +- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds; + +@end + +@interface GTMSessionFetcherService (BackwardsCompatibilityOnly) + +// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves. +// This method is just for compatibility with the old fetcher. +@property(atomic, assign) NSInteger cookieStorageMethod; + +@end + +GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m new file mode 100644 index 00000000..f9942c01 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m @@ -0,0 +1,1381 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMSessionFetcherService.h" + +NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification + = @"kGTMSessionFetcherServiceSessionBecameInvalidNotification"; +NSString *const kGTMSessionFetcherServiceSessionKey + = @"kGTMSessionFetcherServiceSessionKey"; + +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMSessionFetcher (ServiceMethods) +- (BOOL)beginFetchMayDelay:(BOOL)mayDelay + mayAuthorize:(BOOL)mayAuthorize; +@end +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +@interface GTMSessionFetcherService () + +@property(atomic, strong, readwrite) NSDictionary *delayedFetchersByHost; +@property(atomic, strong, readwrite) NSDictionary *runningFetchersByHost; + +@end + +// Since NSURLSession doesn't support a separate delegate per task (!), instances of this +// class serve as a session delegate trampoline. +// +// This class maps a session's tasks to fetchers, and resends delegate messages to the task's +// fetcher. +@interface GTMSessionFetcherSessionDelegateDispatcher : NSObject + +// The session for the tasks in this dispatcher's task-to-fetcher map. +@property(atomic) NSURLSession *session; + +// The timer interval for invalidating a session that has no active tasks. +@property(atomic) NSTimeInterval discardInterval; + +// The current discard timer. +@property(atomic, readonly) NSTimer *discardTimer; + + +- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService + sessionDiscardInterval:(NSTimeInterval)discardInterval; + +- (void)setFetcher:(GTMSessionFetcher *)fetcher + forTask:(NSURLSessionTask *)task; +- (void)removeFetcher:(GTMSessionFetcher *)fetcher; + +// Before using a session, tells the delegate dispatcher to stop the discard timer. +- (void)startSessionUsage; + +// When abandoning a delegate dispatcher, we want to avoid the session retaining +// the delegate after tasks complete. +- (void)abandon; + +@end + + +@implementation GTMSessionFetcherService { + NSMutableDictionary *_delayedFetchersByHost; + NSMutableDictionary *_runningFetchersByHost; + NSUInteger _maxRunningFetchersPerHost; + + // When this ivar is nil, the service will not reuse sessions. + GTMSessionFetcherSessionDelegateDispatcher *_delegateDispatcher; + + // Fetchers will wait on this if another fetcher is creating the shared NSURLSession. + dispatch_semaphore_t _sessionCreationSemaphore; + + dispatch_queue_t _callbackQueue; + NSOperationQueue *_delegateQueue; + NSHTTPCookieStorage *_cookieStorage; + NSString *_userAgent; + NSTimeInterval _timeout; + + NSURLCredential *_credential; // Username & password. + NSURLCredential *_proxyCredential; // Credential supplied to proxy servers. + + NSInteger _cookieStorageMethod; + + id _authorizer; + + // For waitForCompletionOfAllFetchersWithTimeout: we need to wait on stopped fetchers since + // they've not yet finished invoking their queued callbacks. This array is nil except when + // waiting on fetchers. + NSMutableArray *_stoppedFetchersToWaitFor; + + // For fetchers that enqueued their callbacks before stopAllFetchers was called on the service, + // set a barrier so the callbacks know to bail out. + NSDate *_stoppedAllFetchersDate; +} + +@synthesize maxRunningFetchersPerHost = _maxRunningFetchersPerHost, + configuration = _configuration, + configurationBlock = _configurationBlock, + cookieStorage = _cookieStorage, + userAgent = _userAgent, + challengeBlock = _challengeBlock, + credential = _credential, + proxyCredential = _proxyCredential, + allowedInsecureSchemes = _allowedInsecureSchemes, + allowLocalhostRequest = _allowLocalhostRequest, + allowInvalidServerCertificates = _allowInvalidServerCertificates, + retryEnabled = _retryEnabled, + retryBlock = _retryBlock, + maxRetryInterval = _maxRetryInterval, + minRetryInterval = _minRetryInterval, + metricsCollectionBlock = _metricsCollectionBlock, + properties = _properties, + unusedSessionTimeout = _unusedSessionTimeout, + testBlock = _testBlock; + +#if GTM_BACKGROUND_TASK_FETCHING +@synthesize skipBackgroundTask = _skipBackgroundTask; +#endif + +- (instancetype)init { + self = [super init]; + if (self) { + _delayedFetchersByHost = [[NSMutableDictionary alloc] init]; + _runningFetchersByHost = [[NSMutableDictionary alloc] init]; + _maxRunningFetchersPerHost = 10; + _cookieStorageMethod = -1; + _unusedSessionTimeout = 60.0; + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + _callbackQueue = dispatch_get_main_queue(); + + _delegateQueue = [[NSOperationQueue alloc] init]; + _delegateQueue.maxConcurrentOperationCount = 1; + _delegateQueue.name = @"com.google.GTMSessionFetcher.NSURLSessionDelegateQueue"; + + _sessionCreationSemaphore = dispatch_semaphore_create(1); + + // Starting with the SDKs for OS X 10.11/iOS 9, the service has a default useragent. + // Apps can remove this and get the default system "CFNetwork" useragent by setting the + // fetcher service's userAgent property to nil. +#if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0) + _userAgent = GTMFetcherStandardUserAgentString(nil); +#endif + } + return self; +} + +- (void)dealloc { + [self detachAuthorizer]; + [_delegateDispatcher abandon]; +} + +#pragma mark Generate a new fetcher + +// Clients may override this method. Clients should not override any other library methods. +- (id)fetcherWithRequest:(NSURLRequest *)request + fetcherClass:(Class)fetcherClass { + GTMSessionFetcher *fetcher = [[fetcherClass alloc] initWithRequest:request + configuration:self.configuration]; + fetcher.callbackQueue = self.callbackQueue; + fetcher.sessionDelegateQueue = self.sessionDelegateQueue; + fetcher.challengeBlock = self.challengeBlock; + fetcher.credential = self.credential; + fetcher.proxyCredential = self.proxyCredential; + fetcher.authorizer = self.authorizer; + fetcher.cookieStorage = self.cookieStorage; + fetcher.allowedInsecureSchemes = self.allowedInsecureSchemes; + fetcher.allowLocalhostRequest = self.allowLocalhostRequest; + fetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates; + fetcher.configurationBlock = self.configurationBlock; + fetcher.retryEnabled = self.retryEnabled; + fetcher.retryBlock = self.retryBlock; + fetcher.maxRetryInterval = self.maxRetryInterval; + fetcher.minRetryInterval = self.minRetryInterval; + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) { + fetcher.metricsCollectionBlock = self.metricsCollectionBlock; + } + fetcher.properties = self.properties; + fetcher.service = self; + if (self.cookieStorageMethod >= 0) { + [fetcher setCookieStorageMethod:self.cookieStorageMethod]; + } + +#if GTM_BACKGROUND_TASK_FETCHING + fetcher.skipBackgroundTask = self.skipBackgroundTask; +#endif + + NSString *userAgent = self.userAgent; + if (userAgent.length > 0 + && [request valueForHTTPHeaderField:@"User-Agent"] == nil) { + [fetcher setRequestValue:userAgent + forHTTPHeaderField:@"User-Agent"]; + } + fetcher.testBlock = self.testBlock; + + return fetcher; +} + +- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request { + return [self fetcherWithRequest:request + fetcherClass:[GTMSessionFetcher class]]; +} + +- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL { + return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]]; +} + +- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString { + NSURL *url = [NSURL URLWithString:requestURLString]; + return [self fetcherWithURL:url]; +} + +// Returns a session for the fetcher's host, or nil. +- (NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSURLSession *session = _delegateDispatcher.session; + return session; + } +} + +// Returns a session for the fetcher's host, or nil. For shared sessions, this +// waits on a semaphore, blocking other fetchers while the caller creates the +// session if needed. +- (NSURLSession *)sessionForFetcherCreation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + if (!_delegateDispatcher) { + // This fetcher is creating a non-shared session, so skip the semaphore usage. + return nil; + } + } + + // Wait if another fetcher is currently creating a session; avoid waiting + // inside the @synchronized block, as that can deadlock. + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Before getting the NSURLSession for task creation, it is + // important to invalidate and nil out the session discard timer; otherwise + // the session can be invalidated between when it is returned to the + // fetcher, and when the fetcher attempts to create its NSURLSessionTask. + [_delegateDispatcher startSessionUsage]; + + NSURLSession *session = _delegateDispatcher.session; + if (session) { + // The calling fetcher will receive a preexisting session, so + // we can allow other fetchers to create a session. + dispatch_semaphore_signal(_sessionCreationSemaphore); + } else { + // No existing session was obtained, so the calling fetcher will create the session; + // it *must* invoke fetcherDidCreateSession: to signal the dispatcher's semaphore after + // the session has been created (or fails to be created) to avoid a hang. + } + return session; + } +} + +- (id)sessionDelegate { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateDispatcher; + } +} + +#pragma mark Queue Management + +- (void)addRunningFetcher:(GTMSessionFetcher *)fetcher + forHost:(NSString *)host { + // Add to the array of running fetchers for this host, creating the array if needed. + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + if (runningForHost == nil) { + runningForHost = [NSMutableArray arrayWithObject:fetcher]; + [_runningFetchersByHost setObject:runningForHost forKey:host]; + } else { + [runningForHost addObject:fetcher]; + } +} + +- (void)addDelayedFetcher:(GTMSessionFetcher *)fetcher + forHost:(NSString *)host { + // Add to the array of delayed fetchers for this host, creating the array if needed. + NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + if (delayedForHost == nil) { + delayedForHost = [NSMutableArray arrayWithObject:fetcher]; + [_delayedFetchersByHost setObject:delayedForHost forKey:host]; + } else { + [delayedForHost addObject:fetcher]; + } +} + +- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSString *host = fetcher.request.URL.host; + if (host == nil) { + return NO; + } + NSArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher]; + BOOL isDelayed = (delayedForHost != nil) && (idx != NSNotFound); + return isDelayed; + } +} + +- (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher { + // Entry point from the fetcher + NSURL *requestURL = fetcher.request.URL; + NSString *host = requestURL.host; + + // Addresses "file:///path" case where localhost is the implicit host. + if (host.length == 0 && [requestURL isFileURL]) { + host = @"localhost"; + } + + if (host.length == 0) { + // Data URIs legitimately have no host, reject other hostless URLs. + GTMSESSION_ASSERT_DEBUG([[requestURL scheme] isEqual:@"data"], @"%@ lacks host", fetcher); + return YES; + } + + BOOL shouldBeginResult; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + if (runningForHost != nil + && [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) { + GTMSESSION_ASSERT_DEBUG(NO, @"%@ was already running", fetcher); + return YES; + } + + BOOL shouldRunNow = (fetcher.usingBackgroundSession + || _maxRunningFetchersPerHost == 0 + || _maxRunningFetchersPerHost > + [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]); + if (shouldRunNow) { + [self addRunningFetcher:fetcher forHost:host]; + shouldBeginResult = YES; + } else { + [self addDelayedFetcher:fetcher forHost:host]; + shouldBeginResult = NO; + } + } // @synchronized(self) + + // We'll save the host that serves as the key for this fetcher's array + // to avoid any chance of the underlying request changing, stranding + // the fetcher in the wrong array + fetcher.serviceHost = host; + + return shouldBeginResult; +} + +- (void)startFetcher:(GTMSessionFetcher *)fetcher { + [fetcher beginFetchMayDelay:NO + mayAuthorize:YES]; +} + +// Internal utility. Returns a fetcher's delegate if it's a dispatcher, or nil if the fetcher +// is its own delegate (possibly via proxy) and has no dispatcher. +- (GTMSessionFetcherSessionDelegateDispatcher *)delegateDispatcherForFetcher:(GTMSessionFetcher *)fetcher { + GTMSessionCheckNotSynchronized(self); + + NSURLSession *fetcherSession = fetcher.session; + if (fetcherSession) { + id fetcherDelegate = fetcherSession.delegate; + // If the delegate is non-nil and claims to be a GTMSessionFetcher, there is no dispatcher; + // assume the fetcher is the delegate or has been proxied (some third-party frameworks + // are known to swizzle NSURLSession to proxy its delegate). + BOOL hasDispatcher = (fetcherDelegate != nil && + ![fetcherDelegate isKindOfClass:[GTMSessionFetcher class]]); + if (hasDispatcher) { + GTMSESSION_ASSERT_DEBUG([fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]], + @"Fetcher delegate class: %@", [fetcherDelegate class]); + return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate; + } + } + return nil; +} + +- (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher { + if (fetcher.canShareSession) { + NSURLSession *fetcherSession = fetcher.session; + GTMSESSION_ASSERT_DEBUG(fetcherSession != nil, @"Fetcher missing its session: %@", fetcher); + + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + if (delegateDispatcher) { + GTMSESSION_ASSERT_DEBUG(delegateDispatcher.session == nil, + @"Fetcher made an extra session: %@", fetcher); + + // Save this fetcher's session. + delegateDispatcher.session = fetcherSession; + + // Allow other fetchers to request this session now. + dispatch_semaphore_signal(_sessionCreationSemaphore); + } + } +} + +- (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher { + // If this fetcher has a separate delegate with a shared session, then + // this fetcher should be added to the delegate's map of tasks to fetchers. + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + if (delegateDispatcher) { + GTMSESSION_ASSERT_DEBUG(fetcher.canShareSession, + @"Inappropriate shared session: %@", fetcher); + + // There should already be a session, from this or a previous fetcher. + // + // Sanity check that the fetcher's session is the delegate's shared session. + NSURLSession *sharedSession = delegateDispatcher.session; + NSURLSession *fetcherSession = fetcher.session; + GTMSESSION_ASSERT_DEBUG(sharedSession != nil, @"Missing delegate session: %@", fetcher); + GTMSESSION_ASSERT_DEBUG(fetcherSession == sharedSession, + @"Inconsistent session: %@ %@ (shared: %@)", + fetcher, fetcherSession, sharedSession); + + if (sharedSession != nil && fetcherSession == sharedSession) { + NSURLSessionTask *task = fetcher.sessionTask; + GTMSESSION_ASSERT_DEBUG(task != nil, @"Missing session task: %@", fetcher); + + if (task) { + [delegateDispatcher setFetcher:fetcher + forTask:task]; + } + } + } +} + +- (void)stopFetcher:(GTMSessionFetcher *)fetcher { + [fetcher stopFetching]; +} + +- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher { + // Entry point from the fetcher + NSString *host = fetcher.serviceHost; + if (!host) { + // fetcher has been stopped previously + return; + } + + // This removeFetcher: invocation is a fallback; typically, fetchers are removed from the task + // map when the task completes. + GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher = + [self delegateDispatcherForFetcher:fetcher]; + [delegateDispatcher removeFetcher:fetcher]; + + NSMutableArray *fetchersToStart; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // If a test is waiting for all fetchers to stop, it needs to wait for this one + // to invoke its callbacks on the callback queue. + [_stoppedFetchersToWaitFor addObject:fetcher]; + + NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host]; + [runningForHost removeObject:fetcher]; + + NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host]; + [delayedForHost removeObject:fetcher]; + + while (delayedForHost.count > 0 + && [[self class] numberOfNonBackgroundSessionFetchers:runningForHost] + < _maxRunningFetchersPerHost) { + // Start another delayed fetcher running, scanning for the minimum + // priority value, defaulting to FIFO for equal priorities + GTMSessionFetcher *nextFetcher = nil; + for (GTMSessionFetcher *delayedFetcher in delayedForHost) { + if (nextFetcher == nil + || delayedFetcher.servicePriority < nextFetcher.servicePriority) { + nextFetcher = delayedFetcher; + } + } + + if (nextFetcher) { + [self addRunningFetcher:nextFetcher forHost:host]; + runningForHost = [_runningFetchersByHost objectForKey:host]; + + [delayedForHost removeObjectIdenticalTo:nextFetcher]; + + if (!fetchersToStart) { + fetchersToStart = [NSMutableArray array]; + } + [fetchersToStart addObject:nextFetcher]; + } + } + + if (runningForHost.count == 0) { + // None left; remove the empty array + [_runningFetchersByHost removeObjectForKey:host]; + } + + if (delayedForHost.count == 0) { + [_delayedFetchersByHost removeObjectForKey:host]; + } + } // @synchronized(self) + + // Start fetchers outside of the synchronized block to avoid a deadlock. + for (GTMSessionFetcher *nextFetcher in fetchersToStart) { + [self startFetcher:nextFetcher]; + } + + // The fetcher is no longer in the running or the delayed array, + // so remove its host and thread properties + fetcher.serviceHost = nil; +} + +- (NSUInteger)numberOfFetchers { + NSUInteger running = [self numberOfRunningFetchers]; + NSUInteger delayed = [self numberOfDelayedFetchers]; + return running + delayed; +} + +- (NSUInteger)numberOfRunningFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger sum = 0; + for (NSString *host in _runningFetchersByHost) { + NSArray *fetchers = [_runningFetchersByHost objectForKey:host]; + sum += fetchers.count; + } + return sum; + } +} + +- (NSUInteger)numberOfDelayedFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger sum = 0; + for (NSString *host in _delayedFetchersByHost) { + NSArray *fetchers = [_delayedFetchersByHost objectForKey:host]; + sum += fetchers.count; + } + return sum; + } +} + +- (NSArray *)issuedFetchers { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSMutableArray *allFetchers = [NSMutableArray array]; + void (^accumulateFetchers)(id, id, BOOL *) = ^(NSString *host, + NSArray *fetchersForHost, + BOOL *stop) { + [allFetchers addObjectsFromArray:fetchersForHost]; + }; + [_runningFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers]; + [_delayedFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers]; + + GTMSESSION_ASSERT_DEBUG(allFetchers.count == [NSSet setWithArray:allFetchers].count, + @"Fetcher appears multiple times\n running: %@\n delayed: %@", + _runningFetchersByHost, _delayedFetchersByHost); + + return allFetchers.count > 0 ? allFetchers : nil; + } +} + +- (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL { + NSString *host = requestURL.host; + if (host.length == 0) return nil; + + NSURL *targetURL = [requestURL absoluteURL]; + + NSArray *allFetchers = [self issuedFetchers]; + NSIndexSet *indexes = [allFetchers indexesOfObjectsPassingTest:^BOOL(GTMSessionFetcher *fetcher, + NSUInteger idx, + BOOL *stop) { + NSURL *fetcherURL = [fetcher.request.URL absoluteURL]; + return [fetcherURL isEqual:targetURL]; + }]; + + NSArray *result = nil; + if (indexes.count > 0) { + result = [allFetchers objectsAtIndexes:indexes]; + } + return result; +} + +- (void)stopAllFetchers { + NSArray *delayedFetchersByHost; + NSArray *runningFetchersByHost; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Set the time barrier so fetchers know not to call back even if + // the stop calls below occur after the fetchers naturally + // stopped and so were removed from _runningFetchersByHost, + // but while the callbacks were already enqueued before stopAllFetchers + // was invoked. + _stoppedAllFetchersDate = [[NSDate alloc] init]; + + // Remove fetchers from the delayed list to avoid fetcherDidStop: from + // starting more fetchers running as a side effect of stopping one + delayedFetchersByHost = _delayedFetchersByHost.allValues; + [_delayedFetchersByHost removeAllObjects]; + + runningFetchersByHost = _runningFetchersByHost.allValues; + [_runningFetchersByHost removeAllObjects]; + } + + for (NSArray *delayedForHost in delayedFetchersByHost) { + for (GTMSessionFetcher *fetcher in delayedForHost) { + [self stopFetcher:fetcher]; + } + } + + for (NSArray *runningForHost in runningFetchersByHost) { + for (GTMSessionFetcher *fetcher in runningForHost) { + [self stopFetcher:fetcher]; + } + } +} + +- (NSDate *)stoppedAllFetchersDate { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _stoppedAllFetchersDate; + } +} + +#pragma mark Accessors + +- (BOOL)reuseSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateDispatcher != nil; + } +} + +- (void)setReuseSession:(BOOL)shouldReuse { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + BOOL wasReusing = (_delegateDispatcher != nil); + if (shouldReuse != wasReusing) { + [self abandonDispatcher]; + if (shouldReuse) { + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + } else { + _delegateDispatcher = nil; + } + } + } +} + +- (void)resetSession { + GTMSessionCheckNotSynchronized(self); + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + [self resetSessionInternal]; + } + + dispatch_semaphore_signal(_sessionCreationSemaphore); +} + +- (void)resetSessionInternal { + GTMSessionCheckSynchronized(self); + + // The old dispatchers may be retained as delegates of any ongoing sessions by those sessions. + if (_delegateDispatcher) { + [self abandonDispatcher]; + _delegateDispatcher = + [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self + sessionDiscardInterval:_unusedSessionTimeout]; + } +} + +- (void)resetSessionForDispatcherDiscardTimer:(NSTimer *)timer { + GTMSessionCheckNotSynchronized(self); + + dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER); + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_delegateDispatcher.discardTimer == timer) { + // If the delegate dispatcher's current discardTimer is the same object as the timer + // that fired, no fetcher has recently attempted to start using the session by calling + // startSessionUsage, which invalidates and nils out the timer. + [self resetSessionInternal]; + } else { + // A fetcher has invalidated the timer between its triggering and now, potentially + // meaning a fetcher has requested access to the NSURLSession, and may be in the process + // of starting a new task. The dispatcher should not be abandoned, as this can lead + // to a race condition between calling -finishTasksAndInvalidate on the NSURLSession + // and the fetcher attempting to create a new task. + } + } + + dispatch_semaphore_signal(_sessionCreationSemaphore); +} + +- (NSTimeInterval)unusedSessionTimeout { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _unusedSessionTimeout; + } +} + +- (void)setUnusedSessionTimeout:(NSTimeInterval)timeout { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _unusedSessionTimeout = timeout; + _delegateDispatcher.discardInterval = timeout; + } +} + +// This method should be called inside of @synchronized(self) +- (void)abandonDispatcher { + GTMSessionCheckSynchronized(self); + [_delegateDispatcher abandon]; +} + +- (NSDictionary *)runningFetchersByHost { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_runningFetchersByHost copy]; + } +} + +- (void)setRunningFetchersByHost:(NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _runningFetchersByHost = [dict mutableCopy]; + } +} + +- (NSDictionary *)delayedFetchersByHost { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_delayedFetchersByHost copy]; + } +} + +- (void)setDelayedFetchersByHost:(NSDictionary *)dict { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delayedFetchersByHost = [dict mutableCopy]; + } +} + +- (id)authorizer { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _authorizer; + } +} + +- (void)setAuthorizer:(id)obj { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (obj != _authorizer) { + [self detachAuthorizer]; + } + + _authorizer = obj; + } + + // Use the fetcher service for the authorization fetches if the auth + // object supports fetcher services + if ([obj respondsToSelector:@selector(setFetcherService:)]) { +#if GTM_USE_SESSION_FETCHER + [obj setFetcherService:self]; +#else + [obj setFetcherService:(id)self]; +#endif + } +} + +// This should be called inside a @synchronized(self) block except during dealloc. +- (void)detachAuthorizer { + // This method is called by the fetcher service's dealloc and setAuthorizer: + // methods; do not override. + // + // The fetcher service retains the authorizer, and the authorizer has a + // weak pointer to the fetcher service (a non-zeroing pointer for + // compatibility with iOS 4 and Mac OS X 10.5/10.6.) + // + // When this fetcher service no longer uses the authorizer, we want to remove + // the authorizer's dependence on the fetcher service. Authorizers can still + // function without a fetcher service. + if ([_authorizer respondsToSelector:@selector(fetcherService)]) { + id authFetcherService = [_authorizer fetcherService]; + if (authFetcherService == self) { + [_authorizer setFetcherService:nil]; + } + } +} + +- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _callbackQueue; + } // @synchronized(self) +} + +- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _callbackQueue = queue ?: dispatch_get_main_queue(); + } // @synchronized(self) +} + +- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateQueue; + } // @synchronized(self) +} + +- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateQueue = queue ?: [NSOperationQueue mainQueue]; + } // @synchronized(self) +} + +- (NSOperationQueue *)delegateQueue { + // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects + // any custom delegate queue for calling the app. + return nil; +} + ++ (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers { + NSUInteger sum = 0; + for (GTMSessionFetcher *fetcher in fetchers) { + if (!fetcher.usingBackgroundSession) { + ++sum; + } + } + return sum; +} + +@end + +@implementation GTMSessionFetcherService (TestingSupport) + ++ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil + fakedError:(NSError *)fakedErrorOrNil { +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + NSURL *url = [NSURL URLWithString:@"http://example.invalid"]; + NSHTTPURLResponse *fakedResponse = + [[NSHTTPURLResponse alloc] initWithURL:url + statusCode:(fakedErrorOrNil ? 500 : 200) + HTTPVersion:@"HTTP/1.1" + headerFields:nil]; + return [self mockFetcherServiceWithFakedData:fakedDataOrNil + fakedResponse:fakedResponse + fakedError:fakedErrorOrNil]; +#else + GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); + return nil; +#endif // GTM_DISABLE_FETCHER_TEST_BLOCK +} + ++ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil + fakedResponse:(NSHTTPURLResponse *)fakedResponse + fakedError:(NSError *)fakedErrorOrNil { +#if !GTM_DISABLE_FETCHER_TEST_BLOCK + GTMSessionFetcherService *service = [[self alloc] init]; + service.allowedInsecureSchemes = @[ @"http" ]; + service.testBlock = ^(GTMSessionFetcher *fetcherToTest, + GTMSessionFetcherTestResponse testResponse) { + testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil); + }; + return service; +#else + GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); + return nil; +#endif // GTM_DISABLE_FETCHER_TEST_BLOCK +} + +#pragma mark Synchronous Wait for Unit Testing + +- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds { + NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + _stoppedFetchersToWaitFor = [NSMutableArray array]; + + BOOL shouldSpinRunLoop = [NSThread isMainThread]; + const NSTimeInterval kSpinInterval = 0.001; + BOOL didTimeOut = NO; + while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) { + didTimeOut = [giveUpDate timeIntervalSinceNow] < 0; + if (didTimeOut) break; + + GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject; + if (stoppedFetcher) { + [_stoppedFetchersToWaitFor removeObject:stoppedFetcher]; + [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval]; + } + + if (shouldSpinRunLoop) { + NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; + } else { + [NSThread sleepForTimeInterval:kSpinInterval]; + } + } + _stoppedFetchersToWaitFor = nil; + + return !didTimeOut; +} + +@end + +@implementation GTMSessionFetcherService (BackwardsCompatibilityOnly) + +- (NSInteger)cookieStorageMethod { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _cookieStorageMethod; + } +} + +- (void)setCookieStorageMethod:(NSInteger)cookieStorageMethod { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _cookieStorageMethod = cookieStorageMethod; + } +} + +@end + +@implementation GTMSessionFetcherSessionDelegateDispatcher { + __weak GTMSessionFetcherService *_parentService; + NSURLSession *_session; + + // The task map maps NSURLSessionTasks to GTMSessionFetchers + NSMutableDictionary *_taskToFetcherMap; + // The discard timer will invalidate sessions after the session's last task completes. + NSTimer *_discardTimer; + NSTimeInterval _discardInterval; +} + +@synthesize discardInterval = _discardInterval, + session = _session; + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return nil; +} + +- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService + sessionDiscardInterval:(NSTimeInterval)discardInterval { + self = [super init]; + if (self) { + _discardInterval = discardInterval; + _parentService = parentService; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ %p %@ %@", + [self class], self, + _session ?: @"", + _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @""]; +} + +- (NSTimer *)discardTimer { + GTMSessionCheckNotSynchronized(self); + @synchronized(self) { + return _discardTimer; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)startDiscardTimer { + GTMSessionCheckSynchronized(self); + [_discardTimer invalidate]; + _discardTimer = nil; + if (_discardInterval > 0) { + _discardTimer = [NSTimer timerWithTimeInterval:_discardInterval + target:self + selector:@selector(discardTimerFired:) + userInfo:nil + repeats:NO]; + [_discardTimer setTolerance:(_discardInterval / 10)]; + [[NSRunLoop mainRunLoop] addTimer:_discardTimer forMode:NSRunLoopCommonModes]; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)destroyDiscardTimer { + GTMSessionCheckSynchronized(self); + [_discardTimer invalidate]; + _discardTimer = nil; +} + +- (void)discardTimerFired:(NSTimer *)timer { + GTMSessionFetcherService *service; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + NSUInteger numberOfTasks = _taskToFetcherMap.count; + if (numberOfTasks == 0) { + service = _parentService; + } + } + + // Inform the service that the discard timer has fired, and should check whether the + // service can abandon us. -resetSession cannot be called directly, as there is a + // race condition that must be guarded against with the NSURLSession being returned + // from sessionForFetcherCreation outside other locks. The service can take steps + // to prevent resetting the session if that has occurred. + // + // The service must be called from outside the @synchronized block. + [service resetSessionForDispatcherDiscardTimer:timer]; +} + +- (void)abandon { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self destroySessionAndTimer]; + } +} + +- (void)startSessionUsage { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [self destroyDiscardTimer]; + } +} + +// This method should be called inside of a @synchronized(self) block. +- (void)destroySessionAndTimer { + GTMSessionCheckSynchronized(self); + [self destroyDiscardTimer]; + + // Break any retain cycle from the session holding the delegate. + [_session finishTasksAndInvalidate]; + + // Immediately clear the session so no new task may be issued with it. + // + // The _taskToFetcherMap needs to stay valid until the outstanding tasks finish. + _session = nil; +} + +- (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task { + GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"missing fetcher"); + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_taskToFetcherMap == nil) { + _taskToFetcherMap = [[NSMutableDictionary alloc] init]; + } + + if (fetcher) { + [_taskToFetcherMap setObject:fetcher forKey:task]; + [self destroyDiscardTimer]; + } + } +} + +- (void)removeFetcher:(GTMSessionFetcher *)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Typically, a fetcher should be removed when its task invokes + // URLSession:task:didCompleteWithError:. + // + // When fetching with a testBlock, though, the task completed delegate + // method may not be invoked, requiring cleanup here. + NSArray *tasks = [_taskToFetcherMap allKeysForObject:fetcher]; + GTMSESSION_ASSERT_DEBUG(tasks.count <= 1, @"fetcher task not unmapped: %@", tasks); + [_taskToFetcherMap removeObjectsForKeys:tasks]; + + if (_taskToFetcherMap.count == 0) { + [self startDiscardTimer]; + } + } +} + +// This helper method provides synchronized access to the task map for the delegate +// methods below. +- (id)fetcherForTask:(NSURLSessionTask *)task { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return [_taskToFetcherMap objectForKey:task]; + } +} + +- (void)removeTaskFromMap:(NSURLSessionTask *)task { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + [_taskToFetcherMap removeObjectForKey:task]; + } +} + +- (void)setSession:(NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _session = session; + } +} + +- (NSURLSession *)session { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _session; + } +} + +- (NSTimeInterval)discardInterval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _discardInterval; + } +} + +- (void)setDiscardInterval:(NSTimeInterval)interval { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _discardInterval = interval; + } +} + +// NSURLSessionDelegate protocol methods. + +// - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session; +// +// TODO(seh): How do we route this to an appropriate fetcher? + + +- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { + GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", + [self class], self, session, error); + NSDictionary *localTaskToFetcherMap; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _session = nil; + + localTaskToFetcherMap = [_taskToFetcherMap copy]; + } + + // Any "suspended" tasks may not have received callbacks from NSURLSession when the session + // completes; we'll call them now. + [localTaskToFetcherMap enumerateKeysAndObjectsUsingBlock:^(NSURLSessionTask *task, + GTMSessionFetcher *fetcher, + BOOL *stop) { + if (fetcher.session == session) { + // Our delegate method URLSession:task:didCompleteWithError: will rely on + // _taskToFetcherMap so that should still contain this fetcher. + NSError *canceledError = [NSError errorWithDomain:NSURLErrorDomain + code:NSURLErrorCancelled + userInfo:nil]; + [self URLSession:session task:task didCompleteWithError:canceledError]; + } else { + GTMSESSION_ASSERT_DEBUG(0, @"Unexpected session in fetcher: %@ has %@ (expected %@)", + fetcher, fetcher.session, session); + } + }]; + + // Our tests rely on this notification to know the session discard timer fired. + NSDictionary *userInfo = @{ kGTMSessionFetcherServiceSessionKey : session }; + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:kGTMSessionFetcherServiceSessionBecameInvalidNotification + object:_parentService + userInfo:userInfo]; +} + + +#pragma mark - NSURLSessionTaskDelegate + +// NSURLSessionTaskDelegate protocol methods. +// +// We won't test here if the fetcher responds to these since we only want this +// class to implement the same delegate methods the fetcher does (so NSURLSession's +// tests for respondsToSelector: will have the same result whether the session +// delegate is the fetcher or this dispatcher.) + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task +willPerformHTTPRedirection:response + newRequest:request + completionHandler:completionHandler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + didReceiveChallenge:challenge + completionHandler:handler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + needNewBodyStream:(void (^)(NSInputStream *bodyStream))handler { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + needNewBodyStream:handler]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent +totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session + task:task + didSendBodyData:bytesSent + totalBytesSent:totalBytesSent +totalBytesExpectedToSend:totalBytesExpectedToSend]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error { + id fetcher = [self fetcherForTask:task]; + + // This is the usual way tasks are removed from the task map. + [self removeTaskFromMap:task]; + + [fetcher URLSession:session + task:task + didCompleteWithError:error]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics + API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) { + id fetcher = [self fetcherForTask:task]; + [fetcher URLSession:session task:task didFinishCollectingMetrics:metrics]; +} + +// NSURLSessionDataDelegate protocol methods. + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition))handler { + id fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + didReceiveResponse:response + completionHandler:handler]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { + id fetcher = [self fetcherForTask:dataTask]; + GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"Missing fetcher for %@", dataTask); + [self removeTaskFromMap:dataTask]; + if (fetcher) { + GTMSESSION_ASSERT_DEBUG([fetcher isKindOfClass:[GTMSessionFetcher class]], + @"Expecting GTMSessionFetcher"); + [self setFetcher:(GTMSessionFetcher *)fetcher forTask:downloadTask]; + } + + [fetcher URLSession:session + dataTask:dataTask +didBecomeDownloadTask:downloadTask]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + id fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + didReceiveData:data]; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + willCacheResponse:(NSCachedURLResponse *)proposedResponse + completionHandler:(void (^)(NSCachedURLResponse *))handler { + id fetcher = [self fetcherForTask:dataTask]; + [fetcher URLSession:session + dataTask:dataTask + willCacheResponse:proposedResponse + completionHandler:handler]; +} + +// NSURLSessionDownloadDelegate protocol methods. + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask +didFinishDownloadingToURL:(NSURL *)location { + id fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask +didFinishDownloadingToURL:location]; +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didWriteData:(int64_t)bytesWritten + totalBytesWritten:(int64_t)totalWritten +totalBytesExpectedToWrite:(int64_t)totalExpected { + id fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask + didWriteData:bytesWritten + totalBytesWritten:totalWritten +totalBytesExpectedToWrite:totalExpected]; +} + +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)downloadTask + didResumeAtOffset:(int64_t)fileOffset +expectedTotalBytes:(int64_t)expectedTotalBytes { + id fetcher = [self fetcherForTask:downloadTask]; + [fetcher URLSession:session + downloadTask:downloadTask + didResumeAtOffset:fileOffset + expectedTotalBytes:expectedTotalBytes]; +} + +@end diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h b/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h new file mode 100644 index 00000000..2f9023a9 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h @@ -0,0 +1,175 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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. + */ + +// GTMSessionUploadFetcher implements Google's resumable upload protocol. + +// +// This subclass of GTMSessionFetcher simulates the series of fetches +// needed for chunked upload as a single fetch operation. +// +// Protocol document: TBD +// +// To the client, the only fetcher that exists is this class; the subsidiary +// fetchers needed for uploading chunks are not visible (though the most recent +// chunk fetcher may be accessed via the -activeFetcher or -chunkFetcher methods, and +// -responseHeaders and -statusCode reflect results from the most recent chunk +// fetcher.) +// +// Chunk fetchers are discarded as soon as they have completed. +// +// The protocol also allows for a cancellation notification request to be sent to the +// server to allow discarding of the currently uploaded data and this will be sent +// automatically upon calling stopFetching if the upload has already started. +// +// Note: Unlike the fetcher superclass, the methods of GTMSessionUploadFetcher should +// only be used from the main thread until further work is done to make this subclass +// thread-safe. + +#import "GTMSessionFetcher.h" +#import "GTMSessionFetcherService.h" + +GTM_ASSUME_NONNULL_BEGIN + +// The value to use for file size parameters when the file size is not yet known. +extern int64_t const kGTMSessionUploadFetcherUnknownFileSize; + +// Unless an application knows it needs a smaller chunk size, it should use the standard +// chunk size, which sends the entire file as a single chunk to minimize upload overhead. +// Setting an explicit chunk size that comfortably fits in memory is advisable for large +// uploads. +extern int64_t const kGTMSessionUploadFetcherStandardChunkSize; + +// When uploading requires data buffer allocations (such as uploading from an NSData or +// an NSFileHandle) this is the maximum buffer size that will be created by the fetcher. +extern int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize; + +// Notification that the upload location URL was provided by the server. +extern NSString *const kGTMSessionFetcherUploadLocationObtainedNotification; + +// Block to provide data during uploads. +// +// Response data may be allocated with dataWithBytesNoCopy:length:freeWhenDone: for efficiency, +// and released after the response block returns. +// +// If the length of the file being uploaded is unknown or already set, send +// kGTMSessionUploadFetcherUnknownFileSize for |fullUploadLength|. Otherwise, set |fullUploadLength| +// to its proper value. +// +// Pass nil as the data (and optionally an NSError) for a failure. +typedef void (^GTMSessionUploadFetcherDataProviderResponse)(NSData * GTM_NULLABLE_TYPE data, + int64_t fullUploadLength, + NSError * GTM_NULLABLE_TYPE error); +// Do not call the response with an NSData object with less data than the requested length unless +// you are passing the fullUploadLength to the fetcher for the first time and it is the last chunk +// of data in the file being uploaded. +typedef void (^GTMSessionUploadFetcherDataProvider)(int64_t offset, int64_t length, + GTMSessionUploadFetcherDataProviderResponse response); + +// Block to be notified about the final status of the cancellation request started in stopFetching. +// +// |fetcher| will be the cancel request that was sent to the server, or nil if stopFetching is not +// going to send a cancel request. If |fetcher| is provided, the other parameters correspond to the +// completion handler of the cancellation request fetcher. +typedef void (^GTMSessionUploadFetcherCancellationHandler)( + GTMSessionFetcher * GTM_NULLABLE_TYPE fetcher, + NSData * GTM_NULLABLE_TYPE data, + NSError * GTM_NULLABLE_TYPE error); + +@interface GTMSessionUploadFetcher : GTMSessionFetcher + +// Create an upload fetcher specifying either the request or the resume location URL, +// then set an upload data source using one of these: +// +// setUploadFileURL: +// setUploadDataLength:provider: +// setUploadFileHandle: +// setUploadData: + ++ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; + +// Allows cellular access. ++ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; + ++ (instancetype)uploadFetcherWithLocation:(NSURL *GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + allowsCellularAccess:(BOOL)allowsCellularAccess + fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; + +// Allows dataProviders for files of unknown length. Pass kGTMSessionUploadFetcherUnknownFileSize as +// |fullLength| if the length is unknown. +- (void)setUploadDataLength:(int64_t)fullLength + provider:(GTM_NULLABLE GTMSessionUploadFetcherDataProvider)block; + ++ (NSArray *)uploadFetchersForBackgroundSessions; ++ (GTM_NULLABLE instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier; + +- (void)pauseFetching; +- (void)resumeFetching; +- (BOOL)isPaused; + +@property(atomic, strong, GTM_NULLABLE) NSURL *uploadLocationURL; +@property(atomic, strong, GTM_NULLABLE) NSData *uploadData; +@property(atomic, strong, GTM_NULLABLE) NSURL *uploadFileURL; +@property(atomic, strong, GTM_NULLABLE) NSFileHandle *uploadFileHandle; +@property(atomic, copy, readonly, GTM_NULLABLE) GTMSessionUploadFetcherDataProvider uploadDataProvider; +@property(atomic, copy) NSString *uploadMIMEType; +@property(atomic, readonly, assign) int64_t chunkSize; +@property(atomic, readonly, assign) int64_t currentOffset; +// Reflects the original NSURLRequest's @c allowCellularAccess property. +@property(atomic, readonly, assign) BOOL allowsCellularAccess; + +// The fetcher for the current data chunk, if any +@property(atomic, strong, GTM_NULLABLE) GTMSessionFetcher *chunkFetcher; + +// The active fetcher is the current chunk fetcher, or the upload fetcher itself +// if no chunk fetcher has yet been created. +@property(atomic, readonly) GTMSessionFetcher *activeFetcher; + +// The last request made by an active fetcher. Useful for testing. +@property(atomic, readonly, GTM_NULLABLE) NSURLRequest *lastChunkRequest; + +// The status code from the most recently-completed fetch. +@property(atomic, assign) NSInteger statusCode; + +// Invoked as part of the stop fetching process. Invoked immediately if there is no upload in +// progress, otherwise invoked with the results of the attempt to notify the server that the +// upload will not continue. +// +// Unlike other callbacks, since this is related specifically to the stopFetching flow it is not +// cleared by stopFetching. It will instead clear itself after it is invoked or if the completion +// has occured before stopFetching is called. +@property(atomic, copy, GTM_NULLABLE) GTMSessionUploadFetcherCancellationHandler + cancellationHandler; + +// Exposed for testing only. +@property(atomic, readonly, GTM_NULLABLE) dispatch_queue_t delegateCallbackQueue; +@property(atomic, readonly, GTM_NULLABLE) GTMSessionFetcherCompletionHandler delegateCompletionHandler; + +@end + +@interface GTMSessionFetcher (GTMSessionUploadFetcherMethods) + +@property(readonly, GTM_NULLABLE) GTMSessionUploadFetcher *parentUploadFetcher; + +@end + +GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m b/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m new file mode 100644 index 00000000..7759bb15 --- /dev/null +++ b/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m @@ -0,0 +1,1989 @@ +/* Copyright 2014 Google Inc. All rights reserved. + * + * 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 !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "GTMSessionUploadFetcher.h" + +static NSString *const kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey = @"_upChunk"; +static NSString *const kGTMSessionIdentifierUploadFileURLMetadataKey = @"_upFileURL"; +static NSString *const kGTMSessionIdentifierUploadFileLengthMetadataKey = @"_upFileLen"; +static NSString *const kGTMSessionIdentifierUploadLocationURLMetadataKey = @"_upLocURL"; +static NSString *const kGTMSessionIdentifierUploadMIMETypeMetadataKey = @"_uploadMIME"; +static NSString *const kGTMSessionIdentifierUploadChunkSizeMetadataKey = @"_upChSize"; +static NSString *const kGTMSessionIdentifierUploadCurrentOffsetMetadataKey = @"_upOffset"; +static NSString *const kGTMSessionIdentifierUploadAllowsCellularAccess = @"_upAllowsCellularAccess"; + +static NSString *const kGTMSessionHeaderXGoogUploadChunkGranularity = @"X-Goog-Upload-Chunk-Granularity"; +static NSString *const kGTMSessionHeaderXGoogUploadCommand = @"X-Goog-Upload-Command"; +static NSString *const kGTMSessionHeaderXGoogUploadContentLength = @"X-Goog-Upload-Content-Length"; +static NSString *const kGTMSessionHeaderXGoogUploadContentType = @"X-Goog-Upload-Content-Type"; +static NSString *const kGTMSessionHeaderXGoogUploadOffset = @"X-Goog-Upload-Offset"; +static NSString *const kGTMSessionHeaderXGoogUploadProtocol = @"X-Goog-Upload-Protocol"; +static NSString *const kGTMSessionXGoogUploadProtocolResumable = @"resumable"; +static NSString *const kGTMSessionHeaderXGoogUploadSizeReceived = @"X-Goog-Upload-Size-Received"; +static NSString *const kGTMSessionHeaderXGoogUploadStatus = @"X-Goog-Upload-Status"; +static NSString *const kGTMSessionHeaderXGoogUploadURL = @"X-Goog-Upload-URL"; + +// Property of chunk fetchers identifying the parent upload fetcher. Non-retained NSValue. +static NSString *const kGTMSessionUploadFetcherChunkParentKey = @"_uploadFetcherChunkParent"; + +int64_t const kGTMSessionUploadFetcherUnknownFileSize = -1; + +int64_t const kGTMSessionUploadFetcherStandardChunkSize = (int64_t)LLONG_MAX; + +#if TARGET_OS_IPHONE +int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 10 * 1024 * 1024; // 10 MB for iOS, watchOS, tvOS +#else +int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 100 * 1024 * 1024; // 100 MB for macOS +#endif + +typedef NS_ENUM(NSUInteger, GTMSessionUploadFetcherStatus) { + kStatusUnknown, + kStatusActive, + kStatusFinal, + kStatusCancelled, +}; + +NSString *const kGTMSessionFetcherUploadLocationObtainedNotification = + @"kGTMSessionFetcherUploadLocationObtainedNotification"; + +#if !GTMSESSION_BUILD_COMBINED_SOURCES +@interface GTMSessionFetcher (ProtectedMethods) + +// Access to non-public method on the parent fetcher class. +- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks; +- (void)createSessionIdentifierWithMetadata:(NSDictionary *)metadata; +- (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(id)target + didFinishSelector:(SEL)finishedSelector; +- (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue + afterUserStopped:(BOOL)afterStopped + block:(void (^)(void))block; +- (NSTimer *)retryTimer; +- (void)beginFetchForRetry; + +@property(readwrite, strong) NSData *downloadedData; +- (void)releaseCallbacks; + +- (NSInteger)statusCodeUnsynchronized; + +- (BOOL)userStoppedFetching; + +@end +#endif // !GTMSESSION_BUILD_COMBINED_SOURCES + +@interface GTMSessionUploadFetcher () + +// Changing readonly to readwrite. +@property(atomic, strong, readwrite) NSURLRequest *lastChunkRequest; +@property(atomic, readwrite, assign) int64_t currentOffset; + +// Internal properties. +@property(strong, atomic, GTM_NULLABLE) GTMSessionFetcher *fetcherInFlight; // Synchronized on self. + +@property(assign, atomic, getter=isSubdataGenerating) BOOL subdataGenerating; +@property(assign, atomic) BOOL shouldInitiateOffsetQuery; +@property(assign, atomic) int64_t uploadGranularity; +@property(assign, atomic) BOOL allowsCellularAccess; + +@end + +@implementation GTMSessionUploadFetcher { + GTMSessionFetcher *_chunkFetcher; + + // We'll call through to the delegate's completion handler. + GTMSessionFetcherCompletionHandler _delegateCompletionHandler; + dispatch_queue_t _delegateCallbackQueue; + + // The initial fetch's body length and bytes actually sent are + // needed for calculating progress during subsequent chunk uploads + int64_t _initialBodyLength; + int64_t _initialBodySent; + + // The upload server address for the chunks of this upload session. + NSURL *_uploadLocationURL; + + // _uploadData, _uploadDataProvider, or _uploadFileHandle may be set, but only one. + NSData *_uploadData; + NSFileHandle *_uploadFileHandle; + GTMSessionUploadFetcherDataProvider _uploadDataProvider; + NSURL *_uploadFileURL; + int64_t _uploadFileLength; + NSString *_uploadMIMEType; + int64_t _chunkSize; + int64_t _uploadGranularity; + BOOL _isPaused; + BOOL _isRestartedUpload; + BOOL _shouldInitiateOffsetQuery; + + // Tied to useBackgroundSession property, since this property is applicable to chunk fetchers. + BOOL _useBackgroundSessionOnChunkFetchers; + + // We keep the latest offset into the upload data just for progress reporting. + int64_t _currentOffset; + + NSDictionary *_recentChunkReponseHeaders; + NSInteger _recentChunkStatusCode; + + // For waiting, we need to know the fetcher in flight, if any, and if subdata generation + // is in progress. + GTMSessionFetcher *_fetcherInFlight; + BOOL _isSubdataGenerating; + BOOL _isCancelInFlight; + + GTMSessionUploadFetcherCancellationHandler _cancellationHandler; +} + ++ (void)load { + [self uploadFetchersForBackgroundSessions]; +} + ++ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTMSessionFetcherService *)fetcherService { + GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:request + fetcherService:fetcherService]; + [fetcher setLocationURL:nil + uploadMIMEType:uploadMIMEType + chunkSize:chunkSize + allowsCellularAccess:request.allowsCellularAccess]; + return fetcher; +} + ++ (instancetype)uploadFetcherWithLocation:(NSURL *GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil { + return [self uploadFetcherWithLocation:uploadLocationURL + uploadMIMEType:uploadMIMEType + chunkSize:chunkSize + allowsCellularAccess:YES + fetcherService:fetcherServiceOrNil]; +} + ++ (instancetype)uploadFetcherWithLocation:(NSURL *GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + allowsCellularAccess:(BOOL)allowsCellularAccess + fetcherService:(GTMSessionFetcherService *)fetcherService { + GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:nil + fetcherService:fetcherService]; + [fetcher setLocationURL:uploadLocationURL + uploadMIMEType:uploadMIMEType + chunkSize:chunkSize + allowsCellularAccess:allowsCellularAccess]; + return fetcher; +} + ++ (instancetype)uploadFetcherForSessionIdentifierMetadata:(NSDictionary *)metadata { + GTMSESSION_ASSERT_DEBUG( + [metadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] boolValue], + @"Session identifier metadata is not for an upload fetcher: %@", metadata); + + NSNumber *uploadFileLengthNum = metadata[kGTMSessionIdentifierUploadFileLengthMetadataKey]; + GTMSESSION_ASSERT_DEBUG(uploadFileLengthNum != nil, + @"Session metadata missing an UploadFileSize"); + if (uploadFileLengthNum == nil) return nil; + + int64_t uploadFileLength = [uploadFileLengthNum longLongValue]; + GTMSESSION_ASSERT_DEBUG(uploadFileLength >= 0, @"Session metadata UploadFileSize is unknown"); + + NSString *uploadFileURLString = metadata[kGTMSessionIdentifierUploadFileURLMetadataKey]; + GTMSESSION_ASSERT_DEBUG(uploadFileURLString, @"Session metadata missing an UploadFileURL"); + if (uploadFileURLString == nil) return nil; + + NSURL *uploadFileURL = [NSURL URLWithString:uploadFileURLString]; + // There used to be a call here to NSURL checkResourceIsReachableAndReturnError: to check for the + // existence of the file (also tried NSFileManager fileExistsAtPath:). We've determined + // empirically that the check can fail at startup even when the upload file does in fact exist. + // For now, we'll go ahead and restore the background upload fetcher. If the file doesn't exist, + // it will fail later. + + NSString *uploadLocationURLString = metadata[kGTMSessionIdentifierUploadLocationURLMetadataKey]; + NSURL *uploadLocationURL = + uploadLocationURLString ? [NSURL URLWithString:uploadLocationURLString] : nil; + + NSString *uploadMIMEType = + metadata[kGTMSessionIdentifierUploadMIMETypeMetadataKey]; + int64_t uploadChunkSize = + [metadata[kGTMSessionIdentifierUploadChunkSizeMetadataKey] longLongValue]; + if (uploadChunkSize <= 0) { + uploadChunkSize = kGTMSessionUploadFetcherStandardChunkSize; + } + int64_t currentOffset = + [metadata[kGTMSessionIdentifierUploadCurrentOffsetMetadataKey] longLongValue]; + + BOOL allowsCellularAccess = YES; + if (metadata[kGTMSessionIdentifierUploadAllowsCellularAccess]) { + allowsCellularAccess = [metadata[kGTMSessionIdentifierUploadAllowsCellularAccess] boolValue]; + } + + GTMSESSION_ASSERT_DEBUG(currentOffset <= uploadFileLength, + @"CurrentOffset (%lld) exceeds UploadFileSize (%lld)", + currentOffset, uploadFileLength); + if (currentOffset > uploadFileLength) return nil; + + GTMSessionUploadFetcher *uploadFetcher = [self uploadFetcherWithLocation:uploadLocationURL + uploadMIMEType:uploadMIMEType + chunkSize:uploadChunkSize + allowsCellularAccess:allowsCellularAccess + fetcherService:nil]; + // Set the upload file length before setting the upload file URL tries to determine the length. + [uploadFetcher setUploadFileLength:uploadFileLength]; + + uploadFetcher.uploadFileURL = uploadFileURL; + uploadFetcher.sessionUserInfo = metadata; + uploadFetcher.useBackgroundSession = YES; + uploadFetcher.currentOffset = currentOffset; + uploadFetcher.delegateCallbackQueue = uploadFetcher.callbackQueue; + uploadFetcher.allowedInsecureSchemes = @[ @"http" ]; // Allowed on restored upload fetcher. + return uploadFetcher; +} + ++ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request + fetcherService:(GTMSessionFetcherService *)fetcherService { + // Internal utility method for instantiating fetchers + GTMSessionUploadFetcher *fetcher; + if ([fetcherService isKindOfClass:[GTMSessionFetcherService class]]) { + fetcher = [fetcherService fetcherWithRequest:request + fetcherClass:self]; + } else { + fetcher = [self fetcherWithRequest:request]; + } + fetcher.useBackgroundSession = YES; + return fetcher; +} + ++ (NSPointerArray *)uploadFetcherPointerArrayForBackgroundSessions { + static NSPointerArray *gUploadFetcherPointerArrayForBackgroundSessions = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gUploadFetcherPointerArrayForBackgroundSessions = [NSPointerArray weakObjectsPointerArray]; + }); + return gUploadFetcherPointerArrayForBackgroundSessions; +} + ++ (instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier { + GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier"); + NSArray *uploadFetchersForBackgroundSessions = [self uploadFetchersForBackgroundSessions]; + for (GTMSessionUploadFetcher *uploadFetcher in uploadFetchersForBackgroundSessions) { + if ([uploadFetcher.chunkFetcher.sessionIdentifier isEqual:sessionIdentifier]) { + return uploadFetcher; + } + } + return nil; +} + ++ (NSArray *)uploadFetchersForBackgroundSessions { + NSMutableSet *restoredSessionIdentifiers = [[NSMutableSet alloc] init]; + NSMutableArray *uploadFetchers = [[NSMutableArray alloc] init]; + NSPointerArray *uploadFetcherPointerArray = [self uploadFetcherPointerArrayForBackgroundSessions]; + + // Collect the background session upload fetchers that are still in memory. + @synchronized(uploadFetcherPointerArray) { + [uploadFetcherPointerArray compact]; + for (GTMSessionUploadFetcher *uploadFetcher in uploadFetcherPointerArray) { + NSString *sessionIdentifier = uploadFetcher.chunkFetcher.sessionIdentifier; + if (sessionIdentifier) { + [restoredSessionIdentifiers addObject:sessionIdentifier]; + [uploadFetchers addObject:uploadFetcher]; + } + } + } // @synchronized(uploadFetcherPointerArray) + + // The system may have other ongoing background upload sessions. Restore upload fetchers for those + // too. + NSArray *fetchers = [GTMSessionFetcher fetchersForBackgroundSessions]; + for (GTMSessionFetcher *fetcher in fetchers) { + NSString *sessionIdentifier = fetcher.sessionIdentifier; + if (!sessionIdentifier || [restoredSessionIdentifiers containsObject:sessionIdentifier]) { + continue; + } + NSDictionary *sessionIdentifierMetadata = [fetcher sessionIdentifierMetadata]; + if (sessionIdentifierMetadata == nil) { + continue; + } + if (![sessionIdentifierMetadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] boolValue]) { + continue; + } + GTMSessionUploadFetcher *uploadFetcher = + [self uploadFetcherForSessionIdentifierMetadata:sessionIdentifierMetadata]; + if (uploadFetcher == nil) { + // Something went wrong with this upload fetcher, so kill the restored chunk fetcher. + [fetcher stopFetching]; + continue; + } + [uploadFetchers addObject:uploadFetcher]; + uploadFetcher->_chunkFetcher = fetcher; + uploadFetcher->_fetcherInFlight = fetcher; + [uploadFetcher attachSendProgressBlockToChunkFetcher:fetcher]; + fetcher.completionHandler = + [fetcher completionHandlerWithTarget:uploadFetcher + didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)]; + + GTMSESSION_LOG_DEBUG(@"%@ restoring upload fetcher %@ for chunk fetcher %@", + [self class], uploadFetcher, fetcher); + } + return uploadFetchers; +} + +- (void)setUploadData:(NSData *)data { + BOOL changed = NO; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadData != data) { + _uploadData = data; + changed = YES; + } + } + if (changed) { + [self setupRequestHeaders]; + } +} + +- (NSData *)uploadData { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadData; + } +} + +- (void)setUploadFileHandle:(NSFileHandle *)fh { + BOOL changed = NO; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadFileHandle != fh) { + _uploadFileHandle = fh; + changed = YES; + } + } + if (changed) { + [self setupRequestHeaders]; + } +} + +- (NSFileHandle *)uploadFileHandle { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadFileHandle; + } +} + +- (void)setUploadFileURL:(NSURL *)uploadURL { + BOOL changed = NO; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadFileURL != uploadURL) { + _uploadFileURL = uploadURL; + changed = YES; + } + } + if (changed) { + [self setupRequestHeaders]; + } +} + +- (NSURL *)uploadFileURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadFileURL; + } +} + +- (void)setUploadFileLength:(int64_t)fullLength { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadFileLength == kGTMSessionUploadFetcherUnknownFileSize && + fullLength != kGTMSessionUploadFetcherUnknownFileSize) { + _uploadFileLength = fullLength; + } + } +} + +- (void)setUploadDataLength:(int64_t)fullLength + provider:(GTMSessionUploadFetcherDataProvider)block { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _uploadDataProvider = [block copy]; + _uploadFileLength = fullLength; + } + [self setupRequestHeaders]; +} + +- (GTMSessionUploadFetcherDataProvider)uploadDataProvider { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadDataProvider; + } +} + + +- (void)setUploadMIMEType:(NSString *)uploadMIMEType { + GTMSESSION_ASSERT_DEBUG(0, @"TODO: disallow setUploadMIMEType by making declaration readonly"); + // (and uploadMIMEType, chunksize, currentOffset) + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _uploadMIMEType = uploadMIMEType; + } +} + +- (NSString *)uploadMIMEType { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadMIMEType; + } +} + +- (int64_t)chunkSize { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _chunkSize; + } +} + +- (void)setupRequestHeaders { + GTMSessionCheckNotSynchronized(self); + +#if DEBUG + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + int hasData = (_uploadData != nil) ? 1 : 0; + int hasFileHandle = (_uploadFileHandle != nil) ? 1 : 0; + int hasFileURL = (_uploadFileURL != nil) ? 1 : 0; + int hasUploadDataProvider = (_uploadDataProvider != nil) ? 1 : 0; + int numberOfSources = hasData + hasFileHandle + hasFileURL + hasUploadDataProvider; + #pragma unused(numberOfSources) + GTMSESSION_ASSERT_DEBUG(numberOfSources == 1, + @"Need just one upload source (%d)", numberOfSources); + } // @synchronized(self) +#endif + + // Add our custom headers to the initial request indicating the data + // type and total size to be delivered later in the chunk requests. + NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; + + GTMSESSION_ASSERT_DEBUG((mutableRequest == nil) != (_uploadLocationURL == nil), + @"Request and location are mutually exclusive"); + if (!mutableRequest) return; + + [mutableRequest setValue:kGTMSessionXGoogUploadProtocolResumable + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol]; + [mutableRequest setValue:@"start" + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; + [mutableRequest setValue:_uploadMIMEType + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentType]; + [mutableRequest setValue:@([self fullUploadLength]).stringValue + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength]; + + NSString *method = mutableRequest.HTTPMethod; + if (method == nil || [method caseInsensitiveCompare:@"GET"] == NSOrderedSame) { + [mutableRequest setHTTPMethod:@"POST"]; + } + + // Ensure the user agent header identifies this to the upload server as a + // GTMSessionUploadFetcher client. The /1 can be incremented in the unlikely circumstance + // we need to make a bug fix in the client that the server can recognize. + NSString *const kUserAgentStub = @"(GTMSUF/1)"; + NSString *userAgent = [mutableRequest valueForHTTPHeaderField:@"User-Agent"]; + if (userAgent == nil + || [userAgent rangeOfString:kUserAgentStub].location == NSNotFound) { + if (userAgent.length == 0) { + userAgent = GTMFetcherStandardUserAgentString(nil); + } + userAgent = [userAgent stringByAppendingFormat:@" %@", kUserAgentStub]; + [mutableRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + } + [self setRequest:mutableRequest]; +} + +- (void)setLocationURL:(NSURL *GTM_NULLABLE_TYPE)location + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + allowsCellularAccess:(BOOL)allowsCellularAccess { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(chunkSize > 0, @"chunk size is zero"); + + _allowsCellularAccess = allowsCellularAccess; + + // When resuming an upload, set the known upload target URL. + _uploadLocationURL = location; + + _uploadMIMEType = uploadMIMEType; + _chunkSize = chunkSize; + + // Indicate that we've not yet determined the file handle's length + _uploadFileLength = kGTMSessionUploadFetcherUnknownFileSize; + + // Indicate that we've not yet determined the upload fetcher status + _recentChunkStatusCode = -1; + + // If this is restarting an upload begun by another fetcher, + // the location is specified but the request is nil + _isRestartedUpload = (location != nil); + } // @synchronized(self) +} + +- (int64_t)fullUploadLength { + int64_t result; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_uploadData) { + result = (int64_t)_uploadData.length; + } else { + if (_uploadFileLength == kGTMSessionUploadFetcherUnknownFileSize) { + if (_uploadFileHandle) { + // First time through, seek to end to determine file length + _uploadFileLength = (int64_t)[_uploadFileHandle seekToEndOfFile]; + } else if (_uploadDataProvider) { + // _uploadFileLength is set when the _uploadDataProvider is set. + GTMSESSION_ASSERT_DEBUG(_uploadFileLength >= 0, @"No uploadDataProvider length set"); + } else { + NSNumber *filesizeNum; + NSError *valueError; + if ([_uploadFileURL getResourceValue:&filesizeNum + forKey:NSURLFileSizeKey + error:&valueError]) { + _uploadFileLength = filesizeNum.longLongValue; + } else { + GTMSESSION_ASSERT_DEBUG(NO, @"Cannot get file size: %@\n %@", + valueError, _uploadFileURL.path); + _uploadFileLength = 0; + } + } + } + result = _uploadFileLength; + } + } // @synchronized(self) + return result; +} + +// Make a subdata of the upload data. +- (void)generateChunkSubdataWithOffset:(int64_t)offset + length:(int64_t)length + response:(GTMSessionUploadFetcherDataProviderResponse)response { + GTMSessionUploadFetcherDataProvider uploadDataProvider = self.uploadDataProvider; + if (uploadDataProvider) { + uploadDataProvider(offset, length, response); + return; + } + + NSData *uploadData = self.uploadData; + if (uploadData) { + // NSData provided. + NSData *resultData; + if (offset == 0 && length == (int64_t)uploadData.length) { + resultData = uploadData; + } else { + int64_t dataLength = (int64_t)uploadData.length; + // Ensure our range is valid. b/18007814 + if (offset + length > dataLength) { + NSString *errorMessage = [NSString stringWithFormat: + @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld", + offset, length, dataLength]; + GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); + response(nil, + kGTMSessionUploadFetcherUnknownFileSize, + [self uploadChunkUnavailableErrorWithDescription:errorMessage]); + return; + } + NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length); + + @try { + resultData = [uploadData subdataWithRange:range]; + } + @catch (NSException *exception) { + NSString *errorMessage = exception.description; + GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); + response(nil, + kGTMSessionUploadFetcherUnknownFileSize, + [self uploadChunkUnavailableErrorWithDescription:errorMessage]); + return; + } + } + response(resultData, kGTMSessionUploadFetcherUnknownFileSize, nil); + return; + } + NSURL *uploadFileURL = self.uploadFileURL; + if (uploadFileURL) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self generateChunkSubdataFromFileURL:uploadFileURL + offset:offset + length:length + response:response]; + }); + return; + } + GTMSESSION_ASSERT_DEBUG(_uploadFileHandle, @"Unexpectedly missing upload data package"); + NSFileHandle *uploadFileHandle = self.uploadFileHandle; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self generateChunkSubdataFromFileHandle:uploadFileHandle + offset:offset + length:length + response:response]; + }); +} + +- (void)generateChunkSubdataFromFileHandle:(NSFileHandle *)fileHandle + offset:(int64_t)offset + length:(int64_t)length + response:(GTMSessionUploadFetcherDataProviderResponse)response { + NSData *resultData; + NSError *error; + @try { + [fileHandle seekToFileOffset:(unsigned long long)offset]; + resultData = [fileHandle readDataOfLength:(NSUInteger)length]; + } + @catch (NSException *exception) { + GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileHandle failed to read, %@", exception); + error = [self uploadChunkUnavailableErrorWithDescription:exception.description]; + } + // The response always re-dispatches to the main thread, so we skip doing that here. + response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error); +} + +- (void)generateChunkSubdataFromFileURL:(NSURL *)fileURL + offset:(int64_t)offset + length:(int64_t)length + response:(GTMSessionUploadFetcherDataProviderResponse)response { + GTMSessionCheckNotSynchronized(self); + + NSData *resultData; + NSError *error; + int64_t fullUploadLength = [self fullUploadLength]; + NSData *mappedData = + [NSData dataWithContentsOfURL:fileURL + options:NSDataReadingMappedAlways + NSDataReadingUncached + error:&error]; + if (!mappedData) { + // We could not create an NSData by memory-mapping the file. +#if TARGET_IPHONE_SIMULATOR + // NSTemporaryDirectory() can differ in the simulator between app restarts, + // yet the contents for the new path remains unchanged, so try the latest temp path. + if ([error.domain isEqual:NSCocoaErrorDomain] && (error.code == NSFileReadNoSuchFileError)) { + NSString *filename = [fileURL lastPathComponent]; + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; + NSURL *newFileURL = [NSURL fileURLWithPath:filePath]; + if (![newFileURL isEqual:fileURL]) { + [self generateChunkSubdataFromFileURL:newFileURL + offset:offset + length:length + response:response]; + return; + } + } +#endif + + // If the file is just too large to create an NSData for, or if for some other reason we can't + // map it, create an NSFileHandle instead to read a subset into an NSData. +#if DEBUG + NSNumber *fileSizeNum; + BOOL hasFileSize = [fileURL getResourceValue:&fileSizeNum forKey:NSURLFileSizeKey error:NULL]; + GTMSESSION_LOG_DEBUG(@"Note: uploadFileURL is falling back to creating upload chunks by reading" + @" an NSFileHandle since uploadFileURL failed to map the upload file," + @" file size %@, %@", + hasFileSize ? fileSizeNum : @"unknown", error); +#endif + + NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL + error:&error]; + if (fileHandle != nil) { + [self generateChunkSubdataFromFileHandle:fileHandle + offset:offset + length:length + response:response]; + return; + } + GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileURL failed to read, %@", error); + // Fall through with the error. + } else { + // Successfully created an NSData by memory-mapping the file. + if ((NSUInteger)(offset + length) > mappedData.length) { + NSString *errorMessage = [NSString stringWithFormat: + @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld\texpected UploadLength: %lld", + offset, length, (long long)mappedData.length, fullUploadLength]; + GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); + response(nil, + kGTMSessionUploadFetcherUnknownFileSize, + [self uploadChunkUnavailableErrorWithDescription:errorMessage]); + return; + } + if (offset > 0 || length < fullUploadLength) { + NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length); + resultData = [mappedData subdataWithRange:range]; + } else { + resultData = mappedData; + } + } + // The response always re-dispatches to the main thread, so we skip re-dispatching here. + response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error); +} + +- (NSError *)uploadChunkUnavailableErrorWithDescription:(NSString *)description { + // The description in the userInfo is intended as a clue to programmers, not + // for client code to examine or rely on. + NSDictionary *userInfo = @{ @"description" : description }; + return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain + code:GTMSessionFetcherErrorUploadChunkUnavailable + userInfo:userInfo]; +} + +- (NSError *)prematureFailureErrorWithUserInfo:(NSDictionary *)userInfo { + // An error for if we get an unexpected status from the upload server or + // otherwise cannot continue. This is an issue beyond the upload protocol; + // there's no way the client can do anything useful except give up. + NSError *error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain + code:501 // Not implemented + userInfo:userInfo]; + return error; +} + ++ (GTMSessionUploadFetcherStatus)uploadStatusFromResponseHeaders:(NSDictionary *)responseHeaders { + NSString *statusString = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus]; + if ([statusString isEqual:@"active"]) { + return kStatusActive; + } + if ([statusString isEqual:@"final"]) { + return kStatusFinal; + } + if ([statusString isEqual:@"cancelled"]) { + return kStatusCancelled; + } + return kStatusUnknown; +} + +#pragma mark Method overrides affecting the initial fetch only + +- (void)setCompletionHandler:(GTMSessionFetcherCompletionHandler)handler { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateCompletionHandler = handler; + } +} + +- (void)setDelegateCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateCallbackQueue = queue; + } +} + +- (dispatch_queue_t GTM_NULLABLE_TYPE)delegateCallbackQueue { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _delegateCallbackQueue; + } +} + +- (BOOL)isRestartedUpload { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _isRestartedUpload; + } +} + +- (GTMSessionFetcher * GTM_NULLABLE_TYPE)chunkFetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _chunkFetcher; + } +} + +- (void)setChunkFetcher:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _chunkFetcher = fetcher; + } +} + +- (void)setFetcherInFlight:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _fetcherInFlight = fetcher; + } +} + +- (GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcherInFlight { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _fetcherInFlight; + } +} + +- (void)setCancellationHandler:(GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE) + cancellationHandler { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _cancellationHandler = cancellationHandler; + } +} + +- (GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE)cancellationHandler { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _cancellationHandler; + } +} + +- (void)beginFetchForRetry { + GTMSessionCheckNotSynchronized(self); + + // Override the superclass to reset the initial body length and fetcher-in-flight, + // then call the superclass implementation. + [self setInitialBodyLength:[self bodyLength]]; + + GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@", + self.fetcherInFlight); + self.fetcherInFlight = self; + [super beginFetchForRetry]; +} + +- (void)beginFetchWithCompletionHandler:(GTMSessionFetcherCompletionHandler)handler { + GTMSessionCheckNotSynchronized(self); + + [self setInitialBodyLength:[self bodyLength]]; + + // We'll hold onto the superclass's callback queue so we can invoke the handler + // even after the superclass has released the queue and its callback handler, as + // happens during auth failure. + [self setDelegateCallbackQueue:self.callbackQueue]; + self.completionHandler = handler; + + if ([self isRestartedUpload]) { + // When restarting an upload, we know the destination location for chunk fetches, + // but we need to query to find the initial offset. + if (![self isPaused]) { + [self sendQueryForUploadOffsetWithFetcherProperties:self.properties]; + } + return; + } + // We don't want to call into the client's completion block immediately + // after the finish of the initial connection (the delegate is called only + // when uploading finishes), so we substitute our own completion block to be + // called when the initial connection finishes + GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@", + self.fetcherInFlight); + + self.fetcherInFlight = self; + [super beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { + self.fetcherInFlight = nil; + // callback + + BOOL hasTestBlock = (self.testBlock != nil); + if (![self isRestartedUpload] && !hasTestBlock) { + if (error == nil) { + [self beginChunkFetches]; + } else { + if ([self retryTimer] == nil) { + [self invokeFinalCallbackWithData:nil + error:error + shouldInvalidateLocation:YES]; + } + } + } else { + // If there was no initial request, then this fetch is resuming some + // other uploadFetcher's initial request, and the superclass's connection + // is never used, so at this point we call the user's actual completion + // block. + if (!hasTestBlock) { + [self invokeFinalCallbackWithData:data + error:error + shouldInvalidateLocation:YES]; + } else { + // There was a test block, so we won't do chunk fetches, but we simulate obtaining + // the data to be uploaded from the upload data provider block or the file handle, + // and then call back. + [self generateChunkSubdataWithOffset:0 + length:[self fullUploadLength] + response:^(NSData *generateData, int64_t fullUploadLength, NSError *generateError) { + [self invokeFinalCallbackWithData:data + error:error + shouldInvalidateLocation:YES]; + }]; + } + } + }]; +} + +- (void)beginChunkFetches { + GTMSessionCheckNotSynchronized(self); + +#if DEBUG + // The initial response of the resumable upload protocol should have an + // empty body + // + // This assert typically happens because the upload create/edit link URL was + // not supplied with the request, and the server is thus expecting a non- + // resumable request/response. + if (self.downloadedData.length > 0) { + NSData *downloadedData = self.downloadedData; + NSString *str = [[NSString alloc] initWithData:downloadedData + encoding:NSUTF8StringEncoding]; + #pragma unused(str) + GTMSESSION_ASSERT_DEBUG(NO, @"unexpected response data (uploading to the wrong URL?)\n%@", str); + } +#endif + + // We need to get the upload URL from the location header to continue. + NSDictionary *responseHeaders = [self responseHeaders]; + + [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders]; + + GTMSessionUploadFetcherStatus uploadStatus = + [[self class] uploadStatusFromResponseHeaders:responseHeaders]; + GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown, + @"beginChunkFetches has unexpected upload status for headers %@", responseHeaders); + + BOOL isPrematureStop = (uploadStatus == kStatusFinal) || (uploadStatus == kStatusCancelled); + + NSString *uploadLocationURLStr = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadURL]; + BOOL hasUploadLocation = (uploadLocationURLStr.length > 0); + + if (isPrematureStop || !hasUploadLocation) { + GTMSESSION_ASSERT_DEBUG(NO, @"Premature failure: upload-status:\"%@\" location:%@", + [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], uploadLocationURLStr); + // We cannot continue since we do not know the location to use + // as our upload destination. + NSDictionary *userInfo = nil; + NSData *downloadedData = self.downloadedData; + if (downloadedData.length > 0) { + userInfo = @{ kGTMSessionFetcherStatusDataKey : downloadedData }; + } + NSError *failureError = [self prematureFailureErrorWithUserInfo:userInfo]; + [self invokeFinalCallbackWithData:nil + error:failureError + shouldInvalidateLocation:YES]; + return; + } + + self.uploadLocationURL = [NSURL URLWithString:uploadLocationURLStr]; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc postNotificationName:kGTMSessionFetcherUploadLocationObtainedNotification + object:self]; + + // we've now sent all of the initial post body data, so we need to include + // its size in future progress indicator callbacks + [self setInitialBodySent:[self initialBodyLength]]; + + // just in case the user paused us during the initial fetch... + if (![self isPaused]) { + [self uploadNextChunkWithOffset:0]; + } +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent + totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + // Overrides the superclass. + [self invokeDelegateWithDidSendBytes:bytesSent + totalBytesSent:totalBytesSent + totalBytesExpectedToSend:totalBytesExpectedToSend + [self fullUploadLength]]; +} + +- (BOOL)shouldReleaseCallbacksUponCompletion { + // Overrides the superclass. + + // We don't want the superclass to release the delegate and callback + // blocks once the initial fetch has finished + // + // This is invoked for only successful completion of the connection; + // an error always will invoke and release the callbacks + return NO; +} + +- (void)invokeFinalCallbackWithData:(NSData *)data + error:(NSError *)error + shouldInvalidateLocation:(BOOL)shouldInvalidateLocation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (shouldInvalidateLocation) { + _uploadLocationURL = nil; + } + + dispatch_queue_t queue = _delegateCallbackQueue; + GTMSessionFetcherCompletionHandler handler = _delegateCompletionHandler; + if (queue && handler) { + [self invokeOnCallbackQueue:queue + afterUserStopped:NO + block:^{ + handler(data, error); + }]; + } + } // @synchronized(self) + + [self releaseUploadAndBaseCallbacks:!self.userStoppedFetching]; +} + +- (void)releaseUploadAndBaseCallbacks:(BOOL)shouldReleaseCancellation { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateCallbackQueue = nil; + _delegateCompletionHandler = nil; + _uploadDataProvider = nil; + if (shouldReleaseCancellation) { + _cancellationHandler = nil; + } + } + + // Release the base class's callbacks, too, if needed. + [self releaseCallbacks]; +} + +- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks { + GTMSessionCheckNotSynchronized(self); + + // Clear _fetcherInFlight when stopped. Moved from stopFetching, since that's a public method, + // where this method does the work. Fixes issue clearing value when retryBlock included. + GTMSessionFetcher *fetcherInFlight = self.fetcherInFlight; + if (fetcherInFlight == self) { + self.fetcherInFlight = nil; + } + + [super stopFetchReleasingCallbacks:shouldReleaseCallbacks]; + + if (shouldReleaseCallbacks) { + [self releaseUploadAndBaseCallbacks:NO]; + } +} + +#pragma mark Chunk fetching methods + +- (void)uploadNextChunkWithOffset:(int64_t)offset { + // use the properties in each chunk fetcher + NSDictionary *props = [self properties]; + + [self uploadNextChunkWithOffset:offset + fetcherProperties:props]; +} + +- (void)sendQueryForUploadOffsetWithFetcherProperties:(NSDictionary *)props { + GTMSessionFetcher *queryFetcher = [self uploadFetcherWithProperties:props + isQueryFetch:YES]; + queryFetcher.bodyData = [NSData data]; + + NSString *originalComment = self.comment; + [queryFetcher setCommentWithFormat:@"%@ (query offset)", + originalComment ? originalComment : @"upload"]; + + [queryFetcher setRequestValue:@"query" forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; + + self.fetcherInFlight = queryFetcher; + [queryFetcher beginFetchWithDelegate:self + didFinishSelector:@selector(queryFetcher:finishedWithData:error:)]; +} + +- (void)queryFetcher:(GTMSessionFetcher *)queryFetcher + finishedWithData:(NSData *)data + error:(NSError *)error { + self.fetcherInFlight = nil; + + NSDictionary *responseHeaders = [queryFetcher responseHeaders]; + NSString *sizeReceivedHeader; + + GTMSessionUploadFetcherStatus uploadStatus = + [[self class] uploadStatusFromResponseHeaders:responseHeaders]; + GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown || error != nil, + @"query fetcher completion has unexpected upload status for headers %@", responseHeaders); + + if (error == nil) { + sizeReceivedHeader = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadSizeReceived]; + + if (uploadStatus == kStatusCancelled || + (uploadStatus == kStatusActive && sizeReceivedHeader == nil)) { + NSDictionary *userInfo = nil; + if (data.length > 0) { + userInfo = @{ kGTMSessionFetcherStatusDataKey : data }; + } + error = [self prematureFailureErrorWithUserInfo:userInfo]; + } + } + + if (error == nil) { + int64_t offset = [sizeReceivedHeader longLongValue]; + int64_t fullUploadLength = [self fullUploadLength]; + if (uploadStatus == kStatusFinal || + (offset >= fullUploadLength && + fullUploadLength != kGTMSessionUploadFetcherUnknownFileSize)) { + // Handle we're done + [self chunkFetcher:queryFetcher finishedWithData:data error:nil]; + } else { + [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders]; + [self uploadNextChunkWithOffset:offset]; + } + } else { + // Handle query error + [self chunkFetcher:queryFetcher finishedWithData:data error:error]; + } +} + +- (void)sendCancelUploadWithFetcherProperties:(NSDictionary *)props { + @synchronized(self) { + _isCancelInFlight = YES; + } + GTMSessionFetcher *cancelFetcher = [self uploadFetcherWithProperties:props + isQueryFetch:YES]; + cancelFetcher.bodyData = [NSData data]; + + NSString *originalComment = self.comment; + [cancelFetcher setCommentWithFormat:@"%@ (cancel)", + originalComment ? originalComment : @"upload"]; + + [cancelFetcher setRequestValue:@"cancel" forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; + + self.fetcherInFlight = cancelFetcher; + [cancelFetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { + self.fetcherInFlight = nil; + if (![self triggerCancellationHandlerForFetch:cancelFetcher data:data error:error]) { + if (error) { + GTMSESSION_LOG_DEBUG(@"cancelFetcher %@", error); + } + } + @synchronized(self) { + self->_isCancelInFlight = NO; + } + }]; +} + +- (void)uploadNextChunkWithOffset:(int64_t)offset + fetcherProperties:(NSDictionary *)props { + GTMSessionCheckNotSynchronized(self); + + // Example chunk headers: + // X-Goog-Upload-Command: upload, finalize + // X-Goog-Upload-Offset: 0 + // Content-Length: 2000000 + // Content-Type: image/jpeg + // + // {bytes 0-1999999} + + // The chunk upload URL requires no authentication header. + GTMSessionFetcher *chunkFetcher = [self uploadFetcherWithProperties:props + isQueryFetch:NO]; + [self attachSendProgressBlockToChunkFetcher:chunkFetcher]; + int64_t chunkSize = [self updateChunkFetcher:chunkFetcher + forChunkAtOffset:offset]; + BOOL isUploadingFileURL = (self.uploadFileURL != nil); + int64_t fullUploadLength = [self fullUploadLength]; + + // The chunk size may have changed, so determine again if we're uploading the full file. + BOOL isUploadingFullFile = (offset == 0 && + fullUploadLength != kGTMSessionUploadFetcherUnknownFileSize && + chunkSize >= fullUploadLength); + if (isUploadingFullFile && isUploadingFileURL) { + // The data is the full upload file URL. + chunkFetcher.bodyFileURL = self.uploadFileURL; + [self beginChunkFetcher:chunkFetcher + offset:offset]; + } else { + // Make an NSData for the subset for this upload chunk. + self.subdataGenerating = YES; + [self generateChunkSubdataWithOffset:offset + length:chunkSize + response:^(NSData *chunkData, int64_t uploadFileLength, NSError *chunkError) { + // The subdata methods may leave us on a background thread. + dispatch_async(dispatch_get_main_queue(), ^{ + self.subdataGenerating = NO; + + // dont allow the updating of fileLength for uploads not using a data provider as they + // should know the file length before the upload starts. + if (self->_uploadDataProvider != nil && uploadFileLength > 0) { + [self setUploadFileLength:uploadFileLength]; + // Update the command and content-length headers if this is the last chunk to be sent. + if (offset + chunkSize >= uploadFileLength) { + int64_t updatedChunkSize = [self updateChunkFetcher:chunkFetcher + forChunkAtOffset:offset]; + if (updatedChunkSize == 0) { + // Calling beginChunkFetcher early when there is no more data to send allows us to + // properly handle nil chunkData below without having to account for the case where + // we are just finalizing the file. + chunkFetcher.bodyData = [[NSData alloc] init]; + [self beginChunkFetcher:chunkFetcher + offset:offset]; + return; + } + } + } + + if (chunkData == nil) { + NSError *responseError = chunkError; + if (!responseError) { + responseError = [self uploadChunkUnavailableErrorWithDescription:@"chunkData is nil"]; + } + [self invokeFinalCallbackWithData:nil + error:responseError + shouldInvalidateLocation:YES]; + return; + } + + BOOL didWriteFile = NO; + if (isUploadingFileURL) { + // Make a temporary file with the data subset. + NSString *tempName = + [NSString stringWithFormat:@"GTMUpload_temp_%@", [[NSUUID UUID] UUIDString]]; + NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempName]; + NSError *writeError; + didWriteFile = [chunkData writeToFile:tempPath + options:NSDataWritingAtomic + error:&writeError]; + if (didWriteFile) { + chunkFetcher.bodyFileURL = [NSURL fileURLWithPath:tempPath]; + } else { + GTMSESSION_LOG_DEBUG(@"writeToFile failed: %@\n%@", writeError, tempPath); + } + } + if (!didWriteFile) { + chunkFetcher.bodyData = [chunkData copy]; + } + [self beginChunkFetcher:chunkFetcher + offset:offset]; + }); + }]; + } +} + +- (void)beginChunkFetcher:(GTMSessionFetcher *)chunkFetcher + offset:(int64_t)offset { + + // Track the current offset for progress reporting + self.currentOffset = offset; + + // Hang on to the fetcher in case we need to cancel it. We set these before beginning the + // chunk fetch so the observers notified of chunk fetches can inspect the upload fetcher to + // match to the chunk. + self.chunkFetcher = chunkFetcher; + self.fetcherInFlight = chunkFetcher; + + // Update the last chunk request, including any request headers. + self.lastChunkRequest = chunkFetcher.request; + + [chunkFetcher beginFetchWithDelegate:self + didFinishSelector:@selector(chunkFetcher:finishedWithData:error:)]; +} + +- (void)attachSendProgressBlockToChunkFetcher:(GTMSessionFetcher *)chunkFetcher { + chunkFetcher.sendProgressBlock = ^(int64_t bytesSent, int64_t totalBytesSent, + int64_t totalBytesExpectedToSend) { + // The total bytes expected include the initial body and the full chunked + // data, independent of how big this fetcher's chunk is. + int64_t initialBodySent = [self bodyLength]; // TODO(grobbins) use [self initialBodySent] + int64_t totalSent = initialBodySent + self.currentOffset + totalBytesSent; + int64_t totalExpected = initialBodySent + [self fullUploadLength]; + + [self invokeDelegateWithDidSendBytes:bytesSent + totalBytesSent:totalSent + totalBytesExpectedToSend:totalExpected]; + }; +} + +- (NSDictionary *)uploadSessionIdentifierMetadata { + NSMutableDictionary *metadata = [NSMutableDictionary dictionary]; + metadata[kGTMSessionIdentifierIsUploadChunkFetcherMetadataKey] = @YES; + GTMSESSION_ASSERT_DEBUG(self.uploadFileURL, + @"Invalid upload fetcher to create session identifier for metadata"); + metadata[kGTMSessionIdentifierUploadFileURLMetadataKey] = [self.uploadFileURL absoluteString]; + metadata[kGTMSessionIdentifierUploadFileLengthMetadataKey] = @([self fullUploadLength]); + + if (self.uploadLocationURL) { + metadata[kGTMSessionIdentifierUploadLocationURLMetadataKey] = + [self.uploadLocationURL absoluteString]; + } + if (self.uploadMIMEType) { + metadata[kGTMSessionIdentifierUploadMIMETypeMetadataKey] = self.uploadMIMEType; + } + metadata[kGTMSessionIdentifierUploadChunkSizeMetadataKey] = @(self.chunkSize); + metadata[kGTMSessionIdentifierUploadCurrentOffsetMetadataKey] = @(self.currentOffset); + metadata[kGTMSessionIdentifierUploadAllowsCellularAccess] = @(self.request.allowsCellularAccess); + + return metadata; +} + +- (GTMSessionFetcher *)uploadFetcherWithProperties:(NSDictionary *)properties + isQueryFetch:(BOOL)isQueryFetch { + GTMSessionCheckNotSynchronized(self); + + // Common code to make a request for a query command or for a chunk upload. + NSURL *uploadLocationURL = self.uploadLocationURL; + NSMutableURLRequest *chunkRequest = [NSMutableURLRequest requestWithURL:uploadLocationURL]; + [chunkRequest setHTTPMethod:@"PUT"]; + + // copy the user-agent from the original connection + // n.b. that self.request is nil for upload fetchers created with an existing upload location + // URL. + NSURLRequest *origRequest = self.request; + + chunkRequest.allowsCellularAccess = origRequest.allowsCellularAccess; + if (!origRequest) { + chunkRequest.allowsCellularAccess = _allowsCellularAccess; + } + NSString *userAgent = [origRequest valueForHTTPHeaderField:@"User-Agent"]; + if (userAgent.length > 0) { + [chunkRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + } + + [chunkRequest setValue:kGTMSessionXGoogUploadProtocolResumable + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol]; + + // To avoid timeouts when debugging, copy the timeout of the initial fetcher. + NSTimeInterval origTimeout = [origRequest timeoutInterval]; + [chunkRequest setTimeoutInterval:origTimeout]; + + // + // Make a new chunk fetcher. + // + GTMSessionFetcher *chunkFetcher = [GTMSessionFetcher fetcherWithRequest:chunkRequest]; + chunkFetcher.callbackQueue = self.callbackQueue; + chunkFetcher.sessionUserInfo = self.sessionUserInfo; + chunkFetcher.configurationBlock = self.configurationBlock; + chunkFetcher.allowedInsecureSchemes = self.allowedInsecureSchemes; + chunkFetcher.allowLocalhostRequest = self.allowLocalhostRequest; + chunkFetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates; + chunkFetcher.useUploadTask = !isQueryFetch; + + if (self.uploadFileURL && !isQueryFetch && self.useBackgroundSession) { + [chunkFetcher createSessionIdentifierWithMetadata:[self uploadSessionIdentifierMetadata]]; + } + + // Give the chunk fetcher the same properties as the previous chunk fetcher + chunkFetcher.properties = [properties mutableCopy]; + [chunkFetcher setProperty:[NSValue valueWithNonretainedObject:self] + forKey:kGTMSessionUploadFetcherChunkParentKey]; + + // copy other fetcher settings to the new fetcher + chunkFetcher.retryEnabled = self.retryEnabled; + chunkFetcher.maxRetryInterval = self.maxRetryInterval; + + if ([self isRetryEnabled]) { + // We interpose our own retry method both so we can change the request to ask the server to + // tell us where to resume the chunk. + chunkFetcher.retryBlock = ^(BOOL suggestedWillRetry, NSError *chunkError, + GTMSessionFetcherRetryResponse response) { + void (^finish)(BOOL) = ^(BOOL shouldRetry){ + // We'll retry by sending an offset query. + if (shouldRetry) { + self.shouldInitiateOffsetQuery = !isQueryFetch; + + // We don't know what our actual offset is anymore, but the server will tell us. + self.currentOffset = 0; + } + // We don't actually want to retry this specific fetcher. + response(NO); + }; + + GTMSessionFetcherRetryBlock retryBlock = self.retryBlock; + if (retryBlock) { + // Ask the client, then call the finish block above. + retryBlock(suggestedWillRetry, chunkError, finish); + } else { + finish(suggestedWillRetry); + } + }; + } + + return chunkFetcher; +} + +- (void)chunkFetcher:(GTMSessionFetcher *)chunkFetcher + finishedWithData:(NSData *)data + error:(NSError *)error { + BOOL hasDestroyedOldChunkFetcher = NO; + self.fetcherInFlight = nil; + + NSDictionary *responseHeaders = [chunkFetcher responseHeaders]; + GTMSessionUploadFetcherStatus uploadStatus = + [[self class] uploadStatusFromResponseHeaders:responseHeaders]; + GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown + || error != nil + || self.wasCreatedFromBackgroundSession, + @"chunk fetcher completion has kStatusUnknown upload status for headers %@ fetcher %@", + responseHeaders, self); + BOOL isUploadStatusStopped = (uploadStatus == kStatusFinal || uploadStatus == kStatusCancelled); + + // Check if the fetcher was actually querying. If it failed, do not retry, + // as it would enter an infinite retry loop. + NSString *uploadCommand = + chunkFetcher.request.allHTTPHeaderFields[kGTMSessionHeaderXGoogUploadCommand]; + BOOL isQueryFetch = [uploadCommand isEqual:@"query"]; + + // TODO + // Maybe here we can check to see if the request had x goog content length set. (the file length one). + NSString *previousContentLengthValue = + [chunkFetcher.request valueForHTTPHeaderField:@"Content-Length"]; + // The Content-Length header may not be present if the chunk fetcher was recreated from + // a background session. + BOOL hasKnownChunkSize = (previousContentLengthValue != nil); + int64_t previousContentLength = [previousContentLengthValue longLongValue]; + + BOOL needsQuery = (!hasKnownChunkSize && !isUploadStatusStopped); + + if (error || (needsQuery && !isQueryFetch)) { + NSInteger status = error.code; + + // Status 4xx indicates a bad offset in the Google upload protocol. However, do not retry status + // 404 per spec, nor if the upload size appears to have been zero (since the server will just + // keep asking us to retry.) + if (self.shouldInitiateOffsetQuery || + (needsQuery && !isQueryFetch) || + ([error.domain isEqual:kGTMSessionFetcherStatusDomain] && + status >= 400 && status <= 499 && + status != 404 && + uploadStatus == kStatusActive && + previousContentLength > 0)) { + self.shouldInitiateOffsetQuery = NO; + [self destroyChunkFetcher]; + hasDestroyedOldChunkFetcher = YES; + [self sendQueryForUploadOffsetWithFetcherProperties:chunkFetcher.properties]; + } else { + // Some unexpected status has occurred; handle it as we would a regular + // object fetcher failure. + [self invokeFinalCallbackWithData:data + error:error + shouldInvalidateLocation:NO]; + } + } else { + // The chunk has uploaded successfully. + int64_t newOffset = self.currentOffset + previousContentLength; +#if DEBUG + // Verify that if we think all of the uploading data has been sent, the server responded with + // the "final" upload status. + BOOL hasUploadAllData = (newOffset == [self fullUploadLength]); + BOOL isFinalStatus = (uploadStatus == kStatusFinal); + #pragma unused(hasUploadAllData,isFinalStatus) + GTMSESSION_ASSERT_DEBUG(hasUploadAllData == isFinalStatus || !hasKnownChunkSize, + @"uploadStatus:%@ newOffset:%lld (%lld + %lld) fullUploadLength:%lld" + @" chunkFetcher:%@ requestHeaders:%@ responseHeaders:%@", + [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], + newOffset, self.currentOffset, previousContentLength, + [self fullUploadLength], + chunkFetcher, chunkFetcher.request.allHTTPHeaderFields, + responseHeaders); +#endif + if (isUploadStatusStopped || + (!_uploadData && _uploadFileLength == 0) || + (_currentOffset > _uploadFileLength && _uploadFileLength > 0)) { + // This was the last chunk. + if (error == nil && uploadStatus == kStatusCancelled) { + // Report cancelled status as an error. + NSDictionary *userInfo = nil; + if (data.length > 0) { + userInfo = @{ kGTMSessionFetcherStatusDataKey : data }; + } + data = nil; + error = [self prematureFailureErrorWithUserInfo:userInfo]; + } else { + // The upload is in final status. + // + // Take the chunk fetcher's data as the superclass data. + self.downloadedData = data; + self.statusCode = chunkFetcher.statusCode; + } + + // we're done + [self invokeFinalCallbackWithData:data + error:error + shouldInvalidateLocation:YES]; + } else { + // Start the next chunk. + self.currentOffset = newOffset; + + // We want to destroy this chunk fetcher before creating the next one, but + // we want to pass on its properties + NSDictionary *props = [chunkFetcher properties]; + + // We no longer need to be able to cancel this chunkFetcher. Destroy it + // before we create a new chunk fetcher. + [self destroyChunkFetcher]; + hasDestroyedOldChunkFetcher = YES; + + [self uploadNextChunkWithOffset:newOffset + fetcherProperties:props]; + } + } + if (!hasDestroyedOldChunkFetcher) { + [self destroyChunkFetcher]; + } +} + +- (void)destroyChunkFetcher { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_fetcherInFlight == _chunkFetcher) { + _fetcherInFlight = nil; + } + + [_chunkFetcher stopFetching]; + + NSURL *chunkFileURL = _chunkFetcher.bodyFileURL; + BOOL wasTemporaryUploadFile = ![chunkFileURL isEqual:_uploadFileURL]; + if (wasTemporaryUploadFile) { + NSError *error; + [[NSFileManager defaultManager] removeItemAtURL:chunkFileURL + error:&error]; + if (error) { + GTMSESSION_LOG_DEBUG(@"removingItemAtURL failed: %@\n%@", error, chunkFileURL); + } + } + + _recentChunkReponseHeaders = _chunkFetcher.responseHeaders; + + // To avoid retain cycles, remove all properties except the parent identifier. + _chunkFetcher.properties = + @{ kGTMSessionUploadFetcherChunkParentKey : [NSValue valueWithNonretainedObject:self] }; + + _chunkFetcher.retryBlock = nil; + _chunkFetcher.sendProgressBlock = nil; + _chunkFetcher = nil; + } // @synchronized(self) +} + +// This method calculates the proper values to pass to the client's send progress block. +// +// The actual total bytes sent include the initial body sent, plus the +// offset into the batched data prior to the current chunk fetcher + +- (void)invokeDelegateWithDidSendBytes:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent + totalBytesExpectedToSend:(int64_t)totalBytesExpected { + GTMSessionCheckNotSynchronized(self); + + // Ensure the chunk fetcher survives the callback in case the user pauses the upload process. + __block GTMSessionFetcher *holdFetcher = self.chunkFetcher; + + [self invokeOnCallbackQueue:self.delegateCallbackQueue + afterUserStopped:NO + block:^{ + GTMSessionFetcherSendProgressBlock sendProgressBlock = self.sendProgressBlock; + if (sendProgressBlock) { + sendProgressBlock(bytesSent, totalBytesSent, totalBytesExpected); + } + holdFetcher = nil; + }]; +} + +- (void)retrieveUploadChunkGranularityFromResponseHeaders:(NSDictionary *)responseHeaders { + GTMSessionCheckNotSynchronized(self); + + // Standard granularity for Google uploads is 256K. + NSString *chunkGranularityHeader = + [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadChunkGranularity]; + self.uploadGranularity = chunkGranularityHeader.longLongValue; +} + +#pragma mark - + +- (BOOL)isPaused { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _isPaused; + } // @synchronized(self) +} + +- (void)pauseFetching { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _isPaused = YES; + } // @synchronized(self) + + // Pausing just means stopping the current chunk from uploading; + // when we resume, we will send a query request to the server to + // figure out what bytes to resume sending. + // + // We won't try to cancel the initial data upload, but rather will check + // for being paused in beginChunkFetches. + [self destroyChunkFetcher]; +} + +- (void)resumeFetching { + BOOL wasPaused; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + wasPaused = _isPaused; + _isPaused = NO; + } // @synchronized(self) + + if (wasPaused) { + [self sendQueryForUploadOffsetWithFetcherProperties:self.properties]; + } +} + +- (void)stopFetching { + // Overrides the superclass + [self destroyChunkFetcher]; + + // If we think the server is waiting for more data, then tell it there won't be more. + if (self.uploadLocationURL) { + [self sendCancelUploadWithFetcherProperties:[self properties]]; + self.uploadLocationURL = nil; + } else { + [self invokeOnCallbackQueue:self.callbackQueue + afterUserStopped:YES + block:^{ + // Repeated calls to stopFetching may cause this path to be reached despite having sent a real + // cancel request, check here to ensure that the cancellation handler invocation which fires + // will definitely be for the real request sent previously. + @synchronized(self) { + if (self->_isCancelInFlight) { + return; + } + } + [self triggerCancellationHandlerForFetch:nil data:nil error:nil]; + }]; + } + + [super stopFetching]; +} + +// Fires the cancellation handler, returning whether there was a handler to be fired. +- (BOOL)triggerCancellationHandlerForFetch:(GTMSessionFetcher *)fetcher + data:(NSData *)data + error:(NSError *)error { + GTMSessionUploadFetcherCancellationHandler handler = self.cancellationHandler; + if (handler) { + handler(fetcher, data, error); + self.cancellationHandler = nil; + return YES; + } + return NO; +} + +#pragma mark - + +- (int64_t)updateChunkFetcher:(GTMSessionFetcher *)chunkFetcher + forChunkAtOffset:(int64_t)offset { + BOOL isUploadingFileURL = (self.uploadFileURL != nil); + + // Upload another chunk, meeting server-required granularity. + int64_t chunkSize = self.chunkSize; + + int64_t fullUploadLength = [self fullUploadLength]; + BOOL isFileLengthKnown = fullUploadLength >= 0; + + BOOL isUploadingFullFile = (offset == 0 && isFileLengthKnown && chunkSize >= fullUploadLength); + if (!isUploadingFileURL || !isUploadingFullFile) { + // We're not uploading the entire file and given the file URL. Since we'll be + // allocating a subdata block for a chunk, we need to bound it to something that + // won't blow the process's memory. + if (chunkSize > kGTMSessionUploadFetcherMaximumDemandBufferSize) { + chunkSize = kGTMSessionUploadFetcherMaximumDemandBufferSize; + } + } + + int64_t granularity = self.uploadGranularity; + if (granularity > 0) { + if (chunkSize < granularity) { + chunkSize = granularity; + } else { + chunkSize = chunkSize - (chunkSize % granularity); + } + } + + GTMSESSION_ASSERT_DEBUG(offset < fullUploadLength || fullUploadLength == 0, + @"offset %lld exceeds data length %lld", offset, fullUploadLength); + + if (granularity > 0) { + offset = offset - (offset % granularity); + } + + // If the chunk size is bigger than the remaining data, or else + // it's close enough in size to the remaining data that we'd rather + // avoid having a whole extra http fetch for the leftover bit, then make + // this chunk size exactly match the remaining data size + NSString *command; + int64_t thisChunkSize = chunkSize; + + BOOL isChunkTooBig = (thisChunkSize >= (fullUploadLength - offset)); + BOOL isChunkAlmostBigEnough = (fullUploadLength - offset - 2500 < thisChunkSize); + BOOL isFinalChunk = (isChunkTooBig || isChunkAlmostBigEnough) && isFileLengthKnown; + if (isFinalChunk) { + thisChunkSize = fullUploadLength - offset; + if (thisChunkSize > 0) { + command = @"upload, finalize"; + } else { + command = @"finalize"; + } + } else { + command = @"upload"; + } + NSString *lengthStr = @(thisChunkSize).stringValue; + NSString *offsetStr = @(offset).stringValue; + + [chunkFetcher setRequestValue:command forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; + [chunkFetcher setRequestValue:lengthStr forHTTPHeaderField:@"Content-Length"]; + [chunkFetcher setRequestValue:offsetStr forHTTPHeaderField:kGTMSessionHeaderXGoogUploadOffset]; + if (_uploadFileLength != kGTMSessionUploadFetcherUnknownFileSize) { + [chunkFetcher setRequestValue:@([self fullUploadLength]).stringValue + forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength]; + } + + // Append the range of bytes in this chunk to the fetcher comment. + NSString *baseComment = self.comment; + [chunkFetcher setCommentWithFormat:@"%@ (%lld-%lld)", + baseComment ? baseComment : @"upload", offset, MAX(0, offset + thisChunkSize - 1)]; + + return thisChunkSize; +} + +// Public properties. +@synthesize currentOffset = _currentOffset, + allowsCellularAccess = _allowsCellularAccess, + delegateCompletionHandler = _delegateCompletionHandler, + chunkFetcher = _chunkFetcher, + lastChunkRequest = _lastChunkRequest, + subdataGenerating = _subdataGenerating, + shouldInitiateOffsetQuery = _shouldInitiateOffsetQuery, + uploadGranularity = _uploadGranularity; + +// Internal properties. +@dynamic fetcherInFlight; +@dynamic activeFetcher; +@dynamic statusCode; +@dynamic delegateCallbackQueue; + ++ (void)removePointer:(void *)pointer fromPointerArray:(NSPointerArray *)pointerArray { + for (NSUInteger index = 0, count = pointerArray.count; index < count; ++index) { + void *pointerAtIndex = [pointerArray pointerAtIndex:index]; + if (pointerAtIndex == pointer) { + [pointerArray removePointerAtIndex:index]; + return; + } + } +} + +- (BOOL)useBackgroundSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _useBackgroundSessionOnChunkFetchers; + } // @synchronized(self +} + +- (void)setUseBackgroundSession:(BOOL)useBackgroundSession { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_useBackgroundSessionOnChunkFetchers != useBackgroundSession) { + _useBackgroundSessionOnChunkFetchers = useBackgroundSession; + NSPointerArray *uploadFetcherPointerArrayForBackgroundSessions = + [[self class] uploadFetcherPointerArrayForBackgroundSessions]; + @synchronized(uploadFetcherPointerArrayForBackgroundSessions) { + if (_useBackgroundSessionOnChunkFetchers) { + [uploadFetcherPointerArrayForBackgroundSessions addPointer:(__bridge void *)self]; + } else { + [[self class] removePointer:(__bridge void *)self + fromPointerArray:uploadFetcherPointerArrayForBackgroundSessions]; + } + } // @synchronized(uploadFetcherPointerArrayForBackgroundSessions) + } + } // @synchronized(self) +} + +- (BOOL)canFetchWithBackgroundSession { + // The initial upload fetcher is always a foreground session; the + // useBackgroundSession property will apply only to chunk fetchers, + // not to queries. + return NO; +} + +- (NSDictionary *)responseHeaders { + GTMSessionCheckNotSynchronized(self); + // Overrides the superclass + + // If asked for the fetcher's response, use the most recent chunk fetcher's response, + // since the original request's response lacks useful information like the actual + // Content-Type. + NSDictionary *dict = self.chunkFetcher.responseHeaders; + if (dict) { + return dict; + } + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + if (_recentChunkReponseHeaders) { + return _recentChunkReponseHeaders; + } + } // @synchronized(self + + // No chunk fetcher yet completed, so return whatever we have from the initial fetch. + return [super responseHeaders]; +} + +- (NSInteger)statusCodeUnsynchronized { + GTMSessionCheckSynchronized(self); + + if (_recentChunkStatusCode != -1) { + // Overrides the superclass to indicate status appropriate to the initial + // or latest chunk fetch + return _recentChunkStatusCode; + } else { + return [super statusCodeUnsynchronized]; + } +} + + +- (void)setStatusCode:(NSInteger)val { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _recentChunkStatusCode = val; + } +} + +- (int64_t)initialBodyLength { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _initialBodyLength; + } +} + +- (void)setInitialBodyLength:(int64_t)length { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _initialBodyLength = length; + } +} + +- (int64_t)initialBodySent { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _initialBodySent; + } +} + +- (void)setInitialBodySent:(int64_t)length { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _initialBodySent = length; + } +} + +- (NSURL *)uploadLocationURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + return _uploadLocationURL; + } +} + +- (void)setUploadLocationURL:(NSURL *)locationURL { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _uploadLocationURL = locationURL; + } +} + +- (GTMSessionFetcher *)activeFetcher { + GTMSessionFetcher *result = self.fetcherInFlight; + if (result) return result; + + return self; +} + +- (BOOL)isFetching { + // If there is an active chunk fetcher, then the upload fetcher is considered + // to still be fetching. + if (self.fetcherInFlight != nil) return YES; + + return [super isFetching]; +} + +- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; + + while (self.fetcherInFlight || self.subdataGenerating) { + if ([timeoutDate timeIntervalSinceNow] < 0) return NO; + + if (self.subdataGenerating) { + // Allow time for subdata generation. + NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001]; + [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; + } else { + // Wait for any chunk or query fetchers that still have pending callbacks or + // notifications. + BOOL timedOut; + + if (self.fetcherInFlight == self) { + timedOut = ![super waitForCompletionWithTimeout:timeoutInSeconds]; + } else { + timedOut = ![self.fetcherInFlight waitForCompletionWithTimeout:timeoutInSeconds]; + } + if (timedOut) return NO; + } + } + return YES; +} + +@end + +@implementation GTMSessionFetcher (GTMSessionUploadFetcherMethods) + +- (GTMSessionUploadFetcher *)parentUploadFetcher { + NSValue *property = [self propertyForKey:kGTMSessionUploadFetcherChunkParentKey]; + if (!property) return nil; + + GTMSessionUploadFetcher *uploadFetcher = property.nonretainedObjectValue; + + GTMSESSION_ASSERT_DEBUG([uploadFetcher isKindOfClass:[GTMSessionUploadFetcher class]], + @"Unexpected parent upload fetcher class: %@", [uploadFetcher class]); + return uploadFetcher; +} + +@end diff --git a/Pods/GoogleSignIn/.cocoapods.yml b/Pods/GoogleSignIn/.cocoapods.yml new file mode 100755 index 00000000..64a82b1e --- /dev/null +++ b/Pods/GoogleSignIn/.cocoapods.yml @@ -0,0 +1,5 @@ +try: + install: + pre: + - git clone https://github.com/googlesamples/google-services + project: 'google-services/ios/signin/SignInExample.xcodeproj' diff --git a/Pods/GoogleSignIn/CHANGELOG.md b/Pods/GoogleSignIn/CHANGELOG.md new file mode 100755 index 00000000..4c929073 --- /dev/null +++ b/Pods/GoogleSignIn/CHANGELOG.md @@ -0,0 +1,132 @@ +# 2019-11-7 -- v5.0.2 +- Fixes the wrong error code being sent to `signIn:didSignInForUser:withError:` when the user + cancels iOS's consent dialog during the sign-in flow. + +# 2019-10-9 -- v5.0.1 +- Fixes an issue that the sign in flow cannot be correctly started on iOS 13. +- The zip distribution requires Xcode 11 or above. + +# 2019-8-14 -- v5.0.0 +- Changes to GIDSignIn + - `uiDelegate` has been replaced with `presentingViewController`. + - `hasAuthInKeychain` has been replaced with `hasPreviousSignIn`. + - `signInSilently` has been replaced with `restorePreviousSignIn`. + - Removed deprecated `kGIDSignInErrorCodeNoSignInHandlersInstalled` error code. +- Changes to GIDAuthentication + - Removed deprecated methods `getAccessTokenWithHandler:` and `refreshAccessTokenWithHandler:`. +- Changes to GIDGoogleUser + - Removed deprecated property `accessibleScopes`, use `grantedScopes` instead. +- Adds dependencies on AppAuth and GTMAppAuth. +- Removes the dependency on GoogleToolboxForMac. +- Drops support for iOS 7. + +# 2018-11-26 -- v4.4.0 +- Removes the dependency on GTM OAuth 2. + +# 2018-10-1 -- v4.3.0 +- Supports Google's Enterprise Mobile Management. + +# 2018-8-10 -- v4.2.0 +- Adds `grantedScopes` to `GIDGoogleUser`, allowing confirmation of which scopes + have been granted after a successful sign-in. +- Deprecates `accessibleScopes` in `GIDGoogleUser`, use `grantedScopes` instead. +- Localizes `GIDSignInButton` for hi (Hindi) and fr-CA (French (Canada)). +- Adds dependency to the system `LocalAuthentication` framework. + +# 2018-1-8 -- v4.1.2 +- Add `pod try` support for the GoogleSignIn CocoaPod. + +# 2017-10-17 -- v4.1.1 +- Fixes an issue that `GIDSignInUIDelegate`'s `signInWillDispatch:error:` was + not called on iOS 11. Please note that it is intended that neither + `signIn:presentViewController:` nor `signIn:dismissViewController:` is called + on iOS 11 because SFAuthenticationSession is not presented by the app's view + controller. + +# 2017-09-13 -- v4.1.0 +- Uses SFAuthenticationSession on iOS 11. + +# 2017-02-06 -- v4.0.2 +- No longer depends on GoogleAppUtilities. + +# 2016-10-24 -- v4.0.1 +- Switches to open source pod dependencies. +- Appearance of sign-in button no longer depends on requested scopes. + +# 2016-04-21 -- v4.0.0 +- GoogleSignIn pod now takes form of a static framework. Import with + `#import ` in Objective-C. +- Adds module support. You can also use `@import GoogleSignIn;` in Objective-C, + if module is enabled, and `import GoogleSignIn` in Swift without using a + bridge-header. +- For users of the stand-alone zip distribution, multiple frameworks are now + provided and all need to be added to a project. This decomposition allows more + flexibility in case of duplicated dependencies. +- Removes deprecated method `checkGoogleSignInAppInstalled` from `GIDSignIn`. +- Removes `allowsSignInWithBrowser` and `allowsSignInWithWebView` properties + from `GIDSignIn`. +- No longer requires adding bundle ID as a URL scheme supported by the app. + +# 2016-03-04 -- v3.0.0 +- Provides `givenName` and `familyName` properties on `GIDProfileData`. +- Allows setting the `loginHint` property on `GIDSignIn` to prefill the user's + ID or email address in the sign-in flow. +- Removed the `UIViewController(SignIn)` category as well as the `delegate` + property from `GIDSignInButton`. +- Requires that `uiDelegate` has been set properly on `GIDSignIn` and that + SafariServices framework has been linked. +- Removes the dependency on StoreKit. +- Provides bitcode support. +- Requires Xcode 7.0 or above due to bitcode incompatibilities with Xcode 6. + +# 2015-10-26 -- v2.4.0 +- Updates sign-in button with the new Google logo. +- Supports domain restriction for sign-in. +- Allows refreshing ID tokens. + +# 2015-10-09 -- v2.3.2 +- No longer requires Xcode 7. + +# 2015-10-01 -- v2.3.1 +- Fixes a crash in `GIDProfileData`'s `imageURLWithDimension:`. + +# 2015-09-25 -- v2.3.0 +- Requires Xcode 7.0 or above. +- Uses SFSafariViewController for signing in on iOS 9. `uiDelegate` must be + set for this to work. +- Optimizes fetching user profile. +- Supports GTMFetcherAuthorizationProtocol in GIDAuthentication. + +# 2015-07-15 -- v2.2.0 +- Compatible with iOS 9 (beta). Note that this version of the Sign-In SDK does + not include bitcode, so you must set ENABLE_BITCODE to NO in your project if + you use Xcode 7. +- Adds descriptive identifiers for GIDSignInButton's Auto Layout constraints. +- `signInSilently` no longer requires setting `uiDelegate`. + +# 2015-06-17 -- v2.1.0 +- Fixes Auto Layout issues with GIDSignInButton. +- Adds API to refresh access token in GIDAuthentication. +- Better exception description for unassigned clientID in GIDSignIn. +- Other minor bug fixes. + +# 2015-05-28 -- v2.0.1 +- Bug fixes + +# 2015-05-21 -- v2.0.0 +- Supports sign-in via UIWebView rather than app switching to a browser, + configurable with the new `allowsSignInWithWebView` property. +- Now apps which have disabled the app switch to a browser via the + `allowsSignInWithBrowser` and in-app web view via `allowsSignInWithWebView` + properties have the option to display a prompt instructing the user to + download the Google app from the App Store. +- Fixes sign-in button sizing issue when auto-layout is enabled +- `signInSilently` now calls the delegate with error when `hasAuthInKeychain` + is `NO` as documented +- Other minor bug fixes + +# 2015-03-12 -- v1.0.0 +- New sign-in focused SDK with refreshed API +- Dynamically rendered sign-in button with contextual branding +- Basic profile support +- Added allowsSignInWithBrowser property diff --git a/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/GoogleSignIn b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/GoogleSignIn new file mode 100755 index 00000000..12c05067 Binary files /dev/null and b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/GoogleSignIn differ diff --git a/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDAuthentication.h b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDAuthentication.h new file mode 100755 index 00000000..3571c6e8 --- /dev/null +++ b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDAuthentication.h @@ -0,0 +1,65 @@ +/* + * GIDAuthentication.h + * Google Sign-In iOS SDK + * + * Copyright 2014 Google Inc. + * + * Use of this SDK is subject to the Google APIs Terms of Service: + * https://developers.google.com/terms/ + */ + +#import + +@protocol GTMFetcherAuthorizationProtocol; +@class GIDAuthentication; + +/// The callback block that takes a `GIDAuthentication`, or an error if attempt +/// to refresh was unsuccessful. +typedef void (^GIDAuthenticationHandler)(GIDAuthentication *authentication, NSError *error); + +/// The callback block that takes an access token, or an error if attempt to refresh was +/// unsuccessful. +typedef void (^GIDAccessTokenHandler)(NSString *accessToken, NSError *error); + +/// This class represents the OAuth 2.0 entities needed for sign-in. +@interface GIDAuthentication : NSObject + +/// The client ID associated with the authentication. +@property(nonatomic, readonly) NSString *clientID; + +/// The OAuth2 access token to access Google services. +@property(nonatomic, readonly) NSString *accessToken; + +/// The estimated expiration date of the access token. +@property(nonatomic, readonly) NSDate *accessTokenExpirationDate; + +/// The OAuth2 refresh token to exchange for new access tokens. +@property(nonatomic, readonly) NSString *refreshToken; + +/// An OpenID Connect ID token that identifies the user. Send this token to your server to +/// authenticate the user there. For more information on this topic, see +/// https://developers.google.com/identity/sign-in/ios/backend-auth +@property(nonatomic, readonly) NSString *idToken; + +/// The estimated expiration date of the ID token. +@property(nonatomic, readonly) NSDate *idTokenExpirationDate; + +/// Gets a new authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`. +/// +/// @return A new authorizer +- (id)fetcherAuthorizer; + +/// Get a valid access token and a valid ID token, refreshing them first if they have expired or are +/// about to expire. +/// +/// @param handler A callback block that takes a `GIDAuthentication`, or an +/// error if attempt to refresh was unsuccessful. +- (void)getTokensWithHandler:(GIDAuthenticationHandler)handler; + +/// Refreshes the access token and the ID token using the refresh token. +/// +/// @param handler A callback block that takes a `GIDAuthentication`, or an +/// error if attempt to refresh was unsuccessful. +- (void)refreshTokensWithHandler:(GIDAuthenticationHandler)handler; + +@end diff --git a/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDGoogleUser.h b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDGoogleUser.h new file mode 100755 index 00000000..17e6e5ea --- /dev/null +++ b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDGoogleUser.h @@ -0,0 +1,39 @@ +/* + * GIDGoogleUser.h + * Google Sign-In iOS SDK + * + * Copyright 2014 Google Inc. + * + * Use of this SDK is subject to the Google APIs Terms of Service: + * https://developers.google.com/terms/ + */ + +#import + +@class GIDAuthentication; +@class GIDProfileData; + +/// This class represents a user account. +@interface GIDGoogleUser : NSObject + +/// The Google user ID. +@property(nonatomic, readonly) NSString *userID; + +/// Representation of the Basic profile data. It is only available if +/// `GIDSignIn.shouldFetchBasicProfile` is set and either `-[GIDSignIn signIn]` or +/// `-[GIDSignIn restorePreviousSignIn]` has been completed successfully. +@property(nonatomic, readonly) GIDProfileData *profile; + +/// The authentication object for the user. +@property(nonatomic, readonly) GIDAuthentication *authentication; + +/// The API scopes granted to the app in an array of `NSString`. +@property(nonatomic, readonly) NSArray *grantedScopes; + +/// For Google Apps hosted accounts, the domain of the user. +@property(nonatomic, readonly) NSString *hostedDomain; + +/// An OAuth2 authorization code for the home server. +@property(nonatomic, readonly) NSString *serverAuthCode; + +@end diff --git a/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDProfileData.h b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDProfileData.h new file mode 100755 index 00000000..f07d4752 --- /dev/null +++ b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDProfileData.h @@ -0,0 +1,37 @@ +/* + * GIDProfileData.h + * Google Sign-In iOS SDK + * + * Copyright 2014 Google Inc. + * + * Use of this SDK is subject to the Google APIs Terms of Service: + * https://developers.google.com/terms/ + */ + +#import + +/// This class represents the basic profile information of a `GIDGoogleUser`. +@interface GIDProfileData : NSObject + +/// The Google user's email. +@property(nonatomic, readonly) NSString *email; + +/// The Google user's full name. +@property(nonatomic, readonly) NSString *name; + +/// The Google user's given name. +@property(nonatomic, readonly) NSString *givenName; + +/// The Google user's family name. +@property(nonatomic, readonly) NSString *familyName; + +/// Whether or not the user has profile image. +@property(nonatomic, readonly) BOOL hasImage; + +/// Gets the user's profile image URL for the given dimension in pixels for each side of the square. +/// +/// @param dimension The desired height (and width) of the profile image. +/// @return The URL of the user's profile image. +- (NSURL *)imageURLWithDimension:(NSUInteger)dimension; + +@end diff --git a/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDSignIn.h b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDSignIn.h new file mode 100755 index 00000000..12046159 --- /dev/null +++ b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDSignIn.h @@ -0,0 +1,170 @@ +/* + * GIDSignIn.h + * Google Sign-In iOS SDK + * + * Copyright 2012 Google Inc. + * + * Use of this SDK is subject to the Google APIs Terms of Service: + * https://developers.google.com/terms/ + */ + +#import +#import + +@class GIDGoogleUser; +@class GIDSignIn; + +/// The error domain for `NSError`s returned by the Google Identity SDK. +extern NSString *const kGIDSignInErrorDomain; + +/// A list of potential error codes returned from the Google Identity SDK. +typedef NS_ENUM(NSInteger, GIDSignInErrorCode) { + /// Indicates an unknown error has occurred. + kGIDSignInErrorCodeUnknown = -1, + /// Indicates a problem reading or writing to the application keychain. + kGIDSignInErrorCodeKeychain = -2, + /// Indicates there are no valid auth tokens in the keychain. This error code will be returned by + /// `restorePreviousSignIn` if the user has not signed in before or if they have since signed out. + kGIDSignInErrorCodeHasNoAuthInKeychain = -4, + /// Indicates the user canceled the sign in request. + kGIDSignInErrorCodeCanceled = -5, + /// Indicates an Enterprise Mobility Management related error has occurred. + kGIDSignInErrorCodeEMM = -6, +}; + +/// A protocol implemented by the delegate of `GIDSignIn` to receive a refresh token or an error. +@protocol GIDSignInDelegate + +/// The sign-in flow has finished and was successful if `error` is `nil`. +- (void)signIn:(GIDSignIn *)signIn + didSignInForUser:(GIDGoogleUser *)user + withError:(NSError *)error; + +@optional + +/// Finished disconnecting `user` from the app successfully if `error` is `nil`. +- (void)signIn:(GIDSignIn *)signIn + didDisconnectWithUser:(GIDGoogleUser *)user + withError:(NSError *)error; + +@end + +/// This class signs the user in with Google. It also provides single sign-on via a capable Google +/// app if one is installed. +/// +/// For reference, please see "Google Sign-In for iOS" at +/// https://developers.google.com/identity/sign-in/ios +/// +/// Here is sample code to use `GIDSignIn`: +/// 1. Get a reference to the `GIDSignIn` shared instance: +/// ``` +/// GIDSignIn *signIn = [GIDSignIn sharedInstance]; +/// ``` +/// 2. Call `[signIn setDelegate:self]`; +/// 3. Set up delegate method `signIn:didSignInForUser:withError:`. +/// 4. Call `handleURL` on the shared instance from `application:openUrl:...` in your app delegate. +/// 5. Call `signIn` on the shared instance; +@interface GIDSignIn : NSObject + +/// The authentication object for the current user, or `nil` if there is currently no logged in +/// user. +@property(nonatomic, readonly) GIDGoogleUser *currentUser; + +/// The object to be notified when authentication is finished. +@property(nonatomic, weak) id delegate; + +/// The view controller used to present `SFSafariViewContoller` on iOS 9 and 10. +@property(nonatomic, weak) UIViewController *presentingViewController; + +/// The client ID of the app from the Google APIs console. Must set for sign-in to work. +@property(nonatomic, copy) NSString *clientID; + +/// The API scopes requested by the app in an array of `NSString`s. The default value is `@[]`. +/// +/// This property is optional. If you set it, set it before calling `signIn`. +@property(nonatomic, copy) NSArray *scopes; + +/// Whether or not to fetch basic profile data after signing in. The data is saved in the +/// `GIDGoogleUser.profileData` object. +/// +/// Setting the flag will add "email" and "profile" to scopes. +/// Defaults to `YES`. +@property(nonatomic, assign) BOOL shouldFetchBasicProfile; + +/// The language for sign-in, in the form of ISO 639-1 language code optionally followed by a dash +/// and ISO 3166-1 alpha-2 region code, such as `@"it"` or `@"pt-PT"`. Only set if different from +/// system default. +/// +/// This property is optional. If you set it, set it before calling `signIn`. +@property(nonatomic, copy) NSString *language; + +/// The login hint to the authorization server, for example the user's ID, or email address, +/// to be prefilled if possible. +/// +/// This property is optional. If you set it, set it before calling `signIn`. +@property(nonatomic, copy) NSString *loginHint; + +/// The client ID of the home web server. This will be returned as the `audience` property of the +/// OpenID Connect ID token. For more info on the ID token: +/// https://developers.google.com/identity/sign-in/ios/backend-auth +/// +/// This property is optional. If you set it, set it before calling `signIn`. +@property(nonatomic, copy) NSString *serverClientID; + +/// The OpenID2 realm of the home web server. This allows Google to include the user's OpenID +/// Identifier in the OpenID Connect ID token. +/// +/// This property is optional. If you set it, set it before calling `signIn`. +@property(nonatomic, copy) NSString *openIDRealm; + +/// The Google Apps domain to which users must belong to sign in. To verify, check +/// `GIDGoogleUser`'s `hostedDomain` property. +/// +/// This property is optional. If you set it, set it before calling `signIn`. +@property(nonatomic, copy) NSString *hostedDomain; + +/// Returns a shared `GIDSignIn` instance. ++ (GIDSignIn *)sharedInstance; + +/// Unavailable. Use `sharedInstance` to instantiate `GIDSignIn`. ++ (instancetype)new NS_UNAVAILABLE; + +/// Unavailable. Use `sharedInstance` to instantiate `GIDSignIn`. +- (instancetype)init NS_UNAVAILABLE; + +/// This method should be called from your `UIApplicationDelegate`'s `application:openURL:options` +/// and `application:openURL:sourceApplication:annotation` method(s). +/// +/// @param url The URL that was passed to the app. +/// @return `YES` if `GIDSignIn` handled this URL. +- (BOOL)handleURL:(NSURL *)url; + +/// Checks if there is a previously authenticated user saved in keychain. +/// +/// @return `YES` if there is a previously authenticated user saved in keychain. +- (BOOL)hasPreviousSignIn; + +/// Attempts to restore a previously authenticated user without interaction. + +/// The delegate will be +/// called at the end of this process indicating success or failure. The current values of +/// `GIDSignIn`'s configuration properties will not impact the restored user. +- (void)restorePreviousSignIn; + +/// Starts an interactive sign-in flow using `GIDSignIn`'s configuration properties. +/// +/// The delegate +/// will be called at the end of this process. Any saved sign-in state will be replaced by the +/// result of this flow. Note that this method should not be called when the app is starting up, +/// (e.g in `application:didFinishLaunchingWithOptions:`); instead use the `restorePreviousSignIn` +/// method to restore a previous sign-in. +- (void)signIn; + +/// Marks current user as being in the signed out state. +- (void)signOut; + +/// Disconnects the current user from the app and revokes previous authentication. If the operation +/// succeeds, the OAuth 2.0 token is also removed from keychain. +- (void)disconnect; + +@end diff --git a/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDSignInButton.h b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDSignInButton.h new file mode 100755 index 00000000..795f5698 --- /dev/null +++ b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GIDSignInButton.h @@ -0,0 +1,53 @@ +/* + * GIDSignInButton.h + * Google Sign-In iOS SDK + * + * Copyright 2012 Google Inc. + * + * Use of this SDK is subject to the Google APIs Terms of Service: + * https://developers.google.com/terms/ + */ + +#import + +/// The layout styles supported by the `GIDSignInButton`. +/// +/// The minimum size of the button depends on the language used for text. +/// The following dimensions (in points) fit for all languages: +/// - kGIDSignInButtonStyleStandard: 230 x 48 +/// - kGIDSignInButtonStyleWide: 312 x 48 +/// - kGIDSignInButtonStyleIconOnly: 48 x 48 (no text, fixed size) +typedef NS_ENUM(NSInteger, GIDSignInButtonStyle) { + kGIDSignInButtonStyleStandard = 0, + kGIDSignInButtonStyleWide = 1, + kGIDSignInButtonStyleIconOnly = 2 +}; + +/// The color schemes supported by the `GIDSignInButton`. +typedef NS_ENUM(NSInteger, GIDSignInButtonColorScheme) { + kGIDSignInButtonColorSchemeDark = 0, + kGIDSignInButtonColorSchemeLight = 1 +}; + +/// This class provides the "Sign in with Google" button. +/// +/// You can instantiate this class programmatically or from a NIB file. You +/// should set up the `GIDSignIn` shared instance with your client ID and any +/// additional scopes, implement the delegate methods for `GIDSignIn`, and add +/// this button to your view hierarchy. +@interface GIDSignInButton : UIControl + +/// The layout style for the sign-in button. +/// Possible values: +/// - kGIDSignInButtonStyleStandard: 230 x 48 (default) +/// - kGIDSignInButtonStyleWide: 312 x 48 +/// - kGIDSignInButtonStyleIconOnly: 48 x 48 (no text, fixed size) +@property(nonatomic, assign) GIDSignInButtonStyle style; + +/// The color scheme for the sign-in button. +/// Possible values: +/// - kGIDSignInButtonColorSchemeDark +/// - kGIDSignInButtonColorSchemeLight (default) +@property(nonatomic, assign) GIDSignInButtonColorScheme colorScheme; + +@end diff --git a/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GoogleSignIn.h b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GoogleSignIn.h new file mode 100755 index 00000000..8ccf7cd2 --- /dev/null +++ b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Headers/GoogleSignIn.h @@ -0,0 +1,5 @@ +#import "GIDAuthentication.h" +#import "GIDGoogleUser.h" +#import "GIDProfileData.h" +#import "GIDSignIn.h" +#import "GIDSignInButton.h" diff --git a/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Modules/module.modulemap b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Modules/module.modulemap new file mode 100755 index 00000000..421acdc9 --- /dev/null +++ b/Pods/GoogleSignIn/Frameworks/GoogleSignIn.framework/Modules/module.modulemap @@ -0,0 +1,13 @@ +framework module GoogleSignIn { + umbrella header "GoogleSignIn.h" + export * + module * { export * } + link framework "AuthenticationServices" + link framework "CoreGraphics" + link framework "CoreText" + link framework "Foundation" + link framework "LocalAuthentication" + link framework "SafariServices" + link framework "Security" + link framework "UIKit" +} diff --git a/Pods/GoogleSignIn/README.md b/Pods/GoogleSignIn/README.md new file mode 100755 index 00000000..4418b664 --- /dev/null +++ b/Pods/GoogleSignIn/README.md @@ -0,0 +1,18 @@ +# Google Sign-In SDK for iOS + +The Google Sign-In SDK allows users to sign in with their Google account from +third-party apps. + +Please visit [our developer site](https://developers.google.com/identity/sign-in/ios/) +for integration instructions, documentation, support information, and terms of +service. + +## Getting Started + +* Try our example app with: + ``` + $ pod try GoogleSignIn + ``` + and follow the directions [here](https://developers.google.com/identity/sign-in/ios/start). +* Read our [getting started guides](https://developers.google.com/identity/sign-in/ios/start-integrating). +* Take a look at the [API reference](https://developers.google.com/identity/sign-in/ios/api/). diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/Info.plist b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/Info.plist new file mode 100755 index 00000000..290d5343 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIconFile + + CFBundleIdentifier + com.google.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSPrincipalClass + + + diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/Roboto-Bold.ttf b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/Roboto-Bold.ttf new file mode 100755 index 00000000..68822caf Binary files /dev/null and b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/Roboto-Bold.ttf differ diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ar.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ar.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..0f0b1766 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ar.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "تسجيل الدخول"; + +/* Long form sign-in button text */ +"Sign in with Google" = "تسجيل الدخول باستخدام Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "تسجيل الدخول باستخدام Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "احصل على تطبيق Google المجاني وسجل الدخول إلى التطبيقات من خلال حساب Google. لا توجد حاجة لتذكر كلمات المرور."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "إلغاء"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "جلب"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "موافق"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "إلغاء"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "إعدادات"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "يتعذَّر تسجيل الدخول إلى الحساب"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "يطلب منك المشرف تعيين رمز مرور على هذا الجهاز للدخول إلى هذا الحساب. يُرجى تعيين رمز المرور وإعادة المحاولة."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "لا يتوافق هذا الجهاز مع سياسة الأمان التي أعدها مشرفك"; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "هل تريد الربط بتطبيق Device Policy؟"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "يجب الربط مع تطبيق Device Policy قبل تسجيل الدخول لحماية بيانات مؤسستك."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "ربط"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ca.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ca.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..f5cdb308 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ca.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Inicia la sessió"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Inicia la sessió amb Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Inicia la sessió amb Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Obteniu l'aplicació Google gratuïta i inicieu la sessió a les aplicacions amb el vostre compte de Google. D'aquesta manera, ja no haureu de recordar cap més contrasenya."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Cancel·la"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Obtén"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "D’acord"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Cancel·la"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Configuració"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "No es pot iniciar la sessió al compte"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "L'administrador requereix que estableixis una contrasenya en aquest dispositiu per accedir al compte. Estableix una contrasenya i torna-ho a provar."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "El dispositiu no compleix la política de seguretat establerta pel teu administrador."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Vols connectar-te amb l'aplicació Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Per protegir les dades de la teva organització, t'has de connectar amb l'aplicació Device Policy abans d'iniciar la sessió."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Vull connectar-me"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/cs.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/cs.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..43f2c8b6 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/cs.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Přihlásit se"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Přihlásit se účtem Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Přihlašujte se účtem Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Nainstalujte si zdarma aplikaci Google a přihlašujte se do aplikací pomocí účtu Google. Nebudete si už muset pamatovat spoustu hesel."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Zrušit"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Instalovat"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Zrušit"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Nastavení"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Nelze se přihlásit k účtu"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Administrátor vyžaduje, abyste v tomto zařízení nastavili heslo pro přístup k tomuto účtu. Nastavte prosím heslo a zkuste to znovu."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Zařízení nevyhovuje bezpečnostním zásadám nastaveným administrátorem."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Propojit s aplikací Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Aby bylo možné chránit data vaší organizace, před přihlášením je nutné aktivovat propojení s aplikací Device Policy."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Propojit"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/da.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/da.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..22d5f52f --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/da.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Log ind"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Log ind med Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Log ind med Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Hent den gratis Google-app, og log ind på apps med din Google-konto. Du slipper for at huske på adgangskoder."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Annuller"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Hent"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Annuller"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Indstillinger"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Der kunne ikke logges ind på kontoen"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Din administrator kræver, at du angiver en adgangskode på enheden for at få adgang til kontoen. Angiv en adgangskode, og prøv igen."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Enheden overholder ikke den sikkerhedspolitik, der er angivet af din administrator."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Vil du oprette forbindelse til appen Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Du skal oprette forbindelse til appen Device Policy, inden du logger ind, for at beskytte din organisations data."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Opret forbindelse"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/de.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/de.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..2a26c4bd --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/de.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Anmelden"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Über Google anmelden"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Über Google anmelden"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Installieren Sie die kostenlose Google App und melden Sie sich mit Ihrem Google-Konto in Apps an. So müssen Sie sich keine Passwörter mehr merken."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Abbrechen"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Installieren"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Abbrechen"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Einstellungen"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Anmelden im Konto nicht möglich"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Ihr Administrator hat festgelegt, dass auf diesem Gerät ein Sicherheitscode eingerichtet werden muss, um auf dieses Konto zuzugreifen. Bitte legen Sie einen Sicherheitscode fest und versuchen Sie es noch einmal."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Das Gerät ist nicht mit den von Ihrem Administrator festgelegten Sicherheitsrichtlinien konform."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Mit der Device Policy App verknüpfen?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Zum Schutz der Daten Ihrer Organisation müssen Sie Ihr Gerät zuerst mit der Device Policy App verknüpfen, bevor Sie sich anmelden."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Verknüpfen"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/el.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/el.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..284db52e --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/el.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Σύνδεση"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Συνδεθείτε με το Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Συνδεθείτε με το Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Αποκτήστε τη δωρεάν εφαρμογή Google και συνδεθείτε σε εφαρμογές με το Λογαριασμό σας Google. Δεν χρειάζεται να απομνημονεύετε κωδικούς πρόσβασης."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Ακύρωση"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Λήψη"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "ΟΚ"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Άκυρο"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Ρυθμίσεις"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Δεν είναι δυνατή η σύνδεση στον λογαριασμό"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Ο διαχειριστής σας απαιτεί να ορίσετε έναν κωδικό πρόσβασης στη συσκευή, για να έχετε πρόσβαση σε αυτόν τον λογαριασμό. Ορίστε έναν κωδικό πρόσβασης και δοκιμάστε ξανά."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Η συσκευή δεν συμμορφώνεται με την πολιτική ασφαλείας που έχει ορίσει ο διαχειριστής σας."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Σύνδεση με την εφαρμογή Device Policy;"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Προκειμένου να προστατεύσετε τα δεδομένα του οργανισμού σας, θα πρέπει να συνδεθείτε με την εφαρμογή Device Policy προτού συνδεθείτε στον λογαριασμό σας."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Σύνδεση"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/en.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/en.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..460ad64a --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/en.lproj/GoogleSignIn.strings @@ -0,0 +1,32 @@ +/* Sign-in button text */ +"Sign in" = "Sign in"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Sign in with Google"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Cancel"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Settings"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Unable to sign in to account"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Your administrator requires you to set a passcode on this device to access this account. Please set a passcode and try again."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "The device is not compliant with the security policy set by your administrator."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Connect with Device Policy App?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "In order to protect your organization's data, you must connect with the Device Policy app before logging in."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Connect"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/en_GB.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/en_GB.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..402dbe56 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/en_GB.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Sign in"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Sign in with Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Sign in with Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Get the free Google app and sign in to apps with your Google Account. No need to remember passwords."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Cancel"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Get"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Cancel"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Settings"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Unable to sign in to account"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Your administrator requires you to set a passcode on this device to access this account. Please set a passcode and try again."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "The device is not compliant with the security policy set by your administrator."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Connect with Device Policy App?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "In order to protect your organisation's data, you must connect with the Device Policy app before logging in."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Connect"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/es.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/es.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..f13de7b6 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/es.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Iniciar sesión"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Iniciar sesión con Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Iniciar sesión con Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Obtén la aplicación Google gratuita e inicia sesión en aplicaciones con tu cuenta de Google. No tendrás que recordar las contraseñas."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Cancelar"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Obtener"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "Aceptar"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Cancelar"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Configuración"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "No se ha podido iniciar sesión en la cuenta"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "El administrador requiere que configures una contraseña en este dispositivo para acceder a esta cuenta. Inténtalo de nuevo cuando lo hayas hecho."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "El dispositivo no cumple la política de privacidad que ha definido tu administrador."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "¿Has conectado tu dispositivo con la aplicación Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Para proteger los datos de tu organización, debes conectar tu dispositivo con la aplicación Device Policy antes de iniciar sesión."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Conectar"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/es_MX.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/es_MX.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..12a2892f --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/es_MX.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Acceder"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Acceder con Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Acceder con Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Obtén Google app y accede a aplicaciones con tu cuenta de Google. No hace falta recordar contraseñas."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Cancelar"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Obtener"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "Aceptar"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Cancelar"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Configuración"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "No es posible acceder a la cuenta"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Para acceder a esta cuenta, tu administrador requiere que establezcas una contraseña en el dispositivo. Configúrala y vuelve a intentarlo."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "El dispositivo no cumple con la política de seguridad que estableció el administrador."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "¿Deseas conectarte con la app de Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Para proteger los datos de tu organización, debes conectarte con la app de Device Policy antes de acceder."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Conectar"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fi.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fi.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..0134a2cd --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fi.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Kirjaudu sisään"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Kirjaudu Google-tilin tunnuksilla"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Kirjaudu Google-tilin tunnuksilla"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Hanki ilmainen Google-sovellus ja kirjaudu sovelluksiin Google-tililläsi. Sinun ei tarvitse muistaa salasanoja."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Peruuta"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Hae"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Peruuta"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Asetukset"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Kirjautuminen tilille ei onnistu"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Järjestelmänvalvoja edellyttää tunnuskoodin määrittämistä, ennen kuin voit käyttää tiliä tällä laitteella. Määritä tunnuskoodi ja yritä uudelleen."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Laite ei noudata järjestelmänvalvojan määrittämää verkkotunnuskäytäntöä."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Muodostetaanko yhteys Device Policy ‑sovellukseen?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Suojaa organisaatiosi dataa muodostamalla yhteys Device Policy ‑sovellukseen ennen kirjautumista."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Muodosta yhteys"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fr.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fr.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..96b921ee --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fr.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Se connecter"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Se connecter avec Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Se connecter avec Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Installez l'appli Google gratuite et connectez-vous à des applications avec votre compte Google. Plus besoin de vous souvenir de vos mots de passe."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Annuler"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Installer"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Annuler"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Paramètres"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Impossible de se connecter au compte"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Votre administrateur exige que vous définissiez un mot de passe sur cet appareil pour accéder à ce compte. Veuillez définir un mot de passe, puis réessayer."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "L'appareil ne respecte pas les règles de sécurité définies par votre administrateur."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Se connecter à l'application Device Policy ?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Afin de protéger les données de votre organisation, vous devez vous connecter à l'application Device Policy avant de vous connecter à votre compte."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Connexion"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fr_CA.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fr_CA.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..532d63e5 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/fr_CA.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Se connecter"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Se connecter à Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Connectez-vous à Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Téléchargez gratuitement l'application Google et connectez-vous à des applications avec votre compte Google. Plus besoin de mémoriser vos mots de passe."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Annuler"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Télécharger"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Annuler"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Paramètres"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Impossible de se connecter au compte"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Pour que votre administrateur puisse accéder à ce compte, vous devez définir un mot de passe sur cet appareil. Veuillez définir un mot de passe et réessayer."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "L'appareil n'est pas conforme à la politique de sécurité définie par votre administrateur."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Connexion avec l'application Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Pour protéger les données de votre organisation, vous devez vous connecter à l'application Device Policy avant de vous connecter."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Connexion"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google.png b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google.png new file mode 100755 index 00000000..26f15cb3 Binary files /dev/null and b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google.png differ diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google@2x.png b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google@2x.png new file mode 100755 index 00000000..26edd541 Binary files /dev/null and b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google@2x.png differ diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google@3x.png b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google@3x.png new file mode 100755 index 00000000..d978ed26 Binary files /dev/null and b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/google@3x.png differ diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/he.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/he.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..efd53fef --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/he.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "היכנס"; + +/* Long form sign-in button text */ +"Sign in with Google" = "היכנס באמצעות Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "כניסה באמצעות Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "התקן את Google app בחינם והיכנס אל אפליקציות באמצעות חשבון Google. לא תצטרך עוד לזכור סיסמאות."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "בטל"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "התקן"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "אישור"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "ביטול"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "הגדרות"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "לא ניתן להיכנס לחשבון"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "מנהל המערכת דורש ממך להגדיר קוד סיסמה במכשיר זה כדי לגשת לחשבון זה. יש להגדיר קוד סיסמה ולנסות שוב."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "המכשיר אינו פועל בהתאם למדיניות האבטחה שנקבעה על-ידי מנהל המערכת."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "האם להתחבר באמצעות האפליקציית Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "על מנת להגן על נתוני הארגון שלך, יש להתחבר באמצעות אפליקציית Device Policy לפני הכניסה לחשבון."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "התחברות"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hi.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hi.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..3b7cd1ff --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hi.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "साइन इन करें"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Google के साथ साइन इन करें"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Google के साथ साइन इन करें"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "मुफ़्त Google ऐप्लिकेशन पाएं और अपने Google खाते से ऐप्लिकेशन में साइन इन करें. पासवर्ड याद रखने की ज़रूरत नहीं."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "अभी नहीं"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "पाएं"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "ठीक"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "अभी नहीं"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "सेटिंग"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "खाते में साइन इन नहीं किया जा सका"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "आपके एडमिन के लिए ज़रूरी है कि आप यह खाता एक्सेस करने के लिए इस डिवाइस पर एक पासकोड सेट करें. कृपया पासकोड सेट करें और दोबारा कोशिश करें."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "डिवाइस आपके एडमिन के ज़रिए सेट की गई सुरक्षा नीति का अनुपालन नहीं करता है."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "क्या Device Policy ऐप्लिकेशन से कनेक्ट करना चाहते हैं?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "अपने संगठन डेटा की सुरक्षा के लिए, आपको लॉग-इन करने से पहले Device Policy ऐप्लिकेशन से कनेक्ट करना होगा."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "कनेक्ट करें"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hr.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hr.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..32b6cc3d --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hr.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Prijava"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Prijavite se putem Googlea"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Prijavite se putem Googlea"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Preuzmite besplatnu aplikaciju Google i prijavljujte se na aplikacije svojim Google računom. Ne morate pamtiti zaporke."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Odustani"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Nabavi"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "U redu"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Odbaci"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Postavke"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Prijava na račun nije moguća"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Vaš administrator zahtijeva da postavite šifru zaporke na ovom uređaju da biste pristupili računu. Postavite šifru zaporke i pokušajte ponovo."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Uređaj nije usklađen sa sigurnosnim pravilima koja je postavio vaš administrator."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Želite li se povezati s aplikacijom Pravila za uređaje?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Da biste zaštitili podatke svoje organizacije, morate se povezati s aplikacijom Pravila za uređaje prije prijave."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Poveži"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hu.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hu.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..9359cf51 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/hu.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Bejelentkezés"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Bejelentkezés Google-fiókkal"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Bejelentkezés Google-fiókkal"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Telepítse az ingyenes Google alkalmazást, és jelentkezzen be az egyes termékekbe Google-fiókjával. Nem kell különböző jelszavakat megjegyeznie."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Mégse"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Telepítés"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Mégse"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Beállítások"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Nem sikerült bejelentkezni a fiókba"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Adminisztrátora biztonsági kód beállítását kéri ezen az eszközön a fiókhoz való hozzáféréshez. Kérjük, állítson be biztonsági kódot, majd próbálja újra."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Az eszköz nem felel meg a rendszergazda által beállított biztonsági házirendnek."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Csatlakozik a Device Policy alkalmazáshoz?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "A szervezet adatainak védelme érdekében a bejelentkezés előtt csatlakoznia kell a Device Policy alkalmazáshoz."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Csatlakozás"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/id.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/id.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..9b66ca3c --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/id.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Masuk"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Masuk dengan Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Masuk dengan Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Dapatkan Google app gratis dan masuk ke aplikasi dengan Akun Google. Tidak perlu mengingat sandi."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Batal"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Ambil"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "Oke"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Batal"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Setelan"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Tidak dapat login ke akun"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Administrator mengharuskan Anda menyetel kode sandi di perangkat ini untuk mengakses akun ini. Setel kode sandi dan coba lagi."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Perangkat ini tidak sesuai dengan kebijakan keamanan yang disetel oleh administrator."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Sambungkan dengan Aplikasi Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Untuk melindungi data organisasi, Anda harus tersambung dengan aplikasi Device Policy sebelum login."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Sambungkan"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/it.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/it.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..9c3e576a --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/it.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Accedi"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Accedi con Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Accedi con Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Scarica gratis l'app Google app e accedi alle app con il tuo account Google: liberati dai vincoli delle password."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Annulla"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Scarica"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Annulla"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Impostazioni"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Impossibile accedere all'account"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "L'amministratore richiede l'impostazione di un passcode sul dispositivo per accedere a questo account. Imposta un passcode e riprova."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Il dispositivo non è conforme alle norme di sicurezza stabilite dall'amministratore."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Vuoi collegarti all'app Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Per proteggere i dati della tua organizzazione, devi collegarti all'app Device Policy prima di accedere."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Collega"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ja.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ja.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..6dab02ca --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ja.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "ログイン"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Googleでログイン"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Googleでログイン"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "無料のGoogleアプリをインストールして、Googleアカウントでアプリにログインしよう。パスワードを覚えておく必要はありません。"; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "キャンセル"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "インストール"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "キャンセル"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "設定"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "アカウントにログインできません"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "このアカウントにアクセスするには、この端末でパスコードを設定する必要があります。パスコードを設定してから、もう一度お試しください。"; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "この端末は、管理者が設定したセキュリティ ポリシーに準拠していません。"; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Device Policy アプリと接続しますか?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "組織のデータを保護するために、ログインする前に Device Policy アプリと接続する必要があります。"; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "接続"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ko.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ko.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..b596605d --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ko.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "로그인"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Google 계정으로 로그인"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Google 계정으로 로그인"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "무료 Google 앱을 다운로드하여 Google 계정으로 앱에 로그인하세요. 비밀번호를 기억할 필요가 없습니다."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "취소"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "설치"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "확인"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "취소"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "설정"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "계정에 로그인할 수 없음"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "관리자의 설정에 따라 이 계정에 액세스하려면 사용 중인 기기에 비밀번호를 설정해야 합니다. 비밀번호를 설정한 후 다시 시도해 주세요."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "관리자가 설정한 보안 정책을 준수하지 않는 기기입니다."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Device Policy 앱과 연결하시겠습니까?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "조직의 데이터를 보호하려면 로그인하기 전에 Device Policy 앱과 연결해야 합니다."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "연결"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ms.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ms.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..700aaf2c --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ms.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Log masuk"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Log masuk dengan Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Log masuk dengan Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Dapatkan apl Google percuma dan log masuk ke apl menggunakan Akaun Google anda. Tidak perlu mengingati kata laluan."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Batal"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Dapatkan"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Batal"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Tetapan"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Tidak dapat log masuk ke akaun"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Pentadbir menghendaki anda menetapkan kod laluan pada peranti ini untuk mengakses akaun ini. Sila tetapkan kod laluan, kemudian cuba lagi."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Peranti tidak mematuhi dasar keselamatan yang ditetapkan oleh pentadbir anda."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Berhubung dengan Apl Dasar Peranti?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Untuk melindungi data organisasi anda, anda mesti berhubung dengan apl Dasar Peranti sebelum log masuk."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Hubungkan"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/nb.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/nb.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..994b40ff --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/nb.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Logg på"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Logg på med Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Logg på med Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Skaff deg den gratis Google-appen, og logg på apper med Google-kontoen din. Du trenger ikke å huske passord."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Avbryt"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Hent"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Avbryt"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Innstillinger"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Kan ikke logge på kontoen"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Administratoren din krever at du angir en adgangskode på denne enheten for å få tilgang til kontoen. Angi en adgangskode, og prøv på nytt."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Enheten overholder ikke retningslinjene for sikkerhet som ble angitt av administratoren din."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Vil du koble til med Device Policy-appen?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "For å beskytte dataene til organisasjonen din må du koble til med Device Policy-appen før du logger på."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Koble til"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/nl.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/nl.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..fb539e08 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/nl.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Inloggen"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Inloggen met Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Inloggen met Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Installeer de gratis Google-app en log in bij apps met uw Google-account. U hoeft geen wachtwoorden te onthouden."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Annuleren"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Installeren"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Annuleren"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Instellingen"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Kan niet inloggen op account"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Uw beheerder vereist dat u een toegangscode instelt op dit apparaat om toegang te krijgen tot dit account. Stel een toegangscode in en probeer het opnieuw."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Het apparaat voldoet niet aan het beveiligingsbeleid dat is ingesteld door uw beheerder."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Verbinden met Device Policy-app?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Ter bescherming van de gegevens van uw organisatie moet u verbinding maken met de Device Policy-app voordat u inlogt."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Verbinden"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pl.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pl.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..6ed4b576 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pl.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Zaloguj się"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Zaloguj się przez Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Zaloguj się przez Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Pobierz darmową aplikację Google i zaloguj się do aplikacji, używając konta Google. Nie musisz pamiętać haseł."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Anuluj"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Pobierz"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Anuluj"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Ustawienia"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Nie można zalogować się na konto"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Administrator wymaga ustawienia kodu dostępu do konta na tym urządzeniu. Ustaw kod dostępu i spróbuj ponownie."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Urządzenie nie jest zgodne z zasadami bezpieczeństwa ustanowionymi przez Twojego administratora."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Połączyć z aplikacją Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Aby chronić dane organizacji, przed zalogowaniem musisz się połączyć z aplikacją Device Policy."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Połącz"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..3207312c --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Fazer login"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Fazer login com o Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Fazer login com o Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Faça o download do Google app gratuitamente e faça login em aplicativos com sua Conta do Google. Não há necessidade de lembrar senhas."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Cancelar"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Instalar"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Cancelar"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Configurações"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Não foi possível fazer login na conta"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Seu administrador exige que você defina uma senha neste dispositivo para acessar esta conta. Defina uma senha e tente novamente."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "O dispositivo não está em conformidade com a política de segurança definida pelo administrador."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Conectar-se ao app Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Para proteger os dados da sua organização, você precisa se conectar ao app Device Policy antes de fazer login."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Conectar"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt_BR.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt_BR.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..3207312c --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt_BR.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Fazer login"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Fazer login com o Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Fazer login com o Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Faça o download do Google app gratuitamente e faça login em aplicativos com sua Conta do Google. Não há necessidade de lembrar senhas."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Cancelar"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Instalar"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Cancelar"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Configurações"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Não foi possível fazer login na conta"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Seu administrador exige que você defina uma senha neste dispositivo para acessar esta conta. Defina uma senha e tente novamente."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "O dispositivo não está em conformidade com a política de segurança definida pelo administrador."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Conectar-se ao app Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Para proteger os dados da sua organização, você precisa se conectar ao app Device Policy antes de fazer login."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Conectar"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt_PT.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt_PT.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..91d7a258 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/pt_PT.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Iniciar sessão"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Iniciar sessão com o Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Iniciar sessão com o Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Obtenha a aplicação Google gratuita e inicie sessão nas aplicações com a sua Conta Google. Não precisa de memorizar palavras-passe."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Cancelar"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Obter"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Cancelar"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Definições"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Não é possível iniciar sessão na conta"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "O administrador requer a definição de um código secreto neste dispositivo para aceder a esta conta. Defina um código secreto e tente novamente."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "O dispositivo não está em conformidade com a política de segurança definida pelo seu administrador."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Pretende ligar-se à aplicação Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Para proteger os dados da sua entidade, tem de se ligar à aplicação Device Policy antes de iniciar sessão."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Ligar"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ro.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ro.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..34b4239e --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ro.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Conectați-vă"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Conectați-vă cu Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Conectați-vă cu Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Instalați aplicația Google gratuită și conectați-vă la aplicații folosind Contul Google. Nu mai trebuie să rețineți parolele."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Anulați"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Instalați"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Anulați"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Setări"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Nu vă puteți conecta la cont"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Administratorul impune să setați o parolă pe acest dispozitiv ca să accesați contul. Setați o parolă și încercați din nou."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Dispozitivul nu respectă politica de securitate stabilită de administrator."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Vă conectați cu aplicația Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Pentru a vă proteja datele organizației, trebuie să vă conectați cu aplicația Device Policy înainte de a vă conecta."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Conectați"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ru.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ru.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..6d6c98ab --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/ru.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Войти"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Войти в аккаунт Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Надоело вводить пароль?"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Установите бесплатное приложение Google и входите в другие мобильные программы, используя учетные данные своего аккаунта."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Отмена"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Установить"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "ОК"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Отмена"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Настройки"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Не удалось войти в аккаунт"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "В соответствии с требованиями администратора для входа в аккаунт необходимо установить на устройстве код доступа. Сделайте это и повторите попытку."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Устройство не соответствует правилам безопасности, которые установлены администратором."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Подключить приложение Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "В целях защиты корпоративных данных перед входом в аккаунт необходимо подключить приложение Device Policy."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Подключить"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/sk.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/sk.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..eaf2f7f2 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/sk.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Prihlásiť sa"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Prihlásiť sa pomocou účtu Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Prihlásenie pomocou účtu Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Nainštalujte si zdarma aplikáciu Google a prihlasujte sa do aplikácií pomocou účtu Google. Nebudete si už musieť pamätať rôzne heslá."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Zrušiť"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Inštalovať"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Zrušiť"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Nastavenia"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Nedá sa prihlásiť do účtu"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Správca vyžaduje, aby ste v tomto zariadení nastavili vstupný kód na prístup do príslušného účtu. Nastavte vstupný kód a skúste to znova."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Zariadenie nespĺňa pravidlá zabezpečenia nastavené vaším správcom."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Prepojiť s aplikáciou Pravidlá pre zariadenie?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Na to, aby bolo možné chrániť dáta vašej organizácie, je nutné pred prihlásením aktivovať prepojenie s aplikáciou Pravidlá pre zariadenie."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Prepojiť"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/sv.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/sv.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..3192e071 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/sv.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Logga in"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Logga in med Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Logga in med Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Hämta Google-appen utan kostnad och logga in i appar med ditt Google-konto. Du behöver inte komma ihåg en massa lösenord."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Avbryt"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Hämta"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "Ok"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Avbryt"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Inställningar"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Det gick inte att logga in på kontot"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Administratören kräver att du anger ett lösenord på den här enheten för att få åtkomst till kontot. Ange ett lösenord och försök igen."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Säkerhetspolicyn som administratören har angett efterlevs inte på enheten."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Vill du ansluta med appen Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Du måste ansluta med appen Device Policy innan du loggar in för att skydda organisationens data."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Anslut"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/th.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/th.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..353394ca --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/th.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "ลงชื่อเข้าใช้"; + +/* Long form sign-in button text */ +"Sign in with Google" = "ลงชื่อเข้าใช้ด้วย Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "ลงชื่อเข้าใช้ด้วย Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "ติดตั้งแอป Google ฟรีและลงชื่อเข้าใช้แอปต่างๆ ด้วยบัญชี Google คุณไม่ต้องจำรหัสผ่านอีกแล้ว"; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "ยกเลิก"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "ติดตั้ง"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "ตกลง"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "ยกเลิก"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "การตั้งค่า"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "ลงชื่อเข้าใช้บัญชีไม่ได้"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "ผู้ดูแลระบบกำหนดให้คุณตั้งรหัสผ่านในอุปกรณ์นี้เพื่อเข้าถึงบัญชีนี้ โปรดตั้งรหัสผ่าน แล้วลองอีกครั้ง"; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "อุปกรณ์ไม่ตรงตามนโยบายความปลอดภัยที่กำหนดโดยผู้ดูแลระบบของคุณ"; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "เชื่อมต่อแอป Device Policy ไหม"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "เพื่อปกป้องข้อมูลขององค์กร คุณต้องเชื่อมต่อแอป Device Policy ก่อนลงชื่อเข้าสู่ระบบ"; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "เชื่อมต่อ"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/tr.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/tr.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..414b7b9e --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/tr.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Oturum aç"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Google ile oturum aç"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Google ile oturum aç"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Ücretsiz Google uygulamasını edinin ve uygulamalarda Google Hesabınızla oturum açın. Şifrelerinizi hatırlamanız gerekmez."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "İptal"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Yükle"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "Tamam"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "İptal"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Ayarlar"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Hesapta oturum açılamıyor"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Yöneticiniz, bu hesaba erişmek için bu cihazda bir şifre kodu ayarlamanızı gerektiriyor. Lütfen şifre kodu ayarlayın ve tekrar deneyin."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Bu cihaz, yöneticinizin ayarladığı güvenlik politikasıyla uyumlu değil."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Cihaz Politika Uygulamasına bağlanılsın mı?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Kuruluşunuzun verilerini korumak için, giriş yapmadan önce Cihaz Politikası uygulamasına bağlanmalısınız."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Bağlan"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/uk.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/uk.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..faaa0bc6 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/uk.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Увійти"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Увійти в обліковий запис Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Входьте в обліковий запис Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Установіть безкоштовний додаток Google і входьте в обліковий запис Google у додатках. Не потрібно запам’ятовувати паролі."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Скасувати"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Установити"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Скасувати"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Налаштування"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Не вдається ввійти в обліковий запис"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Щоб увійти в обліковий запис, потрібно налаштувати код доступу на пристрої. Зробіть це й повторіть спробу."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Пристрій не відповідає правилу безпеки, яке налаштував адміністратор."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "З’єднатися з додатком Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Щоб захистити дані організації, потрібно з’єднатися з додатком Device Policy, перш ніж увійти."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "З’єднатися"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/vi.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/vi.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..94f858ce --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/vi.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "Đăng nhập"; + +/* Long form sign-in button text */ +"Sign in with Google" = "Đăng nhập bằng Google"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "Đăng nhập bằng Google"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "Tải ứng dụng Google miễn phí và đăng nhập vào các ứng dụng bằng Tài khoản Google của bạn. Không cần phải nhớ mật khẩu."; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "Hủy"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "Tải"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "OK"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "Hủy"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "Cài đặt"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "Không thể đăng nhập vào tài khoản"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "Quản trị viên của bạn yêu cầu bạn phải đặt mật mã trên thiết bị này để truy cập vào tài khoản này. Hãy đặt mật mã và thử lại."; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "Thiết bị này không tuân thủ chính sách bảo mật do quản trị viên của bạn thiết lập."; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "Kết nối với ứng dụng Device Policy?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "Để bảo vệ dữ liệu của tổ chức của mình, bạn phải kết nối với ứng dụng Device Policy trước khi đăng nhập."; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "Kết nối"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/zh_CN.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/zh_CN.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..0c087f61 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/zh_CN.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "登录"; + +/* Long form sign-in button text */ +"Sign in with Google" = "使用 Google 帐号登录"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "使用 Google 帐号登录"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "安装免费的“Google”应用后,您可以使用自己的 Google 帐号登录众多应用(无需记住众多密码)。"; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "取消"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "安装"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "确定"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "取消"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "设置"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "无法登录帐号"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "您的管理员要求您必须先在此设备上设置密码,然后才能访问此帐号。请设置密码并重试。"; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "该设备不符合管理员设置的安全政策。"; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "要关联 Device Policy 应用吗?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "要保护您组织的数据,您必须在登录前关联 Device Policy 应用。"; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "关联"; diff --git a/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/zh_TW.lproj/GoogleSignIn.strings b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/zh_TW.lproj/GoogleSignIn.strings new file mode 100755 index 00000000..748468f1 --- /dev/null +++ b/Pods/GoogleSignIn/Resources/GoogleSignIn.bundle/zh_TW.lproj/GoogleSignIn.strings @@ -0,0 +1,44 @@ +/* Sign-in button text */ +"Sign in" = "登入"; + +/* Long form sign-in button text */ +"Sign in with Google" = "登入 Google 帳戶"; + +/* The title of the promotional prompt to install the Google app. */ +"PromoTitle" = "登入 Google 帳戶"; + +/* The body message of the promotional prompt to install the Google app. */ +"PromoMessage" = "只要安裝免費的 Google app,即可使用 Google 帳戶登入應用程式,而不必費心記住密碼。"; + +/* The cancel button on the promotional prompt to install the Google app. */ +"PromoActionCancel" = "取消"; + +/* The install button on the promotional prompt to install the Google app. */ +"PromoActionInstall" = "安裝"; + +/* The text for the button for user to acknowledge and dismiss a dialog. */ +"OK" = "確定"; + +/* The text for the button for user to dismiss a dialog without taking any action. */ +"Cancel" = "取消"; + +/* The name of the iOS native "Settings" app. */ +"SettingsAppName" = "設定"; + +/* The title for the error dialog for unable to sign in because of EMM policy. */ +"EmmErrorTitle" = "無法登入帳戶"; + +/* The text in the error dialog asking user to set up a passcode for the device due to EMM policy. */ +"EmmPasscodeRequired" = "管理員要求您必須為這個裝置設定通行碼,才能存取這個帳戶。請設定通行碼,然後再試一次。"; + +/* The text in the error dialog informing user that EMM policy prevented sign-in on the device. */ +"EmmGeneralError" = "這部裝置不符合您的管理員所設定的安全性政策規定。"; + +/* The title in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectTitle" = "要連結 Device Policy 應用程式嗎?"; + +/* The text in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectText" = "為了保護貴機構的資料,您必須在登入前連結 Device Policy 應用程式。"; + +/* The action button label in the error dialog informing user that connecting with Device Policy app is required. */ +"EmmConnectLabel" = "連結"; diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock new file mode 100644 index 00000000..537c3f7b --- /dev/null +++ b/Pods/Manifest.lock @@ -0,0 +1,38 @@ +PODS: + - AppAuth (1.4.0): + - AppAuth/Core (= 1.4.0) + - AppAuth/ExternalUserAgent (= 1.4.0) + - AppAuth/Core (1.4.0) + - AppAuth/ExternalUserAgent (1.4.0) + - GoogleSignIn (5.0.2): + - AppAuth (~> 1.2) + - GTMAppAuth (~> 1.0) + - GTMSessionFetcher/Core (~> 1.1) + - GTMAppAuth (1.0.0): + - AppAuth/Core (~> 1.0) + - GTMSessionFetcher (~> 1.1) + - GTMSessionFetcher (1.4.0): + - GTMSessionFetcher/Full (= 1.4.0) + - GTMSessionFetcher/Core (1.4.0) + - GTMSessionFetcher/Full (1.4.0): + - GTMSessionFetcher/Core (= 1.4.0) + +DEPENDENCIES: + - GoogleSignIn + +SPEC REPOS: + trunk: + - AppAuth + - GoogleSignIn + - GTMAppAuth + - GTMSessionFetcher + +SPEC CHECKSUMS: + AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7 + GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213 + GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e + GTMSessionFetcher: 6f5c8abbab8a9bce4bb3f057e317728ec6182b10 + +PODFILE CHECKSUM: 78a8cb73e37727fdec10b33d5e8d32bbbad8444f + +COCOAPODS: 1.9.2 diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 00000000..771c5021 --- /dev/null +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,1982 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + CAD3534FC55B0333104E5117C0A9A324 /* GoogleSignIn */ = { + isa = PBXAggregateTarget; + buildConfigurationList = B0030D626B503D8EB0EA00BB772D6396 /* Build configuration list for PBXAggregateTarget "GoogleSignIn" */; + buildPhases = ( + ); + dependencies = ( + 2857566728641F74273DD7DA9E370771 /* PBXTargetDependency */, + 314A9E36FBEA4C73491669F15EA30E5F /* PBXTargetDependency */, + 74F3D34B7F14020AE0A57D83A7889F98 /* PBXTargetDependency */, + ); + name = GoogleSignIn; + productName = GoogleSignIn; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 013D945A6EB50B6FC9854DE84D18E44A /* OIDAuthorizationService+IOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 26FD7859CD2E03E7FC8158229A0568A8 /* OIDAuthorizationService+IOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 06953BA616880F876D31CECCEDC11105 /* OIDAuthState.m in Sources */ = {isa = PBXBuildFile; fileRef = EEA01564A45AE0B2D7B33D0A173EDE68 /* OIDAuthState.m */; }; + 0725D5B07457983CE2485610920F40F0 /* GTMGatherInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 2886E3CC8B435722D172AFD44E448283 /* GTMGatherInputStream.m */; }; + 075E34225D4CE71C2F2FC63FB9CE0CA3 /* OIDAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 32CDEF47E6D0D52EEE7CB6FC86090D58 /* OIDAuthorizationResponse.m */; }; + 082620B4CAFF07381D466C7F54CDC435 /* OIDAuthStateChangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 09DB544D3B1B801F759CC3577F7BFAE2 /* OIDAuthStateChangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A83C33151A74978B5009466B16AA320 /* OIDExternalUserAgentCatalyst.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DAA0A5F9F8461368D30F0690494CEF1 /* OIDExternalUserAgentCatalyst.m */; }; + 0B764CD79758A19F877947F521050A30 /* OIDTokenUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F30E37336A67E34D69CE3F6697404B8 /* OIDTokenUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0E23750FE124E07AFF21DB9AC9542BCE /* AppAuth-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 716060E95444EF216AACA0CB19A8327F /* AppAuth-dummy.m */; }; + 152481DAFF8FA47F61BB412784DF31E9 /* OIDAuthState+IOS.m in Sources */ = {isa = PBXBuildFile; fileRef = E58D2E563A823896B05C184A88BEC5F3 /* OIDAuthState+IOS.m */; }; + 16F96D0B2EE7EBED059FF327B8E90E5E /* OIDClientMetadataParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = ABE775C88B3DFE4CE3E1358EFB6FC3CA /* OIDClientMetadataParameters.m */; }; + 17587A84F44BB3F9BBE44E3F0BF91929 /* OIDScopeUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 1125054B815CBABCD555A9C85116A8C5 /* OIDScopeUtilities.m */; }; + 19A26781E567CE6B5034148C70558904 /* GTMSessionFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 491A043663A145B4E13044E1C11DF1B8 /* GTMSessionFetcher.m */; }; + 19B49E9D25674FD49D6819A68C8B48D8 /* GTMSessionFetcherLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = DAA391547896D3DFD85B0C72E39C34AD /* GTMSessionFetcherLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1C61400E88FC471BECD871E263FFA844 /* GTMSessionFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = E9C487C6396F4F34AA486FA681B36E7F /* GTMSessionFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1C885217C92D9FFF2AC1765CBB300E4C /* Pods-mentorship ios-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = BDE780ECB5DE32F67CA9FC18B0E78C80 /* Pods-mentorship ios-dummy.m */; }; + 2095003DC22D3D86A334AA1A1561900D /* OIDErrorUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 60899ED9DED004E7B0D816E6BA0FBE17 /* OIDErrorUtilities.m */; }; + 218BA22AD876AB6E19146F5A9ABC8F5E /* OIDURLQueryComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 58675B60CB9308C7F6CF1E590479D63A /* OIDURLQueryComponent.m */; }; + 22BA02A9AF9DDB1A405D82C84953B295 /* OIDResponseTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = ACCBA4B91DFEFC2176100A70B856BC6A /* OIDResponseTypes.m */; }; + 243C0608B35874A4CAB88E3951BAD348 /* GTMReadMonitorInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 29E999D050BC6AC2E2B4658E5450C009 /* GTMReadMonitorInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2808F21DE383D3C9BD0DA03A7B95F6AA /* OIDExternalUserAgentIOSCustomBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = 17ED11E753F5ED39DCB3A46E8455EA0E /* OIDExternalUserAgentIOSCustomBrowser.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2A793266E8EBA0902699FB04A0BBFD79 /* Pods-mentorship ios-mentorship iosUITests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 26978B4C518564639BE8B2386D94265E /* Pods-mentorship ios-mentorship iosUITests-dummy.m */; }; + 2B268B1D89DDEE445606464B1105E0F3 /* OIDIDToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B30FDB0F55755AE165583A4942695F /* OIDIDToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2B9DE014CBDC27683B204852475EB8A6 /* OIDExternalUserAgentIOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 6FB2DA57F1766D527F365AFDF52A72D6 /* OIDExternalUserAgentIOS.m */; }; + 2C034AC52FF9090141709228E7A67121 /* OIDServiceDiscovery.m in Sources */ = {isa = PBXBuildFile; fileRef = EFE65E9AF53F83E24423FA1FCEC50885 /* OIDServiceDiscovery.m */; }; + 2D56A3ED5FE2959E984D12CC08761F44 /* GTMSessionFetcher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F6ECBE22F5655207E067F9EEAD60238C /* GTMSessionFetcher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D8D67C396A0A1A33320AB9924E9DB42 /* OIDError.m in Sources */ = {isa = PBXBuildFile; fileRef = B5C23FDD115D6C6944C9C16DF0A248E4 /* OIDError.m */; }; + 321995AB5C5DD22B2DC924BC8F2AF053 /* OIDTokenResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 27B32EA1466E95564B5FAD5E821AFFCB /* OIDTokenResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 355CF2719234ADB11A1993E2120CC373 /* OIDTokenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 18492EC0DB790FFE76C1FB289EF489B5 /* OIDTokenRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3989F5BDEC4A21202F727FB35ECB9E4D /* OIDTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 493064774FA103423D33D9F2E8489FF3 /* OIDTokenResponse.m */; }; + 39D4516A2DB8E91D2A1C0CDB79BDCBCE /* OIDURLQueryComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C28FCC1CEF4208E89FACA0CB2FDCB48 /* OIDURLQueryComponent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 39DDFC3157A616235C548673C91887B0 /* GTMSessionUploadFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = A7AA2B0D2F921EFAE386870CD4175555 /* GTMSessionUploadFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3BFE8CD59118C4908BDCF67DA762C118 /* OIDEndSessionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = EEF2E161EA57E8FD07C267755E8004A3 /* OIDEndSessionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3C8B38097E676E0CAD4C9EDCAD691C9D /* GTMAppAuth-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A5671479D49B01EA29DC81E86747A24 /* GTMAppAuth-dummy.m */; }; + 3F609DF53628B2F834F882C40F39C99C /* OIDTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B3DCE7187E04CC451FE91221AF6E4B56 /* OIDTokenRequest.m */; }; + 408352680097A53851035145970371B1 /* OIDAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = EEE3EABEA79A11C08EB88A403EFA169E /* OIDAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 40FC84D2AFB6832635DD9A929E516BAE /* OIDGrantTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 25D1D33BE58E2175C32DC08C25152B91 /* OIDGrantTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4480D20BAE2A84682B5DE51F51A5DD4D /* OIDDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 2AFA720E1B4DE82D628D142989729554 /* OIDDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4609E25A2C08EDA3E3729934BC90DCDE /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B74456FC79B31A118299C8CFA29E02F /* SafariServices.framework */; }; + 4870D0FB963B09D3D9FAA4E3DC9263FB /* OIDClientMetadataParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 221BBB3F1D598981E3CDAF4EE8C85BB7 /* OIDClientMetadataParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 487B398FF6DE0468B5E8E4FEBB432327 /* OIDExternalUserAgentCatalyst.h in Headers */ = {isa = PBXBuildFile; fileRef = A0E687C93BF9BC8A38F9F3D5C019E6D0 /* OIDExternalUserAgentCatalyst.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4E03547F2FCFE772ACCDA5A0F2861A21 /* GTMAppAuth-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 301BBC564FA7E1A935A554E14F038BD2 /* GTMAppAuth-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 527B5C1B1C9E916C516A73E93CA8BB62 /* GTMSessionFetcher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CACC84D9888E5F02DFB66C1EFC6B53C2 /* GTMSessionFetcher.framework */; }; + 5C43E64B8DFCE99DD92F62D538C0DFF0 /* OIDFieldMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 861F1981C779535A562232F6DECA4B4E /* OIDFieldMapping.m */; }; + 5D4BB578718CC33E5E14170F3E04B696 /* GTMAppAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = F8DB658EC192A6B16FF51B90CA4F0AE9 /* GTMAppAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5D8C1D8B1B94C05E1209CFADC4CBA523 /* Pods-mentorship iosTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BD49343D0716132373053EB4843C9F3 /* Pods-mentorship iosTests-dummy.m */; }; + 617968B11188B95B6117BE9A51BCC2F7 /* OIDErrorUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C6E88A1D88407285CECA9C9E47BFB6C /* OIDErrorUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 61D9C8902B2B3C97CD30023D322B8BAB /* GTMSessionFetcherService.h in Headers */ = {isa = PBXBuildFile; fileRef = D92C3A75664A17434FB87EAA1C9F736F /* GTMSessionFetcherService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 670E2A31490D13725DFB5CBBF08B12EF /* OIDAuthState+IOS.h in Headers */ = {isa = PBXBuildFile; fileRef = EB9B60E189FAC21B2C9104C7DAB0D65A /* OIDAuthState+IOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 677020069CB1BF19DC94430FFAA4DE64 /* GTMMIMEDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 41C243F924B1681C225C02C7D6137956 /* GTMMIMEDocument.m */; }; + 6B2C401E08F2AC0A87453061CA3E8E2C /* OIDAuthState.h in Headers */ = {isa = PBXBuildFile; fileRef = A1A2046586D911B298FE96991C14207A /* OIDAuthState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6F5E49B223BE8067ACA22E443E6125A7 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C8E8CAA1727B3C595EF995FFA650CD6 /* Foundation.framework */; }; + 705E22C52493D0AEDE135873802B54E0 /* OIDScopes.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C04B2D71B76EC26B743C01F9E9BE912 /* OIDScopes.m */; }; + 71BCEC30B9A618753E543ED8C606B9F8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C8E8CAA1727B3C595EF995FFA650CD6 /* Foundation.framework */; }; + 75E674393E820798B5C4A708746E4E49 /* GTMKeychain_iOS.m in Sources */ = {isa = PBXBuildFile; fileRef = BBF941AEBADD9F6AD4538FE8788888F6 /* GTMKeychain_iOS.m */; }; + 77F8771176D9BE429820216FFD687D1B /* AppAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7AA99808D37E94902B0E4448AB29ADE /* AppAuth.framework */; }; + 7887F7AA5B1214BF90AA09CE6CC4AECC /* GTMAppAuthFetcherAuthorization+Keychain.h in Headers */ = {isa = PBXBuildFile; fileRef = 476A4E97C23078E3B85FEF92AF4D6F62 /* GTMAppAuthFetcherAuthorization+Keychain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 79117048A3C4F245532CAE57EE37901A /* OIDExternalUserAgentIOSCustomBrowser.m in Sources */ = {isa = PBXBuildFile; fileRef = A63891EFBD9E2B177549512671020B36 /* OIDExternalUserAgentIOSCustomBrowser.m */; }; + 7ACD06125D4ECC1EC05EAF68B2DC0C99 /* OIDScopeUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B96527396460EFCD85BCB4CFEA21BB8 /* OIDScopeUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7C15A5740A2F9022423ED5583A2D578B /* GTMSessionFetcher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = B1DCCC57B7C242E3D54FFAD8890F1CDF /* GTMSessionFetcher-dummy.m */; }; + 7F7C8089080C1BB6D4AD94CE2AAB1749 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C8E8CAA1727B3C595EF995FFA650CD6 /* Foundation.framework */; }; + 7F99EA6887D99503660FCDF1DD7A3E04 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72CDB17148C70EAFA425064D46E0AB43 /* Security.framework */; }; + 8390E44AF1A51804ECC9B7DAEB30F30F /* GTMReadMonitorInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CE4F8EF50120F68B743B907FC614C98 /* GTMReadMonitorInputStream.m */; }; + 83BAF0108579702991C01C961439111A /* OIDExternalUserAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F32C739DF04BB1DCE9756B9874F9B55 /* OIDExternalUserAgent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8417CFCEA12E96E62CFA4A646672BCCB /* OIDServiceDiscovery.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6C8BFBB00A11E520F0E20A4B44E172 /* OIDServiceDiscovery.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 85AC70140CF3718AA932C21689BF865A /* OIDExternalUserAgentSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 7FDB4F9D06C19DAFFC1DF358C8CF570E /* OIDExternalUserAgentSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 86C6E9D09DD9A2F765ED9CF5F981560C /* OIDURLSessionProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = DC804BAE68E3036FAB3CA483E3B5D4D5 /* OIDURLSessionProvider.m */; }; + 87A2AA12423B55AD7791A99D79534D46 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C8E8CAA1727B3C595EF995FFA650CD6 /* Foundation.framework */; }; + 8953999C7727F2F912F6A737FD0FA0AC /* GTMGatherInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = BA1D24E57164F99EF8C7E3CA1804BD42 /* GTMGatherInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8955CA20EEA55F5D75CDE87F0CADCF83 /* OIDScopes.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DE351FA8C920C81A50ADA6FF5326C65 /* OIDScopes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8ACA9C8B657C3C149214E3649860AF76 /* OIDResponseTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 42F35D3AD5A478242B95522CA059B5D9 /* OIDResponseTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8DD2342949A98F8B328CA82C88F1742D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C8E8CAA1727B3C595EF995FFA650CD6 /* Foundation.framework */; }; + 91907BA379327CACDF19EA39EAD05338 /* OIDEndSessionRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = EEA474C0422A725B8ACF1448991B3BB4 /* OIDEndSessionRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 935771162DF644301DCB001153BE1A4A /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72CDB17148C70EAFA425064D46E0AB43 /* Security.framework */; }; + 95D660DE029B3057EF7922C44E75B6FF /* OIDTokenUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 947742DE685D3112A4947B5D79CC0C57 /* OIDTokenUtilities.m */; }; + 97185747EA1A3BB7552CB5A802B56757 /* GTMAppAuthFetcherAuthorization+Keychain.m in Sources */ = {isa = PBXBuildFile; fileRef = F678ABCE32389498766DDA3647223327 /* GTMAppAuthFetcherAuthorization+Keychain.m */; }; + A14710681730656A8EE4F2793773C18B /* OIDAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = A475F803F0CAA576ABB9F5910018FE04 /* OIDAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A1AF5BF63FEEA98CB2D165039170E6F7 /* OIDAuthorizationService+IOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D48485D1A687CC167B4E447E691B6EF /* OIDAuthorizationService+IOS.m */; }; + A1BB5BA455F04F024EE7204993AE5188 /* Pods-mentorship ios-mentorship iosUITests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5BB1F49651B7E6C2A30CD99A9E0B4CF0 /* Pods-mentorship ios-mentorship iosUITests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A3C394C6C598DFAC45199851B2FF5F97 /* GTMMIMEDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = EE8CD03E234491AC3FFEA89415C2EC3C /* GTMMIMEDocument.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A75213FD8D0028674022934581BEC725 /* GTMSessionUploadFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 7604721BAE0C9863C9871789F8BF5C35 /* GTMSessionUploadFetcher.m */; }; + A837179BAD5CC1FFC6BFAC2C5C67E334 /* OIDServiceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBFB2DBA93531F1A771C1333958547D /* OIDServiceConfiguration.m */; }; + AA3BE332B851918B23E03F283868116C /* OIDError.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D706C0B2250B3275152E7E01EFD89A4 /* OIDError.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AC8E8C869F03F07CB4E0018D956F8386 /* GTMOAuth2KeychainCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 2675390570F0CB22B1A5323F499195D9 /* GTMOAuth2KeychainCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AE07D07F98D805B39CAAB4E89F1E87B8 /* OIDServiceConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = B0E52B096DDF89977735505C25E2307D /* OIDServiceConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B00AF14E70C26C9D19EDB3AB4F04123E /* OIDRegistrationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 96CDC5766FB3A0359F50EA531C9400CC /* OIDRegistrationRequest.m */; }; + B35F09AAE81C4CCCB215BF097DD570D2 /* GTMAppAuthFetcherAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CFB68E7A5CA39188D89F85913215076 /* GTMAppAuthFetcherAuthorization.m */; }; + B714634348FC636AD9B6AAB61EEB7778 /* GTMSessionFetcherService.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B35DF42BAB316E651B49CAD522D30D2 /* GTMSessionFetcherService.m */; }; + BBC602EC86EB71FC4ACD5728C9058E23 /* OIDRegistrationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = BF78AC6BAB39C75DEAC27C39FD0C90E6 /* OIDRegistrationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BF588BB40FADB1F474BB6D2BB22CAAE1 /* GTMAppAuthFetcherAuthorization.h in Headers */ = {isa = PBXBuildFile; fileRef = E49C7D735C96D80767D1DE34A6447961 /* GTMAppAuthFetcherAuthorization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C23F697B76C8D3242F232D958E0C00E3 /* OIDAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 78B0DD0227B7E59711E1B5ED8247519F /* OIDAuthorizationService.m */; }; + C2AABCD1F39FCA174A0C33D19E160BBF /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36B1B635135A16FECC5DEC33BB89C9FE /* SystemConfiguration.framework */; }; + C359863B7FF01F6B19CDA89D30126EE6 /* OIDAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F1D3C4CB0ACD5E0120EC1686BB130F1 /* OIDAuthorizationRequest.m */; }; + C760F013C47E323D4C9D3E1FF5E34338 /* OIDAuthStateErrorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7FA8AAFA32ACE623962A8E103480262D /* OIDAuthStateErrorDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CE9462D01A9FBD478D4A0742AA127534 /* Pods-mentorship iosTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 58F025C65EC807FFAC14F520B0DA02CF /* Pods-mentorship iosTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D4E43B95AA2B76BDEC6B97B152F00463 /* OIDURLSessionProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 95B6991B778BE3F367235DBDC2021762 /* OIDURLSessionProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D87549BF6D8F527CF351E13326FCEF21 /* OIDExternalUserAgentIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 305D7A0C5FB8E82571085947BC82C642 /* OIDExternalUserAgentIOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D901F6867BD4353BECC4F396BCD31533 /* GTMOAuth2KeychainCompatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4082F9791C7B3B032996A82B6617632F /* GTMOAuth2KeychainCompatibility.m */; }; + D90A3C067FE031487AD6F53979803AAA /* OIDEndSessionRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6C903BCB7155D03E0DE50753C984C025 /* OIDEndSessionRequest.m */; }; + DC4164A4DCDACFD4960D61FA1DA61A01 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C8E8CAA1727B3C595EF995FFA650CD6 /* Foundation.framework */; }; + DE23788919FF5136AC4B77F9B29FB4C1 /* OIDFieldMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 3003261E01B3BCA95DBA61082A8D6B15 /* OIDFieldMapping.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E3B32C12184BD887E6687F73097489FA /* OIDRegistrationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F56E8DA7268229361868CD6D5EFBF1 /* OIDRegistrationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E4420DBF08343EA2886691A140BF7C3E /* GTMKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = 11AE9BE473FE105DDB322875E4268930 /* GTMKeychain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EA278C7D9EB69B68733EF68A0C150345 /* OIDAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 60A130AEE49119150B8AD52A61A362CA /* OIDAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE191018EF34395C61D30D78FED87E45 /* AppAuthCore.h in Headers */ = {isa = PBXBuildFile; fileRef = F1B784727061BFA4B97C8D316FDADAA9 /* AppAuthCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EE329DF01337A5B2040D3D7532BEB766 /* OIDRegistrationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2980A0A5CA912C4B704DFFDCCB19CFC1 /* OIDRegistrationResponse.m */; }; + EF92363675B8414D14801D29A288FA31 /* SafariServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B74456FC79B31A118299C8CFA29E02F /* SafariServices.framework */; }; + F246177F1215CDBE9AB3B1BB58624C04 /* OIDGrantTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 268A2998A4DDBB2C0AC87F587AA2DEC5 /* OIDGrantTypes.m */; }; + F3D2632F5565AFA96A15FBA9EAFFF410 /* AppAuth-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = DB41389C8E5C0DB7BEE25B19223EE498 /* AppAuth-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F47BF5598DBEDEB0A44679023AFC7E66 /* OIDIDToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EDE178F2A7F6AF86943F64AE4F32B6A /* OIDIDToken.m */; }; + F598CB205564B44BC8BC6B774DEB1B62 /* OIDExternalUserAgentRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 022E8B0795B3B3AAA93ABC1FF0A540A5 /* OIDExternalUserAgentRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F63C85CE50933EE6BDB86F9C87253F1C /* OIDEndSessionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 144A8518E212ECDBCB6C20D9C15B5A57 /* OIDEndSessionResponse.m */; }; + F677A4A964B9095056DA905477CE4F3C /* AppAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DE8EC5AEF87FED97A80DA8C7C4B8602 /* AppAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F6C1C1A047487F90686267300F19F1B9 /* GTMSessionFetcherLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F5344C9A8985BC64A8826434AE14D90 /* GTMSessionFetcherLogging.m */; }; + FF06333F8E0F64CF45567B6419768574 /* Pods-mentorship ios-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D91E1E6D5C5C883EC5FF59B05091CB4 /* Pods-mentorship ios-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 01AC7A177511421386583884F07403DD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CAD3534FC55B0333104E5117C0A9A324; + remoteInfo = GoogleSignIn; + }; + 0EDCF38BA44E7804550420B1923AE7C0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5C642AA10FB29936669CC269F42079C6; + remoteInfo = AppAuth; + }; + 255C113C5B8CEBDE5DFD4788BBC6FC4B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D676E21115185671D7258A56944ABE98; + remoteInfo = GTMSessionFetcher; + }; + 26DE710C83D26F0E3253DA99DD52D8F2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8627999EF1D5E93E13DAFF580DA8CDCF; + remoteInfo = GTMAppAuth; + }; + 2F6F337FCAEA9A42D506A040A3DF35B8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5C642AA10FB29936669CC269F42079C6; + remoteInfo = AppAuth; + }; + 380F79DB94E2732DF6F327B9142D4189 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E669114D21D744114419853D6A306777; + remoteInfo = "Pods-mentorship ios"; + }; + 69653A44A453E231003B2B78EACD2801 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8627999EF1D5E93E13DAFF580DA8CDCF; + remoteInfo = GTMAppAuth; + }; + 786BDA01217A80D3A27F97A9E29C36AB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D676E21115185671D7258A56944ABE98; + remoteInfo = GTMSessionFetcher; + }; + 96C4B52CEBD25FE7612CDFAC4EBB56D6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5C642AA10FB29936669CC269F42079C6; + remoteInfo = AppAuth; + }; + 9C295E8BC77DF498D6BDAD99C4F660FF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CAD3534FC55B0333104E5117C0A9A324; + remoteInfo = GoogleSignIn; + }; + A720C631E1946701F927ADC25CF2A59E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D676E21115185671D7258A56944ABE98; + remoteInfo = GTMSessionFetcher; + }; + CFBE3CB2F286EF0D95A2ACDEBE0C1014 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8627999EF1D5E93E13DAFF580DA8CDCF; + remoteInfo = GTMAppAuth; + }; + D3B98C3FD3F5938B890DF7680BBB7E42 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D676E21115185671D7258A56944ABE98; + remoteInfo = GTMSessionFetcher; + }; + E8554359B2E0525707CC4735C3DFE1CE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5C642AA10FB29936669CC269F42079C6; + remoteInfo = AppAuth; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 022E8B0795B3B3AAA93ABC1FF0A540A5 /* OIDExternalUserAgentRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDExternalUserAgentRequest.h; path = Source/AppAuthCore/OIDExternalUserAgentRequest.h; sourceTree = ""; }; + 05F56E8DA7268229361868CD6D5EFBF1 /* OIDRegistrationResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDRegistrationResponse.h; path = Source/AppAuthCore/OIDRegistrationResponse.h; sourceTree = ""; }; + 07CA8B3AD67B29339CE5BF50DD1F4181 /* Pods-mentorship iosTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-mentorship iosTests-acknowledgements.plist"; sourceTree = ""; }; + 09DB544D3B1B801F759CC3577F7BFAE2 /* OIDAuthStateChangeDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthStateChangeDelegate.h; path = Source/AppAuthCore/OIDAuthStateChangeDelegate.h; sourceTree = ""; }; + 0C0AAACBC37788AED131E7B8D0C77CC4 /* Pods-mentorship ios-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-mentorship ios-resources.sh"; sourceTree = ""; }; + 0C6E88A1D88407285CECA9C9E47BFB6C /* OIDErrorUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDErrorUtilities.h; path = Source/AppAuthCore/OIDErrorUtilities.h; sourceTree = ""; }; + 0CFB68E7A5CA39188D89F85913215076 /* GTMAppAuthFetcherAuthorization.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMAppAuthFetcherAuthorization.m; path = Source/GTMAppAuthFetcherAuthorization.m; sourceTree = ""; }; + 0D48485D1A687CC167B4E447E691B6EF /* OIDAuthorizationService+IOS.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "OIDAuthorizationService+IOS.m"; path = "Source/AppAuth/iOS/OIDAuthorizationService+IOS.m"; sourceTree = ""; }; + 0EF43F7F5C2500641CE581C5997CE1BD /* Pods-mentorship ios-mentorship iosUITests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-mentorship ios-mentorship iosUITests-resources.sh"; sourceTree = ""; }; + 104D3E9636F5950B3E5E52D45BE250BE /* Pods-mentorship ios-mentorship iosUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-mentorship ios-mentorship iosUITests.debug.xcconfig"; sourceTree = ""; }; + 1125054B815CBABCD555A9C85116A8C5 /* OIDScopeUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDScopeUtilities.m; path = Source/AppAuthCore/OIDScopeUtilities.m; sourceTree = ""; }; + 11AE9BE473FE105DDB322875E4268930 /* GTMKeychain.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMKeychain.h; path = Source/GTMKeychain.h; sourceTree = ""; }; + 144A8518E212ECDBCB6C20D9C15B5A57 /* OIDEndSessionResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDEndSessionResponse.m; path = Source/AppAuthCore/OIDEndSessionResponse.m; sourceTree = ""; }; + 17ED11E753F5ED39DCB3A46E8455EA0E /* OIDExternalUserAgentIOSCustomBrowser.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDExternalUserAgentIOSCustomBrowser.h; path = Source/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h; sourceTree = ""; }; + 18492EC0DB790FFE76C1FB289EF489B5 /* OIDTokenRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDTokenRequest.h; path = Source/AppAuthCore/OIDTokenRequest.h; sourceTree = ""; }; + 221BBB3F1D598981E3CDAF4EE8C85BB7 /* OIDClientMetadataParameters.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDClientMetadataParameters.h; path = Source/AppAuthCore/OIDClientMetadataParameters.h; sourceTree = ""; }; + 22CAD830484EBA2C090D8883F1AD9B11 /* Pods-mentorship iosTests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-mentorship iosTests-Info.plist"; sourceTree = ""; }; + 23E453339F50955000DE65F30282EF6F /* GoogleSignIn.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleSignIn.debug.xcconfig; sourceTree = ""; }; + 25A1A434C35BDFA6CA4D6AFCE828FE18 /* Pods-mentorship ios-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-mentorship ios-acknowledgements.plist"; sourceTree = ""; }; + 25D1D33BE58E2175C32DC08C25152B91 /* OIDGrantTypes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDGrantTypes.h; path = Source/AppAuthCore/OIDGrantTypes.h; sourceTree = ""; }; + 26108ECE6955E499A7A690C9E6FC537D /* GoogleSignIn.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GoogleSignIn.release.xcconfig; sourceTree = ""; }; + 2675390570F0CB22B1A5323F499195D9 /* GTMOAuth2KeychainCompatibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMOAuth2KeychainCompatibility.h; path = Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.h; sourceTree = ""; }; + 268A2998A4DDBB2C0AC87F587AA2DEC5 /* OIDGrantTypes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDGrantTypes.m; path = Source/AppAuthCore/OIDGrantTypes.m; sourceTree = ""; }; + 26978B4C518564639BE8B2386D94265E /* Pods-mentorship ios-mentorship iosUITests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-mentorship ios-mentorship iosUITests-dummy.m"; sourceTree = ""; }; + 26FD7859CD2E03E7FC8158229A0568A8 /* OIDAuthorizationService+IOS.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "OIDAuthorizationService+IOS.h"; path = "Source/AppAuth/iOS/OIDAuthorizationService+IOS.h"; sourceTree = ""; }; + 27B32EA1466E95564B5FAD5E821AFFCB /* OIDTokenResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDTokenResponse.h; path = Source/AppAuthCore/OIDTokenResponse.h; sourceTree = ""; }; + 2822649A60BD61AEA45CA5F8F59D7E7D /* Pods-mentorship ios-mentorship iosUITests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-mentorship ios-mentorship iosUITests.modulemap"; sourceTree = ""; }; + 2886E3CC8B435722D172AFD44E448283 /* GTMGatherInputStream.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMGatherInputStream.m; path = Source/GTMGatherInputStream.m; sourceTree = ""; }; + 2980A0A5CA912C4B704DFFDCCB19CFC1 /* OIDRegistrationResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDRegistrationResponse.m; path = Source/AppAuthCore/OIDRegistrationResponse.m; sourceTree = ""; }; + 29E999D050BC6AC2E2B4658E5450C009 /* GTMReadMonitorInputStream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMReadMonitorInputStream.h; path = Source/GTMReadMonitorInputStream.h; sourceTree = ""; }; + 2AFA720E1B4DE82D628D142989729554 /* OIDDefines.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDDefines.h; path = Source/AppAuthCore/OIDDefines.h; sourceTree = ""; }; + 2BD49343D0716132373053EB4843C9F3 /* Pods-mentorship iosTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-mentorship iosTests-dummy.m"; sourceTree = ""; }; + 2E0A983AC48FC2B1ED279AE8F7E10446 /* Pods-mentorship ios-mentorship iosUITests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-mentorship ios-mentorship iosUITests-acknowledgements.markdown"; sourceTree = ""; }; + 2EDE178F2A7F6AF86943F64AE4F32B6A /* OIDIDToken.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDIDToken.m; path = Source/AppAuthCore/OIDIDToken.m; sourceTree = ""; }; + 3003261E01B3BCA95DBA61082A8D6B15 /* OIDFieldMapping.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDFieldMapping.h; path = Source/AppAuthCore/OIDFieldMapping.h; sourceTree = ""; }; + 301BBC564FA7E1A935A554E14F038BD2 /* GTMAppAuth-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMAppAuth-umbrella.h"; sourceTree = ""; }; + 305D7A0C5FB8E82571085947BC82C642 /* OIDExternalUserAgentIOS.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDExternalUserAgentIOS.h; path = Source/AppAuth/iOS/OIDExternalUserAgentIOS.h; sourceTree = ""; }; + 30A1D65779BE9FA691AEBD03B5455BBB /* Pods-mentorship ios.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-mentorship ios.release.xcconfig"; sourceTree = ""; }; + 32CDEF47E6D0D52EEE7CB6FC86090D58 /* OIDAuthorizationResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationResponse.m; path = Source/AppAuthCore/OIDAuthorizationResponse.m; sourceTree = ""; }; + 3328F331E40151217475F5186FA7E7A7 /* GTMAppAuth.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GTMAppAuth.release.xcconfig; sourceTree = ""; }; + 36B1B635135A16FECC5DEC33BB89C9FE /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + 38E47E42BE9D59EA60D119AF99378190 /* Pods-mentorship iosTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-mentorship iosTests.release.xcconfig"; sourceTree = ""; }; + 394F0AB33B23B23E9F0AB42DD0AF2641 /* GTMAppAuth-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GTMAppAuth-Info.plist"; sourceTree = ""; }; + 3BA62B607F0873F92288CE7D791F0C26 /* Pods-mentorship ios-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-mentorship ios-Info.plist"; sourceTree = ""; }; + 3D706C0B2250B3275152E7E01EFD89A4 /* OIDError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDError.h; path = Source/AppAuthCore/OIDError.h; sourceTree = ""; }; + 3D7C16DDFF54708124C71B98D3015298 /* Pods-mentorship ios-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-mentorship ios-acknowledgements.markdown"; sourceTree = ""; }; + 3F30E37336A67E34D69CE3F6697404B8 /* OIDTokenUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDTokenUtilities.h; path = Source/AppAuthCore/OIDTokenUtilities.h; sourceTree = ""; }; + 3FD1B4C1246D643E9476438C28048FA8 /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4082F9791C7B3B032996A82B6617632F /* GTMOAuth2KeychainCompatibility.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMOAuth2KeychainCompatibility.m; path = Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.m; sourceTree = ""; }; + 41C243F924B1681C225C02C7D6137956 /* GTMMIMEDocument.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMMIMEDocument.m; path = Source/GTMMIMEDocument.m; sourceTree = ""; }; + 42F35D3AD5A478242B95522CA059B5D9 /* OIDResponseTypes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDResponseTypes.h; path = Source/AppAuthCore/OIDResponseTypes.h; sourceTree = ""; }; + 476A4E97C23078E3B85FEF92AF4D6F62 /* GTMAppAuthFetcherAuthorization+Keychain.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GTMAppAuthFetcherAuthorization+Keychain.h"; path = "Source/GTMAppAuthFetcherAuthorization+Keychain.h"; sourceTree = ""; }; + 4807CDA153392A9CAE6AEC4C6E01E9E7 /* GoogleSignIn.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleSignIn.framework; path = Frameworks/GoogleSignIn.framework; sourceTree = ""; }; + 491A043663A145B4E13044E1C11DF1B8 /* GTMSessionFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcher.m; path = Source/GTMSessionFetcher.m; sourceTree = ""; }; + 493064774FA103423D33D9F2E8489FF3 /* OIDTokenResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDTokenResponse.m; path = Source/AppAuthCore/OIDTokenResponse.m; sourceTree = ""; }; + 4B96527396460EFCD85BCB4CFEA21BB8 /* OIDScopeUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDScopeUtilities.h; path = Source/AppAuthCore/OIDScopeUtilities.h; sourceTree = ""; }; + 51B30FDB0F55755AE165583A4942695F /* OIDIDToken.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDIDToken.h; path = Source/AppAuthCore/OIDIDToken.h; sourceTree = ""; }; + 5321FF27F66FC5A2D3BD99190EDD8598 /* Pods-mentorship ios-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-mentorship ios-frameworks.sh"; sourceTree = ""; }; + 534513D6AB6E325748777DB2920D6D37 /* Pods-mentorship ios.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-mentorship ios.debug.xcconfig"; sourceTree = ""; }; + 56FD8760A2AC82EC108EE1DD13BA7BA3 /* GTMSessionFetcher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMSessionFetcher-prefix.pch"; sourceTree = ""; }; + 58675B60CB9308C7F6CF1E590479D63A /* OIDURLQueryComponent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDURLQueryComponent.m; path = Source/AppAuthCore/OIDURLQueryComponent.m; sourceTree = ""; }; + 58F025C65EC807FFAC14F520B0DA02CF /* Pods-mentorship iosTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-mentorship iosTests-umbrella.h"; sourceTree = ""; }; + 5B74456FC79B31A118299C8CFA29E02F /* SafariServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SafariServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/SafariServices.framework; sourceTree = DEVELOPER_DIR; }; + 5BB1F49651B7E6C2A30CD99A9E0B4CF0 /* Pods-mentorship ios-mentorship iosUITests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-mentorship ios-mentorship iosUITests-umbrella.h"; sourceTree = ""; }; + 5F32C739DF04BB1DCE9756B9874F9B55 /* OIDExternalUserAgent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDExternalUserAgent.h; path = Source/AppAuthCore/OIDExternalUserAgent.h; sourceTree = ""; }; + 5F5344C9A8985BC64A8826434AE14D90 /* GTMSessionFetcherLogging.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcherLogging.m; path = Source/GTMSessionFetcherLogging.m; sourceTree = ""; }; + 5F96B3A022E0040FBB3194E3231BBF9B /* GTMSessionFetcher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GTMSessionFetcher.release.xcconfig; sourceTree = ""; }; + 606A63F7D678535DD67E9BBC16DBC3F9 /* AppAuth-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "AppAuth-prefix.pch"; sourceTree = ""; }; + 60726EAF3A48B3A5DCFD505280476615 /* Pods-mentorship iosTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-mentorship iosTests.debug.xcconfig"; sourceTree = ""; }; + 60899ED9DED004E7B0D816E6BA0FBE17 /* OIDErrorUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDErrorUtilities.m; path = Source/AppAuthCore/OIDErrorUtilities.m; sourceTree = ""; }; + 60A130AEE49119150B8AD52A61A362CA /* OIDAuthorizationService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationService.h; path = Source/AppAuthCore/OIDAuthorizationService.h; sourceTree = ""; }; + 6723353B59967A2F8CE5ADF84C4F9434 /* Pods_mentorship_ios_mentorship_iosUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_mentorship_ios_mentorship_iosUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6B35DF42BAB316E651B49CAD522D30D2 /* GTMSessionFetcherService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcherService.m; path = Source/GTMSessionFetcherService.m; sourceTree = ""; }; + 6C903BCB7155D03E0DE50753C984C025 /* OIDEndSessionRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDEndSessionRequest.m; path = Source/AppAuthCore/OIDEndSessionRequest.m; sourceTree = ""; }; + 6DAA0A5F9F8461368D30F0690494CEF1 /* OIDExternalUserAgentCatalyst.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDExternalUserAgentCatalyst.m; path = Source/AppAuth/iOS/OIDExternalUserAgentCatalyst.m; sourceTree = ""; }; + 6F1D3C4CB0ACD5E0120EC1686BB130F1 /* OIDAuthorizationRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationRequest.m; path = Source/AppAuthCore/OIDAuthorizationRequest.m; sourceTree = ""; }; + 6FB2DA57F1766D527F365AFDF52A72D6 /* OIDExternalUserAgentIOS.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDExternalUserAgentIOS.m; path = Source/AppAuth/iOS/OIDExternalUserAgentIOS.m; sourceTree = ""; }; + 6FD122EA0AB9AB2A8FFA5F676C795DF0 /* GTMAppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTMAppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 716060E95444EF216AACA0CB19A8327F /* AppAuth-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "AppAuth-dummy.m"; sourceTree = ""; }; + 7263B6009980D6AE2C85F9ED19236EE0 /* Pods-mentorship iosTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-mentorship iosTests-acknowledgements.markdown"; sourceTree = ""; }; + 72CDB17148C70EAFA425064D46E0AB43 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + 75CE3C661C504B77322B2D9258F6D617 /* GTMSessionFetcher-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GTMSessionFetcher-Info.plist"; sourceTree = ""; }; + 7604721BAE0C9863C9871789F8BF5C35 /* GTMSessionUploadFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionUploadFetcher.m; path = Source/GTMSessionUploadFetcher.m; sourceTree = ""; }; + 78B0DD0227B7E59711E1B5ED8247519F /* OIDAuthorizationService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationService.m; path = Source/AppAuthCore/OIDAuthorizationService.m; sourceTree = ""; }; + 7AC153978EAF0C98144886FEBC893ABE /* Pods-mentorship ios-mentorship iosUITests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-mentorship ios-mentorship iosUITests-Info.plist"; sourceTree = ""; }; + 7C8E8CAA1727B3C595EF995FFA650CD6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 7FA8AAFA32ACE623962A8E103480262D /* OIDAuthStateErrorDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthStateErrorDelegate.h; path = Source/AppAuthCore/OIDAuthStateErrorDelegate.h; sourceTree = ""; }; + 7FDB4F9D06C19DAFFC1DF358C8CF570E /* OIDExternalUserAgentSession.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDExternalUserAgentSession.h; path = Source/AppAuthCore/OIDExternalUserAgentSession.h; sourceTree = ""; }; + 85D0D1BFD4E9D0872A7069F4560FA17B /* GTMAppAuth.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GTMAppAuth.modulemap; sourceTree = ""; }; + 861F1981C779535A562232F6DECA4B4E /* OIDFieldMapping.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDFieldMapping.m; path = Source/AppAuthCore/OIDFieldMapping.m; sourceTree = ""; }; + 8D91E1E6D5C5C883EC5FF59B05091CB4 /* Pods-mentorship ios-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-mentorship ios-umbrella.h"; sourceTree = ""; }; + 8DE351FA8C920C81A50ADA6FF5326C65 /* OIDScopes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDScopes.h; path = Source/AppAuthCore/OIDScopes.h; sourceTree = ""; }; + 8E09B339B3377120973AF1950AF17CD7 /* AppAuth-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "AppAuth-Info.plist"; sourceTree = ""; }; + 9145CB48AC842C88E43BB74ECE14B3B3 /* Pods-mentorship ios.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-mentorship ios.modulemap"; sourceTree = ""; }; + 917B0E1C097160C75E249E040413F058 /* AppAuth.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = AppAuth.release.xcconfig; sourceTree = ""; }; + 947742DE685D3112A4947B5D79CC0C57 /* OIDTokenUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDTokenUtilities.m; path = Source/AppAuthCore/OIDTokenUtilities.m; sourceTree = ""; }; + 95B6991B778BE3F367235DBDC2021762 /* OIDURLSessionProvider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDURLSessionProvider.h; path = Source/AppAuthCore/OIDURLSessionProvider.h; sourceTree = ""; }; + 96CDC5766FB3A0359F50EA531C9400CC /* OIDRegistrationRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDRegistrationRequest.m; path = Source/AppAuthCore/OIDRegistrationRequest.m; sourceTree = ""; }; + 9A5671479D49B01EA29DC81E86747A24 /* GTMAppAuth-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GTMAppAuth-dummy.m"; sourceTree = ""; }; + 9C04B2D71B76EC26B743C01F9E9BE912 /* OIDScopes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDScopes.m; path = Source/AppAuthCore/OIDScopes.m; sourceTree = ""; }; + 9C28FCC1CEF4208E89FACA0CB2FDCB48 /* OIDURLQueryComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDURLQueryComponent.h; path = Source/AppAuthCore/OIDURLQueryComponent.h; sourceTree = ""; }; + 9CE4F8EF50120F68B743B907FC614C98 /* GTMReadMonitorInputStream.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMReadMonitorInputStream.m; path = Source/GTMReadMonitorInputStream.m; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9DE8EC5AEF87FED97A80DA8C7C4B8602 /* AppAuth.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = AppAuth.h; path = Source/AppAuth.h; sourceTree = ""; }; + A0E687C93BF9BC8A38F9F3D5C019E6D0 /* OIDExternalUserAgentCatalyst.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDExternalUserAgentCatalyst.h; path = Source/AppAuth/iOS/OIDExternalUserAgentCatalyst.h; sourceTree = ""; }; + A1A2046586D911B298FE96991C14207A /* OIDAuthState.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthState.h; path = Source/AppAuthCore/OIDAuthState.h; sourceTree = ""; }; + A475F803F0CAA576ABB9F5910018FE04 /* OIDAuthorizationResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationResponse.h; path = Source/AppAuthCore/OIDAuthorizationResponse.h; sourceTree = ""; }; + A63891EFBD9E2B177549512671020B36 /* OIDExternalUserAgentIOSCustomBrowser.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDExternalUserAgentIOSCustomBrowser.m; path = Source/AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m; sourceTree = ""; }; + A7AA2B0D2F921EFAE386870CD4175555 /* GTMSessionUploadFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionUploadFetcher.h; path = Source/GTMSessionUploadFetcher.h; sourceTree = ""; }; + A7AA99808D37E94902B0E4448AB29ADE /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + ABE775C88B3DFE4CE3E1358EFB6FC3CA /* OIDClientMetadataParameters.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDClientMetadataParameters.m; path = Source/AppAuthCore/OIDClientMetadataParameters.m; sourceTree = ""; }; + ACCBA4B91DFEFC2176100A70B856BC6A /* OIDResponseTypes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDResponseTypes.m; path = Source/AppAuthCore/OIDResponseTypes.m; sourceTree = ""; }; + B01846C50A6E49C262464A2FBE59F1E9 /* AppAuth.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = AppAuth.debug.xcconfig; sourceTree = ""; }; + B0E52B096DDF89977735505C25E2307D /* OIDServiceConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDServiceConfiguration.h; path = Source/AppAuthCore/OIDServiceConfiguration.h; sourceTree = ""; }; + B1C0F3D9E0A9BFF24C88E6E03E929131 /* Pods-mentorship ios-mentorship iosUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-mentorship ios-mentorship iosUITests.release.xcconfig"; sourceTree = ""; }; + B1DCCC57B7C242E3D54FFAD8890F1CDF /* GTMSessionFetcher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GTMSessionFetcher-dummy.m"; sourceTree = ""; }; + B3DCE7187E04CC451FE91221AF6E4B56 /* OIDTokenRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDTokenRequest.m; path = Source/AppAuthCore/OIDTokenRequest.m; sourceTree = ""; }; + B5C23FDD115D6C6944C9C16DF0A248E4 /* OIDError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDError.m; path = Source/AppAuthCore/OIDError.m; sourceTree = ""; }; + BA1D24E57164F99EF8C7E3CA1804BD42 /* GTMGatherInputStream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMGatherInputStream.h; path = Source/GTMGatherInputStream.h; sourceTree = ""; }; + BB26709A53BE378F14D172F7831ADAF9 /* Pods-mentorship ios-mentorship iosUITests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-mentorship ios-mentorship iosUITests-frameworks.sh"; sourceTree = ""; }; + BBF941AEBADD9F6AD4538FE8788888F6 /* GTMKeychain_iOS.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMKeychain_iOS.m; path = Source/iOS/GTMKeychain_iOS.m; sourceTree = ""; }; + BDE780ECB5DE32F67CA9FC18B0E78C80 /* Pods-mentorship ios-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-mentorship ios-dummy.m"; sourceTree = ""; }; + BF78AC6BAB39C75DEAC27C39FD0C90E6 /* OIDRegistrationRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDRegistrationRequest.h; path = Source/AppAuthCore/OIDRegistrationRequest.h; sourceTree = ""; }; + C1998E0D8085221AD87F89B614C10E52 /* GTMSessionFetcher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTMSessionFetcher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C279CC1E3D7BE35AB42CE83FF5907FB5 /* Pods_mentorship_ios.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_mentorship_ios.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C415FC7125A8E8F9E00115D2158B5E98 /* GoogleSignIn.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = GoogleSignIn.bundle; path = Resources/GoogleSignIn.bundle; sourceTree = ""; }; + CACC84D9888E5F02DFB66C1EFC6B53C2 /* GTMSessionFetcher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTMSessionFetcher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D04E76110A30D474F45397417CF66599 /* GTMAppAuth.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GTMAppAuth.debug.xcconfig; sourceTree = ""; }; + D182BA1346A18D9DAB46AE36C1E2F5F5 /* Pods-mentorship ios-mentorship iosUITests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-mentorship ios-mentorship iosUITests-acknowledgements.plist"; sourceTree = ""; }; + D92C3A75664A17434FB87EAA1C9F736F /* GTMSessionFetcherService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcherService.h; path = Source/GTMSessionFetcherService.h; sourceTree = ""; }; + DAA391547896D3DFD85B0C72E39C34AD /* GTMSessionFetcherLogging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcherLogging.h; path = Source/GTMSessionFetcherLogging.h; sourceTree = ""; }; + DB41389C8E5C0DB7BEE25B19223EE498 /* AppAuth-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "AppAuth-umbrella.h"; sourceTree = ""; }; + DC804BAE68E3036FAB3CA483E3B5D4D5 /* OIDURLSessionProvider.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDURLSessionProvider.m; path = Source/AppAuthCore/OIDURLSessionProvider.m; sourceTree = ""; }; + DE6C8BFBB00A11E520F0E20A4B44E172 /* OIDServiceDiscovery.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDServiceDiscovery.h; path = Source/AppAuthCore/OIDServiceDiscovery.h; sourceTree = ""; }; + DFA00892743197B5F9646DD66F230A34 /* AppAuth.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = AppAuth.modulemap; sourceTree = ""; }; + E49C7D735C96D80767D1DE34A6447961 /* GTMAppAuthFetcherAuthorization.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMAppAuthFetcherAuthorization.h; path = Source/GTMAppAuthFetcherAuthorization.h; sourceTree = ""; }; + E58D2E563A823896B05C184A88BEC5F3 /* OIDAuthState+IOS.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "OIDAuthState+IOS.m"; path = "Source/AppAuth/iOS/OIDAuthState+IOS.m"; sourceTree = ""; }; + E9C487C6396F4F34AA486FA681B36E7F /* GTMSessionFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcher.h; path = Source/GTMSessionFetcher.h; sourceTree = ""; }; + EAED381C8A63DF974E2643895A50FC06 /* GTMAppAuth-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMAppAuth-prefix.pch"; sourceTree = ""; }; + EB9B60E189FAC21B2C9104C7DAB0D65A /* OIDAuthState+IOS.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "OIDAuthState+IOS.h"; path = "Source/AppAuth/iOS/OIDAuthState+IOS.h"; sourceTree = ""; }; + EE1A963BFF40376C402DCFC8F5CFEB34 /* Pods_mentorship_iosTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_mentorship_iosTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EE8CD03E234491AC3FFEA89415C2EC3C /* GTMMIMEDocument.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMMIMEDocument.h; path = Source/GTMMIMEDocument.h; sourceTree = ""; }; + EEA01564A45AE0B2D7B33D0A173EDE68 /* OIDAuthState.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthState.m; path = Source/AppAuthCore/OIDAuthState.m; sourceTree = ""; }; + EEA474C0422A725B8ACF1448991B3BB4 /* OIDEndSessionRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDEndSessionRequest.h; path = Source/AppAuthCore/OIDEndSessionRequest.h; sourceTree = ""; }; + EEE3EABEA79A11C08EB88A403EFA169E /* OIDAuthorizationRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationRequest.h; path = Source/AppAuthCore/OIDAuthorizationRequest.h; sourceTree = ""; }; + EEF2E161EA57E8FD07C267755E8004A3 /* OIDEndSessionResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDEndSessionResponse.h; path = Source/AppAuthCore/OIDEndSessionResponse.h; sourceTree = ""; }; + EFE65E9AF53F83E24423FA1FCEC50885 /* OIDServiceDiscovery.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDServiceDiscovery.m; path = Source/AppAuthCore/OIDServiceDiscovery.m; sourceTree = ""; }; + F1B784727061BFA4B97C8D316FDADAA9 /* AppAuthCore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = AppAuthCore.h; path = Source/AppAuthCore.h; sourceTree = ""; }; + F30665C00BF13BFADDD8BECDF2C02D31 /* GTMSessionFetcher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GTMSessionFetcher.debug.xcconfig; sourceTree = ""; }; + F678ABCE32389498766DDA3647223327 /* GTMAppAuthFetcherAuthorization+Keychain.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "GTMAppAuthFetcherAuthorization+Keychain.m"; path = "Source/GTMAppAuthFetcherAuthorization+Keychain.m"; sourceTree = ""; }; + F6ECBE22F5655207E067F9EEAD60238C /* GTMSessionFetcher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMSessionFetcher-umbrella.h"; sourceTree = ""; }; + F8DB658EC192A6B16FF51B90CA4F0AE9 /* GTMAppAuth.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMAppAuth.h; path = Source/GTMAppAuth.h; sourceTree = ""; }; + FA19F308CAB86A96F87E57D22CC3C792 /* Pods-mentorship iosTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-mentorship iosTests.modulemap"; sourceTree = ""; }; + FBBFB2DBA93531F1A771C1333958547D /* OIDServiceConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDServiceConfiguration.m; path = Source/AppAuthCore/OIDServiceConfiguration.m; sourceTree = ""; }; + FF3B5B3603A180F6F37665F34A5ACE2C /* GTMSessionFetcher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GTMSessionFetcher.modulemap; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 072D6922919B2D97BDAF9CFCD9FF59C8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 77F8771176D9BE429820216FFD687D1B /* AppAuth.framework in Frameworks */, + 87A2AA12423B55AD7791A99D79534D46 /* Foundation.framework in Frameworks */, + 527B5C1B1C9E916C516A73E93CA8BB62 /* GTMSessionFetcher.framework in Frameworks */, + 4609E25A2C08EDA3E3729934BC90DCDE /* SafariServices.framework in Frameworks */, + 7F99EA6887D99503660FCDF1DD7A3E04 /* Security.framework in Frameworks */, + C2AABCD1F39FCA174A0C33D19E160BBF /* SystemConfiguration.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 16CF22217BEA907AC6550A5C485822F1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 71BCEC30B9A618753E543ED8C606B9F8 /* Foundation.framework in Frameworks */, + EF92363675B8414D14801D29A288FA31 /* SafariServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 65E7CB4C247125E90090B9CCFE914B70 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC4164A4DCDACFD4960D61FA1DA61A01 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8D6B9E1BD179EF938832330D32B26D5B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7F7C8089080C1BB6D4AD94CE2AAB1749 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 98AB3B5AFE6FC96417A7790B3B87727A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD2342949A98F8B328CA82C88F1742D /* Foundation.framework in Frameworks */, + 935771162DF644301DCB001153BE1A4A /* Security.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A78E5BE81BADF2BF1190CD4D5BFF614B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6F5E49B223BE8067ACA22E443E6125A7 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 04B7C86BBAB9BEA20DEB5F3D6A99A300 /* Full */ = { + isa = PBXGroup; + children = ( + BA1D24E57164F99EF8C7E3CA1804BD42 /* GTMGatherInputStream.h */, + 2886E3CC8B435722D172AFD44E448283 /* GTMGatherInputStream.m */, + EE8CD03E234491AC3FFEA89415C2EC3C /* GTMMIMEDocument.h */, + 41C243F924B1681C225C02C7D6137956 /* GTMMIMEDocument.m */, + 29E999D050BC6AC2E2B4658E5450C009 /* GTMReadMonitorInputStream.h */, + 9CE4F8EF50120F68B743B907FC614C98 /* GTMReadMonitorInputStream.m */, + ); + name = Full; + sourceTree = ""; + }; + 1DDF0144637DFE93214B78D4ED64983F /* Pods */ = { + isa = PBXGroup; + children = ( + 33A01798A7187AC069E1DF9406C8CF26 /* AppAuth */, + E6E0000435D7AF21058FFF93B939D6BF /* GoogleSignIn */, + 8335AE7F253BD12530205BA755CBEE2E /* GTMAppAuth */, + 820BA1C7E4FB79925E5CC352B2FBD955 /* GTMSessionFetcher */, + ); + name = Pods; + sourceTree = ""; + }; + 2E12181945F4538E0856F3B889F24F2C /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7AA99808D37E94902B0E4448AB29ADE /* AppAuth.framework */, + CACC84D9888E5F02DFB66C1EFC6B53C2 /* GTMSessionFetcher.framework */, + B104FE9AC033593E2BA9A15D5D3E7AF3 /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + 30A371940DAE61C2B0EE55ED212238B4 /* Pods-mentorship iosTests */ = { + isa = PBXGroup; + children = ( + FA19F308CAB86A96F87E57D22CC3C792 /* Pods-mentorship iosTests.modulemap */, + 7263B6009980D6AE2C85F9ED19236EE0 /* Pods-mentorship iosTests-acknowledgements.markdown */, + 07CA8B3AD67B29339CE5BF50DD1F4181 /* Pods-mentorship iosTests-acknowledgements.plist */, + 2BD49343D0716132373053EB4843C9F3 /* Pods-mentorship iosTests-dummy.m */, + 22CAD830484EBA2C090D8883F1AD9B11 /* Pods-mentorship iosTests-Info.plist */, + 58F025C65EC807FFAC14F520B0DA02CF /* Pods-mentorship iosTests-umbrella.h */, + 60726EAF3A48B3A5DCFD505280476615 /* Pods-mentorship iosTests.debug.xcconfig */, + 38E47E42BE9D59EA60D119AF99378190 /* Pods-mentorship iosTests.release.xcconfig */, + ); + name = "Pods-mentorship iosTests"; + path = "Target Support Files/Pods-mentorship iosTests"; + sourceTree = ""; + }; + 33A01798A7187AC069E1DF9406C8CF26 /* AppAuth */ = { + isa = PBXGroup; + children = ( + A0F01145C3494D26B9357769E23A0088 /* Core */, + 7524F4E91674DAE5430D2DDB76D289CA /* ExternalUserAgent */, + 49CB805DCCB2EC9B4A93B4F84F4CEDBF /* Support Files */, + ); + path = AppAuth; + sourceTree = ""; + }; + 476DEC641E636B3A996D67C3BC4C79FB /* Support Files */ = { + isa = PBXGroup; + children = ( + FF3B5B3603A180F6F37665F34A5ACE2C /* GTMSessionFetcher.modulemap */, + B1DCCC57B7C242E3D54FFAD8890F1CDF /* GTMSessionFetcher-dummy.m */, + 75CE3C661C504B77322B2D9258F6D617 /* GTMSessionFetcher-Info.plist */, + 56FD8760A2AC82EC108EE1DD13BA7BA3 /* GTMSessionFetcher-prefix.pch */, + F6ECBE22F5655207E067F9EEAD60238C /* GTMSessionFetcher-umbrella.h */, + F30665C00BF13BFADDD8BECDF2C02D31 /* GTMSessionFetcher.debug.xcconfig */, + 5F96B3A022E0040FBB3194E3231BBF9B /* GTMSessionFetcher.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/GTMSessionFetcher"; + sourceTree = ""; + }; + 49CB805DCCB2EC9B4A93B4F84F4CEDBF /* Support Files */ = { + isa = PBXGroup; + children = ( + DFA00892743197B5F9646DD66F230A34 /* AppAuth.modulemap */, + 716060E95444EF216AACA0CB19A8327F /* AppAuth-dummy.m */, + 8E09B339B3377120973AF1950AF17CD7 /* AppAuth-Info.plist */, + 606A63F7D678535DD67E9BBC16DBC3F9 /* AppAuth-prefix.pch */, + DB41389C8E5C0DB7BEE25B19223EE498 /* AppAuth-umbrella.h */, + B01846C50A6E49C262464A2FBE59F1E9 /* AppAuth.debug.xcconfig */, + 917B0E1C097160C75E249E040413F058 /* AppAuth.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/AppAuth"; + sourceTree = ""; + }; + 5A68FBBAA221C910949B030A75EE83A5 /* Pods-mentorship ios */ = { + isa = PBXGroup; + children = ( + 9145CB48AC842C88E43BB74ECE14B3B3 /* Pods-mentorship ios.modulemap */, + 3D7C16DDFF54708124C71B98D3015298 /* Pods-mentorship ios-acknowledgements.markdown */, + 25A1A434C35BDFA6CA4D6AFCE828FE18 /* Pods-mentorship ios-acknowledgements.plist */, + BDE780ECB5DE32F67CA9FC18B0E78C80 /* Pods-mentorship ios-dummy.m */, + 5321FF27F66FC5A2D3BD99190EDD8598 /* Pods-mentorship ios-frameworks.sh */, + 3BA62B607F0873F92288CE7D791F0C26 /* Pods-mentorship ios-Info.plist */, + 0C0AAACBC37788AED131E7B8D0C77CC4 /* Pods-mentorship ios-resources.sh */, + 8D91E1E6D5C5C883EC5FF59B05091CB4 /* Pods-mentorship ios-umbrella.h */, + 534513D6AB6E325748777DB2920D6D37 /* Pods-mentorship ios.debug.xcconfig */, + 30A1D65779BE9FA691AEBD03B5455BBB /* Pods-mentorship ios.release.xcconfig */, + ); + name = "Pods-mentorship ios"; + path = "Target Support Files/Pods-mentorship ios"; + sourceTree = ""; + }; + 5F6C8F37429E7ADD29B480399FC659BB /* Resources */ = { + isa = PBXGroup; + children = ( + C415FC7125A8E8F9E00115D2158B5E98 /* GoogleSignIn.bundle */, + ); + name = Resources; + sourceTree = ""; + }; + 7524F4E91674DAE5430D2DDB76D289CA /* ExternalUserAgent */ = { + isa = PBXGroup; + children = ( + 9DE8EC5AEF87FED97A80DA8C7C4B8602 /* AppAuth.h */, + 26FD7859CD2E03E7FC8158229A0568A8 /* OIDAuthorizationService+IOS.h */, + 0D48485D1A687CC167B4E447E691B6EF /* OIDAuthorizationService+IOS.m */, + EB9B60E189FAC21B2C9104C7DAB0D65A /* OIDAuthState+IOS.h */, + E58D2E563A823896B05C184A88BEC5F3 /* OIDAuthState+IOS.m */, + A0E687C93BF9BC8A38F9F3D5C019E6D0 /* OIDExternalUserAgentCatalyst.h */, + 6DAA0A5F9F8461368D30F0690494CEF1 /* OIDExternalUserAgentCatalyst.m */, + 305D7A0C5FB8E82571085947BC82C642 /* OIDExternalUserAgentIOS.h */, + 6FB2DA57F1766D527F365AFDF52A72D6 /* OIDExternalUserAgentIOS.m */, + 17ED11E753F5ED39DCB3A46E8455EA0E /* OIDExternalUserAgentIOSCustomBrowser.h */, + A63891EFBD9E2B177549512671020B36 /* OIDExternalUserAgentIOSCustomBrowser.m */, + ); + name = ExternalUserAgent; + sourceTree = ""; + }; + 7C3A1BD6691E2859FAEF7B48CEEA0CC0 /* Core */ = { + isa = PBXGroup; + children = ( + E9C487C6396F4F34AA486FA681B36E7F /* GTMSessionFetcher.h */, + 491A043663A145B4E13044E1C11DF1B8 /* GTMSessionFetcher.m */, + DAA391547896D3DFD85B0C72E39C34AD /* GTMSessionFetcherLogging.h */, + 5F5344C9A8985BC64A8826434AE14D90 /* GTMSessionFetcherLogging.m */, + D92C3A75664A17434FB87EAA1C9F736F /* GTMSessionFetcherService.h */, + 6B35DF42BAB316E651B49CAD522D30D2 /* GTMSessionFetcherService.m */, + A7AA2B0D2F921EFAE386870CD4175555 /* GTMSessionUploadFetcher.h */, + 7604721BAE0C9863C9871789F8BF5C35 /* GTMSessionUploadFetcher.m */, + ); + name = Core; + sourceTree = ""; + }; + 820BA1C7E4FB79925E5CC352B2FBD955 /* GTMSessionFetcher */ = { + isa = PBXGroup; + children = ( + 7C3A1BD6691E2859FAEF7B48CEEA0CC0 /* Core */, + 04B7C86BBAB9BEA20DEB5F3D6A99A300 /* Full */, + 476DEC641E636B3A996D67C3BC4C79FB /* Support Files */, + ); + path = GTMSessionFetcher; + sourceTree = ""; + }; + 8335AE7F253BD12530205BA755CBEE2E /* GTMAppAuth */ = { + isa = PBXGroup; + children = ( + F8DB658EC192A6B16FF51B90CA4F0AE9 /* GTMAppAuth.h */, + E49C7D735C96D80767D1DE34A6447961 /* GTMAppAuthFetcherAuthorization.h */, + 0CFB68E7A5CA39188D89F85913215076 /* GTMAppAuthFetcherAuthorization.m */, + 476A4E97C23078E3B85FEF92AF4D6F62 /* GTMAppAuthFetcherAuthorization+Keychain.h */, + F678ABCE32389498766DDA3647223327 /* GTMAppAuthFetcherAuthorization+Keychain.m */, + 11AE9BE473FE105DDB322875E4268930 /* GTMKeychain.h */, + BBF941AEBADD9F6AD4538FE8788888F6 /* GTMKeychain_iOS.m */, + 2675390570F0CB22B1A5323F499195D9 /* GTMOAuth2KeychainCompatibility.h */, + 4082F9791C7B3B032996A82B6617632F /* GTMOAuth2KeychainCompatibility.m */, + CA40200D8E4CCAF4A8A06A5AD751445E /* Support Files */, + ); + path = GTMAppAuth; + sourceTree = ""; + }; + 89AE7F95E49B6241DDD073B701B76AE4 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4807CDA153392A9CAE6AEC4C6E01E9E7 /* GoogleSignIn.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9066D3C5B2ACF2E2D48DA4559FCA79A6 /* Products */ = { + isa = PBXGroup; + children = ( + 3FD1B4C1246D643E9476438C28048FA8 /* AppAuth.framework */, + 6FD122EA0AB9AB2A8FFA5F676C795DF0 /* GTMAppAuth.framework */, + C1998E0D8085221AD87F89B614C10E52 /* GTMSessionFetcher.framework */, + C279CC1E3D7BE35AB42CE83FF5907FB5 /* Pods_mentorship_ios.framework */, + 6723353B59967A2F8CE5ADF84C4F9434 /* Pods_mentorship_ios_mentorship_iosUITests.framework */, + EE1A963BFF40376C402DCFC8F5CFEB34 /* Pods_mentorship_iosTests.framework */, + ); + name = Products; + sourceTree = ""; + }; + A0F01145C3494D26B9357769E23A0088 /* Core */ = { + isa = PBXGroup; + children = ( + F1B784727061BFA4B97C8D316FDADAA9 /* AppAuthCore.h */, + EEE3EABEA79A11C08EB88A403EFA169E /* OIDAuthorizationRequest.h */, + 6F1D3C4CB0ACD5E0120EC1686BB130F1 /* OIDAuthorizationRequest.m */, + A475F803F0CAA576ABB9F5910018FE04 /* OIDAuthorizationResponse.h */, + 32CDEF47E6D0D52EEE7CB6FC86090D58 /* OIDAuthorizationResponse.m */, + 60A130AEE49119150B8AD52A61A362CA /* OIDAuthorizationService.h */, + 78B0DD0227B7E59711E1B5ED8247519F /* OIDAuthorizationService.m */, + A1A2046586D911B298FE96991C14207A /* OIDAuthState.h */, + EEA01564A45AE0B2D7B33D0A173EDE68 /* OIDAuthState.m */, + 09DB544D3B1B801F759CC3577F7BFAE2 /* OIDAuthStateChangeDelegate.h */, + 7FA8AAFA32ACE623962A8E103480262D /* OIDAuthStateErrorDelegate.h */, + 221BBB3F1D598981E3CDAF4EE8C85BB7 /* OIDClientMetadataParameters.h */, + ABE775C88B3DFE4CE3E1358EFB6FC3CA /* OIDClientMetadataParameters.m */, + 2AFA720E1B4DE82D628D142989729554 /* OIDDefines.h */, + EEA474C0422A725B8ACF1448991B3BB4 /* OIDEndSessionRequest.h */, + 6C903BCB7155D03E0DE50753C984C025 /* OIDEndSessionRequest.m */, + EEF2E161EA57E8FD07C267755E8004A3 /* OIDEndSessionResponse.h */, + 144A8518E212ECDBCB6C20D9C15B5A57 /* OIDEndSessionResponse.m */, + 3D706C0B2250B3275152E7E01EFD89A4 /* OIDError.h */, + B5C23FDD115D6C6944C9C16DF0A248E4 /* OIDError.m */, + 0C6E88A1D88407285CECA9C9E47BFB6C /* OIDErrorUtilities.h */, + 60899ED9DED004E7B0D816E6BA0FBE17 /* OIDErrorUtilities.m */, + 5F32C739DF04BB1DCE9756B9874F9B55 /* OIDExternalUserAgent.h */, + 022E8B0795B3B3AAA93ABC1FF0A540A5 /* OIDExternalUserAgentRequest.h */, + 7FDB4F9D06C19DAFFC1DF358C8CF570E /* OIDExternalUserAgentSession.h */, + 3003261E01B3BCA95DBA61082A8D6B15 /* OIDFieldMapping.h */, + 861F1981C779535A562232F6DECA4B4E /* OIDFieldMapping.m */, + 25D1D33BE58E2175C32DC08C25152B91 /* OIDGrantTypes.h */, + 268A2998A4DDBB2C0AC87F587AA2DEC5 /* OIDGrantTypes.m */, + 51B30FDB0F55755AE165583A4942695F /* OIDIDToken.h */, + 2EDE178F2A7F6AF86943F64AE4F32B6A /* OIDIDToken.m */, + BF78AC6BAB39C75DEAC27C39FD0C90E6 /* OIDRegistrationRequest.h */, + 96CDC5766FB3A0359F50EA531C9400CC /* OIDRegistrationRequest.m */, + 05F56E8DA7268229361868CD6D5EFBF1 /* OIDRegistrationResponse.h */, + 2980A0A5CA912C4B704DFFDCCB19CFC1 /* OIDRegistrationResponse.m */, + 42F35D3AD5A478242B95522CA059B5D9 /* OIDResponseTypes.h */, + ACCBA4B91DFEFC2176100A70B856BC6A /* OIDResponseTypes.m */, + 8DE351FA8C920C81A50ADA6FF5326C65 /* OIDScopes.h */, + 9C04B2D71B76EC26B743C01F9E9BE912 /* OIDScopes.m */, + 4B96527396460EFCD85BCB4CFEA21BB8 /* OIDScopeUtilities.h */, + 1125054B815CBABCD555A9C85116A8C5 /* OIDScopeUtilities.m */, + B0E52B096DDF89977735505C25E2307D /* OIDServiceConfiguration.h */, + FBBFB2DBA93531F1A771C1333958547D /* OIDServiceConfiguration.m */, + DE6C8BFBB00A11E520F0E20A4B44E172 /* OIDServiceDiscovery.h */, + EFE65E9AF53F83E24423FA1FCEC50885 /* OIDServiceDiscovery.m */, + 18492EC0DB790FFE76C1FB289EF489B5 /* OIDTokenRequest.h */, + B3DCE7187E04CC451FE91221AF6E4B56 /* OIDTokenRequest.m */, + 27B32EA1466E95564B5FAD5E821AFFCB /* OIDTokenResponse.h */, + 493064774FA103423D33D9F2E8489FF3 /* OIDTokenResponse.m */, + 3F30E37336A67E34D69CE3F6697404B8 /* OIDTokenUtilities.h */, + 947742DE685D3112A4947B5D79CC0C57 /* OIDTokenUtilities.m */, + 9C28FCC1CEF4208E89FACA0CB2FDCB48 /* OIDURLQueryComponent.h */, + 58675B60CB9308C7F6CF1E590479D63A /* OIDURLQueryComponent.m */, + 95B6991B778BE3F367235DBDC2021762 /* OIDURLSessionProvider.h */, + DC804BAE68E3036FAB3CA483E3B5D4D5 /* OIDURLSessionProvider.m */, + ); + name = Core; + sourceTree = ""; + }; + A54249752BEA6DC9A23A623C3CDD1A3B /* Pods-mentorship ios-mentorship iosUITests */ = { + isa = PBXGroup; + children = ( + 2822649A60BD61AEA45CA5F8F59D7E7D /* Pods-mentorship ios-mentorship iosUITests.modulemap */, + 2E0A983AC48FC2B1ED279AE8F7E10446 /* Pods-mentorship ios-mentorship iosUITests-acknowledgements.markdown */, + D182BA1346A18D9DAB46AE36C1E2F5F5 /* Pods-mentorship ios-mentorship iosUITests-acknowledgements.plist */, + 26978B4C518564639BE8B2386D94265E /* Pods-mentorship ios-mentorship iosUITests-dummy.m */, + BB26709A53BE378F14D172F7831ADAF9 /* Pods-mentorship ios-mentorship iosUITests-frameworks.sh */, + 7AC153978EAF0C98144886FEBC893ABE /* Pods-mentorship ios-mentorship iosUITests-Info.plist */, + 0EF43F7F5C2500641CE581C5997CE1BD /* Pods-mentorship ios-mentorship iosUITests-resources.sh */, + 5BB1F49651B7E6C2A30CD99A9E0B4CF0 /* Pods-mentorship ios-mentorship iosUITests-umbrella.h */, + 104D3E9636F5950B3E5E52D45BE250BE /* Pods-mentorship ios-mentorship iosUITests.debug.xcconfig */, + B1C0F3D9E0A9BFF24C88E6E03E929131 /* Pods-mentorship ios-mentorship iosUITests.release.xcconfig */, + ); + name = "Pods-mentorship ios-mentorship iosUITests"; + path = "Target Support Files/Pods-mentorship ios-mentorship iosUITests"; + sourceTree = ""; + }; + B104FE9AC033593E2BA9A15D5D3E7AF3 /* iOS */ = { + isa = PBXGroup; + children = ( + 7C8E8CAA1727B3C595EF995FFA650CD6 /* Foundation.framework */, + 5B74456FC79B31A118299C8CFA29E02F /* SafariServices.framework */, + 72CDB17148C70EAFA425064D46E0AB43 /* Security.framework */, + 36B1B635135A16FECC5DEC33BB89C9FE /* SystemConfiguration.framework */, + ); + name = iOS; + sourceTree = ""; + }; + BAFB95EF2A8D57A554F6818E46AA15F7 /* Support Files */ = { + isa = PBXGroup; + children = ( + 23E453339F50955000DE65F30282EF6F /* GoogleSignIn.debug.xcconfig */, + 26108ECE6955E499A7A690C9E6FC537D /* GoogleSignIn.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/GoogleSignIn"; + sourceTree = ""; + }; + CA40200D8E4CCAF4A8A06A5AD751445E /* Support Files */ = { + isa = PBXGroup; + children = ( + 85D0D1BFD4E9D0872A7069F4560FA17B /* GTMAppAuth.modulemap */, + 9A5671479D49B01EA29DC81E86747A24 /* GTMAppAuth-dummy.m */, + 394F0AB33B23B23E9F0AB42DD0AF2641 /* GTMAppAuth-Info.plist */, + EAED381C8A63DF974E2643895A50FC06 /* GTMAppAuth-prefix.pch */, + 301BBC564FA7E1A935A554E14F038BD2 /* GTMAppAuth-umbrella.h */, + D04E76110A30D474F45397417CF66599 /* GTMAppAuth.debug.xcconfig */, + 3328F331E40151217475F5186FA7E7A7 /* GTMAppAuth.release.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/GTMAppAuth"; + sourceTree = ""; + }; + CD5E6003CB671A8A2198EE97725EB412 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 5A68FBBAA221C910949B030A75EE83A5 /* Pods-mentorship ios */, + A54249752BEA6DC9A23A623C3CDD1A3B /* Pods-mentorship ios-mentorship iosUITests */, + 30A371940DAE61C2B0EE55ED212238B4 /* Pods-mentorship iosTests */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + 2E12181945F4538E0856F3B889F24F2C /* Frameworks */, + 1DDF0144637DFE93214B78D4ED64983F /* Pods */, + 9066D3C5B2ACF2E2D48DA4559FCA79A6 /* Products */, + CD5E6003CB671A8A2198EE97725EB412 /* Targets Support Files */, + ); + sourceTree = ""; + }; + E6E0000435D7AF21058FFF93B939D6BF /* GoogleSignIn */ = { + isa = PBXGroup; + children = ( + 89AE7F95E49B6241DDD073B701B76AE4 /* Frameworks */, + 5F6C8F37429E7ADD29B480399FC659BB /* Resources */, + BAFB95EF2A8D57A554F6818E46AA15F7 /* Support Files */, + ); + path = GoogleSignIn; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 4ED0E215E12EB40D1306883BC6DB97EA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 8953999C7727F2F912F6A737FD0FA0AC /* GTMGatherInputStream.h in Headers */, + A3C394C6C598DFAC45199851B2FF5F97 /* GTMMIMEDocument.h in Headers */, + 243C0608B35874A4CAB88E3951BAD348 /* GTMReadMonitorInputStream.h in Headers */, + 2D56A3ED5FE2959E984D12CC08761F44 /* GTMSessionFetcher-umbrella.h in Headers */, + 1C61400E88FC471BECD871E263FFA844 /* GTMSessionFetcher.h in Headers */, + 19B49E9D25674FD49D6819A68C8B48D8 /* GTMSessionFetcherLogging.h in Headers */, + 61D9C8902B2B3C97CD30023D322B8BAB /* GTMSessionFetcherService.h in Headers */, + 39DDFC3157A616235C548673C91887B0 /* GTMSessionUploadFetcher.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 594AF7C17FFFD6791D6EAB88E0F424FD /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + FF06333F8E0F64CF45567B6419768574 /* Pods-mentorship ios-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AF0495172B1DCF3E83F1F96FF98530FB /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + CE9462D01A9FBD478D4A0742AA127534 /* Pods-mentorship iosTests-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B988B1E8057F208BF303CA7151F9F8FD /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E03547F2FCFE772ACCDA5A0F2861A21 /* GTMAppAuth-umbrella.h in Headers */, + 5D4BB578718CC33E5E14170F3E04B696 /* GTMAppAuth.h in Headers */, + 7887F7AA5B1214BF90AA09CE6CC4AECC /* GTMAppAuthFetcherAuthorization+Keychain.h in Headers */, + BF588BB40FADB1F474BB6D2BB22CAAE1 /* GTMAppAuthFetcherAuthorization.h in Headers */, + E4420DBF08343EA2886691A140BF7C3E /* GTMKeychain.h in Headers */, + AC8E8C869F03F07CB4E0018D956F8386 /* GTMOAuth2KeychainCompatibility.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BF20DD677E68B0FFF096FA87BA4F642E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F3D2632F5565AFA96A15FBA9EAFFF410 /* AppAuth-umbrella.h in Headers */, + F677A4A964B9095056DA905477CE4F3C /* AppAuth.h in Headers */, + EE191018EF34395C61D30D78FED87E45 /* AppAuthCore.h in Headers */, + 408352680097A53851035145970371B1 /* OIDAuthorizationRequest.h in Headers */, + A14710681730656A8EE4F2793773C18B /* OIDAuthorizationResponse.h in Headers */, + 013D945A6EB50B6FC9854DE84D18E44A /* OIDAuthorizationService+IOS.h in Headers */, + EA278C7D9EB69B68733EF68A0C150345 /* OIDAuthorizationService.h in Headers */, + 670E2A31490D13725DFB5CBBF08B12EF /* OIDAuthState+IOS.h in Headers */, + 6B2C401E08F2AC0A87453061CA3E8E2C /* OIDAuthState.h in Headers */, + 082620B4CAFF07381D466C7F54CDC435 /* OIDAuthStateChangeDelegate.h in Headers */, + C760F013C47E323D4C9D3E1FF5E34338 /* OIDAuthStateErrorDelegate.h in Headers */, + 4870D0FB963B09D3D9FAA4E3DC9263FB /* OIDClientMetadataParameters.h in Headers */, + 4480D20BAE2A84682B5DE51F51A5DD4D /* OIDDefines.h in Headers */, + 91907BA379327CACDF19EA39EAD05338 /* OIDEndSessionRequest.h in Headers */, + 3BFE8CD59118C4908BDCF67DA762C118 /* OIDEndSessionResponse.h in Headers */, + AA3BE332B851918B23E03F283868116C /* OIDError.h in Headers */, + 617968B11188B95B6117BE9A51BCC2F7 /* OIDErrorUtilities.h in Headers */, + 83BAF0108579702991C01C961439111A /* OIDExternalUserAgent.h in Headers */, + 487B398FF6DE0468B5E8E4FEBB432327 /* OIDExternalUserAgentCatalyst.h in Headers */, + D87549BF6D8F527CF351E13326FCEF21 /* OIDExternalUserAgentIOS.h in Headers */, + 2808F21DE383D3C9BD0DA03A7B95F6AA /* OIDExternalUserAgentIOSCustomBrowser.h in Headers */, + F598CB205564B44BC8BC6B774DEB1B62 /* OIDExternalUserAgentRequest.h in Headers */, + 85AC70140CF3718AA932C21689BF865A /* OIDExternalUserAgentSession.h in Headers */, + DE23788919FF5136AC4B77F9B29FB4C1 /* OIDFieldMapping.h in Headers */, + 40FC84D2AFB6832635DD9A929E516BAE /* OIDGrantTypes.h in Headers */, + 2B268B1D89DDEE445606464B1105E0F3 /* OIDIDToken.h in Headers */, + BBC602EC86EB71FC4ACD5728C9058E23 /* OIDRegistrationRequest.h in Headers */, + E3B32C12184BD887E6687F73097489FA /* OIDRegistrationResponse.h in Headers */, + 8ACA9C8B657C3C149214E3649860AF76 /* OIDResponseTypes.h in Headers */, + 8955CA20EEA55F5D75CDE87F0CADCF83 /* OIDScopes.h in Headers */, + 7ACD06125D4ECC1EC05EAF68B2DC0C99 /* OIDScopeUtilities.h in Headers */, + AE07D07F98D805B39CAAB4E89F1E87B8 /* OIDServiceConfiguration.h in Headers */, + 8417CFCEA12E96E62CFA4A646672BCCB /* OIDServiceDiscovery.h in Headers */, + 355CF2719234ADB11A1993E2120CC373 /* OIDTokenRequest.h in Headers */, + 321995AB5C5DD22B2DC924BC8F2AF053 /* OIDTokenResponse.h in Headers */, + 0B764CD79758A19F877947F521050A30 /* OIDTokenUtilities.h in Headers */, + 39D4516A2DB8E91D2A1C0CDB79BDCBCE /* OIDURLQueryComponent.h in Headers */, + D4E43B95AA2B76BDEC6B97B152F00463 /* OIDURLSessionProvider.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FB5319D5B9236392A84250F86298EC31 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A1BB5BA455F04F024EE7204993AE5188 /* Pods-mentorship ios-mentorship iosUITests-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 5C642AA10FB29936669CC269F42079C6 /* AppAuth */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9FB8D339D52B52F9DAB7BA9CD4DC84FF /* Build configuration list for PBXNativeTarget "AppAuth" */; + buildPhases = ( + BF20DD677E68B0FFF096FA87BA4F642E /* Headers */, + FA40C5373E6F05613AE4464C1A8C3500 /* Sources */, + 16CF22217BEA907AC6550A5C485822F1 /* Frameworks */, + 789D540271448693859C5D41E90A58D6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppAuth; + productName = AppAuth; + productReference = 3FD1B4C1246D643E9476438C28048FA8 /* AppAuth.framework */; + productType = "com.apple.product-type.framework"; + }; + 76E1B0952CE19EBCE9AF6F103D78195C /* Pods-mentorship iosTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DF23B01EE78FED9F33D13FC5A2F88DDC /* Build configuration list for PBXNativeTarget "Pods-mentorship iosTests" */; + buildPhases = ( + AF0495172B1DCF3E83F1F96FF98530FB /* Headers */, + D4E03B93E0F731C576112111954C3FB0 /* Sources */, + A78E5BE81BADF2BF1190CD4D5BFF614B /* Frameworks */, + 43ED3B3462A59357CE96A59A678DC9CB /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 0D982A1D7F357ABBCBDD0393CAFD1A10 /* PBXTargetDependency */, + ); + name = "Pods-mentorship iosTests"; + productName = "Pods-mentorship iosTests"; + productReference = EE1A963BFF40376C402DCFC8F5CFEB34 /* Pods_mentorship_iosTests.framework */; + productType = "com.apple.product-type.framework"; + }; + 8627999EF1D5E93E13DAFF580DA8CDCF /* GTMAppAuth */ = { + isa = PBXNativeTarget; + buildConfigurationList = 512819EFFDDD399A495CCD1F0590D138 /* Build configuration list for PBXNativeTarget "GTMAppAuth" */; + buildPhases = ( + B988B1E8057F208BF303CA7151F9F8FD /* Headers */, + F058E53D94E8724221F616AF519B0229 /* Sources */, + 072D6922919B2D97BDAF9CFCD9FF59C8 /* Frameworks */, + 68C0932CA3EDF9FF746703A88C8A4ADE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 359F6EDD76137B81044E0AFD2E99782A /* PBXTargetDependency */, + EF9A8E7636B6024678594EDD08A976BC /* PBXTargetDependency */, + ); + name = GTMAppAuth; + productName = GTMAppAuth; + productReference = 6FD122EA0AB9AB2A8FFA5F676C795DF0 /* GTMAppAuth.framework */; + productType = "com.apple.product-type.framework"; + }; + D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */ = { + isa = PBXNativeTarget; + buildConfigurationList = 42C7BA3B903A64485E1086C189729A31 /* Build configuration list for PBXNativeTarget "GTMSessionFetcher" */; + buildPhases = ( + 4ED0E215E12EB40D1306883BC6DB97EA /* Headers */, + E765E73510C0488113AACCBE9B6B1254 /* Sources */, + 98AB3B5AFE6FC96417A7790B3B87727A /* Frameworks */, + B9E1F148707B6CB1D0ABC6EC07A0537A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GTMSessionFetcher; + productName = GTMSessionFetcher; + productReference = C1998E0D8085221AD87F89B614C10E52 /* GTMSessionFetcher.framework */; + productType = "com.apple.product-type.framework"; + }; + D94C94B952E5B99E5CAE638B92E0D039 /* Pods-mentorship ios-mentorship iosUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2F8B0941A47C97BB6D72DDC4E5A4F847 /* Build configuration list for PBXNativeTarget "Pods-mentorship ios-mentorship iosUITests" */; + buildPhases = ( + FB5319D5B9236392A84250F86298EC31 /* Headers */, + E9BECB47070DD5E1568111D7874D6FEB /* Sources */, + 65E7CB4C247125E90090B9CCFE914B70 /* Frameworks */, + 10382B4D747D1141EA542E5D03A38CEE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + AD8A10422FD4FC9ABF5B00130B47B535 /* PBXTargetDependency */, + 174AACF64E64769F0AF40926164C4560 /* PBXTargetDependency */, + 2CB51FD98B4D7C992AABDCB604859E8D /* PBXTargetDependency */, + 34481A5F990D06F48B08F606DED54BC8 /* PBXTargetDependency */, + ); + name = "Pods-mentorship ios-mentorship iosUITests"; + productName = "Pods-mentorship ios-mentorship iosUITests"; + productReference = 6723353B59967A2F8CE5ADF84C4F9434 /* Pods_mentorship_ios_mentorship_iosUITests.framework */; + productType = "com.apple.product-type.framework"; + }; + E669114D21D744114419853D6A306777 /* Pods-mentorship ios */ = { + isa = PBXNativeTarget; + buildConfigurationList = E73FA4029C51E49A76EEC62D43253558 /* Build configuration list for PBXNativeTarget "Pods-mentorship ios" */; + buildPhases = ( + 594AF7C17FFFD6791D6EAB88E0F424FD /* Headers */, + 89B0A95ED7FE80C8D16B52F53E4CC0D5 /* Sources */, + 8D6B9E1BD179EF938832330D32B26D5B /* Frameworks */, + B822AA25D0E908576E0171E60817C15B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 971DF98DE0A09A0D90E62215AEF57B2F /* PBXTargetDependency */, + 0FFD3235ECC22912917DEB39DCE8E61D /* PBXTargetDependency */, + 84680776B0DA0A2741A88D0CB4A61F12 /* PBXTargetDependency */, + AF74AA6CD01E032979CDD016F47029E2 /* PBXTargetDependency */, + ); + name = "Pods-mentorship ios"; + productName = "Pods-mentorship ios"; + productReference = C279CC1E3D7BE35AB42CE83FF5907FB5 /* Pods_mentorship_ios.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1100; + LastUpgradeCheck = 1100; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 10.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + productRefGroup = 9066D3C5B2ACF2E2D48DA4559FCA79A6 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5C642AA10FB29936669CC269F42079C6 /* AppAuth */, + CAD3534FC55B0333104E5117C0A9A324 /* GoogleSignIn */, + 8627999EF1D5E93E13DAFF580DA8CDCF /* GTMAppAuth */, + D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */, + E669114D21D744114419853D6A306777 /* Pods-mentorship ios */, + D94C94B952E5B99E5CAE638B92E0D039 /* Pods-mentorship ios-mentorship iosUITests */, + 76E1B0952CE19EBCE9AF6F103D78195C /* Pods-mentorship iosTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 10382B4D747D1141EA542E5D03A38CEE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43ED3B3462A59357CE96A59A678DC9CB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 68C0932CA3EDF9FF746703A88C8A4ADE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 789D540271448693859C5D41E90A58D6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B822AA25D0E908576E0171E60817C15B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B9E1F148707B6CB1D0ABC6EC07A0537A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 89B0A95ED7FE80C8D16B52F53E4CC0D5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1C885217C92D9FFF2AC1765CBB300E4C /* Pods-mentorship ios-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D4E03B93E0F731C576112111954C3FB0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5D8C1D8B1B94C05E1209CFADC4CBA523 /* Pods-mentorship iosTests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E765E73510C0488113AACCBE9B6B1254 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0725D5B07457983CE2485610920F40F0 /* GTMGatherInputStream.m in Sources */, + 677020069CB1BF19DC94430FFAA4DE64 /* GTMMIMEDocument.m in Sources */, + 8390E44AF1A51804ECC9B7DAEB30F30F /* GTMReadMonitorInputStream.m in Sources */, + 7C15A5740A2F9022423ED5583A2D578B /* GTMSessionFetcher-dummy.m in Sources */, + 19A26781E567CE6B5034148C70558904 /* GTMSessionFetcher.m in Sources */, + F6C1C1A047487F90686267300F19F1B9 /* GTMSessionFetcherLogging.m in Sources */, + B714634348FC636AD9B6AAB61EEB7778 /* GTMSessionFetcherService.m in Sources */, + A75213FD8D0028674022934581BEC725 /* GTMSessionUploadFetcher.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E9BECB47070DD5E1568111D7874D6FEB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A793266E8EBA0902699FB04A0BBFD79 /* Pods-mentorship ios-mentorship iosUITests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F058E53D94E8724221F616AF519B0229 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3C8B38097E676E0CAD4C9EDCAD691C9D /* GTMAppAuth-dummy.m in Sources */, + 97185747EA1A3BB7552CB5A802B56757 /* GTMAppAuthFetcherAuthorization+Keychain.m in Sources */, + B35F09AAE81C4CCCB215BF097DD570D2 /* GTMAppAuthFetcherAuthorization.m in Sources */, + 75E674393E820798B5C4A708746E4E49 /* GTMKeychain_iOS.m in Sources */, + D901F6867BD4353BECC4F396BCD31533 /* GTMOAuth2KeychainCompatibility.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FA40C5373E6F05613AE4464C1A8C3500 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E23750FE124E07AFF21DB9AC9542BCE /* AppAuth-dummy.m in Sources */, + C359863B7FF01F6B19CDA89D30126EE6 /* OIDAuthorizationRequest.m in Sources */, + 075E34225D4CE71C2F2FC63FB9CE0CA3 /* OIDAuthorizationResponse.m in Sources */, + A1AF5BF63FEEA98CB2D165039170E6F7 /* OIDAuthorizationService+IOS.m in Sources */, + C23F697B76C8D3242F232D958E0C00E3 /* OIDAuthorizationService.m in Sources */, + 152481DAFF8FA47F61BB412784DF31E9 /* OIDAuthState+IOS.m in Sources */, + 06953BA616880F876D31CECCEDC11105 /* OIDAuthState.m in Sources */, + 16F96D0B2EE7EBED059FF327B8E90E5E /* OIDClientMetadataParameters.m in Sources */, + D90A3C067FE031487AD6F53979803AAA /* OIDEndSessionRequest.m in Sources */, + F63C85CE50933EE6BDB86F9C87253F1C /* OIDEndSessionResponse.m in Sources */, + 2D8D67C396A0A1A33320AB9924E9DB42 /* OIDError.m in Sources */, + 2095003DC22D3D86A334AA1A1561900D /* OIDErrorUtilities.m in Sources */, + 0A83C33151A74978B5009466B16AA320 /* OIDExternalUserAgentCatalyst.m in Sources */, + 2B9DE014CBDC27683B204852475EB8A6 /* OIDExternalUserAgentIOS.m in Sources */, + 79117048A3C4F245532CAE57EE37901A /* OIDExternalUserAgentIOSCustomBrowser.m in Sources */, + 5C43E64B8DFCE99DD92F62D538C0DFF0 /* OIDFieldMapping.m in Sources */, + F246177F1215CDBE9AB3B1BB58624C04 /* OIDGrantTypes.m in Sources */, + F47BF5598DBEDEB0A44679023AFC7E66 /* OIDIDToken.m in Sources */, + B00AF14E70C26C9D19EDB3AB4F04123E /* OIDRegistrationRequest.m in Sources */, + EE329DF01337A5B2040D3D7532BEB766 /* OIDRegistrationResponse.m in Sources */, + 22BA02A9AF9DDB1A405D82C84953B295 /* OIDResponseTypes.m in Sources */, + 705E22C52493D0AEDE135873802B54E0 /* OIDScopes.m in Sources */, + 17587A84F44BB3F9BBE44E3F0BF91929 /* OIDScopeUtilities.m in Sources */, + A837179BAD5CC1FFC6BFAC2C5C67E334 /* OIDServiceConfiguration.m in Sources */, + 2C034AC52FF9090141709228E7A67121 /* OIDServiceDiscovery.m in Sources */, + 3F609DF53628B2F834F882C40F39C99C /* OIDTokenRequest.m in Sources */, + 3989F5BDEC4A21202F727FB35ECB9E4D /* OIDTokenResponse.m in Sources */, + 95D660DE029B3057EF7922C44E75B6FF /* OIDTokenUtilities.m in Sources */, + 218BA22AD876AB6E19146F5A9ABC8F5E /* OIDURLQueryComponent.m in Sources */, + 86C6E9D09DD9A2F765ED9CF5F981560C /* OIDURLSessionProvider.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0D982A1D7F357ABBCBDD0393CAFD1A10 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "Pods-mentorship ios"; + target = E669114D21D744114419853D6A306777 /* Pods-mentorship ios */; + targetProxy = 380F79DB94E2732DF6F327B9142D4189 /* PBXContainerItemProxy */; + }; + 0FFD3235ECC22912917DEB39DCE8E61D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMAppAuth; + target = 8627999EF1D5E93E13DAFF580DA8CDCF /* GTMAppAuth */; + targetProxy = 26DE710C83D26F0E3253DA99DD52D8F2 /* PBXContainerItemProxy */; + }; + 174AACF64E64769F0AF40926164C4560 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMAppAuth; + target = 8627999EF1D5E93E13DAFF580DA8CDCF /* GTMAppAuth */; + targetProxy = 69653A44A453E231003B2B78EACD2801 /* PBXContainerItemProxy */; + }; + 2857566728641F74273DD7DA9E370771 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = AppAuth; + target = 5C642AA10FB29936669CC269F42079C6 /* AppAuth */; + targetProxy = 96C4B52CEBD25FE7612CDFAC4EBB56D6 /* PBXContainerItemProxy */; + }; + 2CB51FD98B4D7C992AABDCB604859E8D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMSessionFetcher; + target = D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */; + targetProxy = 786BDA01217A80D3A27F97A9E29C36AB /* PBXContainerItemProxy */; + }; + 314A9E36FBEA4C73491669F15EA30E5F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMAppAuth; + target = 8627999EF1D5E93E13DAFF580DA8CDCF /* GTMAppAuth */; + targetProxy = CFBE3CB2F286EF0D95A2ACDEBE0C1014 /* PBXContainerItemProxy */; + }; + 34481A5F990D06F48B08F606DED54BC8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleSignIn; + target = CAD3534FC55B0333104E5117C0A9A324 /* GoogleSignIn */; + targetProxy = 01AC7A177511421386583884F07403DD /* PBXContainerItemProxy */; + }; + 359F6EDD76137B81044E0AFD2E99782A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = AppAuth; + target = 5C642AA10FB29936669CC269F42079C6 /* AppAuth */; + targetProxy = 0EDCF38BA44E7804550420B1923AE7C0 /* PBXContainerItemProxy */; + }; + 74F3D34B7F14020AE0A57D83A7889F98 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMSessionFetcher; + target = D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */; + targetProxy = 255C113C5B8CEBDE5DFD4788BBC6FC4B /* PBXContainerItemProxy */; + }; + 84680776B0DA0A2741A88D0CB4A61F12 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMSessionFetcher; + target = D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */; + targetProxy = D3B98C3FD3F5938B890DF7680BBB7E42 /* PBXContainerItemProxy */; + }; + 971DF98DE0A09A0D90E62215AEF57B2F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = AppAuth; + target = 5C642AA10FB29936669CC269F42079C6 /* AppAuth */; + targetProxy = E8554359B2E0525707CC4735C3DFE1CE /* PBXContainerItemProxy */; + }; + AD8A10422FD4FC9ABF5B00130B47B535 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = AppAuth; + target = 5C642AA10FB29936669CC269F42079C6 /* AppAuth */; + targetProxy = 2F6F337FCAEA9A42D506A040A3DF35B8 /* PBXContainerItemProxy */; + }; + AF74AA6CD01E032979CDD016F47029E2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GoogleSignIn; + target = CAD3534FC55B0333104E5117C0A9A324 /* GoogleSignIn */; + targetProxy = 9C295E8BC77DF498D6BDAD99C4F660FF /* PBXContainerItemProxy */; + }; + EF9A8E7636B6024678594EDD08A976BC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMSessionFetcher; + target = D676E21115185671D7258A56944ABE98 /* GTMSessionFetcher */; + targetProxy = A720C631E1946701F927ADC25CF2A59E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 05151936F291832ED2DC420B9357812D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 38E47E42BE9D59EA60D119AF99378190 /* Pods-mentorship iosTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 0EFD4BD48160F40F3EE3D90F31FEE3BB /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D04E76110A30D474F45397417CF66599 /* GTMAppAuth.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMAppAuth/GTMAppAuth-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/GTMAppAuth/GTMAppAuth.modulemap"; + PRODUCT_MODULE_NAME = GTMAppAuth; + PRODUCT_NAME = GTMAppAuth; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 2D4F131D012BEB7D41559F457CFD7CCE /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5F96B3A022E0040FBB3194E3231BBF9B /* GTMSessionFetcher.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap"; + PRODUCT_MODULE_NAME = GTMSessionFetcher; + PRODUCT_NAME = GTMSessionFetcher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 3BFB49A0C652DF4A91F6D35EE641F051 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B01846C50A6E49C262464A2FBE59F1E9 /* AppAuth.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/AppAuth/AppAuth-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/AppAuth/AppAuth-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/AppAuth/AppAuth.modulemap"; + PRODUCT_MODULE_NAME = AppAuth; + PRODUCT_NAME = AppAuth; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 57A09D1AFDF0C5A690229235D8E8D6A3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 30A1D65779BE9FA691AEBD03B5455BBB /* Pods-mentorship ios.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-mentorship ios/Pods-mentorship ios-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-mentorship ios/Pods-mentorship ios.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 67F8D4F8DD26D5EC07F205DCD12199FD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 23E453339F50955000DE65F30282EF6F /* GoogleSignIn.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 725AF7BEC3A726CB2863C829AA3EFF70 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 26108ECE6955E499A7A690C9E6FC537D /* GoogleSignIn.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 75D8B3092EA8147D11970DCB2E160C57 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 104D3E9636F5950B3E5E52D45BE250BE /* Pods-mentorship ios-mentorship iosUITests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9D8447784477E8922A7D86AAF264CAC1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3328F331E40151217475F5186FA7E7A7 /* GTMAppAuth.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMAppAuth/GTMAppAuth-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/GTMAppAuth/GTMAppAuth.modulemap"; + PRODUCT_MODULE_NAME = GTMAppAuth; + PRODUCT_NAME = GTMAppAuth; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + B5256F5749EC31101A1D20A738FC66A5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 60726EAF3A48B3A5DCFD505280476615 /* Pods-mentorship iosTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C24F2BDA2FB974249C52EF121C1D13CC /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B1C0F3D9E0A9BFF24C88E6E03E929131 /* Pods-mentorship ios-mentorship iosUITests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C73AD503AEFF4C5DD1901708DC6D9640 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F30665C00BF13BFADDD8BECDF2C02D31 /* GTMSessionFetcher.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap"; + PRODUCT_MODULE_NAME = GTMSessionFetcher; + PRODUCT_NAME = GTMSessionFetcher; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + CEED3BE9D9BBFC1B5BE09AA928ADAB77 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 917B0E1C097160C75E249E040413F058 /* AppAuth.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/AppAuth/AppAuth-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/AppAuth/AppAuth-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/AppAuth/AppAuth.modulemap"; + PRODUCT_MODULE_NAME = AppAuth; + PRODUCT_NAME = AppAuth; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + DFD9D19FEEBF98DE271D7FBABCB845B5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + E7BB2277FCA594B333AE0E87CBFC628D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 534513D6AB6E325748777DB2920D6D37 /* Pods-mentorship ios.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 4K84MJ7D8T; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-mentorship ios/Pods-mentorship ios-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-mentorship ios/Pods-mentorship ios.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + F5F04600F5626DC980F02790D1ED2C19 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2F8B0941A47C97BB6D72DDC4E5A4F847 /* Build configuration list for PBXNativeTarget "Pods-mentorship ios-mentorship iosUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 75D8B3092EA8147D11970DCB2E160C57 /* Debug */, + C24F2BDA2FB974249C52EF121C1D13CC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 42C7BA3B903A64485E1086C189729A31 /* Build configuration list for PBXNativeTarget "GTMSessionFetcher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C73AD503AEFF4C5DD1901708DC6D9640 /* Debug */, + 2D4F131D012BEB7D41559F457CFD7CCE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DFD9D19FEEBF98DE271D7FBABCB845B5 /* Debug */, + F5F04600F5626DC980F02790D1ED2C19 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 512819EFFDDD399A495CCD1F0590D138 /* Build configuration list for PBXNativeTarget "GTMAppAuth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0EFD4BD48160F40F3EE3D90F31FEE3BB /* Debug */, + 9D8447784477E8922A7D86AAF264CAC1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9FB8D339D52B52F9DAB7BA9CD4DC84FF /* Build configuration list for PBXNativeTarget "AppAuth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3BFB49A0C652DF4A91F6D35EE641F051 /* Debug */, + CEED3BE9D9BBFC1B5BE09AA928ADAB77 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B0030D626B503D8EB0EA00BB772D6396 /* Build configuration list for PBXAggregateTarget "GoogleSignIn" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 67F8D4F8DD26D5EC07F205DCD12199FD /* Debug */, + 725AF7BEC3A726CB2863C829AA3EFF70 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DF23B01EE78FED9F33D13FC5A2F88DDC /* Build configuration list for PBXNativeTarget "Pods-mentorship iosTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B5256F5749EC31101A1D20A738FC66A5 /* Debug */, + 05151936F291832ED2DC420B9357812D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E73FA4029C51E49A76EEC62D43253558 /* Build configuration list for PBXNativeTarget "Pods-mentorship ios" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E7BB2277FCA594B333AE0E87CBFC628D /* Debug */, + 57A09D1AFDF0C5A690229235D8E8D6A3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git a/Pods/Target Support Files/AppAuth/AppAuth-Info.plist b/Pods/Target Support Files/AppAuth/AppAuth-Info.plist new file mode 100644 index 00000000..7b6b52a4 --- /dev/null +++ b/Pods/Target Support Files/AppAuth/AppAuth-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.4.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/AppAuth/AppAuth-dummy.m b/Pods/Target Support Files/AppAuth/AppAuth-dummy.m new file mode 100644 index 00000000..0f45668e --- /dev/null +++ b/Pods/Target Support Files/AppAuth/AppAuth-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_AppAuth : NSObject +@end +@implementation PodsDummy_AppAuth +@end diff --git a/Pods/Target Support Files/AppAuth/AppAuth-prefix.pch b/Pods/Target Support Files/AppAuth/AppAuth-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Pods/Target Support Files/AppAuth/AppAuth-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/AppAuth/AppAuth-umbrella.h b/Pods/Target Support Files/AppAuth/AppAuth-umbrella.h new file mode 100644 index 00000000..7e9c5909 --- /dev/null +++ b/Pods/Target Support Files/AppAuth/AppAuth-umbrella.h @@ -0,0 +1,84 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "AppAuthCore.h" +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDAuthorizationService.h" +#import "OIDAuthState.h" +#import "OIDAuthStateChangeDelegate.h" +#import "OIDAuthStateErrorDelegate.h" +#import "OIDClientMetadataParameters.h" +#import "OIDDefines.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" +#import "OIDError.h" +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentRequest.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDFieldMapping.h" +#import "OIDGrantTypes.h" +#import "OIDIDToken.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDResponseTypes.h" +#import "OIDScopes.h" +#import "OIDScopeUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDTokenUtilities.h" +#import "OIDURLQueryComponent.h" +#import "OIDURLSessionProvider.h" +#import "AppAuth.h" +#import "AppAuthCore.h" +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDAuthorizationService.h" +#import "OIDAuthState.h" +#import "OIDAuthStateChangeDelegate.h" +#import "OIDAuthStateErrorDelegate.h" +#import "OIDClientMetadataParameters.h" +#import "OIDDefines.h" +#import "OIDEndSessionRequest.h" +#import "OIDEndSessionResponse.h" +#import "OIDError.h" +#import "OIDErrorUtilities.h" +#import "OIDExternalUserAgent.h" +#import "OIDExternalUserAgentRequest.h" +#import "OIDExternalUserAgentSession.h" +#import "OIDFieldMapping.h" +#import "OIDGrantTypes.h" +#import "OIDIDToken.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDResponseTypes.h" +#import "OIDScopes.h" +#import "OIDScopeUtilities.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDTokenUtilities.h" +#import "OIDURLQueryComponent.h" +#import "OIDURLSessionProvider.h" +#import "OIDAuthorizationService+IOS.h" +#import "OIDAuthState+IOS.h" +#import "OIDExternalUserAgentCatalyst.h" +#import "OIDExternalUserAgentIOS.h" +#import "OIDExternalUserAgentIOSCustomBrowser.h" + +FOUNDATION_EXPORT double AppAuthVersionNumber; +FOUNDATION_EXPORT const unsigned char AppAuthVersionString[]; + diff --git a/Pods/Target Support Files/AppAuth/AppAuth.debug.xcconfig b/Pods/Target Support Files/AppAuth/AppAuth.debug.xcconfig new file mode 100644 index 00000000..47199e55 --- /dev/null +++ b/Pods/Target Support Files/AppAuth/AppAuth.debug.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/AppAuth +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "SafariServices" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/AppAuth +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/AppAuth/AppAuth.modulemap b/Pods/Target Support Files/AppAuth/AppAuth.modulemap new file mode 100644 index 00000000..9299d0a3 --- /dev/null +++ b/Pods/Target Support Files/AppAuth/AppAuth.modulemap @@ -0,0 +1,6 @@ +framework module AppAuth { + umbrella header "AppAuth-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/AppAuth/AppAuth.release.xcconfig b/Pods/Target Support Files/AppAuth/AppAuth.release.xcconfig new file mode 100644 index 00000000..47199e55 --- /dev/null +++ b/Pods/Target Support Files/AppAuth/AppAuth.release.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/AppAuth +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "SafariServices" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/AppAuth +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-Info.plist b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-Info.plist new file mode 100644 index 00000000..2243fe6e --- /dev/null +++ b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-dummy.m b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-dummy.m new file mode 100644 index 00000000..04b91f31 --- /dev/null +++ b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_GTMAppAuth : NSObject +@end +@implementation PodsDummy_GTMAppAuth +@end diff --git a/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-umbrella.h b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-umbrella.h new file mode 100644 index 00000000..4ab5f86a --- /dev/null +++ b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-umbrella.h @@ -0,0 +1,21 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "GTMAppAuth.h" +#import "GTMAppAuthFetcherAuthorization+Keychain.h" +#import "GTMAppAuthFetcherAuthorization.h" +#import "GTMKeychain.h" +#import "GTMOAuth2KeychainCompatibility.h" + +FOUNDATION_EXPORT double GTMAppAuthVersionNumber; +FOUNDATION_EXPORT const unsigned char GTMAppAuthVersionString[]; + diff --git a/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.debug.xcconfig b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.debug.xcconfig new file mode 100644 index 00000000..217cc41a --- /dev/null +++ b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.debug.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GTMAppAuth +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.modulemap b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.modulemap new file mode 100644 index 00000000..b23c8d07 --- /dev/null +++ b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.modulemap @@ -0,0 +1,6 @@ +framework module GTMAppAuth { + umbrella header "GTMAppAuth-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.release.xcconfig b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.release.xcconfig new file mode 100644 index 00000000..217cc41a --- /dev/null +++ b/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.release.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GTMAppAuth +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist new file mode 100644 index 00000000..7b6b52a4 --- /dev/null +++ b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.4.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-dummy.m b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-dummy.m new file mode 100644 index 00000000..13d68b3f --- /dev/null +++ b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_GTMSessionFetcher : NSObject +@end +@implementation PodsDummy_GTMSessionFetcher +@end diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch new file mode 100644 index 00000000..beb2a244 --- /dev/null +++ b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-umbrella.h b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-umbrella.h new file mode 100644 index 00000000..b9e0b3de --- /dev/null +++ b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-umbrella.h @@ -0,0 +1,23 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "GTMSessionFetcher.h" +#import "GTMSessionFetcherLogging.h" +#import "GTMSessionFetcherService.h" +#import "GTMSessionUploadFetcher.h" +#import "GTMGatherInputStream.h" +#import "GTMMIMEDocument.h" +#import "GTMReadMonitorInputStream.h" + +FOUNDATION_EXPORT double GTMSessionFetcherVersionNumber; +FOUNDATION_EXPORT const unsigned char GTMSessionFetcherVersionString[]; + diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.debug.xcconfig b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.debug.xcconfig new file mode 100644 index 00000000..af4ab71f --- /dev/null +++ b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.debug.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Security" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GTMSessionFetcher +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap new file mode 100644 index 00000000..5121a4dc --- /dev/null +++ b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap @@ -0,0 +1,6 @@ +framework module GTMSessionFetcher { + umbrella header "GTMSessionFetcher-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.release.xcconfig b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.release.xcconfig new file mode 100644 index 00000000..af4ab71f --- /dev/null +++ b/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.release.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Security" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GTMSessionFetcher +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/GoogleSignIn/GoogleSignIn.debug.xcconfig b/Pods/Target Support Files/GoogleSignIn/GoogleSignIn.debug.xcconfig new file mode 100644 index 00000000..889b0187 --- /dev/null +++ b/Pods/Target Support Files/GoogleSignIn/GoogleSignIn.debug.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_ROOT}/GoogleSignIn/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "CoreText" -framework "Foundation" -framework "LocalAuthentication" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleSignIn +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/GoogleSignIn/GoogleSignIn.release.xcconfig b/Pods/Target Support Files/GoogleSignIn/GoogleSignIn.release.xcconfig new file mode 100644 index 00000000..889b0187 --- /dev/null +++ b/Pods/Target Support Files/GoogleSignIn/GoogleSignIn.release.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_ROOT}/GoogleSignIn/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "CoreText" -framework "Foundation" -framework "LocalAuthentication" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/GoogleSignIn +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-Info.plist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-Info.plist new file mode 100644 index 00000000..2243fe6e --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-acknowledgements.markdown b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-acknowledgements.markdown new file mode 100644 index 00000000..1a67fe47 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-acknowledgements.markdown @@ -0,0 +1,625 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## AppAuth + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +## GTMAppAuth + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +## GTMSessionFetcher + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +## GoogleSignIn + +Copyright 2019 Google +Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-acknowledgements.plist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-acknowledgements.plist new file mode 100644 index 00000000..e4bf7bb8 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-acknowledgements.plist @@ -0,0 +1,675 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + License + Apache License, Version 2.0 + Title + AppAuth + Type + PSGroupSpecifier + + + FooterText + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + License + Apache License, Version 2.0 + Title + GTMAppAuth + Type + PSGroupSpecifier + + + FooterText + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + License + Apache + Title + GTMSessionFetcher + Type + PSGroupSpecifier + + + FooterText + Copyright 2019 Google + License + Copyright + Title + GoogleSignIn + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-dummy.m b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-dummy.m new file mode 100644 index 00000000..5a050b72 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_mentorship_ios_mentorship_iosUITests : NSObject +@end +@implementation PodsDummy_Pods_mentorship_ios_mentorship_iosUITests +@end diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Debug-input-files.xcfilelist new file mode 100644 index 00000000..6f954221 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Debug-input-files.xcfilelist @@ -0,0 +1,4 @@ +${PODS_ROOT}/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks.sh +${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework +${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework +${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Debug-output-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Debug-output-files.xcfilelist new file mode 100644 index 00000000..23d1c4d4 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Debug-output-files.xcfilelist @@ -0,0 +1,3 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMAppAuth.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Release-input-files.xcfilelist new file mode 100644 index 00000000..6f954221 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Release-input-files.xcfilelist @@ -0,0 +1,4 @@ +${PODS_ROOT}/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks.sh +${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework +${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework +${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Release-output-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Release-output-files.xcfilelist new file mode 100644 index 00000000..23d1c4d4 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks-Release-output-files.xcfilelist @@ -0,0 +1,3 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMAppAuth.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks.sh b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks.sh new file mode 100755 index 00000000..906bef66 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-frameworks.sh @@ -0,0 +1,211 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then + # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy + # frameworks to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +# Copies and strips a vendored framework +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + # Use filter instead of exclude so missing patterns don't throw errors. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + elif [ -L "${binary}" ]; then + echo "Destination binary is symlinked..." + dirname="$(dirname "${binary}")" + binary="${dirname}/$(readlink "${binary}")" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} + +# Copies and strips a vendored dSYM +install_dsym() { + local source="$1" + warn_missing_arch=${2:-true} + if [ -r "$source" ]; then + # Copy the dSYM into the targets temp dir. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" + + local basename + basename="$(basename -s .dSYM "$source")" + binary_name="$(ls "$source/Contents/Resources/DWARF")" + binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then + strip_invalid_archs "$binary" "$warn_missing_arch" + fi + + if [[ $STRIP_BINARY_RETVAL == 1 ]]; then + # Move the stripped file into its final destination. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" + else + # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. + touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" + fi + fi +} + +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" + + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + warn_missing_arch=${2:-true} + # Get architectures for current target binary + binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" + # Intersect them with the architectures we are building for + intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" + # If there are no archs supported by this binary then warn the user + if [[ -z "$intersected_archs" ]]; then + if [[ "$warn_missing_arch" == "true" ]]; then + echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." + fi + STRIP_BINARY_RETVAL=0 + return + fi + stripped="" + for arch in $binary_archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi + STRIP_BINARY_RETVAL=1 +} + +install_artifact() { + artifact="$1" + base="$(basename "$artifact")" + case $base in + *.framework) + install_framework "$artifact" + ;; + *.dSYM) + # Suppress arch warnings since XCFrameworks will include many dSYM files + install_dsym "$artifact" "false" + ;; + *.bcsymbolmap) + install_bcsymbolmap "$artifact" + ;; + *) + echo "error: Unrecognized artifact "$artifact"" + ;; + esac +} + +copy_artifacts() { + file_list="$1" + while read artifact; do + install_artifact "$artifact" + done <$file_list +} + +ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt" +if [ -r "${ARTIFACT_LIST_FILE}" ]; then + copy_artifacts "${ARTIFACT_LIST_FILE}" +fi + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework" + install_framework "${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework" + install_framework "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework" + install_framework "${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework" + install_framework "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework" +fi +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Debug-input-files.xcfilelist new file mode 100644 index 00000000..b0c2385d --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Debug-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources.sh +${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Debug-output-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Debug-output-files.xcfilelist new file mode 100644 index 00000000..03d868ad --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Debug-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Release-input-files.xcfilelist new file mode 100644 index 00000000..b0c2385d --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Release-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources.sh +${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Release-output-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Release-output-files.xcfilelist new file mode 100644 index 00000000..03d868ad --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources-Release-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources.sh b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources.sh new file mode 100755 index 00000000..af0641bf --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-resources.sh @@ -0,0 +1,129 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then + # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy + # resources to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + +RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt +> "$RESOURCES_TO_COPY" + +XCASSET_FILES=() + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +case "${TARGETED_DEVICE_FAMILY:-}" in + 1,2) + TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" + ;; + 1) + TARGET_DEVICE_ARGS="--target-device iphone" + ;; + 2) + TARGET_DEVICE_ARGS="--target-device ipad" + ;; + 3) + TARGET_DEVICE_ARGS="--target-device tv" + ;; + 4) + TARGET_DEVICE_ARGS="--target-device watch" + ;; + *) + TARGET_DEVICE_ARGS="--target-device mac" + ;; +esac + +install_resource() +{ + if [[ "$1" = /* ]] ; then + RESOURCE_PATH="$1" + else + RESOURCE_PATH="${PODS_ROOT}/$1" + fi + if [[ ! -e "$RESOURCE_PATH" ]] ; then + cat << EOM +error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. +EOM + exit 1 + fi + case $RESOURCE_PATH in + *.storyboard) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.xib) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.framework) + echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + ;; + *.xcdatamodel) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" + ;; + *.xcdatamodeld) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" + ;; + *.xcmappingmodel) + echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true + xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" + ;; + *.xcassets) + ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" + XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") + ;; + *) + echo "$RESOURCE_PATH" || true + echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" + ;; + esac +} +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_resource "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_resource "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle" +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then + mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi +rm -f "$RESOURCES_TO_COPY" + +if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] +then + # Find all other xcassets (this unfortunately includes those of path pods and other targets). + OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "${PODS_ROOT}*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + else + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" + fi +fi diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-umbrella.h b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-umbrella.h new file mode 100644 index 00000000..8c1c6cf8 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_mentorship_ios_mentorship_iosUITestsVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_mentorship_ios_mentorship_iosUITestsVersionString[]; + diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.debug.xcconfig b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.debug.xcconfig new file mode 100644 index 00000000..680fe412 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.debug.xcconfig @@ -0,0 +1,10 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_ROOT}/GoogleSignIn/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth/AppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth/GTMAppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -ObjC -framework "AppAuth" -framework "CoreGraphics" -framework "CoreText" -framework "Foundation" -framework "GTMAppAuth" -framework "GTMSessionFetcher" -framework "GoogleSignIn" -framework "LocalAuthentication" -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.modulemap b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.modulemap new file mode 100644 index 00000000..70bc41bc --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.modulemap @@ -0,0 +1,6 @@ +framework module Pods_mentorship_ios_mentorship_iosUITests { + umbrella header "Pods-mentorship ios-mentorship iosUITests-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.release.xcconfig b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.release.xcconfig new file mode 100644 index 00000000..680fe412 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.release.xcconfig @@ -0,0 +1,10 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_ROOT}/GoogleSignIn/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth/AppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth/GTMAppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -ObjC -framework "AppAuth" -framework "CoreGraphics" -framework "CoreText" -framework "Foundation" -framework "GTMAppAuth" -framework "GTMSessionFetcher" -framework "GoogleSignIn" -framework "LocalAuthentication" -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-Info.plist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-Info.plist new file mode 100644 index 00000000..2243fe6e --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-acknowledgements.markdown b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-acknowledgements.markdown new file mode 100644 index 00000000..1a67fe47 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-acknowledgements.markdown @@ -0,0 +1,625 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## AppAuth + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +## GTMAppAuth + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +## GTMSessionFetcher + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + +## GoogleSignIn + +Copyright 2019 Google +Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-acknowledgements.plist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-acknowledgements.plist new file mode 100644 index 00000000..e4bf7bb8 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-acknowledgements.plist @@ -0,0 +1,675 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + License + Apache License, Version 2.0 + Title + AppAuth + Type + PSGroupSpecifier + + + FooterText + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + License + Apache License, Version 2.0 + Title + GTMAppAuth + Type + PSGroupSpecifier + + + FooterText + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + + License + Apache + Title + GTMSessionFetcher + Type + PSGroupSpecifier + + + FooterText + Copyright 2019 Google + License + Copyright + Title + GoogleSignIn + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-dummy.m b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-dummy.m new file mode 100644 index 00000000..47bb4c0d --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_mentorship_ios : NSObject +@end +@implementation PodsDummy_Pods_mentorship_ios +@end diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Debug-input-files.xcfilelist new file mode 100644 index 00000000..c20c5f2b --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Debug-input-files.xcfilelist @@ -0,0 +1,4 @@ +${PODS_ROOT}/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks.sh +${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework +${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework +${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Debug-output-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Debug-output-files.xcfilelist new file mode 100644 index 00000000..23d1c4d4 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Debug-output-files.xcfilelist @@ -0,0 +1,3 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMAppAuth.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Release-input-files.xcfilelist new file mode 100644 index 00000000..c20c5f2b --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Release-input-files.xcfilelist @@ -0,0 +1,4 @@ +${PODS_ROOT}/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks.sh +${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework +${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework +${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Release-output-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Release-output-files.xcfilelist new file mode 100644 index 00000000..23d1c4d4 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks-Release-output-files.xcfilelist @@ -0,0 +1,3 @@ +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMAppAuth.framework +${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks.sh b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks.sh new file mode 100755 index 00000000..906bef66 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-frameworks.sh @@ -0,0 +1,211 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then + # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy + # frameworks to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +# Copies and strips a vendored framework +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + # Use filter instead of exclude so missing patterns don't throw errors. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + elif [ -L "${binary}" ]; then + echo "Destination binary is symlinked..." + dirname="$(dirname "${binary}")" + binary="${dirname}/$(readlink "${binary}")" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} + +# Copies and strips a vendored dSYM +install_dsym() { + local source="$1" + warn_missing_arch=${2:-true} + if [ -r "$source" ]; then + # Copy the dSYM into the targets temp dir. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" + + local basename + basename="$(basename -s .dSYM "$source")" + binary_name="$(ls "$source/Contents/Resources/DWARF")" + binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then + strip_invalid_archs "$binary" "$warn_missing_arch" + fi + + if [[ $STRIP_BINARY_RETVAL == 1 ]]; then + # Move the stripped file into its final destination. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" + else + # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. + touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" + fi + fi +} + +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" + + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + warn_missing_arch=${2:-true} + # Get architectures for current target binary + binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" + # Intersect them with the architectures we are building for + intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" + # If there are no archs supported by this binary then warn the user + if [[ -z "$intersected_archs" ]]; then + if [[ "$warn_missing_arch" == "true" ]]; then + echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." + fi + STRIP_BINARY_RETVAL=0 + return + fi + stripped="" + for arch in $binary_archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi + STRIP_BINARY_RETVAL=1 +} + +install_artifact() { + artifact="$1" + base="$(basename "$artifact")" + case $base in + *.framework) + install_framework "$artifact" + ;; + *.dSYM) + # Suppress arch warnings since XCFrameworks will include many dSYM files + install_dsym "$artifact" "false" + ;; + *.bcsymbolmap) + install_bcsymbolmap "$artifact" + ;; + *) + echo "error: Unrecognized artifact "$artifact"" + ;; + esac +} + +copy_artifacts() { + file_list="$1" + while read artifact; do + install_artifact "$artifact" + done <$file_list +} + +ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt" +if [ -r "${ARTIFACT_LIST_FILE}" ]; then + copy_artifacts "${ARTIFACT_LIST_FILE}" +fi + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework" + install_framework "${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework" + install_framework "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework" + install_framework "${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework" + install_framework "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework" +fi +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Debug-input-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Debug-input-files.xcfilelist new file mode 100644 index 00000000..7a75b8c8 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Debug-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources.sh +${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Debug-output-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Debug-output-files.xcfilelist new file mode 100644 index 00000000..03d868ad --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Debug-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Release-input-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Release-input-files.xcfilelist new file mode 100644 index 00000000..7a75b8c8 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Release-input-files.xcfilelist @@ -0,0 +1,2 @@ +${PODS_ROOT}/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources.sh +${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Release-output-files.xcfilelist b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Release-output-files.xcfilelist new file mode 100644 index 00000000..03d868ad --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources-Release-output-files.xcfilelist @@ -0,0 +1 @@ +${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle \ No newline at end of file diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources.sh b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources.sh new file mode 100755 index 00000000..af0641bf --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-resources.sh @@ -0,0 +1,129 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then + # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy + # resources to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + +RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt +> "$RESOURCES_TO_COPY" + +XCASSET_FILES=() + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +case "${TARGETED_DEVICE_FAMILY:-}" in + 1,2) + TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" + ;; + 1) + TARGET_DEVICE_ARGS="--target-device iphone" + ;; + 2) + TARGET_DEVICE_ARGS="--target-device ipad" + ;; + 3) + TARGET_DEVICE_ARGS="--target-device tv" + ;; + 4) + TARGET_DEVICE_ARGS="--target-device watch" + ;; + *) + TARGET_DEVICE_ARGS="--target-device mac" + ;; +esac + +install_resource() +{ + if [[ "$1" = /* ]] ; then + RESOURCE_PATH="$1" + else + RESOURCE_PATH="${PODS_ROOT}/$1" + fi + if [[ ! -e "$RESOURCE_PATH" ]] ; then + cat << EOM +error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script. +EOM + exit 1 + fi + case $RESOURCE_PATH in + *.storyboard) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.xib) + echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true + ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS} + ;; + *.framework) + echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + ;; + *.xcdatamodel) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom" + ;; + *.xcdatamodeld) + echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true + xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd" + ;; + *.xcmappingmodel) + echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true + xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm" + ;; + *.xcassets) + ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH" + XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") + ;; + *) + echo "$RESOURCE_PATH" || true + echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY" + ;; + esac +} +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_resource "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_resource "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle" +fi + +mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then + mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" +fi +rm -f "$RESOURCES_TO_COPY" + +if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ] +then + # Find all other xcassets (this unfortunately includes those of path pods and other targets). + OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "${PODS_ROOT}*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" + else + printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist" + fi +fi diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-umbrella.h b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-umbrella.h new file mode 100644 index 00000000..e30a2e50 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_mentorship_iosVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_mentorship_iosVersionString[]; + diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.debug.xcconfig b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.debug.xcconfig new file mode 100644 index 00000000..680fe412 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.debug.xcconfig @@ -0,0 +1,10 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_ROOT}/GoogleSignIn/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth/AppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth/GTMAppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -ObjC -framework "AppAuth" -framework "CoreGraphics" -framework "CoreText" -framework "Foundation" -framework "GTMAppAuth" -framework "GTMSessionFetcher" -framework "GoogleSignIn" -framework "LocalAuthentication" -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.modulemap b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.modulemap new file mode 100644 index 00000000..4a04c1f9 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.modulemap @@ -0,0 +1,6 @@ +framework module Pods_mentorship_ios { + umbrella header "Pods-mentorship ios-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.release.xcconfig b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.release.xcconfig new file mode 100644 index 00000000..680fe412 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship ios/Pods-mentorship ios.release.xcconfig @@ -0,0 +1,10 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_ROOT}/GoogleSignIn/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth/AppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth/GTMAppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -ObjC -framework "AppAuth" -framework "CoreGraphics" -framework "CoreText" -framework "Foundation" -framework "GTMAppAuth" -framework "GTMSessionFetcher" -framework "GoogleSignIn" -framework "LocalAuthentication" -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-Info.plist b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-Info.plist new file mode 100644 index 00000000..2243fe6e --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-acknowledgements.markdown b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-acknowledgements.markdown new file mode 100644 index 00000000..102af753 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-acknowledgements.markdown @@ -0,0 +1,3 @@ +# Acknowledgements +This application makes use of the following third party libraries: +Generated by CocoaPods - https://cocoapods.org diff --git a/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-acknowledgements.plist b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-acknowledgements.plist new file mode 100644 index 00000000..7acbad1e --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-acknowledgements.plist @@ -0,0 +1,29 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-dummy.m b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-dummy.m new file mode 100644 index 00000000..ae669c35 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_mentorship_iosTests : NSObject +@end +@implementation PodsDummy_Pods_mentorship_iosTests +@end diff --git a/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-umbrella.h b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-umbrella.h new file mode 100644 index 00000000..36a0e289 --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_mentorship_iosTestsVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_mentorship_iosTestsVersionString[]; + diff --git a/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.debug.xcconfig b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.debug.xcconfig new file mode 100644 index 00000000..a00682aa --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.debug.xcconfig @@ -0,0 +1,9 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_ROOT}/GoogleSignIn/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth/AppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth/GTMAppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "AppAuth" -framework "CoreGraphics" -framework "CoreText" -framework "Foundation" -framework "GTMAppAuth" -framework "GTMSessionFetcher" -framework "LocalAuthentication" -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.modulemap b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.modulemap new file mode 100644 index 00000000..5f9f672f --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.modulemap @@ -0,0 +1,6 @@ +framework module Pods_mentorship_iosTests { + umbrella header "Pods-mentorship iosTests-umbrella.h" + + export * + module * { export * } +} diff --git a/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.release.xcconfig b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.release.xcconfig new file mode 100644 index 00000000..a00682aa --- /dev/null +++ b/Pods/Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.release.xcconfig @@ -0,0 +1,9 @@ +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher" "${PODS_ROOT}/GoogleSignIn/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AppAuth/AppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMAppAuth/GTMAppAuth.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "AppAuth" -framework "CoreGraphics" -framework "CoreText" -framework "Foundation" -framework "GTMAppAuth" -framework "GTMSessionFetcher" -framework "LocalAuthentication" -framework "SafariServices" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -weak_framework "AuthenticationServices" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/mentorship ios.xcodeproj/project.pbxproj b/mentorship ios.xcodeproj/project.pbxproj index 7b10b6d2..f7a73aac 100644 --- a/mentorship ios.xcodeproj/project.pbxproj +++ b/mentorship ios.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 26456FF21393E3AB6B5DA003 /* Pods_mentorship_ios_mentorship_iosUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C2E468318F39338BF1CD504 /* Pods_mentorship_ios_mentorship_iosUITests.framework */; }; 2B0A6F785ABDD7721B8DB7D6 /* Pods_mentorship_iosTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F1CB15A69AE397D647DD813 /* Pods_mentorship_iosTests.framework */; }; + 450FF474259810A200F35EF2 /* CoreUserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450FF473259810A200F35EF2 /* CoreUserProfile.swift */; }; 530F44142482752500B1FD0B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530F44132482752500B1FD0B /* AppDelegate.swift */; }; 530F44162482752500B1FD0B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530F44152482752500B1FD0B /* SceneDelegate.swift */; }; 530F44192482752500B1FD0B /* mentorship_ios.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 530F44172482752500B1FD0B /* mentorship_ios.xcdatamodeld */; }; @@ -130,6 +131,7 @@ /* Begin PBXFileReference section */ 02B5AA94E26793BDDEA92D24 /* Pods-mentorship iosTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-mentorship iosTests.release.xcconfig"; path = "Target Support Files/Pods-mentorship iosTests/Pods-mentorship iosTests.release.xcconfig"; sourceTree = ""; }; + 450FF473259810A200F35EF2 /* CoreUserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUserProfile.swift; sourceTree = ""; }; 4B7C6AD7AFF5534C376F1BAF /* Pods_mentorship_ios.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_mentorship_ios.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4FF5654F84F759331EBDC783 /* Pods-mentorship ios-mentorship iosUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-mentorship ios-mentorship iosUITests.release.xcconfig"; path = "Target Support Files/Pods-mentorship ios-mentorship iosUITests/Pods-mentorship ios-mentorship iosUITests.release.xcconfig"; sourceTree = ""; }; 530F44102482752500B1FD0B /* mentorship ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "mentorship ios.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -299,6 +301,14 @@ path = Pods; sourceTree = ""; }; + 450FF4722598104600F35EF2 /* CoredataServices */ = { + isa = PBXGroup; + children = ( + 450FF473259810A200F35EF2 /* CoreUserProfile.swift */, + ); + path = CoredataServices; + sourceTree = ""; + }; 530F44072482752500B1FD0B = { isa = PBXGroup; children = ( @@ -324,6 +334,7 @@ 530F44122482752500B1FD0B /* mentorship ios */ = { isa = PBXGroup; children = ( + 450FF4722598104600F35EF2 /* CoredataServices */, 53F0CC7024E341E800B46F19 /* mentorship ios.entitlements */, 532A652924ECF292000E4A6A /* Config */, 83AA531B249F85E7001D6B0B /* Utilities */, @@ -368,7 +379,6 @@ 53D4545324CD50F500083215 /* HomeTests.swift */, 53A6DDEF24BE52A100D5DFCE /* ProfileTests.swift */, 53A6DC8324CDAC7C00E4FE14 /* RelationTests.swift */, - 53AC6DE624D2BF8700F7FDE3 /* TaskCommentsTests.swift */, 53A6DC8524CDC4EF00E4FE14 /* MembersTests.swift */, 53A6DC8724CDCBCA00E4FE14 /* SettingsTests.swift */, 534B69D924CB6DAE007646E6 /* MockURLProtocol.swift */, @@ -976,6 +986,7 @@ 53A115D9249E1BD00065998C /* Settings.swift in Sources */, 535DD3FB24E02982006C711C /* SocialSignIn.swift in Sources */, 5311839524AB4D7B0006036F /* Relation.swift in Sources */, + 450FF474259810A200F35EF2 /* CoreUserProfile.swift in Sources */, 53ED6B0D248D04970055DFF0 /* Members.swift in Sources */, 531B75EC248524D600CEBF62 /* SignUp.swift in Sources */, 83AA531D249F860F001D6B0B /* UIHelper.swift in Sources */, diff --git a/mentorship ios/CoredataServices/CoreUserProfile.swift b/mentorship ios/CoredataServices/CoreUserProfile.swift new file mode 100644 index 00000000..308cb5d1 --- /dev/null +++ b/mentorship ios/CoredataServices/CoreUserProfile.swift @@ -0,0 +1,70 @@ +// +// CoreUserProfile.swift +// Created on 27/12/20 +// Created for AnitaB.org Mentorship-iOS +// + +import Foundation +import CoreData +import UIKit + +class CoreUserProfile { + + var HomeUserName = [UserProfileData]() + let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext + + + func NewUserCore(profile: ProfileModel.ProfileData) { + loadCoredata() + if (HomeUserName.count != 0 ){ + if(HomeUserName[0].username != profile.name?.capitalized){ + HomeUserName[0].username = profile.name?.capitalized + HomeUserName[0].id = String(profile.id) + } + } + else{ + let new = UserProfileData(context: context) + new.username = profile.name!.capitalized + new.id = String(profile.id) + HomeUserName.append(new) + } + print("printing the profile in coredata \(profile)") + saveCoredata() + print("printing cout of coredata \(String(HomeUserName.count))") + print("here we have new username ///// --------------- ///// \(HomeUserName)") + + + + } + + func test(token: String) { + let token = "Bearer " + token + print("printing token in coreasavedata \(token)") + + } + + + func loadCoredata(){ + let request : NSFetchRequest = UserProfileData.fetchRequest() + do{ + try HomeUserName = context.fetch(request) + print("printing cout of coredata \(String(HomeUserName.count))") + print("here we have new username ///// --------------- ///// \(HomeUserName)") + }catch{ + print("getting an error in loading data from core \(error)") + } + } + + func saveCoredata() { + + do{ + try context.save() + }catch{ + print("getting an error in saving coredata of username \(error)") + } + + + } + + +} diff --git a/mentorship ios/Service/Networking/LoginAPI.swift b/mentorship ios/Service/Networking/LoginAPI.swift index c1b17415..9f9f5732 100644 --- a/mentorship ios/Service/Networking/LoginAPI.swift +++ b/mentorship ios/Service/Networking/LoginAPI.swift @@ -23,6 +23,8 @@ class LoginAPI: LoginService { .sink { response in var loginResponseData = LoginModel.LoginResponseData(message: response.message) // if login successful, store access token in keychain + print("here testting the decdoed respose\(response)") +// CoreUserProfile().saveCore(token: response.accessToken!) if var token = response.accessToken { token = "Bearer " + token do { @@ -32,6 +34,7 @@ class LoginAPI: LoginService { loginResponseData.message = "Failed to save access token" } } +// CoreUserProfile().test(token: String(response.accessToken!)) // completion handler completion(loginResponseData) } @@ -51,6 +54,8 @@ class LoginAPI: LoginService { urlString: URLStringConstants.Users.login, uploadData: uploadData, keychainUsername: loginData.username) { response in + + print("login response /////// \(response)") completion(response) } } diff --git a/mentorship ios/Service/Networking/ProfileAPI.swift b/mentorship ios/Service/Networking/ProfileAPI.swift index dd866ba6..c8c93ee3 100644 --- a/mentorship ios/Service/Networking/ProfileAPI.swift +++ b/mentorship ios/Service/Networking/ProfileAPI.swift @@ -28,6 +28,8 @@ class ProfileAPI: ProfileService { .receive(on: RunLoop.main) .catch { _ in Just(ProfileViewModel().getProfile()) } .sink { profile in + //calling coredata to save profile + CoreUserProfile().NewUserCore(profile: profile) ProfileViewModel().saveProfile(profile: profile) completion(profile) } diff --git a/mentorship ios/Views/Home/Home.swift b/mentorship ios/Views/Home/Home.swift index c5325a97..c778a463 100644 --- a/mentorship ios/Views/Home/Home.swift +++ b/mentorship ios/Views/Home/Home.swift @@ -5,6 +5,7 @@ // import SwiftUI +import CoreData struct Home: View { var homeService: HomeService = HomeAPI() @@ -13,9 +14,13 @@ struct Home: View { private var relationsData: UIHelper.HomeScreen.RelationsListData { return homeViewModel.relationsListData } + //suporting variables for coredata + let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext + @State var HomeUserName = [UserProfileData]() + @State var userFirstName: String = "" - var userFirstName: String { - + + var userFirst: String { //Return just the first name if let editFullName = self.homeViewModel.userName?.capitalized { let trimmedFullName = editFullName.trimmingCharacters(in: .whitespaces) @@ -23,7 +28,6 @@ struct Home: View { return userNameAsArray[0] } return "" - } func useHomeService() { @@ -32,7 +36,6 @@ struct Home: View { home.update(viewModel: self.homeViewModel) self.homeViewModel.isLoading = false } - // if first time load, load profile too and use isLoading state (used to express in UI). if self.homeViewModel.firstTimeLoad { // set isLoading to true (expressed in UI) @@ -43,7 +46,25 @@ struct Home: View { profile.update(viewModel: self.homeViewModel) // set first time load to false self.homeViewModel.firstTimeLoad = false + //loading data from coredata , + loadCoredata() + } + + } + } + + func loadCoredata(){ + print("loadCoredata called") + + let request : NSFetchRequest = UserProfileData.fetchRequest() + do{ + try HomeUserName = context.fetch(request) + print("here we have new username ///// --------------- ///// \(HomeUserName)") + if (HomeUserName.count != 0){ + userFirstName = HomeUserName[HomeUserName.count - 1].username! } + }catch{ + print("getting an error in loading data from core \(error)") } } diff --git a/mentorship ios/mentorship_ios.xcdatamodeld/mentorship_ios.xcdatamodel/contents b/mentorship ios/mentorship_ios.xcdatamodeld/mentorship_ios.xcdatamodel/contents index 50d2514e..465ce62f 100644 --- a/mentorship ios/mentorship_ios.xcdatamodeld/mentorship_ios.xcdatamodel/contents +++ b/mentorship ios/mentorship_ios.xcdatamodeld/mentorship_ios.xcdatamodel/contents @@ -1,4 +1,10 @@ - - + + + + + + + + \ No newline at end of file