/** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110 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]; } } #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; } #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; } - (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; } /* 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); } } } /* 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); } - (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); }); } - (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); } - (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