libs-base/Source/NSURLSession.m
2024-07-02 19:19:14 +01:00

1724 lines
47 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
NSURLSession.m
Copyright (C) 2017-2023 Free Software Foundation, Inc.
Written by: Daniel Ferreira <dtf@stanford.edu>
Date: November 2017
Author: Richard Frith-Macdonald <richard@brainstorm.co.uk>
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 <scottc@net-community.com> 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 "GSURLPrivate.h"
#import <curl/curl.h>
#import "GSDispatch.h"
#import "GSEasyHandle.h"
#import "GSHTTPURLProtocol.h"
#import "GSMultiHandle.h"
#import "GSPThread.h"
#import "GSTaskRegistry.h"
#import "GSURLSessionTaskBody.h"
#import "Foundation/NSError.h"
#import "Foundation/NSException.h"
#import "Foundation/NSOperation.h"
#import "Foundation/NSPredicate.h"
#import "Foundation/NSURLError.h"
#import "Foundation/NSURLSession.h"
#import "Foundation/NSURLRequest.h"
#import "Foundation/NSValue.h"
GS_DECLARE const float NSURLSessionTaskPriorityDefault = 0.5;
GS_DECLARE const float NSURLSessionTaskPriorityLow = 0.0;
GS_DECLARE const float NSURLSessionTaskPriorityHigh = 1.0;
GS_DECLARE const int64_t NSURLSessionTransferSizeUnknown = -1;
/* NSURLSession API implementation overview
*
* This implementation uses libcurl for the HTTP layer implementation. At a
* high level, the [NSURLSession] keeps a curl *multi handle*, and each
* [NSURLSessionTask] has an *easy handle*. This way these two APIs somewhat
* have a 1-to-1 mapping.
*
* The [NSURLSessionTask] class is in charge of configuring its *easy handle*
* and adding it to the owning sessions *multi handle*. Adding / removing
* the handle effectively resumes / suspends the transfer.
*
* The [NSURLSessionTasks] class has subclasses, but this design puts all the
* logic into the parent [NSURLSessionTask].
*
* The session class uses the [GSTaskRegistry] to keep track of its tasks.
*
* The task class uses an GSInternalState type together with GSTransferState
* to keep track of its state and each transfers state.
* NB. a single task may do multiple transfers (e.g. as the result of a
* redirect).
*
* The [NSURLSession] has a libdispatch *work queue*, and all internal work is
* done on that queue, such that the code doesn't have to deal with thread
* safety beyond that. All work inside a [NSURLSessionTask] will run on this
* work queue, and so will code manipulating the session's *multi handle*.
*
* Delegate callbacks are, however, done on the passed in delegateQueue.
* Any calls into this API need to switch onto the *work queue* as needed.
*
* Most of HTTP is defined in [RFC 2616](https://tools.ietf.org/html/rfc2616).
* While libcurl handles many of these details, some are handled by this
* NSURLSession implementation.
*/
@interface NSURLSession ()
- (dispatch_queue_t) workQueue;
@end
@interface NSURLSessionTask()
- (instancetype) initWithSession: (NSURLSession*)session
request: (NSURLRequest*)request
taskIdentifier: (NSUInteger)identifier;
- (void) getProtocolWithCompletion: (void (^)(NSURLProtocol* protocol))completion;
- (void) setState: (NSURLSessionTaskState)state;
- (void) invalidateProtocol;
- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler;
- (void) setDataCompletionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler;
- (void) setDownloadCompletionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
@end
@interface NSURLSessionTask (URLProtocolClient) <NSURLProtocolClient>
@end
typedef NS_ENUM(NSUInteger, NSURLSessionTaskProtocolState) {
NSURLSessionTaskProtocolStateToBeCreated = 0,
NSURLSessionTaskProtocolStateAwaitingCacheReply = 1,
NSURLSessionTaskProtocolStateExisting = 2,
NSURLSessionTaskProtocolStateInvalidated = 3,
};
static unsigned nextSessionIdentifier()
{
static gs_mutex_t lock = GS_MUTEX_INIT_STATIC;
static unsigned sessionCounter = 0;
GS_MUTEX_LOCK(lock);
sessionCounter += 1;
GS_MUTEX_UNLOCK(lock);
return sessionCounter;
}
@implementation NSURLSession
{
dispatch_queue_t _workQueue;
NSUInteger _nextTaskIdentifier;
BOOL _invalidated;
GSTaskRegistry *_taskRegistry;
}
+ (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];
});
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 <NSURLSessionDelegate>)delegate
delegateQueue: (NSOperationQueue*)queue
{
NSURLSession *session;
session = [[NSURLSession alloc] initWithConfiguration: configuration
delegate: delegate
delegateQueue: queue];
return AUTORELEASE(session);
}
- (instancetype) initWithConfiguration: (NSURLSessionConfiguration*)configuration
delegate: (id <NSURLSessionDelegate>)delegate
delegateQueue: (NSOperationQueue*)queue
{
if (nil != (self = [super init]))
{
char label[30];
dispatch_queue_t targetQueue;
_taskRegistry = [[GSTaskRegistry alloc] init];
#if defined(CURLSSLBACKEND_GNUTLS)
curl_global_sslset(CURLSSLBACKEND_GNUTLS, NULL, NULL)l
#endif
curl_global_init(CURL_GLOBAL_SSL);
sprintf(label, "NSURLSession %u", nextSessionIdentifier());
targetQueue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
#if HAVE_DISPATCH_QUEUE_CREATE_WITH_TARGET
_workQueue = dispatch_queue_create_with_target(label,
DISPATCH_QUEUE_SERIAL, targetQueue);
#else
_workQueue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(_workQueue, targetQueue);
#endif
if (nil != queue)
{
ASSIGN(_delegateQueue, queue);
}
else
{
_delegateQueue = [[NSOperationQueue alloc] init];
[_delegateQueue setMaxConcurrentOperationCount: 1];
}
_delegate = delegate;
ASSIGN(_configuration, configuration);
_nextTaskIdentifier = 1;
_invalidated = NO;
_multiHandle = [[GSMultiHandle alloc] initWithConfiguration: configuration
workQueue: _workQueue];
[NSURLProtocol registerClass: [GSHTTPURLProtocol class]];
}
return self;
}
- (void) dealloc
{
DESTROY(_taskRegistry);
DESTROY(_configuration);
DESTROY(_delegateQueue);
DESTROY(_multiHandle);
[super dealloc];
}
- (dispatch_queue_t) workQueue
{
return _workQueue;
}
- (NSOperationQueue*) delegateQueue
{
return _delegateQueue;
}
- (id <NSURLSessionDelegate>) delegate
{
return _delegate;
}
- (NSURLSessionConfiguration*) configuration
{
return _configuration;
}
- (NSString*) sessionDescription
{
return _sessionDescription;
}
- (void) setSessionDescription: (NSString*)sessionDescription
{
ASSIGN(_sessionDescription, sessionDescription);
}
- (void) finishTasksAndInvalidate
{
dispatch_async(_workQueue,
^{
_invalidated = YES;
void (^invalidateSessionCallback)(void) =
^{
if (nil == _delegate) return;
[[self delegateQueue] addOperationWithBlock:
^{
if ([_delegate respondsToSelector: @selector(URLSession:didBecomeInvalidWithError:)])
{
[_delegate URLSession: self didBecomeInvalidWithError: nil];
}
_delegate = nil;
}];
};
if (![_taskRegistry isEmpty])
{
[_taskRegistry notifyOnTasksCompletion: invalidateSessionCallback];
}
else
{
invalidateSessionCallback();
}
});
}
- (void) invalidateAndCancel
{
NSEnumerator *e;
NSURLSessionTask *task;
dispatch_sync(_workQueue,
^{
_invalidated = YES;
});
e = [[_taskRegistry allTasks] objectEnumerator];
while (nil != (task = [e nextObject]))
{
[task cancel];
}
dispatch_async(_workQueue,
^{
if (nil == _delegate)
{
return;
}
[[self delegateQueue] addOperationWithBlock:
^{
if ([_delegate respondsToSelector: @selector(URLSession:didBecomeInvalidWithError:)])
{
[_delegate URLSession: self didBecomeInvalidWithError: nil];
}
_delegate = nil;
}];
});
}
- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request
{
NSURLSessionDataTask *task;
if (_invalidated)
{
return nil;
}
task = [[NSURLSessionDataTask alloc] initWithSession: self
request: request
taskIdentifier: _nextTaskIdentifier++];
[self addTask: task];
return AUTORELEASE(task);
}
- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url
{
NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL: url];
[request setHTTPMethod: @"POST"];
return [self dataTaskWithRequest: request];
}
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
fromFile: (NSURL*)fileURL
{
return [self notImplemented: _cmd];
}
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
fromData: (NSData*)bodyData
{
return [self notImplemented: _cmd];
}
- (NSURLSessionUploadTask*) uploadTaskWithStreamedRequest: (NSURLRequest*)request
{
return [self notImplemented: _cmd];
}
- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request
{
NSURLSessionDownloadTask *task;
if (_invalidated)
{
return nil;
}
task = [[NSURLSessionDownloadTask alloc] initWithSession: self
request: request
taskIdentifier: _nextTaskIdentifier++];
[self addTask: task];
return AUTORELEASE(task);
}
- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL*)url
{
NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL: url];
[request setHTTPMethod: @"GET"];
return [self downloadTaskWithRequest: request];
}
- (NSURLSessionDownloadTask*) downloadTaskWithResumeData: (NSData*)resumeData
{
return [self notImplemented: _cmd];
}
- (void) getTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, NSURLSessionDataTask*) *dataTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionUploadTask*) *uploadTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionDownloadTask*) *downloadTasks))completionHandler
{
NSArray *allTasks, *dataTasks, *uploadTasks, *downloadTasks;
allTasks = [_taskRegistry allTasks];
dataTasks = [allTasks filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(id task, NSDictionary* bindings) {
return [task isKindOfClass:[NSURLSessionDataTask class]];
}]];
uploadTasks = [allTasks filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(id task, NSDictionary* bindings) {
return [task isKindOfClass:[NSURLSessionUploadTask class]];
}]];
downloadTasks = [allTasks filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(id task, NSDictionary* bindings) {
return [task isKindOfClass:[NSURLSessionDownloadTask class]];
}]];
[[self delegateQueue] addOperationWithBlock:
^{
completionHandler(dataTasks, uploadTasks, downloadTasks);
}];
}
- (void) getAllTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, __kindof NSURLSessionTask*) *tasks))completionHandler
{
NSArray *allTasks = [_taskRegistry allTasks];
[[self delegateQueue] addOperationWithBlock:
^{
completionHandler(allTasks);
}];
}
- (void) addTask: (NSURLSessionTask*)task
{
[_taskRegistry addTask: task];
}
- (void) removeTask: (NSURLSessionTask*)task
{
[_taskRegistry removeTask: task];
}
@end
@implementation NSURLSession (NSURLSessionAsynchronousConvenience)
- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler
{
NSURLSessionDataTask *task;
if (_invalidated)
{
return nil;
}
task = [[NSURLSessionDataTask alloc] initWithSession: self
request: request
taskIdentifier: _nextTaskIdentifier++];
[task setDataCompletionHandler: completionHandler];
[self addTask: task];
return AUTORELEASE(task);
}
- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler
{
NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL: url];
[request setHTTPMethod: @"POST"];
return [self dataTaskWithRequest: request
completionHandler: completionHandler];
}
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
fromFile: (NSURL*)fileURL
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler
{
return [self notImplemented: _cmd];
}
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
fromData: (NSData*)bodyData
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler
{
return [self notImplemented: _cmd];
}
- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler
{
NSURLSessionDataTask *task;
if (_invalidated)
{
return nil;
}
task = [[NSURLSessionDataTask alloc] initWithSession: self
request: request
taskIdentifier: _nextTaskIdentifier++];
[task setDownloadCompletionHandler: completionHandler];
[self addTask: task];
return AUTORELEASE(task);
}
- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL*)url
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler
{
NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL: url];
[request setHTTPMethod: @"GET"];
return [self downloadTaskWithRequest: request
completionHandler: completionHandler];
}
- (NSURLSessionDownloadTask*) downloadTaskWithResumeData: (NSData*)resumeData
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler
{
return [self notImplemented: _cmd];
}
@end
@implementation _NSURLProtocolClient
- (instancetype) init
{
if (nil != (self = [super init]))
{
_cachePolicy = NSURLCacheStorageNotAllowed;
}
return self;
}
- (void) dealloc
{
DESTROY(_cacheableData);
DESTROY(_cacheableResponse);
[super dealloc];
}
- (void) URLProtocol: (NSURLProtocol *)protocol
cachedResponseIsValid: (NSCachedURLResponse *)cachedResponse
{
}
- (void) URLProtocol: (NSURLProtocol *)protocol
didFailWithError: (NSError *)error
{
NSURLSessionTask *task = [protocol task];
NSAssert(nil != task, @"Missing task");
[self task: task didFailWithError: error];
}
- (void) task: (NSURLSessionTask *)task
didFailWithError: (NSError *)error
{
NSURLSession *session;
NSOperationQueue *delegateQueue;
id<NSURLSessionDelegate> delegate;
void (^dataCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error);
void (^downloadCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error);
session = [task session];
NSAssert(nil != session, @"Missing session");
delegateQueue = [session delegateQueue];
delegate = [session delegate];
dataCompletionHandler = [task dataCompletionHandler];
downloadCompletionHandler = [task downloadCompletionHandler];
if (nil != delegate)
{
[delegateQueue addOperationWithBlock:
^{
if (NSURLSessionTaskStateCompleted == [task state])
{
return;
}
if ([delegate respondsToSelector:
@selector(URLSession:task:didCompleteWithError:)])
{
[(id<NSURLSessionTaskDelegate>)delegate URLSession: session
task: task
didCompleteWithError: error];
}
[task setState: NSURLSessionTaskStateCompleted];
dispatch_async([session workQueue],
^{
[session removeTask: task];
});
}];
}
else if (nil != dataCompletionHandler)
{
[delegateQueue addOperationWithBlock:
^{
if (NSURLSessionTaskStateCompleted == [task state])
{
return;
}
dataCompletionHandler(nil, nil, error);
[task setState: NSURLSessionTaskStateCompleted];
dispatch_async([session workQueue],
^{
[session removeTask: task];
});
}];
}
else if (nil != downloadCompletionHandler)
{
[delegateQueue addOperationWithBlock:
^{
if (NSURLSessionTaskStateCompleted == [task state])
{
return;
}
downloadCompletionHandler(nil, nil, error);
[task setState: NSURLSessionTaskStateCompleted];
dispatch_async([session workQueue],
^{
[session removeTask: task];
});
}];
}
else
{
if (NSURLSessionTaskStateCompleted == [task state])
{
return;
}
[task setState: NSURLSessionTaskStateCompleted];
dispatch_async([session workQueue],
^{
[session removeTask: task];
});
}
[task invalidateProtocol];
}
- (void) URLProtocol: (NSURLProtocol *)protocol
didLoadData: (NSData *)data
{
NSURLSessionTask *task = [protocol task];
NSURLSession *session;
id<NSURLSessionDelegate> delegate;
NSAssert(nil != task, @"Missing task");
session = [task session];
delegate = [session delegate];
switch (_cachePolicy)
{
case NSURLCacheStorageAllowed:
case NSURLCacheStorageAllowedInMemoryOnly:
{
if (nil != _cacheableData)
{
[_cacheableData addObject: data];
}
break;
}
case NSURLCacheStorageNotAllowed:
break;
}
if (nil != delegate
&& [task isKindOfClass: [NSURLSessionDataTask class]]
&& [delegate respondsToSelector:
@selector(URLSession:dataTask:didReceiveData:)])
{
[[session delegateQueue] addOperationWithBlock:
^{
[(id<NSURLSessionDataDelegate>)delegate URLSession: session
dataTask: (NSURLSessionDataTask*)task
didReceiveData: data];
}];
}
}
- (void) URLProtocol: (NSURLProtocol *)protocol
didReceiveAuthenticationChallenge: (NSURLAuthenticationChallenge *)challenge
{
//FIXME
}
- (void) URLProtocol: (NSURLProtocol *)protocol
didReceiveResponse: (NSURLResponse *)response
cacheStoragePolicy: (NSURLCacheStoragePolicy)policy
{
NSURLSessionTask *task = [protocol task];
NSURLSession *session;
id<NSURLSessionDelegate> delegate;
NSAssert(nil != task, @"Missing task");
[task setResponse: response];
session = [task session];
if (![task isKindOfClass: [NSURLSessionDataTask class]])
{
return;
}
_cachePolicy = policy;
if (nil != [[session configuration] URLCache])
{
switch (policy)
{
case NSURLCacheStorageAllowed:
case NSURLCacheStorageAllowedInMemoryOnly:
ASSIGN(_cacheableData, [NSMutableArray array]);
ASSIGN(_cacheableResponse, response);
break;
case NSURLCacheStorageNotAllowed:
break;
}
}
delegate = [session delegate];
if (nil != delegate)
{
[[session delegateQueue] addOperationWithBlock:
^{
if ([delegate respondsToSelector: @selector
(URLSession:dataTask:didReceiveResponse:completionHandler:)])
{
NSURLSessionDataTask *dataTask = (NSURLSessionDataTask*)task;
[(id<NSURLSessionDataDelegate>)delegate URLSession: session
dataTask: dataTask
didReceiveResponse: response
completionHandler:
^(NSURLSessionResponseDisposition disposition) {
if (disposition != NSURLSessionResponseAllow)
{
NSLog(@"Warning: ignoring disposition %d from completion handler",
(int)disposition);
}
}];
}
}];
}
}
- (void) URLProtocol: (NSURLProtocol *)protocol
wasRedirectedToRequest: (NSURLRequest *)request
redirectResponse: (NSURLResponse *)redirectResponse
{
NSAssert(NO, @"The NSURLSession implementation doesn't currently handle redirects directly.");
}
- (NSURLProtectionSpace*) _protectionSpaceFrom: (NSHTTPURLResponse*)response
{
NSURLProtectionSpace *space = nil;
NSString *auth;
auth = [[response allHeaderFields] objectForKey: @"WWW-Authenticate"];
if (nil != auth)
{
NSURL *url = [response URL];
NSString *host = [url host];
NSNumber *port = [url port];
NSString *scheme = [url scheme];
NSRange range;
NSString *realm;
NSString *method;
if (nil == host) host = @"";
if (nil == port) port = [NSNumber numberWithInt: 80];
method = [[auth componentsSeparatedByString: @" "] firstObject];
range = [auth rangeOfString: @"realm="];
realm = range.length > 0
? [auth substringFromIndex: NSMaxRange(range)] : @"";
space = AUTORELEASE([[NSURLProtectionSpace alloc]
initWithHost: host
port: [port integerValue]
protocol: scheme
realm: realm
authenticationMethod: method]);
}
return space;
}
- (void) URLProtocolDidFinishLoading: (NSURLProtocol *)protocol
{
NSURLSessionTask *task = [protocol task];
NSURLSession *session;
NSHTTPURLResponse *urlResponse;
NSURLCache *cache;
NSOperationQueue *delegateQueue;
id<NSURLSessionDelegate> delegate;
void (^dataCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error);
void (^downloadCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error);
NSAssert(nil != task, @"Missing task");
session = [task session];
urlResponse = (NSHTTPURLResponse*)[task response];
if ([urlResponse statusCode] == 401)
{
[self _protectionSpaceFrom: urlResponse];
}
delegate = [session delegate];
delegateQueue = [session delegateQueue];
dataCompletionHandler = [task dataCompletionHandler];
downloadCompletionHandler = [task downloadCompletionHandler];
if (nil != (cache = [[session configuration] URLCache])
&& [task isKindOfClass: [NSURLSessionDataTask class]]
&& nil != _cacheableData
&& nil != _cacheableResponse)
{
NSCachedURLResponse *cacheable;
NSMutableData *data;
NSEnumerator *e;
NSData *d;
data = [NSMutableData data];
e = [_cacheableData objectEnumerator];
while (nil != (d = [e nextObject]))
{
[data appendData: d];
}
cacheable = [[NSCachedURLResponse alloc] initWithResponse: urlResponse
data: data
userInfo: nil
storagePolicy: _cachePolicy];
[cache storeCachedResponse: cacheable
forDataTask: (NSURLSessionDataTask*)task];
RELEASE(cacheable);
}
if (nil != dataCompletionHandler)
{
NSData *data = [NSURLProtocol propertyForKey: @"tempData"
inRequest: [protocol request]];
[delegateQueue addOperationWithBlock:
^{
if (NSURLSessionTaskStateCompleted == [task state])
{
return;
}
dataCompletionHandler(data, urlResponse, nil);
[task setState: NSURLSessionTaskStateCompleted];
dispatch_async([session workQueue],
^{
[session removeTask: task];
});
}];
}
else if (nil != downloadCompletionHandler)
{
NSURL *fileURL = [NSURLProtocol propertyForKey: @"tempFileURL"
inRequest: [protocol request]];
[delegateQueue addOperationWithBlock:
^{
if (NSURLSessionTaskStateCompleted == [task state])
{
return;
}
downloadCompletionHandler(fileURL, urlResponse, nil);
[task setState: NSURLSessionTaskStateCompleted];
dispatch_async([session workQueue],
^{
[session removeTask: task];
});
}];
}
else if (nil != delegate)
{
// Send delegate with temporary fileURL
if ([task isKindOfClass: [NSURLSessionDownloadTask class]]
&& [delegate respondsToSelector: @selector(URLSession:downloadTask:didFinishDownloadingToURL:)])
{
id<NSURLSessionDownloadDelegate> downloadDelegate;
NSURLSessionDownloadTask *downloadTask;
NSURL *fileURL;
downloadDelegate = (id<NSURLSessionDownloadDelegate>)delegate;
downloadTask = (NSURLSessionDownloadTask *)task;
fileURL = [NSURLProtocol propertyForKey: @"tempFileURL"
inRequest: [protocol request]];
[delegateQueue addOperationWithBlock:
^{
[downloadDelegate URLSession: session
downloadTask: downloadTask
didFinishDownloadingToURL: fileURL];
}];
}
[delegateQueue addOperationWithBlock:
^{
if (NSURLSessionTaskStateCompleted == [task state])
{
return;
}
if ([delegate respondsToSelector:
@selector(URLSession:task:didCompleteWithError:)])
{
[(id<NSURLSessionTaskDelegate>)delegate URLSession: session
task: task
didCompleteWithError: nil];
}
[task setState: NSURLSessionTaskStateCompleted];
dispatch_async([session workQueue],
^{
[session removeTask: task];
});
}];
}
else
{
if (NSURLSessionTaskStateCompleted != [task state])
{
[task setState: NSURLSessionTaskStateCompleted];
dispatch_async([session workQueue],
^{
[session removeTask: task];
});
}
}
[task invalidateProtocol];
}
- (void) URLProtocol: (NSURLProtocol *)protocol
didCancelAuthenticationChallenge: (NSURLAuthenticationChallenge *)challenge
{
//FIXME
}
@end
@implementation NSURLSessionTask
{
NSURLSession *_session;
NSLock *_protocolLock;
NSURLSessionTaskProtocolState _protocolState;
NSURLProtocol *_protocol;
NSMutableArray *_protocolBag;
Class _protocolClass;
BOOL _hasTriggeredResume;
float _priority;
}
- (instancetype) initWithSession: (NSURLSession*)session
request: (NSURLRequest*)request
taskIdentifier: (NSUInteger)identifier
{
NSEnumerator *e;
Class protocolClass;
if (nil != (self = [super init]))
{
NSData *data;
NSInputStream *stream;
/*
* Only retain the session once the -resume method is called
* and release the session as the last thing done once the
* task has completed. This avoids a retain loop causing
* session and tasks to be leaked.
*/
_session = session;
ASSIGN(_originalRequest, request);
ASSIGN(_currentRequest, request);
if ([(data = [request HTTPBody]) length] > 0)
{
_knownBody = [[GSURLSessionTaskBody alloc] initWithData: data];
}
else if (nil != (stream = [request HTTPBodyStream]))
{
_knownBody = [[GSURLSessionTaskBody alloc]
initWithInputStream: stream];
}
_taskIdentifier = identifier;
#if HAVE_DISPATCH_QUEUE_CREATE_WITH_TARGET
_workQueue = dispatch_queue_create_with_target("org.gnustep.NSURLSessionTask.WorkQueue", DISPATCH_QUEUE_SERIAL, [session workQueue]);
#else
_workQueue = dispatch_queue_create("org.gnustep.NSURLSessionTask.WorkQueue", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(_workQueue, [session workQueue]);
#endif
_state = NSURLSessionTaskStateSuspended;
_suspendCount = 1;
_protocolLock = [[NSLock alloc] init];
_protocolState = NSURLSessionTaskProtocolStateToBeCreated;
_protocol = nil;
_hasTriggeredResume = NO;
_priority = NSURLSessionTaskPriorityDefault;
e = [[[session configuration] protocolClasses] objectEnumerator];
while (nil != (protocolClass = [e nextObject]))
{
if ([protocolClass canInitWithTask: self])
{
_protocolClass = protocolClass;
break;
}
}
NSAssert(nil != _protocolClass, @"Unsupported protocol for request: %@", request);
}
return self;
}
- (void) dealloc
{
DESTROY(_originalRequest);
DESTROY(_currentRequest);
DESTROY(_response);
DESTROY(_taskDescription);
DESTROY(_error);
DESTROY(_protocolLock);
DESTROY(_protocol);
DESTROY(_protocolBag);
DESTROY(_dataCompletionHandler);
DESTROY(_knownBody);
dispatch_release(_workQueue);
[super dealloc];
}
- (NSURLSessionTaskState) updateTaskState
{
if (0 == _suspendCount)
{
_state = NSURLSessionTaskStateRunning;
}
else
{
_state = NSURLSessionTaskStateSuspended;
}
return _state;
}
- (NSUInteger) taskIdentifier
{
return _taskIdentifier;
}
- (NSURLRequest*) originalRequest
{
return _originalRequest;
}
- (NSURLRequest*) currentRequest
{
return _currentRequest;
}
- (NSURLResponse*) response
{
return _response;
}
- (void) setResponse: (NSURLResponse*)response
{
ASSIGN(_response, response);
}
- (int64_t) countOfBytesReceived
{
return _countOfBytesReceived;
}
- (int64_t) countOfBytesSent
{
return _countOfBytesSent;
}
- (int64_t) countOfBytesExpectedToSend
{
return _countOfBytesExpectedToSend;
}
- (int64_t) countOfBytesExpectedToReceive
{
return _countOfBytesExpectedToReceive;
}
- (NSString*) taskDescription
{
return _taskDescription;
}
- (void) setTaskDescription: (NSString*)taskDescription
{
ASSIGN(_taskDescription, taskDescription);
}
- (NSURLSessionTaskState) state
{
return _state;
}
- (NSError*) error
{
return _error;
}
- (BOOL) isSuspendedAfterResume
{
return _hasTriggeredResume && (_state == NSURLSessionTaskStateSuspended);
}
- (void) cancel
{
dispatch_sync(_workQueue,
^{
if (!(NSURLSessionTaskStateRunning == _state
|| NSURLSessionTaskStateSuspended == _state))
{
return;
}
_state = NSURLSessionTaskStateCanceling;
[self getProtocolWithCompletion:
^(NSURLProtocol *protocol) {
dispatch_async(_workQueue,
^{
_error = [[NSError alloc] initWithDomain: NSURLErrorDomain
code: NSURLErrorCancelled
userInfo: nil];
if (nil != protocol)
{
id<NSURLProtocolClient> client;
[protocol stopLoading];
if (nil != (client = [protocol client]))
{
[client URLProtocol: protocol didFailWithError: _error];
}
}
});
}];
});
}
- (void) suspend
{
dispatch_sync(_workQueue,
^{
if (NSURLSessionTaskStateCanceling == _state
|| NSURLSessionTaskStateCompleted == _state)
{
return;
}
_suspendCount++;
[self updateTaskState];
if (1 == _suspendCount)
{
[self getProtocolWithCompletion:
^(NSURLProtocol *protocol){
dispatch_async(_workQueue,
^{
if (nil != protocol)
{
[protocol stopLoading];
}
});
}];
}
});
}
- (void) resume
{
/*
* Properly retain the session to keep a reference
* to the task. This ensures correct API behaviour.
*/
RETAIN(_session);
dispatch_sync(_workQueue,
^{
if (NSURLSessionTaskStateCanceling == _state
|| NSURLSessionTaskStateCompleted == _state)
{
return;
}
if (_suspendCount > 0)
{
_suspendCount--;
}
[self updateTaskState];
if (0 == _suspendCount)
{
_hasTriggeredResume = YES;
[self getProtocolWithCompletion:
^(NSURLProtocol* protocol) {
dispatch_async(_workQueue,
^{
if (_suspendCount != 0
|| NSURLSessionTaskStateCanceling == _state
|| NSURLSessionTaskStateCompleted == _state)
{
return;
}
if (nil != protocol)
{
[protocol startLoading];
}
else if (nil == _error)
{
NSDictionary *userInfo;
_NSURLProtocolClient *client;
userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[_originalRequest URL], NSURLErrorFailingURLErrorKey,
[[_originalRequest URL] absoluteString], NSURLErrorFailingURLStringErrorKey,
nil];
_error = [[NSError alloc] initWithDomain: NSURLErrorDomain
code: NSURLErrorUnsupportedURL
userInfo: userInfo];
client = AUTORELEASE([[_NSURLProtocolClient alloc] init]);
[client task: self didFailWithError: _error];
}
});
}];
}
});
}
- (float) priority
{
return _priority;;
}
- (void) setPriority: (float)priority
{
_priority = priority;
}
- (id) copyWithZone: (NSZone*)zone
{
NSURLSessionTask *copy = [[[self class] alloc] init];
if (copy)
{
copy->_taskIdentifier = _taskIdentifier;
copy->_originalRequest = [_originalRequest copyWithZone: zone];
copy->_currentRequest = [_currentRequest copyWithZone: zone];
copy->_response = [_response copyWithZone: zone];
copy->_countOfBytesReceived = _countOfBytesReceived;
copy->_countOfBytesSent = _countOfBytesSent;
copy->_countOfBytesExpectedToSend = _countOfBytesExpectedToSend;
copy->_countOfBytesExpectedToReceive = _countOfBytesExpectedToReceive;
copy->_taskDescription = [_taskDescription copyWithZone: zone];
copy->_state = _state;
copy->_error = [_error copyWithZone: zone];
copy->_session = _session;
dispatch_retain(_workQueue);
copy->_workQueue = _workQueue;
copy->_suspendCount = _suspendCount;
copy->_protocolLock = [_protocolLock copy];
}
return copy;
}
- (void) getProtocolWithCompletion: (void (^)(NSURLProtocol* protocol))completion
{
[_protocolLock lock];
switch (_protocolState)
{
case NSURLSessionTaskProtocolStateToBeCreated:
{
NSURLCache *cache;
NSURLRequestCachePolicy cachePolicy;
NSCachedURLResponse *cachedResponse;
cache = [[_session configuration] URLCache];
cachePolicy = [[self currentRequest] cachePolicy];
if (nil != cache
&& [self isUsingLocalCacheWithPolicy: cachePolicy]
&& [self isKindOfClass: [NSURLSessionDataTask class]])
{
ASSIGN(_protocolBag, [NSMutableArray array]);
[_protocolBag addObject: completion];
_protocolState = NSURLSessionTaskProtocolStateAwaitingCacheReply;
[_protocolLock unlock];
cachedResponse =
[cache cachedResponseForDataTask: (NSURLSessionDataTask*)self];
_protocol = [[_protocolClass alloc] initWithTask: self
cachedResponse: cachedResponse
client: nil];
[self satisfyProtocolRequest];
}
else
{
_protocol = [[_protocolClass alloc] initWithTask: self
cachedResponse: nil
client: nil];
_protocolState = NSURLSessionTaskProtocolStateExisting;
[_protocolLock unlock];
completion(_protocol);
}
break;
}
case NSURLSessionTaskProtocolStateAwaitingCacheReply:
{
[_protocolBag addObject: completion];
[_protocolLock unlock];
break;
}
case NSURLSessionTaskProtocolStateExisting:
{
[_protocolLock unlock];
completion(_protocol);
break;
}
case NSURLSessionTaskProtocolStateInvalidated:
{
[_protocolLock unlock];
completion(nil);
break;
}
}
}
- (void) satisfyProtocolRequest
{
[_protocolLock lock];
switch (_protocolState)
{
case NSURLSessionTaskProtocolStateToBeCreated:
{
_protocolState = NSURLSessionTaskProtocolStateExisting;
[_protocolLock unlock];
break;
}
case NSURLSessionTaskProtocolStateAwaitingCacheReply:
{
_protocolState = NSURLSessionTaskProtocolStateExisting;
[_protocolLock unlock];
void (^callback)(NSURLProtocol*);
for (int i = 0; i < [_protocolBag count]; i++)
{
callback = [_protocolBag objectAtIndex: i];
callback(_protocol);
}
DESTROY(_protocolBag);
break;
}
case NSURLSessionTaskProtocolStateExisting:
case NSURLSessionTaskProtocolStateInvalidated:
{
[_protocolLock unlock];
break;
}
}
}
- (BOOL) isUsingLocalCacheWithPolicy: (NSURLRequestCachePolicy)policy
{
switch (policy)
{
case NSURLRequestUseProtocolCachePolicy:
return true;
case NSURLRequestReloadIgnoringLocalCacheData:
return false;
case NSURLRequestReturnCacheDataElseLoad:
return true;
case NSURLRequestReturnCacheDataDontLoad:
return true;
case NSURLRequestReloadIgnoringLocalAndRemoteCacheData:
case NSURLRequestReloadRevalidatingCacheData:
return false;
default:
return false;
}
}
- (NSURLSession*) session
{
return _session;
}
- (void) setState: (NSURLSessionTaskState)state
{
_state = state;
}
- (void) invalidateProtocol
{
[_protocolLock lock];
_protocolState = NSURLSessionTaskProtocolStateInvalidated;
DESTROY(_protocol);
/*
* Release session at the end of the task
* and not when -dealloc is called.
*/
DESTROY(_session);
[_protocolLock unlock];
}
- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler
{
return _dataCompletionHandler;
}
- (void) setDataCompletionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler
{
ASSIGN(_dataCompletionHandler, completionHandler);
}
- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler
{
return _downloadCompletionHandler;
}
- (void) setDownloadCompletionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler
{
ASSIGN(_downloadCompletionHandler, completionHandler);
}
@end
@implementation NSURLSessionDataTask
@end
@implementation NSURLSessionUploadTask
@end
@implementation NSURLSessionDownloadTask
@end
@implementation NSURLSessionConfiguration
static NSURLSessionConfiguration *def = nil;
+ (void) initialize
{
if (nil == def)
{
def = [NSURLSessionConfiguration new];
}
}
+ (NSURLSessionConfiguration*) defaultSessionConfiguration
{
return AUTORELEASE([def copy]);
}
+ (NSURLSessionConfiguration*) ephemeralSessionConfiguration
{
// return default session since we don't store any data on disk anyway
return AUTORELEASE([def copy]);
}
+ (NSURLSessionConfiguration*) backgroundSessionConfigurationWithIdentifier:(NSString*)identifier
{
NSURLSessionConfiguration *configuration = [def copy];
configuration->_identifier = [identifier copy];
return AUTORELEASE(configuration);
}
- (instancetype) init
{
if (nil != (self = [super init]))
{
_protocolClasses = [[NSArray alloc] initWithObjects:
[GSHTTPURLProtocol class], nil];
_HTTPMaximumConnectionsPerHost = 1;
_HTTPShouldUsePipelining = YES;
_HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
_HTTPCookieStorage = nil;
_HTTPShouldSetCookies = NO;
_HTTPAdditionalHeaders = nil;
_HTTPMaximumConnectionLifetime = 0; // Zero or less means default
}
return self;
}
- (void) dealloc
{
DESTROY(_identifier);
DESTROY(_HTTPAdditionalHeaders);
DESTROY(_HTTPCookieStorage);
DESTROY(_protocolClasses);
DESTROY(_URLCache);
DESTROY(_URLCredentialStorage);
[super dealloc];
}
- (NSString*) identifier
{
return _identifier;
}
- (NSURLCache*) URLCache
{
return _URLCache;
}
- (void) setURLCache: (NSURLCache*)cache
{
ASSIGN(_URLCache, cache);
}
- (void) setURLCredentialStorage: (NSURLCredentialStorage*)storage
{
ASSIGN(_URLCredentialStorage, storage);
}
- (NSURLRequestCachePolicy) requestCachePolicy
{
return _requestCachePolicy;
}
- (void) setRequestCachePolicy: (NSURLRequestCachePolicy)policy
{
_requestCachePolicy = policy;
}
- (NSArray*) protocolClasses
{
return _protocolClasses;
}
- (NSInteger) HTTPMaximumConnectionsPerHost
{
return _HTTPMaximumConnectionsPerHost;
}
- (void) setHTTPMaximumConnectionsPerHost: (NSInteger)n
{
_HTTPMaximumConnectionsPerHost = n;
}
- (NSInteger) HTTPMaximumConnectionLifetime
{
return _HTTPMaximumConnectionLifetime;
}
- (void) setHTTPMaximumConnectionLifetime: (NSInteger)n
{
_HTTPMaximumConnectionLifetime = n;
}
- (BOOL) HTTPShouldUsePipelining
{
return _HTTPShouldUsePipelining;
}
- (void) setHTTPShouldUsePipelining: (BOOL)flag
{
_HTTPShouldUsePipelining = flag;
}
- (NSHTTPCookieAcceptPolicy) HTTPCookieAcceptPolicy
{
return _HTTPCookieAcceptPolicy;
}
- (void) setHTTPCookieAcceptPolicy: (NSHTTPCookieAcceptPolicy)policy
{
_HTTPCookieAcceptPolicy = policy;
}
- (NSHTTPCookieStorage*) HTTPCookieStorage
{
return _HTTPCookieStorage;
}
- (void) setHTTPCookieStorage: (NSHTTPCookieStorage*)storage
{
ASSIGN(_HTTPCookieStorage, storage);
}
- (BOOL) HTTPShouldSetCookies
{
return _HTTPShouldSetCookies;
}
- (void) setHTTPShouldSetCookies: (BOOL)flag
{
_HTTPShouldSetCookies = flag;
}
- (NSDictionary*) HTTPAdditionalHeaders
{
return _HTTPAdditionalHeaders;
}
- (void) setHTTPAdditionalHeaders: (NSDictionary*)headers
{
ASSIGN(_HTTPAdditionalHeaders, headers);
}
- (NSURLRequest*) configureRequest: (NSURLRequest*)request
{
return [self setCookiesOnRequest: request];
}
- (NSURLRequest*) setCookiesOnRequest: (NSURLRequest*)request
{
NSMutableURLRequest *r = AUTORELEASE([request mutableCopy]);
if (_HTTPShouldSetCookies)
{
if (nil != _HTTPCookieStorage && nil != [request URL])
{
NSArray *cookies = [_HTTPCookieStorage cookiesForURL: [request URL]];
if (nil != cookies && [cookies count] > 0)
{
NSDictionary *cookiesHeaderFields;
NSString *cookieValue;
cookiesHeaderFields
= [NSHTTPCookie requestHeaderFieldsWithCookies: cookies];
cookieValue = [cookiesHeaderFields objectForKey: @"Cookie"];
if (nil != cookieValue && [cookieValue length] > 0)
{
[r setValue: cookieValue forHTTPHeaderField: @"Cookie"];
}
}
}
}
return AUTORELEASE([r copy]);
}
- (NSURLCredentialStorage*) URLCredentialStorage
{
return _URLCredentialStorage;
}
- (id) copyWithZone: (NSZone*)zone
{
NSURLSessionConfiguration *copy = [[[self class] alloc] init];
if (copy)
{
copy->_identifier = [_identifier copy];
copy->_URLCache = [_URLCache copy];
copy->_URLCredentialStorage = [_URLCredentialStorage copy];
copy->_protocolClasses = [_protocolClasses copyWithZone: zone];
copy->_HTTPMaximumConnectionsPerHost = _HTTPMaximumConnectionsPerHost;
copy->_HTTPShouldUsePipelining = _HTTPShouldUsePipelining;
copy->_HTTPCookieAcceptPolicy = _HTTPCookieAcceptPolicy;
copy->_HTTPCookieStorage = [_HTTPCookieStorage copy];
copy->_HTTPShouldSetCookies = _HTTPShouldSetCookies;
copy->_HTTPAdditionalHeaders
= [_HTTPAdditionalHeaders copyWithZone: zone];
}
return copy;
}
@end