/* 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.
//
// Custom headers can also be provided per-request via an instance of `GTMFetcherDecoratorProtocol`
// passed to `-[GTMSessionFetcherService addDecorator:]`.
//
// 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, 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 more verbose logging from NSURLSession during debugging.
#if 0
#define GTMSESSION_LOG_DEBUG_VERBOSE(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__)
#else
#define GTMSESSION_LOG_DEBUG_VERBOSE(...)
#endif

// 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 !defined(GTM_BACKGROUND_TASK_FETCHING) && \
    (TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_MACCATALYST)
#define GTM_BACKGROUND_TASK_FETCHING 1
#endif

#ifdef __cplusplus
extern "C" {
#endif

// When creating background sessions to perform out-of-process uploads and
// downloads, on app launch any background sessions must be reconnected in
// order to receive events that occurred while the app was not running.
//
// The fetcher will automatically attempt to recreate the sessions on app
// start, but doing so reads from NSUserDefaults. This may have launch-time
// performance impacts.
//
// To avoid launch performance impacts, on iPhone/iPad with iOS 13+ the
// GTMSessionFetcher class will register for the app launch notification and
// perform the reconnect then.
//
// Apps targeting Mac or older iOS SDKs can opt into the new behavior by defining
// GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH=1.
//
// Apps targeting new SDKs can force the old behavior by defining
// GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH = 0.
#ifndef GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH
// Default to the on-launch behavior for iOS 13+.
#if TARGET_OS_IOS && defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
#define GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH 1
#else
#define GTMSESSION_RECONNECT_BACKGROUND_SESSIONS_ON_LAUNCH 0
#endif
#endif

NS_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 *_Nullable data,
                                                   NSError *_Nullable 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 *_Nullable credential);
typedef void (^GTMSessionFetcherChallengeBlock)(
    GTMSessionFetcher *fetcher, NSURLAuthenticationChallenge *challenge,
    GTMSessionFetcherChallengeDispositionBlock dispositionBlock);
typedef void (^GTMSessionFetcherWillRedirectResponse)(NSURLRequest *_Nullable redirectedRequest);
typedef void (^GTMSessionFetcherWillRedirectBlock)(NSHTTPURLResponse *redirectResponse,
                                                   NSURLRequest *redirectRequest,
                                                   GTMSessionFetcherWillRedirectResponse response);
typedef void (^GTMSessionFetcherAccumulateDataBlock)(NSData *_Nullable buffer);
typedef void (^GTMSessionFetcherSimulateByteTransferBlock)(NSData *_Nullable 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 *_Nullable cachedResponse);
typedef void (^GTMSessionFetcherWillCacheURLResponseBlock)(
    NSCachedURLResponse *proposedResponse,
    GTMSessionFetcherWillCacheURLResponseResponse responseBlock);
typedef void (^GTMSessionFetcherRetryResponse)(BOOL shouldRetry);
typedef void (^GTMSessionFetcherRetryBlock)(BOOL suggestedWillRetry, NSError *_Nullable error,
                                            GTMSessionFetcherRetryResponse response);

API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0))
typedef void (^GTMSessionFetcherMetricsCollectionBlock)(NSURLSessionTaskMetrics *metrics);

typedef void (^GTMSessionFetcherTestResponse)(NSHTTPURLResponse *_Nullable response,
                                              NSData *_Nullable data, NSError *_Nullable error);
typedef void (^GTMSessionFetcherTestBlock)(GTMSessionFetcher *fetcherToTest,
                                           GTMSessionFetcherTestResponse testResponse);

void GTMSessionFetcherAssertValidSelector(id _Nullable obj, SEL _Nullable 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 *_Nullable 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.
//
// The application version may be overridden by adding to the bundle's
// Info.plist a "GTMUserAgentVersion" key.
//
// If no bundle ID or override is available, the process name preceded
// by "proc_" is used.
NSString *GTMFetcherApplicationIdentifier(NSBundle *_Nullable 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 *_Nullable GTMDataFromInputStream(NSInputStream *inputStream, NSError **outError);

#ifdef __cplusplus
}  // extern "C"
#endif

// Completion handler passed to -[GTMFetcherDecoratorProtocol fetcherWillStart:completionHandler:].
typedef void (^GTMFetcherDecoratorFetcherWillStartCompletionHandler)(NSURLRequest *_Nullable,
                                                                     NSError *_Nullable);

// Allows intercepting a request and optionally modifying it before the request (or a retry)
// is sent. See `-[GTMSessionFetcherService addDecorator:]` and `-[GTMSessionFetcherService
// removeDecorator:]`.
//
// Decorator methods must be thread-safe, as they might be invoked on any queue.
@protocol GTMFetcherDecoratorProtocol <NSObject>

// Invoked just before a fetcher's request starts.
//
// After the decorator's work is complete, the decorator must invoke `handler(request, error)`
// either synchronously or asynchronously (on any queue).
//
// If no changes are to be made, pass `nil` for both `request` and `error`.
//
// Otherwise, if `error` is non-nil, then the fetcher is stopped with the given error, and any
// further decorators' `-fetcherWillStart:completionHandler:` methods are not invoked.
//
// Otherwise, the decorator may use `[fetcher.request mutableCopy]`, make changes to the mutable
// copy of the request, and pass the result to the handler via the `request` parameter.
//
// To distinguish the initial fetch from retries, the decorator can look at `fetcher.retryCount`.
//
// This method must not block the caller (e.g., performing synchronous I/O). Perform any blocking
// work or I/O on a different queue, then invoke `handler` with the results after the blocking work
// completes.
- (void)fetcherWillStart:(GTMSessionFetcher *)fetcher
       completionHandler:(GTMFetcherDecoratorFetcherWillStartCompletionHandler)handler;

// Invoked just after a fetcher's request finishes (either on success or on failure).
//
// After the decorator's work is complete, the decorator must invoke `handler()` either
// synchronously or asynchronously (on any queue).
//
// To access the result of the fetch, the decorator can look at `fetcher.response`.
//
// This method must not block the caller (e.g., performing synchronous I/O). Perform any blocking
// work or I/O on a different queue, then invoke `handler` with the results after the blocking work
// completes.
- (void)fetcherDidFinish:(GTMSessionFetcher *)fetcher
                withData:(nullable NSData *)data
                   error:(nullable NSError *)error
       completionHandler:(void (^)(void))handler;

@end

// 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;
- (nullable NSURLSession *)session;
- (nullable NSURLSession *)sessionForFetcherCreation;
- (nullable id<NSURLSessionDelegate>)sessionDelegate;
- (nullable NSDate *)stoppedAllFetchersDate;

@property(atomic, readonly, strong, nullable) NSOperationQueue *delegateQueue;

@optional
// This property is optional, for now, to enable releasing the feature without breaking existing
// code that fakes the service but doesn't implement this.
@property(atomic, readonly, strong, nullable) NSArray<id<GTMFetcherDecoratorProtocol>> *decorators;

@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:(nullable NSMutableURLRequest *)request
                delegate:(id)delegate
       didFinishSelector:(SEL)sel;

- (void)stopAuthorization;

- (void)stopAuthorizationForRequest:(NSURLRequest *)request;

- (BOOL)isAuthorizingRequest:(NSURLRequest *)request;

- (BOOL)isAuthorizedRequest:(NSURLRequest *)request;

@property(atomic, strong, readonly, nullable) NSString *userEmail;

@optional

// Indicate if authorization may be attempted. Even if this succeeds,
// authorization may fail if the user's permissions have been revoked.
@property(atomic, readonly) BOOL canAuthorize;

// For development only, allow authorization of non-SSL requests, allowing
// transmission of the bearer token unencrypted.
@property(atomic, assign) BOOL shouldAuthorizeAllRequests;

- (void)authorizeRequest:(nullable NSMutableURLRequest *)request
       completionHandler:(void (^)(NSError *_Nullable error))handler;

@property(atomic, weak, nullable) id<GTMSessionFetcherServiceProtocol> fetcherService;

- (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:(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;
+ (nullable instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier;

// Returns an array of currently active fetchers for background sessions,
// both restarted and newly created ones.
+ (NSArray<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:(nullable NSURLRequest *)request
                  configuration:(nullable NSURLSessionConfiguration *)configuration;

// The fetcher's request.  This may not be set after beginFetch has been invoked. The request
// may change due to redirects.
@property(atomic, strong, 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:(nullable NSString *)value forHTTPHeaderField:(NSString *)field;

// Data used for resuming a download task.
@property(atomic, readonly, 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, 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, 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, nullable) NSURLSession *session;

// The task in flight.
@property(atomic, readonly, nullable) NSURLSessionTask *sessionTask;

// The background session identifier.
@property(atomic, readonly, nullable) NSString *sessionIdentifier;

// Indicates a fetcher created to finish a background session task.
@property(atomic, readonly) BOOL wasCreatedFromBackgroundSession;

// Indicates the client has committed to reconnecting this background session when
// the app restarts. If this value is YES, the session fetcher will not automatically
// call beginFetchWithCompletionHandler: on the restored fetcher on app start, and
// the session will not handle system events until the client explicitly does.
@property(atomic, assign) BOOL clientWillReconnectBackgroundSession;

// 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, nullable) NSDictionary<NSString *, NSString *> *sessionUserInfo;

// The human-readable description to be assigned to the task.
@property(atomic, copy, 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.
- (nullable NSDictionary<NSString *, NSString *> *)sessionIdentifierMetadata;

#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
// The app should pass to this method the completion handler passed in the app delegate method
// application:handleEventsForBackgroundURLSession:completionHandler:
+ (void)application:(UIApplication *)application
    handleEventsForBackgroundURLSession:(NSString *)identifier
                      completionHandler:(GTMSessionFetcherSystemCompletionHandler)completionHandler;
#endif

// Indicate that a newly created session should be a background session.
// A new session identifier will be created by the fetcher.
//
// Warning:  The only thing background sessions are for is rare download
// of huge, batched files of data. And even just for those, there's a lot
// of pain and hackery needed to get transfers to actually happen reliably
// with background sessions.
//
// Don't try to upload or download in many background sessions, since the system
// will impose an exponentially increasing time penalty to prevent the app from
// getting too much background execution time.
//
// References:
//
//   "Moving to Fewer, Larger Transfers"
//   https://forums.developer.apple.com/thread/14853
//
//   "NSURLSession’s Resume Rate Limiter"
//   https://forums.developer.apple.com/thread/14854
//
//   "Background Session Task state persistence"
//   https://forums.developer.apple.com/thread/11554
//
@property(atomic, assign) BOOL useBackgroundSession;

// Indicates if the fetcher was started using a background session.
@property(atomic, readonly, getter=isUsingBackgroundSession) BOOL usingBackgroundSession;

// Indicates if uploads should use an upload task.  This is always set for file or stream-provider
// bodies, but may be set explicitly for NSData bodies.
@property(atomic, assign) BOOL useUploadTask;

// Indicates that the fetcher is using a session that may be shared with other fetchers.
@property(atomic, readonly) BOOL canShareSession;

// By default, the fetcher allows only secure (https) schemes unless this
// property is set, or the GTM_ALLOW_INSECURE_REQUESTS build flag is set.
//
// For example, during debugging when fetching from a development server that lacks SSL support,
// this may be set to @[ @"http" ], or when the fetcher is used to retrieve local files,
// this may be set to @[ @"file" ].
//
// This should be left as nil for release builds to avoid creating the opportunity for
// leaking private user behavior and data.  If a server is providing insecure URLs
// for fetching by the client app, report the problem as server security & privacy bug.
//
// For builds with the iOS 9/OS X 10.11 and later SDKs, this property is required only when
// the app specifies NSAppTransportSecurity/NSAllowsArbitraryLoads in the main bundle's Info.plist.
@property(atomic, copy, nullable) NSArray<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, nullable) NSHTTPCookieStorage *cookieStorage;

// Setting the credential is optional; it is used if the connection receives
// an authentication challenge.
@property(atomic, strong, 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, 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, nullable) NSData *bodyData;

// File to use as the request body. This forces use of an upload task.
@property(atomic, strong, 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, 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, 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, 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, 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, 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, nullable) GTMSessionFetcherWillRedirectBlock willRedirectBlock;

// The optional send progress block reports body bytes uploaded.
//
// This is called on the callback queue.
@property(atomic, copy, 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, 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, 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, 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, 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, nullable) GTMSessionFetcherRetryBlock retryBlock;

// The optional block for collecting the metrics of the present session.
//
// This is called on the callback queue.
@property(atomic, copy, nullable)
    GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE(
        ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0));

// Retry intervals must be strictly less than maxRetryInterval, else
// they will be limited to maxRetryInterval and no further retries will
// be attempted.  Setting maxRetryInterval to 0.0 will reset it to the
// default value, 60 seconds for downloads and 600 seconds for uploads.
@property(atomic, assign) NSTimeInterval maxRetryInterval;

// Starting retry interval.  Setting minRetryInterval to 0.0 will reset it
// to a random value between 1.0 and 2.0 seconds.  Clients should normally not
// set this except for unit testing.
@property(atomic, assign) NSTimeInterval minRetryInterval;

// Multiplier used to increase the interval between retries, typically 2.0.
// Clients should not need to set this.
@property(atomic, assign) double retryFactor;

// Number of retries attempted.
@property(atomic, readonly) NSUInteger retryCount;

// Interval delay to precede next retry.
@property(atomic, readonly) NSTimeInterval nextRetryInterval;

#if GTM_BACKGROUND_TASK_FETCHING
// Skip use of a UIBackgroundTask, thus requiring fetches to complete when the app is in the
// foreground.
//
// Targets should define GTM_BACKGROUND_TASK_FETCHING to 0 to avoid use of a UIBackgroundTask
// on iOS to allow fetches to complete in the background.  This property is available when
// it's not practical to set the preprocessor define.
@property(atomic, assign) BOOL skipBackgroundTask;
#endif  // GTM_BACKGROUND_TASK_FETCHING

// Begin fetching the request
//
// The delegate may optionally implement the callback or pass nil for the selector or handler.
//
// The delegate and all callback blocks are retained between the beginFetch call until after the
// finish callback, or until the fetch is stopped.
//
// An error is passed to the callback for server statuses 300 or
// higher, with the status stored as the error object's code.
//
// finishedSEL has a signature like:
//   - (void)fetcher:(GTMSessionFetcher *)fetcher
//  finishedWithData:(NSData *)data
//             error:(NSError *)error;
//
// If the application has specified a destinationFileURL or an accumulateDataBlock
// for the fetcher, the data parameter passed to the callback will be nil.

- (void)beginFetchWithDelegate:(nullable id)delegate didFinishSelector:(nullable SEL)finishedSEL;

- (void)beginFetchWithCompletionHandler:(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, nullable) GTMSessionFetcherCompletionHandler completionHandler;

// A block to be called if download resume data becomes available.
@property(atomic, strong, 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, nullable) NSDictionary<NSString *, NSString *> *responseHeaders;

// The response, once it's been received.
@property(atomic, strong, readonly, nullable) NSURLResponse *response;

// Bytes downloaded so far.
@property(atomic, readonly) int64_t downloadedLength;

// Buffer of currently-downloaded data, if available.
@property(atomic, readonly, strong, 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, 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, nullable) NSDate *initialBeginFetchDate;

// userData is retained solely for the convenience of the client.
@property(atomic, strong, nullable) id userData;

// Stored property values are retained solely for the convenience of the client.
@property(atomic, copy, nullable) NSDictionary<NSString *, id> *properties;

- (void)setProperty:(nullable id)obj
             forKey:(NSString *)key;  // Pass nil for obj to remove the property.
- (nullable id)propertyForKey:(NSString *)key;

- (void)addPropertiesFromDictionary:(NSDictionary<NSString *, id> *)dict;

// Comments are useful for logging, so are strongly recommended for each fetcher.
@property(atomic, copy, nullable) NSString *comment;

- (void)setCommentWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2);

// Log of request and response, if logging is enabled
@property(atomic, copy, nullable) NSString *log;

// Callbacks are run on this queue. If none is supplied, the main queue is used.
//
// CAUTION: This block MUST be a serial queue. Setting a concurrent queue can result in callbacks
// being dispatched concurrently, leading events to appear out-of-order.
@property(atomic, strong, 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, null_resettable) NSOperationQueue *sessionDelegateQueue;

// DEPRECATED: Callers should use XCTestExpectation instead.
//
// 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
    __deprecated_msg("Use XCTestExpectation instead");

// 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, nullable) GTMSessionFetcherTestBlock testBlock;

+ (void)setGlobalTestBlock:(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, nullable) NSString *logRequestBody;
@property(atomic, assign) BOOL deferResponseBodyLogging;
@property(atomic, copy, nullable) NSString *logResponseBody;

// Internal logging support.
@property(atomic, readonly) NSData *loggedStreamData;
@property(atomic, assign) BOOL hasLoggedError;
@property(atomic, strong, nullable) NSURL *redirectedFromURL;
- (void)appendLoggedStreamData:(NSData *)dataToAdd;
- (void)clearLoggedStreamData;

#endif  // STRIP_GTM_FETCH_LOGGING

@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:(nullable NSArray<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.
+ (nullable 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__

NS_ASSUME_NONNULL_END
