diff --git a/Example-macOS-Swift/GTMAppAuth-Swift-Bridging-Header.h b/Example-macOS-Swift/GTMAppAuth-Swift-Bridging-Header.h new file mode 100644 index 00000000..1b2cb5d6 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.pbxproj b/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.pbxproj new file mode 100644 index 00000000..86f0b239 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.pbxproj @@ -0,0 +1,604 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 385BE2527244022DBCBC22A8 /* Pods_GTMAppAuth_Swift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E6AA28D251392FA4B72DB9 /* Pods_GTMAppAuth_Swift.framework */; }; + 80C61DB5203C73BC00220CB9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C61DB4203C73BC00220CB9 /* AppDelegate.swift */; }; + 80C61DB9203C73BC00220CB9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 80C61DB8203C73BC00220CB9 /* Assets.xcassets */; }; + 80C61DBC203C73BC00220CB9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 80C61DBA203C73BC00220CB9 /* Main.storyboard */; }; + 80C61DC8203C73BC00220CB9 /* GTMAppAuth_SwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80C61DC7203C73BC00220CB9 /* GTMAppAuth_SwiftTests.swift */; }; + 80FB1248203C901100E14EEC /* bridging header.m in Sources */ = {isa = PBXBuildFile; fileRef = 80FB1247203C901100E14EEC /* bridging header.m */; }; + 80FB124D203D99E300E14EEC /* GTMAppAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80FB124B203D99E300E14EEC /* GTMAppAuthViewController.swift */; }; + 915E13BE7642D4ABC232772E /* Pods_GTMAppAuth_SwiftTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7999701DEDDF7939467F05BF /* Pods_GTMAppAuth_SwiftTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 80C61DC4203C73BC00220CB9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 80C61DA9203C73BC00220CB9 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 80C61DB0203C73BC00220CB9; + remoteInfo = "GTMAppAuth-Swift"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 24F24829DA3591D088B47B76 /* Pods-GTMAppAuth-SwiftTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GTMAppAuth-SwiftTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.debug.xcconfig"; sourceTree = ""; }; + 529022113C7BAFF1FBB95BB7 /* Pods-GTMAppAuth-Swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GTMAppAuth-Swift.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.debug.xcconfig"; sourceTree = ""; }; + 6D76C479BB087D444C644CE0 /* Pods-GTMAppAuth-SwiftTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GTMAppAuth-SwiftTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.release.xcconfig"; sourceTree = ""; }; + 75A7C9EC69BCEDEF2F54737F /* Pods-GTMAppAuth-Swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GTMAppAuth-Swift.release.xcconfig"; path = "Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.release.xcconfig"; sourceTree = ""; }; + 7999701DEDDF7939467F05BF /* Pods_GTMAppAuth_SwiftTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GTMAppAuth_SwiftTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 80C61DB1203C73BC00220CB9 /* GTMAppAuth-Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "GTMAppAuth-Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 80C61DB4203C73BC00220CB9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 80C61DB8203C73BC00220CB9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 80C61DBB203C73BC00220CB9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 80C61DBD203C73BC00220CB9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 80C61DBE203C73BC00220CB9 /* GTMAppAuth_Swift.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GTMAppAuth_Swift.entitlements; sourceTree = ""; }; + 80C61DC3203C73BC00220CB9 /* GTMAppAuth-SwiftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "GTMAppAuth-SwiftTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 80C61DC7203C73BC00220CB9 /* GTMAppAuth_SwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GTMAppAuth_SwiftTests.swift; sourceTree = ""; }; + 80C61DC9203C73BC00220CB9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 80FB1246203C901100E14EEC /* GTMAppAuth-Swift-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GTMAppAuth-Swift-Bridging-Header.h"; sourceTree = ""; }; + 80FB1247203C901100E14EEC /* bridging header.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "bridging header.m"; sourceTree = ""; }; + 80FB124B203D99E300E14EEC /* GTMAppAuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GTMAppAuthViewController.swift; sourceTree = ""; }; + 80FB124F203EF86100E14EEC /* GTMAppAuth-Swift.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "GTMAppAuth-Swift.entitlements"; sourceTree = ""; }; + C1E6AA28D251392FA4B72DB9 /* Pods_GTMAppAuth_Swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GTMAppAuth_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 80C61DAE203C73BC00220CB9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 385BE2527244022DBCBC22A8 /* Pods_GTMAppAuth_Swift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80C61DC0203C73BC00220CB9 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 915E13BE7642D4ABC232772E /* Pods_GTMAppAuth_SwiftTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 80C61DA8203C73BC00220CB9 = { + isa = PBXGroup; + children = ( + 80FB1247203C901100E14EEC /* bridging header.m */, + 80C61DB3203C73BC00220CB9 /* GTMAppAuth-Swift */, + 80C61DC6203C73BC00220CB9 /* GTMAppAuth-SwiftTests */, + 80C61DB2203C73BC00220CB9 /* Products */, + 9EC1CC3DB1B35B11E1CBC089 /* Pods */, + A4EA1075EE079E0282B21511 /* Frameworks */, + 80FB1246203C901100E14EEC /* GTMAppAuth-Swift-Bridging-Header.h */, + ); + sourceTree = ""; + }; + 80C61DB2203C73BC00220CB9 /* Products */ = { + isa = PBXGroup; + children = ( + 80C61DB1203C73BC00220CB9 /* GTMAppAuth-Swift.app */, + 80C61DC3203C73BC00220CB9 /* GTMAppAuth-SwiftTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 80C61DB3203C73BC00220CB9 /* GTMAppAuth-Swift */ = { + isa = PBXGroup; + children = ( + 80FB124F203EF86100E14EEC /* GTMAppAuth-Swift.entitlements */, + 80FB124B203D99E300E14EEC /* GTMAppAuthViewController.swift */, + 80C61DB4203C73BC00220CB9 /* AppDelegate.swift */, + 80C61DB8203C73BC00220CB9 /* Assets.xcassets */, + 80C61DBA203C73BC00220CB9 /* Main.storyboard */, + 80C61DBD203C73BC00220CB9 /* Info.plist */, + 80C61DBE203C73BC00220CB9 /* GTMAppAuth_Swift.entitlements */, + ); + path = "GTMAppAuth-Swift"; + sourceTree = ""; + }; + 80C61DC6203C73BC00220CB9 /* GTMAppAuth-SwiftTests */ = { + isa = PBXGroup; + children = ( + 80C61DC7203C73BC00220CB9 /* GTMAppAuth_SwiftTests.swift */, + 80C61DC9203C73BC00220CB9 /* Info.plist */, + ); + path = "GTMAppAuth-SwiftTests"; + sourceTree = ""; + }; + 9EC1CC3DB1B35B11E1CBC089 /* Pods */ = { + isa = PBXGroup; + children = ( + 529022113C7BAFF1FBB95BB7 /* Pods-GTMAppAuth-Swift.debug.xcconfig */, + 75A7C9EC69BCEDEF2F54737F /* Pods-GTMAppAuth-Swift.release.xcconfig */, + 24F24829DA3591D088B47B76 /* Pods-GTMAppAuth-SwiftTests.debug.xcconfig */, + 6D76C479BB087D444C644CE0 /* Pods-GTMAppAuth-SwiftTests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + A4EA1075EE079E0282B21511 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C1E6AA28D251392FA4B72DB9 /* Pods_GTMAppAuth_Swift.framework */, + 7999701DEDDF7939467F05BF /* Pods_GTMAppAuth_SwiftTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 80C61DB0203C73BC00220CB9 /* GTMAppAuth-Swift */ = { + isa = PBXNativeTarget; + buildConfigurationList = 80C61DCC203C73BC00220CB9 /* Build configuration list for PBXNativeTarget "GTMAppAuth-Swift" */; + buildPhases = ( + 9A4C33EEDDB7CE00290D1B02 /* [CP] Check Pods Manifest.lock */, + 80C61DAD203C73BC00220CB9 /* Sources */, + 80C61DAE203C73BC00220CB9 /* Frameworks */, + 80C61DAF203C73BC00220CB9 /* Resources */, + 17999F184A758A0B527829DB /* [CP] Embed Pods Frameworks */, + 23B12367D5BB4A0E53CB3DC3 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "GTMAppAuth-Swift"; + productName = "GTMAppAuth-Swift"; + productReference = 80C61DB1203C73BC00220CB9 /* GTMAppAuth-Swift.app */; + productType = "com.apple.product-type.application"; + }; + 80C61DC2203C73BC00220CB9 /* GTMAppAuth-SwiftTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 80C61DCF203C73BC00220CB9 /* Build configuration list for PBXNativeTarget "GTMAppAuth-SwiftTests" */; + buildPhases = ( + B8AF70AE16C4A0FC0E079F83 /* [CP] Check Pods Manifest.lock */, + 80C61DBF203C73BC00220CB9 /* Sources */, + 80C61DC0203C73BC00220CB9 /* Frameworks */, + 80C61DC1203C73BC00220CB9 /* Resources */, + 20681742433D6CFAD32BCA28 /* [CP] Embed Pods Frameworks */, + 4C18273792505E09CECF386C /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 80C61DC5203C73BC00220CB9 /* PBXTargetDependency */, + ); + name = "GTMAppAuth-SwiftTests"; + productName = "GTMAppAuth-SwiftTests"; + productReference = 80C61DC3203C73BC00220CB9 /* GTMAppAuth-SwiftTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 80C61DA9203C73BC00220CB9 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 80C61DB0203C73BC00220CB9 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 0920; + ProvisioningStyle = Automatic; + }; + 80C61DC2203C73BC00220CB9 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + TestTargetID = 80C61DB0203C73BC00220CB9; + }; + }; + }; + buildConfigurationList = 80C61DAC203C73BC00220CB9 /* Build configuration list for PBXProject "GTMAppAuth-Swift" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 80C61DA8203C73BC00220CB9; + productRefGroup = 80C61DB2203C73BC00220CB9 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 80C61DB0203C73BC00220CB9 /* GTMAppAuth-Swift */, + 80C61DC2203C73BC00220CB9 /* GTMAppAuth-SwiftTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 80C61DAF203C73BC00220CB9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 80C61DB9203C73BC00220CB9 /* Assets.xcassets in Resources */, + 80C61DBC203C73BC00220CB9 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80C61DC1203C73BC00220CB9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 17999F184A758A0B527829DB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework", + "${BUILT_PRODUCTS_DIR}/GTMAppAuth/GTMAppAuth.framework", + "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMAppAuth.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 20681742433D6CFAD32BCA28 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 23B12367D5BB4A0E53CB3DC3 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 4C18273792505E09CECF386C /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9A4C33EEDDB7CE00290D1B02 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GTMAppAuth-Swift-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B8AF70AE16C4A0FC0E079F83 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-GTMAppAuth-SwiftTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 80C61DAD203C73BC00220CB9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 80FB1248203C901100E14EEC /* bridging header.m in Sources */, + 80C61DB5203C73BC00220CB9 /* AppDelegate.swift in Sources */, + 80FB124D203D99E300E14EEC /* GTMAppAuthViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 80C61DBF203C73BC00220CB9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 80C61DC8203C73BC00220CB9 /* GTMAppAuth_SwiftTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 80C61DC5203C73BC00220CB9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 80C61DB0203C73BC00220CB9 /* GTMAppAuth-Swift */; + targetProxy = 80C61DC4203C73BC00220CB9 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 80C61DBA203C73BC00220CB9 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 80C61DBB203C73BC00220CB9 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 80C61DCA203C73BC00220CB9 /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_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; + CODE_SIGN_IDENTITY = "-"; + 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 = ( + "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; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 80C61DCB203C73BC00220CB9 /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_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; + CODE_SIGN_IDENTITY = "-"; + 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_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; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + }; + name = Release; + }; + 80C61DCD203C73BC00220CB9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 529022113C7BAFF1FBB95BB7 /* Pods-GTMAppAuth-Swift.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "GTMAppAuth-Swift/GTMAppAuth-Swift.entitlements"; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 3GC4AU8T59; + INFOPLIST_FILE = "GTMAppAuth-Swift/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.mobilebloke.GoogleOAuthDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "GTMAppAuth-Swift-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + 80C61DCE203C73BC00220CB9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 75A7C9EC69BCEDEF2F54737F /* Pods-GTMAppAuth-Swift.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "GTMAppAuth-Swift/GTMAppAuth-Swift.entitlements"; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 3GC4AU8T59; + INFOPLIST_FILE = "GTMAppAuth-Swift/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.mobilebloke.GoogleOAuthDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "GTMAppAuth-Swift-Bridging-Header.h"; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; + 80C61DD0203C73BC00220CB9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24F24829DA3591D088B47B76 /* Pods-GTMAppAuth-SwiftTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "GTMAppAuth-SwiftTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "Google.GTMAppAuth-SwiftTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMAppAuth-Swift.app/Contents/MacOS/GTMAppAuth-Swift"; + }; + name = Debug; + }; + 80C61DD1203C73BC00220CB9 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6D76C479BB087D444C644CE0 /* Pods-GTMAppAuth-SwiftTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "GTMAppAuth-SwiftTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "Google.GTMAppAuth-SwiftTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GTMAppAuth-Swift.app/Contents/MacOS/GTMAppAuth-Swift"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 80C61DAC203C73BC00220CB9 /* Build configuration list for PBXProject "GTMAppAuth-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 80C61DCA203C73BC00220CB9 /* Debug */, + 80C61DCB203C73BC00220CB9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 80C61DCC203C73BC00220CB9 /* Build configuration list for PBXNativeTarget "GTMAppAuth-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 80C61DCD203C73BC00220CB9 /* Debug */, + 80C61DCE203C73BC00220CB9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 80C61DCF203C73BC00220CB9 /* Build configuration list for PBXNativeTarget "GTMAppAuth-SwiftTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 80C61DD0203C73BC00220CB9 /* Debug */, + 80C61DD1203C73BC00220CB9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 80C61DA9203C73BC00220CB9 /* Project object */; +} diff --git a/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..9ee94634 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.xcworkspace/xcuserdata/grantk2.xcuserdatad/UserInterfaceState.xcuserstate b/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.xcworkspace/xcuserdata/grantk2.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..3b5f84d4 Binary files /dev/null and b/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/project.xcworkspace/xcuserdata/grantk2.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/xcschememanagement.plist b/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..1097f1a6 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + GTMAppAuth-Swift.xcscheme + + orderHint + 5 + + + + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/contents.xcworkspacedata b/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..735a4664 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/xcuserdata/grantk2.xcuserdatad/UserInterfaceState.xcuserstate b/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/xcuserdata/grantk2.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..257f7501 Binary files /dev/null and b/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/xcuserdata/grantk2.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/xcuserdata/grantk2.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/xcuserdata/grantk2.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 00000000..530cbea7 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift.xcworkspace/xcuserdata/grantk2.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift/AppDelegate.swift b/Example-macOS-Swift/GTMAppAuth-Swift/AppDelegate.swift new file mode 100644 index 00000000..a21a6211 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift/AppDelegate.swift @@ -0,0 +1,42 @@ +// +// AppDelegate.swift +// GTMAppAuth-Swift +// +// Created by Grant K2 on 20/02/2018. +// Copyright © 2018 Google. All rights reserved. +// + +import Cocoa +import GTMAppAuth + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + var window: NSWindow? + + var currentAuthorizationFlow:OIDAuthorizationFlowSession? + + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Register for Call back URL events + let aem = NSAppleEventManager.shared(); + aem.setEventHandler(self, andSelector: #selector(AppDelegate.handleGetURLEvent(event:replyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL)) + + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + func applicationWillFinishLaunching(notification: NSNotification?) { + // -- launch the app with url + } + + @objc func handleGetURLEvent(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) { + + let urlString = event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))?.stringValue! + let url = URL(string: urlString!)! + currentAuthorizationFlow?.resumeAuthorizationFlow(with: url) + } +} + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example-macOS-Swift/GTMAppAuth-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..2db2b1c7 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example-macOS-Swift/GTMAppAuth-Swift/Base.lproj/Main.storyboard b/Example-macOS-Swift/GTMAppAuth-Swift/Base.lproj/Main.storyboard new file mode 100644 index 00000000..e3ecef8b --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift/Base.lproj/Main.storyboard @@ -0,0 +1,834 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuth-Swift.entitlements b/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuth-Swift.entitlements new file mode 100644 index 00000000..625af03d --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuth-Swift.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuthViewController.swift b/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuthViewController.swift new file mode 100644 index 00000000..75088059 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuthViewController.swift @@ -0,0 +1,253 @@ +// +// GTMAppAuthViewController.swift +// GTMAppAuth-Swift +// +// Created by Grant K2 on 21/02/2018. +// Copyright © 2018 Google. All rights reserved. +// +import Foundation +import Cocoa +import GTMAppAuth +import AppAuth +import QuartzCore +import GTMSessionFetcher + +class GTMAppAuthViewController: NSViewController, OIDAuthStateChangeDelegate, OIDAuthStateErrorDelegate { + + var appDel: AppDelegate = (NSApplication.shared.delegate as? AppDelegate)! + + var authorization: GTMAppAuthFetcherAuthorization? + + // NOTE: + // To run this sample, you need to register your own Google API client at + // https://console.developers.google.com/apis/credentials?project=_ and update three configuration + // points in the sample: kClientID and kRedirectURI constants in AppAuthExampleViewController.m + // and the URI scheme in Info.plist (URL Types -> Item 0 -> URL Schemes -> Item 0). + // Full instructions: https://github.com/openid/AppAuth-iOS/blob/master/Example-Mac/README.md + //TODO: Fix incorrect link to doc from original project + + // 1. Obtain details from here: https://console.developers.google.com + let kIssuer: String = "https://accounts.google.com" //TODO: Adopt String code style when done porting. + let kClientID: String = "com.googleusercontent.apps." // TODO: Grab from Google generated Plist + let kclientSecret = "" // Client ID for IOS config in Google Apis console doesn't seem to generate a client Secret so we will leave it blank + let kRedirectURI = "com.googleusercontent.apps.:/oauthredirect" // TODO: Grab from Google Plist + + // 2. Make sure you have enabled Sandbox Entitlements and have allowed Outgoing connection otherwise this will not work.(Under Project-> Capabilities. This has been done in this project) + // 3. Update the `kRedirectURI` with the *reverse DNS notation* form of the client ID. For example, if the client ID is + // `YOUR_CLIENT.apps.googleusercontent.com`, the reverse DNS notation would be + // `com.googleusercontent.apps.YOUR_CLIENT`. A path component is added resulting in + // `"com.googleusercontent.apps.:/oauthredirect"`. + // 4. If you want to access a specific service then update the scopes below. We have added in Scope to get ID and Profile data for the purpose of this demo. For additional Google scopes, you can get these from the Google scopes URL: https://developers.google.com/identity/protocols/googlescopes + let scopesToAccess = [OIDScopeOpenID, OIDScopeProfile] + + let kExampleAuthorizeKey = "authState" + + @IBAction func authWithAutoCodeExchange(_ sender: NSButton) { + + guard let issuer = URL(string: kIssuer) else { + self.logMessage("Error creating URL for : \(kIssuer)") + return + } + self.logMessage("Fetching configuration for issuer: \(issuer)") + let redirectURI = URL(string: kRedirectURI)! + + //discovers endpoints + //TODO: Make it clearer in doc how to do the various methods to get config. Will do it manually for now as its simple and easy. + // Manually get Endpoints + let config = GTMAppAuthFetcherAuthorization.configurationForGoogle() + print(config) + self.logMessage("Got configuration:\(config)") + + // builds authentication request + let request: OIDAuthorizationRequest = OIDAuthorizationRequest(configuration: config, clientId: self.kClientID, clientSecret: self.kclientSecret, scopes: scopesToAccess, redirectURL: redirectURI, responseType: OIDResponseTypeCode, additionalParameters: nil) + + // performs authentication request + self.appDel.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, callback: { (authState, error) in + + if let authState = authState { + let authorizationLocal = GTMAppAuthFetcherAuthorization(authState: authState) + self.setAuthorization(authorizationLocal) + self.logMessage("Got authorization tokens. Access token: \(authState.lastTokenResponse?.accessToken)") + } else { + if let error = error { + self.setAuthorization(nil) + self.logMessage("Authorization error: \(error.localizedDescription)") + } + } + }) + } + @IBAction func userinfo(_ sender: NSButton) { + self.logMessage("Performing User Info Request") + + // Creates a GTMSessionFetcherService with the authorization. + // Normally you would save this service object and re-use it for all REST API calls. + let fetcherService = GTMSessionFetcherService() + fetcherService.authorizer = self.authorization + + // Creates a fetcher for the API call. + let userinfoEndpoint = URL(string: "https://www.googleapis.com/oauth2/v3/userinfo")! + let fetcher = fetcherService.fetcher(with: userinfoEndpoint) + fetcher.beginFetch { (data, error) in + // Checks for an error. + if let error = error { + print(error.localizedDescription) + self.logMessage("error:\(error.localizedDescription)") + // OIDOAuthTokenErrorDomain indicates an issue with the authorization. + //TODO: Error doesn't have a domain + // if error. == OIDOAuthTokenErrorDomain { + // self.setAuthorization(nil) + // self.logMessage("Authorization error during token refresh, clearing state. \(error)") + // } else { + // self.logMessage("Transient error during token refresh. \(error)") + // } + } + //JSON Error + + do { + let jsonDictORArray = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) + // Sucess Response: + self.logMessage("Success \(jsonDictORArray)") + } catch (let parsingError){ + print("JSON decoding error: \(parsingError)") + return + } + + } + } + + @IBAction func forceRefresh(_ sender: NSButton) { + authorization?.authState.setNeedsTokenRefresh() + } + + @IBAction func clearAuthState(_ sender: NSButton) { + self.authorization = nil + } + + @IBAction func clearLog(_ sender: NSButton) { + //logTextView.textStorage = NSTextStorage(string: "") // This is get only so can't use the method in other docs + logTextView.string = "" + } + + @IBOutlet var logTextView: NSTextView! + @IBOutlet weak var authAutoButton: NSButton! + @IBOutlet weak var userInfoButton: NSButton! + @IBOutlet weak var forceRefreshButton: NSButton! + @IBOutlet weak var clearAuthStateButton: NSButton! + + + + + override func viewDidLoad() { + super.viewDidLoad() + + assert(kIssuer != "https://issuer.example.com", + "Update kIssuer with your own issuer.\n" + + "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md"); + + assert(kClientID != "YOUR_CLIENT_ID", + "Update kClientID with your own client ID.\n" + + "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md"); + + assert(kRedirectURI != "com.example.app:/oauth2redirect/example-provider", + "Update kRedirectURI with your own redirect URI.\n" + + "Instructions: https://github.com/openid/AppAuth-iOS/blob/master/Examples/Example-iOS_Swift-Carthage/README.md"); + + // verifies that the custom URI scheme has been updated in the Info.plist + guard let urlTypes: [AnyObject] = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [AnyObject], urlTypes.count > 0 else { + assertionFailure("No custom URI scheme has been configured for the project.") + return + } + + guard let items = urlTypes[0] as? [String: AnyObject], + let urlSchemes = items["CFBundleURLSchemes"] as? [AnyObject], urlSchemes.count > 0 else { + assertionFailure("No custom URI scheme has been configured for the project.") + return + } + + guard let urlScheme = urlSchemes[0] as? String else { + assertionFailure("No custom URI scheme has been configured for the project.") + return + } + self.loadState() + self.updateUI() + } + func saveState() { + if authorization!.canAuthorize() { + GTMAppAuthFetcherAuthorization.save(authorization!, toKeychainForName: kExampleAuthorizeKey) + } else { + GTMAppAuthFetcherAuthorization.removeFromKeychain(forName: kExampleAuthorizeKey) + } + } + func loadState() { + if let authorization = GTMAppAuthFetcherAuthorization(fromKeychainForName: kExampleAuthorizeKey) { + self.setAuthorization(authorization) + } + } + + func updateUI() { + guard authorization != nil + else{ + self.authAutoButton.title = "Authorize" + return + } + userInfoButton.isEnabled = (authorization?.canAuthorize())! + forceRefreshButton.isEnabled = (authorization?.canAuthorize())! + clearAuthStateButton.isEnabled = authorization != nil + + // dynamically changes authorize button text depending on authorized state + if authorization != nil { + authAutoButton.title = "Authorize" + } else { + authAutoButton.title = "Re-authorize" + } + } + + func stateChanged() { + self.saveState() + self.updateUI() + } + + func didChangeState(state: OIDAuthState) { + self.stateChanged() + } + + + //MARK: Protocol Methods + func authState(_ state: OIDAuthState, didEncounterAuthorizationError error: Error) { + self.logMessage("Received authorization error \(error)") + } + + + func didChange(_ state: OIDAuthState) { + // TODO(referenced wdenniss): update for GTMAppAuth + self.stateChanged() + } + + + func logMessage(_ message: String) { + //TODO: Clarify objective of this... + // // gets message as string + // va_list argp; + // va_start(argp, format); + // NSString *log = [[NSString alloc] initWithFormat:format arguments:argp]; + // va_end(argp); + + // outputs to stdout + NSLog("%@", message) + + // appends to output log + //TODO: If we are using STD out above we probably want to format the list. + // appends to output log + // let dataFormatter = DateFormatter() + // dataFormatter.dateFormat = "hh:mm:ss" + + let logLineAttr = NSAttributedString(string: message) + logTextView.textStorage?.append(logLineAttr) + } + func setAuthorization(_ authorization: GTMAppAuthFetcherAuthorization?) { + self.authorization = authorization + self.saveState() + self.updateUI() + } + +} diff --git a/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuth_Swift.entitlements b/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuth_Swift.entitlements new file mode 100644 index 00000000..0c67376e --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift/GTMAppAuth_Swift.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/Example-macOS-Swift/GTMAppAuth-Swift/Info.plist b/Example-macOS-Swift/GTMAppAuth-Swift/Info.plist new file mode 100644 index 00000000..44a82457 --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-Swift/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.<YOUR-CLIENT-ID> + + + + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2018 Google. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/Example-macOS-Swift/GTMAppAuth-SwiftTests/GTMAppAuth_SwiftTests.swift b/Example-macOS-Swift/GTMAppAuth-SwiftTests/GTMAppAuth_SwiftTests.swift new file mode 100644 index 00000000..1e996c5b --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-SwiftTests/GTMAppAuth_SwiftTests.swift @@ -0,0 +1,36 @@ +// +// GTMAppAuth_SwiftTests.swift +// GTMAppAuth-SwiftTests +// +// Created by Grant K2 on 20/02/2018. +// Copyright © 2018 Google. All rights reserved. +// + +import XCTest +@testable import GTMAppAuth_Swift + +class GTMAppAuth_SwiftTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Example-macOS-Swift/GTMAppAuth-SwiftTests/Info.plist b/Example-macOS-Swift/GTMAppAuth-SwiftTests/Info.plist new file mode 100644 index 00000000..6c40a6cd --- /dev/null +++ b/Example-macOS-Swift/GTMAppAuth-SwiftTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Example-macOS-Swift/Podfile b/Example-macOS-Swift/Podfile new file mode 100644 index 00000000..957b2b18 --- /dev/null +++ b/Example-macOS-Swift/Podfile @@ -0,0 +1,16 @@ +# Uncomment the next line to define a global platform for your project +platform :osx, '10.13' + +target 'GTMAppAuth-Swift' do + # Comment the next line if you're not using Swift and don't want to use dynamic frameworks + use_frameworks! + + # Pods for GTMAppAuth-Swift + pod 'GTMAppAuth' + + target 'GTMAppAuth-SwiftTests' do + inherit! :search_paths + # Pods for testing + end + +end diff --git a/Example-macOS-Swift/Podfile.lock b/Example-macOS-Swift/Podfile.lock new file mode 100644 index 00000000..72d992cd --- /dev/null +++ b/Example-macOS-Swift/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - AppAuth (0.9.1) + - GTMAppAuth (0.6.1): + - AppAuth (~> 0.9.0) + - GTMSessionFetcher (~> 1.1) + - GTMSessionFetcher (1.1.12): + - GTMSessionFetcher/Full (= 1.1.12) + - GTMSessionFetcher/Core (1.1.12) + - GTMSessionFetcher/Full (1.1.12): + - GTMSessionFetcher/Core (= 1.1.12) + +DEPENDENCIES: + - GTMAppAuth + +SPEC CHECKSUMS: + AppAuth: f7d9a2ce4ffe79a62a8b0c1d0938e88d9809b846 + GTMAppAuth: ae710e1e53431e2bcdd57f9f92daa16a58a2947d + GTMSessionFetcher: ebaa1f79a5366922c1735f1566901f50beba23b7 + +PODFILE CHECKSUM: c3e1940b595222effba02ad4f5c6b515baa815aa + +COCOAPODS: 1.3.1 diff --git a/Example-macOS-Swift/Pods/AppAuth/LICENSE b/Example-macOS-Swift/Pods/AppAuth/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/AppAuth/README.md b/Example-macOS-Swift/Pods/AppAuth/README.md new file mode 100644 index 00000000..055efbe9 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/README.md @@ -0,0 +1,352 @@ +![AppAuth for iOS and macOS](https://rawgit.com/openid/AppAuth-iOS/master/appauth_lockup.svg) +[![Build Status](https://www.bitrise.io/app/8e4dbca635a964dc.svg?token=8rT4oJnhjUuFWH-QvXuJzg&branch=master)](https://www.bitrise.io/app/8e4dbca635a964dc) +[![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 +[OAuth 2.0 for Native Apps](https://tools.ietf.org/html/draft-ietf-oauth-native-apps) +including using `SFSafariViewController` on iOS for the auth request. For this +reason, `UIWebView` is explicitly *not* supported due to usability and security +reasons. + +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 (AS) that supports +[native apps](https://tools.ietf.org/html/draft-ietf-oauth-native-apps), +either through custom URI scheme redirects, or universal links. +AS's 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.8 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 (AS) that supports +[native apps](https://tools.ietf.org/html/draft-ietf-oauth-native-apps), +either through custom URI scheme, or loopback HTTP redirects. +AS's 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 [Example/README.md](Example/README.md) to configure +with your own OAuth client (you need to update 3 configuration points with your +client info to try the demo). + +## Setup + +AppAuth supports three options for dependency management. + +### 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. 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/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: + +```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 – iOS + +First you need to have a property in your AppDelegate to hold the session, in +order to continue the authorization flow from the redirect. + +```objc +// property of the app's AppDelegate +@property(nonatomic, strong, nullable) + id currentAuthorizationFlow; +``` + +And your main class, a property to store the auth state: + +```objc +// property of the containing class +@property(nonatomic, strong, nullable) OIDAuthState *authState; +``` + +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 allows you to perform these +requests manually. See the `authNoCodeExchange` method in the included Example +app for a demonstration. + +```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]; + } +}]; +``` + +*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). + +```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 resumeAuthorizationFlowWithURL:url]) { + _currentAuthorizationFlow = nil; + return YES; + } + + // Your additional URL handling (if any) goes here. + + return NO; +} +``` + +### 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. + +```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. + +```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. + +```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 +}]; +``` + +## API Documentation + +Browse the [API documentation] +(http://openid.github.io/AppAuth-iOS/docs/latest/annotated.html). + +## Included Samples + +You can try out the iOS sample included in the source distribution by opening +`Example/Example.xcworkspace`. You can easily convert the Example +workspace to a Pod workspace by deleting the `AppAuth` project, and +[configuring the pod](#setup). You can also +[try out the sample via CocoaPods](#try). Be sure to follow the instructions in +[Example/README.md](Example/README.md) to configure your own OAuth client ID +for use with the example. + +You can try out the macOS sample included in the source distribution by +executing `pod install` in the `Example-Mac` folder, then opening +`Example-Mac.xcworkspace`. Be sure to follow the instructions in +[Example-Mac/README.md](Example-Mac/README.md) to configure your own OAuth +client ID for use with the example. diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/AppAuth.h b/Example-macOS-Swift/Pods/AppAuth/Source/AppAuth.h new file mode 100644 index 00000000..908c6f30 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/AppAuth.h @@ -0,0 +1,83 @@ +/*! @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 "OIDAuthorizationUICoordinator.h" +#import "OIDError.h" +#import "OIDErrorUtilities.h" +#import "OIDGrantTypes.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" + +#if TARGET_OS_TV +#elif TARGET_OS_WATCH +#elif TARGET_OS_IOS +#import "OIDAuthState+IOS.h" +#import "OIDAuthorizationService+IOS.h" +#import "OIDAuthorizationUICoordinatorIOS.h" +#elif TARGET_OS_MAC +#import "OIDAuthState+Mac.h" +#import "OIDAuthorizationService+Mac.h" +#import "OIDAuthorizationUICoordinatorMac.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 [OAuth 2.0 for Native Apps] + (https://tools.ietf.org/html/draft-ietf-oauth-native-apps) + including using `SFSafariViewController` for the auth request. For this reason, + `UIWebView` is explicitly *not* supported due to usability and security reasons. + + 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthState.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthState.h new file mode 100644 index 00000000..8c5e11b5 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthState.h @@ -0,0 +1,289 @@ +/*! @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 OIDAuthorizationFlowSession; +@protocol OIDAuthorizationUICoordinator; +@protocol OIDAuthStateChangeDelegate; +@protocol OIDAuthStateErrorDelegate; + +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 { + // private variables + /*! @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; + + // property variables + NSString *_refreshToken; + NSString *_scope; + OIDAuthorizationResponse *_lastAuthorizationResponse; + OIDTokenResponse *_lastTokenResponse; + OIDRegistrationResponse *_lastRegistrationResponse; + NSError *_authorizationError; + __weak id _stateChangeDelegate; + __weak id _errorDelegate; +} + +/*! @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. + @param authorizationRequest The authorization request to present. + @param UICoordinator Generic authorization UI coordinator that can present an authorization + request. + @param callback The method called when the request has completed or failed. + @return A @c OIDAuthorizationFlowSession instance which will terminate when it + receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a + @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + UICoordinator:(id)UICoordinator + 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 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; + +/*! @brief Deprecated, use @c OIDAuthState.performActionWithFreshTokens:. + @discussion 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. + @deprecated Use @c OIDAuthState.performActionWithFreshTokens: which is equivalent. + */ +- (void)withFreshTokensPerformAction:(OIDAuthStateAction)action + __deprecated_msg("Use OIDAuthState.performActionWithFreshTokens:"); + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthState.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthState.m new file mode 100644 index 00000000..53edba79 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthState.m @@ -0,0 +1,531 @@ +/*! @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" + +/*! @brief Key used to encode the @c refreshToken property for @c NSSecureCoding. + */ +static NSString *const kRefreshTokenKey = @"refreshToken"; + +/*! @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; + +@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 + +@synthesize refreshToken = _refreshToken; +@synthesize scope = _scope; +@synthesize lastAuthorizationResponse = _lastAuthorizationResponse; +@synthesize lastTokenResponse = _lastTokenResponse; +@synthesize lastRegistrationResponse = _lastRegistrationResponse; +@synthesize authorizationError = _authorizationError; +@synthesize stateChangeDelegate = _stateChangeDelegate; +@synthesize errorDelegate = _errorDelegate; + +#pragma mark - Convenience initializers + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + UICoordinator:(id)UICoordinator + callback:(OIDAuthStateAuthorizationCallback)callback { + // presents the authorization request + id authFlowSession = [OIDAuthorizationService + presentAuthorizationRequest:authorizationRequest + UICoordinator:UICoordinator + 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 + callback:^(OIDTokenResponse *_Nullable tokenResponse, + NSError *_Nullable tokenError) { + OIDAuthState *authState; + if (tokenResponse) { + authState = [[OIDAuthState alloc] + initWithAuthorizationResponse: + authorizationResponse + tokenResponse:tokenResponse]; + } + callback(authState, tokenError); + }]; + } else { + // implicit or hybrid flow (hybrid flow assumes code is not for this + // client) + 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 response The authorization response. + */ +- (instancetype)initWithAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse { + return [self initWithAuthorizationResponse:authorizationResponse tokenResponse:nil]; +} + + +/*! @brief Designated initializer. + @param response 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]), + self, + (self.isAuthorized) ? @"YES" : @"NO", + _refreshToken, + _scope, + self.accessToken, + self.accessTokenExpirationDate, + 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]; + } + 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]; +} + +#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:_lastAuthorizationResponse.request.redirectURL + clientID:_lastAuthorizationResponse.request.clientID + clientSecret:_lastAuthorizationResponse.request.clientSecret + scope:_lastAuthorizationResponse.request.scope + 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 { + if ([self isTokenFresh]) { + // access token is valid within tolerance levels, perform action + dispatch_async(dispatch_get_main_queue(), ^() { + 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(dispatch_get_main_queue(), ^() { + action(nil, nil, tokenRefreshError); + }); + return; + } + + // access token is expired, first refresh the token, then perform action + NSAssert(_pendingActionsSyncObject, @"_pendingActionsSyncObject cannot be nil"); + @synchronized(_pendingActionsSyncObject) { + // if a token is already in the process of being refreshed, adds to pending actions + if (_pendingActions) { + [_pendingActions addObject:action]; + return; + } + + // creates a list of pending actions, starting with this one + _pendingActions = [NSMutableArray arrayWithObject:action]; + } + + // refresh the tokens + OIDTokenRequest *tokenRefreshRequest = + [self tokenRefreshRequestWithAdditionalParameters:additionalParameters]; + [OIDAuthorizationService performTokenRequest:tokenRefreshRequest + callback:^(OIDTokenResponse *_Nullable response, + NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^() { + // update OIDAuthState based on response + if (response) { + _needsTokenRefresh = NO; + [self updateWithTokenResponse:response error:nil]; + } else { + if (error.domain == OIDOAuthTokenErrorDomain) { + _needsTokenRefresh = NO; + [self updateWithAuthorizationError:error]; + } else { + if ([_errorDelegate respondsToSelector: + @selector(authState:didEncounterTransientError:)]) { + [_errorDelegate authState:self didEncounterTransientError:error]; + } + } + } + + // nil the pending queue and process everything that was queued up + NSArray *actionsToProcess; + @synchronized(_pendingActionsSyncObject) { + actionsToProcess = _pendingActions; + _pendingActions = nil; + } + for (OIDAuthStateAction actionToProcess in actionsToProcess) { + actionToProcess(self.accessToken, self.idToken, error); + } + }); + }]; +} + +#pragma mark - Deprecated + +- (void)withFreshTokensPerformAction:(OIDAuthStateAction)action { + [self performActionWithFreshTokens:action additionalRefreshParameters:nil]; +} + +#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/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthStateChangeDelegate.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthStateChangeDelegate.h new file mode 100644 index 00000000..2570df13 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthStateErrorDelegate.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthStateErrorDelegate.h new file mode 100644 index 00000000..91a9b1cd --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationRequest.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationRequest.h new file mode 100644 index 00000000..971557ed --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationRequest.h @@ -0,0 +1,246 @@ +/*! @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 "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 { + // property variables + OIDServiceConfiguration *_configuration; + NSString *_responseType; + NSString *_clientID; + NSString *_clientSecret; + NSString *_scope; + NSURL *_redirectURL; + NSString *_state; + NSString *_codeVerifier; + NSString *_codeChallenge; + NSString *_codeChallengeMethod; + NSDictionary *_additionalParameters; +} + +/*! @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 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: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, 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 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 + 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationRequest.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationRequest.m new file mode 100644 index 00000000..3c00ecb7 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationRequest.m @@ -0,0 +1,332 @@ +/*! @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 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\" response_type."; + +/*! @brief Code challenge request method. + */ +NSString *const OIDOAuthorizationRequestCodeChallengeMethodS256 = @"S256"; + +@implementation OIDAuthorizationRequest + +@synthesize configuration = _configuration; +@synthesize responseType = _responseType; +@synthesize clientID = _clientID; +@synthesize clientSecret = _clientSecret; +@synthesize scope = _scope; +@synthesize redirectURL = _redirectURL; +@synthesize state = _state; +@synthesize codeVerifier = _codeVerifier; +@synthesize codeChallenge = _codeChallenge; +@synthesize codeChallengeMethod = _codeChallengeMethod; +@synthesize additionalParameters = _additionalParameters; + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithConfiguration: + clientId: + scopes: + redirectURL: + responseType: + additionalParameters:) + ); + +- (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 + 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]; + // Attention: Please refer to https://github.com/openid/AppAuth-iOS/issues/105 + // If you change the restriction on response type here, you must also update initWithCoder: + if (![_responseType isEqualToString:OIDResponseTypeCode]) { + // AppAuth only supports the `code` response type. + // Discussion: https://github.com/openid/AppAuth-iOS/issues/98 + NSAssert(NO, OIDOAuthUnsupportedResponseTypeMessage, _responseType); + return nil; + } + _state = [state 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] + 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]; + // Attention: Please refer to https://github.com/openid/AppAuth-iOS/issues/105 + // If the initializer relaxes it's restriction on the response type field, this code must also + // be updated to re-enable use of the serialized responseType value. The value of 'code' here + // is only a valid assumption for that reason. + // [aDecoder decodeObjectOfClass:[NSString class] forKey:kResponseTypeKey]; + NSString *responseType = OIDResponseTypeCode; + 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 *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 + 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:_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]), + 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 sha265: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 (_codeChallenge) { + [query addParameter:kCodeChallengeKey value:_codeChallenge]; + } + if (_codeChallengeMethod) { + [query addParameter:kCodeChallengeMethodKey value:_codeChallengeMethod]; + } + + // Construct the URL: + return [query URLByReplacingQueryInURL:_configuration.authorizationEndpoint]; +} + +@end diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationResponse.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationResponse.h new file mode 100644 index 00000000..57b3af7b --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationResponse.h @@ -0,0 +1,139 @@ +/*! @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 { + // property variables + OIDAuthorizationRequest *_request; + NSString *_authorizationCode; + NSString *_state; + NSString *_accessToken; + NSDate *_accessTokenExpirationDate; + NSString *_tokenType; + NSString *_idToken; + NSString *_scope; + NSDictionary *> *_additionalParameters; +} + +/*! @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 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:(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/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationResponse.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationResponse.m new file mode 100644 index 00000000..df5d6c5a --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationResponse.m @@ -0,0 +1,219 @@ +/*! @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" + +/*! @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 + +@synthesize request = _request; +@synthesize authorizationCode = _authorizationCode; +@synthesize state = _state; +@synthesize accessToken = _accessToken; +@synthesize accessTokenExpirationDate = _accessTokenExpirationDate; +@synthesize tokenType = _tokenType; +@synthesize idToken = _idToken; +@synthesize scope = _scope; +@synthesize additionalParameters = _additionalParameters; + +/*! @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]), + self, + _authorizationCode, + _state, + _accessToken, + _accessTokenExpirationDate, + _tokenType, + _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:_scope + refreshToken:nil + codeVerifier:_request.codeVerifier + additionalParameters:additionalParameters]; +} + +@end diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationService.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationService.h new file mode 100644 index 00000000..fe80c0b4 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationService.h @@ -0,0 +1,176 @@ +/*! @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 OIDRegistrationRequest; +@class OIDRegistrationResponse; +@class OIDServiceConfiguration; +@class OIDTokenRequest; +@class OIDTokenResponse; +@protocol OIDAuthorizationFlowSession; +@protocol OIDAuthorizationUICoordinator; + +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 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 { + // property variables + OIDServiceConfiguration *_configuration; +} + +/*! @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 UICoordinator Generic authorization UI coordinator that can present an authorization + request. + @param callback The method called when the request has completed or failed. + @return A @c OIDAuthorizationFlowSession instance which will terminate when it + receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a + @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. + */ ++ (id) + presentAuthorizationRequest:(OIDAuthorizationRequest *)request + UICoordinator:(id)UICoordinator + callback:(OIDAuthorizationCallback)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 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 + +/*! @brief Represents an in-flight authorization flow session. + */ +@protocol OIDAuthorizationFlowSession + +/*! @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 OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: 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 Clients should call this method with the result of the authorization code flow if it + becomes available. + @param URL The redirect URL invoked by the authorization server. + @discussion When the URL represented a valid authorization response, implementations + should clean up any left-over UI state from the authorization, for example by + closing the \SFSafariViewController or looback HTTP listener if those were used. + The completion block of the pending authorization 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)resumeAuthorizationFlowWithURL:(NSURL *)URL; + +/*! @brief @c OIDAuthorizationUICoordinator or clients should call this method when the + authorization flow failed with a non-OAuth error. + @param error The error that is the reason for the failure of this authorization flow. + @remarks Has no effect if called more than once, or after a @c cancel message was received. + */ +- (void)failAuthorizationFlowWithError:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationService.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationService.m new file mode 100644 index 00000000..b072f718 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationService.m @@ -0,0 +1,481 @@ +/*! @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 "OIDAuthorizationUICoordinator.h" +#import "OIDDefines.h" +#import "OIDErrorUtilities.h" +#import "OIDRegistrationRequest.h" +#import "OIDRegistrationResponse.h" +#import "OIDServiceConfiguration.h" +#import "OIDServiceDiscovery.h" +#import "OIDTokenRequest.h" +#import "OIDTokenResponse.h" +#import "OIDURLQueryComponent.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 The state authorization parameter. + */ +static NSString *const kStateParameter = @"state"; + +NS_ASSUME_NONNULL_BEGIN + +@interface OIDAuthorizationFlowSessionImplementation : NSObject { + // private variables + OIDAuthorizationRequest *_request; + id _UICoordinator; + OIDAuthorizationCallback _pendingauthorizationFlowCallback; +} + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request + NS_DESIGNATED_INITIALIZER; + +@end + +@implementation OIDAuthorizationFlowSessionImplementation + +- (instancetype)initWithRequest:(OIDAuthorizationRequest *)request { + self = [super init]; + if (self) { + _request = [request copy]; + } + return self; +} + +- (void)presentAuthorizationWithCoordinator:(id)UICoordinator + callback:(OIDAuthorizationCallback)authorizationFlowCallback { + _UICoordinator = UICoordinator; + _pendingauthorizationFlowCallback = authorizationFlowCallback; + NSURL *URL = [_request authorizationRequestURL]; + BOOL authorizationFlowStarted = [_UICoordinator presentAuthorizationWithURL:URL session:self]; + if (!authorizationFlowStarted) { + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeSafariOpenError + underlyingError:nil + description:@"Unable to open Safari."]; + [self didFinishWithResponse:nil error:safariError]; + } +} + +- (void)cancel { + [_UICoordinator dismissAuthorizationAnimated:YES + completion:^{ + NSError *error = [OIDErrorUtilities + errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow + underlyingError:nil + description:nil]; + [self didFinishWithResponse:nil error:error]; + }]; +} + +- (BOOL)shouldHandleURL:(NSURL *)URL { + NSURL *standardizedURL = [URL standardizedURL]; + NSURL *standardizedRedirectURL = [_request.redirectURL standardizedURL]; + + return OIDIsEqualIncludingNil(standardizedURL.scheme, standardizedRedirectURL.scheme) && + 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)resumeAuthorizationFlowWithURL:(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 (!_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]; + } + + // verifies that the state in the response matches the state in the request, or both are nil + if (!OIDIsEqualIncludingNil(_request.state, query.dictionaryValue[kStateParameter])) { + 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]; + } + + // no error, should be a valid OAuth 2.0 response + if (!error) { + response = [[OIDAuthorizationResponse alloc] initWithRequest:_request + parameters:query.dictionaryValue]; + } + + [_UICoordinator dismissAuthorizationAnimated:YES + completion:^{ + [self didFinishWithResponse:response error:error]; + }]; + + return YES; +} + +- (void)failAuthorizationFlowWithError:(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; + _UICoordinator = nil; + if (callback) { + callback(response, error); + } +} + +@end + +@implementation OIDAuthorizationService + +@synthesize configuration = _configuration; + ++ (void)discoverServiceConfigurationForIssuer:(NSURL *)issuerURL + completion:(OIDDiscoveryCallback)completion { + NSURL *fullDiscoveryURL = + [issuerURL URLByAppendingPathComponent:kOpenIDConfigurationWellKnownPath]; + + return [[self class] discoverServiceConfigurationForDiscoveryURL:fullDiscoveryURL + completion:completion]; +} + ++ (void)discoverServiceConfigurationForDiscoveryURL:(NSURL *)discoveryURL + completion:(OIDDiscoveryCallback)completion { + + if (@available(macOS 10.9, *)) { + NSURLSession *session = [NSURLSession sharedSession]; + 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) { + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:error.localizedDescription]; + 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]; + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:URLResponseError + description:nil]; + 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) { + error = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:nil]; + 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]; + } else { + // Fallback on earlier versions + } + + +} + +#pragma mark - Authorization Endpoint + ++ (id) + presentAuthorizationRequest:(OIDAuthorizationRequest *)request + UICoordinator:(id)UICoordinator + callback:(OIDAuthorizationCallback)callback { + OIDAuthorizationFlowSessionImplementation *flowSession = + [[OIDAuthorizationFlowSessionImplementation alloc] initWithRequest:request]; + [flowSession presentAuthorizationWithCoordinator:UICoordinator callback:callback]; + return flowSession; +} + +#pragma mark - Token Endpoint + ++ (void)performTokenRequest:(OIDTokenRequest *)request callback:(OIDTokenCallback)callback { + NSURLRequest *URLRequest = [request URLRequest]; + if (@available(macOS 10.9, *)) { + NSURLSession *session = [NSURLSession sharedSession]; + [[session dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // A network error or server error occurred. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response; + NSInteger statusCode = HTTPURLResponse.statusCode; + if (statusCode != 200) { + // A server error occurred. + NSError *serverError = + [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse data:data]; + + // HTTP 400 may indicate an RFC6749 Section 5.2 error response. + // HTTP 429 may occur during polling for device-flow requests for the slow_down error + // https://tools.ietf.org/html/draft-ietf-oauth-device-flow-03#section-3.5 + if (statusCode == 400 || statusCode == 429) { + 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:OIDOAuthTokenErrorDomain + OAuthResponse:json + underlyingError:serverError]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, oauthError); + }); + return; + } + } + + // not an OAuth error, just a generic server error + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError + underlyingError:serverError + description:nil]; + 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. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonDeserializationError + description:nil]; + 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:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, returnedError); + }); + return; + } + + // Success + dispatch_async(dispatch_get_main_queue(), ^{ + callback(tokenResponse, nil); + }); + }] resume]; + } else { + // Fallback on earlier versions + } + +} + + +#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; + } + + if (@available(macOS 10.9, *)) { + NSURLSession *session = [NSURLSession sharedSession]; + [[session dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // A network error or server error occurred. + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:nil]; + 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 + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError + underlyingError:serverError + description:nil]; + 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. + NSError *returnedError = [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonDeserializationError + description:nil]; + 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:jsonDeserializationError + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, returnedError); + }); + return; + } + + // Success + dispatch_async(dispatch_get_main_queue(), ^{ + completion(registrationResponse, nil); + }); + }] resume]; + } else { + // Fallback on earlier versions + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationUICoordinator.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationUICoordinator.h new file mode 100644 index 00000000..a20de6b7 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDAuthorizationUICoordinator.h @@ -0,0 +1,51 @@ +/*! @file OIDAuthorizationUICoordinator.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 OIDAuthorizationFlowSession; + +NS_ASSUME_NONNULL_BEGIN + +/*! @protocol OIDAuthorizationUICoordinator + @brief An authorization UI coordinator that presents an authorization request. Clients may + provide custom implementations of an authorization UI coordinator to customize the way the + authorization request is presented to the user. + */ +@protocol OIDAuthorizationUICoordinator + +/*! @brief Presents the authorization UI for the given URL. + @param URL The URL that should be used when presenting the authorization UI. + @param session The @c OIDAuthorizationFlowSession instance that initiates presenting the + authorization UI. Concrete implementations of a @c OIDAuthorizationUICoordinator may call + resumeAuthorizationFlowWithURL or failAuthorizationFlowWithError on session to either + resume or fail the authorization. + @return YES If the authorization UI was successfully presented to the user. + */ +- (BOOL)presentAuthorizationWithURL:(NSURL *)URL session:(id)session; + +/*! @brief Dimisses the authorization UI and calls completion when the dismiss operation ends. + @param animated Wheter or not the dismiss operation should be animated. + @remarks Has no effect if no authorization UI is presented. + @param completion The block to be called when the dismiss operations ends + */ +- (void)dismissAuthorizationAnimated:(BOOL)animated completion:(void (^)(void))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDClientMetadataParameters.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDClientMetadataParameters.h new file mode 100644 index 00000000..39ea2d62 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDClientMetadataParameters.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDClientMetadataParameters.m new file mode 100644 index 00000000..79ad4676 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDDefines.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDDefines.h new file mode 100644 index 00000000..0dccf842 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDDefines.h @@ -0,0 +1,44 @@ +/*! @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]; \ +} diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDError.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDError.h new file mode 100644 index 00000000..229802cc --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDError.h @@ -0,0 +1,381 @@ +/*! @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 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 OIDAuthorizationFlowSession receives a message after it has already completed. + */ +extern NSString *const OIDOAuthExceptionInvalidAuthorizationFlow; + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDError.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDError.m new file mode 100644 index 00000000..697e63ac --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDError.m @@ -0,0 +1,42 @@ +/*! @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 " + "OIDAuthorizationFlowSession after it already completed."; + +NSString *const OIDOAuthErrorResponseErrorKey = @"OIDOAuthErrorResponseErrorKey"; + +NSString *const OIDOAuthErrorFieldError = @"error"; + +NSString *const OIDOAuthErrorFieldErrorDescription = @"error_description"; + +NSString *const OIDOAuthErrorFieldErrorURI = @"error_uri"; diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDErrorUtilities.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDErrorUtilities.h new file mode 100644 index 00000000..3380f6fe --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDErrorUtilities.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDErrorUtilities.m new file mode 100644 index 00000000..3e4e305b --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDErrorUtilities.m @@ -0,0 +1,161 @@ +/*! @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]) { + 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 = errorResponse[OIDOAuthErrorFieldErrorDescription]; + NSString *oauthErrorURI = errorResponse[OIDOAuthErrorFieldErrorURI]; + + // 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDFieldMapping.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDFieldMapping.h new file mode 100644 index 00000000..22d91592 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDFieldMapping.h @@ -0,0 +1,131 @@ +/*! @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 { + // property variables + NSString *_name; + Class _expectedType; + OIDFieldMappingConversionFunction _conversion; +} + +/*! @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/Example-macOS-Swift/Pods/AppAuth/Source/OIDFieldMapping.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDFieldMapping.m new file mode 100644 index 00000000..bb1c8182 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDFieldMapping.m @@ -0,0 +1,136 @@ +/*! @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 + +@synthesize name = _name; +@synthesize expectedType = _expectedType; +@synthesize conversion = _conversion; + +- (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/Example-macOS-Swift/Pods/AppAuth/Source/OIDGrantTypes.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDGrantTypes.h new file mode 100644 index 00000000..6e650047 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDGrantTypes.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDGrantTypes.m new file mode 100644 index 00000000..2b193437 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationRequest.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationRequest.h new file mode 100644 index 00000000..47cf0635 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationRequest.h @@ -0,0 +1,118 @@ +/*! @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 { + // property variables + OIDServiceConfiguration *_configuration; + NSString *_applicationType; + NSArray *_redirectURIs; + NSArray *_responseTypes; + NSArray *_grantTypes; + NSString *_subjectType; + NSString *_tokenEndpointAuthenticationMethod; + NSDictionary *_additionalParameters; +} + +/*! @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 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 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 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 + 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationRequest.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationRequest.m new file mode 100644 index 00000000..e6018af3 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationRequest.m @@ -0,0 +1,224 @@ +/*! @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 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 + +@synthesize configuration = _configuration; +@synthesize applicationType = _applicationType; +@synthesize redirectURIs = _redirectURIs; +@synthesize responseTypes = _responseTypes; +@synthesize grantTypes = _grantTypes; +@synthesize subjectType = _subjectType; +@synthesize tokenEndpointAuthenticationMethod = _tokenEndpointAuthenticationMethod; +@synthesize additionalParameters = _additionalParameters; + +#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 { + self = [super init]; + if (self) { + _configuration = [configuration 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]; + 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 + additionalParameters:additionalParameters]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_configuration forKey:kConfigurationKey]; + [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]), + self, + request.URL, + requestBody]; +} + +- (NSURLRequest *)URLRequest { + static NSString *const kHTTPPost = @"POST"; + static NSString *const kHTTPContentTypeHeaderKey = @"Content-Type"; + static NSString *const kHTTPContentTypeHeaderValue = @"application/json"; + + 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]; + 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationResponse.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationResponse.h new file mode 100644 index 00000000..f7fec771 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationResponse.h @@ -0,0 +1,137 @@ +/*! @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 { + // property variables + OIDRegistrationRequest *_request; + NSString *_clientID; + NSDate *_clientIDIssuedAt; + NSString *_clientSecret; + NSDate *_clientSecretExpiresAt; + NSString *_registrationAccessToken; + NSURL *_registrationClientURI; + NSString *_tokenEndpointAuthenticationMethod; + NSDictionary *> *_additionalParameters; +} + +/*! @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/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationResponse.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationResponse.m new file mode 100644 index 00000000..251a9918 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDRegistrationResponse.m @@ -0,0 +1,173 @@ +/*! @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" + +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 + +@synthesize request = _request; +@synthesize clientID = _clientID; +@synthesize clientIDIssuedAt = _clientIDIssuedAt; +@synthesize clientSecret = _clientSecret; +@synthesize clientSecretExpiresAt = _clientSecretExpiresAt; +@synthesize registrationAccessToken = _registrationAccessToken; +@synthesize registrationClientURI = _registrationClientURI; +@synthesize tokenEndpointAuthenticationMethod = _tokenEndpointAuthenticationMethod; +@synthesize additionalParameters = _additionalParameters; + +/*! @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]), + self, + _clientID, + _clientIDIssuedAt, + _clientSecret, + _clientSecretExpiresAt, + _registrationAccessToken, + _registrationClientURI, + _additionalParameters, + _request]; +} + +@end diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDResponseTypes.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDResponseTypes.h new file mode 100644 index 00000000..405ef938 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDResponseTypes.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDResponseTypes.m new file mode 100644 index 00000000..78eaf187 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDScopeUtilities.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDScopeUtilities.h new file mode 100644 index 00000000..c0a5190c --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDScopeUtilities.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDScopeUtilities.m new file mode 100644 index 00000000..a0bcb8c8 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDScopes.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDScopes.h new file mode 100644 index 00000000..da8bb189 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDScopes.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDScopes.m new file mode 100644 index 00000000..62dd707e --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/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/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceConfiguration.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceConfiguration.h new file mode 100644 index 00000000..513e5d97 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceConfiguration.h @@ -0,0 +1,86 @@ +/*! @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 { + // property variables + NSURL *_authorizationEndpoint; + NSURL *_tokenEndpoint; + NSURL *_registrationEndpoint; + OIDServiceDiscovery *_discoveryDocument; +} + +/*! @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 dynamic client registration endpoint URI. + */ +@property(nonatomic, readonly, nullable) NSURL *registrationEndpoint; + +/*! @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 discoveryDocument The discovery document from which to extract the required OAuth + configuration. + */ +- (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *)discoveryDocument; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceConfiguration.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceConfiguration.m new file mode 100644 index 00000000..d3121c28 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceConfiguration.m @@ -0,0 +1,164 @@ +/*! @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 registrationEndpoint property. + */ +static NSString *const kRegistrationEndpointKey = @"registrationEndpoint"; + +/*! @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 + registrationEndpoint:(nullable NSURL *)registrationEndpoint + discoveryDocument:(nullable OIDServiceDiscovery *)discoveryDocument + NS_DESIGNATED_INITIALIZER; + +@end + +@implementation OIDServiceConfiguration + +@synthesize authorizationEndpoint = _authorizationEndpoint; +@synthesize tokenEndpoint = _tokenEndpoint; +@synthesize registrationEndpoint = _registrationEndpoint; +@synthesize discoveryDocument = _discoveryDocument; + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector( + initWithAuthorizationEndpoint: + tokenEndpoint: + registrationEndpoint:) + ); + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + registrationEndpoint:(nullable NSURL *)registrationEndpoint + discoveryDocument:(nullable OIDServiceDiscovery *)discoveryDocument { + + self = [super init]; + if (self) { + _authorizationEndpoint = [authorizationEndpoint copy]; + _tokenEndpoint = [tokenEndpoint copy]; + _registrationEndpoint = [registrationEndpoint copy]; + _discoveryDocument = [discoveryDocument copy]; + } + return self; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + registrationEndpoint:nil + discoveryDocument:nil]; +} + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + registrationEndpoint:(nullable NSURL *)registrationEndpoint { + return [self initWithAuthorizationEndpoint:authorizationEndpoint + tokenEndpoint:tokenEndpoint + registrationEndpoint:registrationEndpoint + discoveryDocument:nil]; +} + +- (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *) discoveryDocument { + return [self initWithAuthorizationEndpoint:discoveryDocument.authorizationEndpoint + tokenEndpoint:discoveryDocument.tokenEndpoint + registrationEndpoint:discoveryDocument.registrationEndpoint + 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 *registrationEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kRegistrationEndpointKey]; + // 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 + registrationEndpoint:registrationEndpoint + discoveryDocument:discoveryDocument]; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:_authorizationEndpoint forKey:kAuthorizationEndpointKey]; + [aCoder encodeObject:_tokenEndpoint forKey:kTokenEndpointKey]; + [aCoder encodeObject:_registrationEndpoint forKey:kRegistrationEndpointKey]; + [aCoder encodeObject:_discoveryDocument forKey:kDiscoveryDocumentKey]; +} + +#pragma mark - description + +- (NSString *)description { + return [NSString stringWithFormat: + @"OIDServiceConfiguration authorizationEndpoint: %@, tokenEndpoint: %@, " + "registrationEndpoint: %@, discoveryDocument: [%@]", + _authorizationEndpoint, + _tokenEndpoint, + _registrationEndpoint, + _discoveryDocument]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceDiscovery.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceDiscovery.h new file mode 100644 index 00000000..e3c8eda9 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceDiscovery.h @@ -0,0 +1,355 @@ +/*! @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 { + // private variables + NSDictionary *_discoveryDictionary; +} + +/*! @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 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceDiscovery.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceDiscovery.m new file mode 100644 index 00000000..56501057 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDServiceDiscovery.m @@ -0,0 +1,348 @@ +/*! @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 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 + +- (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:nil]; + 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 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenRequest.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenRequest.h new file mode 100644 index 00000000..c0261382 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenRequest.h @@ -0,0 +1,170 @@ +/*! @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 { + // property variables + OIDServiceConfiguration *_configuration; + NSString *_grantType; + NSString *_authorizationCode; + NSURL *_redirectURL; + NSString *_clientID; + NSString *_clientSecret; + NSString *_scope; + NSString *_refreshToken; + NSString *_codeVerifier; + NSDictionary *_additionalParameters; +} + +/*! @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) 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 scopes An array of scopes to combine into a single scope string per the OAuth2 spec. + @param refreshToken The refresh token. + @param additionalParameters The client's additional token request parameters. + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(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 scope The value of the scope parameter is expressed as a list of space-delimited, + case-sensitive strings. + @param refreshToken The refresh token. + @param additionalParameters The client's additional token request parameters. + */ +- (instancetype)initWithConfiguration:(OIDServiceConfiguration *)configuration + grantType:(NSString *)grantType + authorizationCode:(nullable NSString *)code + redirectURL:(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/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenRequest.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenRequest.m new file mode 100644 index 00000000..ec066f73 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenRequest.m @@ -0,0 +1,304 @@ +/*! @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 "OIDScopeUtilities.h" +#import "OIDServiceConfiguration.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 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 + +@synthesize configuration = _configuration; +@synthesize grantType = _grantType; +@synthesize authorizationCode = _authorizationCode; +@synthesize redirectURL = _redirectURL; +@synthesize clientID = _clientID; +@synthesize clientSecret = _clientSecret; +@synthesize scope = _scope; +@synthesize refreshToken = _refreshToken; +@synthesize codeVerifier = _codeVerifier; +@synthesize additionalParameters = _additionalParameters; + +- (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:(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:(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]; + } + 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]), + 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) { + NSString *credentials = [NSString stringWithFormat:@"%@:%@", _clientID, _clientSecret]; + NSData *plainData = [credentials dataUsingEncoding:NSUTF8StringEncoding]; + if (@available(macOS 10.9, *)) { + NSString *basicAuth = [plainData base64EncodedStringWithOptions:kNilOptions]; + + NSString *authValue = [NSString stringWithFormat:@"Basic %@", basicAuth]; + [httpHeaders setObject:authValue forKey:@"Authorization"]; + } else { + // Fallback on earlier versions + } + + } 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenResponse.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenResponse.h new file mode 100644 index 00000000..52a3e3a0 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenResponse.h @@ -0,0 +1,112 @@ +/*! @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 { + // property variables + OIDTokenRequest *_request; + NSString *_accessToken; + NSDate *_accessTokenExpirationDate; + NSString *_tokenType; + NSString *_idToken; + NSString *_refreshToken; + NSString *_scope; + NSDictionary *> *_additionalParameters; +} + +/*! @brief The request which was serviced. + */ +@property(nonatomic, readonly) OIDTokenRequest *request; + +/*! @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. + @remarks expires_in + @seealso OIDTokenResponse.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 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/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenResponse.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenResponse.m new file mode 100644 index 00000000..537d3878 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenResponse.m @@ -0,0 +1,171 @@ +/*! @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" + +/*! @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 + +@synthesize request = _request; +@synthesize accessToken = _accessToken; +@synthesize accessTokenExpirationDate = _accessTokenExpirationDate; +@synthesize tokenType = _tokenType; +@synthesize idToken = _idToken; +@synthesize refreshToken = _refreshToken; +@synthesize scope = _scope; +@synthesize additionalParameters = _additionalParameters; + +/*! @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]), + self, + _accessToken, + _accessTokenExpirationDate, + _tokenType, + _idToken, + _refreshToken, + _scope, + _additionalParameters, + _request]; +} + +#pragma mark - + +@end diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenUtilities.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenUtilities.h new file mode 100644 index 00000000..f13d937b --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenUtilities.h @@ -0,0 +1,55 @@ +/*! @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 *)sha265:(NSString *)inputString; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenUtilities.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenUtilities.m new file mode 100644 index 00000000..a3ce6d7e --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDTokenUtilities.m @@ -0,0 +1,55 @@ +/*! @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 + +@implementation OIDTokenUtilities + ++ (NSString *)encodeBase64urlNoPadding:(NSData *)data { + if (@available(macOS 10.9, *)) { + 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; + } else { + return @"String"; + } +} + ++ (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 *)sha265:(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; +} + +@end diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDURLQueryComponent.h b/Example-macOS-Swift/Pods/AppAuth/Source/OIDURLQueryComponent.h new file mode 100644 index 00000000..068dcc36 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDURLQueryComponent.h @@ -0,0 +1,84 @@ +/*! @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. + */ +@interface OIDURLQueryComponent : NSObject { + // private variables + /*! @brief A dictionary of parameter names and values representing the contents of the query. + */ + NSMutableDictionary *> *_parameters; +} + +/*! @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; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/OIDURLQueryComponent.m b/Example-macOS-Swift/Pods/AppAuth/Source/OIDURLQueryComponent.m new file mode 100644 index 00000000..b973e0dc --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/OIDURLQueryComponent.m @@ -0,0 +1,193 @@ +/*! @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 + +- (nullable instancetype)init { + self = [super init]; + if (self) { + _parameters = [NSMutableDictionary dictionary]; + } + return self; +} + +- (nullable instancetype)initWithURL:(NSURL *)URL { + self = [self init]; + if (self) { + if (!gOIDURLQueryComponentForceIOS7Handling && [NSURLQueryItem class]) { + // If NSURLQueryItem is available, use it for deconstructing the new URL. (iOS 8+) + NSURLComponents *components = + [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + NSArray *queryItems = components.queryItems; + for (NSURLQueryItem *queryItem in queryItems) { + [self addParameter:queryItem.name value:queryItem.value]; + } + } else { + // Fallback for iOS 7 + NSString *query = URL.query; + 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; +} + +- (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 { + 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; +} + +/*! @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 = + [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; + // Removes additional characters we don't want to see in the query component. + [allowedParamCharacters removeCharactersInString:kQueryStringParamAdditionalDisallowedCharacters]; + + 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 (!gOIDURLQueryComponentForceIOS7Handling && [NSURLQueryItem class]) { + 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]), + self, + _parameters]; +} + +@end diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h new file mode 100644 index 00000000..f60803c1 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h @@ -0,0 +1,188 @@ +/*! @file OIDLoopbackHTTPServer.h + @brief AppAuth iOS SDK + @copyright + Copyright 2016 The AppAuth Authors. + @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. + */ + +// Based on the MiniSOAP Sample +// https://developer.apple.com/library/mac/samplecode/MiniSOAP/Introduction/Intro.html +// Modified to limit connections to the loopback interface only. + +#import +#import + +@class HTTPConnection, HTTPServerRequest, TCPServer; + +extern NSString * const TCPServerErrorDomain; + +typedef enum { + kTCPServerCouldNotBindToIPv4Address = 1, + kTCPServerCouldNotBindToIPv6Address = 2, + kTCPServerNoSocketsAvailable = 3, +} TCPServerErrorCode; + +@protocol TCPServerDelegate + +- (void)TCPServer:(TCPServer *)server + didReceiveConnectionFromAddress:(NSData *)addr + inputStream:(NSInputStream *)istr + outputStream:(NSOutputStream *)ostr; + +@end + +@interface TCPServer : NSObject { +@private + __weak id delegate; + NSString *domain; + NSString *name; + NSString *type; + uint16_t port; + CFSocketRef ipv4socket; + CFSocketRef ipv6socket; + NSNetService *netService; +} + +- (id)delegate; +- (void)setDelegate:(id)value; + +- (NSString *)domain; +- (void)setDomain:(NSString *)value; + +- (NSString *)name; +- (void)setName:(NSString *)value; + +- (NSString *)type; +- (void)setType:(NSString *)value; + +- (uint16_t)port; +- (void)setPort:(uint16_t)value; + +- (BOOL)start:(NSError **)error; +- (BOOL)stop; + +- (BOOL)hasIPv4Socket; +- (BOOL)hasIPv6Socket; + +// called when a new connection comes in; by default, informs the delegate +- (void)handleNewConnectionFromAddress:(NSData *)addr + inputStream:(NSInputStream *)istr + outputStream:(NSOutputStream *)ostr; + +@end + +@interface HTTPServer : TCPServer { +@private + Class connClass; + NSURL *docRoot; + // Currently active connections spawned from the HTTPServer. + NSMutableArray *connections; +} + +- (Class)connectionClass; +// used to configure the subclass of HTTPConnection to create when +// a new connection comes in; by default, this is HTTPConnection +- (void)setConnectionClass:(Class)value; + +@end + +@interface HTTPServer (HTTPServerDelegateMethods) +// If the delegate implements this method, this is called +// by an HTTPServer when a new connection comes in. If the +// delegate wishes to refuse the connection, then it should +// invalidate the connection object from within this method. +- (void)HTTPServer:(HTTPServer *)serv didMakeNewConnection:(HTTPConnection *)conn; +@end + + +// This class represents each incoming client connection. +@interface HTTPConnection : NSObject { +@private + __weak id delegate; + NSData *peerAddress; + __weak HTTPServer *server; + NSMutableArray *requests; + NSInputStream *istream; + NSOutputStream *ostream; + NSMutableData *ibuffer; + NSMutableData *obuffer; + BOOL isValid; + BOOL firstResponseDone; +} + +- (id)initWithPeerAddress:(NSData *)addr + inputStream:(NSInputStream *)istr + outputStream:(NSOutputStream *)ostr + forServer:(HTTPServer *)serv; + +- (id)delegate; +- (void)setDelegate:(id)value; + +- (NSData *)peerAddress; + +- (HTTPServer *)server; + +// get the next request that needs to be responded to +- (HTTPServerRequest *)nextRequest; + +- (BOOL)isValid; +// shut down the connection +- (void)invalidate; + +// perform the default handling action: GET and HEAD requests for files +// in the local file system (relative to the documentRoot of the server) +- (void)performDefaultRequestHandling:(HTTPServerRequest *)sreq; + +@end + +@interface HTTPConnection (HTTPConnectionDelegateMethods) +// The "didReceiveRequest:" tells the delegate when a new request comes in. +- (void)HTTPConnection:(HTTPConnection *)conn didReceiveRequest:(HTTPServerRequest *)mess; +- (void)HTTPConnection:(HTTPConnection *)conn didSendResponse:(HTTPServerRequest *)mess; +@end + + +// As NSURLRequest and NSURLResponse are not entirely suitable for use from +// the point of view of an HTTP server, we use CFHTTPMessageRef to encapsulate +// requests and responses. This class packages the (future) response with a +// request and other info for convenience. +@interface HTTPServerRequest : NSObject { +@private + HTTPConnection *connection; + CFHTTPMessageRef request; + CFHTTPMessageRef response; + NSInputStream *responseStream; +} + +- (id)initWithRequest:(CFHTTPMessageRef)req connection:(HTTPConnection *)conn; + +- (HTTPConnection *)connection; + +- (CFHTTPMessageRef)request; + +// The response may include a body. As soon as the response is set, +// the response may be written out to the network. +- (CFHTTPMessageRef)response; +- (void)setResponse:(CFHTTPMessageRef)value; + +- (NSInputStream *)responseBodyStream; +// If there is to be a response body stream (when, say, a big +// file is to be returned, rather than reading the whole thing +// into memory), then it must be set on the request BEFORE the +// response [headers] itself. +- (void)setResponseBodyStream:(NSInputStream *)value; + +@end + + diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m new file mode 100644 index 00000000..bf66f017 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m @@ -0,0 +1,607 @@ +/*! @file OIDLoopbackHTTPServer.m + @brief AppAuth iOS SDK + @copyright + Copyright 2016 The AppAuth Authors. + @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 "OIDLoopbackHTTPServer.h" +#include +#include +#include + +@implementation HTTPServer + +- (id)init { + self = [super init]; + connClass = [HTTPConnection self]; + connections = [[NSMutableArray alloc] init]; + return self; +} + +- (Class)connectionClass { + return connClass; +} + +- (void)setConnectionClass:(Class)value { + connClass = value; +} + +// Removes the connection from the list of active connections. +- (void)removeConnection:(HTTPConnection *)connection { + [connections removeObject:connection]; +} + +// Converts the TCPServer delegate notification into the HTTPServer delegate method. +- (void)handleNewConnectionFromAddress:(NSData *)addr inputStream:(NSInputStream *)istr outputStream:(NSOutputStream *)ostr { + HTTPConnection *connection = [[connClass alloc] initWithPeerAddress:addr inputStream:istr outputStream:ostr forServer:self]; + // Adds connection to the active connection list to retain it. + [connections addObject:connection]; + [connection setDelegate:[self delegate]]; + if ([self delegate] && [[self delegate] respondsToSelector:@selector(HTTPServer:didMakeNewConnection:)]) { + [[self delegate] HTTPServer:self didMakeNewConnection:connection]; + } +} + +@end + + +@implementation HTTPConnection + +- (id)init { + return nil; +} + +- (id)initWithPeerAddress:(NSData *)addr inputStream:(NSInputStream *)istr outputStream:(NSOutputStream *)ostr forServer:(HTTPServer *)serv { + peerAddress = [addr copy]; + server = serv; + istream = istr; + ostream = ostr; + [istream setDelegate:self]; + [ostream setDelegate:self]; + [istream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:(id)kCFRunLoopCommonModes]; + [ostream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:(id)kCFRunLoopCommonModes]; + [istream open]; + [ostream open]; + isValid = YES; + return self; +} + +- (void)dealloc { + [self invalidate]; +} + +- (id)delegate { + return delegate; +} + +- (void)setDelegate:(id)value { + delegate = value; +} + +- (NSData *)peerAddress { + return peerAddress; +} + +- (HTTPServer *)server { + return server; +} + +- (HTTPServerRequest *)nextRequest { + NSUInteger idx, cnt = requests ? [requests count] : 0; + for (idx = 0; idx < cnt; idx++) { + id obj = [requests objectAtIndex:idx]; + if ([obj response] == nil) { + return obj; + } + } + return nil; +} + +- (BOOL)isValid { + return isValid; +} + +- (void)invalidate { + if (isValid) { + isValid = NO; + [server removeConnection:self]; + [istream setDelegate:nil]; + [ostream setDelegate:nil]; + [istream close]; + [ostream close]; + istream = nil; + ostream = nil; + ibuffer = nil; + obuffer = nil; + requests = nil; + } +} + +// YES return means that a complete request was parsed, and the caller +// should call again as the buffered bytes may have another complete +// request available. +- (BOOL)processIncomingBytes { + CFHTTPMessageRef working = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE); + CFHTTPMessageAppendBytes(working, [ibuffer bytes], [ibuffer length]); + + // This "try and possibly succeed" approach is potentially expensive + // (lots of bytes being copied around), but the only API available for + // the server to use, short of doing the parsing itself. + + // HTTPConnection does not handle the chunked transfer encoding + // described in the HTTP spec. And if there is no Content-Length + // header, then the request is the remainder of the stream bytes. + + if (CFHTTPMessageIsHeaderComplete(working)) { + NSString *contentLengthValue = (__bridge_transfer NSString *)CFHTTPMessageCopyHeaderFieldValue(working, (CFStringRef)@"Content-Length"); + + unsigned contentLength = contentLengthValue ? [contentLengthValue intValue] : 0; + NSData *body = (__bridge_transfer NSData *)CFHTTPMessageCopyBody(working); + NSUInteger bodyLength = [body length]; + if (contentLength <= bodyLength) { + NSData *newBody = [NSData dataWithBytes:[body bytes] length:contentLength]; + [ibuffer setLength:0]; + [ibuffer appendBytes:([body bytes] + contentLength) length:(bodyLength - contentLength)]; + CFHTTPMessageSetBody(working, (__bridge CFDataRef)newBody); + } else { + CFRelease(working); + return NO; + } + } else { + return NO; + } + + HTTPServerRequest *request = [[HTTPServerRequest alloc] initWithRequest:working connection:self]; + if (!requests) { + requests = [[NSMutableArray alloc] init]; + } + [requests addObject:request]; + if (delegate && [delegate respondsToSelector:@selector(HTTPConnection:didReceiveRequest:)]) { + // Schedules the delegate to be executed later on the main thread. Cannot call the delegate + // directly as this method is called in a loop in order to process multiple messages, and + // the delegate may choose to stop and dealloc the listener – so we need queue the messages + // and process them separately. + dispatch_async(dispatch_get_main_queue(), ^() { + [delegate HTTPConnection:self didReceiveRequest:request]; + }); + } else { + [self performDefaultRequestHandling:request]; + } + + CFRelease(working); + return YES; +} + +- (void)processOutgoingBytes { + // The HTTP headers, then the body if any, then the response stream get + // written out, in that order. The Content-Length: header is assumed to + // be properly set in the response. Outgoing responses are processed in + // the order the requests were received (required by HTTP). + + // Write as many bytes as possible, from buffered bytes, response + // headers and body, and response stream. + + if (![ostream hasSpaceAvailable]) { + return; + } + + NSUInteger olen = [obuffer length]; + if (0 < olen) { + NSInteger writ = [ostream write:[obuffer bytes] maxLength:olen]; + // buffer any unwritten bytes for later writing + if (writ < olen) { + memmove([obuffer mutableBytes], [obuffer mutableBytes] + writ, olen - writ); + [obuffer setLength:olen - writ]; + return; + } + [obuffer setLength:0]; + } + + NSUInteger cnt = requests ? [requests count] : 0; + HTTPServerRequest *req = (0 < cnt) ? [requests objectAtIndex:0] : nil; + + CFHTTPMessageRef cfresp = req ? [req response] : NULL; + if (!cfresp) return; + + if (!obuffer) { + obuffer = [[NSMutableData alloc] init]; + } + + if (!firstResponseDone) { + firstResponseDone = YES; + NSData *serialized = (__bridge_transfer NSData *)CFHTTPMessageCopySerializedMessage(cfresp); + NSUInteger olen = [serialized length]; + if (0 < olen) { + NSInteger writ = [ostream write:[serialized bytes] maxLength:olen]; + if (writ < olen) { + // buffer any unwritten bytes for later writing + [obuffer setLength:(olen - writ)]; + memmove([obuffer mutableBytes], [serialized bytes] + writ, olen - writ); + return; + } + } + } + + NSInputStream *respStream = [req responseBodyStream]; + if (respStream) { + if ([respStream streamStatus] == NSStreamStatusNotOpen) { + [respStream open]; + } + // read some bytes from the stream into our local buffer + [obuffer setLength:16 * 1024]; + NSInteger read = [respStream read:[obuffer mutableBytes] maxLength:[obuffer length]]; + [obuffer setLength:read]; + } + + if (0 == [obuffer length]) { + // When we get to this point with an empty buffer, then the + // processing of the response is done. If the input stream + // is closed or at EOF, then no more requests are coming in. + if (delegate && [delegate respondsToSelector:@selector(HTTPConnection:didSendResponse:)]) { + [delegate HTTPConnection:self didSendResponse:req]; + } + [requests removeObjectAtIndex:0]; + firstResponseDone = NO; + if ([istream streamStatus] == NSStreamStatusAtEnd && [requests count] == 0) { + [self invalidate]; + } + return; + } + + olen = [obuffer length]; + if (0 < olen) { + NSInteger writ = [ostream write:[obuffer bytes] maxLength:olen]; + // buffer any unwritten bytes for later writing + if (writ < olen) { + memmove([obuffer mutableBytes], [obuffer mutableBytes] + writ, olen - writ); + } + [obuffer setLength:olen - writ]; + } +} + +- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)streamEvent { + switch(streamEvent) { + case NSStreamEventHasBytesAvailable:; + uint8_t buf[16 * 1024]; + uint8_t *buffer = NULL; + NSUInteger len = 0; + if (![istream getBuffer:&buffer length:&len]) { + NSInteger amount = [istream read:buf maxLength:sizeof(buf)]; + buffer = buf; + len = amount; + } + if (0 < len) { + if (!ibuffer) { + ibuffer = [[NSMutableData alloc] init]; + } + [ibuffer appendBytes:buffer length:len]; + } + do {} while ([self processIncomingBytes]); + break; + case NSStreamEventHasSpaceAvailable:; + [self processOutgoingBytes]; + break; + case NSStreamEventEndEncountered:; + [self processIncomingBytes]; + if (stream == ostream) { + // When the output stream is closed, no more writing will succeed and + // will abandon the processing of any pending requests and further + // incoming bytes. + [self invalidate]; + } + break; + case NSStreamEventErrorOccurred:; + NSLog(@"HTTPServer stream error: %@", [stream streamError]); + break; + default: + break; + } +} + +- (void)performDefaultRequestHandling:(HTTPServerRequest *)mess { + CFHTTPMessageRef request = [mess request]; + + NSString *vers = (__bridge_transfer id)CFHTTPMessageCopyVersion(request); + if (!vers || ![vers isEqual:(id)kCFHTTPVersion1_1]) { + CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 505, NULL, (__bridge CFStringRef)vers); // Version Not Supported + [mess setResponse:response]; + CFRelease(response); + return; + } + + // 500s all requests when no delegate set to handle them. + CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 500, NULL, kCFHTTPVersion1_1); // Bad Request + [mess setResponse:response]; + CFRelease(response); +} + +@end + + +@implementation HTTPServerRequest + +- (id)init { + return nil; +} + +- (id)initWithRequest:(CFHTTPMessageRef)req connection:(HTTPConnection *)conn { + connection = conn; + request = (CFHTTPMessageRef)CFRetain(req); + return self; +} + +- (void)dealloc { + if (request) CFRelease(request); + if (response) CFRelease(response); +} + +- (HTTPConnection *)connection { + return connection; +} + +- (CFHTTPMessageRef)request { + return request; +} + +- (CFHTTPMessageRef)response { + return response; +} + +- (void)setResponse:(CFHTTPMessageRef)value { + if (value != response) { + if (response) CFRelease(response); + response = (CFHTTPMessageRef)CFRetain(value); + if (response) { + // check to see if the response can now be sent out + [connection processOutgoingBytes]; + } + } +} + +- (NSInputStream *)responseBodyStream { + return responseStream; +} + +- (void)setResponseBodyStream:(NSInputStream *)value { + if (value != responseStream) { + responseStream = value; + } +} + +@end + +NSString * const TCPServerErrorDomain = @"TCPServerErrorDomain"; + +@implementation TCPServer + +- (id)init { + return self; +} + +- (void)dealloc { + [self stop]; +} + +- (id)delegate { + return delegate; +} + +- (void)setDelegate:(id)value { + delegate = value; +} + +- (NSString *)domain { + return domain; +} + +- (void)setDomain:(NSString *)value { + if (domain != value) { + domain = [value copy]; + } +} + +- (NSString *)name { + return name; +} + +- (void)setName:(NSString *)value { + if (name != value) { + name = [value copy]; + } +} + +- (NSString *)type { + return type; +} + +- (void)setType:(NSString *)value { + if (type != value) { + type = [value copy]; + } +} + +- (uint16_t)port { + return port; +} + +- (void)setPort:(uint16_t)value { + port = value; +} + +- (void)handleNewConnectionFromAddress:(NSData *)addr inputStream:(NSInputStream *)istr outputStream:(NSOutputStream *)ostr { + // if the delegate implements the delegate method, call it + if (delegate && [(NSObject*)delegate respondsToSelector:@selector(TCPServer:didReceiveConnectionFromAddress:inputStream:outputStream:)]) { + [delegate TCPServer:self didReceiveConnectionFromAddress:addr inputStream:istr outputStream:ostr]; + } +} + +// This function is called by CFSocket when a new connection comes in. +// We gather some data here, and convert the function call to a method +// invocation on TCPServer. +static void TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { + TCPServer *server = (__bridge TCPServer *)info; + if (kCFSocketAcceptCallBack == type) { + // for an AcceptCallBack, the data parameter is a pointer to a CFSocketNativeHandle + CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data; + uint8_t name[SOCK_MAXADDRLEN]; + socklen_t namelen = sizeof(name); + NSData *peer = nil; + if (0 == getpeername(nativeSocketHandle, (struct sockaddr *)name, &namelen)) { + peer = [NSData dataWithBytes:name length:namelen]; + } + CFReadStreamRef readStream = NULL; + CFWriteStreamRef writeStream = NULL; + CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &readStream, &writeStream); + if (readStream && writeStream) { + CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); + [server handleNewConnectionFromAddress:peer inputStream:(__bridge NSInputStream *)readStream outputStream:(__bridge NSOutputStream *)writeStream]; + } else { + // on any failure, need to destroy the CFSocketNativeHandle + // since we are not going to use it any more + close(nativeSocketHandle); + } + if (readStream) CFRelease(readStream); + if (writeStream) CFRelease(writeStream); + } +} + +- (BOOL)start:(NSError **)error { + CFSocketContext socketCtxt = {0, (__bridge void *)(self), NULL, NULL, NULL}; + ipv4socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&TCPServerAcceptCallBack, &socketCtxt); + ipv6socket = CFSocketCreate(kCFAllocatorDefault, PF_INET6, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&TCPServerAcceptCallBack, &socketCtxt); + + if (NULL == ipv4socket || NULL == ipv6socket) { + if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerNoSocketsAvailable userInfo:nil]; + if (ipv4socket) CFRelease(ipv4socket); + if (ipv6socket) CFRelease(ipv6socket); + ipv4socket = NULL; + ipv6socket = NULL; + return NO; + } + + int yes = 1; + setsockopt(CFSocketGetNative(ipv4socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); + setsockopt(CFSocketGetNative(ipv6socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); + + // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port); + addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)]; + + if (kCFSocketSuccess == CFSocketSetAddress(ipv4socket, (CFDataRef)address4)) { + if (0 == port) { + // now that the binding was successful, we get the port number + // -- we will need it for the v6 endpoint and for the NSNetService + NSData *addr = (__bridge_transfer NSData *)CFSocketCopyAddress(ipv4socket); + memcpy(&addr4, [addr bytes], [addr length]); + port = ntohs(addr4.sin_port); + } + } else { + if (ipv4socket) CFRelease(ipv4socket); + ipv4socket = NULL; + } + + // set up the IPv6 endpoint + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_len = sizeof(addr6); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(port); + memcpy(&(addr6.sin6_addr), &in6addr_loopback, sizeof(addr6.sin6_addr)); + NSData *address6 = [NSData dataWithBytes:&addr6 length:sizeof(addr6)]; + + if (kCFSocketSuccess == CFSocketSetAddress(ipv6socket, (CFDataRef)address6)) { + if (0 == port) { + // In this case the IPv4 socket failed to bind but the IPv6 socket succeeded + // Get the port number of the IPv6 socket + NSData *addr = (__bridge_transfer NSData *)CFSocketCopyAddress(ipv6socket); + memcpy(&addr6, [addr bytes], [addr length]); + port = ntohs(addr6.sin6_port); + } + } else { + if (ipv6socket) CFRelease(ipv6socket); + ipv6socket = NULL; + } + + if (!ipv4socket && !ipv6socket) { + // Couldn't bind an IPv4 or IPv6 socket, return an error + if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv4Address userInfo:nil]; + return NO; + } + + // set up the run loop sources for the sockets + CFRunLoopRef cfrl = CFRunLoopGetCurrent(); + + if (ipv4socket) { + CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, ipv4socket, 0); + CFRunLoopAddSource(cfrl, source4, kCFRunLoopCommonModes); + CFRelease(source4); + } + + if (ipv6socket) { + CFRunLoopSourceRef source6 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, ipv6socket, 0); + CFRunLoopAddSource(cfrl, source6, kCFRunLoopCommonModes); + CFRelease(source6); + } + + // we can only publish the service if we have a type to publish with + if (nil != type) { + NSString *publishingDomain = domain ? domain : @""; + NSString *publishingName = nil; + if (nil != name) { + publishingName = name; + } else { + NSString * thisHostName = [[NSProcessInfo processInfo] hostName]; + if ([thisHostName hasSuffix:@".local"]) { + publishingName = [thisHostName substringToIndex:([thisHostName length] - 6)]; + } + } + netService = [[NSNetService alloc] initWithDomain:publishingDomain type:type name:publishingName port:port]; + [netService publish]; + } + + return YES; +} + +- (BOOL)stop { + [netService stop]; + netService = nil; + if (ipv4socket) { + CFSocketInvalidate(ipv4socket); + CFRelease(ipv4socket); + ipv4socket = NULL; + } + if (ipv6socket) { + CFSocketInvalidate(ipv6socket); + CFRelease(ipv6socket); + ipv6socket = NULL; + } + return YES; +} + +- (BOOL)hasIPv4Socket { + return ipv4socket != nil; +} + +- (BOOL)hasIPv6Socket { + return ipv6socket != nil; +} + +@end diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthState+Mac.h b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthState+Mac.h new file mode 100644 index 00000000..ddb6e377 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthState+Mac.h @@ -0,0 +1,40 @@ +/*! @file OIDAuthState+Mac.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 "OIDAuthState.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief macOS specific convenience methods for @c OIDAuthState. + */ +@interface OIDAuthState (Mac) + +/*! @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. + @param authorizationRequest The authorization request to present. + @param callback The method called when the request has completed or failed. + @return A @c OIDAuthorizationFlowSession instance which will terminate when it + receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a + @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. + */ ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + callback:(OIDAuthStateAuthorizationCallback)callback; +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthState+Mac.m b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthState+Mac.m new file mode 100644 index 00000000..02f367b6 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthState+Mac.m @@ -0,0 +1,34 @@ +/*! @file OIDAuthState+Mac.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 "OIDAuthState+Mac.h" + +#import "OIDAuthorizationUICoordinatorMac.h" + +@implementation OIDAuthState (Mac) + ++ (id) + authStateByPresentingAuthorizationRequest:(OIDAuthorizationRequest *)authorizationRequest + callback:(OIDAuthStateAuthorizationCallback)callback { + OIDAuthorizationUICoordinatorMac *coordinator = [[OIDAuthorizationUICoordinatorMac alloc] init]; + return [self authStateByPresentingAuthorizationRequest:authorizationRequest + UICoordinator:coordinator + callback:callback]; +} + +@end diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationService+Mac.h b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationService+Mac.h new file mode 100644 index 00000000..c8c98c15 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationService+Mac.h @@ -0,0 +1,38 @@ +/*! @file OIDAuthorizationService+Mac.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 "OIDAuthorizationService.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Provides macOS specific authorization request handling. + */ +@interface OIDAuthorizationService (Mac) + +/*! @brief Perform an authorization flow using the default browser. + @param request The authorization request. + @param callback The method called when the request has completed or failed. + @return A @c OIDAuthorizationFlowSession instance which will terminate when it + receives a @c OIDAuthorizationFlowSession.cancel message, or after processing a + @c OIDAuthorizationFlowSession.resumeAuthorizationFlowWithURL: message. + */ ++ (id)presentAuthorizationRequest:(OIDAuthorizationRequest *)request + callback:(OIDAuthorizationCallback)callback; +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationService+Mac.m b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationService+Mac.m new file mode 100644 index 00000000..d1b138a1 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationService+Mac.m @@ -0,0 +1,35 @@ +/*! @file OIDAuthorizationService+Mac.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 "OIDAuthorizationService+Mac.h" + +#import "OIDAuthorizationUICoordinatorMac.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDAuthorizationService (Mac) + ++ (id)presentAuthorizationRequest:(OIDAuthorizationRequest *)request + callback:(OIDAuthorizationCallback)callback { + OIDAuthorizationUICoordinatorMac *coordinator = [[OIDAuthorizationUICoordinatorMac alloc] init]; + return [self presentAuthorizationRequest:request UICoordinator:coordinator callback:callback]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationUICoordinatorMac.h b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationUICoordinatorMac.h new file mode 100644 index 00000000..06f0a342 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationUICoordinatorMac.h @@ -0,0 +1,34 @@ +/*! @file OIDAuthorizationUICoordinatorMac.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 "OIDAuthorizationUICoordinator.h" + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief An Mac specific authorization UI Coordinator that uses the default browser to + present an authorization request. + */ +@interface OIDAuthorizationUICoordinatorMac : NSObject { + // private variables + BOOL _authorizationFlowInProgress; + __weak id _session; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationUICoordinatorMac.m b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationUICoordinatorMac.m new file mode 100644 index 00000000..be68a109 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDAuthorizationUICoordinatorMac.m @@ -0,0 +1,67 @@ +/*! @file OIDAuthorizationUICoordinatorMac.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 "OIDAuthorizationUICoordinatorMac.h" + +#import + +#import "OIDAuthorizationService.h" +#import "OIDErrorUtilities.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation OIDAuthorizationUICoordinatorMac + +- (BOOL)presentAuthorizationWithURL:(NSURL *)URL session:(id)session { + if (_authorizationFlowInProgress) { + // TODO: Handle errors as authorization is already in progress. + return NO; + } + + _authorizationFlowInProgress = YES; + _session = session; + BOOL openedBrowser = [[NSWorkspace sharedWorkspace] openURL:URL]; + if (!openedBrowser) { + [self cleanUp]; + NSError *safariError = [OIDErrorUtilities errorWithCode:OIDErrorCodeBrowserOpenError + underlyingError:nil + description:@"Unable to open the browser."]; + [session failAuthorizationFlowWithError:safariError]; + } + return openedBrowser; +} + +- (void)dismissAuthorizationAnimated:(BOOL)animated completion:(void (^)(void))completion { + if (!_authorizationFlowInProgress) { + // Ignore this call if there is no authorization flow in progress. + return; + } + // Ideally the browser tab with the URL should be closed here, but the AppAuth library does not + // control the browser. + [self cleanUp]; + if (completion) completion(); +} + +- (void)cleanUp { + _session = nil; + _authorizationFlowInProgress = NO; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDRedirectHTTPHandler.h b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDRedirectHTTPHandler.h new file mode 100644 index 00000000..f445ebbe --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDRedirectHTTPHandler.h @@ -0,0 +1,77 @@ +/*! @file OIDRedirectHTTPHandler.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 + +NS_ASSUME_NONNULL_BEGIN + +@class HTTPServer; +@protocol OIDAuthorizationFlowSession; + +/*! @brief Start a HTTP server on the loopback interface (i.e. @c 127.0.0.1) to receive OAuth + authorization response redirects on macOS. + */ +@interface OIDRedirectHTTPHandler : NSObject { + // private variables + HTTPServer *_httpServ; + NSURL *_successURL; + // property variables + NSObject *_currentAuthorizationFlow; +} + +/*! @brief The authorization flow session which receives the return URL from the browser. + @discussion The loopback HTTP server will try sending incoming request URLs to the OAuth + redirect handler to continue the flow. This should be set while an authorization flow is + in progress. + */ +@property(nonatomic, strong, nullable) id currentAuthorizationFlow; + +/*! @brief Creates an a loopback HTTP redirect URI handler with the given success URL. + @param successURL The URL that the user is redirected to after the authorization flow completes + either with a result of success or error. The contents of this page should instruct the user + to return to the app. + @discussion Once you have initiated the authorization request, be sure to set + @c currentAuthorizationFlow on this object so that any authorization responses received by + this listener will be routed accordingly. + */ +- (instancetype)initWithSuccessURL:(nullable NSURL *)successURL; + +/*! @brief Starts listening on the loopback interface on a random available port, and returns a URL + with the base address. Use the returned redirect URI to build an @c OIDAuthorizationRequest, + and once you initiate the request, set the resulting @c OIDAuthorizationFlowSession to + @c currentAuthorizationFlow so the response can be handled. + @param error The error if an error occurred while starting the local HTTP server. + @return The URL containing the address of the server with the randomly assigned available port. + @discussion Each instance of @c OIDRedirectHTTPHandler can only listen for a single + authorization response. Calling this more than once will result in the previous listener + being cancelled (equivalent of @c cancelHTTPListener being called). + */ +- (NSURL *)startHTTPListener:(NSError **)error; + +/*! @brief Stops listening the loopback interface and sends an cancellation error (in the domain + ::OIDGeneralErrorDomain, with the code ::OIDErrorCodeProgramCanceledAuthorizationFlow) to + the @c currentAuthorizationFlow. Has no effect if called when no requests are pending. + @discussion The HTTP listener is stopped automatically on receiving a valid authorization + response (regardless of whether the authorization succeeded or not), this method should not + be called except when abandoning the authorization request. + */ +- (void)cancelHTTPListener; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDRedirectHTTPHandler.m b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDRedirectHTTPHandler.m new file mode 100644 index 00000000..3003de02 --- /dev/null +++ b/Example-macOS-Swift/Pods/AppAuth/Source/macOS/OIDRedirectHTTPHandler.m @@ -0,0 +1,163 @@ +/*! @file OIDRedirectHTTPHandler.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 "OIDRedirectHTTPHandler.h" + +#import "OIDAuthorizationService.h" +#import "OIDErrorUtilities.h" +#import "OIDLoopbackHTTPServer.h" + +/*! @brief Page that is returned following a completed authorization. Show your own page instead by + supplying a URL in @c initWithSuccessURL that the user will be redirected to. + */ +static NSString *const kHTMLAuthorizationComplete = + @"Authorization complete.
Return to the app."; + +/*! @brief Error warning that the @c currentAuthorizationFlow is not set on this object (likely a + developer error, unless the user stumbled upon the loopback server before the authorization + had started completely). + @description An object conforming to @c OIDAuthorizationFlowSession is returned when the + authorization is presented with + @c OIDAuthorizationService::presentAuthorizationRequest:callback:. It should be set to + @c currentAuthorization when using a loopback redirect. + */ +static NSString *const kHTMLErrorMissingCurrentAuthorizationFlow = + @"AppAuth Error: No currentAuthorizationFlow is set on the " + "OIDRedirectHTTPHandler. Cannot process redirect."; + +/*! @brief Error warning that the URL does not represent a valid redirect. This should be rare, may + happen if the user stumbles upon the loopback server randomly. + */ +static NSString *const kHTMLErrorRedirectNotValid = + @"AppAuth Error: Not a valid redirect."; + +@implementation OIDRedirectHTTPHandler + +@synthesize currentAuthorizationFlow = _currentAuthorizationFlow; + +- (instancetype)init { + return [self initWithSuccessURL:nil]; +} + +- (instancetype)initWithSuccessURL:(nullable NSURL *)successURL { + self = [super init]; + if (self) { + _successURL = [successURL copy]; + } + return self; +} + +- (NSURL *)startHTTPListener:(NSError **)returnError { + // Cancels any pending requests. + [self cancelHTTPListener]; + + // Starts a HTTP server on the loopback interface. + // By not specifying a port, a random available one will be assigned. + _httpServ = [[HTTPServer alloc] init]; + [_httpServ setDelegate:self]; + NSError *error = nil; + if (![_httpServ start:&error]) { + if (returnError) { + *returnError = error; + } + return nil; + } else if ([_httpServ hasIPv4Socket]) { + // Prefer the IPv4 loopback address + NSString *serverURL = [NSString stringWithFormat:@"http://127.0.0.1:%d/", [_httpServ port]]; + return [NSURL URLWithString:serverURL]; + } else if ([_httpServ hasIPv6Socket]) { + // Use the IPv6 loopback address if IPv4 isn't available + NSString *serverURL = [NSString stringWithFormat:@"http://[::1]:%d/", [_httpServ port]]; + return [NSURL URLWithString:serverURL]; + } + + return nil; +} + +- (void)cancelHTTPListener { + [self stopHTTPListener]; + + // Cancels the pending authorization flow (if any) with error. + NSError *cancelledError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeProgramCanceledAuthorizationFlow + underlyingError:nil + description:@"The HTTP listener was cancelled programmatically."]; + [_currentAuthorizationFlow failAuthorizationFlowWithError:cancelledError]; + _currentAuthorizationFlow = nil; +} + +/*! @brief Stops listening on the loopback interface without modifying the state of the + @c currentAuthorizationFlow. Should be called when the authorization flow completes or is + cancelled. + */ +- (void)stopHTTPListener { + _httpServ.delegate = nil; + [_httpServ stop]; + _httpServ = nil; +} + +- (void)HTTPConnection:(HTTPConnection *)conn didReceiveRequest:(HTTPServerRequest *)mess { + // Sends URL to AppAuth. + CFURLRef url = CFHTTPMessageCopyRequestURL(mess.request); + BOOL handled = [_currentAuthorizationFlow resumeAuthorizationFlowWithURL:(__bridge NSURL *)url]; + + // Stops listening to further requests after the first valid authorization response. + if (handled) { + _currentAuthorizationFlow = nil; + [self stopHTTPListener]; + } + + // Responds to browser request. + NSString *bodyText = kHTMLAuthorizationComplete; + NSInteger httpResponseCode = (_successURL) ? 302 : 200; + // Returns an error page if a URL other than the expected redirect is requested. + if (!handled) { + if (_currentAuthorizationFlow) { + bodyText = kHTMLErrorRedirectNotValid; + httpResponseCode = 404; + } else { + bodyText = kHTMLErrorMissingCurrentAuthorizationFlow; + httpResponseCode = 400; + } + } + NSData *data = [bodyText dataUsingEncoding:NSUTF8StringEncoding]; + + CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, + httpResponseCode, + NULL, + kCFHTTPVersion1_1); + if (httpResponseCode == 302) { + CFHTTPMessageSetHeaderFieldValue(response, + (__bridge CFStringRef)@"Location", + (__bridge CFStringRef)_successURL.absoluteString); + } + CFHTTPMessageSetHeaderFieldValue(response, + (__bridge CFStringRef)@"Content-Length", + (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", + (unsigned long)data.length]); + CFHTTPMessageSetBody(response, (__bridge CFDataRef)data); + + [mess setResponse:response]; + CFRelease(response); +} + +- (void)dealloc { + [self cancelHTTPListener]; +} + +@end diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/LICENSE b/Example-macOS-Swift/Pods/GTMAppAuth/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMAppAuth/README.md b/Example-macOS-Swift/Pods/GTMAppAuth/README.md new file mode 100644 index 00000000..62cb83d7 --- /dev/null +++ b/Example-macOS-Swift/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 resumeAuthorizationFlowWithURL: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 resumeAuthorizationFlowWithURL: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 samples `Example-Mac` and `Example-iOS`. In the +folder run `pod install`, then open the `xcworkspace` file. + +Be sure to follow the instructions in +[Example-iOS/README.md](Example-iOS/README.md) or +[Example-macOS/README.md](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/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuth.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuth.h new file mode 100644 index 00000000..2bd5349f --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuth.h @@ -0,0 +1,33 @@ +/*! @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" +#import "GTMTVAuthorizationRequest.h" +#import "GTMTVAuthorizationResponse.h" +#import "GTMTVAuthorizationService.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/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.h new file mode 100644 index 00000000..f0b9ffeb --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.m b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization+Keychain.m new file mode 100644 index 00000000..e4910a81 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.h new file mode 100644 index 00000000..0fb74f6b --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.h @@ -0,0 +1,133 @@ +/*! @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); + +/*! @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; + +/*! @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/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.m b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.m new file mode 100644 index 00000000..7c302e67 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMAppAuthFetcherAuthorization.m @@ -0,0 +1,518 @@ +/*! @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" + +#import "AppAuth.h" + +#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 = [[self class] extractIDTokenClaimsNoVerification:idToken]; + if (claimsDictionary) { + _userEmail = (NSString *)[claimsDictionary[@"email"] copy]; + _userEmailIsVerified = [(NSNumber *)claimsDictionary[@"email_verified"] stringValue]; + _userID = [claimsDictionary[@"sub"] copy]; + } + } + } + return self; +} + +#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]; +} + +# 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 - ID Token extraction + ++ (nullable NSDictionary *)extractIDTokenClaimsNoVerification:(NSString *)idToken { + NSArray *sections = [idToken componentsSeparatedByString:@"."]; + if (sections.count > 1) { + // Gets the JWT payload section. + NSMutableString *body = [sections[1] mutableCopy]; + + // Converts base64url to base64. + NSRange range = NSMakeRange(0, body.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]; + + // Parses JSON. + NSError *error; + id object = [NSJSONSerialization JSONObjectWithData:decodedData options:0 error:&error]; + if (error) { + NSLog(@"Error %@ parsing token payload %@", error, body); + } + if ([object isKindOfClass:[NSDictionary class]]) { + return (NSDictionary *)object; + } + } + return nil; +} + +#pragma mark - GTMFetcherAuthorizationProtocol + +#pragma mark Authorizing Requests + +/*! @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 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 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]; + } + + // Obtains fresh tokens from AppAuth. + [_authState performActionWithFreshTokens:^(NSString *_Nullable accessToken, + NSString *_Nullable idToken, + NSError *_Nullable error) { + // Processes queue. + @synchronized(_authorizationQueue) { + for (GTMAppAuthFetcherAuthorizationArgs *fetcherArgs in _authorizationQueue) { + [self authorizeRequestImmediateArgs:fetcherArgs accessToken:accessToken error:error]; + } + [_authorizationQueue removeAllObjects]; + } + }]; +} + +/*! @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); + } +} + +/*! @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 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; +} + +@end diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMKeychain.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMKeychain.h new file mode 100644 index 00000000..b4d5d062 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.h new file mode 100644 index 00000000..574961bc --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.h @@ -0,0 +1,135 @@ +/*! @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 serviceProvider An optional string to describe the service. + @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 + serviceProvider:(nullable NSString *)serviceProvider + 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 serviceProvider An optional string to describe the service. + @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 + serviceProvider:(nullable NSString *)serviceProvider + 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/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.m b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.m new file mode 100644 index 00000000..2f71adc0 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.m @@ -0,0 +1,321 @@ +/*! @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" + +#import "AppAuth.h" +#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"; + +static NSString *const kGTMOAuth2ServiceProviderGoogle = @"Google"; + +@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 + serviceProvider:(nullable NSString *)serviceProvider + 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 + serviceProvider:serviceProvider + tokenURL:tokenURL + redirectURI:redirectURI + clientID:clientID + clientSecret:clientSecret]; + return authorization; +} + ++ (GTMAppAuthFetcherAuthorization *)authorizeFromPersistenceString:(NSString *)persistenceString + serviceProvider:(NSString *)serviceProvider + 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:@"token" + state:nil + codeVerifier:nil + codeChallenge:nil + codeChallengeMethod:nil + additionalParameters:nil]; + OIDAuthorizationResponse *authResponse = + [[OIDAuthorizationResponse alloc] initWithRequest:authRequest parameters:dict]; + 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:dict]; + 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 + serviceProvider:kGTMOAuth2ServiceProviderGoogle + 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/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationRequest.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationRequest.h new file mode 100644 index 00000000..27f7b85f --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationRequest.h @@ -0,0 +1,57 @@ +/*! @file GTMTVAuthorizationRequest.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 + +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "AppAuth.h" +#endif // GTMAPPAUTH_USER_IMPORTS + +@class GTMTVServiceConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Represents a TV and limited input device authorization request. + @see https://developers.google.com/identity/protocols/OAuth2ForDevices + */ +@interface GTMTVAuthorizationRequest : OIDAuthorizationRequest + +/*! @brief Creates a TV authorization request with opinionated defaults + @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 additionalParameters The client's additional authorization parameters. + */ +- (instancetype) + initWithConfiguration:(GTMTVServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(NSString *)clientSecret + scopes:(nullable NSArray *)scopes + additionalParameters:(nullable NSDictionary *)additionalParameters; + +/*! @brief Constructs an @c NSURLRequest representing the TV authorization request. + @return An @c NSURLRequest representing the TV authorization request. + */ +- (NSURLRequest *)URLRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationRequest.m b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationRequest.m new file mode 100644 index 00000000..1b4102e8 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationRequest.m @@ -0,0 +1,123 @@ +/*! @file GTMTVAuthorizationRequest.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 "GTMTVAuthorizationRequest.h" + +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "AppAuth.h" +#import "OIDURLQueryComponent.h" +#endif // GTMAPPAUTH_USER_IMPORTS +#import "GTMTVServiceConfiguration.h" + +@implementation GTMTVAuthorizationRequest + +- (instancetype) + initWithConfiguration:(GTMTVServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(nullable NSString *)clientSecret + scope:(nullable NSString *)scope + redirectURL:(NSURL *)redirectURL + responseType:(NSString *)responseType + state:(nullable NSString *)state + codeVerifier:(nullable NSString *)codeVerifier + codeChallenge:(nullable NSString *)codeChallenge + codeChallengeMethod:(nullable NSString *)codeChallengeMethod + additionalParameters:(nullable NSDictionary *)additionalParameters { + + if (![configuration isKindOfClass:[GTMTVServiceConfiguration class]]) { + NSAssert([configuration isKindOfClass:[GTMTVServiceConfiguration class]], + @"configuration parameter must be of type GTMTVServiceConfiguration, encountered %@", + NSStringFromClass([configuration class])); + return nil; + } + + return [super initWithConfiguration:configuration + clientId:clientID + clientSecret:clientSecret + scope:scope + redirectURL:redirectURL + responseType:responseType + state:state + codeVerifier:codeVerifier + codeChallenge:codeChallenge + codeChallengeMethod:codeChallengeMethod + additionalParameters:additionalParameters]; +} + +- (instancetype) + initWithConfiguration:(GTMTVServiceConfiguration *)configuration + clientId:(NSString *)clientID + clientSecret:(NSString *)clientSecret + scopes:(nullable NSArray *)scopes + additionalParameters:(nullable NSDictionary *)additionalParameters { + return [self initWithConfiguration:configuration + clientId:clientID + clientSecret:clientSecret + scopes:scopes + redirectURL:[[NSURL alloc] init] + responseType:OIDResponseTypeCode + additionalParameters:additionalParameters]; +} + +#pragma mark - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, request: %@>", + NSStringFromClass([self class]), + self, + self.authorizationRequestURL]; +} + +#pragma mark - + +- (NSURLRequest *)URLRequest { + OIDURLQueryComponent *query = [[OIDURLQueryComponent alloc] init]; + + // Required parameters. + [query addParameter:@"client_id" value:self.clientID]; + + if (self.additionalParameters) { + // Add any additional parameters the client has specified. + [query addParameters:(NSDictionary *)self.additionalParameters]; + } + + if (self.scope) { + [query addParameter:@"scope" value:(NSString *)self.scope]; + } + + static NSString *const kHTTPPost = @"POST"; + static NSString *const kHTTPContentTypeHeaderKey = @"Content-Type"; + static NSString *const kHTTPContentTypeHeaderValue = + @"application/x-www-form-urlencoded; charset=UTF-8"; + + GTMTVServiceConfiguration *tvConfiguration = (GTMTVServiceConfiguration *)self.configuration; + + NSMutableURLRequest *URLRequest = + [[NSURLRequest requestWithURL:tvConfiguration.TVAuthorizationEndpoint] mutableCopy]; + URLRequest.HTTPMethod = kHTTPPost; + [URLRequest setValue:kHTTPContentTypeHeaderValue forHTTPHeaderField:kHTTPContentTypeHeaderKey]; + NSString *bodyString = [query URLEncodedParameters]; + NSData *body = [bodyString dataUsingEncoding:NSUTF8StringEncoding]; + URLRequest.HTTPBody = body; + return URLRequest; +} + +@end diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationResponse.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationResponse.h new file mode 100644 index 00000000..12880f5d --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationResponse.h @@ -0,0 +1,96 @@ +/*! @file GTMTVAuthorizationResponse.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 + +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "AppAuth.h" +#endif // GTMAPPAUTH_USER_IMPORTS + +@class GTMTVAuthorizationRequest; +@class OIDTokenRequest; + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief The @c grant_type value for the the TV authorization flow. + @see https://developers.google.com/identity/protocols/OAuth2ForDevices + */ +extern NSString *const GTMTVDeviceTokenGrantType; + +/*! @brief Represents the response to a TV authorization request. + @see https://developers.google.com/identity/protocols/OAuth2ForDevices + */ +@interface GTMTVAuthorizationResponse : OIDAuthorizationResponse + +/*! @brief The verification URL that should be displayed to the user instructing them to visit the + URL and enter the code. + @remarks verification_url + */ +@property(nonatomic, readonly, nullable) NSString *verificationURL; + +/*! @brief The code that should be displayed to the user which they enter at the @c verificationURL. + @remarks user_code + */ +@property(nonatomic, readonly, nullable) NSString *userCode; + +/*! @brief The device code grant used to poll the token endpoint. Rather than using this directly, + use the provided @c tokenPollRequest method to create the token request. + @remarks device_code + */ +@property(nonatomic, readonly, nullable) NSString *deviceCode; + +/*! @brief The interval at which the token endpoint should be polled with the @c deviceCode. + @remarks interval + */ +@property(nonatomic, readonly, nullable) NSNumber *interval; + +/*! @brief The date at which the user can no longer authorize this request. + @remarks expires_in + */ +@property(nonatomic, readonly, nullable) NSDate *expirationDate; + +/*! @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:(GTMTVAuthorizationRequest *)request + parameters:(NSDictionary *> *)parameters + NS_DESIGNATED_INITIALIZER; + +/*! @brief Creates a token request suitable for polling the token endpoint with the @c deviceCode. + @return A @c OIDTokenRequest suitable for polling the token endpoint. + @see https://developers.google.com/identity/protocols/OAuth2ForDevices + */ +- (nullable OIDTokenRequest *)tokenPollRequest; + +/*! @brief Creates a token request suitable for polling the token endpoint with the @c deviceCode. + @param additionalParameters Additional parameters for the token request. + @return A @c OIDTokenRequest suitable for polling the token endpoint. + @see https://developers.google.com/identity/protocols/OAuth2ForDevices + */ +- (nullable OIDTokenRequest *)tokenPollRequestWithAdditionalParameters: + (nullable NSDictionary *)additionalParameters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationResponse.m b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationResponse.m new file mode 100644 index 00000000..2ec769eb --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationResponse.m @@ -0,0 +1,167 @@ +/*! @file GTMTVAuthorizationResponse.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 "GTMTVAuthorizationResponse.h" + +#import "GTMTVAuthorizationRequest.h" +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#import +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "AppAuth.h" +#import "OIDDefines.h" +#import "OIDFieldMapping.h" +#endif // GTMAPPAUTH_USER_IMPORTS + + +NSString *const GTMTVDeviceTokenGrantType = @"http://oauth.net/grant_type/device/1.0"; + +/*! @brief The key for the @c verificationURL property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kVerificationURLKey = @"verification_url"; + +/*! @brief The key for the @c userCode property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kUserCodeKey = @"user_code"; + +/*! @brief The key for the @c deviceCode property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kDeviceCodeKey = @"device_code"; + +/*! @brief The key for the @c expirationDate property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kExpiresInKey = @"expires_in"; + +/*! @brief The key for the @c interval property in the incoming parameters and for + @c NSSecureCoding. + */ +static NSString *const kIntervalKey = @"interval"; + +/*! @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"; + +@implementation GTMTVAuthorizationResponse + +@synthesize verificationURL = _verificationURL; +@synthesize userCode = _userCode; +@synthesize deviceCode = _deviceCode; +@synthesize interval = _interval; +@synthesize expirationDate = _expirationDate; + +/*! @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[kVerificationURLKey] = + [[OIDFieldMapping alloc] initWithName:@"_verificationURL" type:[NSString class]]; + fieldMap[kUserCodeKey] = + [[OIDFieldMapping alloc] initWithName:@"_userCode" type:[NSString class]]; + fieldMap[kDeviceCodeKey] = + [[OIDFieldMapping alloc] initWithName:@"_deviceCode" type:[NSString class]]; + fieldMap[kExpiresInKey] = + [[OIDFieldMapping alloc] initWithName:@"_expirationDate" + 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[kIntervalKey] = + [[OIDFieldMapping alloc] initWithName:@"_interval" type:[NSNumber class]]; + }); + return fieldMap; +} + +#pragma mark - Initializers + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER(@selector(initWithRequest:parameters:)); + +- (instancetype)initWithRequest:(GTMTVAuthorizationRequest *)request + parameters:(NSDictionary *> *)parameters { + self = [super initWithRequest:request parameters:parameters]; + 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 - NSObject overrides + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, verificationURL: %@, userCode: \"%@\", deviceCode: " + "\"%@\", interval: %@, expirationDate: %@, " + "additionalParameters: %@, " + "request: %@>", + NSStringFromClass([self class]), + self, + _verificationURL, + _userCode, + _deviceCode, + _interval, + _expirationDate, + self.additionalParameters, + self.request]; +} + +#pragma mark - + +- (OIDTokenRequest *)tokenPollRequest { + return [self tokenPollRequestWithAdditionalParameters:nil]; +} + +- (OIDTokenRequest *)tokenPollRequestWithAdditionalParameters: + (NSDictionary *)additionalParameters { + OIDTokenRequest *pollRequest = + [[OIDTokenRequest alloc] initWithConfiguration:self.request.configuration + grantType:GTMTVDeviceTokenGrantType + authorizationCode:_deviceCode + redirectURL:[[NSURL alloc] init] + clientID:self.request.clientID + clientSecret:self.request.clientSecret + scopes:nil + refreshToken:nil + codeVerifier:nil + additionalParameters:nil]; + return pollRequest; +} + +@end diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationService.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationService.h new file mode 100644 index 00000000..dfba4ba9 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationService.h @@ -0,0 +1,82 @@ +/*! @file GTMTVAuthorizationService.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 + +@class GTMAppAuthFetcherAuthorization; +@class GTMTVAuthorizationRequest; +@class GTMTVAuthorizationResponse; +@class GTMTVServiceConfiguration; + +/*! @brief The block that is called when the TV authorization has initialized. + @param response The authorization response, or nil if there was an error. Display + @c GTMTVAuthorizationResponse.userCode and @c GTMTVAuthorizationResponse.verificationURL to + the user so they can action the request. + @param error The error if an error occurred. + */ +typedef void (^GTMTVAuthorizationInitialization)(GTMTVAuthorizationResponse *_Nullable response, + NSError *_Nullable error); + +/*! @brief The block that is called when the TV authorization has completed. + @param authorization The @c GTMAppAuthFetcherAuthorization which you can use to authorize + API calls, or nil if there was an error. + @param error The error if an error occurred. + */ +typedef void (^GTMTVAuthorizationCompletion) + (GTMAppAuthFetcherAuthorization *_Nullable authorization, + NSError *_Nullable error); + +/*! @brief Block returned when authorization is initialized to that will cancel the pending + authorization when executed. Has no effect if called twice or after the authorization + concluded. + */ +typedef void (^GTMTVAuthorizationCancelBlock)(); + +/*! @brief Performs authorization flows designed for TVs and other limited input devices. + */ +@interface GTMTVAuthorizationService : NSObject + +#if !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT +/*! @brief Convenience method to return the TV authorization URL for Google. + @return TV authorization URL for Google. + */ ++ (GTMTVServiceConfiguration *)TVConfigurationForGoogle; +#endif // !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT + +/*! @brief Starts a TV authorization flow with the given request and polls for a response. + @param request The TV authorization request to initiate. + @param initialization Block that is called with the initial authorization response. Unlike other + OAuth authorization responses, the TV authorization response doesn't contain the + authorization as the user has yet to grant it. Rather, it contains the information that you + show to the user in order for them to authorize the request on another device. + @param completion Block that is called on the success or failure of the authorization. If the + user approves the request, you will get a @c GTMAppAuthFetherAuthorization that you can use + to authenticate API calls, otherwis eyou will get an error. + @return A block which you can execute if you need to cancel the ongoing authorization. Has no + effect if called twice, or called after the authorization concludes. + @see https://developers.google.com/identity/protocols/OAuth2ForDevices + */ ++ (GTMTVAuthorizationCancelBlock)authorizeTVRequest:(GTMTVAuthorizationRequest *)request + initializaiton:(GTMTVAuthorizationInitialization)initialization + completion:(GTMTVAuthorizationCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationService.m b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationService.m new file mode 100644 index 00000000..e311a271 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVAuthorizationService.m @@ -0,0 +1,259 @@ +/*! @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 "GTMTVAuthorizationService.h" + +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#import +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "AppAuth.h" +#import "OIDDefines.h" +#import "OIDURLQueryComponent.h" +#endif // GTMAPPAUTH_USER_IMPORTS + +#import "GTMAppAuthFetcherAuthorization.h" +#import "GTMTVAuthorizationRequest.h" +#import "GTMTVAuthorizationResponse.h" +#import "GTMTVServiceConfiguration.h" + +/*! @brief Google's device authorization endpoint. + */ +NSString *const kGoogleDeviceAuthorizationEndpoint = + @"https://accounts.google.com/o/oauth2/device/code"; + +/*! @brief The authorization pending error code. + @see https://developers.google.com/identity/protocols/OAuth2ForDevices + */ +NSString *const kErrorCodeAuthorizationPending = @"authorization_pending"; + +/*! @brief The slow down error code. + @see https://developers.google.com/identity/protocols/OAuth2ForDevices + */ +NSString *const kErrorCodeSlowDown = @"slow_down"; + +@implementation GTMTVAuthorizationService + +#pragma mark - Initializers + +#if !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT ++ (GTMTVServiceConfiguration *)TVConfigurationForGoogle { + NSURL *authorizationEndpoint = + [NSURL URLWithString:@"https://accounts.google.com/o/oauth2/v2/auth"]; + NSURL *tokenEndpoint = + [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"]; + NSURL *TVAuthorizationEndpoint = + [NSURL URLWithString:kGoogleDeviceAuthorizationEndpoint]; + + GTMTVServiceConfiguration *configuration = + [[GTMTVServiceConfiguration alloc] initWithAuthorizationEndpoint:authorizationEndpoint + TVAuthorizationEndpoint:TVAuthorizationEndpoint + tokenEndpoint:tokenEndpoint]; + return configuration; +} +#endif // !GTM_APPAUTH_SKIP_GOOGLE_SUPPORT + ++ (GTMTVAuthorizationCancelBlock)authorizeTVRequest:(GTMTVAuthorizationRequest *)request + initializaiton:(GTMTVAuthorizationInitialization)initialization + completion:(GTMTVAuthorizationCompletion)completion { + // Block level variable that can be used to cancel the polling. + __block BOOL pollRunning = YES; + + // Block that will be returned allowign the caller to cancel the polling. + GTMTVAuthorizationCancelBlock cancelBlock = ^{ + if (pollRunning) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSError *cancelError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeProgramCanceledAuthorizationFlow + underlyingError:nil + description:@"Authorization cancelled"]; + completion(nil, cancelError); + }); + } + pollRunning = NO; + }; + + // Performs the initial authorization reqeust. + NSURLRequest *URLRequest = [request URLRequest]; + NSURLSession *session = [NSURLSession sharedSession]; + [[session dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + // A network error or server error occurred. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeNetworkError + underlyingError:error + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, returnedError); + }); + return; + } + + NSHTTPURLResponse *HTTPURLResponse = (NSHTTPURLResponse *)response; + + if (HTTPURLResponse.statusCode != 200) { + // A server error occurred. + NSError *serverError = + [OIDErrorUtilities HTTPErrorWithHTTPResponse:HTTPURLResponse data:data]; + + // HTTP 400 may indicate an RFC6749 Section 5.2 error response, checks for that + if (HTTPURLResponse.statusCode == 400) { + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:(NSData *)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:OIDOAuthTokenErrorDomain + OAuthResponse:json + underlyingError:serverError]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, oauthError); + }); + return; + } + } + + // not an OAuth error, just a generic server error + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeServerError + underlyingError:serverError + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, returnedError); + }); + return; + } + + NSError *jsonDeserializationError; + NSDictionary *> *json = + [NSJSONSerialization JSONObjectWithData:(NSData *)data + options:0 + error:&jsonDeserializationError]; + if (jsonDeserializationError) { + // A problem occurred deserializing the response/JSON. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeJSONDeserializationError + underlyingError:jsonDeserializationError + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, returnedError); + }); + return; + } + + // Parses the authorization response. + GTMTVAuthorizationResponse *TVAuthorizationResponse = + [[GTMTVAuthorizationResponse alloc] initWithRequest:request parameters:json]; + if (!TVAuthorizationResponse) { + // A problem occurred constructing the token response from the JSON. + NSError *returnedError = + [OIDErrorUtilities errorWithCode:OIDErrorCodeTokenResponseConstructionError + underlyingError:jsonDeserializationError + description:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + initialization(nil, returnedError); + }); + return; + } + + // Calls the initialization block to signal that we received a TV authorization response. + dispatch_async(dispatch_get_main_queue(), ^() { + initialization(TVAuthorizationResponse, nil); + }); + + // Creates the token request that will be used to poll the token endpoint. + OIDTokenRequest *pollRequest = [TVAuthorizationResponse tokenPollRequest]; + + // Starting polling interval (may be increased if a slow down message is received). + __block NSTimeInterval interval = [TVAuthorizationResponse.interval doubleValue]; + + // Polls the token endpoint until the authorization completes or expires. + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + do { + // Sleeps for polling interval. + [NSThread sleepForTimeInterval:interval]; + + if (!pollRunning) { + break; + } + + // Polls token endpoint. + [OIDAuthorizationService performTokenRequest:pollRequest + callback:^(OIDTokenResponse *_Nullable tokenResponse, + NSError *_Nullable tokenError) { + if (!pollRunning) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^() { + if (tokenResponse) { + // Success response. + pollRunning = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + OIDAuthState *authState = + [[OIDAuthState alloc] initWithAuthorizationResponse:TVAuthorizationResponse + tokenResponse:tokenResponse]; + GTMAppAuthFetcherAuthorization *authorization = + [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; + completion(authorization, nil); + }); + } else { + if (tokenError.domain == OIDOAuthTokenErrorDomain) { + // OAuth token errors inspected for device flow specific errors. + NSString *errorCode = + tokenError.userInfo[OIDOAuthErrorResponseErrorKey][OIDOAuthErrorFieldError]; + if ([errorCode isEqual:kErrorCodeAuthorizationPending]) { + // authorization_pending is an expected response. + return; + } else if ([errorCode isEqual:kErrorCodeSlowDown]) { + // Increase interval by 20%, enforce a lower bound of 5s. + interval *= 1.20; + interval = MAX(5.0, interval); + } else { + // Unhandled token error, considered fatal. + pollRunning = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, tokenError); + }); + } + } else { + // All other errors considered fatal. + pollRunning = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil, tokenError); + }); + } + } + }); + }]; + } while ([TVAuthorizationResponse.expirationDate timeIntervalSinceNow] > 0 && pollRunning); + }); + }] resume]; + + return cancelBlock; +} + +@end diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVServiceConfiguration.h b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVServiceConfiguration.h new file mode 100644 index 00000000..78045b92 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVServiceConfiguration.h @@ -0,0 +1,65 @@ +/*! @file GTMTVServiceConfiguration.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 "AppAuth.h" +#endif // GTMAPPAUTH_USER_IMPORTS + +NS_ASSUME_NONNULL_BEGIN + +/*! @brief Configuration for authorizing the user with the @c GTMTVAuthorizationService. + */ +@interface GTMTVServiceConfiguration : OIDServiceConfiguration + +/*! @brief The TV authorization endpoint URI. + */ +@property(nonatomic, readonly) NSURL *TVAuthorizationEndpoint; + +/*! @internal + @brief Unavailable. Please use + @c initWithAuthorizationEndpoint:TVAuthorizationEndpoint:tokenEndpoint: + */ +- (instancetype)init NS_UNAVAILABLE; + +/*! @internal + @brief Unavailable. Please use + @c initWithAuthorizationEndpoint:TVAuthorizationEndpoint:tokenEndpoint: + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint NS_UNAVAILABLE; + +/*! @internal + @brief Unavailable. Please use + @c initWithAuthorizationEndpoint:TVAuthorizationEndpoint:tokenEndpoint: + */ +- (instancetype)initWithDiscoveryDocument:(OIDServiceDiscovery *)discoveryDocument NS_UNAVAILABLE; + +/*! @brief Designated initializer. + @param authorizationEndpoint The authorization endpoint URI. + @param TVAuthorizationEndpoint The TV authorization endpoint URI. + @param tokenEndpoint The token exchange and refresh endpoint URI. + */ +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + TVAuthorizationEndpoint:(NSURL *)TVAuthorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVServiceConfiguration.m b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVServiceConfiguration.m new file mode 100644 index 00000000..560bec2e --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/GTMTVServiceConfiguration.m @@ -0,0 +1,100 @@ +/*! @file GTMTVServiceConfiguration.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 "GTMTVServiceConfiguration.h" + +#ifndef GTMAPPAUTH_USER_IMPORTS +#import +#import +#else // GTMAPPAUTH_USER_IMPORTS +#import "AppAuth.h" +#import "OIDDefines.h" +#endif // GTMAPPAUTH_USER_IMPORTS + +/*! @brief The key for the @c TVAuthorizationEndpoint property. + */ +static NSString *const kTVAuthorizationEndpointKey = @"TVAuthorizationEndpoint"; + +NS_ASSUME_NONNULL_BEGIN + +@interface GTMTVServiceConfiguration () + +/*! @brief Designated initializer. + @param aDecoder NSCoder to unserialize the object from. + */ +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; + +@end + +@implementation GTMTVServiceConfiguration + +@synthesize TVAuthorizationEndpoint = _TVAuthorizationEndpoint; + +- (instancetype)init + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithAuthorizationEndpoint:TVAuthorizationEndpoint:tokenEndpoint:)); + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint + OID_UNAVAILABLE_USE_INITIALIZER( + @selector(initWithAuthorizationEndpoint:TVAuthorizationEndpoint:tokenEndpoint:)); + +- (instancetype)initWithAuthorizationEndpoint:(NSURL *)authorizationEndpoint + TVAuthorizationEndpoint:(NSURL *)TVAuthorizationEndpoint + tokenEndpoint:(NSURL *)tokenEndpoint { + self = [super initWithAuthorizationEndpoint:authorizationEndpoint tokenEndpoint:tokenEndpoint]; + if (self) { + _TVAuthorizationEndpoint = [TVAuthorizationEndpoint copy]; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + NSURL *TVAuthorizationEndpoint = [aDecoder decodeObjectOfClass:[NSURL class] + forKey:kTVAuthorizationEndpointKey]; + _TVAuthorizationEndpoint = TVAuthorizationEndpoint; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + [aCoder encodeObject:_TVAuthorizationEndpoint forKey:kTVAuthorizationEndpointKey]; +} + +#pragma mark - description + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, TVAuthorizationEndpoint: %@ tokenEndpoint: %@>", + NSStringFromClass([self class]), + self, + _TVAuthorizationEndpoint, + self.tokenEndpoint]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/GTMAppAuth/Source/macOS/GTMKeychain_macOS.m b/Example-macOS-Swift/Pods/GTMAppAuth/Source/macOS/GTMKeychain_macOS.m new file mode 100644 index 00000000..96d238f4 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMAppAuth/Source/macOS/GTMKeychain_macOS.m @@ -0,0 +1,182 @@ +/*! @file GTMKeychain_macOS.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 + +static const char *kKeychainAccountName = "OAuth"; + +@implementation GTMKeychain + ++ (NSString *)prefsKeyForName:(NSString *)keychainItemName { + NSString *result = [@"OAuth2: " stringByAppendingString:keychainItemName]; + return result; +} + ++ (NSString *)passwordFromKeychainForName:(NSString *)keychainItemName { + // before accessing the keychain, check preferences to verify that we've + // previously saved a token to the keychain (so we don't needlessly raise + // a keychain access permission dialog) + NSString *prefKey = [self prefsKeyForName:keychainItemName]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL flag = [defaults boolForKey:prefKey]; + if (!flag) { + return nil; + } + + SecKeychainRef defaultKeychain = NULL; + const char *utf8ServiceName = keychainItemName.UTF8String; + SecKeychainItemRef *dontWantItemRef = NULL; + + void *passwordBuff = NULL; + UInt32 passwordBuffLength = 0; + + OSStatus err = SecKeychainFindGenericPassword(defaultKeychain, + (UInt32) strlen(utf8ServiceName), utf8ServiceName, + (UInt32) strlen(kKeychainAccountName), kKeychainAccountName, + &passwordBuffLength, &passwordBuff, + dontWantItemRef); + if (err == noErr && passwordBuff != NULL) { + NSString *password = [[NSString alloc] initWithBytes:passwordBuff + length:passwordBuffLength + encoding:NSUTF8StringEncoding]; + + // free the password buffer that was allocated above + SecKeychainItemFreeContent(NULL, passwordBuff); + + if (password != nil) { + return password; + } + } + return nil; +} + ++ (NSData *)passwordDataFromKeychainForName:(NSString *)keychainItemName { + // before accessing the keychain, check preferences to verify that we've + // previously saved a token to the keychain (so we don't needlessly raise + // a keychain access permission dialog) + NSString *prefKey = [self prefsKeyForName:keychainItemName]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + BOOL flag = [defaults boolForKey:prefKey]; + if (!flag) { + return nil; + } + + SecKeychainRef defaultKeychain = NULL; + const char *utf8ServiceName = [keychainItemName UTF8String]; + SecKeychainItemRef *dontWantItemRef = NULL; + + void *passwordBuff = NULL; + UInt32 passwordBuffLength = 0; + + OSStatus err = SecKeychainFindGenericPassword(defaultKeychain, + (UInt32) strlen(utf8ServiceName), utf8ServiceName, + (UInt32) strlen(kKeychainAccountName), kKeychainAccountName, + &passwordBuffLength, &passwordBuff, + dontWantItemRef); + if (err == noErr && passwordBuff != NULL) { + + NSData *passwordData = [[NSData alloc] initWithBytes:passwordBuff length:passwordBuffLength]; + + // free the password buffer that was allocated above + SecKeychainItemFreeContent(NULL, passwordBuff); + + return passwordData; + } + return nil; +} + ++ (BOOL)removePasswordFromKeychainForName:(NSString *)keychainItemName { + SecKeychainRef defaultKeychain = NULL; + SecKeychainItemRef itemRef = NULL; + const char *utf8ServiceName = [keychainItemName UTF8String]; + + // we don't really care about the password here, we just want to + // get the SecKeychainItemRef so we can delete it. + OSStatus err = SecKeychainFindGenericPassword (defaultKeychain, + (UInt32) strlen(utf8ServiceName), utf8ServiceName, + (UInt32) strlen(kKeychainAccountName), kKeychainAccountName, + 0, NULL, // ignore password + &itemRef); + if (err != noErr) { + // failure to find is success + return YES; + } else { + // found something, so delete it + err = SecKeychainItemDelete(itemRef); + CFRelease(itemRef); + + // remove our preference key + NSString *prefKey = [self prefsKeyForName:keychainItemName]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults removeObjectForKey:prefKey]; + + return (err == noErr); + } +} + ++ (BOOL)savePasswordToKeychainForName:(NSString *)keychainItemName password:(NSString *)password { + SecKeychainRef defaultKeychain = NULL; + SecKeychainItemRef *dontWantItemRef= NULL; + const char *utf8ServiceName = [keychainItemName UTF8String]; + const char *utf8Password = [password UTF8String]; + + OSStatus err = SecKeychainAddGenericPassword(defaultKeychain, + (UInt32) strlen(utf8ServiceName), utf8ServiceName, + (UInt32) strlen(kKeychainAccountName), kKeychainAccountName, + (UInt32) strlen(utf8Password), utf8Password, + dontWantItemRef); + BOOL didSucceed = (err == noErr); + if (didSucceed) { + // write to preferences that we have a keychain item (so we know later + // that we can read from the keychain without raising a permissions dialog) + NSString *prefKey = [self prefsKeyForName:keychainItemName]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setBool:YES forKey:prefKey]; + } + + return didSucceed; +} + ++ (BOOL)savePasswordDataToKeychainForName:(NSString *)keychainItemName + passwordData:(NSData *)passwordData { + SecKeychainRef defaultKeychain = NULL; + SecKeychainItemRef *dontWantItemRef= NULL; + const char *utf8ServiceName = [keychainItemName UTF8String]; + const void *passwordBytes = [passwordData bytes]; + UInt32 passwordBytesLength = (UInt32) [passwordData length]; + + OSStatus err = SecKeychainAddGenericPassword(defaultKeychain, + (UInt32) strlen(utf8ServiceName), utf8ServiceName, + (UInt32) strlen(kKeychainAccountName), kKeychainAccountName, + passwordBytesLength, passwordBytes, + dontWantItemRef); + BOOL didSucceed = (err == noErr); + if (didSucceed) { + // write to preferences that we have a keychain item (so we know later + // that we can read from the keychain without raising a permissions dialog) + NSString *prefKey = [self prefsKeyForName:keychainItemName]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults setBool:YES forKey:prefKey]; + } + + return didSucceed; +} + +@end diff --git a/Example-macOS-Swift/Pods/GTMSessionFetcher/LICENSE b/Example-macOS-Swift/Pods/GTMSessionFetcher/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMSessionFetcher/README.md b/Example-macOS-Swift/Pods/GTMSessionFetcher/README.md new file mode 100644 index 00000000..478efde9 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h new file mode 100644 index 00000000..ec3c0125 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m new file mode 100644 index 00000000..0f65310f --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h new file mode 100644 index 00000000..451e1323 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m new file mode 100644 index 00000000..7b21c30f --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m @@ -0,0 +1,629 @@ +/* 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 %tu keys, body %tu bytes)", + [self class], self, _headers.count, _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 (%tu parts)", + [self class], self, _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 %zd has %zd bytes", partCounter - 1, 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 %zd lacks a header separator: %@", 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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h new file mode 100644 index 00000000..4e306428 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m new file mode 100644 index 00000000..6f95dd54 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h new file mode 100644 index 00000000..569475a2 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h @@ -0,0 +1,1308 @@ +/* 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 + +#if (TARGET_OS_TV \ + || TARGET_OS_WATCH \ + || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0)) +#define GTMSESSION_DEPRECATE_ON_2016_SDKS(_MSG) __attribute__((deprecated("" _MSG))) +#else +#define GTMSESSION_DEPRECATE_ON_2016_SDKS(_MSG) +#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 // __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 + +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; + +// 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); + +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(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(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(readonly) BOOL canAuthorize; + +// For development only, allow authorization of non-SSL requests, allowing +// transmission of the bearer token unencrypted. +@property(assign) BOOL shouldAuthorizeAllRequests; + +- (void)authorizeRequest:(GTM_NULLABLE NSMutableURLRequest *)request + completionHandler:(void (^)(NSError * GTM_NULLABLE_TYPE error))handler; + +#if GTM_USE_SESSION_FETCHER +@property (weak, GTM_NULLABLE) id fetcherService; +#else +@property (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(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; + +// The fetcher's request (deprecated.) +// +// Exposing a mutable object in the interface was convenient but a bad design decision due +// to thread-safety requirements. Clients should use the request property and +// setRequestValue:forHTTPHeaderField: instead. +@property(atomic, readonly, GTM_NULLABLE) NSMutableURLRequest *mutableRequest + GTMSESSION_DEPRECATE_ON_2016_SDKS("use 'request' or '-setRequestValue:forHTTPHeaderField:'"); + +// 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(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; + +// 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 DEBUG + #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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m new file mode 100644 index 00000000..c88ddd51 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m @@ -0,0 +1,4570 @@ +/* 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" + +#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 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.; + +#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 + +@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 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 + 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 { + [self fetchersForBackgroundSessions]; +} +#endif + ++ (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 (![NSURLSession class]) { + Class oldFetcherClass = NSClassFromString(@"GTMHTTPFetcher"); + if (oldFetcherClass && request) { + self = [[oldFetcherClass alloc] initWithRequest:(NSURLRequest *)request]; + } else { + self = nil; + } + return 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 %zd", + _delegateQueue.name, _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 + [self setSessionIdentifierInternal:nil]; + self.useBackgroundSession = 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 (TARGET_OS_TV \ + || TARGET_OS_WATCH \ + || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)) + // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name. + _configuration = + [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionIdentifier]; +#elif (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0) + // Do a runtime check to avoid a deprecation warning about using + // +backgroundSessionConfiguration: on iOS 8. + if ([NSURLSessionConfiguration respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) { + // Running on iOS 8+/OS X 10.10+. +#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. + _configuration = + [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionIdentifier]; +#pragma clang diagnostic pop + } else { + // Running on iOS 7/OS X 10.9. + _configuration = + [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier]; + } +#else + // Building with an SDK earlier than iOS 8/OS X 10.10. + _configuration = + [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier]; +#endif + self.usingBackgroundSession = YES; + self.canShareSession = NO; + } else { + _configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; + } +#if !GTM_ALLOW_INSECURE_REQUESTS + _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12; +#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 %tu bytes", + _session, _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 %tu bytes", + _session, fetchRequest, _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 TARGET_OS_TV || TARGET_OS_WATCH + BOOL hasTaskPriority = YES; +#elif (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) \ + || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0) + BOOL hasTaskPriority = YES; +#else + BOOL hasTaskPriority = [newSessionTask respondsToSelector:@selector(setPriority:)]; +#endif + if (hasTaskPriority) { +#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. + newSessionTask.priority = _taskPriority; +#pragma clang diagnostic pop + } + } + +#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.useBackgroundSession) { + // 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. + _testBlock = nil; + _isUsingTestBlock = NO; + [_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; + _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:_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; + + // Simulate receipt of redirection. + if (willRedirectBlock) { + [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES + block:^{ + willRedirectBlock((NSHTTPURLResponse *)response, _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:^{ + if (_challengeBlock) { + NSURL *requestURL = _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; +} + +- (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(_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]; + + // Disallow scheme changes (say, from https to http). + NSURL *originalRequestURL = originalRequest.URL; + NSURL *redirectRequestURL = redirectRequest.URL; + + NSString *originalScheme = originalRequestURL.scheme; + NSString *redirectScheme = redirectRequestURL.scheme; + + if (originalScheme != nil + && [originalScheme caseInsensitiveCompare:@"http"] == NSOrderedSame + && redirectScheme != nil + && [redirectScheme caseInsensitiveCompare:@"https"] == NSOrderedSame) { + // Allow the change from http to https. + } else { + // Disallow any other scheme changes. + redirectScheme = originalScheme; + } + // The new requests's URL overrides the original's URL. + NSURLComponents *components = [NSURLComponents componentsWithURL:redirectRequestURL + resolvingAgainstBaseURL:NO]; + components.scheme = redirectScheme; + NSURL *newURL = components.URL; + [newRequest setURL:newURL]; + + // Any headers in the redirect override headers in the original. + NSDictionary *redirectHeaders = redirectRequest.allHTTPHeaderFields; + for (NSString *key in redirectHeaders) { + NSString *value = [redirectHeaders objectForKey:key]; + [newRequest setValue:value forHTTPHeaderField:key]; + } + + 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 = _downloadedLength > 0; + + [_downloadedData setLength:0]; + _downloadedLength = 0; + + if (hadPreviousData && (dispositionValue != NSURLSessionResponseCancel)) { + // Tell the accumulate block to discard prior data. + GTMSessionFetcherAccumulateDataBlock accumulateBlock = _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 %@", _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) +} + +// 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. + SecTrustResultType trustEval = kSecTrustResultInvalid; + BOOL shouldAllow; + 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))); + } + } + 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 = [_service stoppedAllFetchersDate]; + + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + // Avoid a race between stopFetching and the callback. + if (_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 + && [_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 = _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 = _receivedProgressBlock; + } + if (progressBlock) { + progressBlock((int64_t)bufferLength, _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; + } + // We won't hold on to download progress block during the enqueue; + // it's ok to not send it if the upload finishes. + + [self invokeOnCallbackQueueUnlessStopped:^{ + GTMSessionFetcherDownloadProgressBlock progressBlock; + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + progressBlock = _downloadProgressBlock; + } + if (progressBlock) { + 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. A downside of this is that + // the server may have included error details in the response body, and + // abandoning the downloaded file here means that the details from the + // body are not available to the fetcher client. + GTMSESSION_LOG_DEBUG(@"Abandoning download due to status %zd, file %@", + statusCode, downloadLocationURL.path); + } 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]; + }]; +} + +#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 = nil; + if (_downloadedData.length > 0) { + NSMutableData *data = _downloadedData; + userInfo = @{ kGTMSessionFetcherStatusDataKey : data }; + } + 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 = nil; + if (_downloadedData.length > 0) { + NSMutableData *data = _downloadedData; + userInfo = @{ kGTMSessionFetcherStatusDataKey : data }; + } + 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]; + + @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, + 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; +} + +- (GTM_NULLABLE NSMutableURLRequest *)mutableRequest { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_LOG_DEBUG(@"[GTMSessionFetcher mutableRequest] is deprecated; use -request or" + @" -setRequestValue:forHTTPHeaderField:"); + + return _request; + } // @synchronized(self) +} + +- (void)setMutableRequest:(GTM_NULLABLE NSMutableURLRequest *)request { + GTMSESSION_LOG_DEBUG(@"[GTMSessionFetcher setMutableRequest:] is deprecated; use -request or" + @" -setRequestValue:forHTTPHeaderField:"); + + GTMSESSION_ASSERT_DEBUG(![self isFetching], + @"mutableRequest should not change after beginFetch has been invoked"); + [self updateMutableRequest: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:@"%zd.%zd.%zd", + version.majorVersion, version.minorVersion, 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 +@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:@(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 +GTM_ASSUME_NONNULL_END diff --git a/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h new file mode 100644 index 00000000..bc0a65c0 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h @@ -0,0 +1,107 @@ +/* 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. +// +// Log files are put into a folder on the desktop called "GTMHTTPDebugLogs" +// unless another directory is specified with +setLoggingDirectory. +// +// In the iPhone simulator, the default logs location is the user's home +// directory in ~/Library/Application Support. On the iPhone device, the +// default logs location is the application's documents directory on the device. +// +// 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: the default logs directory is ~/Desktop/GTMHTTPDebugLogs; it will be +// created as needed. If a custom directory is set, the directory should +// already exist. ++ (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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m new file mode 100644 index 00000000..02b46b60 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m @@ -0,0 +1,976 @@ +/* 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) { + // 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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h new file mode 100644 index 00000000..a696ac7e --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h @@ -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. + */ + +// 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; + +#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 method 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; + +// 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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m new file mode 100644 index 00000000..fc6e6d96 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m @@ -0,0 +1,1352 @@ +/* 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, + 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; + 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 and has no dispatcher. +- (GTMSessionFetcherSessionDelegateDispatcher *)delegateDispatcherForFetcher:(GTMSessionFetcher *)fetcher { + GTMSessionCheckNotSynchronized(self); + + NSURLSession *fetcherSession = fetcher.session; + if (fetcherSession) { + id fetcherDelegate = fetcherSession.delegate; + BOOL hasDispatcher = (fetcherDelegate != nil && fetcherDelegate != fetcher); + 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]; + 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]; +} + +// 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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h new file mode 100644 index 00000000..6d0a2276 --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h @@ -0,0 +1,141 @@ +/* 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. +// + +// 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. +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 repsonse 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); + +@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; + ++ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + 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, assign) int64_t chunkSize; +@property(atomic, readonly, assign) int64_t currentOffset; + +// 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; + +// 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/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m new file mode 100644 index 00000000..9798631a --- /dev/null +++ b/Example-macOS-Swift/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m @@ -0,0 +1,1864 @@ +/* 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 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 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; + +@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; + +@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; +} + ++ (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]; + return fetcher; +} + ++ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL + uploadMIMEType:(NSString *)uploadMIMEType + chunkSize:(int64_t)chunkSize + fetcherService:(GTMSessionFetcherService *)fetcherService { + GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:nil + fetcherService:fetcherService]; + [fetcher setLocationURL:uploadLocationURL + uploadMIMEType:uploadMIMEType + chunkSize:chunkSize]; + 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]; + 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 + 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 { + // Collect the background session upload fetchers that are still in memory. + NSPointerArray *uploadFetcherPointerArray = [self uploadFetcherPointerArrayForBackgroundSessions]; + [uploadFetcherPointerArray compact]; + NSMutableSet *restoredSessionIdentifiers = [[NSMutableSet alloc] init]; + NSMutableArray *uploadFetchers = [[NSMutableArray alloc] init]; + for (GTMSessionUploadFetcher *uploadFetcher in uploadFetcherPointerArray) { + NSString *sessionIdentifier = uploadFetcher.chunkFetcher.sessionIdentifier; + if (sessionIdentifier) { + [restoredSessionIdentifiers addObject:sessionIdentifier]; + [uploadFetchers addObject:uploadFetcher]; + } + } + + // 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; + } +} + +- (void)setChunkSize:(int64_t)chunkSize { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _chunkSize = chunkSize; + } +} + +- (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:@"resumable" + 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 { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + GTMSESSION_ASSERT_DEBUG(chunkSize > 0, @"chunk size is zero"); + + // 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); + resultData = [uploadData subdataWithRange:range]; + } + 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 (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)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]; +} + +- (void)releaseUploadAndBaseCallbacks { + @synchronized(self) { + GTMSessionMonitorSynchronized(self); + + _delegateCallbackQueue = nil; + _delegateCompletionHandler = nil; + _uploadDataProvider = 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]; + } +} + +#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 (offset >= fullUploadLength || uploadStatus == kStatusFinal) { + // 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 { + 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 (error) { + GTMSESSION_LOG_DEBUG(@"cancelFetcher %@", error); + } + }]; +} + +- (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 (_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); + 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 + NSURLRequest *origRequest = self.request; + NSString *userAgent = [origRequest valueForHTTPHeaderField:@"User-Agent"]; + if (userAgent.length > 0) { + [chunkRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + } + // 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). + int64_t previousContentLength = + [[chunkFetcher.request valueForHTTPHeaderField:@"Content-Length"] longLongValue]; + // The Content-Length header may not be present if the chunk fetcher was recreated from + // a background session. + BOOL hasKnownChunkSize = (previousContentLength > 0); + 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:%zd (%lld + %zd) fullUploadLength:%lld" + @" chunkFetcher:%@ requestHeaders:%@ responseHeaders:%@", + [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], + newOffset, self.currentOffset, previousContentLength, + [self fullUploadLength], + chunkFetcher, chunkFetcher.request.allHTTPHeaderFields, + responseHeaders); +#endif + if (isUploadStatusStopped || (_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:@"X-Goog-Upload-Chunk-Granularity"]; + 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; + } + + [super stopFetching]; +} + +#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, + 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]; + if (_useBackgroundSessionOnChunkFetchers) { + [uploadFetcherPointerArrayForBackgroundSessions addPointer:(__bridge void *)self]; + } else { + [[self class] removePointer:(__bridge void *)self + fromPointerArray: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/Example-macOS-Swift/Pods/Manifest.lock b/Example-macOS-Swift/Pods/Manifest.lock new file mode 100644 index 00000000..72d992cd --- /dev/null +++ b/Example-macOS-Swift/Pods/Manifest.lock @@ -0,0 +1,22 @@ +PODS: + - AppAuth (0.9.1) + - GTMAppAuth (0.6.1): + - AppAuth (~> 0.9.0) + - GTMSessionFetcher (~> 1.1) + - GTMSessionFetcher (1.1.12): + - GTMSessionFetcher/Full (= 1.1.12) + - GTMSessionFetcher/Core (1.1.12) + - GTMSessionFetcher/Full (1.1.12): + - GTMSessionFetcher/Core (= 1.1.12) + +DEPENDENCIES: + - GTMAppAuth + +SPEC CHECKSUMS: + AppAuth: f7d9a2ce4ffe79a62a8b0c1d0938e88d9809b846 + GTMAppAuth: ae710e1e53431e2bcdd57f9f92daa16a58a2947d + GTMSessionFetcher: ebaa1f79a5366922c1735f1566901f50beba23b7 + +PODFILE CHECKSUM: c3e1940b595222effba02ad4f5c6b515baa815aa + +COCOAPODS: 1.3.1 diff --git a/Example-macOS-Swift/Pods/Pods.xcodeproj/project.pbxproj b/Example-macOS-Swift/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 00000000..1ef59bce --- /dev/null +++ b/Example-macOS-Swift/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,1431 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0501BE98C86A4CDCE5C9638A9E135B26 /* OIDTokenUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 0C44F7BB1F763236E02E003C69E5AE43 /* OIDTokenUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 071B0B8EB5DB7034C67D450BE660F381 /* OIDScopes.h in Headers */ = {isa = PBXBuildFile; fileRef = CAF7CD0E2D117D5931952BAA3AEB4E5E /* OIDScopes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A47704B9B5D394DAEBCEF98EEEF0490 /* OIDTokenRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7B96521F4310D419338750B4346906BA /* OIDTokenRequest.m */; }; + 0E5EC87F8FED784FDF58B46885915E58 /* Pods-GTMAppAuth-SwiftTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 413750522DE19988DFE62F3CA5B62A99 /* Pods-GTMAppAuth-SwiftTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0F9D393FAD4B6987CC1EF1C2065D6EFF /* OIDAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 07D78AF814394887C2564A33AF0B97D2 /* OIDAuthorizationService.m */; }; + 0FAFD5DA4D4F8A45C233BC82446573D4 /* GTMSessionFetcherLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 5274C575B8528D9335223D046197AA6F /* GTMSessionFetcherLogging.m */; }; + 10C7EBD1E6B7DF10C7ADAB9082B928C2 /* OIDClientMetadataParameters.m in Sources */ = {isa = PBXBuildFile; fileRef = B511B87F4D63B758120067B048B60236 /* OIDClientMetadataParameters.m */; }; + 10F25337EF797BD9870D9BE4EE6BD080 /* GTMSessionFetcher-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B037F5E612C976789D770C02270309A /* GTMSessionFetcher-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 18212B525EC84CCFEF86C0B2A7165F53 /* OIDRedirectHTTPHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D9AB78A47ED03E10777117862C92301 /* OIDRedirectHTTPHandler.m */; }; + 1828B5DEA9D13EA3215D1C757131D1B0 /* GTMTVAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = C5A80BD8034544197CC34892BE3100BF /* GTMTVAuthorizationRequest.m */; }; + 183DEC663CF8EF1F49F785136406C150 /* GTMSessionUploadFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 268DE1FCEC59214D23A872C7730C6910 /* GTMSessionUploadFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 18BC67927A5B2085C59DDE132C05674F /* GTMKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = C32C8CB0332F42E4EBB2A2AD42D1AA5D /* GTMKeychain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1AC98EA2FD43DBE2A7BCA3A886279D25 /* GTMAppAuthFetcherAuthorization+Keychain.m in Sources */ = {isa = PBXBuildFile; fileRef = A00C27E0BE043A71583C23D752B1E8A3 /* GTMAppAuthFetcherAuthorization+Keychain.m */; }; + 1C0F162210CB659A08366D77B557500D /* OIDAuthState+Mac.h in Headers */ = {isa = PBXBuildFile; fileRef = 67B47A3BC538D680062F89C4391DD44F /* OIDAuthState+Mac.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 216AA5D514622354B2A6F66F4866D43A /* OIDGrantTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 58CF0886B6691826E9001D0848809DE4 /* OIDGrantTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 28950280C637F5524BEA6440211E4F28 /* AppAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53B4EB85578A387E7DADC7994F50B65D /* AppAuth.framework */; }; + 2924C1DB6FE6E2BA64333E9C2EDE26C2 /* OIDResponseTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 8EF1A9ECFC4901EE5550DC5260C8BF68 /* OIDResponseTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2B5E8BFC324A72D674A98B1CA5A75634 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D20EEF9D90DAF4AA2D35EC4CF6F1DE2 /* Cocoa.framework */; }; + 2D96E4D4DB79A089EBF9F50C2E1982E5 /* OIDError.m in Sources */ = {isa = PBXBuildFile; fileRef = 93B10BDD02765BD708ACF8232A74DF61 /* OIDError.m */; }; + 32EB70E95200FE0587D0045832CAE0B2 /* GTMSessionUploadFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 82366166F1654E3C43C8ADD2DBB02E4E /* GTMSessionUploadFetcher.m */; }; + 33D4E6D11F0CC6664D903C0BEAA6769A /* OIDRedirectHTTPHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = A77D913CAF4B259C8D51D55B163F7BD5 /* OIDRedirectHTTPHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 33EB759F1302E2D99711622D0EE47B92 /* OIDAuthorizationUICoordinatorMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E80A461F76258780EA99F9FD67C546F /* OIDAuthorizationUICoordinatorMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 348C0E9ACA4FA29266AE1F3A5B9CD041 /* OIDAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = E78B019C9C36C48BD44B2F3EA0792BA9 /* OIDAuthorizationResponse.m */; }; + 36BCDB951184C2D915AE19A6329ACD25 /* OIDErrorUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = A3D22FA70BC8303B12A33E07CA3A88D1 /* OIDErrorUtilities.m */; }; + 3C689B04EB90FC07C1A9A3272D1368AF /* GTMTVAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = D12646C17E949D541CDEF5C4D99B8841 /* GTMTVAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 41BDB58A5355A0BC032A17BDADFF7B3D /* GTMAppAuth-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 297E0CC7C29BEB9FB09BDEAC67FF801A /* GTMAppAuth-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 46631544E41DD3CCBC59D2583837F5BB /* Pods-GTMAppAuth-SwiftTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FB87DADF4C59E1E64609FE8720DBBB67 /* Pods-GTMAppAuth-SwiftTests-dummy.m */; }; + 46FED85F5BC8F448DDD112EC53C60ACC /* OIDAuthState.h in Headers */ = {isa = PBXBuildFile; fileRef = 43D77537E02762689103E865D918D9BD /* OIDAuthState.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4762675B70F012A1B10861DDE2EAF519 /* OIDRegistrationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DC71F1C429A0FB8AF5305B2423D637D /* OIDRegistrationResponse.m */; }; + 4A72C1BB33662CC93ECB77D6F3965449 /* OIDFieldMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 22228E4E3691989C05E9044E22DE3F15 /* OIDFieldMapping.m */; }; + 4CC5F1BE76D74AAC6F5DBC91E1154CC8 /* OIDRegistrationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = C3FCC35407C4D0D452ABF952514DA8DF /* OIDRegistrationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4D5D88181ACA41AB58896CB94BC2A97C /* GTMMIMEDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D7B6D8A66679318FD7D12426F40305C /* GTMMIMEDocument.m */; }; + 4DB10B227AC3BD611C82A84E10BDE676 /* GTMAppAuthFetcherAuthorization+Keychain.h in Headers */ = {isa = PBXBuildFile; fileRef = F0F97F505F964EBDCA70CFF735A22538 /* GTMAppAuthFetcherAuthorization+Keychain.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4F9543040008ED1267C9331125ADEBE9 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D20EEF9D90DAF4AA2D35EC4CF6F1DE2 /* Cocoa.framework */; }; + 4FEC2923A3ECF98744A04FA5D2F138F0 /* OIDAuthorizationService+Mac.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF11DC5B834E4A2438B04797AA762D9 /* OIDAuthorizationService+Mac.m */; }; + 5342AD2D8FAA2792FD40555E43267F3A /* OIDAuthStateErrorDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 7157CFD99DC756C5D2F00E09347555B7 /* OIDAuthStateErrorDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 583CFDE8FB3AD2DAA0046B2078E2827A /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D20EEF9D90DAF4AA2D35EC4CF6F1DE2 /* Cocoa.framework */; }; + 5ADD0C6F2091EA4111148B5ED2AC63D1 /* GTMReadMonitorInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 8876921E6A4BD450F4DE3D70E5A3008B /* GTMReadMonitorInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5AFDD64E6309093B05CBB574C85F17B6 /* GTMSessionFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 2F4253090F28F3831459A20263436AC7 /* GTMSessionFetcher.m */; }; + 5C608FF12C2C25ACF745B2C956ED8D43 /* GTMTVAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 60CBD562327AD90070D6B8D7C8630D05 /* GTMTVAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6047CAF59BBF117BD2DFC079092AA1C0 /* GTMAppAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = F9C6415E80E42B66D6B7D4AD400C14E5 /* GTMAppAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6685A0017D95BA5C1D4A53E339EC2A9D /* OIDFieldMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 505664F6EF8400430AA9BEABDEAC4A57 /* OIDFieldMapping.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 676F3B687AFA8A9C463F0A504F92BB80 /* GTMGatherInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = 8159ACC8DF4E850502744CA7F1200D49 /* GTMGatherInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6934E43100EAE802983ECF8C98520A67 /* OIDDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = A431FF9E9BCC60EE5B2BAFEEB3B6E8F8 /* OIDDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6AF4038958B300E6B366CCFF689BDA82 /* Pods-GTMAppAuth-Swift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C48CF3FC1A4A66F3BD7D21ED6984D3E1 /* Pods-GTMAppAuth-Swift-dummy.m */; }; + 6BFBA98190A2346A16D6ADD54019D8C2 /* Pods-GTMAppAuth-Swift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = EFD1FB5B997A336D62E7DF18372B5F3C /* Pods-GTMAppAuth-Swift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 75B3683D881B94A9B623E4179FE31C97 /* OIDServiceConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B0A957F9E9D9A7D401FD8817B29DA76 /* OIDServiceConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 76AA703F0D7152586FE6490C6A2582A4 /* AppAuth-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 0112B7561413209E5EF520F8D21394D1 /* AppAuth-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 77AAB26213802D97147E0D72264AACF2 /* OIDURLQueryComponent.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F455614C198351B498225B7154EC910 /* OIDURLQueryComponent.m */; }; + 7900BA9343FEF8BFA54F428020B112DE /* OIDGrantTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9FE460C14B2DE9BE874B3596196FC5 /* OIDGrantTypes.m */; }; + 7C791AFD4F39448205506BFBE6124819 /* GTMSessionFetcher-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C9731DFBB065378B86F6591BCC7AA1 /* GTMSessionFetcher-dummy.m */; }; + 7D3B50A7278A59D7A3B70DADFE0BC630 /* OIDLoopbackHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = CA7897DD44C568B4F2086BEE55B78C41 /* OIDLoopbackHTTPServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7E76E78F899AE0C548EBD644420DD7A6 /* AppAuth-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 72F262F8143A7A44A999FB185E7B4659 /* AppAuth-dummy.m */; }; + 80F56A5D5F8A6F1199BED8D287F51BEB /* GTMSessionFetcherService.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F565EA67EA0BE556816B3062837D5A7 /* GTMSessionFetcherService.m */; }; + 88BB8F99EE79F5666FD79986320326A0 /* GTMTVAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B60F7C329A120E26E6CFE710659473B /* GTMTVAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 895074C4B272EFA98F4F666A7DC0EC2B /* GTMTVServiceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 726BD793C0AE8E48CA3283ADB10E2B0B /* GTMTVServiceConfiguration.m */; }; + 92369D6CC04CF24653B0DA755AA9190A /* OIDServiceDiscovery.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B51D6731690E0E5801B492AB8EF47B8 /* OIDServiceDiscovery.m */; }; + 932E5A1EC5F04B9FE25911E5BC0C6146 /* OIDAuthStateChangeDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A8A269CE9FCEDD799E374F6DF051620 /* OIDAuthStateChangeDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C2C750E76FFA304EA3C276B055491A4 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93521AC77C2E3814D72B0D39CEB2D9DB /* SystemConfiguration.framework */; }; + A35DE60BB082CCC1192D79AE9EA14CDD /* GTMOAuth2KeychainCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = A8279A101DB1677A61A7302D93991BD2 /* GTMOAuth2KeychainCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A3A4D75C09BB5BC2DCAFA483A29211D6 /* OIDLoopbackHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E91F0AAEF743D6557173C5E3F31A2C9 /* OIDLoopbackHTTPServer.m */; }; + A4EE1773ABB8C7026A59D87B8F6B1B16 /* OIDAuthorizationService+Mac.h in Headers */ = {isa = PBXBuildFile; fileRef = C895C9D57BDA294C1B27B40B3E5AE55D /* OIDAuthorizationService+Mac.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A67F83DDF630CB2010CCEE0D580CD5CA /* GTMGatherInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A423EE85ACBEE686E236AB5AAF5C738 /* GTMGatherInputStream.m */; }; + A8EF00E50074EE82923C01438189C6E4 /* OIDAuthorizationUICoordinatorMac.m in Sources */ = {isa = PBXBuildFile; fileRef = 0130CD89E9B99E97B57227CC26D40EC7 /* OIDAuthorizationUICoordinatorMac.m */; }; + B1A61D7B400E2FEF1BD8064A077960E9 /* OIDErrorUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 26ED91A2A94F828D6ACC08D3E5BE54AF /* OIDErrorUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B36FAD5B4431C8B622CCC9DB5015C5B9 /* GTMSessionFetcherService.h in Headers */ = {isa = PBXBuildFile; fileRef = 46BAF9A2BBD7CE4EFBBFAAFCB5837452 /* GTMSessionFetcherService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B4241D5E2A9C0423DEABDDBB3D1DC802 /* OIDTokenRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 50EF40B896FF10256516BED3A559960B /* OIDTokenRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B69D8CB8B54FC618138EE849B5A445E5 /* GTMMIMEDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 63D61EB9DE0F816EB6B838B945A22B68 /* GTMMIMEDocument.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BA5B6333F3D0109F2BC3EBDF688D2201 /* OIDClientMetadataParameters.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BEF94F31A33E29E05BA3C2423EC70DF /* OIDClientMetadataParameters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BB048558432A3179AA92009354D2E5F5 /* OIDAuthorizationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7092CB85D0A7C887E1E884BED86D0EEC /* OIDAuthorizationRequest.m */; }; + BB5742BF9974168C01C478822DC9DFA1 /* OIDAuthState.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AABFBB0AAECDFAB6F8911F387668306 /* OIDAuthState.m */; }; + C19835EF67CEE7DA206A7590AE337E37 /* OIDTokenResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = D8D3CD9449622ABF352E50DB287180F5 /* OIDTokenResponse.m */; }; + C1B91EFBC60DCCAD7A0FAD11294E525E /* GTMAppAuthFetcherAuthorization.h in Headers */ = {isa = PBXBuildFile; fileRef = 457815DCCE9D0293B4F84A2C75937E8F /* GTMAppAuthFetcherAuthorization.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C3A5E7AE2F999EE67F640609ED448EDE /* GTMTVAuthorizationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 312E46E7D357DB9965D96678DDAB2F97 /* GTMTVAuthorizationService.m */; }; + C50B5AE4D9AA708588655C684AACBFCC /* GTMAppAuth-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = AF702DA5013A7DACFAEF9CBD0C54BFE3 /* GTMAppAuth-dummy.m */; }; + C547A1EA78CF73C6204231F683A284E5 /* GTMTVServiceConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A5449B8BEA459AE0C99C290A0701E4B /* GTMTVServiceConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C5D5DA79B595A18EBB91A334B34BECF5 /* OIDRegistrationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = C1327A1D3EC76F2B2BF8E8D567174777 /* OIDRegistrationRequest.m */; }; + CBF2346CDBEF00B56EFB8A8E78B94ED3 /* OIDResponseTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 59C1538F226597496B5F09B662BA71D5 /* OIDResponseTypes.m */; }; + CCB03C9F741FC6694E33712C425A106A /* GTMSessionFetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E5DAB2CD6B6549BFF5E9899963A5A9A /* GTMSessionFetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CFE1B28F0A9AD7EBBEB3C670861D257F /* GTMSessionFetcher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 101F4F86B9070A68CFD56E3F4D547EA5 /* GTMSessionFetcher.framework */; }; + D096B01C9682BABED1BB0A61960DD293 /* GTMTVAuthorizationResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 0462E7203AF8F9F64AF1225B590F222D /* GTMTVAuthorizationResponse.m */; }; + D12BAFBFD9A0D4BBD821DD77D26E47AB /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9BBCD341C016CCD4DA85EDFB82E5F21 /* Security.framework */; }; + D241E6835562A20D16C728E4161DB0D9 /* GTMKeychain_macOS.m in Sources */ = {isa = PBXBuildFile; fileRef = CD394D3BD3F143E98355A3BE9E6A3DE2 /* GTMKeychain_macOS.m */; }; + D2B0BA564A1623C24D2B133845CE7DE1 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D20EEF9D90DAF4AA2D35EC4CF6F1DE2 /* Cocoa.framework */; }; + D522E7BADA4569415A848B5540488701 /* OIDAuthorizationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AE315D9D46986A57ABD5012C86DFA07 /* OIDAuthorizationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D5863B91BDEDA494D2DCBEB9E1EC449E /* OIDAuthorizationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 07C3DA3E875BCA0E8D43723F191CAF24 /* OIDAuthorizationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D613497C88F148E79D78A1A1C7C64852 /* GTMSessionFetcherLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = 59618C0D2FA6FE200F79DA915293F024 /* GTMSessionFetcherLogging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DE8F05F622CD2B3C584F967E25226BCE /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9BBCD341C016CCD4DA85EDFB82E5F21 /* Security.framework */; }; + DFA541A7FEB9F1160FD7EE04EABA4877 /* GTMAppAuthFetcherAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = 71FDAEC106D6CEC2EA6A556FF738C62D /* GTMAppAuthFetcherAuthorization.m */; }; + E03F982D4F08825ED8BD7FA8EE186CE2 /* OIDAuthorizationUICoordinator.h in Headers */ = {isa = PBXBuildFile; fileRef = A6D79FEEF4102D4E6E683243FEEB4603 /* OIDAuthorizationUICoordinator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E488FB8A760F80ACA91452DB63E0FCE9 /* OIDAuthState+Mac.m in Sources */ = {isa = PBXBuildFile; fileRef = C560E4E01EEE911D5150829999D6B4EA /* OIDAuthState+Mac.m */; }; + E6597EDC5C52CD30F0A5F57A4A1CC2E0 /* OIDURLQueryComponent.h in Headers */ = {isa = PBXBuildFile; fileRef = EA6AD25C336570EEFC9154E40D8C2B38 /* OIDURLQueryComponent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E7C39F3BE7334CF218F16B51615AC7B5 /* OIDTokenUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC5F35397DD767B8966C6F4ED00453E /* OIDTokenUtilities.m */; }; + EC4616372BD38EF2A40AFFCBF7E96F7E /* OIDScopeUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = AE5EA74E8ABD2C821C3CC150EEEA897D /* OIDScopeUtilities.m */; }; + EDA4CDD574072B736B7FB5D01014D97D /* OIDServiceConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = BBC090E9CCDCC87A01703E2C2AAE048C /* OIDServiceConfiguration.m */; }; + EE12A0AADF847F428EF404B28B628F91 /* OIDServiceDiscovery.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D9B2B2E38AA14CE13057437301ADADB /* OIDServiceDiscovery.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EF0DFF4F66056FD879B4893355C06346 /* OIDScopes.m in Sources */ = {isa = PBXBuildFile; fileRef = 6CDCA60C8FEDA026A4394731C508A00B /* OIDScopes.m */; }; + F45FDB243696D75FC5FF5A474500BE0B /* OIDError.h in Headers */ = {isa = PBXBuildFile; fileRef = 1CCC22A54E82AE9BA46B65E007BB3F96 /* OIDError.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F486C6CC51986D65DE458AAAC9CCC62F /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D20EEF9D90DAF4AA2D35EC4CF6F1DE2 /* Cocoa.framework */; }; + F4DB97C8CFFA7A9EB44DCE608FEB51CD /* OIDAuthorizationRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = D027C3989087158567846AA80BBC9CF5 /* OIDAuthorizationRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F4FF216CDC340E892963F5C1E39EC3D3 /* GTMReadMonitorInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 88A9EF9DFBDDD17CA1D16E2C822C6BC9 /* GTMReadMonitorInputStream.m */; }; + F6BCAC4442AC134D238182140D001710 /* AppAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DCB1A4C274558930973FD218F34B0A7 /* AppAuth.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F795B8D59DC50B92EAB0CAEE3D47B911 /* OIDScopeUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = D1D2881D87D1FDF06D6A8E4B6488967C /* OIDScopeUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F9032BEAE2CF7B0BB642E3C4502F766D /* OIDTokenResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 0ACA2D9EDC717944EC9B5417DB942840 /* OIDTokenResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FBF0513A91E7D9868DD4F122C573DFDD /* GTMOAuth2KeychainCompatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 63AD5AA4AE64918D8E1FA3AF6C5F973F /* GTMOAuth2KeychainCompatibility.m */; }; + FE0B8DF15A81F382BB038B7E2128D79A /* OIDRegistrationResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = A9E5D3E621135652A7F13A02D6D4EF96 /* OIDRegistrationResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 16387049D70253869E6E30BB8BB1AA08 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 85A8752F7E46278E78EC494B84140286; + remoteInfo = GTMAppAuth; + }; + 1E76E47F0C2F6C9131FC11BAA25C0325 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 44C9CC69DED21A4BBE47C8AD14D5B2AB; + remoteInfo = GTMSessionFetcher; + }; + 73D326909123B51CD71D389E447B51C2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 44C9CC69DED21A4BBE47C8AD14D5B2AB; + remoteInfo = GTMSessionFetcher; + }; + A776789CFC475351EF8467E28B59B7B3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2178B95FF925655C2AFA1BE15E7F1C4D; + remoteInfo = AppAuth; + }; + C22AA327F2A7EAAA3E9634F51BAB4FA5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2178B95FF925655C2AFA1BE15E7F1C4D; + remoteInfo = AppAuth; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0112B7561413209E5EF520F8D21394D1 /* AppAuth-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "AppAuth-umbrella.h"; sourceTree = ""; }; + 0130CD89E9B99E97B57227CC26D40EC7 /* OIDAuthorizationUICoordinatorMac.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationUICoordinatorMac.m; path = Source/macOS/OIDAuthorizationUICoordinatorMac.m; sourceTree = ""; }; + 0462E7203AF8F9F64AF1225B590F222D /* GTMTVAuthorizationResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMTVAuthorizationResponse.m; path = Source/GTMTVAuthorizationResponse.m; sourceTree = ""; }; + 047A2A549688C65BB01A778D1EB1982C /* Pods-GTMAppAuth-Swift.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-GTMAppAuth-Swift.modulemap"; sourceTree = ""; }; + 07C3DA3E875BCA0E8D43723F191CAF24 /* OIDAuthorizationService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationService.h; path = Source/OIDAuthorizationService.h; sourceTree = ""; }; + 07D78AF814394887C2564A33AF0B97D2 /* OIDAuthorizationService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationService.m; path = Source/OIDAuthorizationService.m; sourceTree = ""; }; + 0AABFBB0AAECDFAB6F8911F387668306 /* OIDAuthState.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthState.m; path = Source/OIDAuthState.m; sourceTree = ""; }; + 0ACA2D9EDC717944EC9B5417DB942840 /* OIDTokenResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDTokenResponse.h; path = Source/OIDTokenResponse.h; sourceTree = ""; }; + 0AF8F6938BBCD936459F7F0B7A10CD21 /* GTMAppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTMAppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0B0A957F9E9D9A7D401FD8817B29DA76 /* OIDServiceConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDServiceConfiguration.h; path = Source/OIDServiceConfiguration.h; sourceTree = ""; }; + 0BEF94F31A33E29E05BA3C2423EC70DF /* OIDClientMetadataParameters.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDClientMetadataParameters.h; path = Source/OIDClientMetadataParameters.h; sourceTree = ""; }; + 0C44F7BB1F763236E02E003C69E5AE43 /* OIDTokenUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDTokenUtilities.h; path = Source/OIDTokenUtilities.h; sourceTree = ""; }; + 101F4F86B9070A68CFD56E3F4D547EA5 /* GTMSessionFetcher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTMSessionFetcher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1263E43D993B842319B799681F37BAB3 /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 16D9A6141D158E619CCE4AF70B95A014 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1796081CA5F1C777C4EF2B234F2241A5 /* Pods-GTMAppAuth-SwiftTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GTMAppAuth-SwiftTests.release.xcconfig"; sourceTree = ""; }; + 1CCC22A54E82AE9BA46B65E007BB3F96 /* OIDError.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDError.h; path = Source/OIDError.h; sourceTree = ""; }; + 1EC5F35397DD767B8966C6F4ED00453E /* OIDTokenUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDTokenUtilities.m; path = Source/OIDTokenUtilities.m; sourceTree = ""; }; + 216B3523D5F612DCE6FAA1939D869536 /* Pods-GTMAppAuth-SwiftTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-GTMAppAuth-SwiftTests-acknowledgements.plist"; sourceTree = ""; }; + 22228E4E3691989C05E9044E22DE3F15 /* OIDFieldMapping.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDFieldMapping.m; path = Source/OIDFieldMapping.m; sourceTree = ""; }; + 268DE1FCEC59214D23A872C7730C6910 /* GTMSessionUploadFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionUploadFetcher.h; path = Source/GTMSessionUploadFetcher.h; sourceTree = ""; }; + 26ED91A2A94F828D6ACC08D3E5BE54AF /* OIDErrorUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDErrorUtilities.h; path = Source/OIDErrorUtilities.h; sourceTree = ""; }; + 297E0CC7C29BEB9FB09BDEAC67FF801A /* GTMAppAuth-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMAppAuth-umbrella.h"; sourceTree = ""; }; + 2D9B2B2E38AA14CE13057437301ADADB /* OIDServiceDiscovery.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDServiceDiscovery.h; path = Source/OIDServiceDiscovery.h; sourceTree = ""; }; + 2DC71F1C429A0FB8AF5305B2423D637D /* OIDRegistrationResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDRegistrationResponse.m; path = Source/OIDRegistrationResponse.m; sourceTree = ""; }; + 2F4253090F28F3831459A20263436AC7 /* GTMSessionFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcher.m; path = Source/GTMSessionFetcher.m; sourceTree = ""; }; + 312E46E7D357DB9965D96678DDAB2F97 /* GTMTVAuthorizationService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMTVAuthorizationService.m; path = Source/GTMTVAuthorizationService.m; sourceTree = ""; }; + 395B9B7E867CC2DA09F00ED89C45DCAF /* Pods-GTMAppAuth-Swift-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-GTMAppAuth-Swift-acknowledgements.plist"; sourceTree = ""; }; + 3D9AB78A47ED03E10777117862C92301 /* OIDRedirectHTTPHandler.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDRedirectHTTPHandler.m; path = Source/macOS/OIDRedirectHTTPHandler.m; sourceTree = ""; }; + 3DEC46BC31E7BB2902689A368FFC154D /* GTMAppAuth-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMAppAuth-prefix.pch"; sourceTree = ""; }; + 3F565EA67EA0BE556816B3062837D5A7 /* GTMSessionFetcherService.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcherService.m; path = Source/GTMSessionFetcherService.m; sourceTree = ""; }; + 413750522DE19988DFE62F3CA5B62A99 /* Pods-GTMAppAuth-SwiftTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-GTMAppAuth-SwiftTests-umbrella.h"; sourceTree = ""; }; + 41477A415ED31CB9F0992A05E3D0C98C /* Pods-GTMAppAuth-SwiftTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-GTMAppAuth-SwiftTests-acknowledgements.markdown"; sourceTree = ""; }; + 43D77537E02762689103E865D918D9BD /* OIDAuthState.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthState.h; path = Source/OIDAuthState.h; sourceTree = ""; }; + 457815DCCE9D0293B4F84A2C75937E8F /* GTMAppAuthFetcherAuthorization.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMAppAuthFetcherAuthorization.h; path = Source/GTMAppAuthFetcherAuthorization.h; sourceTree = ""; }; + 46BAF9A2BBD7CE4EFBBFAAFCB5837452 /* GTMSessionFetcherService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcherService.h; path = Source/GTMSessionFetcherService.h; sourceTree = ""; }; + 4AE315D9D46986A57ABD5012C86DFA07 /* OIDAuthorizationResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationResponse.h; path = Source/OIDAuthorizationResponse.h; sourceTree = ""; }; + 4D7B6D8A66679318FD7D12426F40305C /* GTMMIMEDocument.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMMIMEDocument.m; path = Source/GTMMIMEDocument.m; sourceTree = ""; }; + 4FB692FD504D14D8F97AB2A2E94B7FAD /* Pods_GTMAppAuth_SwiftTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GTMAppAuth_SwiftTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 505664F6EF8400430AA9BEABDEAC4A57 /* OIDFieldMapping.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDFieldMapping.h; path = Source/OIDFieldMapping.h; sourceTree = ""; }; + 50EF40B896FF10256516BED3A559960B /* OIDTokenRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDTokenRequest.h; path = Source/OIDTokenRequest.h; sourceTree = ""; }; + 5274C575B8528D9335223D046197AA6F /* GTMSessionFetcherLogging.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionFetcherLogging.m; path = Source/GTMSessionFetcherLogging.m; sourceTree = ""; }; + 52ECED01430D95C5E502DA3A95680D4C /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 53B4EB85578A387E7DADC7994F50B65D /* AppAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5566E69D94D6DABDBE0C021CCBC139B3 /* Pods-GTMAppAuth-SwiftTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GTMAppAuth-SwiftTests.debug.xcconfig"; sourceTree = ""; }; + 5648D3094A66DA9022565AFDD279DE93 /* Pods-GTMAppAuth-SwiftTests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-GTMAppAuth-SwiftTests-frameworks.sh"; sourceTree = ""; }; + 58C9731DFBB065378B86F6591BCC7AA1 /* GTMSessionFetcher-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GTMSessionFetcher-dummy.m"; sourceTree = ""; }; + 58CF0886B6691826E9001D0848809DE4 /* OIDGrantTypes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDGrantTypes.h; path = Source/OIDGrantTypes.h; sourceTree = ""; }; + 59618C0D2FA6FE200F79DA915293F024 /* GTMSessionFetcherLogging.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcherLogging.h; path = Source/GTMSessionFetcherLogging.h; sourceTree = ""; }; + 59C1538F226597496B5F09B662BA71D5 /* OIDResponseTypes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDResponseTypes.m; path = Source/OIDResponseTypes.m; sourceTree = ""; }; + 5B037F5E612C976789D770C02270309A /* GTMSessionFetcher-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMSessionFetcher-umbrella.h"; sourceTree = ""; }; + 5B51D6731690E0E5801B492AB8EF47B8 /* OIDServiceDiscovery.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDServiceDiscovery.m; path = Source/OIDServiceDiscovery.m; sourceTree = ""; }; + 5DF11DC5B834E4A2438B04797AA762D9 /* OIDAuthorizationService+Mac.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "OIDAuthorizationService+Mac.m"; path = "Source/macOS/OIDAuthorizationService+Mac.m"; sourceTree = ""; }; + 5E91F0AAEF743D6557173C5E3F31A2C9 /* OIDLoopbackHTTPServer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDLoopbackHTTPServer.m; path = Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.m; sourceTree = ""; }; + 5F455614C198351B498225B7154EC910 /* OIDURLQueryComponent.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDURLQueryComponent.m; path = Source/OIDURLQueryComponent.m; sourceTree = ""; }; + 60CBD562327AD90070D6B8D7C8630D05 /* GTMTVAuthorizationRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMTVAuthorizationRequest.h; path = Source/GTMTVAuthorizationRequest.h; sourceTree = ""; }; + 63AD5AA4AE64918D8E1FA3AF6C5F973F /* GTMOAuth2KeychainCompatibility.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMOAuth2KeychainCompatibility.m; path = Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.m; sourceTree = ""; }; + 63D61EB9DE0F816EB6B838B945A22B68 /* GTMMIMEDocument.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMMIMEDocument.h; path = Source/GTMMIMEDocument.h; sourceTree = ""; }; + 67B47A3BC538D680062F89C4391DD44F /* OIDAuthState+Mac.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "OIDAuthState+Mac.h"; path = "Source/macOS/OIDAuthState+Mac.h"; sourceTree = ""; }; + 6A423EE85ACBEE686E236AB5AAF5C738 /* GTMGatherInputStream.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMGatherInputStream.m; path = Source/GTMGatherInputStream.m; sourceTree = ""; }; + 6B5E9280CF889696F19C4996F8F61C7D /* GTMSessionFetcher-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GTMSessionFetcher-prefix.pch"; sourceTree = ""; }; + 6CDCA60C8FEDA026A4394731C508A00B /* OIDScopes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDScopes.m; path = Source/OIDScopes.m; sourceTree = ""; }; + 6D20EEF9D90DAF4AA2D35EC4CF6F1DE2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; }; + 7092CB85D0A7C887E1E884BED86D0EEC /* OIDAuthorizationRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationRequest.m; path = Source/OIDAuthorizationRequest.m; sourceTree = ""; }; + 7157CFD99DC756C5D2F00E09347555B7 /* OIDAuthStateErrorDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthStateErrorDelegate.h; path = Source/OIDAuthStateErrorDelegate.h; sourceTree = ""; }; + 71FDAEC106D6CEC2EA6A556FF738C62D /* GTMAppAuthFetcherAuthorization.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMAppAuthFetcherAuthorization.m; path = Source/GTMAppAuthFetcherAuthorization.m; sourceTree = ""; }; + 726BD793C0AE8E48CA3283ADB10E2B0B /* GTMTVServiceConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMTVServiceConfiguration.m; path = Source/GTMTVServiceConfiguration.m; sourceTree = ""; }; + 72F262F8143A7A44A999FB185E7B4659 /* AppAuth-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "AppAuth-dummy.m"; sourceTree = ""; }; + 73DA4F52EAC2E3CDBDAF76296C72E54A /* Pods-GTMAppAuth-Swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GTMAppAuth-Swift.debug.xcconfig"; sourceTree = ""; }; + 7A5449B8BEA459AE0C99C290A0701E4B /* GTMTVServiceConfiguration.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMTVServiceConfiguration.h; path = Source/GTMTVServiceConfiguration.h; sourceTree = ""; }; + 7ABD6A9941B3674B5DCEA335FA0F08E0 /* Pods_GTMAppAuth_Swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GTMAppAuth_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7B60F7C329A120E26E6CFE710659473B /* GTMTVAuthorizationService.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMTVAuthorizationService.h; path = Source/GTMTVAuthorizationService.h; sourceTree = ""; }; + 7B96521F4310D419338750B4346906BA /* OIDTokenRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDTokenRequest.m; path = Source/OIDTokenRequest.m; sourceTree = ""; }; + 8159ACC8DF4E850502744CA7F1200D49 /* GTMGatherInputStream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMGatherInputStream.h; path = Source/GTMGatherInputStream.h; sourceTree = ""; }; + 82366166F1654E3C43C8ADD2DBB02E4E /* GTMSessionUploadFetcher.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMSessionUploadFetcher.m; path = Source/GTMSessionUploadFetcher.m; sourceTree = ""; }; + 853BAD385167BF90C28F3640EAC6D9C5 /* GTMSessionFetcher.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GTMSessionFetcher.xcconfig; sourceTree = ""; }; + 86A71FB0319124BCFD5A98DF323D3C7D /* Pods-GTMAppAuth-Swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-GTMAppAuth-Swift.release.xcconfig"; sourceTree = ""; }; + 8876921E6A4BD450F4DE3D70E5A3008B /* GTMReadMonitorInputStream.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMReadMonitorInputStream.h; path = Source/GTMReadMonitorInputStream.h; sourceTree = ""; }; + 88A9EF9DFBDDD17CA1D16E2C822C6BC9 /* GTMReadMonitorInputStream.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMReadMonitorInputStream.m; path = Source/GTMReadMonitorInputStream.m; sourceTree = ""; }; + 8AD2E34C5245400810A227FF68518C81 /* AppAuth-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "AppAuth-prefix.pch"; sourceTree = ""; }; + 8E5DAB2CD6B6549BFF5E9899963A5A9A /* GTMSessionFetcher.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMSessionFetcher.h; path = Source/GTMSessionFetcher.h; sourceTree = ""; }; + 8E80A461F76258780EA99F9FD67C546F /* OIDAuthorizationUICoordinatorMac.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationUICoordinatorMac.h; path = Source/macOS/OIDAuthorizationUICoordinatorMac.h; sourceTree = ""; }; + 8EF1A9ECFC4901EE5550DC5260C8BF68 /* OIDResponseTypes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDResponseTypes.h; path = Source/OIDResponseTypes.h; sourceTree = ""; }; + 93521AC77C2E3814D72B0D39CEB2D9DB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; }; + 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 93B10BDD02765BD708ACF8232A74DF61 /* OIDError.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDError.m; path = Source/OIDError.m; sourceTree = ""; }; + 9658C230B99D9094A07489D4BC6B5754 /* GTMSessionFetcher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GTMSessionFetcher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 98D2447F5F36ABFE2815F9EE2016BD7D /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9A0478C9E4AE662DC5BD11BB549D97D7 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9A8A269CE9FCEDD799E374F6DF051620 /* OIDAuthStateChangeDelegate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthStateChangeDelegate.h; path = Source/OIDAuthStateChangeDelegate.h; sourceTree = ""; }; + 9DCB1A4C274558930973FD218F34B0A7 /* AppAuth.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = AppAuth.h; path = Source/AppAuth.h; sourceTree = ""; }; + A00C27E0BE043A71583C23D752B1E8A3 /* GTMAppAuthFetcherAuthorization+Keychain.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "GTMAppAuthFetcherAuthorization+Keychain.m"; path = "Source/GTMAppAuthFetcherAuthorization+Keychain.m"; sourceTree = ""; }; + A3D22FA70BC8303B12A33E07CA3A88D1 /* OIDErrorUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDErrorUtilities.m; path = Source/OIDErrorUtilities.m; sourceTree = ""; }; + A431FF9E9BCC60EE5B2BAFEEB3B6E8F8 /* OIDDefines.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDDefines.h; path = Source/OIDDefines.h; sourceTree = ""; }; + A539DC252E1D09B7A4A2FDA9286A733C /* GTMSessionFetcher.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = GTMSessionFetcher.modulemap; sourceTree = ""; }; + A6D79FEEF4102D4E6E683243FEEB4603 /* OIDAuthorizationUICoordinator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationUICoordinator.h; path = Source/OIDAuthorizationUICoordinator.h; sourceTree = ""; }; + A77D913CAF4B259C8D51D55B163F7BD5 /* OIDRedirectHTTPHandler.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDRedirectHTTPHandler.h; path = Source/macOS/OIDRedirectHTTPHandler.h; sourceTree = ""; }; + A8279A101DB1677A61A7302D93991BD2 /* GTMOAuth2KeychainCompatibility.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMOAuth2KeychainCompatibility.h; path = Source/GTMOAuth2KeychainCompatibility/GTMOAuth2KeychainCompatibility.h; sourceTree = ""; }; + A9E5D3E621135652A7F13A02D6D4EF96 /* OIDRegistrationResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDRegistrationResponse.h; path = Source/OIDRegistrationResponse.h; sourceTree = ""; }; + AD992C9A8DA5C8F16A82AAAA5BD17221 /* Pods-GTMAppAuth-Swift-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-GTMAppAuth-Swift-acknowledgements.markdown"; sourceTree = ""; }; + AE5EA74E8ABD2C821C3CC150EEEA897D /* OIDScopeUtilities.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDScopeUtilities.m; path = Source/OIDScopeUtilities.m; sourceTree = ""; }; + AEE1A66DEB537CFF3AB112909D692722 /* AppAuth.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = AppAuth.modulemap; sourceTree = ""; }; + AF702DA5013A7DACFAEF9CBD0C54BFE3 /* GTMAppAuth-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GTMAppAuth-dummy.m"; sourceTree = ""; }; + B511B87F4D63B758120067B048B60236 /* OIDClientMetadataParameters.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDClientMetadataParameters.m; path = Source/OIDClientMetadataParameters.m; sourceTree = ""; }; + BBC090E9CCDCC87A01703E2C2AAE048C /* OIDServiceConfiguration.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDServiceConfiguration.m; path = Source/OIDServiceConfiguration.m; sourceTree = ""; }; + BE25B32EC60C6525929545B6A0F54FDA /* Pods-GTMAppAuth-SwiftTests-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-GTMAppAuth-SwiftTests-resources.sh"; sourceTree = ""; }; + C1327A1D3EC76F2B2BF8E8D567174777 /* OIDRegistrationRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDRegistrationRequest.m; path = Source/OIDRegistrationRequest.m; sourceTree = ""; }; + C32C8CB0332F42E4EBB2A2AD42D1AA5D /* GTMKeychain.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMKeychain.h; path = Source/GTMKeychain.h; sourceTree = ""; }; + C3FCC35407C4D0D452ABF952514DA8DF /* OIDRegistrationRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDRegistrationRequest.h; path = Source/OIDRegistrationRequest.h; sourceTree = ""; }; + C48CF3FC1A4A66F3BD7D21ED6984D3E1 /* Pods-GTMAppAuth-Swift-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-GTMAppAuth-Swift-dummy.m"; sourceTree = ""; }; + C560E4E01EEE911D5150829999D6B4EA /* OIDAuthState+Mac.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "OIDAuthState+Mac.m"; path = "Source/macOS/OIDAuthState+Mac.m"; sourceTree = ""; }; + C5A80BD8034544197CC34892BE3100BF /* GTMTVAuthorizationRequest.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMTVAuthorizationRequest.m; path = Source/GTMTVAuthorizationRequest.m; sourceTree = ""; }; + C5F02CA745D088E286DF686C047A662F /* Pods-GTMAppAuth-Swift-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-GTMAppAuth-Swift-frameworks.sh"; sourceTree = ""; }; + C7A8A18C9BE76C1D14CBA0F27287C0E9 /* Pods-GTMAppAuth-SwiftTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = "Pods-GTMAppAuth-SwiftTests.modulemap"; sourceTree = ""; }; + C895C9D57BDA294C1B27B40B3E5AE55D /* OIDAuthorizationService+Mac.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "OIDAuthorizationService+Mac.h"; path = "Source/macOS/OIDAuthorizationService+Mac.h"; sourceTree = ""; }; + CA7897DD44C568B4F2086BEE55B78C41 /* OIDLoopbackHTTPServer.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDLoopbackHTTPServer.h; path = Source/macOS/LoopbackHTTPServer/OIDLoopbackHTTPServer.h; sourceTree = ""; }; + CAF7CD0E2D117D5931952BAA3AEB4E5E /* OIDScopes.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDScopes.h; path = Source/OIDScopes.h; sourceTree = ""; }; + CD394D3BD3F143E98355A3BE9E6A3DE2 /* GTMKeychain_macOS.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GTMKeychain_macOS.m; path = Source/macOS/GTMKeychain_macOS.m; sourceTree = ""; }; + D027C3989087158567846AA80BBC9CF5 /* OIDAuthorizationRequest.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDAuthorizationRequest.h; path = Source/OIDAuthorizationRequest.h; sourceTree = ""; }; + D12646C17E949D541CDEF5C4D99B8841 /* GTMTVAuthorizationResponse.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMTVAuthorizationResponse.h; path = Source/GTMTVAuthorizationResponse.h; sourceTree = ""; }; + D1D2881D87D1FDF06D6A8E4B6488967C /* OIDScopeUtilities.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDScopeUtilities.h; path = Source/OIDScopeUtilities.h; sourceTree = ""; }; + D8D3CD9449622ABF352E50DB287180F5 /* OIDTokenResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDTokenResponse.m; path = Source/OIDTokenResponse.m; sourceTree = ""; }; + D9BBCD341C016CCD4DA85EDFB82E5F21 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + E42DE4F63677E6BD77652551081FF693 /* Pods-GTMAppAuth-Swift-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-GTMAppAuth-Swift-resources.sh"; sourceTree = ""; }; + E78B019C9C36C48BD44B2F3EA0792BA9 /* OIDAuthorizationResponse.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDAuthorizationResponse.m; path = Source/OIDAuthorizationResponse.m; sourceTree = ""; }; + EA6AD25C336570EEFC9154E40D8C2B38 /* OIDURLQueryComponent.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = OIDURLQueryComponent.h; path = Source/OIDURLQueryComponent.h; sourceTree = ""; }; + EE9FE460C14B2DE9BE874B3596196FC5 /* OIDGrantTypes.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = OIDGrantTypes.m; path = Source/OIDGrantTypes.m; sourceTree = ""; }; + EFD1FB5B997A336D62E7DF18372B5F3C /* Pods-GTMAppAuth-Swift-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-GTMAppAuth-Swift-umbrella.h"; sourceTree = ""; }; + F0F97F505F964EBDCA70CFF735A22538 /* GTMAppAuthFetcherAuthorization+Keychain.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "GTMAppAuthFetcherAuthorization+Keychain.h"; path = "Source/GTMAppAuthFetcherAuthorization+Keychain.h"; sourceTree = ""; }; + F58E212D5C6A9F713E2932A830E70B12 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F9C6415E80E42B66D6B7D4AD400C14E5 /* GTMAppAuth.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GTMAppAuth.h; path = Source/GTMAppAuth.h; sourceTree = ""; }; + FB87DADF4C59E1E64609FE8720DBBB67 /* Pods-GTMAppAuth-SwiftTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-GTMAppAuth-SwiftTests-dummy.m"; sourceTree = ""; }; + FBC891F22560DB414981345C91518191 /* GTMAppAuth.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = GTMAppAuth.modulemap; sourceTree = ""; }; + FECFCA08A60A5ACEB1B7BD6C72721533 /* AppAuth.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = AppAuth.xcconfig; sourceTree = ""; }; + FFB197384BC8ECFF394B4DC45D3CB040 /* GTMAppAuth.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GTMAppAuth.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 96D8126D85B34A48F75B07E2FD48E2A2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 583CFDE8FB3AD2DAA0046B2078E2827A /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C251ACBD06918135039BA1FAD514343A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 28950280C637F5524BEA6440211E4F28 /* AppAuth.framework in Frameworks */, + D2B0BA564A1623C24D2B133845CE7DE1 /* Cocoa.framework in Frameworks */, + CFE1B28F0A9AD7EBBEB3C670861D257F /* GTMSessionFetcher.framework in Frameworks */, + DE8F05F622CD2B3C584F967E25226BCE /* Security.framework in Frameworks */, + 9C2C750E76FFA304EA3C276B055491A4 /* SystemConfiguration.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D323930ECAFAAD587FAC051E4E93BB3C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F486C6CC51986D65DE458AAAC9CCC62F /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D58FB2237D3E7FDF578907C21E5F2370 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2B5E8BFC324A72D674A98B1CA5A75634 /* Cocoa.framework in Frameworks */, + D12BAFBFD9A0D4BBD821DD77D26E47AB /* Security.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D8EE65B32CC2C501A5DFAC9CB7274A0B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4F9543040008ED1267C9331125ADEBE9 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0EB08BA0B638D87BB7BA7B4035DAFFF5 /* Pods */ = { + isa = PBXGroup; + children = ( + 3D085F99B6B5BE4CA6B353A721D52670 /* AppAuth */, + 680B1FAC4D99A91F7B0C5A8ABE281D22 /* GTMAppAuth */, + AEA0269274C86D04BCC02E48F91608BE /* GTMSessionFetcher */, + ); + name = Pods; + sourceTree = ""; + }; + 14D3F3476480C47F662089559E5E42A5 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 17A19E3AB4FBD68C6C9C9757FBDA5C67 /* Pods-GTMAppAuth-Swift */, + 81710F3870D3956985F8602C437EAD89 /* Pods-GTMAppAuth-SwiftTests */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 17A19E3AB4FBD68C6C9C9757FBDA5C67 /* Pods-GTMAppAuth-Swift */ = { + isa = PBXGroup; + children = ( + 98D2447F5F36ABFE2815F9EE2016BD7D /* Info.plist */, + 047A2A549688C65BB01A778D1EB1982C /* Pods-GTMAppAuth-Swift.modulemap */, + AD992C9A8DA5C8F16A82AAAA5BD17221 /* Pods-GTMAppAuth-Swift-acknowledgements.markdown */, + 395B9B7E867CC2DA09F00ED89C45DCAF /* Pods-GTMAppAuth-Swift-acknowledgements.plist */, + C48CF3FC1A4A66F3BD7D21ED6984D3E1 /* Pods-GTMAppAuth-Swift-dummy.m */, + C5F02CA745D088E286DF686C047A662F /* Pods-GTMAppAuth-Swift-frameworks.sh */, + E42DE4F63677E6BD77652551081FF693 /* Pods-GTMAppAuth-Swift-resources.sh */, + EFD1FB5B997A336D62E7DF18372B5F3C /* Pods-GTMAppAuth-Swift-umbrella.h */, + 73DA4F52EAC2E3CDBDAF76296C72E54A /* Pods-GTMAppAuth-Swift.debug.xcconfig */, + 86A71FB0319124BCFD5A98DF323D3C7D /* Pods-GTMAppAuth-Swift.release.xcconfig */, + ); + name = "Pods-GTMAppAuth-Swift"; + path = "Target Support Files/Pods-GTMAppAuth-Swift"; + sourceTree = ""; + }; + 1B95FBEF930CCFF8ED6D05ADDDAD9FDF /* Products */ = { + isa = PBXGroup; + children = ( + 1263E43D993B842319B799681F37BAB3 /* AppAuth.framework */, + 0AF8F6938BBCD936459F7F0B7A10CD21 /* GTMAppAuth.framework */, + 9658C230B99D9094A07489D4BC6B5754 /* GTMSessionFetcher.framework */, + 7ABD6A9941B3674B5DCEA335FA0F08E0 /* Pods_GTMAppAuth_Swift.framework */, + 4FB692FD504D14D8F97AB2A2E94B7FAD /* Pods_GTMAppAuth_SwiftTests.framework */, + ); + name = Products; + sourceTree = ""; + }; + 34FDCD760056B54C7D96C74E9EB5D7A1 /* Core */ = { + isa = PBXGroup; + children = ( + 8E5DAB2CD6B6549BFF5E9899963A5A9A /* GTMSessionFetcher.h */, + 2F4253090F28F3831459A20263436AC7 /* GTMSessionFetcher.m */, + 59618C0D2FA6FE200F79DA915293F024 /* GTMSessionFetcherLogging.h */, + 5274C575B8528D9335223D046197AA6F /* GTMSessionFetcherLogging.m */, + 46BAF9A2BBD7CE4EFBBFAAFCB5837452 /* GTMSessionFetcherService.h */, + 3F565EA67EA0BE556816B3062837D5A7 /* GTMSessionFetcherService.m */, + 268DE1FCEC59214D23A872C7730C6910 /* GTMSessionUploadFetcher.h */, + 82366166F1654E3C43C8ADD2DBB02E4E /* GTMSessionUploadFetcher.m */, + ); + name = Core; + sourceTree = ""; + }; + 3D085F99B6B5BE4CA6B353A721D52670 /* AppAuth */ = { + isa = PBXGroup; + children = ( + 9DCB1A4C274558930973FD218F34B0A7 /* AppAuth.h */, + D027C3989087158567846AA80BBC9CF5 /* OIDAuthorizationRequest.h */, + 7092CB85D0A7C887E1E884BED86D0EEC /* OIDAuthorizationRequest.m */, + 4AE315D9D46986A57ABD5012C86DFA07 /* OIDAuthorizationResponse.h */, + E78B019C9C36C48BD44B2F3EA0792BA9 /* OIDAuthorizationResponse.m */, + 07C3DA3E875BCA0E8D43723F191CAF24 /* OIDAuthorizationService.h */, + 07D78AF814394887C2564A33AF0B97D2 /* OIDAuthorizationService.m */, + C895C9D57BDA294C1B27B40B3E5AE55D /* OIDAuthorizationService+Mac.h */, + 5DF11DC5B834E4A2438B04797AA762D9 /* OIDAuthorizationService+Mac.m */, + A6D79FEEF4102D4E6E683243FEEB4603 /* OIDAuthorizationUICoordinator.h */, + 8E80A461F76258780EA99F9FD67C546F /* OIDAuthorizationUICoordinatorMac.h */, + 0130CD89E9B99E97B57227CC26D40EC7 /* OIDAuthorizationUICoordinatorMac.m */, + 43D77537E02762689103E865D918D9BD /* OIDAuthState.h */, + 0AABFBB0AAECDFAB6F8911F387668306 /* OIDAuthState.m */, + 67B47A3BC538D680062F89C4391DD44F /* OIDAuthState+Mac.h */, + C560E4E01EEE911D5150829999D6B4EA /* OIDAuthState+Mac.m */, + 9A8A269CE9FCEDD799E374F6DF051620 /* OIDAuthStateChangeDelegate.h */, + 7157CFD99DC756C5D2F00E09347555B7 /* OIDAuthStateErrorDelegate.h */, + 0BEF94F31A33E29E05BA3C2423EC70DF /* OIDClientMetadataParameters.h */, + B511B87F4D63B758120067B048B60236 /* OIDClientMetadataParameters.m */, + A431FF9E9BCC60EE5B2BAFEEB3B6E8F8 /* OIDDefines.h */, + 1CCC22A54E82AE9BA46B65E007BB3F96 /* OIDError.h */, + 93B10BDD02765BD708ACF8232A74DF61 /* OIDError.m */, + 26ED91A2A94F828D6ACC08D3E5BE54AF /* OIDErrorUtilities.h */, + A3D22FA70BC8303B12A33E07CA3A88D1 /* OIDErrorUtilities.m */, + 505664F6EF8400430AA9BEABDEAC4A57 /* OIDFieldMapping.h */, + 22228E4E3691989C05E9044E22DE3F15 /* OIDFieldMapping.m */, + 58CF0886B6691826E9001D0848809DE4 /* OIDGrantTypes.h */, + EE9FE460C14B2DE9BE874B3596196FC5 /* OIDGrantTypes.m */, + CA7897DD44C568B4F2086BEE55B78C41 /* OIDLoopbackHTTPServer.h */, + 5E91F0AAEF743D6557173C5E3F31A2C9 /* OIDLoopbackHTTPServer.m */, + A77D913CAF4B259C8D51D55B163F7BD5 /* OIDRedirectHTTPHandler.h */, + 3D9AB78A47ED03E10777117862C92301 /* OIDRedirectHTTPHandler.m */, + C3FCC35407C4D0D452ABF952514DA8DF /* OIDRegistrationRequest.h */, + C1327A1D3EC76F2B2BF8E8D567174777 /* OIDRegistrationRequest.m */, + A9E5D3E621135652A7F13A02D6D4EF96 /* OIDRegistrationResponse.h */, + 2DC71F1C429A0FB8AF5305B2423D637D /* OIDRegistrationResponse.m */, + 8EF1A9ECFC4901EE5550DC5260C8BF68 /* OIDResponseTypes.h */, + 59C1538F226597496B5F09B662BA71D5 /* OIDResponseTypes.m */, + CAF7CD0E2D117D5931952BAA3AEB4E5E /* OIDScopes.h */, + 6CDCA60C8FEDA026A4394731C508A00B /* OIDScopes.m */, + D1D2881D87D1FDF06D6A8E4B6488967C /* OIDScopeUtilities.h */, + AE5EA74E8ABD2C821C3CC150EEEA897D /* OIDScopeUtilities.m */, + 0B0A957F9E9D9A7D401FD8817B29DA76 /* OIDServiceConfiguration.h */, + BBC090E9CCDCC87A01703E2C2AAE048C /* OIDServiceConfiguration.m */, + 2D9B2B2E38AA14CE13057437301ADADB /* OIDServiceDiscovery.h */, + 5B51D6731690E0E5801B492AB8EF47B8 /* OIDServiceDiscovery.m */, + 50EF40B896FF10256516BED3A559960B /* OIDTokenRequest.h */, + 7B96521F4310D419338750B4346906BA /* OIDTokenRequest.m */, + 0ACA2D9EDC717944EC9B5417DB942840 /* OIDTokenResponse.h */, + D8D3CD9449622ABF352E50DB287180F5 /* OIDTokenResponse.m */, + 0C44F7BB1F763236E02E003C69E5AE43 /* OIDTokenUtilities.h */, + 1EC5F35397DD767B8966C6F4ED00453E /* OIDTokenUtilities.m */, + EA6AD25C336570EEFC9154E40D8C2B38 /* OIDURLQueryComponent.h */, + 5F455614C198351B498225B7154EC910 /* OIDURLQueryComponent.m */, + B2EEB083B0DFDD8E8B10ADDA5075DE6B /* Support Files */, + ); + path = AppAuth; + sourceTree = ""; + }; + 46B3081EBBBD5A2E4F8576D1C577F324 /* Support Files */ = { + isa = PBXGroup; + children = ( + FBC891F22560DB414981345C91518191 /* GTMAppAuth.modulemap */, + FFB197384BC8ECFF394B4DC45D3CB040 /* GTMAppAuth.xcconfig */, + AF702DA5013A7DACFAEF9CBD0C54BFE3 /* GTMAppAuth-dummy.m */, + 3DEC46BC31E7BB2902689A368FFC154D /* GTMAppAuth-prefix.pch */, + 297E0CC7C29BEB9FB09BDEAC67FF801A /* GTMAppAuth-umbrella.h */, + 52ECED01430D95C5E502DA3A95680D4C /* Info.plist */, + ); + name = "Support Files"; + path = "../Target Support Files/GTMAppAuth"; + sourceTree = ""; + }; + 680B1FAC4D99A91F7B0C5A8ABE281D22 /* GTMAppAuth */ = { + isa = PBXGroup; + children = ( + F9C6415E80E42B66D6B7D4AD400C14E5 /* GTMAppAuth.h */, + 457815DCCE9D0293B4F84A2C75937E8F /* GTMAppAuthFetcherAuthorization.h */, + 71FDAEC106D6CEC2EA6A556FF738C62D /* GTMAppAuthFetcherAuthorization.m */, + F0F97F505F964EBDCA70CFF735A22538 /* GTMAppAuthFetcherAuthorization+Keychain.h */, + A00C27E0BE043A71583C23D752B1E8A3 /* GTMAppAuthFetcherAuthorization+Keychain.m */, + C32C8CB0332F42E4EBB2A2AD42D1AA5D /* GTMKeychain.h */, + CD394D3BD3F143E98355A3BE9E6A3DE2 /* GTMKeychain_macOS.m */, + A8279A101DB1677A61A7302D93991BD2 /* GTMOAuth2KeychainCompatibility.h */, + 63AD5AA4AE64918D8E1FA3AF6C5F973F /* GTMOAuth2KeychainCompatibility.m */, + 60CBD562327AD90070D6B8D7C8630D05 /* GTMTVAuthorizationRequest.h */, + C5A80BD8034544197CC34892BE3100BF /* GTMTVAuthorizationRequest.m */, + D12646C17E949D541CDEF5C4D99B8841 /* GTMTVAuthorizationResponse.h */, + 0462E7203AF8F9F64AF1225B590F222D /* GTMTVAuthorizationResponse.m */, + 7B60F7C329A120E26E6CFE710659473B /* GTMTVAuthorizationService.h */, + 312E46E7D357DB9965D96678DDAB2F97 /* GTMTVAuthorizationService.m */, + 7A5449B8BEA459AE0C99C290A0701E4B /* GTMTVServiceConfiguration.h */, + 726BD793C0AE8E48CA3283ADB10E2B0B /* GTMTVServiceConfiguration.m */, + 46B3081EBBBD5A2E4F8576D1C577F324 /* Support Files */, + ); + path = GTMAppAuth; + sourceTree = ""; + }; + 7372BC17B37BE0B6F6735BE6CE5CD950 /* Full */ = { + isa = PBXGroup; + children = ( + 8159ACC8DF4E850502744CA7F1200D49 /* GTMGatherInputStream.h */, + 6A423EE85ACBEE686E236AB5AAF5C738 /* GTMGatherInputStream.m */, + 63D61EB9DE0F816EB6B838B945A22B68 /* GTMMIMEDocument.h */, + 4D7B6D8A66679318FD7D12426F40305C /* GTMMIMEDocument.m */, + 8876921E6A4BD450F4DE3D70E5A3008B /* GTMReadMonitorInputStream.h */, + 88A9EF9DFBDDD17CA1D16E2C822C6BC9 /* GTMReadMonitorInputStream.m */, + ); + name = Full; + sourceTree = ""; + }; + 7DB346D0F39D3F0E887471402A8071AB = { + isa = PBXGroup; + children = ( + 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */, + BAF672F7D860E891799A2A352296367E /* Frameworks */, + 0EB08BA0B638D87BB7BA7B4035DAFFF5 /* Pods */, + 1B95FBEF930CCFF8ED6D05ADDDAD9FDF /* Products */, + 14D3F3476480C47F662089559E5E42A5 /* Targets Support Files */, + ); + sourceTree = ""; + }; + 81710F3870D3956985F8602C437EAD89 /* Pods-GTMAppAuth-SwiftTests */ = { + isa = PBXGroup; + children = ( + F58E212D5C6A9F713E2932A830E70B12 /* Info.plist */, + C7A8A18C9BE76C1D14CBA0F27287C0E9 /* Pods-GTMAppAuth-SwiftTests.modulemap */, + 41477A415ED31CB9F0992A05E3D0C98C /* Pods-GTMAppAuth-SwiftTests-acknowledgements.markdown */, + 216B3523D5F612DCE6FAA1939D869536 /* Pods-GTMAppAuth-SwiftTests-acknowledgements.plist */, + FB87DADF4C59E1E64609FE8720DBBB67 /* Pods-GTMAppAuth-SwiftTests-dummy.m */, + 5648D3094A66DA9022565AFDD279DE93 /* Pods-GTMAppAuth-SwiftTests-frameworks.sh */, + BE25B32EC60C6525929545B6A0F54FDA /* Pods-GTMAppAuth-SwiftTests-resources.sh */, + 413750522DE19988DFE62F3CA5B62A99 /* Pods-GTMAppAuth-SwiftTests-umbrella.h */, + 5566E69D94D6DABDBE0C021CCBC139B3 /* Pods-GTMAppAuth-SwiftTests.debug.xcconfig */, + 1796081CA5F1C777C4EF2B234F2241A5 /* Pods-GTMAppAuth-SwiftTests.release.xcconfig */, + ); + name = "Pods-GTMAppAuth-SwiftTests"; + path = "Target Support Files/Pods-GTMAppAuth-SwiftTests"; + sourceTree = ""; + }; + 97EE563DC1BC30C099EB8217D6AD4084 /* OS X */ = { + isa = PBXGroup; + children = ( + 6D20EEF9D90DAF4AA2D35EC4CF6F1DE2 /* Cocoa.framework */, + D9BBCD341C016CCD4DA85EDFB82E5F21 /* Security.framework */, + 93521AC77C2E3814D72B0D39CEB2D9DB /* SystemConfiguration.framework */, + ); + name = "OS X"; + sourceTree = ""; + }; + AEA0269274C86D04BCC02E48F91608BE /* GTMSessionFetcher */ = { + isa = PBXGroup; + children = ( + 34FDCD760056B54C7D96C74E9EB5D7A1 /* Core */, + 7372BC17B37BE0B6F6735BE6CE5CD950 /* Full */, + CC95BE2E4496C2A4DE8E15F0F2D6AA0C /* Support Files */, + ); + path = GTMSessionFetcher; + sourceTree = ""; + }; + B2EEB083B0DFDD8E8B10ADDA5075DE6B /* Support Files */ = { + isa = PBXGroup; + children = ( + AEE1A66DEB537CFF3AB112909D692722 /* AppAuth.modulemap */, + FECFCA08A60A5ACEB1B7BD6C72721533 /* AppAuth.xcconfig */, + 72F262F8143A7A44A999FB185E7B4659 /* AppAuth-dummy.m */, + 8AD2E34C5245400810A227FF68518C81 /* AppAuth-prefix.pch */, + 0112B7561413209E5EF520F8D21394D1 /* AppAuth-umbrella.h */, + 9A0478C9E4AE662DC5BD11BB549D97D7 /* Info.plist */, + ); + name = "Support Files"; + path = "../Target Support Files/AppAuth"; + sourceTree = ""; + }; + BAF672F7D860E891799A2A352296367E /* Frameworks */ = { + isa = PBXGroup; + children = ( + 53B4EB85578A387E7DADC7994F50B65D /* AppAuth.framework */, + 101F4F86B9070A68CFD56E3F4D547EA5 /* GTMSessionFetcher.framework */, + 97EE563DC1BC30C099EB8217D6AD4084 /* OS X */, + ); + name = Frameworks; + sourceTree = ""; + }; + CC95BE2E4496C2A4DE8E15F0F2D6AA0C /* Support Files */ = { + isa = PBXGroup; + children = ( + A539DC252E1D09B7A4A2FDA9286A733C /* GTMSessionFetcher.modulemap */, + 853BAD385167BF90C28F3640EAC6D9C5 /* GTMSessionFetcher.xcconfig */, + 58C9731DFBB065378B86F6591BCC7AA1 /* GTMSessionFetcher-dummy.m */, + 6B5E9280CF889696F19C4996F8F61C7D /* GTMSessionFetcher-prefix.pch */, + 5B037F5E612C976789D770C02270309A /* GTMSessionFetcher-umbrella.h */, + 16D9A6141D158E619CCE4AF70B95A014 /* Info.plist */, + ); + name = "Support Files"; + path = "../Target Support Files/GTMSessionFetcher"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 53D4B5E6F37E4C0E8865ED1FB1513D31 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 76AA703F0D7152586FE6490C6A2582A4 /* AppAuth-umbrella.h in Headers */, + F6BCAC4442AC134D238182140D001710 /* AppAuth.h in Headers */, + F4DB97C8CFFA7A9EB44DCE608FEB51CD /* OIDAuthorizationRequest.h in Headers */, + D522E7BADA4569415A848B5540488701 /* OIDAuthorizationResponse.h in Headers */, + A4EE1773ABB8C7026A59D87B8F6B1B16 /* OIDAuthorizationService+Mac.h in Headers */, + D5863B91BDEDA494D2DCBEB9E1EC449E /* OIDAuthorizationService.h in Headers */, + E03F982D4F08825ED8BD7FA8EE186CE2 /* OIDAuthorizationUICoordinator.h in Headers */, + 33EB759F1302E2D99711622D0EE47B92 /* OIDAuthorizationUICoordinatorMac.h in Headers */, + 1C0F162210CB659A08366D77B557500D /* OIDAuthState+Mac.h in Headers */, + 46FED85F5BC8F448DDD112EC53C60ACC /* OIDAuthState.h in Headers */, + 932E5A1EC5F04B9FE25911E5BC0C6146 /* OIDAuthStateChangeDelegate.h in Headers */, + 5342AD2D8FAA2792FD40555E43267F3A /* OIDAuthStateErrorDelegate.h in Headers */, + BA5B6333F3D0109F2BC3EBDF688D2201 /* OIDClientMetadataParameters.h in Headers */, + 6934E43100EAE802983ECF8C98520A67 /* OIDDefines.h in Headers */, + F45FDB243696D75FC5FF5A474500BE0B /* OIDError.h in Headers */, + B1A61D7B400E2FEF1BD8064A077960E9 /* OIDErrorUtilities.h in Headers */, + 6685A0017D95BA5C1D4A53E339EC2A9D /* OIDFieldMapping.h in Headers */, + 216AA5D514622354B2A6F66F4866D43A /* OIDGrantTypes.h in Headers */, + 7D3B50A7278A59D7A3B70DADFE0BC630 /* OIDLoopbackHTTPServer.h in Headers */, + 33D4E6D11F0CC6664D903C0BEAA6769A /* OIDRedirectHTTPHandler.h in Headers */, + 4CC5F1BE76D74AAC6F5DBC91E1154CC8 /* OIDRegistrationRequest.h in Headers */, + FE0B8DF15A81F382BB038B7E2128D79A /* OIDRegistrationResponse.h in Headers */, + 2924C1DB6FE6E2BA64333E9C2EDE26C2 /* OIDResponseTypes.h in Headers */, + 071B0B8EB5DB7034C67D450BE660F381 /* OIDScopes.h in Headers */, + F795B8D59DC50B92EAB0CAEE3D47B911 /* OIDScopeUtilities.h in Headers */, + 75B3683D881B94A9B623E4179FE31C97 /* OIDServiceConfiguration.h in Headers */, + EE12A0AADF847F428EF404B28B628F91 /* OIDServiceDiscovery.h in Headers */, + B4241D5E2A9C0423DEABDDBB3D1DC802 /* OIDTokenRequest.h in Headers */, + F9032BEAE2CF7B0BB642E3C4502F766D /* OIDTokenResponse.h in Headers */, + 0501BE98C86A4CDCE5C9638A9E135B26 /* OIDTokenUtilities.h in Headers */, + E6597EDC5C52CD30F0A5F57A4A1CC2E0 /* OIDURLQueryComponent.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2FD6CAF5CDF0877FBC58FA87062AB36 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 41BDB58A5355A0BC032A17BDADFF7B3D /* GTMAppAuth-umbrella.h in Headers */, + 6047CAF59BBF117BD2DFC079092AA1C0 /* GTMAppAuth.h in Headers */, + 4DB10B227AC3BD611C82A84E10BDE676 /* GTMAppAuthFetcherAuthorization+Keychain.h in Headers */, + C1B91EFBC60DCCAD7A0FAD11294E525E /* GTMAppAuthFetcherAuthorization.h in Headers */, + 18BC67927A5B2085C59DDE132C05674F /* GTMKeychain.h in Headers */, + A35DE60BB082CCC1192D79AE9EA14CDD /* GTMOAuth2KeychainCompatibility.h in Headers */, + 5C608FF12C2C25ACF745B2C956ED8D43 /* GTMTVAuthorizationRequest.h in Headers */, + 3C689B04EB90FC07C1A9A3272D1368AF /* GTMTVAuthorizationResponse.h in Headers */, + 88BB8F99EE79F5666FD79986320326A0 /* GTMTVAuthorizationService.h in Headers */, + C547A1EA78CF73C6204231F683A284E5 /* GTMTVServiceConfiguration.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E530412A4F113EEF40873B6739316D4D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 0E5EC87F8FED784FDF58B46885915E58 /* Pods-GTMAppAuth-SwiftTests-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F17B7862A3F2B9C04F9D40C1D6F560DA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 6BFBA98190A2346A16D6ADD54019D8C2 /* Pods-GTMAppAuth-Swift-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F5722ED2D8E31D278FC80D1FE9782CD7 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 676F3B687AFA8A9C463F0A504F92BB80 /* GTMGatherInputStream.h in Headers */, + B69D8CB8B54FC618138EE849B5A445E5 /* GTMMIMEDocument.h in Headers */, + 5ADD0C6F2091EA4111148B5ED2AC63D1 /* GTMReadMonitorInputStream.h in Headers */, + 10F25337EF797BD9870D9BE4EE6BD080 /* GTMSessionFetcher-umbrella.h in Headers */, + CCB03C9F741FC6694E33712C425A106A /* GTMSessionFetcher.h in Headers */, + D613497C88F148E79D78A1A1C7C64852 /* GTMSessionFetcherLogging.h in Headers */, + B36FAD5B4431C8B622CCC9DB5015C5B9 /* GTMSessionFetcherService.h in Headers */, + 183DEC663CF8EF1F49F785136406C150 /* GTMSessionUploadFetcher.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 2178B95FF925655C2AFA1BE15E7F1C4D /* AppAuth */ = { + isa = PBXNativeTarget; + buildConfigurationList = 959AE5694BAB95811F5CE6EA80C0994A /* Build configuration list for PBXNativeTarget "AppAuth" */; + buildPhases = ( + 9B3624C92F697E86AAE3499D4C6E0E5D /* Sources */, + D323930ECAFAAD587FAC051E4E93BB3C /* Frameworks */, + 53D4B5E6F37E4C0E8865ED1FB1513D31 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppAuth; + productName = AppAuth; + productReference = 1263E43D993B842319B799681F37BAB3 /* AppAuth.framework */; + productType = "com.apple.product-type.framework"; + }; + 22A0765D2D797E169C729DE920C761AA /* Pods-GTMAppAuth-SwiftTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5A789842166DDF517DEF9C739CBD8D70 /* Build configuration list for PBXNativeTarget "Pods-GTMAppAuth-SwiftTests" */; + buildPhases = ( + A29A9F2EE46DB6986AC319CD85866030 /* Sources */, + D8EE65B32CC2C501A5DFAC9CB7274A0B /* Frameworks */, + E530412A4F113EEF40873B6739316D4D /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Pods-GTMAppAuth-SwiftTests"; + productName = "Pods-GTMAppAuth-SwiftTests"; + productReference = 4FB692FD504D14D8F97AB2A2E94B7FAD /* Pods_GTMAppAuth_SwiftTests.framework */; + productType = "com.apple.product-type.framework"; + }; + 44C9CC69DED21A4BBE47C8AD14D5B2AB /* GTMSessionFetcher */ = { + isa = PBXNativeTarget; + buildConfigurationList = F3714C433C642A57C6EBFD53EE6EDDA1 /* Build configuration list for PBXNativeTarget "GTMSessionFetcher" */; + buildPhases = ( + 6BA231FD8DCFDC3F0022099336E13D24 /* Sources */, + D58FB2237D3E7FDF578907C21E5F2370 /* Frameworks */, + F5722ED2D8E31D278FC80D1FE9782CD7 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GTMSessionFetcher; + productName = GTMSessionFetcher; + productReference = 9658C230B99D9094A07489D4BC6B5754 /* GTMSessionFetcher.framework */; + productType = "com.apple.product-type.framework"; + }; + 4A5349AB5DE705D496BAF654AD37C7CE /* Pods-GTMAppAuth-Swift */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43CA1CB82F851BA79063B0F64B80D0E6 /* Build configuration list for PBXNativeTarget "Pods-GTMAppAuth-Swift" */; + buildPhases = ( + 0B37CEFB0FA382C2E7B3E4B08EA3B465 /* Sources */, + 96D8126D85B34A48F75B07E2FD48E2A2 /* Frameworks */, + F17B7862A3F2B9C04F9D40C1D6F560DA /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + 62A6DB73FD73BB705649CEA2338E55EA /* PBXTargetDependency */, + C007649FE9DF7F86FA95A4C80AE80866 /* PBXTargetDependency */, + F12BB0564B120E1483172869FC881AFC /* PBXTargetDependency */, + ); + name = "Pods-GTMAppAuth-Swift"; + productName = "Pods-GTMAppAuth-Swift"; + productReference = 7ABD6A9941B3674B5DCEA335FA0F08E0 /* Pods_GTMAppAuth_Swift.framework */; + productType = "com.apple.product-type.framework"; + }; + 85A8752F7E46278E78EC494B84140286 /* GTMAppAuth */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4BBA2C35500467ECC48E79CDBA79A536 /* Build configuration list for PBXNativeTarget "GTMAppAuth" */; + buildPhases = ( + 9DD64D55C8668746E408FDAB9669B1EE /* Sources */, + C251ACBD06918135039BA1FAD514343A /* Frameworks */, + A2FD6CAF5CDF0877FBC58FA87062AB36 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + F96EC5B84EAEF64C213C79883AB11853 /* PBXTargetDependency */, + 6F1F2C94996665E36CAD6C43B5730F09 /* PBXTargetDependency */, + ); + name = GTMAppAuth; + productName = GTMAppAuth; + productReference = 0AF8F6938BBCD936459F7F0B7A10CD21 /* GTMAppAuth.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D41D8CD98F00B204E9800998ECF8427E /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 0700; + }; + buildConfigurationList = 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 7DB346D0F39D3F0E887471402A8071AB; + productRefGroup = 1B95FBEF930CCFF8ED6D05ADDDAD9FDF /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2178B95FF925655C2AFA1BE15E7F1C4D /* AppAuth */, + 85A8752F7E46278E78EC494B84140286 /* GTMAppAuth */, + 44C9CC69DED21A4BBE47C8AD14D5B2AB /* GTMSessionFetcher */, + 4A5349AB5DE705D496BAF654AD37C7CE /* Pods-GTMAppAuth-Swift */, + 22A0765D2D797E169C729DE920C761AA /* Pods-GTMAppAuth-SwiftTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 0B37CEFB0FA382C2E7B3E4B08EA3B465 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6AF4038958B300E6B366CCFF689BDA82 /* Pods-GTMAppAuth-Swift-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6BA231FD8DCFDC3F0022099336E13D24 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A67F83DDF630CB2010CCEE0D580CD5CA /* GTMGatherInputStream.m in Sources */, + 4D5D88181ACA41AB58896CB94BC2A97C /* GTMMIMEDocument.m in Sources */, + F4FF216CDC340E892963F5C1E39EC3D3 /* GTMReadMonitorInputStream.m in Sources */, + 7C791AFD4F39448205506BFBE6124819 /* GTMSessionFetcher-dummy.m in Sources */, + 5AFDD64E6309093B05CBB574C85F17B6 /* GTMSessionFetcher.m in Sources */, + 0FAFD5DA4D4F8A45C233BC82446573D4 /* GTMSessionFetcherLogging.m in Sources */, + 80F56A5D5F8A6F1199BED8D287F51BEB /* GTMSessionFetcherService.m in Sources */, + 32EB70E95200FE0587D0045832CAE0B2 /* GTMSessionUploadFetcher.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B3624C92F697E86AAE3499D4C6E0E5D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E76E78F899AE0C548EBD644420DD7A6 /* AppAuth-dummy.m in Sources */, + BB048558432A3179AA92009354D2E5F5 /* OIDAuthorizationRequest.m in Sources */, + 348C0E9ACA4FA29266AE1F3A5B9CD041 /* OIDAuthorizationResponse.m in Sources */, + 4FEC2923A3ECF98744A04FA5D2F138F0 /* OIDAuthorizationService+Mac.m in Sources */, + 0F9D393FAD4B6987CC1EF1C2065D6EFF /* OIDAuthorizationService.m in Sources */, + A8EF00E50074EE82923C01438189C6E4 /* OIDAuthorizationUICoordinatorMac.m in Sources */, + E488FB8A760F80ACA91452DB63E0FCE9 /* OIDAuthState+Mac.m in Sources */, + BB5742BF9974168C01C478822DC9DFA1 /* OIDAuthState.m in Sources */, + 10C7EBD1E6B7DF10C7ADAB9082B928C2 /* OIDClientMetadataParameters.m in Sources */, + 2D96E4D4DB79A089EBF9F50C2E1982E5 /* OIDError.m in Sources */, + 36BCDB951184C2D915AE19A6329ACD25 /* OIDErrorUtilities.m in Sources */, + 4A72C1BB33662CC93ECB77D6F3965449 /* OIDFieldMapping.m in Sources */, + 7900BA9343FEF8BFA54F428020B112DE /* OIDGrantTypes.m in Sources */, + A3A4D75C09BB5BC2DCAFA483A29211D6 /* OIDLoopbackHTTPServer.m in Sources */, + 18212B525EC84CCFEF86C0B2A7165F53 /* OIDRedirectHTTPHandler.m in Sources */, + C5D5DA79B595A18EBB91A334B34BECF5 /* OIDRegistrationRequest.m in Sources */, + 4762675B70F012A1B10861DDE2EAF519 /* OIDRegistrationResponse.m in Sources */, + CBF2346CDBEF00B56EFB8A8E78B94ED3 /* OIDResponseTypes.m in Sources */, + EF0DFF4F66056FD879B4893355C06346 /* OIDScopes.m in Sources */, + EC4616372BD38EF2A40AFFCBF7E96F7E /* OIDScopeUtilities.m in Sources */, + EDA4CDD574072B736B7FB5D01014D97D /* OIDServiceConfiguration.m in Sources */, + 92369D6CC04CF24653B0DA755AA9190A /* OIDServiceDiscovery.m in Sources */, + 0A47704B9B5D394DAEBCEF98EEEF0490 /* OIDTokenRequest.m in Sources */, + C19835EF67CEE7DA206A7590AE337E37 /* OIDTokenResponse.m in Sources */, + E7C39F3BE7334CF218F16B51615AC7B5 /* OIDTokenUtilities.m in Sources */, + 77AAB26213802D97147E0D72264AACF2 /* OIDURLQueryComponent.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9DD64D55C8668746E408FDAB9669B1EE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C50B5AE4D9AA708588655C684AACBFCC /* GTMAppAuth-dummy.m in Sources */, + 1AC98EA2FD43DBE2A7BCA3A886279D25 /* GTMAppAuthFetcherAuthorization+Keychain.m in Sources */, + DFA541A7FEB9F1160FD7EE04EABA4877 /* GTMAppAuthFetcherAuthorization.m in Sources */, + D241E6835562A20D16C728E4161DB0D9 /* GTMKeychain_macOS.m in Sources */, + FBF0513A91E7D9868DD4F122C573DFDD /* GTMOAuth2KeychainCompatibility.m in Sources */, + 1828B5DEA9D13EA3215D1C757131D1B0 /* GTMTVAuthorizationRequest.m in Sources */, + D096B01C9682BABED1BB0A61960DD293 /* GTMTVAuthorizationResponse.m in Sources */, + C3A5E7AE2F999EE67F640609ED448EDE /* GTMTVAuthorizationService.m in Sources */, + 895074C4B272EFA98F4F666A7DC0EC2B /* GTMTVServiceConfiguration.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A29A9F2EE46DB6986AC319CD85866030 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 46631544E41DD3CCBC59D2583837F5BB /* Pods-GTMAppAuth-SwiftTests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 62A6DB73FD73BB705649CEA2338E55EA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = AppAuth; + target = 2178B95FF925655C2AFA1BE15E7F1C4D /* AppAuth */; + targetProxy = C22AA327F2A7EAAA3E9634F51BAB4FA5 /* PBXContainerItemProxy */; + }; + 6F1F2C94996665E36CAD6C43B5730F09 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMSessionFetcher; + target = 44C9CC69DED21A4BBE47C8AD14D5B2AB /* GTMSessionFetcher */; + targetProxy = 73D326909123B51CD71D389E447B51C2 /* PBXContainerItemProxy */; + }; + C007649FE9DF7F86FA95A4C80AE80866 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMAppAuth; + target = 85A8752F7E46278E78EC494B84140286 /* GTMAppAuth */; + targetProxy = 16387049D70253869E6E30BB8BB1AA08 /* PBXContainerItemProxy */; + }; + F12BB0564B120E1483172869FC881AFC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = GTMSessionFetcher; + target = 44C9CC69DED21A4BBE47C8AD14D5B2AB /* GTMSessionFetcher */; + targetProxy = 1E76E47F0C2F6C9131FC11BAA25C0325 /* PBXContainerItemProxy */; + }; + F96EC5B84EAEF64C213C79883AB11853 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = AppAuth; + target = 2178B95FF925655C2AFA1BE15E7F1C4D /* AppAuth */; + targetProxy = A776789CFC475351EF8467E28B59B7B3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 3982DFD54B631B4D12838F0C9FFA37C1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FECFCA08A60A5ACEB1B7BD6C72721533 /* AppAuth.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "Target Support Files/AppAuth/AppAuth-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/AppAuth/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_FILE = "Target Support Files/AppAuth/AppAuth.modulemap"; + PRODUCT_NAME = AppAuth; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 4D400F0B37DA03363ACEB7BA382D9145 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 73DA4F52EAC2E3CDBDAF76296C72E54A /* Pods-GTMAppAuth-Swift.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "Target Support Files/Pods-GTMAppAuth-Swift/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MODULEMAP_FILE = "Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = Pods_GTMAppAuth_Swift; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 5527E5B88611B42D6FD584D34E4C2818 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 86A71FB0319124BCFD5A98DF323D3C7D /* Pods-GTMAppAuth-Swift.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "Target Support Files/Pods-GTMAppAuth-Swift/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MODULEMAP_FILE = "Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = Pods_GTMAppAuth_Swift; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 578CC549C1AE484D4C28F1519419860E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FFB197384BC8ECFF394B4DC45D3CB040 /* GTMAppAuth.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMAppAuth/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + MODULEMAP_FILE = "Target Support Files/GTMAppAuth/GTMAppAuth.modulemap"; + PRODUCT_NAME = GTMAppAuth; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 586A4671E49737F941A0DB8965424864 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5566E69D94D6DABDBE0C021CCBC139B3 /* Pods-GTMAppAuth-SwiftTests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "Target Support Files/Pods-GTMAppAuth-SwiftTests/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MODULEMAP_FILE = "Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = Pods_GTMAppAuth_SwiftTests; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 6494002A2DF567C613030AF2F6EFE023 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1796081CA5F1C777C4EF2B234F2241A5 /* Pods-GTMAppAuth-SwiftTests.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "Target Support Files/Pods-GTMAppAuth-SwiftTests/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MODULEMAP_FILE = "Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = Pods_GTMAppAuth_SwiftTests; + SDKROOT = macosx; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 7A7B0C7ED3EE647D591B56237F662DD9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FECFCA08A60A5ACEB1B7BD6C72721533 /* AppAuth.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "Target Support Files/AppAuth/AppAuth-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/AppAuth/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MODULEMAP_FILE = "Target Support Files/AppAuth/AppAuth.modulemap"; + PRODUCT_NAME = AppAuth; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 99207FD0108845EF33E7047EDB4701AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FFB197384BC8ECFF394B4DC45D3CB040 /* GTMAppAuth.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMAppAuth/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + MODULEMAP_FILE = "Target Support Files/GTMAppAuth/GTMAppAuth.modulemap"; + PRODUCT_NAME = GTMAppAuth; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + A52053480DB690EA6D3B3F4989DAAF3F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 853BAD385167BF90C28F3640EAC6D9C5 /* GTMSessionFetcher.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMSessionFetcher/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MODULEMAP_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap"; + PRODUCT_NAME = GTMSessionFetcher; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + B1F3D0F50781A63DA8E7965399D7C4C3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 853BAD385167BF90C28F3640EAC6D9C5 /* GTMSessionFetcher.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + GCC_PREFIX_HEADER = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/GTMSessionFetcher/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MODULEMAP_FILE = "Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap"; + PRODUCT_NAME = GTMSessionFetcher; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C4D8B0A6AD4EF0183B7B90C84630289B /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_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; + CODE_SIGNING_REQUIRED = NO; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; + STRIP_INSTALLED_PRODUCT = NO; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + ED3BC417A9BAAB2DFA8DA863F8CA89C0 /* 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_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_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; + CODE_SIGNING_REQUIRED = NO; + 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; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = NO_SIGNING/; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2D8E8EC45A3A1A1D94AE762CB5028504 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ED3BC417A9BAAB2DFA8DA863F8CA89C0 /* Debug */, + C4D8B0A6AD4EF0183B7B90C84630289B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 43CA1CB82F851BA79063B0F64B80D0E6 /* Build configuration list for PBXNativeTarget "Pods-GTMAppAuth-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4D400F0B37DA03363ACEB7BA382D9145 /* Debug */, + 5527E5B88611B42D6FD584D34E4C2818 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4BBA2C35500467ECC48E79CDBA79A536 /* Build configuration list for PBXNativeTarget "GTMAppAuth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 578CC549C1AE484D4C28F1519419860E /* Debug */, + 99207FD0108845EF33E7047EDB4701AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5A789842166DDF517DEF9C739CBD8D70 /* Build configuration list for PBXNativeTarget "Pods-GTMAppAuth-SwiftTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 586A4671E49737F941A0DB8965424864 /* Debug */, + 6494002A2DF567C613030AF2F6EFE023 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 959AE5694BAB95811F5CE6EA80C0994A /* Build configuration list for PBXNativeTarget "AppAuth" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7A7B0C7ED3EE647D591B56237F662DD9 /* Debug */, + 3982DFD54B631B4D12838F0C9FFA37C1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F3714C433C642A57C6EBFD53EE6EDDA1 /* Build configuration list for PBXNativeTarget "GTMSessionFetcher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B1F3D0F50781A63DA8E7965399D7C4C3 /* Debug */, + A52053480DB690EA6D3B3F4989DAAF3F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D41D8CD98F00B204E9800998ECF8427E /* Project object */; +} diff --git a/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/AppAuth.xcscheme b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/AppAuth.xcscheme new file mode 100644 index 00000000..c2fadcb8 --- /dev/null +++ b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/AppAuth.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/GTMAppAuth.xcscheme b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/GTMAppAuth.xcscheme new file mode 100644 index 00000000..e18b282f --- /dev/null +++ b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/GTMAppAuth.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme new file mode 100644 index 00000000..eca41d42 --- /dev/null +++ b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/GTMSessionFetcher.xcscheme @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/Pods-GTMAppAuth-Swift.xcscheme b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/Pods-GTMAppAuth-Swift.xcscheme new file mode 100644 index 00000000..a75bf6ea --- /dev/null +++ b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/Pods-GTMAppAuth-Swift.xcscheme @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/Pods-GTMAppAuth-SwiftTests.xcscheme b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/Pods-GTMAppAuth-SwiftTests.xcscheme new file mode 100644 index 00000000..5819416c --- /dev/null +++ b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/Pods-GTMAppAuth-SwiftTests.xcscheme @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/xcschememanagement.plist b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..73e6af8a --- /dev/null +++ b/Example-macOS-Swift/Pods/Pods.xcodeproj/xcuserdata/grantk2.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,46 @@ + + + + + SchemeUserState + + AppAuth.xcscheme + + isShown + + orderHint + 0 + + GTMAppAuth.xcscheme + + isShown + + orderHint + 1 + + GTMSessionFetcher.xcscheme + + isShown + + orderHint + 2 + + Pods-GTMAppAuth-Swift.xcscheme + + isShown + + orderHint + 3 + + Pods-GTMAppAuth-SwiftTests.xcscheme + + isShown + + orderHint + 4 + + + SuppressBuildableAutocreation + + + diff --git a/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth-dummy.m b/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth-dummy.m new file mode 100644 index 00000000..0f45668e --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth-prefix.pch b/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth-prefix.pch new file mode 100644 index 00000000..082f8af2 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth-umbrella.h b/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth-umbrella.h new file mode 100644 index 00000000..25cc41d9 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth-umbrella.h @@ -0,0 +1,46 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "AppAuth.h" +#import "OIDAuthorizationRequest.h" +#import "OIDAuthorizationResponse.h" +#import "OIDAuthorizationService.h" +#import "OIDAuthorizationUICoordinator.h" +#import "OIDAuthState.h" +#import "OIDAuthStateChangeDelegate.h" +#import "OIDAuthStateErrorDelegate.h" +#import "OIDClientMetadataParameters.h" +#import "OIDDefines.h" +#import "OIDError.h" +#import "OIDErrorUtilities.h" +#import "OIDFieldMapping.h" +#import "OIDGrantTypes.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 "OIDLoopbackHTTPServer.h" +#import "OIDAuthorizationService+Mac.h" +#import "OIDAuthorizationUICoordinatorMac.h" +#import "OIDAuthState+Mac.h" +#import "OIDRedirectHTTPHandler.h" + +FOUNDATION_EXPORT double AppAuthVersionNumber; +FOUNDATION_EXPORT const unsigned char AppAuthVersionString[]; + diff --git a/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth.modulemap b/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth.modulemap new file mode 100644 index 00000000..9299d0a3 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth.xcconfig b/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth.xcconfig new file mode 100644 index 00000000..03ce937c --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/AppAuth/AppAuth.xcconfig @@ -0,0 +1,11 @@ +CODE_SIGN_IDENTITY = +CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/AppAuth +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +GCC_TREAT_WARNINGS_AS_ERRORS = YES +HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" +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 diff --git a/Example-macOS-Swift/Pods/Target Support Files/AppAuth/Info.plist b/Example-macOS-Swift/Pods/Target Support Files/AppAuth/Info.plist new file mode 100644 index 00000000..5d80a394 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/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 + 0.9.1 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-dummy.m b/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-dummy.m new file mode 100644 index 00000000..04b91f31 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch b/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-prefix.pch new file mode 100644 index 00000000..082f8af2 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-umbrella.h b/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-umbrella.h new file mode 100644 index 00000000..601cef40 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth-umbrella.h @@ -0,0 +1,25 @@ +#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 "GTMTVAuthorizationRequest.h" +#import "GTMTVAuthorizationResponse.h" +#import "GTMTVAuthorizationService.h" +#import "GTMTVServiceConfiguration.h" +#import "GTMOAuth2KeychainCompatibility.h" + +FOUNDATION_EXPORT double GTMAppAuthVersionNumber; +FOUNDATION_EXPORT const unsigned char GTMAppAuthVersionString[]; + diff --git a/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.modulemap b/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.modulemap new file mode 100644 index 00000000..b23c8d07 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.xcconfig b/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.xcconfig new file mode 100644 index 00000000..ff695dd9 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/GTMAppAuth.xcconfig @@ -0,0 +1,12 @@ +CODE_SIGN_IDENTITY = +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 +HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" +OTHER_LDFLAGS = -framework "Security" -framework "SystemConfiguration" +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 diff --git a/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/Info.plist b/Example-macOS-Swift/Pods/Target Support Files/GTMAppAuth/Info.plist new file mode 100644 index 00000000..5ff33cbe --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/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 + 0.6.1 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-dummy.m b/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-dummy.m new file mode 100644 index 00000000..13d68b3f --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch b/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-prefix.pch new file mode 100644 index 00000000..082f8af2 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-umbrella.h b/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher-umbrella.h new file mode 100644 index 00000000..8d5609c3 --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap b/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.modulemap new file mode 100644 index 00000000..5121a4dc --- /dev/null +++ b/Example-macOS-Swift/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/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.xcconfig b/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.xcconfig new file mode 100644 index 00000000..d4b3e27b --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/GTMSessionFetcher.xcconfig @@ -0,0 +1,11 @@ +CODE_SIGN_IDENTITY = +CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" +OTHER_LDFLAGS = -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 diff --git a/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/Info.plist b/Example-macOS-Swift/Pods/Target Support Files/GTMSessionFetcher/Info.plist new file mode 100644 index 00000000..7fafbaf5 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/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.1.12 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Info.plist b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Info.plist new file mode 100644 index 00000000..2243fe6e --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/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/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-acknowledgements.markdown b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-acknowledgements.markdown new file mode 100644 index 00000000..5aa6beb8 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-acknowledgements.markdown @@ -0,0 +1,621 @@ +# 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. + +Generated by CocoaPods - https://cocoapods.org diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-acknowledgements.plist b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-acknowledgements.plist new file mode 100644 index 00000000..da8a61a6 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-acknowledgements.plist @@ -0,0 +1,665 @@ + + + + + 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 + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-dummy.m b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-dummy.m new file mode 100644 index 00000000..a2411ecf --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_GTMAppAuth_Swift : NSObject +@end +@implementation PodsDummy_Pods_GTMAppAuth_Swift +@end diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-frameworks.sh b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-frameworks.sh new file mode 100755 index 00000000..942e9e72 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-frameworks.sh @@ -0,0 +1,116 @@ +#!/bin/sh +set -e + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +# 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 .*.??????") + +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[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --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}" + 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 && exit ${PIPESTATUS[0]}) + 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 the dSYM of a vendored framework +install_dsym() { + local source="$1" + if [ -r "$source" ]; then + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}" + fi +} + +# 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_identitiy + 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" + # Get architectures for current file + archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" + stripped="" + for arch in $archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" || exit 1 + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + 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/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-resources.sh b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-resources.sh new file mode 100755 index 00000000..a7df4405 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-resources.sh @@ -0,0 +1,106 @@ +#!/bin/sh +set -e + +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 +} + +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 "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "${PODS_ROOT}*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + 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}" +fi diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-umbrella.h b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-umbrella.h new file mode 100644 index 00000000..902fe364 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift-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_GTMAppAuth_SwiftVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_GTMAppAuth_SwiftVersionString[]; + diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.debug.xcconfig b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.debug.xcconfig new file mode 100644 index 00000000..7e06bd48 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.debug.xcconfig @@ -0,0 +1,10 @@ +CODE_SIGN_IDENTITY = +FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/AppAuth" "$PODS_CONFIGURATION_BUILD_DIR/GTMAppAuth" "$PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' +OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/AppAuth/AppAuth.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GTMAppAuth/GTMAppAuth.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "AppAuth" -framework "GTMAppAuth" -framework "GTMSessionFetcher" +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 diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.modulemap b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.modulemap new file mode 100644 index 00000000..5e0ccfce --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.modulemap @@ -0,0 +1,6 @@ +framework module Pods_GTMAppAuth_Swift { + umbrella header "Pods-GTMAppAuth-Swift-umbrella.h" + + export * + module * { export * } +} diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.release.xcconfig b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.release.xcconfig new file mode 100644 index 00000000..7e06bd48 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-Swift/Pods-GTMAppAuth-Swift.release.xcconfig @@ -0,0 +1,10 @@ +CODE_SIGN_IDENTITY = +FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/AppAuth" "$PODS_CONFIGURATION_BUILD_DIR/GTMAppAuth" "$PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' +OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/AppAuth/AppAuth.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GTMAppAuth/GTMAppAuth.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "AppAuth" -framework "GTMAppAuth" -framework "GTMSessionFetcher" +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 diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Info.plist b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Info.plist new file mode 100644 index 00000000..2243fe6e --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/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/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-acknowledgements.markdown b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-acknowledgements.markdown new file mode 100644 index 00000000..102af753 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-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/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-acknowledgements.plist b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-acknowledgements.plist new file mode 100644 index 00000000..7acbad1e --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-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/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-dummy.m b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-dummy.m new file mode 100644 index 00000000..fa734163 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_GTMAppAuth_SwiftTests : NSObject +@end +@implementation PodsDummy_Pods_GTMAppAuth_SwiftTests +@end diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-frameworks.sh b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-frameworks.sh new file mode 100755 index 00000000..88dd5379 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-frameworks.sh @@ -0,0 +1,105 @@ +#!/bin/sh +set -e + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +# 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 .*.??????") + +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[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --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}" + 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 && exit ${PIPESTATUS[0]}) + 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 the dSYM of a vendored framework +install_dsym() { + local source="$1" + if [ -r "$source" ]; then + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DWARF_DSYM_FOLDER_PATH}" + fi +} + +# 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_identitiy + 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" + # Get architectures for current file + archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" + stripped="" + for arch in $archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" || exit 1 + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi +} + +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-resources.sh b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-resources.sh new file mode 100755 index 00000000..a7df4405 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-resources.sh @@ -0,0 +1,106 @@ +#!/bin/sh +set -e + +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 +} + +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 "$PWD" -iname "*.xcassets" -type d) + while read line; do + if [[ $line != "${PODS_ROOT}*" ]]; then + XCASSET_FILES+=("$line") + fi + done <<<"$OTHER_XCASSETS" + + 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}" +fi diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-umbrella.h b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-umbrella.h new file mode 100644 index 00000000..4152b048 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests-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_GTMAppAuth_SwiftTestsVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_GTMAppAuth_SwiftTestsVersionString[]; + diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.debug.xcconfig b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.debug.xcconfig new file mode 100644 index 00000000..9fecbdad --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.debug.xcconfig @@ -0,0 +1,9 @@ +CODE_SIGN_IDENTITY = +FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/AppAuth" "$PODS_CONFIGURATION_BUILD_DIR/GTMAppAuth" "$PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/../Frameworks' +OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/AppAuth/AppAuth.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GTMAppAuth/GTMAppAuth.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +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 diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.modulemap b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.modulemap new file mode 100644 index 00000000..f9f76929 --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.modulemap @@ -0,0 +1,6 @@ +framework module Pods_GTMAppAuth_SwiftTests { + umbrella header "Pods-GTMAppAuth-SwiftTests-umbrella.h" + + export * + module * { export * } +} diff --git a/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.release.xcconfig b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.release.xcconfig new file mode 100644 index 00000000..9fecbdad --- /dev/null +++ b/Example-macOS-Swift/Pods/Target Support Files/Pods-GTMAppAuth-SwiftTests/Pods-GTMAppAuth-SwiftTests.release.xcconfig @@ -0,0 +1,9 @@ +CODE_SIGN_IDENTITY = +FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/AppAuth" "$PODS_CONFIGURATION_BUILD_DIR/GTMAppAuth" "$PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/../Frameworks' +OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/AppAuth/AppAuth.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GTMAppAuth/GTMAppAuth.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/GTMSessionFetcher/GTMSessionFetcher.framework/Headers" +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 diff --git a/Example-macOS-Swift/README.md b/Example-macOS-Swift/README.md new file mode 100644 index 00000000..93dc80f3 --- /dev/null +++ b/Example-macOS-Swift/README.md @@ -0,0 +1,49 @@ +# Example Project + +## CocoaPods Dependencies + +1. In the current folder, run the following command to install the required +library pods. + +``` +$ pod install +``` + +2. Open the workspace: + +``` +$ open GTMAppAuth-Swift.xcworkspace +``` + +### Creating a Google OAuth Client + +To configure the sample with a Google OAuth client, visit +https://console.developers.google.com/apis/credentials?project=_ and create a +new project. Then tap "Create credentials" and select "OAuth client ID". +Follow the instructions to configure the consent screen (just the Product Name +is needed). + +Then, complete the OAuth client creation by selecting "Other" as the Application +type. + +Copy the client ID to the clipboard. + +### Configure the Example + +The example doesn't work out of the box, you need to configure your settings. We have marked out in GTMAppAuthViewController where you need to update the settings in the project to work. For Clarity, we have also included the details in here: +1. Obtain your Client Id and URL scheme details from here: https://console.developers.google.com. Note you don't need the Client Secret to access the API in IOS / MacOS. +Update the variables `kClientID` with your client ID from above. Update the `kRedirectURI` with the *reverse DNS notation* form +of the client ID. For example, if the client ID is +`YOUR_CLIENT.apps.googleusercontent.com`, the reverse DNS notation would be +`com.googleusercontent.apps.YOUR_CLIENT`. A path component is added resulting in +`com.googleusercontent.apps.YOUR_CLIENT:/oauthredirect`. +2. Make sure you have enabled Sandbox Entitlements and have allowed Outgoing connection otherwise this will not work.(Under Project-> Capabilities. This has been done in this project) +3. Finally, open `Info.plist` and fully expand "URL types" (a.k.a. +"CFBundleURLTypes") and replace `com.googleusercontent.apps.YOUR_CLIENT` with +the reverse DNS notation form of your client ID (not including the +`:/oauthredirect` path component). +4. If you want to access a specific service then update the scopes listed in the `scopesToAccess` variable. We have added in Scope to get ID and Profile data for the purpose of this demo. For additional Google scopes, you can get these from the Google scopes URL: https://developers.google.com/identity/protocols/googlescopes +5. As a temporary work around you may need to update your Pods Project Deployment target to 10.11 as some of the methods used are not supported by older MacOS versions. + + +Once you have made those three changes, the sample should be ready to try with your new OAuth client. diff --git a/Example-macOS-Swift/bridging header.m b/Example-macOS-Swift/bridging header.m new file mode 100644 index 00000000..c0a668a0 --- /dev/null +++ b/Example-macOS-Swift/bridging header.m @@ -0,0 +1,10 @@ +// +// bridging header.m +// GTMAppAuth-Swift +// +// Created by Grant K2 on 20/02/2018. +// Copyright © 2018 Google. All rights reserved. +// + +#import +