commit ceabaff16c95b9fd37947063fabbd3b042a6eb79 parent c333c18e2bd88a33b2b4586bf9da93ee4a4871e3 Author: Naomi Welner <naomi@Naomis-MacBook-Air.local> Date: Tue, 12 Mar 2019 13:07:22 -0700 Adding existing files Diffstat:
86 files changed, 352 insertions(+), 23841 deletions(-)
diff --git a/.DS_Store b/.DS_Store Binary files differ. diff --git a/Podfile b/Podfile @@ -4,7 +4,7 @@ platform :ios, '9.0' target 'TeachersAssistant' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! - pod 'GoogleAPIClientForREST', '~> 1.3.8' + pod 'SheetsTranslator', '~> 0.3' target 'TeachersAssistantTests' do inherit! :search_paths diff --git a/Podfile.lock b/Podfile.lock @@ -1,27 +1,16 @@ PODS: - - GoogleAPIClientForREST (1.3.8): - - GoogleAPIClientForREST/Core (= 1.3.8) - - GTMSessionFetcher (>= 1.1.7) - - GoogleAPIClientForREST/Core (1.3.8): - - GTMSessionFetcher (>= 1.1.7) - - GTMSessionFetcher (1.2.1): - - GTMSessionFetcher/Full (= 1.2.1) - - GTMSessionFetcher/Core (1.2.1) - - GTMSessionFetcher/Full (1.2.1): - - GTMSessionFetcher/Core (= 1.2.1) + - SheetsTranslator (0.3) DEPENDENCIES: - - GoogleAPIClientForREST (~> 1.3.8) + - SheetsTranslator (~> 0.3) SPEC REPOS: https://github.com/cocoapods/specs.git: - - GoogleAPIClientForREST - - GTMSessionFetcher + - SheetsTranslator SPEC CHECKSUMS: - GoogleAPIClientForREST: 5447a194eae517986cafe6421a5330b80b820591 - GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca + SheetsTranslator: eb4971995a2b9c5324724e51dc183137d9654ee8 -PODFILE CHECKSUM: dbc5c2766ede4673e953e610de8390e5215f6c55 +PODFILE CHECKSUM: f6bd879d2a8c3815ad086d6049bd0ec6a3540bf6 COCOAPODS: 1.6.1 diff --git a/Pods/GTMSessionFetcher/LICENSE b/Pods/GTMSessionFetcher/LICENSE @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Pods/GTMSessionFetcher/README.md b/Pods/GTMSessionFetcher/README.md @@ -1,23 +0,0 @@ -# Google Toolbox for Mac - Session Fetcher # - -**Project site** <https://github.com/google/gtm-session-fetcher><br> -**Discussion group** <http://groups.google.com/group/google-toolbox-for-mac> - -[![Build Status](https://travis-ci.org/google/gtm-session-fetcher.svg?branch=master)](https://travis-ci.org/google/gtm-session-fetcher) - -`GTMSessionFetcher` makes it easy for Cocoa applications to perform http -operations. The fetcher is implemented as a wrapper on `NSURLSession`, so its -behavior is asynchronous and uses operating-system settings on iOS and Mac OS X. - -Features include: -- Simple to build; only one source/header file pair is required -- Simple to use: takes just two lines of code to fetch a request -- Supports upload and download sessions -- Flexible cookie storage -- Automatic retry on errors, with exponential backoff -- Support for generating multipart MIME upload streams -- Easy, convenient logging of http requests and responses -- Supports plug-in authentication such as with GTMAppAuth -- Easily testable; self-mocking -- Automatic rate limiting when created by the `GTMSessionFetcherService` factory class -- Fully independent of other projects diff --git a/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h b/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.h @@ -1,52 +0,0 @@ -/* 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 <Foundation/Foundation.h> - -#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 <NSStreamDelegate> - -+ (NSInputStream *)streamWithArray:(NSArray *)dataArray GTM_NONNULL((1)); - -@end - -#endif // GTM_GATHERINPUTSTREAM_DECLARED diff --git a/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m b/Pods/GTMSessionFetcher/Source/GTMGatherInputStream.m @@ -1,185 +0,0 @@ -/* 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<NSStreamDelegate> __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<NSStreamDelegate>)delegate { - return _delegate; -} - -- (void)setDelegate:(id<NSStreamDelegate>)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<NSStreamDelegate> delegate = _delegate; - if (delegate != self) { - [delegate stream:self handleEvent:streamEvent]; - } -} - -#pragma mark - Private - -- (long long)absoluteOffset { - long long absoluteOffset = 0; - NSUInteger index = 0; - for (NSData *data in _dataArray) { - if (index >= _arrayIndex) { - break; - } - absoluteOffset += data.length; - ++index; - } - absoluteOffset += _dataOffset; - return absoluteOffset; -} - -- (void)setAbsoluteOffset:(long long)absoluteOffset { - if (absoluteOffset < 0) { - absoluteOffset = 0; - } - _arrayIndex = 0; - _dataOffset = absoluteOffset; - for (NSData *data in _dataArray) { - long long dataLen = (long long) data.length; - if (dataLen > _dataOffset) { - break; - } - _arrayIndex++; - _dataOffset -= dataLen; - } - if (_arrayIndex == _dataArray.count) { - if (_dataOffset > 0) { - _dataOffset = 0; - } - } -} - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h b/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.h @@ -1,148 +0,0 @@ -/* 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 <Foundation/Foundation.h> - -#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<value> - #define GTM_NSDictionaryOf(key, value) NSDictionary<key, value> - #else - #define GTM_NSArrayOf(value) NSArray - #define GTM_NSDictionaryOf(key, value) NSDictionary - #endif // GTM_DECLARE_GENERICS -#endif // GTM_NSArrayOf - - -// GTMMIMEDocumentPart represents a part of a MIME document. -// -// +[GTMMIMEDocument MIMEPartsWithBoundary:data:] returns an array of these. -@interface GTMMIMEDocumentPart : NSObject - -@property(nonatomic, readonly) GTM_NSDictionaryOf(NSString *, NSString *) *headers; -@property(nonatomic, readonly) NSData *headerData; -@property(nonatomic, readonly) NSData *body; -@property(nonatomic, readonly) NSUInteger length; - -+ (instancetype)partWithHeaders:(NSDictionary *)headers body:(NSData *)body; - -@end - -@interface GTMMIMEDocument : NSObject - -// Get or set the unique boundary for the parts that have been added. -// -// When creating a MIME document from parts, this is typically calculated -// automatically after all parts have been added. -@property(nonatomic, copy) NSString *boundary; - -#pragma mark - Methods for Creating a MIME Document - -+ (instancetype)MIMEDocument; - -// Adds a new part to this mime document with the given headers and body. -// The headers keys and values should be NSStrings. -// Adding a part may cause the boundary string to change. -- (void)addPartWithHeaders:(GTM_NSDictionaryOf(NSString *, NSString *) *)headers - body:(NSData *)body GTM_NONNULL((1,2)); - -// An inputstream that can be used to efficiently read the contents of the MIME document. -// -// Any parameter may be null if the result is not wanted. -- (void)generateInputStream:(NSInputStream **)outStream - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary; - -// A dispatch_data_t with the contents of the MIME document. -// -// Note: dispatch_data_t is one-way toll-free bridged so the result -// may be cast directly to NSData *. -// -// Any parameter may be null if the result is not wanted. -- (void)generateDispatchData:(dispatch_data_t *)outDispatchData - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary; - -// Utility method for making a header section, including trailing newlines. -+ (NSData *)dataWithHeaders:(GTM_NSDictionaryOf(NSString *, NSString *) *)headers; - -#pragma mark - Methods for Parsing a MIME Document - -// Method for parsing out an array of MIME parts from a MIME document. -// -// Returns an array of GTMMIMEDocumentParts. Returns nil if no part can -// be found. -+ (GTM_NSArrayOf(GTMMIMEDocumentPart *) *)MIMEPartsWithBoundary:(NSString *)boundary - data:(NSData *)fullDocumentData; - -// Utility method for efficiently searching possibly discontiguous NSData -// for occurrences of target byte. This method does not "flatten" an NSData -// that is composed of discontiguous blocks. -// -// The byte offsets of non-overlapping occurrences of the target are returned as -// NSNumbers in the array. -+ (void)searchData:(NSData *)data - targetBytes:(const void *)targetBytes - targetLength:(NSUInteger)targetLength - foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets; - -// Utility method to parse header bytes into an NSDictionary. -+ (GTM_NSDictionaryOf(NSString *, NSString *) *)headersWithData:(NSData *)data; - -// ------ UNIT TESTING ONLY BELOW ------ - -// Internal methods, exposed for unit testing only. -- (void)seedRandomWith:(u_int32_t)seed; - -+ (NSUInteger)findBytesWithNeedle:(const unsigned char *)needle - needleLength:(NSUInteger)needleLength - haystack:(const unsigned char *)haystack - haystackLength:(NSUInteger)haystackLength - foundOffset:(NSUInteger *)foundOffset; - -+ (void)searchData:(NSData *)data - targetBytes:(const void *)targetBytes - targetLength:(NSUInteger)targetLength - foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets - foundBlockNumbers:(GTM_NSArrayOf(NSNumber *) **)outFoundBlockNumbers; - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m b/Pods/GTMSessionFetcher/Source/GTMMIMEDocument.m @@ -1,631 +0,0 @@ -/* 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 <NSStreamDelegate> - -+ (NSInputStream *)streamWithArray:(NSArray *)dataArray GTM_NONNULL((1)); - -@end -#endif // GTM_GATHERINPUTSTREAM_DECLARED - -// FindBytes -// -// Helper routine to search for the existence of a set of bytes (needle) within -// a presumed larger set of bytes (haystack). Can find the first part of the -// needle at the very end of the haystack. -// -// Returns the needle length on complete success, the number of bytes matched -// if a partial needle was found at the end of the haystack, and 0 on failure. -static NSUInteger FindBytes(const unsigned char *needle, NSUInteger needleLen, - const unsigned char *haystack, NSUInteger haystackLen, - NSUInteger *foundOffset); - -// SearchDataForBytes -// -// This implements the functionality of the +searchData: methods below. See the documentation -// for those methods. -static void SearchDataForBytes(NSData *data, const void *targetBytes, NSUInteger targetLength, - NSMutableArray *foundOffsets, NSMutableArray *foundBlockNumbers); - -@implementation GTMMIMEDocumentPart { - NSDictionary *_headers; - NSData *_headerData; // Header content including the ending "\r\n". - NSData *_bodyData; -} - -@synthesize headers = _headers, - headerData = _headerData, - body = _bodyData; - -@dynamic length; - -+ (instancetype)partWithHeaders:(NSDictionary *)headers body:(NSData *)body { - return [[self alloc] initWithHeaders:headers body:body]; -} - -- (instancetype)initWithHeaders:(NSDictionary *)headers body:(NSData *)body { - self = [super init]; - if (self) { - _bodyData = body; - _headers = headers; - } - return self; -} - -// Returns true if the part's header or data contain the given set of bytes. -// -// NOTE: We assume that the 'bytes' we are checking for do not contain "\r\n", -// so we don't need to check the concatenation of the header and body bytes. -- (BOOL)containsBytes:(const unsigned char *)bytes length:(NSUInteger)length { - // This uses custom search code rather than strcpy because the encoded data may contain - // null values. - NSData *headerData = self.headerData; - return (FindBytes(bytes, length, headerData.bytes, headerData.length, NULL) == length || - FindBytes(bytes, length, _bodyData.bytes, _bodyData.length, NULL) == length); -} - -- (NSData *)headerData { - if (!_headerData) { - _headerData = [GTMMIMEDocument dataWithHeaders:_headers]; - } - return _headerData; -} - -- (NSData *)body { - return _bodyData; -} - -- (NSUInteger)length { - return _headerData.length + _bodyData.length; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p (headers %lu keys, body %lu bytes)", - [self class], self, (unsigned long)_headers.count, - (unsigned long)_bodyData.length]; -} - -- (BOOL)isEqual:(GTMMIMEDocumentPart *)other { - if (self == other) return YES; - if (![other isKindOfClass:[GTMMIMEDocumentPart class]]) return NO; - return ((_bodyData == other->_bodyData || [_bodyData isEqual:other->_bodyData]) - && (_headers == other->_headers || [_headers isEqual:other->_headers])); -} - -- (NSUInteger)hash { - return _bodyData.hash | _headers.hash; -} - -@end - -@implementation GTMMIMEDocument { - NSMutableArray *_parts; // Ordered array of GTMMIMEDocumentParts. - unsigned long long _length; // Length in bytes of the document. - NSString *_boundary; - u_int32_t _randomSeed; // For testing. -} - -+ (instancetype)MIMEDocument { - return [[self alloc] init]; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _parts = [[NSMutableArray alloc] init]; - } - return self; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p (%lu parts)", - [self class], self, (unsigned long)_parts.count]; -} - -#pragma mark - Joining Parts - -// Adds a new part to this mime document with the given headers and body. -- (void)addPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { - GTMMIMEDocumentPart *part = [GTMMIMEDocumentPart partWithHeaders:headers body:body]; - [_parts addObject:part]; - _boundary = nil; -} - -// For unit testing only, seeds the random number generator so that we will -// have reproducible boundary strings. -- (void)seedRandomWith:(u_int32_t)seed { - _randomSeed = seed; - _boundary = nil; -} - -- (u_int32_t)random { - if (_randomSeed) { - // For testing only. - return _randomSeed++; - } else { - return arc4random(); - } -} - -// Computes the mime boundary to use. This should only be called -// after all the desired document parts have been added since it must compute -// a boundary that does not exist in the document data. -- (NSString *)boundary { - if (_boundary) { - return _boundary; - } - - // Use an easily-readable boundary string. - NSString *const kBaseBoundary = @"END_OF_PART"; - - _boundary = kBaseBoundary; - - // If the boundary isn't unique, append random numbers, up to 10 attempts; - // if that's still not unique, use a random number sequence instead, and call it good. - BOOL didCollide = NO; - - const int maxTries = 10; // Arbitrarily chosen maximum attempts. - for (int tries = 0; tries < maxTries; ++tries) { - - NSData *data = [_boundary dataUsingEncoding:NSUTF8StringEncoding]; - const void *dataBytes = data.bytes; - NSUInteger dataLen = data.length; - - for (GTMMIMEDocumentPart *part in _parts) { - didCollide = [part containsBytes:dataBytes length:dataLen]; - if (didCollide) break; - } - - if (!didCollide) break; // We're fine, no more attempts needed. - - // Try again with a random number appended. - _boundary = [NSString stringWithFormat:@"%@_%08x", kBaseBoundary, [self random]]; - } - - if (didCollide) { - // Fallback... two random numbers. - _boundary = [NSString stringWithFormat:@"%08x_tedborg_%08x", [self random], [self random]]; - } - return _boundary; -} - -- (void)setBoundary:(NSString *)str { - _boundary = [str copy]; -} - -// Internal method. -- (void)generateDataArray:(NSMutableArray *)dataArray - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary { - - // The input stream is of the form: - // --boundary - // [part_1_headers] - // [part_1_data] - // --boundary - // [part_2_headers] - // [part_2_data] - // --boundary-- - - // First we set up our boundary NSData objects. - NSString *boundary = self.boundary; - - NSString *mainBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n", boundary]; - NSString *endBoundary = [NSString stringWithFormat:@"\r\n--%@--\r\n", boundary]; - - NSData *mainBoundaryData = [mainBoundary dataUsingEncoding:NSUTF8StringEncoding]; - NSData *endBoundaryData = [endBoundary dataUsingEncoding:NSUTF8StringEncoding]; - - // Now we add them all in proper order to our dataArray. - unsigned long long length = 0; - - for (GTMMIMEDocumentPart *part in _parts) { - [dataArray addObject:mainBoundaryData]; - [dataArray addObject:part.headerData]; - [dataArray addObject:part.body]; - - length += part.length + mainBoundaryData.length; - } - - [dataArray addObject:endBoundaryData]; - length += endBoundaryData.length; - - if (outLength) *outLength = length; - if (outBoundary) *outBoundary = boundary; -} - -- (void)generateInputStream:(NSInputStream **)outStream - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary { - NSMutableArray *dataArray = outStream ? [NSMutableArray array] : nil; - [self generateDataArray:dataArray - length:outLength - boundary:outBoundary]; - - if (outStream) { - Class streamClass = NSClassFromString(@"GTMGatherInputStream"); - NSAssert(streamClass != nil, @"GTMGatherInputStream not available."); - - *outStream = [streamClass streamWithArray:dataArray]; - } -} - -- (void)generateDispatchData:(dispatch_data_t *)outDispatchData - length:(unsigned long long *)outLength - boundary:(NSString **)outBoundary { - NSMutableArray *dataArray = outDispatchData ? [NSMutableArray array] : nil; - [self generateDataArray:dataArray - length:outLength - boundary:outBoundary]; - - if (outDispatchData) { - // Create an empty data accumulator. - dispatch_data_t dataAccumulator; - - dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - - for (NSData *partData in dataArray) { - __block NSData *immutablePartData = [partData copy]; - dispatch_data_t newDataPart = - dispatch_data_create(immutablePartData.bytes, immutablePartData.length, bgQueue, ^{ - // We want the data retained until this block executes. - immutablePartData = nil; - }); - - if (dataAccumulator == nil) { - // First part. - dataAccumulator = newDataPart; - } else { - // Append the additional part. - dataAccumulator = dispatch_data_create_concat(dataAccumulator, newDataPart); - } - } - *outDispatchData = dataAccumulator; - } -} - -+ (NSData *)dataWithHeaders:(NSDictionary *)headers { - // Generate the header data by coalescing the dictionary as lines of "key: value\r\n". - NSMutableString* headerString = [NSMutableString string]; - - // Sort the header keys so we have a deterministic order for unit testing. - SEL sortSel = @selector(caseInsensitiveCompare:); - NSArray *sortedKeys = [headers.allKeys sortedArrayUsingSelector:sortSel]; - - for (NSString *key in sortedKeys) { - NSString *value = [headers objectForKey:key]; - -#if DEBUG - // Look for troublesome characters in the header keys & values. - NSCharacterSet *badKeyChars = [NSCharacterSet characterSetWithCharactersInString:@":\r\n"]; - NSCharacterSet *badValueChars = [NSCharacterSet characterSetWithCharactersInString:@"\r\n"]; - - NSRange badRange = [key rangeOfCharacterFromSet:badKeyChars]; - NSAssert(badRange.location == NSNotFound, @"invalid key: %@", key); - - badRange = [value rangeOfCharacterFromSet:badValueChars]; - NSAssert(badRange.location == NSNotFound, @"invalid value: %@", value); -#endif - - [headerString appendFormat:@"%@: %@\r\n", key, value]; - } - // Headers end with an extra blank line. - [headerString appendString:@"\r\n"]; - - NSData *result = [headerString dataUsingEncoding:NSUTF8StringEncoding]; - return result; -} - -#pragma mark - Separating Parts - -+ (NSArray *)MIMEPartsWithBoundary:(NSString *)boundary - data:(NSData *)fullDocumentData { - // In MIME documents, the boundary is preceded by CRLF and two dashes, and followed - // at the end by two dashes. - NSData *boundaryData = [boundary dataUsingEncoding:NSUTF8StringEncoding]; - NSUInteger boundaryLength = boundaryData.length; - - NSMutableArray *foundBoundaryOffsets; - [self searchData:fullDocumentData - targetBytes:boundaryData.bytes - targetLength:boundaryLength - foundOffsets:&foundBoundaryOffsets]; - - // According to rfc1341, ignore anything before the first boundary, or after the last, though two - // dashes are expected to follow the last boundary. - if (foundBoundaryOffsets.count < 2) { - return nil; - } - - // Wrap the full document data with a dispatch_data_t for more efficient slicing - // and dicing. - dispatch_data_t dataWrapper; - if ([fullDocumentData conformsToProtocol:@protocol(OS_dispatch_data)]) { - dataWrapper = (dispatch_data_t)fullDocumentData; - } else { - // A no-op self invocation on fullDocumentData will keep it retained until the block is invoked. - dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dataWrapper = dispatch_data_create(fullDocumentData.bytes, - fullDocumentData.length, - bgQueue, ^{ [fullDocumentData self]; }); - } - NSMutableArray *parts; - NSInteger previousBoundaryOffset = -1; - NSInteger partCounter = -1; - NSInteger numberOfPartsWithHeaders = 0; - for (NSNumber *currentBoundaryOffset in foundBoundaryOffsets) { - ++partCounter; - if (previousBoundaryOffset == -1) { - // This is the first boundary. - previousBoundaryOffset = currentBoundaryOffset.integerValue; - continue; - } else { - // Create a part data subrange between the previous boundary and this one. - // - // The last four bytes before a boundary are CRLF--. - // The first two bytes following a boundary are either CRLF or, for the last boundary, --. - NSInteger previousPartDataStartOffset = - previousBoundaryOffset + (NSInteger)boundaryLength + 2; - NSInteger previousPartDataEndOffset = currentBoundaryOffset.integerValue - 4; - NSInteger previousPartDataLength = previousPartDataEndOffset - previousPartDataStartOffset; - - if (previousPartDataLength < 2) { - // The preceding part was too short to be useful. -#if DEBUG - NSLog(@"MIME part %ld has %ld bytes", (long)partCounter - 1, - (long)previousPartDataLength); -#endif - } else { - if (!parts) parts = [NSMutableArray array]; - - dispatch_data_t partData = - dispatch_data_create_subrange(dataWrapper, - (size_t)previousPartDataStartOffset, (size_t)previousPartDataLength); - // Scan the part data for the separator between headers and body. After the CRLF, - // either the headers start immediately, or there's another CRLF and there are no headers. - // - // We need to map the part data to get the first two bytes. (Or we could cast it to - // NSData and get the bytes pointer of that.) If we're concerned that a single part - // data may be expensive to map, we could make a subrange here for just the first two bytes, - // and map that two-byte subrange. - const void *partDataBuffer; - size_t partDataBufferSize; - dispatch_data_t mappedPartData NS_VALID_UNTIL_END_OF_SCOPE = - dispatch_data_create_map(partData, &partDataBuffer, &partDataBufferSize); - dispatch_data_t bodyData; - NSDictionary *headers; - BOOL hasAnotherCRLF = (((char *)partDataBuffer)[0] == '\r' - && ((char *)partDataBuffer)[1] == '\n'); - mappedPartData = nil; - - if (hasAnotherCRLF) { - // There are no headers; skip the CRLF to get to the body, and leave headers nil. - bodyData = dispatch_data_create_subrange(partData, 2, (size_t)previousPartDataLength - 2); - } else { - // There are part headers. They are separated from body data by CRLFCRLF. - NSArray *crlfOffsets; - [self searchData:(NSData *)partData - targetBytes:"\r\n\r\n" - targetLength:4 - foundOffsets:&crlfOffsets]; - if (crlfOffsets.count == 0) { -#if DEBUG - // We could not distinguish body and headers. - NSLog(@"MIME part %ld lacks a header separator: %@", (long)partCounter - 1, - [[NSString alloc] initWithData:(NSData *)partData encoding:NSUTF8StringEncoding]); -#endif - } else { - NSInteger headerSeparatorOffset = ((NSNumber *)crlfOffsets.firstObject).integerValue; - dispatch_data_t headerData = - dispatch_data_create_subrange(partData, 0, (size_t)headerSeparatorOffset); - headers = [self headersWithData:(NSData *)headerData]; - - bodyData = dispatch_data_create_subrange(partData, (size_t)headerSeparatorOffset + 4, - (size_t)(previousPartDataLength - (headerSeparatorOffset + 4))); - - numberOfPartsWithHeaders++; - } // crlfOffsets.count == 0 - } // hasAnotherCRLF - GTMMIMEDocumentPart *part = [GTMMIMEDocumentPart partWithHeaders:headers - body:(NSData *)bodyData]; - [parts addObject:part]; - } // previousPartDataLength < 2 - previousBoundaryOffset = currentBoundaryOffset.integerValue; - } - } -#if DEBUG - // In debug builds, warn if a reasonably long document lacks any CRLF characters. - if (numberOfPartsWithHeaders == 0) { - NSUInteger length = fullDocumentData.length; - if (length > 20) { // Reasonably long. - NSMutableArray *foundCRLFs; - [self searchData:fullDocumentData - targetBytes:"\r\n" - targetLength:2 - foundOffsets:&foundCRLFs]; - if (foundCRLFs.count == 0) { - // Parts were logged above (due to lacking header separators.) - NSLog(@"Warning: MIME document lacks any headers (may have wrong line endings)"); - } - } - } -#endif // DEBUG - return parts; -} - -// Efficiently search the supplied data for the target bytes. -// -// This uses enumerateByteRangesUsingBlock: to scan for bytes. It can find -// the target even if it spans multiple separate byte ranges. -// -// Returns an array of found byte offset values, as NSNumbers. -+ (void)searchData:(NSData *)data - targetBytes:(const void *)targetBytes - targetLength:(NSUInteger)targetLength - foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets { - NSMutableArray *foundOffsets = [NSMutableArray array]; - SearchDataForBytes(data, targetBytes, targetLength, foundOffsets, NULL); - *outFoundOffsets = foundOffsets; -} - - -// This version of searchData: also returns the block numbers (0-based) where the -// target was found, used for testing that the supplied dispatch_data buffer -// has not been flattened. -+ (void)searchData:(NSData *)data - targetBytes:(const void *)targetBytes - targetLength:(NSUInteger)targetLength - foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets - foundBlockNumbers:(GTM_NSArrayOf(NSNumber *) **)outFoundBlockNumbers { - NSMutableArray *foundOffsets = [NSMutableArray array]; - NSMutableArray *foundBlockNumbers = [NSMutableArray array]; - - SearchDataForBytes(data, targetBytes, targetLength, foundOffsets, foundBlockNumbers); - *outFoundOffsets = foundOffsets; - *outFoundBlockNumbers = foundBlockNumbers; -} - -static void SearchDataForBytes(NSData *data, const void *targetBytes, NSUInteger targetLength, - NSMutableArray *foundOffsets, NSMutableArray *foundBlockNumbers) { - __block NSUInteger priorPartialMatchAmount = 0; - __block NSInteger priorPartialMatchStartingBlockNumber = -1; - __block NSInteger blockNumber = -1; - - [data enumerateByteRangesUsingBlock:^(const void *bytes, - NSRange byteRange, - BOOL *stop) { - // Search for the first character in the current range. - const void *ptr = bytes; - NSInteger remainingInCurrentRange = (NSInteger)byteRange.length; - ++blockNumber; - - if (priorPartialMatchAmount > 0) { - NSUInteger amountRemainingToBeMatched = targetLength - priorPartialMatchAmount; - NSUInteger remainingFoundOffset; - NSUInteger amountMatched = FindBytes(targetBytes + priorPartialMatchAmount, - amountRemainingToBeMatched, - ptr, (NSUInteger)remainingInCurrentRange, &remainingFoundOffset); - if (amountMatched == 0 || remainingFoundOffset > 0) { - // No match of the rest of the prior partial match in this range. - } else if (amountMatched < amountRemainingToBeMatched) { - // Another partial match; we're done with this range. - priorPartialMatchAmount = priorPartialMatchAmount + amountMatched; - return; - } else { - // The offset is in an earlier range. - NSUInteger offset = byteRange.location - priorPartialMatchAmount; - [foundOffsets addObject:@(offset)]; - [foundBlockNumbers addObject:@(priorPartialMatchStartingBlockNumber)]; - priorPartialMatchStartingBlockNumber = -1; - } - priorPartialMatchAmount = 0; - } - - while (remainingInCurrentRange > 0) { - NSUInteger offsetFromPtr; - NSUInteger amountMatched = FindBytes(targetBytes, targetLength, ptr, - (NSUInteger)remainingInCurrentRange, &offsetFromPtr); - if (amountMatched == 0) { - // No match in this range. - return; - } - if (amountMatched < targetLength) { - // Found a partial target. If there's another range, we'll check for the rest. - priorPartialMatchAmount = amountMatched; - priorPartialMatchStartingBlockNumber = blockNumber; - return; - } - // Found the full target. - NSUInteger globalOffset = byteRange.location + (NSUInteger)(ptr - bytes) + offsetFromPtr; - - [foundOffsets addObject:@(globalOffset)]; - [foundBlockNumbers addObject:@(blockNumber)]; - - ptr += targetLength + offsetFromPtr; - remainingInCurrentRange -= (targetLength + offsetFromPtr); - } - }]; -} - -// Internal method only for testing; this calls through the static method. -+ (NSUInteger)findBytesWithNeedle:(const unsigned char *)needle - needleLength:(NSUInteger)needleLength - haystack:(const unsigned char *)haystack - haystackLength:(NSUInteger)haystackLength - foundOffset:(NSUInteger *)foundOffset { - return FindBytes(needle, needleLength, haystack, haystackLength, foundOffset); -} - -// Utility method to parse header bytes into an NSDictionary. -+ (NSDictionary *)headersWithData:(NSData *)data { - NSString *headersString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (!headersString) return nil; - - NSMutableDictionary *headers = [NSMutableDictionary dictionary]; - NSScanner *scanner = [NSScanner scannerWithString:headersString]; - // The scanner is skipping leading whitespace and newline characters by default. - NSCharacterSet *newlineCharacters = [NSCharacterSet newlineCharacterSet]; - NSString *key; - NSString *value; - while ([scanner scanUpToString:@":" intoString:&key] - && [scanner scanString:@":" intoString:NULL] - && [scanner scanUpToCharactersFromSet:newlineCharacters intoString:&value]) { - [headers setObject:value forKey:key]; - // Discard the trailing newline. - [scanner scanCharactersFromSet:newlineCharacters intoString:NULL]; - } - return headers; -} - -@end - -// Return how much of the needle was found in the haystack. -// -// If the result is less than needleLen, then the beginning of the needle -// was found at the end of the haystack. -static NSUInteger FindBytes(const unsigned char* needle, NSUInteger needleLen, - const unsigned char* haystack, NSUInteger haystackLen, - NSUInteger *foundOffset) { - const unsigned char *ptr = haystack; - NSInteger remain = (NSInteger)haystackLen; - // Assume memchr is an efficient way to find a match for the first - // byte of the needle, and memcmp is an efficient way to compare a - // range of bytes. - while (remain > 0 && (ptr = memchr(ptr, needle[0], (size_t)remain)) != 0) { - // The first character is present. - NSUInteger offset = (NSUInteger)(ptr - haystack); - remain = (NSInteger)(haystackLen - offset); - - NSUInteger amountToCompare = MIN((NSUInteger)remain, needleLen); - if (memcmp(ptr, needle, amountToCompare) == 0) { - if (foundOffset) *foundOffset = offset; - return amountToCompare; - } - ptr++; - remain--; - } - if (foundOffset) *foundOffset = 0; - return 0; -} diff --git a/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h b/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.h @@ -1,49 +0,0 @@ -/* 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 <Foundation/Foundation.h> - -#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 <NSStreamDelegate> - -+ (instancetype)inputStreamWithStream:(NSInputStream *)input GTM_NONNULL((1)); - -- (instancetype)initWithStream:(NSInputStream *)input GTM_NONNULL((1)); - -// The read monitor selector is called when bytes have been read. It should have this signature: -// -// - (void)inputStream:(GTMReadMonitorInputStream *)stream -// readIntoBuffer:(uint8_t *)buffer -// length:(int64_t)length; - -@property(atomic, weak) id readDelegate; -@property(atomic, assign) SEL readSelector; - -// Modes for invoking callbacks, when necessary. -@property(atomic, strong) NSArray *runLoopModes; - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m b/Pods/GTMSessionFetcher/Source/GTMReadMonitorInputStream.m @@ -1,190 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -#import "GTMReadMonitorInputStream.h" - -@implementation GTMReadMonitorInputStream { - NSInputStream *_inputStream; // Encapsulated stream that does the work. - - NSThread *_thread; // Thread in which this object was created. - NSArray *_runLoopModes; // Modes for calling callbacks, when necessary. -} - - -@synthesize readDelegate = _readDelegate; -@synthesize readSelector = _readSelector; -@synthesize runLoopModes = _runLoopModes; - -// We'll forward all unhandled messages to the NSInputStream class or to the encapsulated input -// stream. This is needed for all messages sent to NSInputStream which aren't handled by our -// superclass; that includes various private run loop calls. -+ (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { - return [NSInputStream methodSignatureForSelector:selector]; -} - -+ (void)forwardInvocation:(NSInvocation*)invocation { - [invocation invokeWithTarget:[NSInputStream class]]; -} - -- (BOOL)respondsToSelector:(SEL)selector { - return [_inputStream respondsToSelector:selector]; -} - -- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { - return [_inputStream methodSignatureForSelector:selector]; -} - -- (void)forwardInvocation:(NSInvocation*)invocation { - [invocation invokeWithTarget:_inputStream]; -} - -#pragma mark - - -+ (instancetype)inputStreamWithStream:(NSInputStream *)input { - return [[self alloc] initWithStream:input]; -} - -- (instancetype)initWithStream:(NSInputStream *)input { - self = [super init]; - if (self) { - _inputStream = input; - _thread = [NSThread currentThread]; - } - return self; -} - -- (instancetype)init { - [self doesNotRecognizeSelector:_cmd]; - return nil; -} - -#pragma mark - - -- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { - // Read from the encapsulated stream. - NSInteger numRead = [_inputStream read:buffer maxLength:len]; - if (numRead > 0) { - if (_readDelegate && _readSelector) { - // Call the read selector with the buffer and number of bytes actually read into it. - BOOL isOnOriginalThread = [_thread isEqual:[NSThread currentThread]]; - if (isOnOriginalThread) { - // Invoke immediately. - NSData *data = [NSData dataWithBytesNoCopy:buffer - length:(NSUInteger)numRead - freeWhenDone:NO]; - [self invokeReadSelectorWithBuffer:data]; - } else { - // Copy the buffer into an NSData to be retained by the performSelector, - // and invoke on the proper thread. - SEL sel = @selector(invokeReadSelectorWithBuffer:); - NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numRead]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - if (_runLoopModes) { - [self performSelector:sel - onThread:_thread - withObject:data - waitUntilDone:NO - modes:_runLoopModes]; - } else { - [self performSelector:sel - onThread:_thread - withObject:data - waitUntilDone:NO]; - } -#pragma clang diagnostic pop - } - } - } - return numRead; -} - -- (void)invokeReadSelectorWithBuffer:(NSData *)data { - const void *buffer = data.bytes; - int64_t length = (int64_t)data.length; - - id argSelf = self; - id readDelegate = _readDelegate; - if (readDelegate) { - NSMethodSignature *signature = [readDelegate methodSignatureForSelector:_readSelector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setSelector:_readSelector]; - [invocation setTarget:readDelegate]; - [invocation setArgument:&argSelf atIndex:2]; - [invocation setArgument:&buffer atIndex:3]; - [invocation setArgument:&length atIndex:4]; - [invocation invoke]; - } -} - -- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len { - return [_inputStream getBuffer:buffer length:len]; -} - -- (BOOL)hasBytesAvailable { - return [_inputStream hasBytesAvailable]; -} - -#pragma mark Standard messages - -// Pass expected messages to our encapsulated stream. -// -// We want our encapsulated NSInputStream to handle the standard messages; -// we don't want the superclass to handle them. -- (void)open { - [_inputStream open]; -} - -- (void)close { - [_inputStream close]; -} - -- (id)delegate { - return [_inputStream delegate]; -} - -- (void)setDelegate:(id)delegate { - [_inputStream setDelegate:delegate]; -} - -- (id)propertyForKey:(NSString *)key { - return [_inputStream propertyForKey:key]; -} - -- (BOOL)setProperty:(id)property forKey:(NSString *)key { - return [_inputStream setProperty:property forKey:key]; -} - -- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { - [_inputStream scheduleInRunLoop:aRunLoop forMode:mode]; -} - -- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode { - [_inputStream removeFromRunLoop:aRunLoop forMode:mode]; -} - -- (NSStreamStatus)streamStatus { - return [_inputStream streamStatus]; -} - -- (NSError *)streamError { - return [_inputStream streamError]; -} - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.h @@ -1,1305 +0,0 @@ -/* 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 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html> -// -// -// 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 <Foundation/Foundation.h> - -#if TARGET_OS_IPHONE -#import <UIKit/UIKit.h> -#endif -#if TARGET_OS_WATCH -#import <WatchKit/WatchKit.h> -#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<value> - #define GTM_NSDictionaryOf(key, value) NSDictionary<key, value> - #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; -extern NSString *const kGTMSessionFetcherStatusDataContentTypeKey; - -// When a fetch fails with an error, these keys are included in the error userInfo -// dictionary if retries were attempted. -extern NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey; -extern NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey; - -// Background session support requires access to NSUserDefaults. -// If [NSUserDefaults standardUserDefaults] doesn't yield the correct NSUserDefaults for your usage, -// ie for an App Extension, then implement this class/method to return the correct NSUserDefaults. -// https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW6 -@interface GTMSessionFetcherUserDefaultsFactory : NSObject - -+ (NSUserDefaults *)fetcherUserDefaults; - -@end - -#ifdef __cplusplus -} -#endif - -typedef NS_ENUM(NSInteger, GTMSessionFetcherError) { - GTMSessionFetcherErrorDownloadFailed = -1, - GTMSessionFetcherErrorUploadChunkUnavailable = -2, - GTMSessionFetcherErrorBackgroundExpiration = -3, - GTMSessionFetcherErrorBackgroundFetchFailed = -4, - GTMSessionFetcherErrorInsecureRequest = -5, - GTMSessionFetcherErrorTaskCreationFailed = -6, -}; - -typedef NS_ENUM(NSInteger, GTMSessionFetcherStatus) { - // Standard http status codes. - GTMSessionFetcherStatusNotModified = 304, - GTMSessionFetcherStatusBadRequest = 400, - GTMSessionFetcherStatusUnauthorized = 401, - GTMSessionFetcherStatusForbidden = 403, - GTMSessionFetcherStatusPreconditionFailed = 412 -}; - -#ifdef __cplusplus -extern "C" { -#endif - -@class GTMSessionCookieStorage; -@class GTMSessionFetcher; - -// The configuration block is for modifying the NSURLSessionConfiguration only. -// DO NOT change any fetcher properties in the configuration block. -typedef void (^GTMSessionFetcherConfigurationBlock)(GTMSessionFetcher *fetcher, - NSURLSessionConfiguration *configuration); -typedef void (^GTMSessionFetcherSystemCompletionHandler)(void); -typedef void (^GTMSessionFetcherCompletionHandler)(NSData * GTM_NULLABLE_TYPE data, - NSError * GTM_NULLABLE_TYPE error); -typedef void (^GTMSessionFetcherBodyStreamProviderResponse)(NSInputStream *bodyStream); -typedef void (^GTMSessionFetcherBodyStreamProvider)(GTMSessionFetcherBodyStreamProviderResponse response); -typedef void (^GTMSessionFetcherDidReceiveResponseDispositionBlock)(NSURLSessionResponseDisposition disposition); -typedef void (^GTMSessionFetcherDidReceiveResponseBlock)(NSURLResponse *response, - GTMSessionFetcherDidReceiveResponseDispositionBlock dispositionBlock); -typedef void (^GTMSessionFetcherChallengeDispositionBlock)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * GTM_NULLABLE_TYPE credential); -typedef void (^GTMSessionFetcherChallengeBlock)(GTMSessionFetcher *fetcher, - NSURLAuthenticationChallenge *challenge, - GTMSessionFetcherChallengeDispositionBlock dispositionBlock); -typedef void (^GTMSessionFetcherWillRedirectResponse)(NSURLRequest * GTM_NULLABLE_TYPE redirectedRequest); -typedef void (^GTMSessionFetcherWillRedirectBlock)(NSHTTPURLResponse *redirectResponse, - NSURLRequest *redirectRequest, - GTMSessionFetcherWillRedirectResponse response); -typedef void (^GTMSessionFetcherAccumulateDataBlock)(NSData * GTM_NULLABLE_TYPE buffer); -typedef void (^GTMSessionFetcherSimulateByteTransferBlock)(NSData * GTM_NULLABLE_TYPE buffer, - int64_t bytesWritten, - int64_t totalBytesWritten, - int64_t totalBytesExpectedToWrite); -typedef void (^GTMSessionFetcherReceivedProgressBlock)(int64_t bytesWritten, - int64_t totalBytesWritten); -typedef void (^GTMSessionFetcherDownloadProgressBlock)(int64_t bytesWritten, - int64_t totalBytesWritten, - int64_t totalBytesExpectedToWrite); -typedef void (^GTMSessionFetcherSendProgressBlock)(int64_t bytesSent, - int64_t totalBytesSent, - int64_t totalBytesExpectedToSend); -typedef void (^GTMSessionFetcherWillCacheURLResponseResponse)(NSCachedURLResponse * GTM_NULLABLE_TYPE cachedResponse); -typedef void (^GTMSessionFetcherWillCacheURLResponseBlock)(NSCachedURLResponse *proposedResponse, - GTMSessionFetcherWillCacheURLResponseResponse responseBlock); -typedef void (^GTMSessionFetcherRetryResponse)(BOOL shouldRetry); -typedef void (^GTMSessionFetcherRetryBlock)(BOOL suggestedWillRetry, - NSError * GTM_NULLABLE_TYPE error, - GTMSessionFetcherRetryResponse response); - -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 <NSObject> -// 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<NSURLSessionDelegate>)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 <NSObject> -@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<GTMSessionFetcherServiceProtocol> fetcherService; -#else -@property (weak, GTM_NULLABLE) id<GTMHTTPFetcherServiceProtocol> 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 <NSObject> -- (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 <NSURLSessionDelegate> - -// 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; - -// 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<GTMFetcherAuthorizationProtocol> authorizer; - -// The service object that created and monitors this fetcher, if any. -@property(atomic, strong) id<GTMSessionFetcherServiceProtocol> 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<GTMUIApplicationProtocol>)substituteUIApplication; -+ (nullable id<GTMUIApplicationProtocol>)substituteUIApplication; -#endif // GTM_BACKGROUND_TASK_FETCHING - -// Exposed for testing. -+ (GTMSessionCookieStorage *)staticCookieStorage; -+ (BOOL)appAllowsInsecureRequests; - -#if STRIP_GTM_FETCH_LOGGING -// If logging is stripped, provide a stub for the main method -// for controlling logging. -+ (void)setLoggingEnabled:(BOOL)flag; -+ (BOOL)isLoggingEnabled; - -#else - -// These methods let an application log specific body text, such as the text description of a binary -// request or response. The application should set the fetcher to defer response body logging until -// the response has been received and the log response body has been set by the app. For example: -// -// fetcher.logRequestBody = [binaryObject stringDescription]; -// fetcher.deferResponseBodyLogging = YES; -// [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { -// if (error == nil) { -// fetcher.logResponseBody = [[[MyThing alloc] initWithData:data] stringDescription]; -// } -// fetcher.deferResponseBodyLogging = NO; -// }]; - -@property(atomic, copy, GTM_NULLABLE) NSString *logRequestBody; -@property(atomic, assign) BOOL deferResponseBodyLogging; -@property(atomic, copy, GTM_NULLABLE) NSString *logResponseBody; - -// Internal logging support. -@property(atomic, readonly) NSData *loggedStreamData; -@property(atomic, assign) BOOL hasLoggedError; -@property(atomic, strong, GTM_NULLABLE) NSURL *redirectedFromURL; -- (void)appendLoggedStreamData:(NSData *)dataToAdd; -- (void)clearLoggedStreamData; - -#endif // STRIP_GTM_FETCH_LOGGING - -@end - -@interface GTMSessionFetcher (BackwardsCompatibilityOnly) -// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves. -// This method is just for compatibility with the old GTMHTTPFetcher class. -- (void)setCookieStorageMethod:(NSInteger)method; -@end - -// Until we can just instantiate NSHTTPCookieStorage for local use, we'll -// implement all the public methods ourselves. This stores cookies only in -// memory. Additional methods are provided for testing. -// -// iOS 9/OS X 10.11 added +[NSHTTPCookieStorage sharedCookieStorageForGroupContainerIdentifier:] -// which may also be used to create cookie storage. -@interface GTMSessionCookieStorage : NSHTTPCookieStorage - -// Add the array off cookies to the storage, replacing duplicates. -// Also removes expired cookies from the storage. -- (void)setCookies:(GTM_NULLABLE GTM_NSArrayOf(NSHTTPCookie *) *)cookies; - -- (void)removeAllCookies; - -@end - -// Macros to monitor synchronization blocks in debug builds. -// These report problems using GTMSessionCheckDebug. -// -// GTMSessionMonitorSynchronized Start monitoring a top-level-only -// @sync scope. -// GTMSessionMonitorRecursiveSynchronized Start monitoring a top-level or -// recursive @sync scope. -// GTMSessionCheckSynchronized Verify that the current execution -// is inside a @sync scope. -// GTMSessionCheckNotSynchronized Verify that the current execution -// is not inside a @sync scope. -// -// Example usage: -// -// - (void)myExternalMethod { -// @synchronized(self) { -// GTMSessionMonitorSynchronized(self) -// -// - (void)myInternalMethod { -// GTMSessionCheckSynchronized(self); -// -// - (void)callMyCallbacks { -// GTMSessionCheckNotSynchronized(self); -// -// GTMSessionCheckNotSynchronized is available for verifying the code isn't -// in a deadlockable @sync state when posting notifications and invoking -// callbacks. Don't use GTMSessionCheckNotSynchronized immediately before a -// @sync scope; the normal recursiveness check of GTMSessionMonitorSynchronized -// can catch those. - -#ifdef __OBJC__ -// If asserts are entirely no-ops, the synchronization monitor is just a bunch -// of counting code that doesn't report exceptional circumstances in any way. -// Only build the synchronization monitor code if NS_BLOCK_ASSERTIONS is not -// defined or asserts are being logged instead. -#if DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) - #define __GTMSessionMonitorSynchronizedVariableInner(varname, counter) \ - varname ## counter - #define __GTMSessionMonitorSynchronizedVariable(varname, counter) \ - __GTMSessionMonitorSynchronizedVariableInner(varname, counter) - - #define GTMSessionMonitorSynchronized(obj) \ - NS_VALID_UNTIL_END_OF_SCOPE id \ - __GTMSessionMonitorSynchronizedVariable(__monitor, __COUNTER__) = \ - [[GTMSessionSyncMonitorInternal alloc] initWithSynchronizationObject:obj \ - allowRecursive:NO \ - functionName:__func__] - - #define GTMSessionMonitorRecursiveSynchronized(obj) \ - NS_VALID_UNTIL_END_OF_SCOPE id \ - __GTMSessionMonitorSynchronizedVariable(__monitor, __COUNTER__) = \ - [[GTMSessionSyncMonitorInternal alloc] initWithSynchronizationObject:obj \ - allowRecursive:YES \ - functionName:__func__] - - #define GTMSessionCheckSynchronized(obj) { \ - GTMSESSION_ASSERT_DEBUG( \ - [GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ - @"GTMSessionCheckSynchronized(" #obj ") failed: not sync'd" \ - @" on " #obj " in %s. Call stack:\n%@", \ - __func__, [NSThread callStackSymbols]); \ - } - - #define GTMSessionCheckNotSynchronized(obj) { \ - GTMSESSION_ASSERT_DEBUG( \ - ![GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ - @"GTMSessionCheckNotSynchronized(" #obj ") failed: was sync'd" \ - @" on " #obj " in %s by %@. Call stack:\n%@", __func__, \ - [GTMSessionSyncMonitorInternal functionsHoldingSynchronizationOnObject:obj], \ - [NSThread callStackSymbols]); \ - } - -// GTMSessionSyncMonitorInternal is a private class that keeps track of the -// beginning and end of synchronized scopes. -// -// This class should not be used directly, but only via the -// GTMSessionMonitorSynchronized macro. -@interface GTMSessionSyncMonitorInternal : NSObject -- (instancetype)initWithSynchronizationObject:(id)object - allowRecursive:(BOOL)allowRecursive - functionName:(const char *)functionName; -// Return the names of the functions that hold sync on the object, or nil if none. -+ (NSArray *)functionsHoldingSynchronizationOnObject:(id)object; -@end - -#else - #define GTMSessionMonitorSynchronized(obj) do { } while (0) - #define GTMSessionMonitorRecursiveSynchronized(obj) do { } while (0) - #define GTMSessionCheckSynchronized(obj) do { } while (0) - #define GTMSessionCheckNotSynchronized(obj) do { } while (0) -#endif // !DEBUG -#endif // __OBJC__ - - -GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m @@ -1,4579 +0,0 @@ -/* 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 <sys/utsname.h> - -#ifndef STRIP_GTM_FETCH_LOGGING - #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. -#endif - -GTM_ASSUME_NONNULL_BEGIN - -NSString *const kGTMSessionFetcherStartedNotification = @"kGTMSessionFetcherStartedNotification"; -NSString *const kGTMSessionFetcherStoppedNotification = @"kGTMSessionFetcherStoppedNotification"; -NSString *const kGTMSessionFetcherRetryDelayStartedNotification = @"kGTMSessionFetcherRetryDelayStartedNotification"; -NSString *const kGTMSessionFetcherRetryDelayStoppedNotification = @"kGTMSessionFetcherRetryDelayStoppedNotification"; - -NSString *const kGTMSessionFetcherCompletionInvokedNotification = @"kGTMSessionFetcherCompletionInvokedNotification"; -NSString *const kGTMSessionFetcherCompletionDataKey = @"data"; -NSString *const kGTMSessionFetcherCompletionErrorKey = @"error"; - -NSString *const kGTMSessionFetcherErrorDomain = @"com.google.GTMSessionFetcher"; -NSString *const kGTMSessionFetcherStatusDomain = @"com.google.HTTPStatus"; -NSString *const kGTMSessionFetcherStatusDataKey = @"data"; // data returned with a kGTMSessionFetcherStatusDomain error -NSString *const kGTMSessionFetcherStatusDataContentTypeKey = @"data_content_type"; - -NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey = @"kGTMSessionFetcherNumberOfRetriesDoneKey"; -NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey = @"kGTMSessionFetcherElapsedIntervalWithRetriesKey"; - -static NSString *const kGTMSessionIdentifierPrefix = @"com.google.GTMSessionFetcher"; -static NSString *const kGTMSessionIdentifierDestinationFileURLMetadataKey = @"_destURL"; -static NSString *const kGTMSessionIdentifierBodyFileURLMetadataKey = @"_bodyURL"; - -// The default max retry interview is 10 minutes for uploads (POST/PUT/PATCH), -// 1 minute for downloads. -static const NSTimeInterval kUnsetMaxRetryInterval = -1.0; -static const NSTimeInterval kDefaultMaxDownloadRetryInterval = 60.0; -static const NSTimeInterval kDefaultMaxUploadRetryInterval = 60.0 * 10.; - -// The maximum data length that can be loaded to the error userInfo -static const int64_t kMaximumDownloadErrorDataLength = 20000; - -#ifdef GTMSESSION_PERSISTED_DESTINATION_KEY -// Projects using unique class names should also define a unique persisted destination key. -static NSString * const kGTMSessionFetcherPersistedDestinationKey = - GTMSESSION_PERSISTED_DESTINATION_KEY; -#else -static NSString * const kGTMSessionFetcherPersistedDestinationKey = - @"com.google.GTMSessionFetcher.downloads"; -#endif - -GTM_ASSUME_NONNULL_END - -// -// GTMSessionFetcher -// - -#if 0 -#define GTM_LOG_BACKGROUND_SESSION(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__) -#else -#define GTM_LOG_BACKGROUND_SESSION(...) -#endif - -#ifndef GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY - #if (TARGET_OS_TV \ - || TARGET_OS_WATCH \ - || (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \ - || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)) - #define GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY 1 - #endif -#endif - -@interface GTMSessionFetcher () - -@property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadedData; -@property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadResumeData; - -#if GTM_BACKGROUND_TASK_FETCHING -// Should always be accessed within an @synchronized(self). -@property(assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier; -#endif - -@property(atomic, readwrite, getter=isUsingBackgroundSession) BOOL usingBackgroundSession; - -@end - -#if !GTMSESSION_BUILD_COMBINED_SOURCES -@interface GTMSessionFetcher (GTMSessionFetcherLoggingInternal) -- (void)logFetchWithError:(NSError *)error; -- (void)logNowWithError:(GTM_NULLABLE NSError *)error; -- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream; -- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: - (GTMSessionFetcherBodyStreamProvider)streamProvider; -@end -#endif // !GTMSESSION_BUILD_COMBINED_SOURCES - -GTM_ASSUME_NONNULL_BEGIN - -static NSTimeInterval InitialMinRetryInterval(void) { - return 1.0 + ((double)(arc4random_uniform(0x0FFFF)) / (double) 0x0FFFF); -} - -static BOOL IsLocalhost(NSString * GTM_NULLABLE_TYPE host) { - // We check if there's host, and then make the comparisons. - if (host == nil) return NO; - return ([host caseInsensitiveCompare:@"localhost"] == NSOrderedSame - || [host isEqual:@"::1"] - || [host isEqual:@"127.0.0.1"]); -} - -static NSDictionary *GTM_NULLABLE_TYPE GTMErrorUserInfoForData( - NSData *GTM_NULLABLE_TYPE data, NSDictionary *GTM_NULLABLE_TYPE responseHeaders) { - NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - - if (data.length > 0) { - userInfo[kGTMSessionFetcherStatusDataKey] = data; - - NSString *contentType = responseHeaders[@"Content-Type"]; - if (contentType) { - userInfo[kGTMSessionFetcherStatusDataContentTypeKey] = contentType; - } - } - - return userInfo.count > 0 ? userInfo : nil; -} - -static GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE gGlobalTestBlock; - -@implementation GTMSessionFetcher { - NSMutableURLRequest *_request; // after beginFetch, changed only in delegate callbacks - BOOL _useUploadTask; // immutable after beginFetch - NSURL *_bodyFileURL; // immutable after beginFetch - GTMSessionFetcherBodyStreamProvider _bodyStreamProvider; // immutable after beginFetch - NSURLSession *_session; - BOOL _shouldInvalidateSession; // immutable after beginFetch - NSURLSession *_sessionNeedingInvalidation; - NSURLSessionConfiguration *_configuration; - NSURLSessionTask *_sessionTask; - NSString *_taskDescription; - float _taskPriority; - NSURLResponse *_response; - NSString *_sessionIdentifier; - BOOL _wasCreatedFromBackgroundSession; - BOOL _didCreateSessionIdentifier; - NSString *_sessionIdentifierUUID; - BOOL _userRequestedBackgroundSession; - BOOL _usingBackgroundSession; - NSMutableData * GTM_NULLABLE_TYPE _downloadedData; - NSError *_downloadFinishedError; - NSData *_downloadResumeData; // immutable after construction - NSData * GTM_NULLABLE_TYPE _downloadTaskErrorData; // Data for when download task fails - NSURL *_destinationFileURL; - int64_t _downloadedLength; - NSURLCredential *_credential; // username & password - NSURLCredential *_proxyCredential; // credential supplied to proxy servers - BOOL _isStopNotificationNeeded; // set when start notification has been sent - BOOL _isUsingTestBlock; // set when a test block was provided (remains set when the block is released) - id _userData; // retained, if set by caller - NSMutableDictionary *_properties; // more data retained for caller - dispatch_queue_t _callbackQueue; - dispatch_group_t _callbackGroup; // read-only after creation - NSOperationQueue *_delegateQueue; // immutable after beginFetch - - id<GTMFetcherAuthorizationProtocol> _authorizer; // immutable after beginFetch - - // The service object that created and monitors this fetcher, if any. - id<GTMSessionFetcherServiceProtocol> _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 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 = @"<download resume data>"; - } else if (_wasCreatedFromBackgroundSession) { - requestStr = @"<from bg session>"; - } else { - requestStr = @"<no request>"; - } - } - return [NSString stringWithFormat:@"%@ %p (%@)", [self class], self, requestStr]; -} - -- (void)dealloc { - GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded, - @"unbalanced fetcher notification for %@", _request.URL); - [self forgetSessionIdentifierForFetcherWithoutSyncCheck]; - - // Note: if a session task or a retry timer was pending, then this instance - // would be retained by those so it wouldn't be getting dealloc'd, - // hence we don't need to stopFetch here -} - -#pragma mark - - -// Begin fetching the URL (or begin a retry fetch). The delegate is retained -// for the duration of the fetch connection. - -- (void)beginFetchWithCompletionHandler:(GTM_NULLABLE GTMSessionFetcherCompletionHandler)handler { - GTMSessionCheckNotSynchronized(self); - - _completionHandler = [handler copy]; - - // The user may have called setDelegate: earlier if they want to use other - // delegate-style callbacks during the fetch; otherwise, the delegate is nil, - // which is fine. - [self beginFetchMayDelay:YES mayAuthorize:YES]; -} - -// Begin fetching the URL for a retry fetch. The delegate and completion handler -// are already provided, and do not need to be copied. -- (void)beginFetchForRetry { - GTMSessionCheckNotSynchronized(self); - - [self beginFetchMayDelay:YES mayAuthorize:YES]; -} - -- (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(GTM_NULLABLE_TYPE id)target - didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector { - GTMSessionFetcherAssertValidSelector(target, finishedSelector, @encode(GTMSessionFetcher *), - @encode(NSData *), @encode(NSError *), 0); - GTMSessionFetcherCompletionHandler completionHandler = ^(NSData *data, NSError *error) { - if (target && finishedSelector) { - id selfArg = self; // Placate ARC. - NSMethodSignature *sig = [target methodSignatureForSelector:finishedSelector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig]; - [invocation setSelector:(SEL)finishedSelector]; - [invocation setTarget:target]; - [invocation setArgument:&selfArg atIndex:2]; - [invocation setArgument:&data atIndex:3]; - [invocation setArgument:&error atIndex:4]; - [invocation invoke]; - } - }; - return completionHandler; -} - -- (void)beginFetchWithDelegate:(GTM_NULLABLE_TYPE id)target - didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector { - GTMSessionCheckNotSynchronized(self); - - GTMSessionFetcherCompletionHandler handler = [self completionHandlerWithTarget:target - didFinishSelector:finishedSelector]; - [self beginFetchWithCompletionHandler:handler]; -} - -- (void)beginFetchMayDelay:(BOOL)mayDelay - mayAuthorize:(BOOL)mayAuthorize { - // This is the internal entry point for re-starting fetches. - GTMSessionCheckNotSynchronized(self); - - NSMutableURLRequest *fetchRequest = _request; // The request property is now externally immutable. - NSURL *fetchRequestURL = fetchRequest.URL; - NSString *priorSessionIdentifier = self.sessionIdentifier; - - // A utility block for creating error objects when we fail to start the fetch. - NSError *(^beginFailureError)(NSInteger) = ^(NSInteger code){ - NSString *urlString = fetchRequestURL.absoluteString; - NSDictionary *userInfo = @{ - NSURLErrorFailingURLStringErrorKey : (urlString ? urlString : @"(missing URL)") - }; - return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain - code:code - userInfo:userInfo]; - }; - - // Catch delegate queue maxConcurrentOperationCount values other than 1, particularly - // NSOperationQueueDefaultMaxConcurrentOperationCount (-1), to avoid the additional complexity - // of simultaneous or out-of-order delegate callbacks. - GTMSESSION_ASSERT_DEBUG(_delegateQueue.maxConcurrentOperationCount == 1, - @"delegate queue %@ should support one concurrent operation, not %ld", - _delegateQueue.name, - (long)_delegateQueue.maxConcurrentOperationCount); - - if (!_initialBeginFetchDate) { - // This ivar is set only here on the initial beginFetch so need not be synchronized. - _initialBeginFetchDate = [[NSDate alloc] init]; - } - - if (self.sessionTask != nil) { - // If cached fetcher returned through fetcherWithSessionIdentifier:, then it's - // already begun, but don't consider this a failure, since the user need not know this. - if (self.sessionIdentifier != nil) { - return; - } - GTMSESSION_ASSERT_DEBUG(NO, @"Fetch object %@ being reused; this should never happen", self); - [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)]; - return; - } - - if (fetchRequestURL == nil && !_downloadResumeData && !priorSessionIdentifier) { - GTMSESSION_ASSERT_DEBUG(NO, @"Beginning a fetch requires a request with a URL"); - [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)]; - return; - } - - // We'll respect the user's request for a background session (unless this is - // an upload fetcher, which does its initial request foreground.) - self.usingBackgroundSession = self.useBackgroundSession && [self canFetchWithBackgroundSession]; - - NSURL *bodyFileURL = self.bodyFileURL; - if (bodyFileURL) { - NSError *fileCheckError; - if (![bodyFileURL checkResourceIsReachableAndReturnError:&fileCheckError]) { - // This assert fires when the file being uploaded no longer exists once - // the fetcher is ready to start the upload. - GTMSESSION_ASSERT_DEBUG_OR_LOG(0, @"Body file is unreachable: %@\n %@", - bodyFileURL.path, fileCheckError); - [self failToBeginFetchWithError:fileCheckError]; - return; - } - } - - NSString *requestScheme = fetchRequestURL.scheme; - BOOL isDataRequest = [requestScheme isEqual:@"data"]; - if (isDataRequest) { - // NSURLSession does not support data URLs in background sessions. -#if DEBUG - if (priorSessionIdentifier || self.sessionIdentifier) { - GTMSESSION_LOG_DEBUG(@"Converting background to foreground session for %@", - fetchRequest); - } -#endif - [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<NSURLSessionDelegate> delegate = [_service sessionDelegate]; - if (!delegate || !self.canShareSession) { - delegate = self; - } - self.session = [NSURLSession sessionWithConfiguration:_configuration - delegate:delegate - delegateQueue:self.sessionDelegateQueue]; - GTMSESSION_ASSERT_DEBUG(self.session, @"Couldn't create session"); - - // Tell the service about the session created by this fetcher. This also signals the - // service's semaphore to allow other fetchers to request this session. - [_service fetcherDidCreateSession:self]; - - // If this assertion fires, the client probably tried to use a session identifier that was - // already used. The solution is to make the client use a unique identifier (or better yet let - // the session fetcher assign the identifier). - GTMSESSION_ASSERT_DEBUG(self.session.delegate == delegate, @"Couldn't assign delegate."); - - if (self.session) { - BOOL isUsingSharedDelegate = (delegate != self); - if (!isUsingSharedDelegate) { - _shouldInvalidateSession = YES; - } - } - } - - if (isRecreatingSession) { - _shouldInvalidateSession = YES; - - // Let's make sure there are tasks still running or if not that we get a callback from a - // completed one; otherwise, we assume the tasks failed. - // This is the observed behavior perhaps 25% of the time within the Simulator running 7.0.3 on - // exiting the app after starting an upload and relaunching the app if we manage to relaunch - // after the task has completed, but before the system relaunches us in the background. - [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, - NSArray *downloadTasks) { - if (dataTasks.count == 0 && uploadTasks.count == 0 && downloadTasks.count == 0) { - double const kDelayInSeconds = 1.0; // We should get progress indication or completion soon - dispatch_time_t checkForFeedbackDelay = - dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDelayInSeconds * NSEC_PER_SEC)); - dispatch_after(checkForFeedbackDelay, dispatch_get_main_queue(), ^{ - if (!self.sessionTask && !fetchRequest) { - // If our task and/or request haven't been restored, then we assume task feedback lost. - [self removePersistedBackgroundSessionFromDefaults]; - NSError *sessionError = - [NSError errorWithDomain:kGTMSessionFetcherErrorDomain - code:GTMSessionFetcherErrorBackgroundFetchFailed - userInfo:nil]; - [self failToBeginFetchWithError:sessionError]; - } - }); - } - }]; - return; - } - - self.downloadedData = nil; - self.downloadedLength = 0; - - if (_servicePriority == NSIntegerMin) { - mayDelay = NO; - } - if (mayDelay && _service) { - BOOL shouldFetchNow = [_service fetcherShouldBeginFetching:self]; - if (!shouldFetchNow) { - // The fetch is deferred, but will happen later. - // - // If this session is held by the fetcher service, clear the session now so that we don't - // assume it's still valid after the fetcher is restarted. - if (self.canShareSession) { - self.session = nil; - } - return; - } - } - - NSString *effectiveHTTPMethod = [fetchRequest valueForHTTPHeaderField:@"X-HTTP-Method-Override"]; - if (effectiveHTTPMethod == nil) { - effectiveHTTPMethod = fetchRequest.HTTPMethod; - } - BOOL isEffectiveHTTPGet = (effectiveHTTPMethod == nil - || [effectiveHTTPMethod isEqual:@"GET"]); - - BOOL needsUploadTask = (self.useUploadTask || self.bodyFileURL || self.bodyStreamProvider); - if (_bodyData || self.bodyStreamProvider || fetchRequest.HTTPBodyStream) { - if (isEffectiveHTTPGet) { - fetchRequest.HTTPMethod = @"POST"; - isEffectiveHTTPGet = NO; - } - - if (_bodyData) { - if (!needsUploadTask) { - fetchRequest.HTTPBody = _bodyData; - } -#if !STRIP_GTM_FETCH_LOGGING - } else if (fetchRequest.HTTPBodyStream) { - if ([self respondsToSelector:@selector(loggedInputStreamForInputStream:)]) { - fetchRequest.HTTPBodyStream = - [self performSelector:@selector(loggedInputStreamForInputStream:) - withObject:fetchRequest.HTTPBodyStream]; - } -#endif - } - } - - // We authorize after setting up the http method and body in the request - // because OAuth 1 may need to sign the request body - if (mayAuthorize && _authorizer && !isDataRequest) { - BOOL isAuthorized = [_authorizer isAuthorizedRequest:fetchRequest]; - if (!isAuthorized) { - // Authorization needed. - // - // If this session is held by the fetcher service, clear the session now so that we don't - // assume it's still valid after authorization completes. - if (self.canShareSession) { - self.session = nil; - } - - // Authorizing the request will recursively call this beginFetch:mayDelay: - // or failToBeginFetchWithError:. - [self authorizeRequest]; - return; - } - } - - // set the default upload or download retry interval, if necessary - if ([self isRetryEnabled] && self.maxRetryInterval <= 0) { - if (isEffectiveHTTPGet || [effectiveHTTPMethod isEqual:@"HEAD"]) { - [self setMaxRetryInterval:kDefaultMaxDownloadRetryInterval]; - } else { - [self setMaxRetryInterval:kDefaultMaxUploadRetryInterval]; - } - } - - // finally, start the connection - NSURLSessionTask *newSessionTask; - BOOL needsDataAccumulator = NO; - if (_downloadResumeData) { - newSessionTask = [_session downloadTaskWithResumeData:_downloadResumeData]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, - @"Failed downloadTaskWithResumeData for %@, resume data %lu bytes", - _session, (unsigned long)_downloadResumeData.length); - } else if (_destinationFileURL && !isDataRequest) { - newSessionTask = [_session downloadTaskWithRequest:fetchRequest]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed downloadTaskWithRequest for %@, %@", - _session, fetchRequest); - } else if (needsUploadTask) { - if (bodyFileURL) { - newSessionTask = [_session uploadTaskWithRequest:fetchRequest - fromFile:bodyFileURL]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, - @"Failed uploadTaskWithRequest for %@, %@, file %@", - _session, fetchRequest, bodyFileURL.path); - } else if (self.bodyStreamProvider) { - newSessionTask = [_session uploadTaskWithStreamedRequest:fetchRequest]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, - @"Failed uploadTaskWithStreamedRequest for %@, %@", - _session, fetchRequest); - } else { - GTMSESSION_ASSERT_DEBUG_OR_LOG(_bodyData != nil, - @"Upload task needs body data, %@", fetchRequest); - newSessionTask = [_session uploadTaskWithRequest:fetchRequest - fromData:(NSData * GTM_NONNULL_TYPE)_bodyData]; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, - @"Failed uploadTaskWithRequest for %@, %@, body data %lu bytes", - _session, fetchRequest, (unsigned long)_bodyData.length); - } - needsDataAccumulator = YES; - } else { - newSessionTask = [_session dataTaskWithRequest:fetchRequest]; - needsDataAccumulator = YES; - GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed dataTaskWithRequest for %@, %@", - _session, fetchRequest); - } - self.sessionTask = newSessionTask; - - if (!newSessionTask) { - // We shouldn't get here; if we're here, an earlier assertion should have fired to explain - // which session task creation failed. - [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorTaskCreationFailed)]; - return; - } - - if (needsDataAccumulator && _accumulateDataBlock == nil) { - self.downloadedData = [NSMutableData data]; - } - if (_taskDescription) { - newSessionTask.taskDescription = _taskDescription; - } - if (_taskPriority >= 0) { -#if 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<GTMUIApplicationProtocol> 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. - self->_testBlock = nil; - self->_isUsingTestBlock = NO; - [self->_sessionTask resume]; - return; - } - - GTMSessionFetcherBodyStreamProvider bodyStreamProvider = self.bodyStreamProvider; - if (bodyStreamProvider) { - bodyStreamProvider(^(NSInputStream *bodyStream){ - // Read from the input stream into an NSData buffer. We'll drain the stream - // explicitly on a background queue. - [self invokeOnCallbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) - afterUserStopped:NO - block:^{ - NSError *streamError; - NSData *streamedData = GTMDataFromInputStream(bodyStream, &streamError); - - dispatch_async(dispatch_get_main_queue(), ^{ - // Continue callbacks on the main thread, since serial behavior - // is more reliable for tests. - [self simulateDataCallbacksForTestBlockWithBodyData:streamedData - response:response - responseData:responseData - error:(error ?: streamError)]; - }); - }]; - }); - } else { - // No input stream; use the supplied data or file URL. - NSURL *bodyFileURL = self.bodyFileURL; - if (bodyFileURL) { - NSError *readError; - self->_bodyData = [NSData dataWithContentsOfURL:bodyFileURL - options:NSDataReadingMappedIfSafe - error:&readError]; - error = readError; - } - - // No stream provider. - - // In real fetches, nothing happens until the run loop spins, so apps have leeway to - // set callbacks after they call beginFetch. We'll mirror that fetcher behavior by - // delaying callbacks here at least to the next spin of the run loop. That keeps - // immediate, synchronous setting of callback blocks after beginFetch working in tests. - dispatch_async(dispatch_get_main_queue(), ^{ - [self simulateDataCallbacksForTestBlockWithBodyData:self->_bodyData - response:response - responseData:responseData - error:error]; - }); - } - }); -} - -- (void)simulateByteTransferReportWithDataLength:(int64_t)totalDataLength - block:(GTMSessionFetcherSendProgressBlock)block { - // This utility method simulates transfer progress with up to three callbacks. - // It is used to call back to any of the progress blocks. - int64_t sendReportSize = totalDataLength / 3 + 1; - int64_t totalSent = 0; - while (totalSent < totalDataLength) { - int64_t bytesRemaining = totalDataLength - totalSent; - sendReportSize = MIN(sendReportSize, bytesRemaining); - totalSent += sendReportSize; - [self invokeOnCallbackQueueUnlessStopped:^{ - block(sendReportSize, totalSent, totalDataLength); - }]; - } -} - -- (void)simulateDataCallbacksForTestBlockWithBodyData:(NSData * GTM_NULLABLE_TYPE)bodyData - response:(NSURLResponse *)response - responseData:(NSData *)suppliedData - error:(NSError *)suppliedError { - __block NSData *responseData = suppliedData; - __block NSError *responseError = suppliedError; - - // This method does the test simulation of callbacks once the upload - // and download data are known. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Get copies of ivars we'll access in async invocations. This simulation assumes - // they won't change during fetcher execution. - NSURL *destinationFileURL = _destinationFileURL; - GTMSessionFetcherWillRedirectBlock willRedirectBlock = _willRedirectBlock; - GTMSessionFetcherDidReceiveResponseBlock didReceiveResponseBlock = _didReceiveResponseBlock; - GTMSessionFetcherSendProgressBlock sendProgressBlock = _sendProgressBlock; - GTMSessionFetcherDownloadProgressBlock downloadProgressBlock = _downloadProgressBlock; - GTMSessionFetcherAccumulateDataBlock accumulateDataBlock = _accumulateDataBlock; - GTMSessionFetcherReceivedProgressBlock receivedProgressBlock = _receivedProgressBlock; - GTMSessionFetcherWillCacheURLResponseBlock willCacheURLResponseBlock = - _willCacheURLResponseBlock; - - // Simulate receipt of redirection. - if (willRedirectBlock) { - [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES - block:^{ - willRedirectBlock((NSHTTPURLResponse *)response, self->_request, - ^(NSURLRequest *redirectRequest) { - // For simulation, we'll assume the app will just continue. - }); - }]; - } - - // If the fetcher has a challenge block, simulate a challenge. - // - // It might be nice to eventually let the user determine which testBlock - // fetches get challenged rather than always executing the supplied - // challenge block. - if (_challengeBlock) { - [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES - block:^{ - if (self->_challengeBlock) { - NSURL *requestURL = self->_request.URL; - NSString *host = requestURL.host; - NSURLProtectionSpace *pspace = - [[NSURLProtectionSpace alloc] initWithHost:host - port:requestURL.port.integerValue - protocol:requestURL.scheme - realm:nil - authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; - id<NSURLAuthenticationChallengeSender> unusedSender = - (id<NSURLAuthenticationChallengeSender>)[NSNull null]; - NSURLAuthenticationChallenge *challenge = - [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:pspace - proposedCredential:nil - previousFailureCount:0 - failureResponse:nil - error:nil - sender:unusedSender]; - self->_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.<ClassName>_<UUID>_<Metadata in JSON format> - 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.<ClassName>_<UUID>_<Metadata in JSON format> - 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<GTMUIApplicationProtocol> 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<GTMFetcherAuthorizationProtocol>)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<GTMSessionFetcherServiceProtocol> service; - NSMutableURLRequest *request; - - // If the task or the retry timer is all that's retaining the fetcher, - // we want to be sure this instance survives stopping at least long enough for - // the stack to unwind. - __autoreleasing GTMSessionFetcher *holdSelf = self; - - BOOL hasCanceledTask = NO; - - [holdSelf destroyRetryTimer]; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _hasStoppedFetching = YES; - - service = _service; - request = _request; - - if (_sessionTask) { - // In case cancelling the task or session calls this recursively, we want - // to ensure that we'll only release the task and delegate once, - // so first set _sessionTask to nil - // - // This may be called in a callback from the task, so use autorelease to avoid - // releasing the task in its own callback. - __autoreleasing NSURLSessionTask *oldTask = _sessionTask; - if (!_isUsingTestBlock) { - _response = _sessionTask.response; - } - _sessionTask = nil; - - if ([oldTask state] != NSURLSessionTaskStateCompleted) { - // For download tasks, when the fetch is stopped, we may provide resume data that can - // be used to create a new session. - BOOL mayResume = (_resumeDataBlock - && [oldTask respondsToSelector:@selector(cancelByProducingResumeData:)]); - if (!mayResume) { - [oldTask cancel]; - // A side effect of stopping the task is that URLSession:task:didCompleteWithError: - // will be invoked asynchronously on the delegate queue. - } else { - void (^resumeBlock)(NSData *) = _resumeDataBlock; - _resumeDataBlock = nil; - - // Save callbackQueue since releaseCallbacks clears it. - dispatch_queue_t callbackQueue = _callbackQueue; - dispatch_group_enter(_callbackGroup); - [(NSURLSessionDownloadTask *)oldTask cancelByProducingResumeData:^(NSData *resumeData) { - [self invokeOnCallbackQueue:callbackQueue - afterUserStopped:YES - block:^{ - resumeBlock(resumeData); - dispatch_group_leave(self->_callbackGroup); - }]; - }]; - } - hasCanceledTask = YES; - } - } - - // If the task was canceled, wait until the URLSession:task:didCompleteWithError: to call - // finishTasksAndInvalidate, since calling it immediately tends to crash, see radar 18471901. - if (_session) { - BOOL shouldInvalidate = _shouldInvalidateSession; -#if TARGET_OS_IPHONE - // Don't invalidate if we've got a systemCompletionHandler, since - // URLSessionDidFinishEventsForBackgroundURLSession: won't be called if invalidated. - shouldInvalidate = shouldInvalidate && !self.systemCompletionHandler; -#endif - if (shouldInvalidate) { - __autoreleasing NSURLSession *oldSession = _session; - _session = nil; - - if (!hasCanceledTask) { - [oldSession finishTasksAndInvalidate]; - } else { - _sessionNeedingInvalidation = oldSession; - } - } - } - } // @synchronized(self) - - // send the stopped notification - [self sendStopNotificationIfNeeded]; - - [_authorizer stopAuthorizationForRequest:request]; - - if (shouldReleaseCallbacks) { - [self releaseCallbacks]; - - self.authorizer = nil; - } - - [service fetcherDidStop:self]; - -#if GTM_BACKGROUND_TASK_FETCHING - [self endBackgroundTask]; -#endif -} - -- (void)setStopNotificationNeeded:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _isStopNotificationNeeded = flag; - } // @synchronized(self) -} - -- (void)sendStopNotificationIfNeeded { - BOOL sendNow = NO; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_isStopNotificationNeeded) { - _isStopNotificationNeeded = NO; - sendNow = YES; - } - } // @synchronized(self) - - if (sendNow) { - [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification - userInfo:nil - requireAsync:NO]; - } -} - -- (void)retryFetch { - [self stopFetchReleasingCallbacks:NO]; - - // A retry will need a configuration with a fresh session identifier. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_sessionIdentifier && _didCreateSessionIdentifier) { - [self forgetSessionIdentifierForFetcher]; - _configuration = nil; - } - - if (_canShareSession) { - // Force a grab of the current session from the fetcher service in case - // the service's old one has become invalid. - _session = nil; - } - } // @synchronized(self) - - [self beginFetchForRetry]; -} - -- (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds { - // Uncovered in upload fetcher testing, because the chunk fetcher is being waited on, and gets - // released by the upload code. The uploader just holds onto it with an ivar, and that gets - // nilled in the chunk fetcher callback. - // Used once in while loop just to avoid unused variable compiler warning. - __autoreleasing GTMSessionFetcher *holdSelf = self; - - NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; - - BOOL shouldSpinRunLoop = ([NSThread isMainThread] && - (!self.callbackQueue - || self.callbackQueue == dispatch_get_main_queue())); - BOOL expired = NO; - - // Loop until the callbacks have been called and released, and until - // the connection is no longer pending, until there are no callback dispatches - // in flight, or until the timeout has expired. - int64_t delta = (int64_t)(100 * NSEC_PER_MSEC); // 100 ms - while (1) { - BOOL isTaskInProgress = (holdSelf->_sessionTask - && [_sessionTask state] != NSURLSessionTaskStateCompleted); - BOOL needsToCallCompletion = (_completionHandler != nil); - BOOL isCallbackInProgress = (_callbackGroup - && dispatch_group_wait(_callbackGroup, dispatch_time(DISPATCH_TIME_NOW, delta))); - - if (!isTaskInProgress && !needsToCallCompletion && !isCallbackInProgress) break; - - expired = ([giveUpDate timeIntervalSinceNow] < 0); - if (expired) { - GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher waitForCompletionWithTimeout:%0.1f expired -- " - @"%@%@%@", timeoutInSeconds, - isTaskInProgress ? @"taskInProgress " : @"", - needsToCallCompletion ? @"needsToCallCompletion " : @"", - isCallbackInProgress ? @"isCallbackInProgress" : @""); - break; - } - - // Run the current run loop 1/1000 of a second to give the networking - // code a chance to work - const NSTimeInterval kSpinInterval = 0.001; - if (shouldSpinRunLoop) { - NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; - [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; - } else { - [NSThread sleepForTimeInterval:kSpinInterval]; - } - } - return !expired; -} - -+ (void)setGlobalTestBlock:(GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE)block { -#if GTM_DISABLE_FETCHER_TEST_BLOCK - GTMSESSION_ASSERT_DEBUG(block == nil, @"test blocks disabled"); -#endif - gGlobalTestBlock = [block copy]; -} - -#if GTM_BACKGROUND_TASK_FETCHING - -static GTM_NULLABLE_TYPE id<GTMUIApplicationProtocol> gSubstituteUIApp; - -+ (void)setSubstituteUIApplication:(nullable id<GTMUIApplicationProtocol>)app { - gSubstituteUIApp = app; -} - -+ (nullable id<GTMUIApplicationProtocol>)substituteUIApplication { - return gSubstituteUIApp; -} - -+ (nullable id<GTMUIApplicationProtocol>)fetcherUIApplication { - id<GTMUIApplicationProtocol> 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<GTMUIApplicationProtocol>)[applicationClass sharedApplication]; - } - return app; -} -#endif // GTM_BACKGROUND_TASK_FETCHING - -#pragma mark NSURLSession Delegate Methods - -// NSURLSession documentation indicates that redirectRequest can be passed to the handler -// but empirically redirectRequest lacks the HTTP body, so passing it will break POSTs. -// Instead, we construct a new request, a copy of the original, with overrides from the -// redirect. - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -willPerformHTTPRedirection:(NSHTTPURLResponse *)redirectResponse - newRequest:(NSURLRequest *)redirectRequest - completionHandler:(void (^)(NSURLRequest * GTM_NULLABLE_TYPE))handler { - [self setSessionTask:task]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ willPerformHTTPRedirection:%@ newRequest:%@", - [self class], self, session, task, redirectResponse, redirectRequest); - - if ([self userStoppedFetching]) { - handler(nil); - return; - } - if (redirectRequest && redirectResponse) { - // Copy the original request, including the body. - NSURLRequest *originalRequest = self.request; - NSMutableURLRequest *newRequest = [originalRequest mutableCopy]; - - // The new requests's URL overrides the original's URL. - [newRequest setURL:[GTMSessionFetcher redirectURLWithOriginalRequestURL:originalRequest.URL - redirectRequestURL:redirectRequest.URL]]; - - // Any headers in the redirect override headers in the original. - NSDictionary *redirectHeaders = redirectRequest.allHTTPHeaderFields; - for (NSString *key in redirectHeaders) { - NSString *value = [redirectHeaders objectForKey:key]; - [newRequest setValue:value forHTTPHeaderField:key]; - } - - redirectRequest = newRequest; - - // Log the response we just received - [self setResponse:redirectResponse]; - [self logNowWithError:nil]; - - GTMSessionFetcherWillRedirectBlock willRedirectBlock = self.willRedirectBlock; - if (willRedirectBlock) { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - [self invokeOnCallbackQueueAfterUserStopped:YES - block:^{ - willRedirectBlock(redirectResponse, redirectRequest, ^(NSURLRequest *clientRequest) { - - // Update the request for future logging. - [self updateMutableRequest:[clientRequest mutableCopy]]; - - handler(clientRequest); - }); - }]; - } // @synchronized(self) - return; - } - // Continues here if the client did not provide a redirect block. - - // Update the request for future logging. - [self updateMutableRequest:[redirectRequest mutableCopy]]; - } - handler(redirectRequest); -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask -didReceiveResponse:(NSURLResponse *)response - completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))handler { - [self setSessionTask:dataTask]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveResponse:%@", - [self class], self, session, dataTask, response); - void (^accumulateAndFinish)(NSURLSessionResponseDisposition) = - ^(NSURLSessionResponseDisposition dispositionValue) { - // This method is called when the server has determined that it - // has enough information to create the NSURLResponse - // it can be called multiple times, for example in the case of a - // redirect, so each time we reset the data. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - BOOL hadPreviousData = self->_downloadedLength > 0; - - [self->_downloadedData setLength:0]; - self->_downloadedLength = 0; - - if (hadPreviousData && (dispositionValue != NSURLSessionResponseCancel)) { - // Tell the accumulate block to discard prior data. - GTMSessionFetcherAccumulateDataBlock accumulateBlock = self->_accumulateDataBlock; - if (accumulateBlock) { - [self invokeOnCallbackQueueUnlessStopped:^{ - accumulateBlock(nil); - }]; - } - } - } // @synchronized(self) - handler(dispositionValue); - }; - - GTMSessionFetcherDidReceiveResponseBlock receivedResponseBlock; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - receivedResponseBlock = _didReceiveResponseBlock; - if (receivedResponseBlock) { - // We will ultimately need to call back to NSURLSession's handler with the disposition value - // for this delegate method even if the user has stopped the fetcher. - [self invokeOnCallbackQueueAfterUserStopped:YES - block:^{ - receivedResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) { - accumulateAndFinish(desiredDisposition); - }); - }]; - } - } // @synchronized(self) - - if (receivedResponseBlock == nil) { - accumulateAndFinish(NSURLSessionResponseAllow); - } -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask -didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didBecomeDownloadTask:%@", - [self class], self, session, dataTask, downloadTask); - [self setSessionTask:downloadTask]; -} - - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * GTM_NULLABLE_TYPE credential))handler { - [self setSessionTask:task]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didReceiveChallenge:%@", - [self class], self, session, task, challenge); - - GTMSessionFetcherChallengeBlock challengeBlock = self.challengeBlock; - if (challengeBlock) { - // The fetcher user has provided custom challenge handling. - // - // We will ultimately need to call back to NSURLSession's handler with the disposition value - // for this delegate method even if the user has stopped the fetcher. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self invokeOnCallbackQueueAfterUserStopped:YES - block:^{ - challengeBlock(self, challenge, handler); - }]; - } - } else { - // No challenge block was provided by the client. - [self respondToChallenge:challenge - completionHandler:handler]; - } -} - -- (void)respondToChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * GTM_NULLABLE_TYPE credential))handler { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSInteger previousFailureCount = [challenge previousFailureCount]; - if (previousFailureCount <= 2) { - NSURLProtectionSpace *protectionSpace = [challenge protectionSpace]; - NSString *authenticationMethod = [protectionSpace authenticationMethod]; - if ([authenticationMethod isEqual:NSURLAuthenticationMethodServerTrust]) { - // SSL. - // - // Background sessions seem to require an explicit check of the server trust object - // rather than default handling. - SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; - if (serverTrust == NULL) { - // No server trust information is available. - handler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - } else { - // Server trust information is available. - void (^callback)(SecTrustRef, BOOL) = ^(SecTrustRef trustRef, BOOL allow){ - if (allow) { - NSURLCredential *trustCredential = [NSURLCredential credentialForTrust:trustRef]; - handler(NSURLSessionAuthChallengeUseCredential, trustCredential); - } else { - GTMSESSION_LOG_DEBUG(@"Cancelling authentication challenge for %@", self->_request.URL); - handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); - } - }; - if (_allowInvalidServerCertificates) { - callback(serverTrust, YES); - } else { - [[self class] evaluateServerTrust:serverTrust - forRequest:_request - completionHandler:callback]; - } - } - return; - } - - NSURLCredential *credential = _credential; - - if ([[challenge protectionSpace] isProxy] && _proxyCredential != nil) { - credential = _proxyCredential; - } - - if (credential) { - handler(NSURLSessionAuthChallengeUseCredential, credential); - } else { - // The credential is still nil; tell the OS to use the default handling. This is needed - // for things that can come out of the keychain (proxies, client certificates, etc.). - // - // Note: Looking up a credential with NSURLCredentialStorage's - // defaultCredentialForProtectionSpace: is *not* the same invoking the handler with - // NSURLSessionAuthChallengePerformDefaultHandling. In the case of - // NSURLAuthenticationMethodClientCertificate, you can get nil back from - // NSURLCredentialStorage, while using this code path instead works. - handler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - } - - } else { - // We've failed auth 3 times. The completion handler will be called with code - // NSURLErrorCancelled. - handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); - } - } // @synchronized(self) -} - -// Return redirect URL based on the original request URL and redirect request URL. -// -// Method disallows any scheme changes between the original request URL and redirect request URL -// aside from "http" to "https". If a change in scheme is detected the redirect URL inherits the -// scheme from the original request URL. -+ (GTM_NULLABLE NSURL *)redirectURLWithOriginalRequestURL:(GTM_NULLABLE NSURL *)originalRequestURL - redirectRequestURL:(GTM_NULLABLE NSURL *)redirectRequestURL { - // In the case of an NSURLSession redirect, neither URL should ever be nil; as a sanity check - // if either is nil return the other URL. - if (!redirectRequestURL) return originalRequestURL; - if (!originalRequestURL) return redirectRequestURL; - - NSString *originalScheme = originalRequestURL.scheme; - NSString *redirectScheme = redirectRequestURL.scheme; - BOOL insecureToSecureRedirect = - (originalScheme != nil && [originalScheme caseInsensitiveCompare:@"http"] == NSOrderedSame && - redirectScheme != nil && [redirectScheme caseInsensitiveCompare:@"https"] == NSOrderedSame); - - // Check for changes to the scheme and disallow any changes except for http to https. - if (!insecureToSecureRedirect && - (redirectScheme.length != originalScheme.length || - [redirectScheme caseInsensitiveCompare:originalScheme] != NSOrderedSame)) { - NSURLComponents *components = - [NSURLComponents componentsWithURL:(NSURL * _Nonnull)redirectRequestURL - resolvingAgainstBaseURL:NO]; - components.scheme = originalScheme; - return components.URL; - } - - return redirectRequestURL; -} - -// Validate the certificate chain. -// -// This may become a public method if it appears to be useful to users. -+ (void)evaluateServerTrust:(SecTrustRef)serverTrust - forRequest:(NSURLRequest *)request - completionHandler:(void (^)(SecTrustRef trustRef, BOOL allow))handler { - // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7. - CFRetain(serverTrust); - - // Evaluate the certificate chain. - // - // The delegate queue may be the main thread. Trust evaluation could cause some - // blocking network activity, so we must evaluate async, as documented at - // https://developer.apple.com/library/ios/technotes/tn2232/ - // - // We must also avoid multiple uses of the trust object, per docs: - // "It is not safe to call this function concurrently with any other function that uses - // the same trust management object, or to re-enter this function for the same trust - // management object." - // - // SecTrustEvaluateAsync both does sync execution of Evaluate and calls back on the - // queue passed to it, according to at sources in - // http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55050.9/lib/SecTrust.cpp - // It would require a global serial queue to ensure the evaluate happens only on a - // single thread at a time, so we'll stick with using SecTrustEvaluate on a background - // thread. - dispatch_queue_t evaluateBackgroundQueue = - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(evaluateBackgroundQueue, ^{ - // It looks like the implementation of SecTrustEvaluate() on Mac grabs a global lock, - // so it may be redundant for us to also lock, but it's easy to synchronize here - // anyway. - 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 = [self->_service stoppedAllFetchersDate]; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - // Avoid a race between stopFetching and the callback. - if (self->_userStoppedFetching) { - return; - } - - // Also avoid calling back if the service has stopped all fetchers - // since this one was created. The fetcher may have stopped before - // stopAllFetchers was invoked, so _userStoppedFetching wasn't set, - // but the app still won't expect the callback to fire after - // the service's stopAllFetchers was invoked. - if (serviceStoppedAllDate - && [self->_initialBeginFetchDate compare:serviceStoppedAllDate] != NSOrderedDescending) { - // stopAllFetchers was called after this fetcher began. - return; - } - } // @synchronized(self) - } - block(); - }); - } -} - -- (void)invokeFetchCallbacksOnCallbackQueueWithData:(GTM_NULLABLE NSData *)data - error:(GTM_NULLABLE NSError *)error { - // Callbacks will be released in the method stopFetchReleasingCallbacks: - GTMSessionFetcherCompletionHandler handler; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - handler = _completionHandler; - - if (handler) { - [self invokeOnCallbackQueueUnlessStopped:^{ - handler(data, error); - - // Post a notification, primarily to allow code to collect responses for - // testing. - // - // The observing code is not likely on the fetcher's callback - // queue, so this posts explicitly to the main queue. - NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - if (data) { - userInfo[kGTMSessionFetcherCompletionDataKey] = data; - } - if (error) { - userInfo[kGTMSessionFetcherCompletionErrorKey] = error; - } - [self postNotificationOnMainThreadWithName:kGTMSessionFetcherCompletionInvokedNotification - userInfo:userInfo - requireAsync:NO]; - }]; - } - } // @synchronized(self) -} - -- (void)postNotificationOnMainThreadWithName:(NSString *)noteName - userInfo:(GTM_NULLABLE NSDictionary *)userInfo - requireAsync:(BOOL)requireAsync { - dispatch_block_t postBlock = ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:noteName - object:self - userInfo:userInfo]; - }; - - if ([NSThread isMainThread] && !requireAsync) { - // Post synchronously for compatibility with older code using the fetcher. - - // Avoid calling out to other code from inside a sync block to avoid risk - // of a deadlock or of recursive sync. - GTMSessionCheckNotSynchronized(self); - - postBlock(); - } else { - dispatch_async(dispatch_get_main_queue(), postBlock); - } -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)uploadTask - needNewBodyStream:(void (^)(NSInputStream * GTM_NULLABLE_TYPE bodyStream))completionHandler { - [self setSessionTask:uploadTask]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ needNewBodyStream:", - [self class], self, session, uploadTask); - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - GTMSessionFetcherBodyStreamProvider provider = _bodyStreamProvider; -#if !STRIP_GTM_FETCH_LOGGING - if ([self respondsToSelector:@selector(loggedStreamProviderForStreamProvider:)]) { - provider = [self performSelector:@selector(loggedStreamProviderForStreamProvider:) - withObject:provider]; - } -#endif - if (provider) { - [self invokeOnCallbackQueueUnlessStopped:^{ - provider(completionHandler); - }]; - } else { - GTMSESSION_ASSERT_DEBUG(NO, @"NSURLSession expects a stream provider"); - - completionHandler(nil); - } - } // @synchronized(self) -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task - didSendBodyData:(int64_t)bytesSent - totalBytesSent:(int64_t)totalBytesSent -totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { - [self setSessionTask:task]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didSendBodyData:%lld" - @" totalBytesSent:%lld totalBytesExpectedToSend:%lld", - [self class], self, session, task, bytesSent, totalBytesSent, - totalBytesExpectedToSend); - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (!_sendProgressBlock) { - return; - } - // We won't hold on to send progress block; it's ok to not send it if the upload finishes. - [self invokeOnCallbackQueueUnlessStopped:^{ - GTMSessionFetcherSendProgressBlock progressBlock; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - progressBlock = self->_sendProgressBlock; - } - if (progressBlock) { - progressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend); - } - }]; - } // @synchronized(self) -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask - didReceiveData:(NSData *)data { - [self setSessionTask:dataTask]; - NSUInteger bufferLength = data.length; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveData:%p (%llu bytes)", - [self class], self, session, dataTask, data, - (unsigned long long)bufferLength); - if (bufferLength == 0) { - // Observed on completing an out-of-process upload. - return; - } - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - GTMSessionFetcherAccumulateDataBlock accumulateBlock = _accumulateDataBlock; - if (accumulateBlock) { - // Let the client accumulate the data. - _downloadedLength += bufferLength; - [self invokeOnCallbackQueueUnlessStopped:^{ - accumulateBlock(data); - }]; - } else if (!_userStoppedFetching) { - // Append to the mutable data buffer unless the fetch has been cancelled. - - // Resumed upload tasks may not yet have a data buffer. - if (_downloadedData == nil) { - // Using NSClassFromString for iOS 6 compatibility. - GTMSESSION_ASSERT_DEBUG( - ![dataTask isKindOfClass:NSClassFromString(@"NSURLSessionDownloadTask")], - @"Resumed download tasks should not receive data bytes"); - _downloadedData = [[NSMutableData alloc] init]; - } - - [_downloadedData appendData:data]; - _downloadedLength = (int64_t)_downloadedData.length; - - // We won't hold on to receivedProgressBlock here; it's ok to not send - // it if the transfer finishes. - if (_receivedProgressBlock) { - [self invokeOnCallbackQueueUnlessStopped:^{ - GTMSessionFetcherReceivedProgressBlock progressBlock; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - progressBlock = self->_receivedProgressBlock; - } - if (progressBlock) { - progressBlock((int64_t)bufferLength, self->_downloadedLength); - } - }]; - } - } - } // @synchronized(self) -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask - willCacheResponse:(NSCachedURLResponse *)proposedResponse - completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ willCacheResponse:%@ %@", - [self class], self, session, dataTask, - proposedResponse, proposedResponse.response); - GTMSessionFetcherWillCacheURLResponseBlock callback; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - callback = _willCacheURLResponseBlock; - - if (callback) { - [self invokeOnCallbackQueueAfterUserStopped:YES - block:^{ - callback(proposedResponse, completionHandler); - }]; - } - } // @synchronized(self) - if (!callback) { - completionHandler(proposedResponse); - } -} - - -- (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)downloadTask - didWriteData:(int64_t)bytesWritten - totalBytesWritten:(int64_t)totalBytesWritten -totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didWriteData:%lld" - @" bytesWritten:%lld totalBytesExpectedToWrite:%lld", - [self class], self, session, downloadTask, bytesWritten, - totalBytesWritten, totalBytesExpectedToWrite); - [self setSessionTask:downloadTask]; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if ((totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown) && - (totalBytesExpectedToWrite < totalBytesWritten)) { - // Have observed cases were bytesWritten == totalBytesExpectedToWrite, - // but totalBytesWritten > totalBytesExpectedToWrite, so setting to unkown in these cases. - totalBytesExpectedToWrite = NSURLSessionTransferSizeUnknown; - } - // 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 = self->_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. - GTMSESSION_LOG_DEBUG(@"Abandoning download due to status %ld, file %@", - (long)statusCode, downloadLocationURL.path); - - // On error code, add the contents of the temporary file to _downloadTaskErrorData - // This way fetcher clients have access to error details possibly passed by the server. - if (_downloadedLength > 0 && _downloadedLength <= kMaximumDownloadErrorDataLength) { - _downloadTaskErrorData = [NSData dataWithContentsOfURL:downloadLocationURL]; - } else if (_downloadedLength > kMaximumDownloadErrorDataLength) { - GTMSESSION_LOG_DEBUG(@"Download error data for file %@ not passed to userInfo due to size " - @"%lld", downloadLocationURL.path, _downloadedLength); - } - } else { - NSError *moveError; - NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent]; - BOOL didMoveDownload = NO; - if ([fileMgr createDirectoryAtURL:destinationFolderURL - withIntermediateDirectories:YES - attributes:nil - error:&moveError]) { - didMoveDownload = [fileMgr moveItemAtURL:downloadLocationURL - toURL:destinationURL - error:&moveError]; - } - if (!didMoveDownload) { - _downloadFinishedError = moveError; - } - GTM_LOG_BACKGROUND_SESSION(@"%@ %p Moved download from \"%@\" to \"%@\" %@", - [self class], self, - downloadLocationURL.path, destinationURL.path, - error ? error : @""); - } - } // @synchronized(self) -} - -/* Sent as the last message related to a specific task. Error may be - * nil, which implies that no error occurred and this task is complete. - */ -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -didCompleteWithError:(NSError *)error { - [self setSessionTask:task]; - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didCompleteWithError:%@", - [self class], self, session, task, error); - - NSInteger status = self.statusCode; - BOOL forceAssumeRetry = NO; - BOOL succeeded = NO; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - -#if !GTM_DISABLE_FETCHER_TEST_BLOCK - // The task is never resumed when a testBlock is used. When the session is destroyed, - // we should ignore the callback, since the testBlock support code itself invokes - // shouldRetryNowForStatus: and finishWithError:shouldRetry: - if (_isUsingTestBlock) return; -#endif - - if (error == nil) { - error = _downloadFinishedError; - } - succeeded = (error == nil && status >= 0 && status < 300); - if (succeeded) { - // Succeeded. - _bodyLength = task.countOfBytesSent; - } - } // @synchronized(self) - - if (succeeded) { - [self finishWithError:nil shouldRetry:NO]; - return; - } - // For background redirects, no delegate method is called, so we cannot restore a stripped - // Authorization header, so if a 403 ("Forbidden") was generated due to a missing OAuth 2 header, - // set the current request's URL to the redirected URL, so we in effect restore the Authorization - // header. - if ((status == 403) && self.usingBackgroundSession) { - NSURL *redirectURL = self.response.URL; - NSURLRequest *request = self.request; - if (![request.URL isEqual:redirectURL]) { - NSString *authorizationHeader = [request.allHTTPHeaderFields objectForKey:@"Authorization"]; - if (authorizationHeader != nil) { - NSMutableURLRequest *mutableRequest = [request mutableCopy]; - mutableRequest.URL = redirectURL; - [self updateMutableRequest:mutableRequest]; - // Avoid assuming the session is still valid. - self.session = nil; - forceAssumeRetry = YES; - } - } - } - - // If invalidating the session was deferred in stopFetchReleasingCallbacks: then do it now. - NSURLSession *oldSession = self.sessionNeedingInvalidation; - if (oldSession) { - [self setSessionNeedingInvalidation:NULL]; - [oldSession finishTasksAndInvalidate]; - } - - // Failed. - [self shouldRetryNowForStatus:status - error:error - forceAssumeRetry:forceAssumeRetry - response:^(BOOL shouldRetry) { - [self finishWithError:error shouldRetry:shouldRetry]; - }]; -} - -#if TARGET_OS_IPHONE -- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSessionDidFinishEventsForBackgroundURLSession:%@", - [self class], self, session); - [self removePersistedBackgroundSessionFromDefaults]; - - GTMSessionFetcherSystemCompletionHandler handler; - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - handler = self.systemCompletionHandler; - self.systemCompletionHandler = nil; - } // @synchronized(self) - if (handler) { - GTM_LOG_BACKGROUND_SESSION(@"%@ %p Calling system completionHandler", [self class], self); - handler(); - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSURLSession *oldSession = _session; - _session = nil; - if (_shouldInvalidateSession) { - [oldSession finishTasksAndInvalidate]; - } - } // @synchronized(self) - } -} -#endif - -- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(GTM_NULLABLE NSError *)error { - // This may happen repeatedly for retries. On authentication callbacks, the retry - // may begin before the prior session sends the didBecomeInvalid delegate message. - GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", - [self class], self, session, error); - if (session == (NSURLSession *)self.session) { - GTM_LOG_SESSION_DELEGATE(@" Unexpected retained invalid session: %@", session); - self.session = nil; - } -} - -- (void)finishWithError:(GTM_NULLABLE NSError *)error shouldRetry:(BOOL)shouldRetry { - [self removePersistedBackgroundSessionFromDefaults]; - - BOOL shouldStopFetching = YES; - NSData *downloadedData = nil; -#if !STRIP_GTM_FETCH_LOGGING - BOOL shouldDeferLogging = NO; -#endif - BOOL shouldBeginRetryTimer = NO; - NSInteger status = [self statusCode]; - NSURL *destinationURL = self.destinationFileURL; - - BOOL fetchSucceeded = (error == nil && status >= 0 && status < 300); - -#if !STRIP_GTM_FETCH_LOGGING - if (!fetchSucceeded) { - if (!shouldDeferLogging && !self.hasLoggedError) { - [self logNowWithError:error]; - self.hasLoggedError = YES; - } - } -#endif // !STRIP_GTM_FETCH_LOGGING - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - -#if !STRIP_GTM_FETCH_LOGGING - shouldDeferLogging = _deferResponseBodyLogging; -#endif - if (fetchSucceeded) { - // Success - if ((_downloadedData.length > 0) && (destinationURL != nil)) { - // Overwrite any previous file at the destination URL. - NSFileManager *fileMgr = [NSFileManager defaultManager]; - [fileMgr removeItemAtURL:destinationURL - error:NULL]; - NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent]; - BOOL didMoveDownload = NO; - if ([fileMgr createDirectoryAtURL:destinationFolderURL - withIntermediateDirectories:YES - attributes:nil - error:&error]) { - didMoveDownload = [_downloadedData writeToURL:destinationURL - options:NSDataWritingAtomic - error:&error]; - } - if (didMoveDownload) { - _downloadedData = nil; - } else { - _downloadFinishedError = error; - } - } - downloadedData = _downloadedData; - } else { - // Unsuccessful with error or status over 300. Retry or notify the delegate of failure - if (shouldRetry) { - // Retrying. - shouldBeginRetryTimer = YES; - shouldStopFetching = NO; - } else { - if (error == nil) { - // Create an error. - NSDictionary *userInfo = GTMErrorUserInfoForData( - _downloadedData.length > 0 ? _downloadedData : _downloadTaskErrorData, - [self responseHeadersUnsynchronized]); - - error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain - code:status - userInfo:userInfo]; - } else { - // If the error had resume data, and the client supplied a resume block, pass the - // data to the client. - void (^resumeBlock)(NSData *) = _resumeDataBlock; - _resumeDataBlock = nil; - if (resumeBlock) { - NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]; - if (resumeData) { - [self invokeOnCallbackQueueAfterUserStopped:YES block:^{ - resumeBlock(resumeData); - }]; - } - } - } - if (_downloadedData.length > 0) { - downloadedData = _downloadedData; - } - // If the error occurred after retries, report the number and duration of the - // retries. This provides a clue to a developer looking at the error description - // that the fetcher did retry before failing with this error. - if (_retryCount > 0) { - NSMutableDictionary *userInfoWithRetries = - [NSMutableDictionary dictionaryWithDictionary:(NSDictionary *)error.userInfo]; - NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow]; - [userInfoWithRetries setObject:@(timeSinceInitialRequest) - forKey:kGTMSessionFetcherElapsedIntervalWithRetriesKey]; - [userInfoWithRetries setObject:@(_retryCount) - forKey:kGTMSessionFetcherNumberOfRetriesDoneKey]; - error = [NSError errorWithDomain:(NSString *)error.domain - code:error.code - userInfo:userInfoWithRetries]; - } - } - } - } // @synchronized(self) - - if (shouldBeginRetryTimer) { - [self beginRetryTimer]; - } - - // We want to send the stop notification before calling the delegate's - // callback selector, since the callback selector may release all of - // the fetcher properties that the client is using to track the fetches. - // - // We'll also stop now so that, to any observers watching the notifications, - // it doesn't look like our wait for a retry (which may be long, - // 30 seconds or more) is part of the network activity. - [self sendStopNotificationIfNeeded]; - - if (shouldStopFetching) { - [self invokeFetchCallbacksOnCallbackQueueWithData:downloadedData - error:error]; - // The upload subclass doesn't want to release callbacks until upload chunks have completed. - BOOL shouldRelease = [self shouldReleaseCallbacksUponCompletion]; - [self stopFetchReleasingCallbacks:shouldRelease]; - } - -#if !STRIP_GTM_FETCH_LOGGING - // _hasLoggedError is only set by this method - if (!shouldDeferLogging && !_hasLoggedError) { - [self logNowWithError:error]; - } -#endif -} - -- (BOOL)shouldReleaseCallbacksUponCompletion { - // A subclass can override this to keep callbacks around after the - // connection has finished successfully - return YES; -} - -- (void)logNowWithError:(GTM_NULLABLE NSError *)error { - GTMSessionCheckNotSynchronized(self); - - // If the logging category is available, then log the current request, - // response, data, and error - if ([self respondsToSelector:@selector(logFetchWithError:)]) { - [self performSelector:@selector(logFetchWithError:) withObject:error]; - } -} - -#pragma mark Retries - -- (BOOL)isRetryError:(NSError *)error { - struct RetryRecord { - __unsafe_unretained NSString *const domain; - NSInteger code; - }; - - struct RetryRecord retries[] = { - { kGTMSessionFetcherStatusDomain, 408 }, // request timeout - { kGTMSessionFetcherStatusDomain, 502 }, // failure gatewaying to another server - { kGTMSessionFetcherStatusDomain, 503 }, // service unavailable - { kGTMSessionFetcherStatusDomain, 504 }, // request timeout - { NSURLErrorDomain, NSURLErrorTimedOut }, - { NSURLErrorDomain, NSURLErrorNetworkConnectionLost }, - { nil, 0 } - }; - - // NSError's isEqual always returns false for equal but distinct instances - // of NSError, so we have to compare the domain and code values explicitly - NSString *domain = error.domain; - NSInteger code = error.code; - for (int idx = 0; retries[idx].domain != nil; idx++) { - if (code == retries[idx].code && [domain isEqual:retries[idx].domain]) { - return YES; - } - } - return NO; -} - -// shouldRetryNowForStatus:error: responds with YES if the user has enabled retries -// and the status or error is one that is suitable for retrying. "Suitable" -// means either the isRetryError:'s list contains the status or error, or the -// user's retry block is present and returns YES when called, or the -// authorizer may be able to fix. -- (void)shouldRetryNowForStatus:(NSInteger)status - error:(NSError *)error - forceAssumeRetry:(BOOL)forceAssumeRetry - response:(GTMSessionFetcherRetryResponse)response { - // Determine if a refreshed authorizer may avoid an authorization error - BOOL willRetry = NO; - - // We assume _authorizer is immutable after beginFetch, and _hasAttemptedAuthRefresh is modified - // only in this method, and this method is invoked on the serial delegate queue. - // - // We want to avoid calling the authorizer from inside a sync block. - BOOL isFirstAuthError = (_authorizer != nil - && !_hasAttemptedAuthRefresh - && status == GTMSessionFetcherStatusUnauthorized); // 401 - - BOOL hasPrimed = NO; - if (isFirstAuthError) { - if ([_authorizer respondsToSelector:@selector(primeForRefresh)]) { - hasPrimed = [_authorizer primeForRefresh]; - } - } - - BOOL shouldRetryForAuthRefresh = NO; - if (hasPrimed) { - shouldRetryForAuthRefresh = YES; - _hasAttemptedAuthRefresh = YES; - [self updateRequestValue:nil forHTTPHeaderField:@"Authorization"]; - } - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - BOOL shouldDoRetry = [self isRetryEnabledUnsynchronized]; - if (shouldDoRetry && ![self hasRetryAfterInterval]) { - - // Determine if we're doing exponential backoff retries - shouldDoRetry = [self nextRetryIntervalUnsynchronized] < _maxRetryInterval; - - if (shouldDoRetry) { - // If an explicit max retry interval was set, we expect repeated backoffs to take - // up to roughly twice that for repeated fast failures. If the initial attempt is - // already more than 3 times the max retry interval, then failures have taken a long time - // (such as from network timeouts) so don't retry again to avoid the app becoming - // unexpectedly unresponsive. - if (_maxRetryInterval > 0) { - NSTimeInterval maxAllowedIntervalBeforeRetry = _maxRetryInterval * 3; - NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow]; - if (timeSinceInitialRequest > maxAllowedIntervalBeforeRetry) { - shouldDoRetry = NO; - } - } - } - } - BOOL canRetry = shouldRetryForAuthRefresh || forceAssumeRetry || shouldDoRetry; - if (canRetry) { - NSDictionary *userInfo = - GTMErrorUserInfoForData(_downloadedData, [self responseHeadersUnsynchronized]); - NSError *statusError = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain - code:status - userInfo:userInfo]; - if (error == nil) { - error = statusError; - } - willRetry = shouldRetryForAuthRefresh || - forceAssumeRetry || - [self isRetryError:error] || - ((error != statusError) && [self isRetryError:statusError]); - - // If the user has installed a retry callback, consult that. - GTMSessionFetcherRetryBlock retryBlock = _retryBlock; - if (retryBlock) { - [self invokeOnCallbackQueueUnlessStopped:^{ - retryBlock(willRetry, error, response); - }]; - return; - } - } - } // @synchronized(self) - response(willRetry); -} - -- (BOOL)hasRetryAfterInterval { - GTMSessionCheckSynchronized(self); - - NSDictionary *responseHeaders = [self responseHeadersUnsynchronized]; - NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"]; - return (retryAfterValue != nil); -} - -- (NSTimeInterval)retryAfterInterval { - GTMSessionCheckSynchronized(self); - - NSDictionary *responseHeaders = [self responseHeadersUnsynchronized]; - NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"]; - if (retryAfterValue == nil) { - return 0; - } - // Retry-After formatted as HTTP-date | delta-seconds - // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - NSDateFormatter *rfc1123DateFormatter = [[NSDateFormatter alloc] init]; - rfc1123DateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; - rfc1123DateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; - rfc1123DateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss z"; - NSDate *retryAfterDate = [rfc1123DateFormatter dateFromString:retryAfterValue]; - NSTimeInterval retryAfterInterval = (retryAfterDate != nil) ? - retryAfterDate.timeIntervalSinceNow : retryAfterValue.intValue; - retryAfterInterval = MAX(0, retryAfterInterval); - return retryAfterInterval; -} - -- (void)beginRetryTimer { - if (![NSThread isMainThread]) { - // Defer creating and starting the timer until we're on the main thread to ensure it has - // a run loop. - dispatch_group_async(_callbackGroup, dispatch_get_main_queue(), ^{ - [self beginRetryTimer]; - }); - return; - } - - [self destroyRetryTimer]; - - @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; -} - -// 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<GTMFetcherAuthorizationProtocol>)authorizer { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _authorizer; - } // @synchronized(self) -} - -- (void)setAuthorizer:(GTM_NULLABLE id<GTMFetcherAuthorizationProtocol>)authorizer { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (authorizer != _authorizer) { - if ([self isFetchingUnsynchronized]) { - GTMSESSION_ASSERT_DEBUG(0, @"authorizer should not change after beginFetch has been invoked"); - } else { - _authorizer = authorizer; - } - } - } // @synchronized(self) -} - -- (GTM_NULLABLE NSData *)downloadedData { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _downloadedData; - } // @synchronized(self) -} - -- (void)setDownloadedData:(GTM_NULLABLE NSData *)data { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _downloadedData = [data mutableCopy]; - } // @synchronized(self) -} - -- (int64_t)downloadedLength { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _downloadedLength; - } // @synchronized(self) -} - -- (void)setDownloadedLength:(int64_t)length { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _downloadedLength = length; - } // @synchronized(self) -} - -- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _callbackQueue; - } // @synchronized(self) -} - -- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _callbackQueue = queue ?: dispatch_get_main_queue(); - } // @synchronized(self) -} - -- (GTM_NULLABLE NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _session; - } // @synchronized(self) -} - -- (NSInteger)servicePriority { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _servicePriority; - } // @synchronized(self) -} - -- (void)setServicePriority:(NSInteger)value { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (value != _servicePriority) { - GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], - @"servicePriority should not change after beginFetch has been invoked"); - - _servicePriority = value; - } - } // @synchronized(self) -} - - -- (void)setSession:(GTM_NULLABLE NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _session = session; - } // @synchronized(self) -} - -- (BOOL)canShareSession { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _canShareSession; - } // @synchronized(self) -} - -- (void)setCanShareSession:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _canShareSession = flag; - } // @synchronized(self) -} - -- (BOOL)useBackgroundSession { - // This reflects if the user requested a background session, not necessarily - // if one was created. That is tracked with _usingBackgroundSession. - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _userRequestedBackgroundSession; - } // @synchronized(self) -} - -- (void)setUseBackgroundSession:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (flag != _userRequestedBackgroundSession) { - GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized], - @"useBackgroundSession should not change after beginFetch has been invoked"); - - _userRequestedBackgroundSession = flag; - } - } // @synchronized(self) -} - -- (BOOL)isUsingBackgroundSession { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _usingBackgroundSession; - } // @synchronized(self) -} - -- (void)setUsingBackgroundSession:(BOOL)flag { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _usingBackgroundSession = flag; - } // @synchronized(self) -} - -- (GTM_NULLABLE NSURLSession *)sessionNeedingInvalidation { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _sessionNeedingInvalidation; - } // @synchronized(self) -} - -- (void)setSessionNeedingInvalidation:(GTM_NULLABLE NSURLSession *)session { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _sessionNeedingInvalidation = session; - } // @synchronized(self) -} - -- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _delegateQueue; - } // @synchronized(self) -} - -- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (queue != _delegateQueue) { - if ([self isFetchingUnsynchronized]) { - GTMSESSION_ASSERT_DEBUG(0, @"sessionDelegateQueue should not change after fetch begins"); - } else { - _delegateQueue = queue ?: [NSOperationQueue mainQueue]; - } - } - } // @synchronized(self) -} - -- (BOOL)userStoppedFetching { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _userStoppedFetching; - } // @synchronized(self) -} - -- (GTM_NULLABLE id)userData { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _userData; - } // @synchronized(self) -} - -- (void)setUserData:(GTM_NULLABLE id)theObj { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _userData = theObj; - } // @synchronized(self) -} - -- (GTM_NULLABLE NSURL *)destinationFileURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _destinationFileURL; - } // @synchronized(self) -} - -- (void)setDestinationFileURL:(GTM_NULLABLE NSURL *)destinationFileURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (((_destinationFileURL == nil) && (destinationFileURL == nil)) || - [_destinationFileURL isEqual:destinationFileURL]) { - return; - } - if (_sessionIdentifier) { - // This is something we don't expect to happen in production. - // However if it ever happen, leave a system log. - NSLog(@"%@: Destination File URL changed from (%@) to (%@) after session identifier has " - @"been created.", - [self class], _destinationFileURL, destinationFileURL); -#if DEBUG - // On both the simulator and devices, the path can change to the download file, but the name - // shouldn't change. Technically, this isn't supported in the fetcher, but the change of - // URL is expected to happen only across development runs through Xcode. - NSString *oldFilename = [_destinationFileURL lastPathComponent]; - NSString *newFilename = [destinationFileURL lastPathComponent]; - #pragma unused(oldFilename) - #pragma unused(newFilename) - GTMSESSION_ASSERT_DEBUG([oldFilename isEqualToString:newFilename], - @"Destination File URL cannot be changed after session identifier has been created"); -#endif - } - _destinationFileURL = destinationFileURL; - } // @synchronized(self) -} - -- (void)setProperties:(GTM_NULLABLE NSDictionary *)dict { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _properties = [dict mutableCopy]; - } // @synchronized(self) -} - -- (GTM_NULLABLE NSDictionary *)properties { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _properties; - } // @synchronized(self) -} - -- (void)setProperty:(GTM_NULLABLE id)obj forKey:(NSString *)key { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_properties == nil && obj != nil) { - _properties = [[NSMutableDictionary alloc] init]; - } - [_properties setValue:obj forKey:key]; - } // @synchronized(self) -} - -- (GTM_NULLABLE id)propertyForKey:(NSString *)key { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [_properties objectForKey:key]; - } // @synchronized(self) -} - -- (void)addPropertiesFromDictionary:(NSDictionary *)dict { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_properties == nil && dict != nil) { - [self setProperties:[dict mutableCopy]]; - } else { - [_properties addEntriesFromDictionary:dict]; - } - } // @synchronized(self) -} - -- (void)setCommentWithFormat:(id)format, ... { -#if !STRIP_GTM_FETCH_LOGGING - NSString *result = format; - if (format) { - va_list argList; - va_start(argList, format); - - result = [[NSString alloc] initWithFormat:format - arguments:argList]; - va_end(argList); - } - [self setComment:result]; -#endif -} - -#if !STRIP_GTM_FETCH_LOGGING -- (NSData *)loggedStreamData { - return _loggedStreamData; -} - -- (void)appendLoggedStreamData:dataToAdd { - if (!_loggedStreamData) { - _loggedStreamData = [NSMutableData data]; - } - [_loggedStreamData appendData:dataToAdd]; -} - -- (void)clearLoggedStreamData { - _loggedStreamData = nil; -} - -- (void)setDeferResponseBodyLogging:(BOOL)deferResponseBodyLogging { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (deferResponseBodyLogging != _deferResponseBodyLogging) { - _deferResponseBodyLogging = deferResponseBodyLogging; - if (!deferResponseBodyLogging && !self.hasLoggedError) { - [_delegateQueue addOperationWithBlock:^{ - [self logNowWithError:nil]; - }]; - } - } - } // @synchronized(self) -} - -- (BOOL)deferResponseBodyLogging { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _deferResponseBodyLogging; - } // @synchronized(self) -} - -#else -+ (void)setLoggingEnabled:(BOOL)flag { -} - -+ (BOOL)isLoggingEnabled { - return NO; -} -#endif // STRIP_GTM_FETCH_LOGGING - -@end - -@implementation GTMSessionFetcher (BackwardsCompatibilityOnly) - -- (void)setCookieStorageMethod:(NSInteger)method { - // For backwards compatibility with the old fetcher, we'll support the old constants. - // - // Clients using the GTMSessionFetcher class should set the cookie storage explicitly - // themselves. - NSHTTPCookieStorage *storage = nil; - switch(method) { - case 0: // kGTMHTTPFetcherCookieStorageMethodStatic - // nil storage will use [[self class] staticCookieStorage] when the fetch begins. - break; - case 1: // kGTMHTTPFetcherCookieStorageMethodFetchHistory - // Do nothing; use whatever was set by the fetcher service. - return; - case 2: // kGTMHTTPFetcherCookieStorageMethodSystemDefault - storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; - break; - case 3: // kGTMHTTPFetcherCookieStorageMethodNone - // Create temporary storage for this fetcher only. - storage = [[GTMSessionCookieStorage alloc] init]; - break; - default: - GTMSESSION_ASSERT_DEBUG(0, @"Invalid cookie storage method: %d", (int)method); - } - self.cookieStorage = storage; -} - -@end - -@implementation GTMSessionCookieStorage { - NSMutableArray *_cookies; - NSHTTPCookieAcceptPolicy _policy; -} - -- (id)init { - self = [super init]; - if (self != nil) { - _cookies = [[NSMutableArray alloc] init]; - } - return self; -} - -- (GTM_NULLABLE NSArray *)cookies { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return [_cookies copy]; - } // @synchronized(self) -} - -- (void)setCookie:(NSHTTPCookie *)cookie { - if (!cookie) return; - if (_policy == NSHTTPCookieAcceptPolicyNever) return; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self internalSetCookie:cookie]; - } // @synchronized(self) -} - -// Note: this should only be called from inside a @synchronized(self) block. -- (void)internalSetCookie:(NSHTTPCookie *)newCookie { - GTMSessionCheckSynchronized(self); - - if (_policy == NSHTTPCookieAcceptPolicyNever) return; - - BOOL isValidCookie = (newCookie.name.length > 0 - && newCookie.domain.length > 0 - && newCookie.path.length > 0); - GTMSESSION_ASSERT_DEBUG(isValidCookie, @"invalid cookie: %@", newCookie); - - if (isValidCookie) { - // Remove the cookie if it's currently in the array. - NSHTTPCookie *oldCookie = [self cookieMatchingCookie:newCookie]; - if (oldCookie) { - [_cookies removeObjectIdenticalTo:oldCookie]; - } - - if (![[self class] hasCookieExpired:newCookie]) { - [_cookies addObject:newCookie]; - } - } -} - -// Add all cookies in the new cookie array to the storage, -// replacing stored cookies as appropriate. -// -// Side effect: removes expired cookies from the storage array. -- (void)setCookies:(GTM_NULLABLE NSArray *)newCookies { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self removeExpiredCookies]; - - for (NSHTTPCookie *newCookie in newCookies) { - [self internalSetCookie:newCookie]; - } - } // @synchronized(self) -} - -- (void)setCookies:(NSArray *)cookies forURL:(GTM_NULLABLE NSURL *)URL mainDocumentURL:(GTM_NULLABLE NSURL *)mainDocumentURL { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (_policy == NSHTTPCookieAcceptPolicyNever) { - return; - } - - if (_policy == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain) { - NSString *mainHost = mainDocumentURL.host; - NSString *associatedHost = URL.host; - if (!mainHost || ![associatedHost hasSuffix:mainHost]) { - return; - } - } - } // @synchronized(self) - [self setCookies:cookies]; -} - -- (void)deleteCookie:(NSHTTPCookie *)cookie { - if (!cookie) return; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - NSHTTPCookie *foundCookie = [self cookieMatchingCookie:cookie]; - if (foundCookie) { - [_cookies removeObjectIdenticalTo:foundCookie]; - } - } // @synchronized(self) -} - -// Retrieve all cookies appropriate for the given URL, considering -// domain, path, cookie name, expiration, security setting. -// Side effect: removed expired cookies from the storage array. -- (GTM_NULLABLE NSArray *)cookiesForURL:(NSURL *)theURL { - NSMutableArray *foundCookies = nil; - - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [self removeExpiredCookies]; - - // We'll prepend "." to the desired domain, since we want the - // actual domain "nytimes.com" to still match the cookie domain - // ".nytimes.com" when we check it below with hasSuffix. - NSString *host = theURL.host.lowercaseString; - NSString *path = theURL.path; - NSString *scheme = [theURL scheme]; - - NSString *requestingDomain = nil; - BOOL isLocalhostRetrieval = NO; - - if (IsLocalhost(host)) { - isLocalhostRetrieval = YES; - } else { - if (host.length > 0) { - requestingDomain = [@"." stringByAppendingString:host]; - } - } - - for (NSHTTPCookie *storedCookie in _cookies) { - NSString *cookieDomain = storedCookie.domain.lowercaseString; - NSString *cookiePath = storedCookie.path; - BOOL cookieIsSecure = [storedCookie isSecure]; - - BOOL isDomainOK; - - if (isLocalhostRetrieval) { - // Prior to 10.5.6, the domain stored into NSHTTPCookies for localhost - // is "localhost.local" - isDomainOK = (IsLocalhost(cookieDomain) - || [cookieDomain isEqual:@"localhost.local"]); - } else { - // Ensure we're matching exact domain names. We prepended a dot to the - // requesting domain, so we can also prepend one here if needed before - // checking if the request contains the cookie domain. - if (![cookieDomain hasPrefix:@"."]) { - cookieDomain = [@"." stringByAppendingString:cookieDomain]; - } - isDomainOK = [requestingDomain hasSuffix:cookieDomain]; - } - - BOOL isPathOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath]; - BOOL isSecureOK = (!cookieIsSecure - || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame); - - if (isDomainOK && isPathOK && isSecureOK) { - if (foundCookies == nil) { - foundCookies = [NSMutableArray array]; - } - [foundCookies addObject:storedCookie]; - } - } - } // @synchronized(self) - return foundCookies; -} - -// Override methods from the NSHTTPCookieStorage (NSURLSessionTaskAdditions) category. -- (void)storeCookies:(NSArray *)cookies forTask:(NSURLSessionTask *)task { - NSURLRequest *currentRequest = task.currentRequest; - [self setCookies:cookies forURL:currentRequest.URL mainDocumentURL:nil]; -} - -- (void)getCookiesForTask:(NSURLSessionTask *)task - completionHandler:(void (^)(GTM_NSArrayOf(NSHTTPCookie *) *))completionHandler { - if (completionHandler) { - NSURLRequest *currentRequest = task.currentRequest; - NSURL *currentRequestURL = currentRequest.URL; - NSArray *cookies = [self cookiesForURL:currentRequestURL]; - completionHandler(cookies); - } -} - -// Return a cookie from the array with the same name, domain, and path as the -// given cookie, or else return nil if none found. -// -// Both the cookie being tested and all cookies in the storage array should -// be valid (non-nil name, domains, paths). -// -// Note: this should only be called from inside a @synchronized(self) block -- (GTM_NULLABLE NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie { - GTMSessionCheckSynchronized(self); - - NSString *name = cookie.name; - NSString *domain = cookie.domain; - NSString *path = cookie.path; - - GTMSESSION_ASSERT_DEBUG(name && domain && path, - @"Invalid stored cookie (name:%@ domain:%@ path:%@)", name, domain, path); - - for (NSHTTPCookie *storedCookie in _cookies) { - if ([storedCookie.name isEqual:name] - && [storedCookie.domain isEqual:domain] - && [storedCookie.path isEqual:path]) { - return storedCookie; - } - } - return nil; -} - -// Internal routine to remove any expired cookies from the array, excluding -// cookies with nil expirations. -// -// Note: this should only be called from inside a @synchronized(self) block -- (void)removeExpiredCookies { - GTMSessionCheckSynchronized(self); - - // Count backwards since we're deleting items from the array - for (NSInteger idx = (NSInteger)_cookies.count - 1; idx >= 0; idx--) { - NSHTTPCookie *storedCookie = [_cookies objectAtIndex:(NSUInteger)idx]; - if ([[self class] hasCookieExpired:storedCookie]) { - [_cookies removeObjectAtIndex:(NSUInteger)idx]; - } - } -} - -+ (BOOL)hasCookieExpired:(NSHTTPCookie *)cookie { - NSDate *expiresDate = [cookie expiresDate]; - if (expiresDate == nil) { - // Cookies seem to have a Expires property even when the expiresDate method returns nil. - id expiresVal = [[cookie properties] objectForKey:NSHTTPCookieExpires]; - if ([expiresVal isKindOfClass:[NSDate class]]) { - expiresDate = expiresVal; - } - } - BOOL hasExpired = (expiresDate != nil && [expiresDate timeIntervalSinceNow] < 0); - return hasExpired; -} - -- (void)removeAllCookies { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - [_cookies removeAllObjects]; - } // @synchronized(self) -} - -- (NSHTTPCookieAcceptPolicy)cookieAcceptPolicy { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _policy; - } // @synchronized(self) -} - -- (void)setCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)cookieAcceptPolicy { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _policy = cookieAcceptPolicy; - } // @synchronized(self) -} - -@end - -void GTMSessionFetcherAssertValidSelector(id GTM_NULLABLE_TYPE obj, SEL GTM_NULLABLE_TYPE sel, ...) { - // Verify that the object's selector is implemented with the proper - // number and type of arguments -#if DEBUG - va_list argList; - va_start(argList, sel); - - if (obj && sel) { - // Check that the selector is implemented - if (![obj respondsToSelector:sel]) { - NSLog(@"\"%@\" selector \"%@\" is unimplemented or misnamed", - NSStringFromClass([(id)obj class]), - NSStringFromSelector((SEL)sel)); - NSCAssert(0, @"callback selector unimplemented or misnamed"); - } else { - const char *expectedArgType; - unsigned int argCount = 2; // skip self and _cmd - NSMethodSignature *sig = [obj methodSignatureForSelector:sel]; - - // Check that each expected argument is present and of the correct type - while ((expectedArgType = va_arg(argList, const char*)) != 0) { - - if ([sig numberOfArguments] > argCount) { - const char *foundArgType = [sig getArgumentTypeAtIndex:argCount]; - - if (0 != strncmp(foundArgType, expectedArgType, strlen(expectedArgType))) { - NSLog(@"\"%@\" selector \"%@\" argument %d should be type %s", - NSStringFromClass([(id)obj class]), - NSStringFromSelector((SEL)sel), (argCount - 2), expectedArgType); - NSCAssert(0, @"callback selector argument type mistake"); - } - } - argCount++; - } - - // Check that the proper number of arguments are present in the selector - if (argCount != [sig numberOfArguments]) { - NSLog(@"\"%@\" selector \"%@\" should have %d arguments", - NSStringFromClass([(id)obj class]), - NSStringFromSelector((SEL)sel), (argCount - 2)); - NSCAssert(0, @"callback selector arguments incorrect"); - } - } - } - - va_end(argList); -#endif -} - -NSString *GTMFetcherCleanedUserAgentString(NSString *str) { - // Reference http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html - // and http://www-archive.mozilla.org/build/user-agent-strings.html - - if (str == nil) return @""; - - NSMutableString *result = [NSMutableString stringWithString:str]; - - // Replace spaces and commas with underscores - [result replaceOccurrencesOfString:@" " - withString:@"_" - options:0 - range:NSMakeRange(0, result.length)]; - [result replaceOccurrencesOfString:@"," - withString:@"_" - options:0 - range:NSMakeRange(0, result.length)]; - - // Delete http token separators and remaining whitespace - static NSCharacterSet *charsToDelete = nil; - if (charsToDelete == nil) { - // Make a set of unwanted characters - NSString *const kSeparators = @"()<>@;:\\\"/[]?={}"; - - NSMutableCharacterSet *mutableChars = - [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy]; - [mutableChars addCharactersInString:kSeparators]; - charsToDelete = [mutableChars copy]; // hang on to an immutable copy - } - - while (1) { - NSRange separatorRange = [result rangeOfCharacterFromSet:charsToDelete]; - if (separatorRange.location == NSNotFound) break; - - [result deleteCharactersInRange:separatorRange]; - }; - - return result; -} - -NSString *GTMFetcherSystemVersionString(void) { - static NSString *sSavedSystemString; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // The Xcode 8 SDKs finally cleaned up this mess by providing TARGET_OS_OSX - // and TARGET_OS_IOS, but to build with older SDKs, those don't exist and - // instead one has to rely on TARGET_OS_MAC (which is true for iOS, watchOS, - // and tvOS) and TARGET_OS_IPHONE (which is true for iOS, watchOS, tvOS). So - // one has to order these carefully so you pick off the specific things - // first. - // If the code can ever assume Xcode 8 or higher (even when building for - // older OSes), then - // TARGET_OS_MAC -> TARGET_OS_OSX - // TARGET_OS_IPHONE -> TARGET_OS_IOS - // TARGET_IPHONE_SIMULATOR -> TARGET_OS_SIMULATOR -#if TARGET_OS_WATCH - // watchOS - WKInterfaceDevice - - WKInterfaceDevice *currentDevice = [WKInterfaceDevice currentDevice]; - - NSString *rawModel = [currentDevice model]; - NSString *model = GTMFetcherCleanedUserAgentString(rawModel); - - NSString *systemVersion = [currentDevice systemVersion]; - -#if TARGET_OS_SIMULATOR - NSString *hardwareModel = @"sim"; -#else - NSString *hardwareModel; - struct utsname unameRecord; - if (uname(&unameRecord) == 0) { - NSString *machineName = @(unameRecord.machine); - hardwareModel = GTMFetcherCleanedUserAgentString(machineName); - } - if (hardwareModel.length == 0) { - hardwareModel = @"unk"; - } -#endif - - sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@", - model, systemVersion, hardwareModel]; - // Example: Apple_Watch/3.0 hw/Watch1_2 -#elif TARGET_OS_TV || TARGET_OS_IPHONE - // iOS and tvOS have UIDevice, use that. - UIDevice *currentDevice = [UIDevice currentDevice]; - - NSString *rawModel = [currentDevice model]; - NSString *model = GTMFetcherCleanedUserAgentString(rawModel); - - NSString *systemVersion = [currentDevice systemVersion]; - -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR - NSString *hardwareModel = @"sim"; -#else - NSString *hardwareModel; - struct utsname unameRecord; - if (uname(&unameRecord) == 0) { - NSString *machineName = @(unameRecord.machine); - hardwareModel = GTMFetcherCleanedUserAgentString(machineName); - } - if (hardwareModel.length == 0) { - hardwareModel = @"unk"; - } -#endif - - sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@", - model, systemVersion, hardwareModel]; - // Example: iPod_Touch/2.2 hw/iPod1_1 - // Example: Apple_TV/9.2 hw/AppleTV5,3 -#elif TARGET_OS_MAC - // Mac build - NSProcessInfo *procInfo = [NSProcessInfo processInfo]; -#if !defined(MAC_OS_X_VERSION_10_10) - BOOL hasOperatingSystemVersion = NO; -#elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10 - BOOL hasOperatingSystemVersion = - [procInfo respondsToSelector:@selector(operatingSystemVersion)]; -#else - BOOL hasOperatingSystemVersion = YES; -#endif - NSString *versString; - if (hasOperatingSystemVersion) { -#if defined(MAC_OS_X_VERSION_10_10) - // A reference to NSOperatingSystemVersion requires the 10.10 SDK. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability" -// Disable unguarded availability warning as we can't use the @availability macro until we require -// all clients to build with Xcode 9 or above. - NSOperatingSystemVersion version = procInfo.operatingSystemVersion; -#pragma clang diagnostic pop - versString = [NSString stringWithFormat:@"%ld.%ld.%ld", - (long)version.majorVersion, (long)version.minorVersion, - (long)version.patchVersion]; -#else -#pragma unused(procInfo) -#endif - } else { - // With Gestalt inexplicably deprecated in 10.8, we're reduced to reading - // the system plist file. - NSString *const kPath = @"/System/Library/CoreServices/SystemVersion.plist"; - NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:kPath]; - versString = [plist objectForKey:@"ProductVersion"]; - if (versString.length == 0) { - versString = @"10.?.?"; - } - } - - sSavedSystemString = [[NSString alloc] initWithFormat:@"MacOSX/%@", versString]; -#elif defined(_SYS_UTSNAME_H) - // Foundation-only build - struct utsname unameRecord; - uname(&unameRecord); - - sSavedSystemString = [NSString stringWithFormat:@"%s/%s", - unameRecord.sysname, unameRecord.release]; // "Darwin/8.11.1" -#else -#error No branch taken for a default user agent -#endif - }); - return sSavedSystemString; -} - -NSString *GTMFetcherStandardUserAgentString(NSBundle * GTM_NULLABLE_TYPE bundle) { - NSString *result = [NSString stringWithFormat:@"%@ %@", - GTMFetcherApplicationIdentifier(bundle), - GTMFetcherSystemVersionString()]; - return result; -} - -NSString *GTMFetcherApplicationIdentifier(NSBundle * GTM_NULLABLE_TYPE bundle) { - @synchronized([GTMSessionFetcher class]) { - static NSMutableDictionary *sAppIDMap = nil; - - // If there's a bundle ID, use that; otherwise, use the process name - if (bundle == nil) { - bundle = [NSBundle mainBundle]; - } - NSString *bundleID = [bundle bundleIdentifier]; - if (bundleID == nil) { - bundleID = @""; - } - - NSString *identifier = [sAppIDMap objectForKey:bundleID]; - if (identifier) return identifier; - - // Apps may add a string to the info.plist to uniquely identify different builds. - identifier = [bundle objectForInfoDictionaryKey:@"GTMUserAgentID"]; - if (identifier.length == 0) { - if (bundleID.length > 0) { - identifier = bundleID; - } else { - // Fall back on the procname, prefixed by "proc" to flag that it's - // autogenerated and perhaps unreliable - NSString *procName = [[NSProcessInfo processInfo] processName]; - identifier = [NSString stringWithFormat:@"proc_%@", procName]; - } - } - - // Clean up whitespace and special characters - identifier = GTMFetcherCleanedUserAgentString(identifier); - - // If there's a version number, append that - NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - if (version.length == 0) { - version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; - } - - // Clean up whitespace and special characters - version = GTMFetcherCleanedUserAgentString(version); - - // Glue the two together (cleanup done above or else cleanup would strip the - // slash) - if (version.length > 0) { - identifier = [identifier stringByAppendingFormat:@"/%@", version]; - } - - if (sAppIDMap == nil) { - sAppIDMap = [[NSMutableDictionary alloc] init]; - } - [sAppIDMap setObject:identifier forKey:bundleID]; - return identifier; - } -} - -#if DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) -@implementation GTMSessionSyncMonitorInternal { - NSValue *_objectKey; // The synchronize target object. - const char *_functionName; // The function containing the monitored sync block. -} - -- (instancetype)initWithSynchronizationObject:(id)object - allowRecursive:(BOOL)allowRecursive - functionName:(const char *)functionName { - self = [super init]; - if (self) { - Class threadKey = [GTMSessionSyncMonitorInternal class]; - _objectKey = [NSValue valueWithNonretainedObject:object]; - _functionName = functionName; - - NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; - NSMutableDictionary *counters = threadDict[threadKey]; - if (counters == nil) { - counters = [NSMutableDictionary dictionary]; - threadDict[(id)threadKey] = counters; - } - NSCountedSet *functionNamesCounter = counters[_objectKey]; - NSUInteger numberOfSyncingFunctions = functionNamesCounter.count; - - if (!allowRecursive) { - BOOL isTopLevelSyncScope = (numberOfSyncingFunctions == 0); - NSArray *stack = [NSThread callStackSymbols]; - GTMSESSION_ASSERT_DEBUG(isTopLevelSyncScope, - @"*** Recursive sync on %@ at %s; previous sync at %@\n%@", - [object class], functionName, functionNamesCounter.allObjects, - [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]); - } - - if (!functionNamesCounter) { - functionNamesCounter = [NSCountedSet set]; - counters[_objectKey] = functionNamesCounter; - } - [functionNamesCounter addObject:(id _Nonnull)@(functionName)]; - } - return self; -} - -- (void)dealloc { - Class threadKey = [GTMSessionSyncMonitorInternal class]; - - NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; - NSMutableDictionary *counters = threadDict[threadKey]; - NSCountedSet *functionNamesCounter = counters[_objectKey]; - NSString *functionNameStr = @(_functionName); - NSUInteger numberOfSyncsByThisFunction = [functionNamesCounter countForObject:functionNameStr]; - NSArray *stack = [NSThread callStackSymbols]; - GTMSESSION_ASSERT_DEBUG(numberOfSyncsByThisFunction > 0, @"Sync not found on %@ at %s\n%@", - [_objectKey.nonretainedObjectValue class], _functionName, - [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]); - [functionNamesCounter removeObject:functionNameStr]; - if (functionNamesCounter.count == 0) { - [counters removeObjectForKey:_objectKey]; - } -} - -+ (NSArray *)functionsHoldingSynchronizationOnObject:(id)object { - Class threadKey = [GTMSessionSyncMonitorInternal class]; - NSValue *localObjectKey = [NSValue valueWithNonretainedObject:object]; - - NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary; - NSMutableDictionary *counters = threadDict[threadKey]; - NSCountedSet *functionNamesCounter = counters[localObjectKey]; - return functionNamesCounter.count > 0 ? functionNamesCounter.allObjects : nil; -} -@end -#endif // DEBUG && (!defined(NS_BLOCK_ASSERTIONS) || GTMSESSION_ASSERT_AS_LOG) -GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.h @@ -1,112 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "GTMSessionFetcher.h" - -// GTM HTTP Logging -// -// All traffic using GTMSessionFetcher can be easily logged. Call -// -// [GTMSessionFetcher setLoggingEnabled:YES]; -// -// to begin generating log files. -// -// Unless explicitly set by the application using +setLoggingDirectory:, -// logs are put into a default directory, located at: -// * macOS: ~/Desktop/GTMHTTPDebugLogs -// * iOS simulator: ~/GTMHTTPDebugLogs (in application sandbox) -// * iOS device: ~/Documents/GTMHTTPDebugLogs (in application sandbox) -// -// Tip: use the Finder's "Sort By Date" to find the most recent logs. -// -// Each run of an application gets a separate set of log files. An html -// file is generated to simplify browsing the run's http transactions. -// The html file includes javascript links for inline viewing of uploaded -// and downloaded data. -// -// A symlink is created in the logs folder to simplify finding the html file -// for the latest run of the application; the symlink is called -// -// AppName_http_log_newest.html -// -// For better viewing of XML logs, use Camino or Firefox rather than Safari. -// -// Each fetcher may be given a comment to be inserted as a label in the logs, -// such as -// [fetcher setCommentWithFormat:@"retrieve item %@", itemName]; -// -// Projects may define STRIP_GTM_FETCH_LOGGING to remove logging code. - -#if !STRIP_GTM_FETCH_LOGGING - -@interface GTMSessionFetcher (GTMSessionFetcherLogging) - -// Note: on macOS the default logs directory is ~/Desktop/GTMHTTPDebugLogs; on -// iOS simulators it will be the ~/GTMHTTPDebugLogs (in the app sandbox); on -// iOS devices it will be in ~/Documents/GTMHTTPDebugLogs (in the app sandbox). -// These directories will be created as needed, and are excluded from backups -// to iCloud and iTunes. -// -// If a custom directory is set, the directory should already exist. It is -// the application's responsibility to exclude any custom directory from -// backups, if desired. -+ (void)setLoggingDirectory:(NSString *)path; -+ (NSString *)loggingDirectory; - -// client apps can turn logging on and off -+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled; -+ (BOOL)isLoggingEnabled; - -// client apps can turn off logging to a file if they want to only check -// the fetcher's log property -+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled; -+ (BOOL)isLoggingToFileEnabled; - -// client apps can optionally specify process name and date string used in -// log file names -+ (void)setLoggingProcessName:(NSString *)processName; -+ (NSString *)loggingProcessName; - -+ (void)setLoggingDateStamp:(NSString *)dateStamp; -+ (NSString *)loggingDateStamp; - -// client apps can specify the directory for the log for this specific run, -// typically to match the directory used by another fetcher class, like: -// -// [GTMSessionFetcher setLogDirectoryForCurrentRun:[GTMHTTPFetcher logDirectoryForCurrentRun]]; -// -// Setting this overrides the logging directory, process name, and date stamp when writing -// the log file. -+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun; -+ (NSString *)logDirectoryForCurrentRun; - -// Prunes old log directories that have not been modified since the provided date. -// This will not delete the current run's log directory. -+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)date; - -// internal; called by fetcher -- (void)logFetchWithError:(NSError *)error; -- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream; -- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider: - (GTMSessionFetcherBodyStreamProvider)streamProvider; - -// internal; accessors useful for viewing logs -+ (NSString *)processNameLogPrefix; -+ (NSString *)symlinkNameSuffix; -+ (NSString *)htmlFileName; - -@end - -#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherLogging.m @@ -1,982 +0,0 @@ -/* 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 <sys/stat.h> -#include <unistd.h> - -#import "GTMSessionFetcherLogging.h" - -#ifndef STRIP_GTM_FETCH_LOGGING - #error GTMSessionFetcher headers should have defaulted this if it wasn't already defined. -#endif - -#if !STRIP_GTM_FETCH_LOGGING - -// Sensitive credential strings are replaced in logs with _snip_ -// -// Apps that must see the contents of sensitive tokens can set this to 1 -#ifndef SKIP_GTM_FETCH_LOGGING_SNIPPING -#define SKIP_GTM_FETCH_LOGGING_SNIPPING 0 -#endif - -// If GTMReadMonitorInputStream is available, it can be used for -// capturing uploaded streams of data -// -// We locally declare methods of GTMReadMonitorInputStream so we -// do not need to import the header, as some projects may not have it available -#if !GTMSESSION_BUILD_COMBINED_SOURCES -@interface GTMReadMonitorInputStream : NSInputStream - -+ (instancetype)inputStreamWithStream:(NSInputStream *)input; - -@property (assign) id readDelegate; -@property (assign) SEL readSelector; - -@end -#else -@class GTMReadMonitorInputStream; -#endif // !GTMSESSION_BUILD_COMBINED_SOURCES - -@interface GTMSessionFetcher (GTMHTTPFetcherLoggingUtilities) - -+ (NSString *)headersStringForDictionary:(NSDictionary *)dict; -+ (NSString *)snipSubstringOfString:(NSString *)originalStr - betweenStartString:(NSString *)startStr - endString:(NSString *)endStr; -- (void)inputStream:(GTMReadMonitorInputStream *)stream - readIntoBuffer:(void *)buffer - length:(int64_t)length; - -@end - -@implementation GTMSessionFetcher (GTMSessionFetcherLogging) - -// fetchers come and fetchers go, but statics are forever -static BOOL gIsLoggingEnabled = NO; -static BOOL gIsLoggingToFile = YES; -static NSString *gLoggingDirectoryPath = nil; -static NSString *gLogDirectoryForCurrentRun = nil; -static NSString *gLoggingDateStamp = nil; -static NSString *gLoggingProcessName = nil; - -+ (void)setLoggingDirectory:(NSString *)path { - gLoggingDirectoryPath = [path copy]; -} - -+ (NSString *)loggingDirectory { - if (!gLoggingDirectoryPath) { - NSArray *paths = nil; -#if TARGET_IPHONE_SIMULATOR - // default to a directory called GTMHTTPDebugLogs into a sandbox-safe - // directory that a developer can find easily, the application home - paths = @[ NSHomeDirectory() ]; -#elif TARGET_OS_IPHONE - // Neither ~/Desktop nor ~/Home is writable on an actual iOS, watchOS, or tvOS device. - // Put it in ~/Documents. - paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); -#else - // default to a directory called GTMHTTPDebugLogs in the desktop folder - paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); -#endif - - NSString *desktopPath = paths.firstObject; - if (desktopPath) { - NSString *const kGTMLogFolderName = @"GTMHTTPDebugLogs"; - NSString *logsFolderPath = [desktopPath stringByAppendingPathComponent:kGTMLogFolderName]; - - NSFileManager *fileMgr = [NSFileManager defaultManager]; - BOOL isDir; - BOOL doesFolderExist = [fileMgr fileExistsAtPath:logsFolderPath isDirectory:&isDir]; - if (!doesFolderExist) { - // make the directory - doesFolderExist = [fileMgr createDirectoryAtPath:logsFolderPath - withIntermediateDirectories:YES - attributes:nil - error:NULL]; - if (doesFolderExist) { - // The directory has been created. Exclude it from backups. - NSURL *pathURL = [NSURL fileURLWithPath:logsFolderPath isDirectory:YES]; - [pathURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL]; - } - } - - if (doesFolderExist) { - // it's there; store it in the global - gLoggingDirectoryPath = [logsFolderPath copy]; - } - } - } - return gLoggingDirectoryPath; -} - -+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun { - // Set the path for this run's logs. - gLogDirectoryForCurrentRun = [logDirectoryForCurrentRun copy]; -} - -+ (NSString *)logDirectoryForCurrentRun { - // make a directory for this run's logs, like SyncProto_logs_10-16_01-56-58PM - if (gLogDirectoryForCurrentRun) return gLogDirectoryForCurrentRun; - - NSString *parentDir = [self loggingDirectory]; - NSString *logNamePrefix = [self processNameLogPrefix]; - NSString *dateStamp = [self loggingDateStamp]; - NSString *dirName = [NSString stringWithFormat:@"%@%@", logNamePrefix, dateStamp]; - NSString *logDirectory = [parentDir stringByAppendingPathComponent:dirName]; - - if (gIsLoggingToFile) { - NSFileManager *fileMgr = [NSFileManager defaultManager]; - // Be sure that the first time this app runs, it's not writing to a preexisting folder - static BOOL gShouldReuseFolder = NO; - if (!gShouldReuseFolder) { - gShouldReuseFolder = YES; - NSString *origLogDir = logDirectory; - for (int ctr = 2; ctr < 20; ++ctr) { - if (![fileMgr fileExistsAtPath:logDirectory]) break; - - // append a digit - logDirectory = [origLogDir stringByAppendingFormat:@"_%d", ctr]; - } - } - if (![fileMgr createDirectoryAtPath:logDirectory - withIntermediateDirectories:YES - attributes:nil - error:NULL]) return nil; - } - gLogDirectoryForCurrentRun = logDirectory; - - return gLogDirectoryForCurrentRun; -} - -+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled { - gIsLoggingEnabled = isLoggingEnabled; -} - -+ (BOOL)isLoggingEnabled { - return gIsLoggingEnabled; -} - -+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled { - gIsLoggingToFile = isLoggingToFileEnabled; -} - -+ (BOOL)isLoggingToFileEnabled { - return gIsLoggingToFile; -} - -+ (void)setLoggingProcessName:(NSString *)processName { - gLoggingProcessName = [processName copy]; -} - -+ (NSString *)loggingProcessName { - // get the process name (once per run) replacing spaces with underscores - if (!gLoggingProcessName) { - NSString *procName = [[NSProcessInfo processInfo] processName]; - gLoggingProcessName = [procName stringByReplacingOccurrencesOfString:@" " withString:@"_"]; - } - return gLoggingProcessName; -} - -+ (void)setLoggingDateStamp:(NSString *)dateStamp { - gLoggingDateStamp = [dateStamp copy]; -} - -+ (NSString *)loggingDateStamp { - // We'll pick one date stamp per run, so a run that starts at a later second - // will get a unique results html file - if (!gLoggingDateStamp) { - // produce a string like 08-21_01-41-23PM - - NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - [formatter setFormatterBehavior:NSDateFormatterBehavior10_4]; - [formatter setDateFormat:@"M-dd_hh-mm-ssa"]; - - gLoggingDateStamp = [formatter stringFromDate:[NSDate date]]; - } - return gLoggingDateStamp; -} - -+ (NSString *)processNameLogPrefix { - static NSString *gPrefix = nil; - if (!gPrefix) { - NSString *processName = [self loggingProcessName]; - gPrefix = [[NSString alloc] initWithFormat:@"%@_log_", processName]; - } - return gPrefix; -} - -+ (NSString *)symlinkNameSuffix { - return @"_log_newest.html"; -} - -+ (NSString *)htmlFileName { - return @"aperçu_http_log.html"; -} - -+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)cutoffDate { - NSFileManager *fileMgr = [NSFileManager defaultManager]; - NSURL *parentDir = [NSURL fileURLWithPath:[[self class] loggingDirectory]]; - NSURL *logDirectoryForCurrentRun = - [NSURL fileURLWithPath:[[self class] logDirectoryForCurrentRun]]; - NSError *error; - NSArray *contents = [fileMgr contentsOfDirectoryAtURL:parentDir - includingPropertiesForKeys:@[ NSURLContentModificationDateKey ] - options:0 - error:&error]; - for (NSURL *itemURL in contents) { - if ([itemURL isEqual:logDirectoryForCurrentRun]) continue; - - NSDate *modDate; - if ([itemURL getResourceValue:&modDate - forKey:NSURLContentModificationDateKey - error:&error]) { - if ([modDate compare:cutoffDate] == NSOrderedAscending) { - if (![fileMgr removeItemAtURL:itemURL error:&error]) { - NSLog(@"deleteLogDirectoriesOlderThanDate failed to delete %@: %@", - itemURL.path, error); - } - } - } else { - NSLog(@"deleteLogDirectoriesOlderThanDate failed to get mod date of %@: %@", - itemURL.path, error); - } - } -} - -// formattedStringFromData returns a prettyprinted string for XML or JSON input, -// and a plain string for other input data -- (NSString *)formattedStringFromData:(NSData *)inputData - contentType:(NSString *)contentType - JSON:(NSDictionary **)outJSON { - if (!inputData) return nil; - - // if the content type is JSON and we have the parsing class available, use that - if ([contentType hasPrefix:@"application/json"] && inputData.length > 5) { - // convert from JSON string to NSObjects and back to a formatted string - NSMutableDictionary *obj = [NSJSONSerialization JSONObjectWithData:inputData - options:NSJSONReadingMutableContainers - error:NULL]; - if (obj) { - if (outJSON) *outJSON = obj; - if ([obj isKindOfClass:[NSMutableDictionary class]]) { - // for security and privacy, omit OAuth 2 response access and refresh tokens - if ([obj valueForKey:@"refresh_token"] != nil) { - [obj setObject:@"_snip_" forKey:@"refresh_token"]; - } - if ([obj valueForKey:@"access_token"] != nil) { - [obj setObject:@"_snip_" forKey:@"access_token"]; - } - } - NSData *data = [NSJSONSerialization dataWithJSONObject:obj - options:NSJSONWritingPrettyPrinted - error:NULL]; - if (data) { - NSString *jsonStr = [[NSString alloc] initWithData:data - encoding:NSUTF8StringEncoding]; - return jsonStr; - } - } - } - -#if !TARGET_OS_IPHONE && !GTM_SKIP_LOG_XMLFORMAT - // verify that this data starts with the bytes indicating XML - - NSString *const kXMLLintPath = @"/usr/bin/xmllint"; - static BOOL gHasCheckedAvailability = NO; - static BOOL gIsXMLLintAvailable = NO; - - if (!gHasCheckedAvailability) { - gIsXMLLintAvailable = [[NSFileManager defaultManager] fileExistsAtPath:kXMLLintPath]; - gHasCheckedAvailability = YES; - } - if (gIsXMLLintAvailable - && inputData.length > 5 - && strncmp(inputData.bytes, "<?xml", 5) == 0) { - - // call xmllint to format the data - NSTask *task = [[NSTask alloc] init]; - [task setLaunchPath:kXMLLintPath]; - - // use the dash argument to specify stdin as the source file - [task setArguments:@[ @"--format", @"-" ]]; - [task setEnvironment:@{}]; - - NSPipe *inputPipe = [NSPipe pipe]; - NSPipe *outputPipe = [NSPipe pipe]; - [task setStandardInput:inputPipe]; - [task setStandardOutput:outputPipe]; - - [task launch]; - - [[inputPipe fileHandleForWriting] writeData:inputData]; - [[inputPipe fileHandleForWriting] closeFile]; - - // drain the stdout before waiting for the task to exit - NSData *formattedData = [[outputPipe fileHandleForReading] readDataToEndOfFile]; - - [task waitUntilExit]; - - int status = [task terminationStatus]; - if (status == 0 && formattedData.length > 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 <<n bytes>> 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 <<n bytes>> - 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:@"<html><head><meta http-equiv=\"content-type\" " - "content=\"text/html; charset=UTF-8\"><title>%@ HTTP fetch log %@</title>", - 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:@"<b>%@ ", now]; - - NSString *comment = [self comment]; - if (comment.length > 0) { - [outputHTML appendFormat:@"%@ ", comment]; - } - [outputHTML appendFormat:@"</b><a href='%@'><i>request/response log</i></a><br>", copyableFileName]; - NSTimeInterval elapsed = -self.initialBeginFetchDate.timeIntervalSinceNow; - [outputHTML appendFormat:@"elapsed: %5.3fsec<br>", 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:@"<FONT COLOR='#990066'><i>redirected from %@</i></FONT><br>", - redirectedFromURLString]; - } - [outputHTML appendFormat:@"<b>request:</b> %@ <code>%@</code><br>\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 = - @" <i>authorized, non-SSL</i><FONT COLOR='#FF00FF'> ⚠</FONT> "; - } else { - headerDetails = @" <i>authorized</i>"; - } - } - NSString *cookiesHdr = [requestHeaders objectForKey:@"Cookie"]; - if (cookiesHdr) { - headerDetails = [headerDetails stringByAppendingString:@" <i>cookies</i>"]; - } - NSString *matchHdr = [requestHeaders objectForKey:@"If-Match"]; - if (matchHdr) { - headerDetails = [headerDetails stringByAppendingString:@" <i>if-match</i>"]; - } - matchHdr = [requestHeaders objectForKey:@"If-None-Match"]; - if (matchHdr) { - headerDetails = [headerDetails stringByAppendingString:@" <i>if-none-match</i>"]; - } - [outputHTML appendFormat:@" headers: %d %@<br>", - (int)numberOfRequestHeaders, headerDetails]; - } else { - [outputHTML appendFormat:@" headers: none<br>"]; - } - // 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, <code>%@</code><br>\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 = - @" <i>JSON error:</i> <FONT COLOR='#FF00FF'>%@ %@ ⚑</FONT>"; - 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 = @"<FONT COLOR='#FF00FF'>%ld %@ %@</FONT>"; - 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 = - @"<FONT COLOR='#FF00FF'>response URL:</FONT> <code>%@</code><br>\n"; - responseURLStr = [NSString stringWithFormat:responseURLFormat, [responseURL absoluteString]]; - } - [outputHTML appendFormat:@"<b>response:</b> status %@<br>\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 ? @" <FONT COLOR='#990066'><i>sets cookies</i></FONT>" : @""; - // Indicate if the server is redirecting - NSString *location = [responseHeaders valueForKey:@"Location"]; - BOOL isRedirect = status >= 300 && status <= 399 && location != nil; - NSString *redirectsStr = - isRedirect ? @" <FONT COLOR='#990066'><i>redirects</i></FONT>" : @""; - [outputHTML appendFormat:@" headers: %d %@ %@<br>\n", - (int)numberOfResponseHeaders, cookiesStr, redirectsStr]; - } else { - [outputHTML appendString:@" headers: none<br>\n"]; - } - } - // error - if (error) { - [outputHTML appendFormat:@"<b>Error:</b> %@ <br>\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, <code>%@</code><br>", - responseDataLength, responseMIMEType]; - NSString *const fmt = - @"<a href=\"%@\"><img src='%@' alt='image' style='border:solid thin;max-height:32'></a>\n"; - [outputHTML appendFormat:fmt, responseDataFileName, responseDataFileName]; - } else { - // The response data was XML; link to the xml file - NSString *const fmt = - @" data: %lld bytes, <code>%@</code> <i><a href=\"%@\">%@</a></i>\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, <code>%@</code>\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 - // <span onCopy='window.event.clipboardData.setData(\"Text\", - // \"copyable stuff\");return false;'>Copy here.</span>" - // 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:@"<br><hr><p>"]; - - // 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 = @"<<Uploaded stream log unavailable without GTMReadMonitorInputStream>>"; - 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 = @"<<Uploaded stream log unavailable without GTMReadMonitorInputStream>>"; - NSData *stringData = [str dataUsingEncoding:NSUTF8StringEncoding]; - [self appendLoggedStreamData:stringData]; - return streamProvider; - } - GTMSessionFetcherBodyStreamProvider loggedStreamProvider = - ^(GTMSessionFetcherBodyStreamProviderResponse response) { - streamProvider(^(NSInputStream *bodyStream) { - bodyStream = [self loggedInputStreamForInputStream:bodyStream]; - response(bodyStream); - }); - }; - return loggedStreamProvider; -} - -@end - -@implementation GTMSessionFetcher (GTMSessionFetcherLoggingUtilities) - -- (void)inputStream:(GTMReadMonitorInputStream *)stream - readIntoBuffer:(void *)buffer - length:(int64_t)length { - // append the captured data - NSData *data = [NSData dataWithBytesNoCopy:buffer - length:(NSUInteger)length - freeWhenDone:NO]; - [self appendLoggedStreamData:data]; -} - -#pragma mark Fomatting Utilities - -+ (NSString *)snipSubstringOfString:(NSString *)originalStr - betweenStartString:(NSString *)startStr - endString:(NSString *)endStr { -#if SKIP_GTM_FETCH_LOGGING_SNIPPING - return originalStr; -#else - if (!originalStr) return nil; - - // Find the start string, and replace everything between it - // and the end string (or the end of the original string) with "_snip_" - NSRange startRange = [originalStr rangeOfString:startStr]; - if (startRange.location == NSNotFound) return originalStr; - - // We found the start string - NSUInteger originalLength = originalStr.length; - NSUInteger startOfTarget = NSMaxRange(startRange); - NSRange targetAndRest = NSMakeRange(startOfTarget, originalLength - startOfTarget); - NSRange endRange = [originalStr rangeOfString:endStr - options:0 - range:targetAndRest]; - NSRange replaceRange; - if (endRange.location == NSNotFound) { - // Found no end marker so replace to end of string - replaceRange = targetAndRest; - } else { - // Replace up to the endStr - replaceRange = NSMakeRange(startOfTarget, endRange.location - startOfTarget); - } - NSString *result = [originalStr stringByReplacingCharactersInRange:replaceRange - withString:@"_snip_"]; - return result; -#endif // SKIP_GTM_FETCH_LOGGING_SNIPPING -} - -+ (NSString *)headersStringForDictionary:(NSDictionary *)dict { - // Format the dictionary in http header style, like - // Accept: application/json - // Cache-Control: no-cache - // Content-Type: application/json; charset=utf-8 - // - // Pad the key names, but not beyond 16 chars, since long custom header - // keys just create too much whitespace - NSArray *keys = [dict.allKeys sortedArrayUsingSelector:@selector(compare:)]; - - NSMutableString *str = [NSMutableString string]; - for (NSString *key in keys) { - NSString *value = [dict valueForKey:key]; - if ([key isEqual:@"Authorization"]) { - // Remove OAuth 1 token - value = [[self class] snipSubstringOfString:value - betweenStartString:@"oauth_token=\"" - endString:@"\""]; - - // Remove OAuth 2 bearer token (draft 16, and older form) - value = [[self class] snipSubstringOfString:value - betweenStartString:@"Bearer " - endString:@"\n"]; - value = [[self class] snipSubstringOfString:value - betweenStartString:@"OAuth " - endString:@"\n"]; - - // Remove Google ClientLogin - value = [[self class] snipSubstringOfString:value - betweenStartString:@"GoogleLogin auth=" - endString:@"\n"]; - } - [str appendFormat:@" %@: %@\n", key, value]; - } - return str; -} - -@end - -#endif // !STRIP_GTM_FETCH_LOGGING diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.h @@ -1,193 +0,0 @@ -/* 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<GTMSessionFetcherServiceProtocol> - -// 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<GTMFetcherAuthorizationProtocol> 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<NSURLSessionDelegate>)sessionDelegate; -- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate; - -// The testBlock can inspect its fetcher parameter's request property to -// determine which fetcher is being faked. -@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock; - -@end - -@interface GTMSessionFetcherService (TestingSupport) - -// Convenience methods to create a fetcher service for testing. -// -// Fetchers generated by this mock fetcher service will not perform any -// network operation, but will invoke callbacks and provide the supplied data -// or error to the completion handler. -// -// You can make more customized mocks by setting the test block property of the service -// or fetcher; the test block can inspect the fetcher's request or other properties. -// -// See the description of the testBlock property below. -+ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil - fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil; -+ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil - fakedResponse:(NSHTTPURLResponse *)fakedResponse - fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil; - -// Spin the run loop and discard events (or, if not on the main thread, just sleep the thread) -// until all running and delayed fetchers have completed. -// -// This is only for use in testing or in tools without a user interface. -// -// Synchronous fetches should never be done by shipping apps; they are -// sufficient reason for rejection from the app store. -// -// Returns NO if timed out. -- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds; - -@end - -@interface GTMSessionFetcherService (BackwardsCompatibilityOnly) - -// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves. -// This method is just for compatibility with the old fetcher. -@property(atomic, assign) NSInteger cookieStorageMethod; - -@end - -GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m b/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m @@ -1,1365 +0,0 @@ -/* 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<NSURLSessionDelegate> - -// 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<GTMFetcherAuthorizationProtocol> _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<NSURLSessionDelegate>)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<NSURLSessionDelegate> 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<GTMFetcherAuthorizationProtocol>)authorizer { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _authorizer; - } -} - -- (void)setAuthorizer:(id<GTMFetcherAuthorizationProtocol>)obj { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - if (obj != _authorizer) { - [self detachAuthorizer]; - } - - _authorizer = obj; - } - - // Use the fetcher service for the authorization fetches if the auth - // object supports fetcher services - if ([obj respondsToSelector:@selector(setFetcherService:)]) { -#if GTM_USE_SESSION_FETCHER - [obj setFetcherService:self]; -#else - [obj setFetcherService:(id)self]; -#endif - } -} - -// This should be called inside a @synchronized(self) block except during dealloc. -- (void)detachAuthorizer { - // This method is called by the fetcher service's dealloc and setAuthorizer: - // methods; do not override. - // - // The fetcher service retains the authorizer, and the authorizer has a - // weak pointer to the fetcher service (a non-zeroing pointer for - // compatibility with iOS 4 and Mac OS X 10.5/10.6.) - // - // When this fetcher service no longer uses the authorizer, we want to remove - // the authorizer's dependence on the fetcher service. Authorizers can still - // function without a fetcher service. - if ([_authorizer respondsToSelector:@selector(fetcherService)]) { - id authFetcherService = [_authorizer fetcherService]; - if (authFetcherService == self) { - [_authorizer setFetcherService:nil]; - } - } -} - -- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _callbackQueue; - } // @synchronized(self) -} - -- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _callbackQueue = queue ?: dispatch_get_main_queue(); - } // @synchronized(self) -} - -- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _delegateQueue; - } // @synchronized(self) -} - -- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _delegateQueue = queue ?: [NSOperationQueue mainQueue]; - } // @synchronized(self) -} - -- (NSOperationQueue *)delegateQueue { - // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects - // any custom delegate queue for calling the app. - return nil; -} - -+ (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers { - NSUInteger sum = 0; - for (GTMSessionFetcher *fetcher in fetchers) { - if (!fetcher.usingBackgroundSession) { - ++sum; - } - } - return sum; -} - -@end - -@implementation GTMSessionFetcherService (TestingSupport) - -+ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil - fakedError:(NSError *)fakedErrorOrNil { -#if !GTM_DISABLE_FETCHER_TEST_BLOCK - NSURL *url = [NSURL URLWithString:@"http://example.invalid"]; - NSHTTPURLResponse *fakedResponse = - [[NSHTTPURLResponse alloc] initWithURL:url - statusCode:(fakedErrorOrNil ? 500 : 200) - HTTPVersion:@"HTTP/1.1" - headerFields:nil]; - return [self mockFetcherServiceWithFakedData:fakedDataOrNil - fakedResponse:fakedResponse - fakedError:fakedErrorOrNil]; -#else - GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); - return nil; -#endif // GTM_DISABLE_FETCHER_TEST_BLOCK -} - -+ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil - fakedResponse:(NSHTTPURLResponse *)fakedResponse - fakedError:(NSError *)fakedErrorOrNil { -#if !GTM_DISABLE_FETCHER_TEST_BLOCK - GTMSessionFetcherService *service = [[self alloc] init]; - service.allowedInsecureSchemes = @[ @"http" ]; - service.testBlock = ^(GTMSessionFetcher *fetcherToTest, - GTMSessionFetcherTestResponse testResponse) { - testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil); - }; - return service; -#else - GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled"); - return nil; -#endif // GTM_DISABLE_FETCHER_TEST_BLOCK -} - -#pragma mark Synchronous Wait for Unit Testing - -- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds { - NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds]; - _stoppedFetchersToWaitFor = [NSMutableArray array]; - - BOOL shouldSpinRunLoop = [NSThread isMainThread]; - const NSTimeInterval kSpinInterval = 0.001; - BOOL didTimeOut = NO; - while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) { - didTimeOut = [giveUpDate timeIntervalSinceNow] < 0; - if (didTimeOut) break; - - GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject; - if (stoppedFetcher) { - [_stoppedFetchersToWaitFor removeObject:stoppedFetcher]; - [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval]; - } - - if (shouldSpinRunLoop) { - NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval]; - [[NSRunLoop currentRunLoop] runUntilDate:stopDate]; - } else { - [NSThread sleepForTimeInterval:kSpinInterval]; - } - } - _stoppedFetchersToWaitFor = nil; - - return !didTimeOut; -} - -@end - -@implementation GTMSessionFetcherService (BackwardsCompatibilityOnly) - -- (NSInteger)cookieStorageMethod { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _cookieStorageMethod; - } -} - -- (void)setCookieStorageMethod:(NSInteger)cookieStorageMethod { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _cookieStorageMethod = cookieStorageMethod; - } -} - -@end - -@implementation GTMSessionFetcherSessionDelegateDispatcher { - __weak GTMSessionFetcherService *_parentService; - NSURLSession *_session; - - // The task map maps NSURLSessionTasks to GTMSessionFetchers - NSMutableDictionary *_taskToFetcherMap; - // The discard timer will invalidate sessions after the session's last task completes. - NSTimer *_discardTimer; - NSTimeInterval _discardInterval; -} - -@synthesize discardInterval = _discardInterval, - session = _session; - -- (instancetype)init { - [self doesNotRecognizeSelector:_cmd]; - return nil; -} - -- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService - sessionDiscardInterval:(NSTimeInterval)discardInterval { - self = [super init]; - if (self) { - _discardInterval = discardInterval; - _parentService = parentService; - } - return self; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ %p %@ %@", - [self class], self, - _session ?: @"<no session>", - _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @"<no tasks>"]; -} - -- (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<NSURLSessionTaskDelegate> 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<NSURLSessionTaskDelegate> 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<NSURLSessionTaskDelegate> 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<NSURLSessionTaskDelegate> 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<NSURLSessionTaskDelegate> 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<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask]; - [fetcher URLSession:session - dataTask:dataTask - didReceiveResponse:response - completionHandler:handler]; -} - -- (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask -didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { - id<NSURLSessionDataDelegate> 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<NSURLSessionDataDelegate> 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<NSURLSessionDataDelegate> 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<NSURLSessionDownloadDelegate> 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<NSURLSessionDownloadDelegate> 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<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask]; - [fetcher URLSession:session - downloadTask:downloadTask - didResumeAtOffset:fileOffset - expectedTotalBytes:expectedTotalBytes]; -} - -@end diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h b/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.h @@ -1,166 +0,0 @@ -/* Copyright 2014 Google Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// GTMSessionUploadFetcher implements Google's resumable upload protocol. - -// -// This subclass of GTMSessionFetcher simulates the series of fetches -// needed for chunked upload as a single fetch operation. -// -// Protocol document: TBD -// -// To the client, the only fetcher that exists is this class; the subsidiary -// fetchers needed for uploading chunks are not visible (though the most recent -// chunk fetcher may be accessed via the -activeFetcher or -chunkFetcher methods, and -// -responseHeaders and -statusCode reflect results from the most recent chunk -// fetcher.) -// -// Chunk fetchers are discarded as soon as they have completed. -// -// The protocol also allows for a cancellation notification request to be sent to the -// server to allow discarding of the currently uploaded data and this will be sent -// automatically upon calling stopFetching if the upload has already started. -// -// Note: Unlike the fetcher superclass, the methods of GTMSessionUploadFetcher should -// only be used from the main thread until further work is done to make this subclass -// thread-safe. - -#import "GTMSessionFetcher.h" -#import "GTMSessionFetcherService.h" - -GTM_ASSUME_NONNULL_BEGIN - -// The value to use for file size parameters when the file size is not yet known. -extern int64_t const kGTMSessionUploadFetcherUnknownFileSize; - -// Unless an application knows it needs a smaller chunk size, it should use the standard -// chunk size, which sends the entire file as a single chunk to minimize upload overhead. -// Setting an explicit chunk size that comfortably fits in memory is advisable for large -// uploads. -extern int64_t const kGTMSessionUploadFetcherStandardChunkSize; - -// When uploading requires data buffer allocations (such as uploading from an NSData or -// an NSFileHandle) this is the maximum buffer size that will be created by the fetcher. -extern int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize; - -// Notification that the upload location URL was provided by the server. -extern NSString *const kGTMSessionFetcherUploadLocationObtainedNotification; - -// Block to provide data during uploads. -// -// Response data may be allocated with dataWithBytesNoCopy:length:freeWhenDone: for efficiency, -// and released after the response block returns. -// -// If the length of the file being uploaded is unknown or already set, send -// kGTMSessionUploadFetcherUnknownFileSize for |fullUploadLength|. Otherwise, set |fullUploadLength| -// to its proper value. -// -// Pass nil as the data (and optionally an NSError) for a failure. -typedef void (^GTMSessionUploadFetcherDataProviderResponse)(NSData * GTM_NULLABLE_TYPE data, - int64_t fullUploadLength, - NSError * GTM_NULLABLE_TYPE error); -// Do not call the response with an NSData object with less data than the requested length unless -// you are passing the fullUploadLength to the fetcher for the first time and it is the last chunk -// of data in the file being uploaded. -typedef void (^GTMSessionUploadFetcherDataProvider)(int64_t offset, int64_t length, - GTMSessionUploadFetcherDataProviderResponse response); - -// Block to be notified about the final status of the cancellation request started in stopFetching. -// -// |fetcher| will be the cancel request that was sent to the server, or nil if stopFetching is not -// going to send a cancel request. If |fetcher| is provided, the other parameters correspond to the -// completion handler of the cancellation request fetcher. -typedef void (^GTMSessionUploadFetcherCancellationHandler)( - GTMSessionFetcher * GTM_NULLABLE_TYPE fetcher, - NSData * GTM_NULLABLE_TYPE data, - NSError * GTM_NULLABLE_TYPE error); - -@interface GTMSessionUploadFetcher : GTMSessionFetcher - -// Create an upload fetcher specifying either the request or the resume location URL, -// then set an upload data source using one of these: -// -// setUploadFileURL: -// setUploadDataLength:provider: -// setUploadFileHandle: -// setUploadData: - -+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize - fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil; - -+ (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; - -// Invoked as part of the stop fetching process. Invoked immediately if there is no upload in -// progress, otherwise invoked with the results of the attempt to notify the server that the -// upload will not continue. -// -// Unlike other callbacks, since this is related specifically to the stopFetching flow it is not -// cleared by stopFetching. It will instead clear itself after it is invoked or if the completion -// has occured before stopFetching is called. -@property(atomic, copy, GTM_NULLABLE) GTMSessionUploadFetcherCancellationHandler - cancellationHandler; - -// Exposed for testing only. -@property(atomic, readonly, GTM_NULLABLE) dispatch_queue_t delegateCallbackQueue; -@property(atomic, readonly, GTM_NULLABLE) GTMSessionFetcherCompletionHandler delegateCompletionHandler; - -@end - -@interface GTMSessionFetcher (GTMSessionUploadFetcherMethods) - -@property(readonly, GTM_NULLABLE) GTMSessionUploadFetcher *parentUploadFetcher; - -@end - -GTM_ASSUME_NONNULL_END diff --git a/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m b/Pods/GTMSessionFetcher/Source/GTMSessionUploadFetcher.m @@ -1,1954 +0,0 @@ -/* 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 kGTMSessionXGoogUploadProtocolResumable = @"resumable"; -static NSString *const kGTMSessionHeaderXGoogUploadSizeReceived = @"X-Goog-Upload-Size-Received"; -static NSString *const kGTMSessionHeaderXGoogUploadStatus = @"X-Goog-Upload-Status"; -static NSString *const kGTMSessionHeaderXGoogUploadURL = @"X-Goog-Upload-URL"; - -// Property of chunk fetchers identifying the parent upload fetcher. Non-retained NSValue. -static NSString *const kGTMSessionUploadFetcherChunkParentKey = @"_uploadFetcherChunkParent"; - -int64_t const kGTMSessionUploadFetcherUnknownFileSize = -1; - -int64_t const kGTMSessionUploadFetcherStandardChunkSize = (int64_t)LLONG_MAX; - -#if TARGET_OS_IPHONE -int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 10 * 1024 * 1024; // 10 MB for iOS, watchOS, tvOS -#else -int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize = 100 * 1024 * 1024; // 100 MB for macOS -#endif - -typedef NS_ENUM(NSUInteger, GTMSessionUploadFetcherStatus) { - kStatusUnknown, - kStatusActive, - kStatusFinal, - kStatusCancelled, -}; - -NSString *const kGTMSessionFetcherUploadLocationObtainedNotification = - @"kGTMSessionFetcherUploadLocationObtainedNotification"; - -#if !GTMSESSION_BUILD_COMBINED_SOURCES -@interface GTMSessionFetcher (ProtectedMethods) - -// Access to non-public method on the parent fetcher class. -- (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks; -- (void)createSessionIdentifierWithMetadata:(NSDictionary *)metadata; -- (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(id)target - didFinishSelector:(SEL)finishedSelector; -- (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue - afterUserStopped:(BOOL)afterStopped - block:(void (^)(void))block; -- (NSTimer *)retryTimer; -- (void)beginFetchForRetry; - -@property(readwrite, strong) NSData *downloadedData; -- (void)releaseCallbacks; - -- (NSInteger)statusCodeUnsynchronized; - -- (BOOL)userStoppedFetching; - -@end -#endif // !GTMSESSION_BUILD_COMBINED_SOURCES - -@interface GTMSessionUploadFetcher () - -// Changing readonly to readwrite. -@property(atomic, strong, readwrite) NSURLRequest *lastChunkRequest; -@property(atomic, readwrite, assign) int64_t currentOffset; - -// Internal properties. -@property(strong, atomic, GTM_NULLABLE) GTMSessionFetcher *fetcherInFlight; // Synchronized on self. - -@property(assign, atomic, getter=isSubdataGenerating) BOOL subdataGenerating; -@property(assign, atomic) BOOL shouldInitiateOffsetQuery; -@property(assign, atomic) int64_t uploadGranularity; - -@end - -@implementation GTMSessionUploadFetcher { - GTMSessionFetcher *_chunkFetcher; - - // We'll call through to the delegate's completion handler. - GTMSessionFetcherCompletionHandler _delegateCompletionHandler; - dispatch_queue_t _delegateCallbackQueue; - - // The initial fetch's body length and bytes actually sent are - // needed for calculating progress during subsequent chunk uploads - int64_t _initialBodyLength; - int64_t _initialBodySent; - - // The upload server address for the chunks of this upload session. - NSURL *_uploadLocationURL; - - // _uploadData, _uploadDataProvider, or _uploadFileHandle may be set, but only one. - NSData *_uploadData; - NSFileHandle *_uploadFileHandle; - GTMSessionUploadFetcherDataProvider _uploadDataProvider; - NSURL *_uploadFileURL; - int64_t _uploadFileLength; - NSString *_uploadMIMEType; - int64_t _chunkSize; - int64_t _uploadGranularity; - BOOL _isPaused; - BOOL _isRestartedUpload; - BOOL _shouldInitiateOffsetQuery; - - // Tied to useBackgroundSession property, since this property is applicable to chunk fetchers. - BOOL _useBackgroundSessionOnChunkFetchers; - - // We keep the latest offset into the upload data just for progress reporting. - int64_t _currentOffset; - - NSDictionary *_recentChunkReponseHeaders; - NSInteger _recentChunkStatusCode; - - // For waiting, we need to know the fetcher in flight, if any, and if subdata generation - // is in progress. - GTMSessionFetcher *_fetcherInFlight; - BOOL _isSubdataGenerating; - BOOL _isCancelInFlight; - - GTMSessionUploadFetcherCancellationHandler _cancellationHandler; -} - -+ (void)load { - [self uploadFetchersForBackgroundSessions]; -} - -+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize - fetcherService:(GTMSessionFetcherService *)fetcherService { - GTMSessionUploadFetcher *fetcher = [self uploadFetcherWithRequest:request - fetcherService:fetcherService]; - [fetcher setLocationURL:nil - uploadMIMEType:uploadMIMEType - chunkSize:chunkSize]; - 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:kGTMSessionXGoogUploadProtocolResumable - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadProtocol]; - [mutableRequest setValue:@"start" - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadCommand]; - [mutableRequest setValue:_uploadMIMEType - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentType]; - [mutableRequest setValue:@([self fullUploadLength]).stringValue - forHTTPHeaderField:kGTMSessionHeaderXGoogUploadContentLength]; - - NSString *method = mutableRequest.HTTPMethod; - if (method == nil || [method caseInsensitiveCompare:@"GET"] == NSOrderedSame) { - [mutableRequest setHTTPMethod:@"POST"]; - } - - // Ensure the user agent header identifies this to the upload server as a - // GTMSessionUploadFetcher client. The /1 can be incremented in the unlikely circumstance - // we need to make a bug fix in the client that the server can recognize. - NSString *const kUserAgentStub = @"(GTMSUF/1)"; - NSString *userAgent = [mutableRequest valueForHTTPHeaderField:@"User-Agent"]; - if (userAgent == nil - || [userAgent rangeOfString:kUserAgentStub].location == NSNotFound) { - if (userAgent.length == 0) { - userAgent = GTMFetcherStandardUserAgentString(nil); - } - userAgent = [userAgent stringByAppendingFormat:@" %@", kUserAgentStub]; - [mutableRequest setValue:userAgent forHTTPHeaderField:@"User-Agent"]; - } - [self setRequest:mutableRequest]; -} - -- (void)setLocationURL:(NSURL * GTM_NULLABLE_TYPE)location - uploadMIMEType:(NSString *)uploadMIMEType - chunkSize:(int64_t)chunkSize { - @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); - - @try { - resultData = [uploadData subdataWithRange:range]; - } - @catch (NSException *exception) { - NSString *errorMessage = exception.description; - GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); - response(nil, - kGTMSessionUploadFetcherUnknownFileSize, - [self uploadChunkUnavailableErrorWithDescription:errorMessage]); - return; - } - } - response(resultData, kGTMSessionUploadFetcherUnknownFileSize, nil); - return; - } - NSURL *uploadFileURL = self.uploadFileURL; - if (uploadFileURL) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self generateChunkSubdataFromFileURL:uploadFileURL - offset:offset - length:length - response:response]; - }); - return; - } - GTMSESSION_ASSERT_DEBUG(_uploadFileHandle, @"Unexpectedly missing upload data package"); - NSFileHandle *uploadFileHandle = self.uploadFileHandle; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self generateChunkSubdataFromFileHandle:uploadFileHandle - offset:offset - length:length - response:response]; - }); -} - -- (void)generateChunkSubdataFromFileHandle:(NSFileHandle *)fileHandle - offset:(int64_t)offset - length:(int64_t)length - response:(GTMSessionUploadFetcherDataProviderResponse)response { - NSData *resultData; - NSError *error; - @try { - [fileHandle seekToFileOffset:(unsigned long long)offset]; - resultData = [fileHandle readDataOfLength:(NSUInteger)length]; - } - @catch (NSException *exception) { - GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileHandle failed to read, %@", exception); - error = [self uploadChunkUnavailableErrorWithDescription:exception.description]; - } - // The response always re-dispatches to the main thread, so we skip doing that here. - response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error); -} - -- (void)generateChunkSubdataFromFileURL:(NSURL *)fileURL - offset:(int64_t)offset - length:(int64_t)length - response:(GTMSessionUploadFetcherDataProviderResponse)response { - GTMSessionCheckNotSynchronized(self); - - NSData *resultData; - NSError *error; - int64_t fullUploadLength = [self fullUploadLength]; - NSData *mappedData = - [NSData dataWithContentsOfURL:fileURL - options:NSDataReadingMappedAlways + NSDataReadingUncached - error:&error]; - if (!mappedData) { - // We could not create an NSData by memory-mapping the file. -#if TARGET_IPHONE_SIMULATOR - // NSTemporaryDirectory() can differ in the simulator between app restarts, - // yet the contents for the new path remains unchanged, so try the latest temp path. - if ([error.domain isEqual:NSCocoaErrorDomain] && (error.code == NSFileReadNoSuchFileError)) { - NSString *filename = [fileURL lastPathComponent]; - NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; - NSURL *newFileURL = [NSURL fileURLWithPath:filePath]; - if (![newFileURL isEqual:fileURL]) { - [self generateChunkSubdataFromFileURL:newFileURL - offset:offset - length:length - response:response]; - return; - } - } -#endif - - // If the file is just too large to create an NSData for, or if for some other reason we can't - // map it, create an NSFileHandle instead to read a subset into an NSData. -#if DEBUG - NSNumber *fileSizeNum; - BOOL hasFileSize = [fileURL getResourceValue:&fileSizeNum forKey:NSURLFileSizeKey error:NULL]; - GTMSESSION_LOG_DEBUG(@"Note: uploadFileURL is falling back to creating upload chunks by reading" - @" an NSFileHandle since uploadFileURL failed to map the upload file," - @" file size %@, %@", - hasFileSize ? fileSizeNum : @"unknown", error); -#endif - - NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL - error:&error]; - if (fileHandle != nil) { - [self generateChunkSubdataFromFileHandle:fileHandle - offset:offset - length:length - response:response]; - return; - } - GTMSESSION_ASSERT_DEBUG(NO, @"uploadFileURL failed to read, %@", error); - // Fall through with the error. - } else { - // Successfully created an NSData by memory-mapping the file. - if ((NSUInteger)(offset + length) > mappedData.length) { - NSString *errorMessage = [NSString stringWithFormat: - @"Range invalid for upload data. offset: %lld\tlength: %lld\tdataLength: %lld\texpected UploadLength: %lld", - offset, length, (long long)mappedData.length, fullUploadLength]; - GTMSESSION_ASSERT_DEBUG(NO, @"%@", errorMessage); - response(nil, - kGTMSessionUploadFetcherUnknownFileSize, - [self uploadChunkUnavailableErrorWithDescription:errorMessage]); - return; - } - if (offset > 0 || length < fullUploadLength) { - NSRange range = NSMakeRange((NSUInteger)offset, (NSUInteger)length); - resultData = [mappedData subdataWithRange:range]; - } else { - resultData = mappedData; - } - } - // The response always re-dispatches to the main thread, so we skip re-dispatching here. - response(resultData, kGTMSessionUploadFetcherUnknownFileSize, error); -} - -- (NSError *)uploadChunkUnavailableErrorWithDescription:(NSString *)description { - // The description in the userInfo is intended as a clue to programmers, not - // for client code to examine or rely on. - NSDictionary *userInfo = @{ @"description" : description }; - return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain - code:GTMSessionFetcherErrorUploadChunkUnavailable - userInfo:userInfo]; -} - -- (NSError *)prematureFailureErrorWithUserInfo:(NSDictionary *)userInfo { - // An error for if we get an unexpected status from the upload server or - // otherwise cannot continue. This is an issue beyond the upload protocol; - // there's no way the client can do anything useful except give up. - NSError *error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain - code:501 // Not implemented - userInfo:userInfo]; - return error; -} - -+ (GTMSessionUploadFetcherStatus)uploadStatusFromResponseHeaders:(NSDictionary *)responseHeaders { - NSString *statusString = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus]; - if ([statusString isEqual:@"active"]) { - return kStatusActive; - } - if ([statusString isEqual:@"final"]) { - return kStatusFinal; - } - if ([statusString isEqual:@"cancelled"]) { - return kStatusCancelled; - } - return kStatusUnknown; -} - -#pragma mark Method overrides affecting the initial fetch only - -- (void)setCompletionHandler:(GTMSessionFetcherCompletionHandler)handler { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _delegateCompletionHandler = handler; - } -} - -- (void)setDelegateCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _delegateCallbackQueue = queue; - } -} - -- (dispatch_queue_t GTM_NULLABLE_TYPE)delegateCallbackQueue { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _delegateCallbackQueue; - } -} - -- (BOOL)isRestartedUpload { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _isRestartedUpload; - } -} - -- (GTMSessionFetcher * GTM_NULLABLE_TYPE)chunkFetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _chunkFetcher; - } -} - -- (void)setChunkFetcher:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _chunkFetcher = fetcher; - } -} - -- (void)setFetcherInFlight:(GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcher { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _fetcherInFlight = fetcher; - } -} - -- (GTMSessionFetcher * GTM_NULLABLE_TYPE)fetcherInFlight { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _fetcherInFlight; - } -} - -- (void)setCancellationHandler:(GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE) - cancellationHandler { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - _cancellationHandler = cancellationHandler; - } -} - -- (GTMSessionUploadFetcherCancellationHandler GTM_NULLABLE_TYPE)cancellationHandler { - @synchronized(self) { - GTMSessionMonitorSynchronized(self); - - return _cancellationHandler; - } -} - -- (void)beginFetchForRetry { - GTMSessionCheckNotSynchronized(self); - - // Override the superclass to reset the initial body length and fetcher-in-flight, - // then call the superclass implementation. - [self setInitialBodyLength:[self bodyLength]]; - - GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@", - self.fetcherInFlight); - self.fetcherInFlight = self; - [super beginFetchForRetry]; -} - -- (void)beginFetchWithCompletionHandler:(GTMSessionFetcherCompletionHandler)handler { - GTMSessionCheckNotSynchronized(self); - - [self setInitialBodyLength:[self bodyLength]]; - - // We'll hold onto the superclass's callback queue so we can invoke the handler - // even after the superclass has released the queue and its callback handler, as - // happens during auth failure. - [self setDelegateCallbackQueue:self.callbackQueue]; - self.completionHandler = handler; - - if ([self isRestartedUpload]) { - // When restarting an upload, we know the destination location for chunk fetches, - // but we need to query to find the initial offset. - if (![self isPaused]) { - [self sendQueryForUploadOffsetWithFetcherProperties:self.properties]; - } - return; - } - // We don't want to call into the client's completion block immediately - // after the finish of the initial connection (the delegate is called only - // when uploading finishes), so we substitute our own completion block to be - // called when the initial connection finishes - GTMSESSION_ASSERT_DEBUG(self.fetcherInFlight == nil, @"unexpected fetcher in flight: %@", - self.fetcherInFlight); - - self.fetcherInFlight = self; - [super beginFetchWithCompletionHandler:^(NSData *data, NSError *error) { - self.fetcherInFlight = nil; - // callback - - BOOL hasTestBlock = (self.testBlock != nil); - if (![self isRestartedUpload] && !hasTestBlock) { - if (error == nil) { - [self beginChunkFetches]; - } else { - if ([self retryTimer] == nil) { - [self invokeFinalCallbackWithData:nil - error:error - shouldInvalidateLocation:YES]; - } - } - } else { - // If there was no initial request, then this fetch is resuming some - // other uploadFetcher's initial request, and the superclass's connection - // is never used, so at this point we call the user's actual completion - // block. - if (!hasTestBlock) { - [self invokeFinalCallbackWithData:data - error:error - shouldInvalidateLocation:YES]; - } else { - // There was a test block, so we won't do chunk fetches, but we simulate obtaining - // the data to be uploaded from the upload data provider block or the file handle, - // and then call back. - [self generateChunkSubdataWithOffset:0 - length:[self fullUploadLength] - response:^(NSData *generateData, int64_t fullUploadLength, NSError *generateError) { - [self invokeFinalCallbackWithData:data - error:error - shouldInvalidateLocation:YES]; - }]; - } - } - }]; -} - -- (void)beginChunkFetches { - GTMSessionCheckNotSynchronized(self); - -#if DEBUG - // The initial response of the resumable upload protocol should have an - // empty body - // - // This assert typically happens because the upload create/edit link URL was - // not supplied with the request, and the server is thus expecting a non- - // resumable request/response. - if (self.downloadedData.length > 0) { - NSData *downloadedData = self.downloadedData; - NSString *str = [[NSString alloc] initWithData:downloadedData - encoding:NSUTF8StringEncoding]; - #pragma unused(str) - GTMSESSION_ASSERT_DEBUG(NO, @"unexpected response data (uploading to the wrong URL?)\n%@", str); - } -#endif - - // We need to get the upload URL from the location header to continue. - NSDictionary *responseHeaders = [self responseHeaders]; - - [self retrieveUploadChunkGranularityFromResponseHeaders:responseHeaders]; - - GTMSessionUploadFetcherStatus uploadStatus = - [[self class] uploadStatusFromResponseHeaders:responseHeaders]; - GTMSESSION_ASSERT_DEBUG(uploadStatus != kStatusUnknown, - @"beginChunkFetches has unexpected upload status for headers %@", responseHeaders); - - BOOL isPrematureStop = (uploadStatus == kStatusFinal) || (uploadStatus == kStatusCancelled); - - NSString *uploadLocationURLStr = [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadURL]; - BOOL hasUploadLocation = (uploadLocationURLStr.length > 0); - - if (isPrematureStop || !hasUploadLocation) { - GTMSESSION_ASSERT_DEBUG(NO, @"Premature failure: upload-status:\"%@\" location:%@", - [responseHeaders objectForKey:kGTMSessionHeaderXGoogUploadStatus], uploadLocationURLStr); - // We cannot continue since we do not know the location to use - // as our upload destination. - NSDictionary *userInfo = nil; - NSData *downloadedData = self.downloadedData; - if (downloadedData.length > 0) { - userInfo = @{ kGTMSessionFetcherStatusDataKey : downloadedData }; - } - NSError *failureError = [self prematureFailureErrorWithUserIn