mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-25 17:51:01 +00:00
818 lines
24 KiB
Objective-C
818 lines
24 KiB
Objective-C
#import "GSURLPrivate.h"
|
|
#import "GSNativeProtocol.h"
|
|
#import "GSTransferState.h"
|
|
#import "GSURLSessionTaskBody.h"
|
|
#import "Foundation/NSData.h"
|
|
#import "Foundation/NSDictionary.h"
|
|
#import "Foundation/NSError.h"
|
|
#import "Foundation/NSException.h"
|
|
#import "Foundation/NSOperation.h"
|
|
#import "Foundation/NSURL.h"
|
|
#import "Foundation/NSURLError.h"
|
|
#import "Foundation/NSURLSession.h"
|
|
#import "Foundation/NSFileHandle.h"
|
|
|
|
|
|
static BOOL isEasyHandlePaused(GSNativeProtocolInternalState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case GSNativeProtocolInternalStateInitial:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateFulfillingFromCache:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateTransferReady:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateTransferInProgress:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateTransferCompleted:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateTransferFailed:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateWaitingForRedirectCompletionHandler:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateWaitingForResponseCompletionHandler:
|
|
return YES;
|
|
case GSNativeProtocolInternalStateTaskCompleted:
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case GSNativeProtocolInternalStateInitial:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateFulfillingFromCache:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateTransferReady:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateTransferInProgress:
|
|
return YES;
|
|
case GSNativeProtocolInternalStateTransferCompleted:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateTransferFailed:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateWaitingForRedirectCompletionHandler:
|
|
return NO;
|
|
case GSNativeProtocolInternalStateWaitingForResponseCompletionHandler:
|
|
return YES;
|
|
case GSNativeProtocolInternalStateTaskCompleted:
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
@interface NSURLSession (GSNativeProtocolInternal)
|
|
|
|
- (void) removeHandle: (GSEasyHandle*)handle;
|
|
|
|
- (void) addHandle: (GSEasyHandle*)handle;
|
|
|
|
@end
|
|
|
|
@implementation NSURLSession (GSNativeProtocolInternal)
|
|
|
|
- (void) removeHandle: (GSEasyHandle*)handle
|
|
{
|
|
[_multiHandle removeHandle: handle];
|
|
}
|
|
|
|
- (void) addHandle: (GSEasyHandle*)handle
|
|
{
|
|
[_multiHandle addHandle: handle];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation NSURLSessionTask (GSNativeProtocolInternal)
|
|
|
|
- (void) setCurrentRequest: (NSURLRequest*)request
|
|
{
|
|
ASSIGN(_currentRequest, request);
|
|
}
|
|
|
|
- (dispatch_queue_t) workQueue
|
|
{
|
|
return _workQueue;
|
|
}
|
|
|
|
- (NSUInteger) suspendCount
|
|
{
|
|
return _suspendCount;
|
|
}
|
|
|
|
- (void) getBodyWithCompletion: (void (^)(GSURLSessionTaskBody *body))completion
|
|
{
|
|
GSURLSessionTaskBody *body;
|
|
|
|
if (nil != _knownBody)
|
|
{
|
|
completion(_knownBody);
|
|
return;
|
|
};
|
|
|
|
body = AUTORELEASE([[GSURLSessionTaskBody alloc] init]);
|
|
completion(body);
|
|
}
|
|
|
|
- (GSURLSessionTaskBody*) knownBody
|
|
{
|
|
return _knownBody;
|
|
}
|
|
|
|
- (void) setKnownBody: (GSURLSessionTaskBody*)body
|
|
{
|
|
ASSIGN(_knownBody, body);
|
|
}
|
|
|
|
- (void) setError: (NSError*)error
|
|
{
|
|
ASSIGN(_error, error);
|
|
}
|
|
|
|
- (void) setCountOfBytesReceived: (int64_t)count
|
|
{
|
|
_countOfBytesReceived = count;
|
|
}
|
|
|
|
- (void) setCountOfBytesExpectedToReceive: (int64_t)count
|
|
{
|
|
_countOfBytesExpectedToReceive = count;
|
|
}
|
|
|
|
- (void) setCountOfBytesExpectedToSend: (int64_t)count
|
|
{
|
|
_countOfBytesExpectedToSend = count;
|
|
}
|
|
|
|
- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler
|
|
{
|
|
return _dataCompletionHandler;
|
|
}
|
|
|
|
- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler
|
|
{
|
|
return _downloadCompletionHandler;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation GSCompletionAction
|
|
|
|
- (void) dealloc
|
|
{
|
|
DESTROY(_redirectRequest);
|
|
[super dealloc];
|
|
}
|
|
|
|
- (GSCompletionActionType) type
|
|
{
|
|
return _type;
|
|
}
|
|
|
|
- (void) setType: (GSCompletionActionType) type
|
|
{
|
|
_type = type;
|
|
}
|
|
|
|
- (int) errorCode
|
|
{
|
|
return _errorCode;
|
|
}
|
|
|
|
- (void) setErrorCode: (int)code
|
|
{
|
|
_errorCode = code;
|
|
}
|
|
|
|
- (NSURLRequest*) redirectRequest
|
|
{
|
|
return _redirectRequest;
|
|
}
|
|
|
|
- (void) setRedirectRequest: (NSURLRequest*)request
|
|
{
|
|
ASSIGN(_redirectRequest, request);
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation GSNativeProtocol
|
|
|
|
+ (BOOL) canInitWithRequest: (NSURLRequest*)request
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (instancetype) initWithTask: (NSURLSessionTask*)_task
|
|
cachedResponse: (NSCachedURLResponse*)_cachedResponse
|
|
client: (id<NSURLProtocolClient>)_client
|
|
{
|
|
if (nil != (self = [super initWithTask: _task
|
|
cachedResponse: _cachedResponse
|
|
client: _client]))
|
|
{
|
|
_internalState = GSNativeProtocolInternalStateInitial;
|
|
_easyHandle = [[GSEasyHandle alloc] initWithDelegate: self];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
DESTROY(_easyHandle);
|
|
DESTROY(_transferState);
|
|
[super dealloc];
|
|
}
|
|
|
|
+ (NSURLRequest*) canonicalRequestForRequest: (NSURLRequest*)request
|
|
{
|
|
return request;
|
|
}
|
|
|
|
- (void) startLoading
|
|
{
|
|
[self resume];
|
|
}
|
|
|
|
- (void) stopLoading
|
|
{
|
|
NSURLSessionTask *task;
|
|
|
|
if (nil != (task = [self task])
|
|
&& NSURLSessionTaskStateSuspended == [task state])
|
|
{
|
|
[self suspend];
|
|
}
|
|
else
|
|
{
|
|
[self setInternalState: GSNativeProtocolInternalStateTransferFailed];
|
|
NSAssert(nil != [task error], @"Missing error for failed task");
|
|
[self completeTaskWithError: [task error]];
|
|
}
|
|
}
|
|
|
|
- (void) setInternalState: (GSNativeProtocolInternalState)newState
|
|
{
|
|
NSURLSessionTask *task;
|
|
GSNativeProtocolInternalState oldState;
|
|
|
|
if (!isEasyHandlePaused(_internalState) && isEasyHandlePaused(newState))
|
|
{
|
|
NSAssert(NO, @"Need to solve pausing receive.");
|
|
}
|
|
|
|
if (isEasyHandleAddedToMultiHandle(_internalState)
|
|
&& !isEasyHandleAddedToMultiHandle(newState))
|
|
{
|
|
if (nil != (task = [self task]))
|
|
{
|
|
[[task session] removeHandle: _easyHandle];
|
|
}
|
|
}
|
|
|
|
oldState = _internalState;
|
|
_internalState = newState;
|
|
|
|
if (!isEasyHandleAddedToMultiHandle(oldState)
|
|
&& isEasyHandleAddedToMultiHandle(_internalState))
|
|
{
|
|
if (nil != (task = [self task]))
|
|
{
|
|
[[task session] addHandle: _easyHandle];
|
|
}
|
|
}
|
|
if (isEasyHandlePaused(oldState) && !isEasyHandlePaused(_internalState))
|
|
{
|
|
NSAssert(NO, @"Need to solve pausing receive.");
|
|
}
|
|
}
|
|
|
|
- (void) startNewTransferWithRequest: (NSURLRequest*)request
|
|
{
|
|
NSURLSessionTask *task = [self task];
|
|
|
|
[task setCurrentRequest: request];
|
|
|
|
NSAssert(nil != [request URL], @"No URL in request.");
|
|
|
|
[task getBodyWithCompletion: ^(GSURLSessionTaskBody *body)
|
|
{
|
|
[task setKnownBody: body];
|
|
[self setInternalState: GSNativeProtocolInternalStateTransferReady];
|
|
ASSIGN(_transferState,
|
|
[self createTransferStateWithURL: [request URL]
|
|
body: body
|
|
workQueue: [task workQueue]]);
|
|
[self configureEasyHandleForRequest: request body: body];
|
|
if ([task suspendCount] < 1)
|
|
{
|
|
[self resume];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void) configureEasyHandleForRequest: (NSURLRequest*)request
|
|
body: (GSURLSessionTaskBody*)body
|
|
{
|
|
NSAssert(NO, @"Requires concrete implementation");
|
|
}
|
|
|
|
- (GSTransferState*) createTransferStateWithURL: (NSURL*)url
|
|
body: (GSURLSessionTaskBody*)body
|
|
workQueue: (dispatch_queue_t)workQueue
|
|
{
|
|
GSDataDrain *drain = [self createTransferBodyDataDrain];
|
|
|
|
switch ([body type])
|
|
{
|
|
case GSURLSessionTaskBodyTypeNone:
|
|
return AUTORELEASE([[GSTransferState alloc] initWithURL: url
|
|
bodyDataDrain: drain]);
|
|
case GSURLSessionTaskBodyTypeData:
|
|
{
|
|
GSBodyDataSource *source;
|
|
|
|
source = AUTORELEASE([[GSBodyDataSource alloc] initWithData: [body data]]);
|
|
return AUTORELEASE([[GSTransferState alloc] initWithURL: url
|
|
bodyDataDrain: drain
|
|
bodySource: source]);
|
|
}
|
|
case GSURLSessionTaskBodyTypeFile:
|
|
{
|
|
GSBodyFileSource *source;
|
|
|
|
source = AUTORELEASE([[GSBodyFileSource alloc] initWithFileURL: [body fileURL]
|
|
workQueue: workQueue
|
|
dataAvailableHandler: ^{
|
|
[_easyHandle unpauseSend];
|
|
}]);
|
|
return AUTORELEASE([[GSTransferState alloc] initWithURL: url
|
|
bodyDataDrain: drain
|
|
bodySource: source]);
|
|
}
|
|
case GSURLSessionTaskBodyTypeStream:
|
|
{
|
|
GSBodyStreamSource *source;
|
|
|
|
source = AUTORELEASE([[GSBodyStreamSource alloc] initWithInputStream: [body inputStream]]);
|
|
return AUTORELEASE([[GSTransferState alloc] initWithURL: url
|
|
bodyDataDrain: drain
|
|
bodySource: source]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// The data drain.
|
|
// This depends on what the task needs.
|
|
- (GSDataDrain*) createTransferBodyDataDrain
|
|
{
|
|
NSURLSessionTask *task = [self task];
|
|
GSDataDrain *dd = AUTORELEASE([[GSDataDrain alloc] init]);
|
|
|
|
if ([task isKindOfClass: [NSURLSessionDownloadTask class]])
|
|
{
|
|
// drain to file for download tasks
|
|
[dd setType: GSDataDrainTypeToFile];
|
|
}
|
|
else if ([task dataCompletionHandler])
|
|
{
|
|
// drain to memory if task has a completion handler, which requires the
|
|
// full body to be passed on completion
|
|
[dd setType: GSDataDrainInMemory];
|
|
}
|
|
else
|
|
{
|
|
// otherwise the data is probably sent to the delegate as it arrives
|
|
[dd setType: GSDataDrainTypeIgnore];
|
|
}
|
|
return dd;
|
|
}
|
|
|
|
- (void) resume
|
|
{
|
|
NSURLSessionTask *task;
|
|
|
|
task = [self task];
|
|
|
|
if (_internalState == GSNativeProtocolInternalStateInitial)
|
|
{
|
|
NSAssert(nil != [task originalRequest], @"Task has no original request.");
|
|
|
|
// Check if the cached response is good to use:
|
|
if (nil != [self cachedResponse]
|
|
&& [self canRespondFromCacheUsing: [self cachedResponse]])
|
|
{
|
|
[self setInternalState:
|
|
GSNativeProtocolInternalStateFulfillingFromCache];
|
|
dispatch_async([task workQueue],
|
|
^{
|
|
id<NSURLProtocolClient> client;
|
|
|
|
client = [self client];
|
|
[client URLProtocol: self
|
|
cachedResponseIsValid: [self cachedResponse]];
|
|
[client URLProtocol: self
|
|
didReceiveResponse: [[self cachedResponse] response]
|
|
cacheStoragePolicy: NSURLCacheStorageNotAllowed];
|
|
if ([[[self cachedResponse] data] length] > 0)
|
|
{
|
|
if ([client respondsToSelector:
|
|
@selector(URLProtocol:didLoad:)])
|
|
{
|
|
[client URLProtocol: self
|
|
didLoadData: [[self cachedResponse] data]];
|
|
}
|
|
}
|
|
if ([client respondsToSelector:
|
|
@selector(URLProtocolDidFinishLoading:)])
|
|
{
|
|
[client URLProtocolDidFinishLoading: self];
|
|
}
|
|
[self setInternalState:
|
|
GSNativeProtocolInternalStateTaskCompleted];
|
|
});
|
|
}
|
|
else
|
|
{
|
|
[self startNewTransferWithRequest: [task originalRequest]];
|
|
}
|
|
}
|
|
|
|
if (_internalState == GSNativeProtocolInternalStateTransferReady
|
|
&& nil != _transferState)
|
|
{
|
|
[self setInternalState: GSNativeProtocolInternalStateTransferInProgress];
|
|
}
|
|
}
|
|
|
|
- (BOOL) canRespondFromCacheUsing: (NSCachedURLResponse*)response
|
|
{
|
|
// Allows a native protocol to process a cached response.
|
|
// If `YES` is returned, the protocol will replay the cached response
|
|
// instead of starting a new transfer. The default implementation invalidates
|
|
// the response in the cache and returns `NO`.
|
|
NSURLCache *cache;
|
|
NSURLSessionTask *task;
|
|
|
|
task = [self task];
|
|
cache = [[[task session] configuration] URLCache];
|
|
if (nil != cache && [task isKindOfClass: [NSURLSessionDataTask class]])
|
|
{
|
|
[cache removeCachedResponseForDataTask: (NSURLSessionDataTask*)task];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void) suspend
|
|
{
|
|
if (_internalState == GSNativeProtocolInternalStateTransferInProgress)
|
|
{
|
|
[self setInternalState: GSNativeProtocolInternalStateTransferReady];
|
|
}
|
|
}
|
|
|
|
- (void) completeTaskWithError: (NSError*)error
|
|
{
|
|
[[self task] setError: error];
|
|
NSAssert(_internalState == GSNativeProtocolInternalStateTransferFailed,
|
|
@"Trying to complete the task, but its transfer isn't complete / failed.");
|
|
|
|
// We don't want a timeout to be triggered after this.
|
|
// The timeout timer needs to be cancelled.
|
|
[_easyHandle setTimeoutTimer: nil];
|
|
|
|
[self setInternalState: GSNativeProtocolInternalStateTaskCompleted];
|
|
}
|
|
|
|
- (GSEasyHandleAction) didReceiveData: (NSData*)data
|
|
{
|
|
NSURLResponse *response;
|
|
|
|
NSAssert(GSNativeProtocolInternalStateTransferInProgress == _internalState,
|
|
@"Received body data, but no transfer in progress.");
|
|
|
|
response = [self validateHeaderCompleteTransferState: _transferState];
|
|
|
|
if (nil != response)
|
|
{
|
|
[_transferState setResponse: response];
|
|
}
|
|
|
|
[self notifyDelegateAboutReceivedData: data];
|
|
|
|
_internalState = GSNativeProtocolInternalStateTransferInProgress;
|
|
ASSIGN(_transferState, [_transferState byAppendingBodyData: data]);
|
|
|
|
return GSEasyHandleActionProceed;
|
|
}
|
|
|
|
- (NSURLResponse*) validateHeaderCompleteTransferState: (GSTransferState*)ts
|
|
{
|
|
if (![ts isHeaderComplete])
|
|
{
|
|
NSAssert(NO, @"Received body data, but the header is not complete, yet.");
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (void) notifyDelegateAboutReceivedData: (NSData*)data
|
|
{
|
|
NSURLSession *session;
|
|
NSURLSessionTask *task;
|
|
id<NSURLSessionDelegate> delegate;
|
|
|
|
task = [self task];
|
|
NSAssert(nil != task, @"Cannot notify");
|
|
|
|
session = [task session];
|
|
NSAssert(nil != session, @"Missing session");
|
|
|
|
/* Calculate received data length */
|
|
[task setCountOfBytesReceived: (int64_t)[data length] + [task countOfBytesReceived]];
|
|
|
|
delegate = [session delegate];
|
|
if (nil != delegate
|
|
&& [task isKindOfClass: [NSURLSessionDataTask class]]
|
|
&& [delegate respondsToSelector: @selector(URLSession:dataTask:didReceiveData:)])
|
|
{
|
|
id<NSURLSessionDataDelegate> dataDelegate;
|
|
NSURLSessionDataTask *dataTask;
|
|
|
|
dataDelegate = (id<NSURLSessionDataDelegate>)delegate;
|
|
dataTask = (NSURLSessionDataTask*)task;
|
|
|
|
[[session delegateQueue] addOperationWithBlock:
|
|
^{
|
|
[dataDelegate URLSession: session
|
|
dataTask: dataTask
|
|
didReceiveData: data];
|
|
}];
|
|
}
|
|
|
|
if (nil != delegate
|
|
&& [task isKindOfClass: [NSURLSessionDownloadTask class]]
|
|
&& [delegate respondsToSelector: @selector
|
|
(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)])
|
|
{
|
|
id<NSURLSessionDownloadDelegate> downloadDelegate;
|
|
NSURLSessionDownloadTask *downloadTask;
|
|
|
|
downloadDelegate = (id<NSURLSessionDownloadDelegate>)delegate;
|
|
downloadTask = (NSURLSessionDownloadTask*)task;
|
|
|
|
[[session delegateQueue] addOperationWithBlock:
|
|
^{
|
|
[downloadDelegate URLSession: session
|
|
downloadTask: downloadTask
|
|
didWriteData: (int64_t)[data length]
|
|
totalBytesWritten: [task countOfBytesReceived]
|
|
totalBytesExpectedToWrite: [task countOfBytesExpectedToReceive]];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void) notifyDelegateAboutUploadedDataCount: (int64_t)count
|
|
{
|
|
NSURLSessionTask *task;
|
|
id<NSURLSessionDelegate> delegate;
|
|
|
|
task = [self task];
|
|
|
|
NSAssert(nil != task, @"Cannot notify");
|
|
|
|
delegate = [[task session] delegate];
|
|
if (nil != delegate
|
|
&& [task isKindOfClass: [NSURLSessionUploadTask class]]
|
|
&& [delegate respondsToSelector: @selector(URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)])
|
|
{
|
|
id<NSURLSessionTaskDelegate> taskDelegate;
|
|
NSURLSession *session;
|
|
|
|
session = [task session];
|
|
NSAssert(nil != session, @"Missing session");
|
|
taskDelegate = (id<NSURLSessionTaskDelegate>)delegate;
|
|
[[session delegateQueue] addOperationWithBlock:
|
|
^{
|
|
[taskDelegate URLSession: session
|
|
task: task
|
|
didSendBodyData: count
|
|
totalBytesSent: [task countOfBytesSent]
|
|
totalBytesExpectedToSend: [task countOfBytesExpectedToSend]];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (GSEasyHandleAction) didReceiveHeaderData: (NSData*)data
|
|
contentLength: (int64_t)contentLength
|
|
{
|
|
NSAssert(NO, @"Require concrete implementation");
|
|
return GSEasyHandleActionAbort;
|
|
}
|
|
|
|
- (void) fillWriteBufferLength: (NSInteger)length
|
|
result: (void (^)(GSEasyHandleWriteBufferResult result, NSInteger length, NSData *data))result
|
|
{
|
|
id<GSURLSessionTaskBodySource> source;
|
|
|
|
NSAssert(GSNativeProtocolInternalStateTransferInProgress == _internalState,
|
|
@"Requested to fill write buffer, but transfer isn't in progress.");
|
|
|
|
source = [_transferState requestBodySource];
|
|
|
|
NSAssert(nil != source,
|
|
@"Requested to fill write buffer, but transfer state has no body source.");
|
|
|
|
if (nil == result)
|
|
{
|
|
return;
|
|
}
|
|
|
|
[source getNextChunkWithLength: length
|
|
completionHandler: ^(GSBodySourceDataChunk chunk, NSData *_Nullable data)
|
|
{
|
|
switch (chunk)
|
|
{
|
|
case GSBodySourceDataChunkData:
|
|
{
|
|
NSUInteger count = [data length];
|
|
[self notifyDelegateAboutUploadedDataCount: (int64_t)count];
|
|
result(GSEasyHandleWriteBufferResultBytes, count, data);
|
|
break;
|
|
}
|
|
case GSBodySourceDataChunkDone:
|
|
result(GSEasyHandleWriteBufferResultBytes, 0, nil);
|
|
break;
|
|
case GSBodySourceDataChunkRetryLater:
|
|
// At this point we'll try to pause the easy handle. The body
|
|
// source is responsible for un-pausing the handle once data
|
|
// becomes available.
|
|
result(GSEasyHandleWriteBufferResultPause, -1, nil);
|
|
break;
|
|
case GSBodySourceDataChunkError:
|
|
result(GSEasyHandleWriteBufferResultAbort, -1, nil);
|
|
break;
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void) transferCompletedWithError: (NSError*)error
|
|
{
|
|
/* At this point the transfer is complete and we can decide what to do.
|
|
* If everything went well, we will simply forward the resulting data
|
|
* to the delegate. But in case of redirects etc. we might send another
|
|
* request.
|
|
*/
|
|
NSURLRequest *request;
|
|
NSURLResponse *response;
|
|
GSCompletionAction *action;
|
|
|
|
if (nil != error)
|
|
{
|
|
[self setInternalState: GSNativeProtocolInternalStateTransferFailed];
|
|
[self failWithError: error request: [self request]];
|
|
return;
|
|
}
|
|
|
|
NSAssert(_internalState == GSNativeProtocolInternalStateTransferInProgress,
|
|
@"Transfer completed, but it wasn't in progress.");
|
|
|
|
request = [[self task] currentRequest];
|
|
NSAssert(nil != request,
|
|
@"Transfer completed, but there's no current request.");
|
|
|
|
if (nil != [[self task] response])
|
|
{
|
|
[_transferState setResponse: [[self task] response]];
|
|
}
|
|
|
|
response = [_transferState response];
|
|
NSAssert(nil != response, @"Transfer completed, but there's no response.");
|
|
|
|
[self setInternalState: GSNativeProtocolInternalStateTransferCompleted];
|
|
|
|
action = [self completeActionForCompletedRequest: request response: response];
|
|
switch ([action type])
|
|
{
|
|
case GSCompletionActionTypeCompleteTask:
|
|
[self completeTask];
|
|
break;
|
|
case GSCompletionActionTypeFailWithError:
|
|
[self setInternalState: GSNativeProtocolInternalStateTransferFailed];
|
|
error = [NSError errorWithDomain: NSURLErrorDomain
|
|
code: [action errorCode]
|
|
userInfo: nil];
|
|
[self failWithError: error request: request];
|
|
break;
|
|
case GSCompletionActionTypeRedirectWithRequest:
|
|
[self redirectForRequest: [action redirectRequest]];
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (GSCompletionAction*) completeActionForCompletedRequest: (NSURLRequest*)request
|
|
response: (NSURLResponse*)response
|
|
{
|
|
GSCompletionAction *action;
|
|
|
|
action = AUTORELEASE([[GSCompletionAction alloc] init]);
|
|
[action setType: GSCompletionActionTypeCompleteTask];
|
|
|
|
return action;
|
|
}
|
|
|
|
- (void) completeTask
|
|
{
|
|
NSURLSessionTask *task;
|
|
GSDataDrain *bodyDataDrain;
|
|
id<NSURLProtocolClient> client;
|
|
|
|
NSAssert(_internalState == GSNativeProtocolInternalStateTransferCompleted,
|
|
@"Trying to complete the task, but its transfer isn't complete.");
|
|
|
|
task = [self task];
|
|
[task setResponse: [_transferState response]];
|
|
client = [self client];
|
|
|
|
// We don't want a timeout to be triggered after this. The timeout timer
|
|
// needs to be cancelled.
|
|
[_easyHandle setTimeoutTimer: nil];
|
|
|
|
[self setInternalState: GSNativeProtocolInternalStateTaskCompleted];
|
|
|
|
// Add complete data to NSURLRequestProperties if the task has a data
|
|
// completion handler
|
|
bodyDataDrain = [_transferState bodyDataDrain];
|
|
if (GSDataDrainInMemory == [bodyDataDrain type])
|
|
{
|
|
NSData *data = AUTORELEASE([[bodyDataDrain data] copy]);
|
|
[[self request] _setProperty: data
|
|
forKey: @"tempData"];
|
|
}
|
|
|
|
// Add temporary file URL to NSURLRequest properties
|
|
// and close the fileHandle
|
|
if ([task isKindOfClass: [NSURLSessionDownloadTask class]])
|
|
{
|
|
[[bodyDataDrain fileHandle] closeFile];
|
|
[[self request] _setProperty: [bodyDataDrain fileURL]
|
|
forKey: @"tempFileURL"];
|
|
}
|
|
|
|
if ([client respondsToSelector: @selector(URLProtocolDidFinishLoading:)])
|
|
{
|
|
[client URLProtocolDidFinishLoading: self];
|
|
}
|
|
}
|
|
|
|
- (void) redirectForRequest: (NSURLRequest*)request
|
|
{
|
|
NSAssert(NO, @"Require concrete implementation");
|
|
}
|
|
|
|
- (void) failWithError: (NSError*)error request: (NSURLRequest*)request
|
|
{
|
|
NSDictionary *info;
|
|
NSError *urlError;
|
|
id<NSURLProtocolClient> client;
|
|
|
|
info = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
error, NSUnderlyingErrorKey,
|
|
[request URL], NSURLErrorFailingURLErrorKey,
|
|
[[request URL] absoluteString], NSURLErrorFailingURLStringErrorKey,
|
|
[error localizedDescription], NSLocalizedDescriptionKey, nil];
|
|
|
|
urlError = [NSError errorWithDomain: NSURLErrorDomain
|
|
code: [error code]
|
|
userInfo: info];
|
|
[self completeTaskWithError: urlError];
|
|
|
|
client = [self client];
|
|
if ([client respondsToSelector: @selector(URLProtocol:didFailWithError:)])
|
|
{
|
|
[client URLProtocol: self didFailWithError: urlError];
|
|
}
|
|
}
|
|
|
|
- (BOOL) seekInputStreamToPosition: (uint64_t)position
|
|
{
|
|
//TODO implement seek for NSURLSessionUploadTask
|
|
return NO;
|
|
}
|
|
|
|
- (void) updateProgressMeterWithTotalBytesSent: (int64_t)totalBytesSent
|
|
totalBytesExpectedToSend: (int64_t)totalBytesExpectedToSend
|
|
totalBytesReceived: (int64_t)totalBytesReceived
|
|
totalBytesExpectedToReceive: (int64_t)totalBytesExpectedToReceive
|
|
{
|
|
// TODO: Update progress. Note that a single NSURLSessionTask might
|
|
// perform multiple transfers. The values in `progress` are only for
|
|
// the current transfer.
|
|
}
|
|
|
|
@end
|
|
|
|
|