/** * NSURLSession.m * * Copyright (C) 2017-2024 Free Software Foundation, Inc. * * Written by: Hugo Melder * Date: May 2024 * Author: Hugo Melder * * This file is part of GNUStep-base * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * If you are interested in a warranty or support for this source code, * contact Scott Christley for more information. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ #import "NSURLSessionPrivate.h" #import "NSURLSessionTaskPrivate.h" #import "Foundation/NSString.h" #import "Foundation/NSArray.h" #import "Foundation/NSStream.h" #import "Foundation/NSUserDefaults.h" #import "Foundation/NSBundle.h" #import "Foundation/NSData.h" #import "GNUstepBase/NSDebug+GNUstepBase.h" /* For NSDebugMLLog */ #import "GNUstepBase/NSObject+GNUstepBase.h" /* For -notImplemented */ #import "GSPThread.h" /* For nextSessionIdentifier() */ #import "GSDispatch.h" /* For dispatch compatibility */ NSString * GS_NSURLSESSION_DEBUG_KEY = @"NSURLSession"; /* We need a globably unique label for the NSURLSession workQueues. */ static NSUInteger nextSessionIdentifier() { static gs_mutex_t lock = GS_MUTEX_INIT_STATIC; static NSUInteger sessionCounter = 0; GS_MUTEX_LOCK(lock); sessionCounter += 1; GS_MUTEX_UNLOCK(lock); return sessionCounter; } #pragma mark - libcurl callbacks /* CURLMOPT_TIMERFUNCTION: Callback to receive timer requests from libcurl */ static int timer_callback(CURLM * multi, /* multi handle */ long timeout_ms, /* timeout in number of ms */ void * clientp) /* private callback pointer */ { NSURLSession * session = (NSURLSession *)clientp; NSDebugLLog( GS_NSURLSESSION_DEBUG_KEY, @"Timer Callback for Session %@: multi=%p timeout_ms=%ld", session, multi, timeout_ms); /* * if timeout_ms is -1, just delete the timer * * For all other values of timeout_ms, this should set or *update* the timer * to the new value */ if (timeout_ms == -1) [session _suspendTimer]; else [session _setTimer: timeout_ms]; return 0; } /* CURLMOPT_SOCKETFUNCTION: libcurl requests socket monitoring using this * callback */ static int socket_callback(CURL * easy, /* easy handle */ curl_socket_t s, /* socket */ int what, /* describes the socket */ void * clientp, /* private callback pointer */ void * socketp) /* private socket pointer */ { NSURLSession * session = clientp; const char * whatstr[] = { "none", "IN", "OUT", "INOUT", "REMOVE" }; NSDebugLLog( GS_NSURLSESSION_DEBUG_KEY, @"Socket Callback for Session %@: socket=%d easy:%p what=%s", session, s, easy, whatstr[what]); if (NULL == socketp) { return [session _addSocket: s easyHandle: easy what: what]; } else if (CURL_POLL_REMOVE == what) { [session _removeSocket: (struct SourceInfo *)socketp]; return 0; } else { return [session _setSocket: s sources: (struct SourceInfo *)socketp what: what]; } } /* socket_callback */ #pragma mark - NSURLSession Implementation @implementation NSURLSession { /* The libcurl multi handle associated with this session. * We use the curl_multi_socket_action API as we utilise our * own event-handling system based on libdispatch. * * Event creation and deletion is driven by the various callbacks * registered during initialisation of the multi handle. */ CURLM * _multiHandle; /* A serial work queue for timer and socket sources * created on libcurl's behalf. */ dispatch_queue_t _workQueue; /* This timer is driven by libcurl and used by * libcurl's multi API. * * The handler notifies libcurl using curl_multi_socket_action * and checks for completed requests by calling * _checkForCompletion. * * See https://curl.se/libcurl/c/CURLMOPT_TIMERFUNCTION.html * and https://curl.se/libcurl/c/curl_multi_socket_action.html * respectively. */ dispatch_source_t _timer; /* The timer may be suspended upon request by libcurl. */ BOOL _isTimerSuspended; /* Only set when session originates from +[NSURLSession sharedSession] */ BOOL _isSharedSession; BOOL _invalidated; /* * Number of currently running handles. * This number is updated by curl_multi_socket_action * in the socket source handlers. */ int _stillRunning; /* List of active tasks. Access is synchronised via the _workQueue. */ NSMutableArray * _tasks; /* PEM encoded blob of one or more certificates. * * See GSCACertificateFilePath in NSUserDefaults.h */ NSData * _certificateBlob; /* Path to PEM encoded CA certificate file. */ NSString * _certificatePath; /* The task identifier for the next task */ _Atomic(NSInteger) _taskIdentifier; /* Lock for _taskIdentifier and _tasks */ gs_mutex_t _taskLock; } + (NSURLSession *) sharedSession { static NSURLSession * session = nil; static dispatch_once_t predicate; dispatch_once( &predicate, ^{ NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; session = [[NSURLSession alloc] initWithConfiguration: configuration delegate: nil delegateQueue: nil]; [session _setSharedSession: YES]; }); return session; } + (NSURLSession *) sessionWithConfiguration: (NSURLSessionConfiguration *)configuration { NSURLSession * session; session = [[NSURLSession alloc] initWithConfiguration: configuration delegate: nil delegateQueue: nil]; return AUTORELEASE(session); } + (NSURLSession *) sessionWithConfiguration: (NSURLSessionConfiguration *)configuration delegate: (id)delegate delegateQueue: (NSOperationQueue *)queue { NSURLSession * session; session = [[NSURLSession alloc] initWithConfiguration: configuration delegate: delegate delegateQueue: queue]; return AUTORELEASE(session); } - (instancetype) initWithConfiguration: (NSURLSessionConfiguration *) configuration delegate: (id)delegate delegateQueue: (NSOperationQueue *)queue { self = [super init]; if (self) { NSString * queueLabel; NSString * caPath; NSUInteger sessionIdentifier; /* To avoid a retain cycle in blocks referencing this object */ __block typeof(self) this = self; sessionIdentifier = nextSessionIdentifier(); queueLabel = [[NSString alloc] initWithFormat: @"org.gnustep.NSURLSession.WorkQueue%ld", sessionIdentifier]; ASSIGN(_delegate, delegate); ASSIGNCOPY(_configuration, configuration); _tasks = [[NSMutableArray alloc] init]; GS_MUTEX_INIT(_taskLock); /* label is strdup'ed by libdispatch */ _workQueue = dispatch_queue_create([queueLabel UTF8String], DISPATCH_QUEUE_SERIAL); [queueLabel release]; if (!_workQueue) return nil; _isTimerSuspended = YES; _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _workQueue); if (!_timer) { return nil; } dispatch_source_set_cancel_handler( _timer, ^{ dispatch_release(this->_timer); }); // Called after timeout set by libcurl is reached dispatch_source_set_event_handler( _timer, ^{ // TODO: Check for return values curl_multi_socket_action( this->_multiHandle, CURL_SOCKET_TIMEOUT, 0, &this->_stillRunning); [this _checkForCompletion]; }); /* Use the provided delegateQueue if available */ if (queue) { _delegateQueue = queue; } else { /* This (serial) NSOperationQueue is only used for dispatching * delegate callbacks and is orthogonal to the workQueue. */ _delegateQueue = [[NSOperationQueue alloc] init]; [_delegateQueue setMaxConcurrentOperationCount: 1]; } /* libcurl Configuration */ curl_global_init(CURL_GLOBAL_SSL); _multiHandle = curl_multi_init(); // Set up CURL multi callbacks curl_multi_setopt(_multiHandle, CURLMOPT_SOCKETFUNCTION, socket_callback); curl_multi_setopt(_multiHandle, CURLMOPT_SOCKETDATA, self); curl_multi_setopt(_multiHandle, CURLMOPT_TIMERFUNCTION, timer_callback); curl_multi_setopt(_multiHandle, CURLMOPT_TIMERDATA, self); // Configure Multi Handle curl_multi_setopt( _multiHandle, CURLMOPT_MAX_HOST_CONNECTIONS, [_configuration HTTPMaximumConnectionsPerHost]); /* Check if GSCACertificateFilePath is set */ caPath = [[NSUserDefaults standardUserDefaults] objectForKey: GSCACertificateFilePath]; if (caPath) { NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Found a GSCACertificateFilePath entry in UserDefaults"); _certificateBlob = [[NSData alloc] initWithContentsOfFile: caPath]; if (!_certificateBlob) { NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Could not open file at GSCACertificateFilePath=%@", caPath); } else { ASSIGN(_certificatePath, caPath); } } } return self; } /* initWithConfiguration */ #pragma mark - Private Methods - (NSData *) _certificateBlob { return _certificateBlob; } - (NSString *) _certificatePath { return _certificatePath; } - (void) _setSharedSession: (BOOL)flag { _isSharedSession = flag; } - (NSInteger) _nextTaskIdentifier { NSInteger identifier; GS_MUTEX_LOCK(_taskLock); identifier = _taskIdentifier; _taskIdentifier += 1; GS_MUTEX_UNLOCK(_taskLock); return identifier; } - (void) _resumeTask: (NSURLSessionTask *)task { dispatch_async( _workQueue, ^{ CURLMcode code; CURLM * multiHandle = _multiHandle; code = curl_multi_add_handle(multiHandle, [task _easyHandle]); NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Added task=%@ easy=%p to multi=%p with return value %d", task, [task _easyHandle], multiHandle, code); }); } - (void) _addHandle: (CURL *)easy { curl_multi_add_handle(_multiHandle, easy); } - (void) _removeHandle: (CURL *)easy { curl_multi_remove_handle(_multiHandle, easy); } - (void) _setTimer: (NSInteger)timeoutMs { dispatch_source_set_timer( _timer, dispatch_time( DISPATCH_TIME_NOW, timeoutMs * NSEC_PER_MSEC), DISPATCH_TIME_FOREVER, // don't repeat timeoutMs * 0.05); // 5% leeway if (_isTimerSuspended) { _isTimerSuspended = NO; dispatch_resume(_timer); } } - (void) _suspendTimer { if (!_isTimerSuspended) { _isTimerSuspended = YES; dispatch_suspend(_timer); } } - (dispatch_queue_t) _workQueue { return _workQueue; } /* This method is called when receiving CURL_POLL_REMOVE in socket_callback. * We cancel all active dispatch sources and release the SourceInfo structure * previously allocated in _addSocket: easyHandle: what: */ - (void) _removeSocket: (struct SourceInfo *)sources { NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Remove socket with SourceInfo: %p", sources); if (sources->readSocket) { dispatch_source_cancel(sources->readSocket); dispatch_release(sources->readSocket); } if (sources->writeSocket) { dispatch_source_cancel(sources->writeSocket); dispatch_release(sources->writeSocket); } free(sources); } /* A socket needs to be configured and the private socket pointer * (socketp) in socket_callback is NULL, meaning we first need to * allocate our SourceInfo structure. */ - (int) _addSocket: (curl_socket_t)socket easyHandle: (CURL *)easy what: (int) what { struct SourceInfo * info; NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Add Socket: %d easy: %p", socket, easy); /* Allocate a new SourceInfo structure on the heap */ if (!(info = calloc(1, sizeof(struct SourceInfo)))) { NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Failed to allocate SourceInfo structure!"); return -1; } /* We can now configure the dispatch sources */ if (-1 == [self _setSocket: socket sources: info what: what]) { NSDebugMLLog(GS_NSURLSESSION_DEBUG_KEY, @"Failed to setup sockets!"); return -1; } /* Assign the SourceInfo for access in subsequent socket_callback calls */ curl_multi_assign(_multiHandle, socket, info); return 0; } /* _addSocket */ - (int) _setSocket: (curl_socket_t)socket sources: (struct SourceInfo *)sources what: (int)what { /* Create a Reading Dispatch Source that listens on socket */ if (CURL_POLL_IN == what || CURL_POLL_INOUT == what) { /* Reset Dispatch Source if previously initialised */ if (sources->readSocket) { dispatch_source_cancel(sources->readSocket); dispatch_release(sources->readSocket); sources->readSocket = NULL; } NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Creating a reading dispatch source: socket=%d sources=%p what=%d", socket, sources, what); sources->readSocket = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, socket, 0, _workQueue); if (!sources->readSocket) { NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Unable to create dispatch source for read socket!"); return -1; } dispatch_source_set_event_handler( sources->readSocket, ^{ int action; action = CURL_CSELECT_IN; curl_multi_socket_action(_multiHandle, socket, action, &_stillRunning); /* Check if the transfer is complete */ [self _checkForCompletion]; /* When _stillRunning reaches zero, all transfers are complete/done */ if (_stillRunning <= 0) { [self _suspendTimer]; } }); dispatch_resume(sources->readSocket); } /* Create a Writing Dispatch Source that listens on socket */ if (CURL_POLL_OUT == what || CURL_POLL_INOUT == what) { /* Reset Dispatch Source if previously initialised */ if (sources->writeSocket) { dispatch_source_cancel(sources->writeSocket); dispatch_release(sources->writeSocket); sources->writeSocket = NULL; } NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Creating a writing dispatch source: socket=%d sources=%p what=%d", socket, sources, what); sources->writeSocket = dispatch_source_create( DISPATCH_SOURCE_TYPE_WRITE, socket, 0, _workQueue); if (!sources->writeSocket) { NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Unable to create dispatch source for write socket!"); return -1; } dispatch_source_set_event_handler( sources->writeSocket, ^{ int action; action = CURL_CSELECT_OUT; curl_multi_socket_action(_multiHandle, socket, action, &_stillRunning); /* Check if the tranfer is complete */ [self _checkForCompletion]; /* When _stillRunning reaches zero, all transfers are complete/done */ if (_stillRunning <= 0) { [self _suspendTimer]; } }); dispatch_resume(sources->writeSocket); } return 0; } /* _setSocket */ /* Called by a socket event handler or by a firing timer set by timer_callback. * * The socket event handler is executed on the _workQueue. */ - (void) _checkForCompletion { CURLMsg * msg; int msgs_left; CURL * easyHandle; CURLcode res; char * eff_url = NULL; NSURLSessionTask * task = nil; /* Ask the multi handle if there are any messages from the individual * transfers. * * Remove the associated easy handle and release the task if the transfer is * done. This completes the life-cycle of a task added to NSURLSession. */ while ((msg = curl_multi_info_read(_multiHandle, &msgs_left))) { if (msg->msg == CURLMSG_DONE) { CURLcode rc; easyHandle = msg->easy_handle; res = msg->data.result; /* Get the NSURLSessionTask instance */ rc = curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &task); if (CURLE_OK != rc) { NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Failed to retrieve task from easy handle %p using " @"CURLINFO_PRIVATE", easyHandle); } rc = curl_easy_getinfo(easyHandle, CURLINFO_EFFECTIVE_URL, &eff_url); if (CURLE_OK != rc) { NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Failed to retrieve effective URL from easy handle %p using " @"CURLINFO_PRIVATE", easyHandle); } NSDebugMLLog( GS_NSURLSESSION_DEBUG_KEY, @"Transfer finished for Task %@ with effective url %s " @"and CURLcode: %s", task, eff_url, curl_easy_strerror(res)); curl_multi_remove_handle(_multiHandle, easyHandle); /* This session might be released in _transferFinishedWithCode. Better * retain it first. */ RETAIN(self); RETAIN(task); [_tasks removeObject: task]; [task _transferFinishedWithCode: res]; RELEASE(task); /* Send URLSession: didBecomeInvalidWithError: to delegate if this * session was invalidated */ if (_invalidated && [_tasks count] == 0 && [_delegate respondsToSelector: @selector(URLSession: didBecomeInvalidWithError :)]) { [_delegateQueue addOperationWithBlock:^{ /* We only support explicit Invalidation for now. Error is set * to nil in this case. */ [_delegate URLSession: self didBecomeInvalidWithError: nil]; }]; } RELEASE(self); } } } /* _checkForCompletion */ /* Adds task to _tasks and updates the delegate */ - (void) _didCreateTask: (NSURLSessionTask *)task { dispatch_async( _workQueue, ^{ [_tasks addObject: task]; }); if ([_delegate respondsToSelector: @selector(URLSession:didCreateTask:)]) { [_delegateQueue addOperationWithBlock:^{ [(id) _delegate URLSession: self didCreateTask : task]; }]; } } #pragma mark - Public API - (void) finishTasksAndInvalidate { if (_isSharedSession) { return; } dispatch_async( _workQueue, ^{ _invalidated = YES; }); } - (void) invalidateAndCancel { if (_isSharedSession) { return; } dispatch_async( _workQueue, ^{ _invalidated = YES; /* Cancel all tasks */ for (NSURLSessionTask * task in _tasks) { [task cancel]; } }); } - (NSURLSessionDataTask *) dataTaskWithRequest: (NSURLRequest *)request { NSURLSessionDataTask * task; NSInteger identifier; identifier = [self _nextTaskIdentifier]; task = [[NSURLSessionDataTask alloc] initWithSession: self request: request taskIdentifier: identifier]; /* We use the session delegate by default. NSURLSessionTaskDelegate * is a purely optional protocol. */ [task setDelegate: (id)_delegate]; [task _setProperties: GSURLSessionUpdatesDelegate]; [self _didCreateTask: task]; return AUTORELEASE(task); } - (NSURLSessionDataTask *) dataTaskWithURL: (NSURL *)url { NSURLRequest * request; request = [NSURLRequest requestWithURL: url]; return [self dataTaskWithRequest: request]; } - (NSURLSessionUploadTask *) uploadTaskWithRequest: (NSURLRequest *)request fromFile: (NSURL *)fileURL { NSURLSessionUploadTask * task; NSInputStream * stream; NSInteger identifier; identifier = [self _nextTaskIdentifier]; stream = [NSInputStream inputStreamWithURL: fileURL]; task = [[NSURLSessionUploadTask alloc] initWithSession: self request: request taskIdentifier: identifier]; /* We use the session delegate by default. NSURLSessionTaskDelegate * is a purely optional protocol. */ [task setDelegate: (id)_delegate]; [task _setProperties: GSURLSessionUpdatesDelegate | GSURLSessionHasInputStream]; [task _setBodyStream: stream]; [task _enableUploadWithSize: 0]; [self _didCreateTask: task]; return AUTORELEASE(task); } /* uploadTaskWithRequest */ - (NSURLSessionUploadTask *) uploadTaskWithRequest: (NSURLRequest *)request fromData: (NSData *)bodyData { NSURLSessionUploadTask * task; NSInteger identifier; identifier = [self _nextTaskIdentifier]; task = [[NSURLSessionUploadTask alloc] initWithSession: self request: request taskIdentifier: identifier]; /* We use the session delegate by default. NSURLSessionTaskDelegate * is a purely optional protocol. */ [task setDelegate: (id)_delegate]; [task _setProperties: GSURLSessionUpdatesDelegate]; [task _enableUploadWithData: bodyData]; [self _didCreateTask: task]; return AUTORELEASE(task); } - (NSURLSessionUploadTask *) uploadTaskWithStreamedRequest: (NSURLRequest *)request { NSURLSessionUploadTask * task; NSInteger identifier; identifier = [self _nextTaskIdentifier]; task = [[NSURLSessionUploadTask alloc] initWithSession: self request: request taskIdentifier: identifier]; /* We use the session delegate by default. NSURLSessionTaskDelegate * is a purely optional protocol. */ [task setDelegate: (id)_delegate]; [task _setProperties: GSURLSessionUpdatesDelegate | GSURLSessionHasInputStream]; [task _enableUploadWithSize: 0]; [self _didCreateTask: task]; return AUTORELEASE(task); } - (NSURLSessionDownloadTask *) downloadTaskWithRequest: (NSURLRequest *)request { NSURLSessionDownloadTask * task; NSInteger identifier; identifier = [self _nextTaskIdentifier]; task = [[NSURLSessionDownloadTask alloc] initWithSession: self request: request taskIdentifier: identifier]; /* We use the session delegate by default. NSURLSessionTaskDelegate * is a purely optional protocol. */ [task setDelegate: (id)_delegate]; [task _setProperties: GSURLSessionWritesDataToFile | GSURLSessionUpdatesDelegate]; [self _didCreateTask: task]; return AUTORELEASE(task); } - (NSURLSessionDownloadTask *) downloadTaskWithURL: (NSURL *)url { NSURLRequest * request; request = [NSURLRequest requestWithURL: url]; return [self downloadTaskWithRequest: request]; } - (NSURLSessionDownloadTask *) downloadTaskWithResumeData: (NSData *)resumeData { return [self notImplemented: _cmd]; } - (void) getTasksWithCompletionHandler: (void (^)( NSArray * dataTasks, NSArray * uploadTasks, NSArray * downloadTasks)) completionHandler { dispatch_async( _workQueue, ^{ NSMutableArray * dataTasks; NSMutableArray * uploadTasks; NSMutableArray * downloadTasks; NSInteger numberOfTasks; Class dataTaskClass; Class uploadTaskClass; Class downloadTaskClass; numberOfTasks = [_tasks count]; dataTasks = [NSMutableArray arrayWithCapacity: numberOfTasks / 2]; uploadTasks = [NSMutableArray arrayWithCapacity: numberOfTasks / 2]; downloadTasks = [NSMutableArray arrayWithCapacity: numberOfTasks / 2]; dataTaskClass = [NSURLSessionDataTask class]; uploadTaskClass = [NSURLSessionUploadTask class]; downloadTaskClass = [NSURLSessionDownloadTask class]; for (NSURLSessionTask * task in _tasks) { if ([task isKindOfClass: dataTaskClass]) { [dataTasks addObject: (NSURLSessionDataTask *)task]; } else if ([task isKindOfClass: uploadTaskClass]) { [uploadTasks addObject: (NSURLSessionUploadTask *)task]; } else if ([task isKindOfClass: downloadTaskClass]) { [downloadTasks addObject: (NSURLSessionDownloadTask *)task]; } } completionHandler(dataTasks, uploadTasks, downloadTasks); }); } /* getTasksWithCompletionHandler */ - (void) getAllTasksWithCompletionHandler: (void (^)(NSArray<__kindof NSURLSessionTask *> * tasks))completionHandler { dispatch_async( _workQueue, ^{ completionHandler(_tasks); }); } #pragma mark - Getter and Setter - (NSOperationQueue *) delegateQueue { return _delegateQueue; } - (id) delegate { return _delegate; } - (NSURLSessionConfiguration *) configuration { return AUTORELEASE([_configuration copy]); } - (NSString *) sessionDescription { return _sessionDescription; } - (void) setSessionDescription: (NSString *)sessionDescription { ASSIGNCOPY(_sessionDescription, sessionDescription); } - (void) dealloc { RELEASE(_delegateQueue); RELEASE(_delegate); RELEASE(_configuration); RELEASE(_tasks); RELEASE(_certificateBlob); RELEASE(_certificatePath); curl_multi_cleanup(_multiHandle); #if defined(HAVE_DISPATCH_CANCEL) dispatch_cancel(_timer); #else dispatch_source_cancel(_timer); #endif dispatch_release(_workQueue); [super dealloc]; } @end @implementation NSURLSession (NSURLSessionAsynchronousConvenience) - (NSURLSessionDataTask *) dataTaskWithRequest: (NSURLRequest *)request completionHandler: (GSNSURLSessionDataCompletionHandler)completionHandler { NSURLSessionDataTask * task; NSInteger identifier; identifier = [self _nextTaskIdentifier]; task = [[NSURLSessionDataTask alloc] initWithSession: self request: request taskIdentifier: identifier]; [task setDelegate: (id)_delegate]; [task _setCompletionHandler: completionHandler]; [task _enableAutomaticRedirects: YES]; [task _setProperties: GSURLSessionStoresDataInMemory | GSURLSessionHasCompletionHandler]; [self _didCreateTask: task]; return AUTORELEASE(task); } - (NSURLSessionDataTask *) dataTaskWithURL: (NSURL *)url completionHandler: (GSNSURLSessionDataCompletionHandler)completionHandler { NSURLRequest * request = [NSURLRequest requestWithURL: url]; return [self dataTaskWithRequest: request completionHandler: completionHandler]; } - (NSURLSessionUploadTask *) uploadTaskWithRequest: (NSURLRequest *)request fromFile: (NSURL *)fileURL completionHandler: (GSNSURLSessionDataCompletionHandler)completionHandler { NSURLSessionUploadTask * task; NSInputStream * stream; NSInteger identifier; identifier = [self _nextTaskIdentifier]; stream = [NSInputStream inputStreamWithURL: fileURL]; task = [[NSURLSessionUploadTask alloc] initWithSession: self request: request taskIdentifier: identifier]; [task setDelegate: (id)_delegate]; [task _setProperties: GSURLSessionStoresDataInMemory | GSURLSessionHasInputStream | GSURLSessionHasCompletionHandler]; [task _setCompletionHandler: completionHandler]; [task _enableAutomaticRedirects: YES]; [task _setBodyStream: stream]; [task _enableUploadWithSize: 0]; [self _didCreateTask: task]; return AUTORELEASE(task); } /* uploadTaskWithRequest */ - (NSURLSessionUploadTask *) uploadTaskWithRequest: (NSURLRequest *)request fromData: (NSData *)bodyData completionHandler: (GSNSURLSessionDataCompletionHandler)completionHandler { NSURLSessionUploadTask * task; NSInteger identifier; identifier = [self _nextTaskIdentifier]; task = [[NSURLSessionUploadTask alloc] initWithSession: self request: request taskIdentifier: identifier]; [task setDelegate: (id)_delegate]; [task _setProperties: GSURLSessionStoresDataInMemory | GSURLSessionHasCompletionHandler]; [task _setCompletionHandler: completionHandler]; [task _enableAutomaticRedirects: YES]; [task _enableUploadWithData: bodyData]; [self _didCreateTask: task]; return AUTORELEASE(task); } - (NSURLSessionDownloadTask *) downloadTaskWithRequest: (NSURLRequest *)request completionHandler: (GSNSURLSessionDownloadCompletionHandler) completionHandler { NSURLSessionDownloadTask * task; NSInteger identifier; identifier = [self _nextTaskIdentifier]; task = [[NSURLSessionDownloadTask alloc] initWithSession: self request: request taskIdentifier: identifier]; [task setDelegate: (id)_delegate]; [task _setProperties: GSURLSessionWritesDataToFile | GSURLSessionHasCompletionHandler]; [task _enableAutomaticRedirects: YES]; [task _setCompletionHandler: completionHandler]; [self _didCreateTask: task]; return AUTORELEASE(task); } - (NSURLSessionDownloadTask *) downloadTaskWithURL: (NSURL *)url completionHandler: (GSNSURLSessionDownloadCompletionHandler)completionHandler { NSURLRequest * request = [NSURLRequest requestWithURL: url]; return [self downloadTaskWithRequest: request completionHandler: completionHandler]; } - (NSURLSessionDownloadTask *) downloadTaskWithResumeData: (NSData *)resumeData completionHandler: (GSNSURLSessionDownloadCompletionHandler)completionHandler { return [self notImplemented: _cmd]; } @end