NSURLSession rewrite (#422)

* clang-format: Do not use tabs

* Ignore clangd cache and compile_commands

* NSBlockOperation: Fix memory leak

* NSHTTPCookie: Fix expires date parsing

* NSHTTPCookie: Release DateFormatter after use

* NSOperation: Remove all objects at end of execution

* Reimplementation of NSURLSession

* NSURLSession: Update expiration dates in test

* Update ChangeLog

* Fix trivial compiler warning caused by missing import

* Import GSDispatch.h for definition of DISPATCH_QUEUE_SERIAL

* Import common.h early to avoid header conflict

* Fix import order to avoid conflicts and ensure we have correct localisation macro

* Check for presence of dispatch_cancel

* Cancel timer using dispatch_source_cancel() if dispatch_cancel() is missing.

* NSURLSession: Replace dispatch_io with dispatch_source in unit test HTTP server

---------

Co-authored-by: hmelder <service@hugomelder.com>
This commit is contained in:
rfm 2024-08-16 13:08:41 +01:00 committed by rfm
parent e3208590b1
commit a1913e5540
53 changed files with 5410 additions and 7178 deletions

View file

@ -374,16 +374,9 @@ ifeq ($(HAVE_BLOCKS), 1)
ifeq ($(GNUSTEP_BASE_HAVE_LIBDISPATCH), 1)
ifeq ($(GNUSTEP_BASE_HAVE_LIBCURL), 1)
BASE_MFILES += \
GSEasyHandle.m \
GSHTTPURLProtocol.m \
GSMultiHandle.m \
GSNativeProtocol.m \
GSTaskRegistry.m \
GSTimeoutSource.m \
GSTransferState.m \
GSURLSessionTaskBody.m \
GSURLSessionTaskBodySource.m \
NSURLSession.m
NSURLSession.m \
NSURLSessionTask.m \
NSURLSessionConfiguration.m
endif
endif
endif

View file

@ -1,236 +0,0 @@
#ifndef INCLUDED_GSEASYHANDLE_H
#define INCLUDED_GSEASYHANDLE_H
#import "common.h"
#import <curl/curl.h>
@class NSData;
@class NSError;
@class NSURL;
@class NSURLSessionConfiguration;
@class NSURLSessionTask;
@class GSTimeoutSource;
typedef NS_ENUM(NSUInteger, GSEasyHandleAction) {
GSEasyHandleActionAbort,
GSEasyHandleActionProceed,
GSEasyHandleActionPause,
};
typedef NS_ENUM(NSUInteger, GSEasyHandleWriteBufferResult) {
GSEasyHandleWriteBufferResultAbort,
GSEasyHandleWriteBufferResultPause,
GSEasyHandleWriteBufferResultBytes,
};
@protocol GSEasyHandleDelegate <NSObject>
/*
* Handle data read from the network.
* - returns: the action to be taken: abort, proceed, or pause.
*/
- (GSEasyHandleAction) didReceiveData: (NSData*)data;
/*
* Handle header data read from the network.
* - returns: the action to be taken: abort, proceed, or pause.
*/
- (GSEasyHandleAction) didReceiveHeaderData: (NSData*)data
contentLength: (int64_t)contentLength;
/*
* Fill a buffer with data to be sent.
* - parameter data: The buffer to fill
* - returns: the number of bytes written to the `data` buffer, or `nil`
* to stop the current transfer immediately.
*/
- (void) fillWriteBufferLength: (NSInteger)length
result: (void (^)(GSEasyHandleWriteBufferResult result, NSInteger length, NSData *data))result;
/*
* The transfer for this handle completed.
* - parameter errorCode: An NSURLError code, or `nil` if no error occurred.
*/
- (void) transferCompletedWithError: (NSError*)error;
/*
* Seek the input stream to the given position
*/
- (BOOL) seekInputStreamToPosition: (uint64_t)position;
/*
* Gets called during the transfer to update progress.
*/
- (void) updateProgressMeterWithTotalBytesSent: (int64_t)totalBytesSent
totalBytesExpectedToSend: (int64_t)totalBytesExpectedToSend
totalBytesReceived: (int64_t)totalBytesReceived
totalBytesExpectedToReceive: (int64_t)totalBytesExpectedToReceive;
@end
/*
* Minimal wrapper around the curl easy interface
* (https://curl.haxx.se/libcurl/c/)
*
* An *easy handle* manages the state of a transfer inside libcurl.
*
* As such the easy handle's responsibility is implementing the HTTP
* protocol while the *multi handle* is in charge of managing sockets and
* reading from / writing to these sockets.
*
* An easy handle is added to a multi handle in order to associate it with
* an actual socket. The multi handle will then feed bytes into the easy
* handle and read bytes from the easy handle. But this process is opaque
* to use. It is further worth noting, that with HTTP/1.1 persistent
* connections and with HTTP/2 there's a 1-to-many relationship between
* TCP streams and HTTP transfers / easy handles. A single TCP stream and
* its socket may be shared by multiple easy handles.
*
* A single HTTP request-response exchange (refered to here as a
* *transfer*) corresponds directly to an easy handle. Hence anything that
* needs to be configured for a specific transfer (e.g. the URL) will be
* configured on an easy handle.
*
* A single `NSURLSessionTask` may do multiple, consecutive transfers, and
* as a result it will have to reconfigure its easy handle between
* transfers. An easy handle can be re-used once its transfer has
* completed.
*
* Note: All code assumes that it is being called on a single thread,
* it is intentionally **not** thread safe.
*/
@interface GSEasyHandle : NSObject
{
CURL *_rawHandle;
char *_errorBuffer;
id<GSEasyHandleDelegate> _delegate;
GSTimeoutSource *_timeoutTimer;
NSURL *_URL;
}
- (CURL*) rawHandle;
- (char*) errorBuffer;
/*
* Set error buffer for error messages
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_ERRORBUFFER.html
*/
- (void) setErrorBuffer: (char*)buffer;
- (GSTimeoutSource*) timeoutTimer;
- (void) setTimeoutTimer: (GSTimeoutSource*)timer;
- (NSURL*) URL;
/*
* URL to use in the request
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_URL.html
*/
- (void) setURL: (NSURL*)URL;
- (void) setPipeWait: (BOOL)flag;
- (instancetype) initWithDelegate: (id<GSEasyHandleDelegate>)delegate;
- (void) transferCompletedWithError: (NSError*)error;
- (int) urlErrorCodeWithEasyCode: (int)easyCode;
- (void) setVerboseMode: (BOOL)flag;
- (void) setDebugOutput: (BOOL)flag
task: (NSURLSessionTask*)task;
- (void) setPassHeadersToDataStream: (BOOL)flag;
/*
* Follow any Location: header that the server sends as part of a HTTP header
* in a 3xx response
*/
- (void) setFollowLocation: (BOOL)flag;
/*
* Switch off the progress meter.
*/
- (void) setProgressMeterOff: (BOOL)flag;
/*
* Skip all signal handling
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_NOSIGNAL.html
*/
- (void) setSkipAllSignalHandling: (BOOL)flag;
/*
* Request failure on HTTP response >= 400
*/
- (void) setFailOnHTTPErrorCode: (BOOL)flag;
- (void) setConnectToHost: (NSString*)host
port: (NSInteger)port;
- (void) setSessionConfig: (NSURLSessionConfiguration*)config;
- (void) setAllowedProtocolsToHTTPAndHTTPS;
/*
* set preferred receive buffer size
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_BUFFERSIZE.html
*/
- (void) setPreferredReceiveBufferSize: (NSInteger)size;
/*
* Set custom HTTP headers
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
*/
- (void) setCustomHeaders: (NSArray*)headers;
/*
* Enable automatic decompression of HTTP downloads
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTP_CONTENT_DECODING.html
*/
- (void) setAutomaticBodyDecompression: (BOOL)flag;
/*
* Set request method
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_CUSTOMREQUEST.html
*/
- (void) setRequestMethod:(NSString*)method;
/*
* Download request without body
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_NOBODY.html
*/
- (void) setNoBody: (BOOL)flag;
/*
* Enable data upload
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_UPLOAD.html
*/
- (void) setUpload: (BOOL)flag;
/*
* Set size of the request body to send
* - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_INFILESIZE_LARGE.html
*/
- (void) setRequestBodyLength: (int64_t)length;
- (void) setTimeout: (NSInteger)timeout;
- (void) setProxy;
- (double) getTimeoutIntervalSpent;
- (void) pauseReceive;
- (void) unpauseReceive;
- (void) pauseSend;
- (void) unpauseSend;
@end
#endif

View file

@ -1,773 +0,0 @@
#import "GSURLPrivate.h"
#import "GSEasyHandle.h"
#import "GSTimeoutSource.h"
#import "Foundation/NSCharacterSet.h"
#import "Foundation/NSURLSession.h"
typedef NS_OPTIONS(NSUInteger, GSEasyHandlePauseState) {
GSEasyHandlePauseStateReceive = 1 << 0,
GSEasyHandlePauseStateSend = 1 << 1
};
@interface GSEasyHandle ()
- (void) resetTimer;
- (NSInteger) didReceiveData: (char*)data
size: (NSInteger)size
nmemb:(NSInteger)nmemb;
- (NSInteger) fillWriteBuffer: (char *)buffer
size: (NSInteger)size
nmemb: (NSInteger)nmemb;
- (NSInteger) didReceiveHeaderData: (char*)headerData
size: (NSInteger)size
nmemb: (NSInteger)nmemb
contentLength: (double)contentLength;
- (int) seekInputStreamWithOffset: (int64_t)offset
origin: (NSInteger)origin;
@end
static void
handleEasyCode(int code)
{
if (CURLE_OK != code)
{
NSString *reason;
NSException *e;
reason = [NSString stringWithFormat: @"An error occurred, CURLcode is %d",
code];
e = [NSException exceptionWithName: @"libcurl.easy"
reason: reason
userInfo: nil];
[e raise];
}
}
static size_t
curl_write_function(char *data, size_t size, size_t nmemb, void *userdata)
{
GSEasyHandle *handle;
if (!userdata)
{
return 0;
}
handle = (GSEasyHandle*)userdata;
[handle resetTimer]; //FIXME should be deffered after the function returns?
return [handle didReceiveData:data size:size nmemb:nmemb];
}
static size_t
curl_read_function(char *data, size_t size, size_t nmemb, void *userdata)
{
GSEasyHandle *handle;
if (!userdata)
{
return 0;
}
handle = (GSEasyHandle*)userdata;
[handle resetTimer]; //FIXME should be deffered after the function returns?
return [handle fillWriteBuffer: data size: size nmemb: nmemb];
}
size_t
curl_header_function(char *data, size_t size, size_t nmemb, void *userdata)
{
GSEasyHandle *handle;
double length;
if (!userdata)
{
return 0;
}
handle = (GSEasyHandle*)userdata;
[handle resetTimer]; //FIXME should be deffered after the function returns?
handleEasyCode(curl_easy_getinfo(handle.rawHandle,
CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length));
return [handle didReceiveHeaderData: data
size: size
nmemb: nmemb
contentLength: length];
}
static int
curl_seek_function(void *userdata, curl_off_t offset, int origin)
{
GSEasyHandle *handle;
if (!userdata)
{
return CURL_SEEKFUNC_FAIL;
}
handle = (GSEasyHandle*)userdata;
return [handle seekInputStreamWithOffset: offset origin: origin];
}
static int
curl_debug_function(CURL *handle, curl_infotype type, char *data,
size_t size, void *userptr)
{
NSURLSessionTask *task;
NSString *text;
NSURLRequest *o;
NSURLRequest *r;
id<GSLogDelegate> d;
if (!userptr)
{
return 0;
}
if (CURLINFO_SSL_DATA_OUT == type || CURLINFO_SSL_DATA_IN == type)
{
return 0; // Don't log encrypted data here
}
task = (NSURLSessionTask*)userptr;
text = @"";
o = [task originalRequest];
r = [task currentRequest];
d = [(nil == r ? o : r) _debugLogDelegate];
if (d != nil)
{
if (CURLINFO_DATA_IN == type || CURLINFO_HEADER_IN == type)
{
if ([d getBytes: (const uint8_t *)data ofLength: size byHandle: o])
{
return 0; // Handled
}
}
if (CURLINFO_DATA_OUT == type || CURLINFO_HEADER_OUT == type)
{
if ([d putBytes: (const uint8_t *)data ofLength: size byHandle: o])
{
return 0; // Handled
}
}
}
if (data)
{
text = [NSString stringWithUTF8String: data];
}
NSLog(@"%p %lu %d %@", o, (unsigned long)[task taskIdentifier], type, text);
return 0;
}
static int
curl_socket_function(void *userdata, curl_socket_t fd, curlsocktype type)
{
return 0;
}
@implementation GSEasyHandle
{
NSURLSessionConfiguration *_config;
GSEasyHandlePauseState _pauseState;
struct curl_slist *_headerList;
}
- (instancetype) initWithDelegate: (id<GSEasyHandleDelegate>)delegate
{
if (nil != (self = [super init]))
{
char *eb;
_rawHandle = curl_easy_init();
_delegate = delegate;
eb = (char *)malloc(sizeof(char) * (CURL_ERROR_SIZE + 1));
_errorBuffer = memset(eb, 0, sizeof(char) * (CURL_ERROR_SIZE + 1));
[self setupCallbacks];
}
return self;
}
- (void) dealloc
{
curl_easy_cleanup(_rawHandle);
curl_slist_free_all(_headerList);
free(_errorBuffer);
DESTROY(_config);
[_timeoutTimer cancel];
DESTROY(_timeoutTimer);
DESTROY(_URL);
[super dealloc];
}
- (CURL*) rawHandle
{
return _rawHandle;
}
- (char*) errorBuffer
{
return _errorBuffer;
}
- (GSTimeoutSource*) timeoutTimer
{
return _timeoutTimer;
}
- (void) setTimeoutTimer: (GSTimeoutSource*)timer
{
[_timeoutTimer cancel];
ASSIGN(_timeoutTimer, timer);
}
- (NSURL*) URL
{
return _URL;
}
- (void) transferCompletedWithError: (NSError*)error
{
[_delegate transferCompletedWithError: error];
}
- (void) resetTimer
{
[_timeoutTimer setTimeout: [_timeoutTimer timeout]];
}
- (void) setupCallbacks
{
// write
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_WRITEDATA, self));
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_WRITEFUNCTION,
curl_write_function));
// read
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_READDATA, self));
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_READFUNCTION,
curl_read_function));
// header
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_HEADERDATA, self));
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_HEADERFUNCTION,
curl_header_function));
// socket options
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_SOCKOPTDATA, self));
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_SOCKOPTFUNCTION,
curl_socket_function));
// seeking in input stream
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_SEEKDATA, self));
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_SEEKFUNCTION,
curl_seek_function));
}
- (int) urlErrorCodeWithEasyCode: (int)easyCode
{
int failureErrno = (int)[self connectFailureErrno];
if (easyCode == CURLE_OK)
{
return 0;
}
else if (failureErrno == ECONNREFUSED)
{
return NSURLErrorCannotConnectToHost;
}
else if (easyCode == CURLE_UNSUPPORTED_PROTOCOL)
{
return NSURLErrorUnsupportedURL;
}
else if (easyCode == CURLE_URL_MALFORMAT)
{
return NSURLErrorBadURL;
}
else if (easyCode == CURLE_COULDNT_RESOLVE_HOST)
{
return NSURLErrorCannotFindHost;
}
else if (easyCode == CURLE_RECV_ERROR && failureErrno == ECONNRESET)
{
return NSURLErrorNetworkConnectionLost;
}
else if (easyCode == CURLE_SEND_ERROR && failureErrno == ECONNRESET)
{
return NSURLErrorNetworkConnectionLost;
}
else if (easyCode == CURLE_GOT_NOTHING)
{
return NSURLErrorBadServerResponse;
}
else if (easyCode == CURLE_ABORTED_BY_CALLBACK)
{
return NSURLErrorUnknown;
}
else if (easyCode == CURLE_COULDNT_CONNECT && failureErrno == ETIMEDOUT)
{
return NSURLErrorTimedOut;
}
else if (easyCode == CURLE_COULDNT_CONNECT)
{
return NSURLErrorCannotConnectToHost;
}
else if (easyCode == CURLE_OPERATION_TIMEDOUT)
{
return NSURLErrorTimedOut;
}
else
{
return NSURLErrorUnknown;
}
}
- (void) setVerboseMode: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_VERBOSE, flag ? 1 : 0));
}
- (void) setDebugOutput: (BOOL)flag
task: (NSURLSessionTask*)task
{
if (flag)
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGDATA, task));
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGFUNCTION,
curl_debug_function));
}
else
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGDATA, NULL));
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGFUNCTION, NULL));
}
}
- (void) setPassHeadersToDataStream: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_HEADER,
flag ? 1 : 0));
}
- (void) setFollowLocation: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_FOLLOWLOCATION,
flag ? 1 : 0));
}
- (void) setProgressMeterOff: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_NOPROGRESS,
flag ? 1 : 0));
}
- (void) setSkipAllSignalHandling: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_NOSIGNAL,
flag ? 1 : 0));
}
- (void) setErrorBuffer: (char*)buffer
{
char *b = buffer ? buffer : _errorBuffer;
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_ERRORBUFFER, b));
}
- (void) setFailOnHTTPErrorCode: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_FAILONERROR,
flag ? 1 : 0));
}
- (void) setURL: (NSURL *)URL
{
ASSIGN(_URL, URL);
if (nil != [URL absoluteString])
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_URL,
[[URL absoluteString] UTF8String]));
}
}
- (void) setPipeWait: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_PIPEWAIT, flag ? 1 : 0));
}
- (void) setConnectToHost: (NSString*)host port: (NSInteger)port
{
if (nil != host)
{
NSString *originHost = [_URL host];
NSString *value;
struct curl_slist *connect_to;
if (0 == port)
{
value = [NSString stringWithFormat: @"%@::%@", originHost, host];
}
else
{
value = [NSString stringWithFormat: @"%@:%lu:%@",
originHost, (unsigned long)port, host];
}
connect_to = curl_slist_append(NULL, [value UTF8String]);
handleEasyCode(
curl_easy_setopt(_rawHandle, CURLOPT_CONNECT_TO, connect_to));
}
}
- (void) setSessionConfig: (NSURLSessionConfiguration*)config
{
ASSIGN(_config, config);
#if defined(CURLOPT_MAXAGE_CONN)
/* This specifies the maximum age of a connection if it is to be considered
* a candidate for re-use. By default curl currently uses 118 seconds, so
* this is what we will get if the configuration does not contain a positive
* number of seconds.
*/
if ([config HTTPMaximumConnectionLifetime] > 0)
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_MAXAGE_CONN,
(long)[config HTTPMaximumConnectionLifetime]));
}
#endif
}
- (void) setAllowedProtocolsToHTTPAndHTTPS
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_PROTOCOLS,
CURLPROTO_HTTP | CURLPROTO_HTTPS));
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_REDIR_PROTOCOLS,
CURLPROTO_HTTP | CURLPROTO_HTTPS));
}
- (void) setPreferredReceiveBufferSize: (NSInteger)size
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_BUFFERSIZE,
MIN(size, CURL_MAX_WRITE_SIZE)));
}
- (void) setCustomHeaders: (NSArray*)headers
{
NSEnumerator *e;
NSString *h;
e = [headers objectEnumerator];
while (nil != (h = [e nextObject]))
{
_headerList = curl_slist_append(_headerList, [h UTF8String]);
}
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_HTTPHEADER, _headerList));
}
- (void) setAutomaticBodyDecompression: (BOOL)flag
{
if (flag)
{
handleEasyCode(curl_easy_setopt(_rawHandle,
CURLOPT_ACCEPT_ENCODING, ""));
handleEasyCode(curl_easy_setopt(_rawHandle,
CURLOPT_HTTP_CONTENT_DECODING, 1));
}
else
{
handleEasyCode(curl_easy_setopt(_rawHandle,
CURLOPT_ACCEPT_ENCODING, NULL));
handleEasyCode(curl_easy_setopt(_rawHandle,
CURLOPT_HTTP_CONTENT_DECODING, 0));
}
}
- (void) setRequestMethod: (NSString*)method
{
if (nil == method)
{
return;
}
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_CUSTOMREQUEST,
[method UTF8String]));
}
- (void) setNoBody: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_NOBODY,
flag ? 1 : 0));
}
- (void) setUpload: (BOOL)flag
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_UPLOAD, flag ? 1 : 0));
}
- (void) setRequestBodyLength: (int64_t)length
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_INFILESIZE_LARGE,
length));
}
- (void) setTimeout: (NSInteger)timeout
{
handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_TIMEOUT,
(long)timeout));
}
- (void) setProxy
{
//TODO setup proxy
}
- (void) updatePauseState: (GSEasyHandlePauseState)pauseState
{
NSUInteger send = pauseState & GSEasyHandlePauseStateSend;
NSUInteger receive = pauseState & GSEasyHandlePauseStateReceive;
int bitmask;
bitmask = 0
| (send ? CURLPAUSE_SEND : CURLPAUSE_SEND_CONT)
| (receive ? CURLPAUSE_RECV : CURLPAUSE_RECV_CONT);
handleEasyCode(curl_easy_pause(_rawHandle, bitmask));
}
- (double) getTimeoutIntervalSpent
{
double timeSpent;
curl_easy_getinfo(_rawHandle, CURLINFO_TOTAL_TIME, &timeSpent);
return timeSpent / 1000;
}
- (long) connectFailureErrno
{
long _errno;
handleEasyCode(curl_easy_getinfo(_rawHandle, CURLINFO_OS_ERRNO, &_errno));
return _errno;
}
- (void) pauseSend
{
if (_pauseState & GSEasyHandlePauseStateSend)
{
return;
}
_pauseState = _pauseState | GSEasyHandlePauseStateSend;
[self updatePauseState: _pauseState];
}
- (void) unpauseSend
{
if (!(_pauseState & GSEasyHandlePauseStateSend))
{
return;
}
_pauseState = _pauseState ^ GSEasyHandlePauseStateSend;
[self updatePauseState: _pauseState];
}
- (void) pauseReceive
{
if (_pauseState & GSEasyHandlePauseStateReceive)
{
return;
}
_pauseState = _pauseState | GSEasyHandlePauseStateReceive;
[self updatePauseState: _pauseState];
}
- (void) unpauseReceive
{
if (!(_pauseState & GSEasyHandlePauseStateReceive))
{
return;
}
_pauseState = _pauseState ^ GSEasyHandlePauseStateReceive;
[self updatePauseState: _pauseState];
}
- (NSInteger) didReceiveData: (char*)data
size: (NSInteger)size
nmemb: (NSInteger)nmemb
{
NSData *buffer;
GSEasyHandleAction action;
NSUInteger bytes;
if (![_delegate respondsToSelector: @selector(didReceiveData:)])
{
return 0;
}
bytes = size * nmemb;
buffer = AUTORELEASE([[NSData alloc] initWithBytes: data length: bytes]);
action = [_delegate didReceiveData: buffer];
switch (action)
{
case GSEasyHandleActionProceed:
return bytes;
case GSEasyHandleActionAbort:
return 0;
case GSEasyHandleActionPause:
_pauseState = _pauseState | GSEasyHandlePauseStateReceive;
return CURL_WRITEFUNC_PAUSE;
}
}
- (NSInteger) didReceiveHeaderData: (char*)headerData
size: (NSInteger)size
nmemb: (NSInteger)nmemb
contentLength: (double)contentLength
{
NSData *buffer;
GSEasyHandleAction action;
NSInteger bytes = size * nmemb;
buffer = [NSData dataWithBytes: headerData length: bytes];
[self setCookiesWithHeaderData: buffer];
if (![_delegate respondsToSelector:
@selector(didReceiveHeaderData:contentLength:)])
{
return 0;
}
action = [_delegate didReceiveHeaderData: buffer
contentLength: (int64_t)contentLength];
switch (action)
{
case GSEasyHandleActionProceed:
return bytes;
case GSEasyHandleActionAbort:
return 0;
case GSEasyHandleActionPause:
_pauseState = _pauseState | GSEasyHandlePauseStateReceive;
return CURL_WRITEFUNC_PAUSE;
}
}
- (NSInteger) fillWriteBuffer: (char*)buffer
size: (NSInteger)size
nmemb: (NSInteger)nmemb
{
__block NSInteger d;
if (![_delegate respondsToSelector: @selector(fillWriteBufferLength:result:)])
{
return CURL_READFUNC_ABORT;
}
[_delegate fillWriteBufferLength: size * nmemb
result: ^(GSEasyHandleWriteBufferResult result, NSInteger length, NSData *data)
{
switch (result)
{
case GSEasyHandleWriteBufferResultPause:
_pauseState = _pauseState | GSEasyHandlePauseStateSend;
d = CURL_READFUNC_PAUSE;
break;
case GSEasyHandleWriteBufferResultAbort:
d = CURL_READFUNC_ABORT;
break;
case GSEasyHandleWriteBufferResultBytes:
memcpy(buffer, [data bytes], length);
d = length;
break;
}
}];
return d;
}
- (int) seekInputStreamWithOffset: (int64_t)offset
origin: (NSInteger)origin
{
NSAssert(SEEK_SET == origin, @"Unexpected 'origin' in seek.");
if (![_delegate respondsToSelector: @selector(seekInputStreamToPosition:)])
{
return CURL_SEEKFUNC_CANTSEEK;
}
if ([_delegate seekInputStreamToPosition: offset])
{
return CURL_SEEKFUNC_OK;
}
else
{
return CURL_SEEKFUNC_CANTSEEK;
}
}
- (void) setCookiesWithHeaderData: (NSData*)data
{
NSString *headerLine;
NSRange r;
NSString *head;
NSString *tail;
NSCharacterSet *set;
NSString *key;
NSString *value;
NSArray *cookies;
NSDictionary *d;
if (nil != _config
&& NSHTTPCookieAcceptPolicyNever != [_config HTTPCookieAcceptPolicy]
&& nil != [_config HTTPCookieStorage])
{
headerLine = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
if (0 == [headerLine length])
{
RELEASE(headerLine);
return;
}
r = [headerLine rangeOfString: @":"];
if (NSNotFound != r.location)
{
head = [headerLine substringToIndex:r.location];
tail = [headerLine substringFromIndex:r.location + 1];
set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
key = [head stringByTrimmingCharactersInSet:set];
value = [tail stringByTrimmingCharactersInSet:set];
if (nil != key && nil != value)
{
d = [NSDictionary dictionaryWithObject: value forKey: key];
cookies = [NSHTTPCookie cookiesWithResponseHeaderFields: d
forURL: _URL];
if ([cookies count] > 0)
{
[[_config HTTPCookieStorage] setCookies: cookies
forURL: _URL
mainDocumentURL: nil];
}
}
}
RELEASE(headerLine);
}
}
@end

View file

@ -1,9 +0,0 @@
#ifndef INCLUDED_GSHTTPURLPROTOCOL_H
#define INCLUDED_GSHTTPURLPROTOCOL_H
#import "GSNativeProtocol.h"
@interface GSHTTPURLProtocol : GSNativeProtocol
@end
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,89 +0,0 @@
#ifndef INCLUDED_GSMULTIHANDLE_H
#define INCLUDED_GSMULTIHANDLE_H
#import "common.h"
#import <curl/curl.h>
#import "GSDispatch.h"
@class NSURLSessionConfiguration;
@class GSEasyHandle;
/*
* Minimal wrapper around curl multi interface
* (https://curl.haxx.se/libcurl/c/libcurl-multi.html).
*
* The the *multi handle* manages the sockets for easy handles
* (`GSEasyHandle`), and this implementation uses
* libdispatch to listen for sockets being read / write ready.
*
* Using `dispatch_source_t` allows this implementation to be
* non-blocking and all code to run on the same thread
* thus keeping is simple.
*
* - SeeAlso: GSEasyHandle
*/
@interface GSMultiHandle : NSObject
{
CURLM *_rawHandle;
}
- (CURLM*) rawHandle;
- (instancetype) initWithConfiguration: (NSURLSessionConfiguration*)configuration
workQueue: (dispatch_queue_t)workQueque;
- (void) addHandle: (GSEasyHandle*)easyHandle;
- (void) removeHandle: (GSEasyHandle*)easyHandle;
- (void) updateTimeoutTimerToValue: (NSInteger)value;
@end
// What read / write ready event to register / unregister.
typedef NS_ENUM(NSUInteger, GSSocketRegisterActionType) {
GSSocketRegisterActionTypeNone = 0,
GSSocketRegisterActionTypeRegisterRead,
GSSocketRegisterActionTypeRegisterWrite,
GSSocketRegisterActionTypeRegisterReadAndWrite,
GSSocketRegisterActionTypeUnregister,
};
@interface GSSocketRegisterAction : NSObject
{
GSSocketRegisterActionType _type;
}
- (instancetype) initWithRawValue: (int)rawValue;
- (GSSocketRegisterActionType) type;
- (BOOL) needsReadSource;
- (BOOL) needsWriteSource;
- (BOOL) needsSource;
@end
/*
* Read and write libdispatch sources for a specific socket.
*
* A simple helper that combines two sources -- both being optional.
*
* This info is stored into the socket using `curl_multi_assign()`.
*
* - SeeAlso: GSSocketRegisterAction
*/
@interface GSSocketSources : NSObject
{
dispatch_source_t _readSource;
dispatch_source_t _writeSource;
}
- (void) createSourcesWithAction: (GSSocketRegisterAction *)action
socket: (curl_socket_t)socket
queue: (dispatch_queue_t)queue
handler: (dispatch_block_t)handler;
- (dispatch_source_t) createSourceWithType: (dispatch_source_type_t)type
socket: (curl_socket_t)socket
queue: (dispatch_queue_t)queue
handler: (dispatch_block_t)handler;
+ (instancetype) from: (void*)socketSourcePtr;
@end
#endif

View file

@ -1,485 +0,0 @@
#import "GSMultiHandle.h"
#import "GSTimeoutSource.h"
#import "GSEasyHandle.h"
#import "Foundation/NSArray.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSError.h"
#import "Foundation/NSException.h"
#import "Foundation/NSURLError.h"
#import "Foundation/NSURLSession.h"
#import "Foundation/NSValue.h"
@interface GSMultiHandle ()
- (void) readAndWriteAvailableDataOnSocket: (curl_socket_t)socket;
- (void) readMessages;
- (void) completedTransferForEasyHandle: (CURL*)rawEasyHandle
easyCode: (int)easyCode;
- (int32_t) registerWithSocket: (curl_socket_t)socket
what: (int)what
socketSourcePtr: (void *)socketSourcePtr;
@end
static void handleEasyCode(int code)
{
if (CURLE_OK != code)
{
NSString *reason;
NSException *e;
reason = [NSString stringWithFormat: @"An error occurred, CURLcode is %d",
code];
e = [NSException exceptionWithName: @"libcurl.easy"
reason: reason
userInfo: nil];
[e raise];
}
}
static void handleMultiCode(int code)
{
if (CURLM_OK != code)
{
NSString *reason;
NSException *e;
reason = [NSString stringWithFormat: @"An error occurred, CURLcode is %d",
code];
e = [NSException exceptionWithName: @"libcurl.multi"
reason: reason
userInfo: nil];
[e raise];
}
}
static int curl_socket_function(CURL *easyHandle, curl_socket_t socket, int what, void *userdata, void *socketptr)
{
GSMultiHandle *handle = (GSMultiHandle*)userdata;
return [handle registerWithSocket: socket
what: what
socketSourcePtr: socketptr];
}
static int curl_timer_function(CURL *easyHandle, int timeout, void *userdata) {
GSMultiHandle *handle = (GSMultiHandle*)userdata;
[handle updateTimeoutTimerToValue: timeout];
return 0;
}
@implementation GSMultiHandle
{
NSMutableArray *_easyHandles;
dispatch_queue_t _queue;
GSTimeoutSource *_timeoutSource;
}
- (CURLM*) rawHandle
{
return _rawHandle;
}
- (instancetype) initWithConfiguration: (NSURLSessionConfiguration*)conf
workQueue: (dispatch_queue_t)aQueue
{
if (nil != (self = [super init]))
{
_rawHandle = curl_multi_init();
_easyHandles = [[NSMutableArray alloc] init];
#if HAVE_DISPATCH_QUEUE_CREATE_WITH_TARGET
_queue = dispatch_queue_create_with_target("GSMultiHandle.isolation",
DISPATCH_QUEUE_SERIAL, aQueue);
#else
_queue = dispatch_queue_create("GSMultiHandle.isolation",
DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(_queue, aQueue);
#endif
[self setupCallbacks];
[self configureWithConfiguration: conf];
}
return self;
}
- (void) dealloc
{
NSEnumerator *e;
GSEasyHandle *handle;
[_timeoutSource cancel];
DESTROY(_timeoutSource);
dispatch_release(_queue);
e = [_easyHandles objectEnumerator];
while (nil != (handle = [e nextObject]))
{
curl_multi_remove_handle([handle rawHandle], _rawHandle);
}
DESTROY(_easyHandles);
curl_multi_cleanup(_rawHandle);
[super dealloc];
}
- (void) configureWithConfiguration: (NSURLSessionConfiguration*)configuration
{
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_MAX_HOST_CONNECTIONS, [configuration HTTPMaximumConnectionsPerHost]));
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_PIPELINING, [configuration HTTPShouldUsePipelining] ? CURLPIPE_MULTIPLEX : CURLPIPE_NOTHING));
}
- (void)setupCallbacks
{
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_SOCKETDATA, (void*)self));
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_SOCKETFUNCTION, curl_socket_function));
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_TIMERDATA, (__bridge void *)self));
handleEasyCode(curl_multi_setopt(_rawHandle, CURLMOPT_TIMERFUNCTION, curl_timer_function));
}
- (void) addHandle: (GSEasyHandle*)easyHandle
{
// If this is the first handle being added, we need to `kick` the
// underlying multi handle by calling `timeoutTimerFired` as
// described in
// <https://curl.haxx.se/libcurl/c/curl_multi_socket_action.html>.
// That will initiate the registration for timeout timer and socket
// readiness.
BOOL needsTimeout = false;
if ([_easyHandles count] == 0)
{
needsTimeout = YES;
}
[_easyHandles addObject: easyHandle];
handleMultiCode(curl_multi_add_handle(_rawHandle, [easyHandle rawHandle]));
if (needsTimeout)
{
[self timeoutTimerFired];
}
}
- (void) removeHandle: (GSEasyHandle*)easyHandle
{
NSEnumerator *e;
int idx = 0;
BOOL found = NO;
GSEasyHandle *h;
e = [_easyHandles objectEnumerator];
while (nil != (h = [e nextObject]))
{
if ([h rawHandle] == [easyHandle rawHandle])
{
found = YES;
break;
}
idx++;
}
NSAssert(found, @"Handle not in list.");
handleMultiCode(curl_multi_remove_handle(_rawHandle, [easyHandle rawHandle]));
[_easyHandles removeObjectAtIndex: idx];
}
- (void) updateTimeoutTimerToValue: (NSInteger)value
{
// A timeout_ms value of -1 passed to this callback means you should delete
// the timer. All other values are valid expire times in number
// of milliseconds.
if (-1 == value)
{
[_timeoutSource suspend];
}
else
{
if (!_timeoutSource)
{
_timeoutSource = [[GSTimeoutSource alloc] initWithQueue: _queue
handler: ^{
[self timeoutTimerFired];
}];
}
[_timeoutSource setTimeout: value];
}
}
- (void) timeoutTimerFired
{
[self readAndWriteAvailableDataOnSocket: CURL_SOCKET_TIMEOUT];
}
- (void) readAndWriteAvailableDataOnSocket: (curl_socket_t)socket
{
int runningHandlesCount = 0;
handleMultiCode(curl_multi_socket_action(_rawHandle, socket, 0, &runningHandlesCount));
[self readMessages];
}
/// Check the status of all individual transfers.
///
/// libcurl refers to this as read multi stack informationals.
/// Check for transfers that completed.
- (void) readMessages
{
while (true)
{
int count = 0;
CURLMsg *msg;
CURL *easyHandle;
int code;
msg = curl_multi_info_read(_rawHandle, &count);
if (NULL == msg || CURLMSG_DONE != msg->msg || !msg->easy_handle) break;
easyHandle = msg->easy_handle;
code = msg->data.result;
[self completedTransferForEasyHandle: easyHandle easyCode: code];
}
}
- (void) completedTransferForEasyHandle: (CURL*)rawEasyHandle
easyCode: (int)easyCode
{
NSEnumerator *e;
GSEasyHandle *h;
GSEasyHandle *handle = nil;
NSError *err = nil;
int errCode;
e = [_easyHandles objectEnumerator];
while (nil != (h = [e nextObject]))
{
if ([h rawHandle] == rawEasyHandle)
{
handle = h;
break;
}
}
NSAssert(nil != handle, @"Transfer completed for easy handle"
@", but it is not in the list of added handles.");
errCode = [handle urlErrorCodeWithEasyCode: easyCode];
if (0 != errCode)
{
NSString *d = nil;
if ([handle errorBuffer][0] == 0)
{
const char *description = curl_easy_strerror(errCode);
d = [[NSString alloc] initWithCString: description
encoding: NSUTF8StringEncoding];
}
else
{
d = [[NSString alloc] initWithCString: [handle errorBuffer]
encoding: NSUTF8StringEncoding];
}
err = [NSError errorWithDomain: NSURLErrorDomain
code: errCode
userInfo: @{NSLocalizedDescriptionKey : d, NSUnderlyingErrorKey: [NSNumber numberWithInt:easyCode]}];
RELEASE(d);
}
[handle transferCompletedWithError: err];
}
- (int32_t) registerWithSocket: (curl_socket_t)socket
what: (int)what
socketSourcePtr: (void *)socketSourcePtr
{
// We get this callback whenever we need to register or unregister a
// given socket with libdispatch.
// The `action` / `what` defines if we should register or unregister
// that we're interested in read and/or write readiness. We will do so
// through libdispatch (DispatchSource) and store the source(s) inside
// a `SocketSources` which we in turn store inside libcurl's multi handle
// by means of curl_multi_assign() -- we retain the object first.
GSSocketRegisterAction *action;
GSSocketSources *socketSources;
action = [[GSSocketRegisterAction alloc] initWithRawValue: what];
socketSources = [GSSocketSources from: socketSourcePtr];
if (nil == socketSources && [action needsSource])
{
GSSocketSources *s;
s = [[GSSocketSources alloc] init];
curl_multi_assign(_rawHandle, socket, (void*)s);
socketSources = s;
}
else if (nil != socketSources
&& GSSocketRegisterActionTypeUnregister == [action type])
{
DESTROY(socketSources);
curl_multi_assign(_rawHandle, socket, NULL);
}
if (nil != socketSources)
{
[socketSources createSourcesWithAction: action
socket: socket
queue: _queue
handler: ^{
[self readAndWriteAvailableDataOnSocket: socket];
}];
}
RELEASE(action);
return 0;
}
@end
@implementation GSSocketRegisterAction
- (instancetype) initWithRawValue: (int)rawValue
{
if (nil != (self = [super init]))
{
switch (rawValue) {
case CURL_POLL_NONE:
_type = GSSocketRegisterActionTypeNone;
break;
case CURL_POLL_IN:
_type = GSSocketRegisterActionTypeRegisterRead;
break;
case CURL_POLL_OUT:
_type = GSSocketRegisterActionTypeRegisterWrite;
break;
case CURL_POLL_INOUT:
_type = GSSocketRegisterActionTypeRegisterReadAndWrite;
break;
case CURL_POLL_REMOVE:
_type = GSSocketRegisterActionTypeUnregister;
break;
default:
NSAssert(NO, @"Invalid CURL_POLL value");
}
}
return self;
}
- (GSSocketRegisterActionType) type
{
return _type;
}
- (BOOL) needsReadSource
{
switch (self.type)
{
case GSSocketRegisterActionTypeRegisterRead:
case GSSocketRegisterActionTypeRegisterReadAndWrite:
return YES;
default:
return NO;
}
}
- (BOOL) needsWriteSource
{
switch (self.type)
{
case GSSocketRegisterActionTypeRegisterWrite:
case GSSocketRegisterActionTypeRegisterReadAndWrite:
return YES;
default:
return NO;
}
}
- (BOOL)needsSource
{
return [self needsReadSource] || [self needsWriteSource];
}
@end
@implementation GSSocketSources
- (void) dealloc
{
if (_readSource)
{
dispatch_source_cancel(_readSource);
}
_readSource = NULL;
if (_writeSource)
{
dispatch_source_cancel(_writeSource);
}
_writeSource = NULL;
[super dealloc];
}
- (void) createSourcesWithAction: (GSSocketRegisterAction*)action
socket: (curl_socket_t)socket
queue: (dispatch_queue_t)queue
handler: (dispatch_block_t)handler
{
if (!_readSource && [action needsReadSource])
{
_readSource = [self createSourceWithType: DISPATCH_SOURCE_TYPE_READ
socket: socket
queue: queue
handler: handler];
}
if (!_writeSource && [action needsWriteSource])
{
_writeSource = [self createSourceWithType: DISPATCH_SOURCE_TYPE_WRITE
socket: socket
queue: queue
handler: handler];
}
}
- (dispatch_source_t) createSourceWithType: (dispatch_source_type_t)type
socket: (curl_socket_t)socket
queue: (dispatch_queue_t)queue
handler: (dispatch_block_t)handler
{
dispatch_source_t source;
source = dispatch_source_create(type, socket, 0, queue);
dispatch_source_set_event_handler(source, handler);
dispatch_source_set_cancel_handler(source, ^{
dispatch_release(source);
});
dispatch_resume(source);
return source;
}
+ (instancetype) from: (void*)socketSourcePtr
{
if (!socketSourcePtr)
{
return nil;
}
else
{
return (GSSocketSources*)socketSourcePtr;
}
}
@end

View file

@ -1,120 +0,0 @@
#ifndef INCLUDED_GSNATIVEPROTOCOL_H
#define INCLUDED_GSNATIVEPROTOCOL_H
#import "GSDispatch.h"
#import "GSEasyHandle.h"
#import "Foundation/NSURLProtocol.h"
#import "Foundation/NSURLSession.h"
@class GSTransferState;
@interface NSURLSessionTask (GSNativeProtocolInternal)
- (void) setCurrentRequest: (NSURLRequest*)request;
- (dispatch_queue_t) workQueue;
- (NSUInteger) suspendCount;
- (void) getBodyWithCompletion: (void (^)(GSURLSessionTaskBody *body))completion;
- (GSURLSessionTaskBody*) knownBody;
- (void) setKnownBody: (GSURLSessionTaskBody*)body;
- (void) setError: (NSError*)error;
- (void) setCountOfBytesReceived: (int64_t)count;
- (void) setCountOfBytesExpectedToReceive: (int64_t)count;
- (void) setCountOfBytesExpectedToSend: (int64_t)count;
- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler;
- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler;
@end
typedef NS_ENUM(NSUInteger, GSCompletionActionType) {
GSCompletionActionTypeCompleteTask,
GSCompletionActionTypeFailWithError,
GSCompletionActionTypeRedirectWithRequest,
};
// Action to be taken after a transfer completes
@interface GSCompletionAction : NSObject
{
GSCompletionActionType _type;
int _errorCode;
NSURLRequest *_redirectRequest;
}
- (GSCompletionActionType) type;
- (void) setType: (GSCompletionActionType) type;
- (int) errorCode;
- (void) setErrorCode: (int)code;
- (NSURLRequest*) redirectRequest;
- (void) setRedirectRequest: (NSURLRequest*)request;
@end
typedef NS_ENUM(NSUInteger, GSNativeProtocolInternalState) {
// Task has been created, but nothing has been done, yet
GSNativeProtocolInternalStateInitial,
// The task is being fulfilled from the cache rather than the network.
GSNativeProtocolInternalStateFulfillingFromCache,
// The easy handle has been fully configured. But it is not added to
// the multi handle.
GSNativeProtocolInternalStateTransferReady,
// The easy handle is currently added to the multi handle
GSNativeProtocolInternalStateTransferInProgress,
// The transfer completed.
// The easy handle has been removed from the multi handle. This does
// not necessarily mean the task completed. A task that gets
// redirected will do multiple transfers.
GSNativeProtocolInternalStateTransferCompleted,
// The transfer failed.
// Same as `GSNativeProtocolInternalStateTransferCompleted`,
// but without response / body data
GSNativeProtocolInternalStateTransferFailed,
// Waiting for the completion handler of the HTTP redirect callback.
// When we tell the delegate that we're about to perform an HTTP
// redirect, we need to wait for the delegate to let us know what
// action to take.
GSNativeProtocolInternalStateWaitingForRedirectCompletionHandler,
// Waiting for the completion handler of the 'did receive response' callback.
// When we tell the delegate that we received a response (i.e. when
// we received a complete header), we need to wait for the delegate to
// let us know what action to take. In this state the easy handle is
// paused in order to suspend delegate callbacks.
GSNativeProtocolInternalStateWaitingForResponseCompletionHandler,
// The task is completed
// Contrast this with `GSNativeProtocolInternalStateTransferCompleted`.
GSNativeProtocolInternalStateTaskCompleted,
};
// This abstract class has the common implementation of Native protocols like
// HTTP, FTP, etc.
// These are libcurl helpers for the URLSession API code.
@interface GSNativeProtocol : NSURLProtocol <GSEasyHandleDelegate>
{
GSEasyHandle *_easyHandle;
GSNativeProtocolInternalState _internalState;
GSTransferState *_transferState;
}
- (void) setInternalState: (GSNativeProtocolInternalState)newState;
- (void) failWithError: (NSError*)error request: (NSURLRequest*)request;
- (void) completeTaskWithError: (NSError*)error;
- (void) completeTask;
- (void) startNewTransferWithRequest: (NSURLRequest*)request;
@end
#endif

View file

@ -1,818 +0,0 @@
#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

View file

@ -1,30 +0,0 @@
#ifndef INCLUDED_GSTASKREGISTRY_H
#define INCLUDED_GSTASKREGISTRY_H
#import "common.h"
@class NSArray;
@class NSURLSessionTask;
/*
* This helper class keeps track of all tasks.
*
* Each `NSURLSession` has a `GSTaskRegistry` for its running tasks.
*
* - Note: This must **only** be accessed on the owning session's work queue.
*/
@interface GSTaskRegistry : NSObject
- (void ) addTask: (NSURLSessionTask*)task;
- (void) removeTask: (NSURLSessionTask*)task;
- (void) notifyOnTasksCompletion: (void (^)(void))tasksCompletion;
- (NSArray*) allTasks;
- (BOOL) isEmpty;
@end
#endif

View file

@ -1,98 +0,0 @@
#import "GSTaskRegistry.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSException.h"
#import "Foundation/NSURLSession.h"
@implementation GSTaskRegistry
{
NSMutableDictionary *_tasks;
void (^_tasksCompletion)(void);
}
- (instancetype) init
{
if (nil != (self = [super init]))
{
_tasks = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) dealloc
{
DESTROY(_tasks);
[super dealloc];
}
- (NSArray*) allTasks
{
return [_tasks allValues];
}
- (BOOL) isEmpty
{
return [_tasks count] == 0;
}
- (void) notifyOnTasksCompletion: (void (^)(void))tasksCompletion
{
_tasksCompletion = tasksCompletion;
}
- (void) addTask: (NSURLSessionTask*)task
{
NSString *identifier;
NSUInteger taskIdentifier;
NSURLSessionTask *t;
taskIdentifier = [task taskIdentifier];
NSAssert(taskIdentifier != 0, @"Invalid task identifier");
identifier = [NSString stringWithFormat: @"%lu", taskIdentifier];
if (nil != (t = [_tasks objectForKey: identifier]))
{
if ([t isEqual: task])
{
NSAssert(NO,
@"Trying to re-insert a task that's already in the registry.");
}
else
{
NSAssert(NO,
@"Trying to insert a task, but a different task with the same"
@" identifier is already in the registry.");
}
}
[_tasks setObject: task forKey: identifier];
}
- (void) removeTask: (NSURLSessionTask*)task
{
NSString *identifier;
NSUInteger taskIdentifier;
taskIdentifier = [task taskIdentifier];
NSAssert(taskIdentifier != 0, @"Invalid task identifier");
identifier = [NSString stringWithFormat: @"%lu", taskIdentifier];
if (nil == [_tasks objectForKey: identifier])
{
NSAssert(NO, @"Trying to remove task, but it's not in the registry.");
}
[_tasks removeObjectForKey: identifier];
if (nil != _tasksCompletion && [self isEmpty])
{
_tasksCompletion();
}
}
@end

View file

@ -1,32 +0,0 @@
#ifndef INCLUDED_GSTIMEOUTSOURCE_H
#define INCLUDED_GSTIMEOUTSOURCE_H
#import "common.h"
#import "GSDispatch.h"
/*
* A helper class that wraps a libdispatch timer.
*
* Used to implement the timeout of `GSMultiHandle` and `GSEasyHandle`
*/
@interface GSTimeoutSource : NSObject
{
dispatch_source_t _timer;
NSInteger _timeoutMs;
bool _isSuspended;
}
- (instancetype) initWithQueue: (dispatch_queue_t)queue
handler: (dispatch_block_t)handler;
- (NSInteger) timeout;
- (void) setTimeout: (NSInteger)timeoutMs;
- (void) suspend;
- (void) cancel;
@end
#endif

View file

@ -1,76 +0,0 @@
#import "GSTimeoutSource.h"
@implementation GSTimeoutSource
- (instancetype) initWithQueue: (dispatch_queue_t)queue
handler: (dispatch_block_t)handler
{
if (nil != (self = [super init]))
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_event_handler(timer, handler);
dispatch_source_set_cancel_handler(timer, ^{
dispatch_release(timer);
});
_timer = timer;
_timeoutMs = -1;
_isSuspended = YES;
}
return self;
}
- (void) dealloc
{
[self cancel];
[super dealloc];
}
- (NSInteger) timeout
{
return _timeoutMs;
}
- (void) setTimeout: (NSInteger)timeoutMs
{
if (timeoutMs >= 0)
{
_timeoutMs = 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 (_isSuspended)
{
_isSuspended = NO;
dispatch_resume(_timer);
}
}
else
{
[self suspend];
}
}
- (void)suspend
{
if (!_isSuspended)
{
_isSuspended = YES;
_timeoutMs = -1;
dispatch_suspend(_timer);
}
}
- (void) cancel
{
if (_timer)
{
dispatch_source_cancel(_timer);
_timer = NULL; // released in cancel handler
}
}
@end

View file

@ -1,140 +0,0 @@
#ifndef INCLUDED_GSTRANSFERTSTATE_H
#define INCLUDED_GSTRANSFERTSTATE_H
#import "GSURLSessionTaskBodySource.h"
@class GSURLSessionTaskBodySource;
@class NSArray;
@class NSData;
@class NSMutableData;
@class NSFileHandle;
@class NSHTTPURLResponse;
@class NSURL;
@class NSURLResponse;
typedef NS_ENUM(NSUInteger, GSParsedResponseHeaderType) {
GSParsedResponseHeaderTypePartial,
GSParsedResponseHeaderTypeComplete
};
/*
* A native protocol like HTTP header being parsed.
*
* It can either be complete (i.e. the final CR LF CR LF has been
* received), or partial.
*/
@interface GSParsedResponseHeader: NSObject
{
NSArray *_lines;
GSParsedResponseHeaderType _type;
}
- (GSParsedResponseHeaderType) type;
/*
* Parse a header line passed by libcurl.
*
* These contain the <CRLF> ending and the final line contains nothing but
* that ending.
* - Returns: Returning nil indicates failure. Otherwise returns a new
* `GSParsedResponseHeader` with the given line added.
*/
- (instancetype) byAppendingHeaderLine: (NSData*)data;
- (NSHTTPURLResponse*) createHTTPURLResponseForURL: (NSURL*)URL;
@end
typedef NS_ENUM(NSUInteger, GSDataDrainType) {
// Concatenate in-memory
GSDataDrainInMemory,
// Write to file
GSDataDrainTypeToFile,
// Do nothing. Might be forwarded to delegate
GSDataDrainTypeIgnore,
};
@interface GSDataDrain: NSObject
{
GSDataDrainType _type;
NSMutableData *_data;
NSURL *_fileURL;
NSFileHandle *_fileHandle;
}
- (GSDataDrainType) type;
- (void) setType: (GSDataDrainType)type;
- (NSMutableData*) data;
- (NSURL*) fileURL;
- (NSFileHandle*) fileHandle;
@end
/*
* State related to an ongoing transfer.
*
* This contains headers received so far, body data received so far, etc.
*
* There's a strict 1-to-1 relationship between a `GSEasyHandle` and a
* `GSTransferState`.
*/
@interface GSTransferState: NSObject
{
NSURL *_url; // The URL that's being requested
GSParsedResponseHeader *_parsedResponseHeader; // Raw headers received.
NSURLResponse *_response; // Once the headers is complete, this will contain the response
id<GSURLSessionTaskBodySource> _requestBodySource; // The body data to be sent in the request
GSDataDrain *_bodyDataDrain; // Body data received
BOOL _isHeaderComplete;
}
// Transfer state that can receive body data, but will not send body data.
- (instancetype) initWithURL: (NSURL*)url
bodyDataDrain: (GSDataDrain*)bodyDataDrain;
// Transfer state that sends body data and can receive body data.
- (instancetype) initWithURL: (NSURL*)url
bodyDataDrain: (GSDataDrain*)bodyDataDrain
bodySource: (id<GSURLSessionTaskBodySource>)bodySource;
- (instancetype) initWithURL: (NSURL*)url
parsedResponseHeader: (GSParsedResponseHeader*)parsedResponseHeader
response: (NSURLResponse*)response
bodySource: (id<GSURLSessionTaskBodySource>)bodySource
bodyDataDrain: (GSDataDrain*)bodyDataDrain;
/*
* Append body data
*/
- (instancetype) byAppendingBodyData: (NSData*)bodyData;
/*
* Appends a header line
*
* Will set the complete response once the header is complete, i.e. the
* return value's `isHeaderComplete` will then by `YES`.
*
* When a parsing error occurs `error` will be set.
*/
- (instancetype) byAppendingHTTPHeaderLineData: (NSData*)data
error: (NSError**)error;
- (NSURLResponse*) response;
- (void) setResponse: (NSURLResponse*)response;
- (BOOL) isHeaderComplete;
- (id<GSURLSessionTaskBodySource>) requestBodySource;
- (GSDataDrain*) bodyDataDrain;
- (NSURL*) URL;
@end
#endif

View file

@ -1,487 +0,0 @@
#import "GSTransferState.h"
#import "GSURLSessionTaskBodySource.h"
#import "Foundation/NSArray.h"
#import "Foundation/NSCharacterSet.h"
#import "Foundation/NSData.h"
#import "Foundation/NSDictionary.h"
#import "Foundation/NSFileHandle.h"
#import "Foundation/NSError.h"
#import "Foundation/NSURL.h"
#import "Foundation/NSURLError.h"
#import "Foundation/NSURLResponse.h"
#import "Foundation/NSURLSession.h"
#import "Foundation/NSUUID.h"
#import "Foundation/NSFileManager.h"
#import "Foundation/NSPathUtilities.h"
#define GS_DELIMITERS_CR 0x0d
#define GS_DELIMITERS_LR 0x0a
@implementation GSParsedResponseHeader
- (instancetype) init
{
if (nil != (self = [super init]))
{
_lines = [[NSMutableArray alloc] init];
_type = GSParsedResponseHeaderTypePartial;
}
return self;
}
- (void) dealloc
{
DESTROY(_lines);
[super dealloc];
}
- (GSParsedResponseHeaderType) type
{
return _type;
}
- (void) setType: (GSParsedResponseHeaderType)type
{
_type = type;
}
- (void) setLines: (NSArray*)lines
{
ASSIGN(_lines, lines);
}
- (instancetype) byAppendingHeaderLine: (NSData*)data
{
NSUInteger length = [data length];
if (length >= 2)
{
uint8_t last2;
uint8_t last1;
[data getBytes: &last2 range: NSMakeRange(length - 2, 1)];
[data getBytes: &last1 range: NSMakeRange(length - 1, 1)];
if (GS_DELIMITERS_CR == last2 && GS_DELIMITERS_LR == last1)
{
NSData *lineBuffer;
NSString *line;
lineBuffer = [data subdataWithRange: NSMakeRange(0, length - 2)];
line = AUTORELEASE([[NSString alloc] initWithData: lineBuffer
encoding: NSUTF8StringEncoding]);
if (nil == line)
{
return nil;
}
return [self _byAppendingHeaderLine: line];
}
}
return nil;
}
- (NSHTTPURLResponse*) createHTTPURLResponseForURL: (NSURL*)URL
{
NSArray *tail;
NSArray *startLine;
NSDictionary *headerFields;
NSString *head;
NSString *s, *v;
head = [_lines firstObject];
if (nil == head)
{
return nil;
}
if ([_lines count] == 0)
{
return nil;
}
tail = [_lines subarrayWithRange: NSMakeRange(1, [_lines count] - 1)];
startLine = [self statusLineFromLine: head];
if (nil == startLine)
{
return nil;
}
headerFields = [self createHeaderFieldsFromLines: tail];
v = [startLine objectAtIndex: 0];
s = [startLine objectAtIndex: 1];
return AUTORELEASE([[NSHTTPURLResponse alloc] initWithURL: URL
statusCode: [s integerValue]
HTTPVersion: v
headerFields: headerFields]);
}
- (NSArray*) statusLineFromLine: (NSString*)line
{
NSArray *a;
NSString *s;
NSInteger status;
a = [line componentsSeparatedByString: @" "];
if ([a count] < 3)
{
return nil;
}
s = [a objectAtIndex: 1];
status = [s integerValue];
if (status >= 100 && status <= 999)
{
return a;
}
else
{
return nil;
}
}
- (NSDictionary *) createHeaderFieldsFromLines: (NSArray *)lines
{
NSMutableDictionary *headerFields = nil;
NSEnumerator *e;
NSString *line;
e = [_lines objectEnumerator];
while (nil != (line = [e nextObject]))
{
NSRange r;
NSString *head;
NSString *tail;
NSCharacterSet *set;
NSString *key;
NSString *value;
NSString *v;
r = [line rangeOfString: @":"];
if (r.location != NSNotFound)
{
head = [line substringToIndex: r.location];
tail = [line substringFromIndex: r.location + 1];
set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
key = [head stringByTrimmingCharactersInSet: set];
value = [tail stringByTrimmingCharactersInSet: set];
if (nil != key && nil != value)
{
if (nil == headerFields)
{
headerFields = [NSMutableDictionary dictionary];
}
if (nil != [headerFields objectForKey: key])
{
v = [NSString stringWithFormat:@"%@, %@",
[headerFields objectForKey: key], value];
[headerFields setObject: v forKey: key];
}
else
{
[headerFields setObject: value forKey: key];
}
}
}
else
{
continue;
}
}
return AUTORELEASE([headerFields copy]);
}
- (instancetype) _byAppendingHeaderLine: (NSString*)line
{
GSParsedResponseHeader *header;
header = AUTORELEASE([[GSParsedResponseHeader alloc] init]);
if ([line length] == 0)
{
switch (_type)
{
case GSParsedResponseHeaderTypePartial:
{
[header setType: GSParsedResponseHeaderTypeComplete];
[header setLines: _lines];
return header;
}
case GSParsedResponseHeaderTypeComplete:
return header;
}
}
else
{
NSMutableArray *lines = [[self partialResponseHeader] mutableCopy];
[lines addObject:line];
[header setType: GSParsedResponseHeaderTypePartial];
[header setLines: lines];
RELEASE(lines);
return header;
}
}
- (NSArray*) partialResponseHeader
{
switch (_type)
{
case GSParsedResponseHeaderTypeComplete:
return [NSArray array];
case GSParsedResponseHeaderTypePartial:
return _lines;
}
}
@end
@implementation GSDataDrain
- (void) dealloc
{
DESTROY(_data);
DESTROY(_fileURL);
DESTROY(_fileHandle);
[super dealloc];
}
- (GSDataDrainType) type
{
return _type;
}
- (void) setType: (GSDataDrainType)type
{
_type = type;
}
- (NSMutableData*) data
{
if (!_data)
{
_data = [[NSMutableData alloc] init];
}
return _data;
}
- (NSURL*) fileURL
{
/* Generate a random fileURL if fileURL is not initialized.
* It would be nice to have this implemented in an initializer. */
if (!_fileURL)
{
NSUUID *randomUUID;
NSString *fileName;
NSURL *tempURL;
randomUUID = [NSUUID UUID];
fileName = [[randomUUID UUIDString] stringByAppendingPathExtension: @"tmp"];
tempURL = [NSURL fileURLWithPath: NSTemporaryDirectory()];
_fileURL = RETAIN([NSURL fileURLWithPath: fileName relativeToURL: tempURL]);
}
return _fileURL;
}
- (NSFileHandle*) fileHandle
{
/* Create temporary file and open a fileHandle for writing. */
if (!_fileHandle)
{
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager createFileAtPath: [[self fileURL] relativePath]
contents: nil
attributes: nil];
_fileHandle = RETAIN([NSFileHandle fileHandleForWritingToURL: [self fileURL] error: NULL]);
}
return _fileHandle;
}
@end
@implementation GSTransferState
- (instancetype) initWithURL: (NSURL*)url
bodyDataDrain: (GSDataDrain*)bodyDataDrain
{
return [self initWithURL: url
parsedResponseHeader: nil
response: nil
bodySource: nil
bodyDataDrain: bodyDataDrain];
}
- (instancetype) initWithURL: (NSURL*)url
bodyDataDrain: (GSDataDrain*)bodyDataDrain
bodySource: (id<GSURLSessionTaskBodySource>)bodySource
{
return [self initWithURL: url
parsedResponseHeader: nil
response: nil
bodySource: bodySource
bodyDataDrain: bodyDataDrain];
}
- (instancetype) initWithURL: (NSURL*)url
parsedResponseHeader: (GSParsedResponseHeader*)parsedResponseHeader
response: (NSURLResponse*)response
bodySource: (id<GSURLSessionTaskBodySource>)bodySource
bodyDataDrain: (GSDataDrain*)bodyDataDrain
{
if (nil != (self = [super init]))
{
ASSIGN(_url, url);
if (nil != parsedResponseHeader)
{
ASSIGN(_parsedResponseHeader, parsedResponseHeader);
}
else
{
_parsedResponseHeader = [[GSParsedResponseHeader alloc] init];
}
ASSIGN(_response, response);
ASSIGN(_requestBodySource, bodySource);
ASSIGN(_bodyDataDrain, bodyDataDrain);
}
return self;
}
- (void) dealloc
{
DESTROY(_url);
DESTROY(_parsedResponseHeader);
DESTROY(_response);
DESTROY(_requestBodySource);
DESTROY(_bodyDataDrain);
[super dealloc];
}
- (instancetype) byAppendingBodyData: (NSData*)bodyData
{
switch ([_bodyDataDrain type])
{
case GSDataDrainInMemory:
[[_bodyDataDrain data] appendData: bodyData];
return self;
case GSDataDrainTypeToFile:
{
NSFileHandle *fileHandle;
fileHandle = [_bodyDataDrain fileHandle];
[fileHandle seekToEndOfFile];
[fileHandle writeData: bodyData];
return self;
}
case GSDataDrainTypeIgnore:
return self;
}
}
- (instancetype) byAppendingHTTPHeaderLineData: (NSData*)data
error: (NSError**)error
{
GSParsedResponseHeader *h;
h = [_parsedResponseHeader byAppendingHeaderLine: data];
if (nil == h)
{
if (error != NULL)
{
*error = [NSError errorWithDomain: NSURLErrorDomain
code: -1
userInfo: nil];
}
return nil;
}
if ([h type] == GSParsedResponseHeaderTypeComplete)
{
NSHTTPURLResponse *response;
GSParsedResponseHeader *ph;
GSTransferState *ts;
response = [h createHTTPURLResponseForURL: _url];
if (nil == response)
{
if (error != NULL)
{
*error = [NSError errorWithDomain: NSURLErrorDomain
code: -1
userInfo: nil];
}
return nil;
}
ph = AUTORELEASE([[GSParsedResponseHeader alloc] init]);
ts = [[GSTransferState alloc] initWithURL: _url
parsedResponseHeader: ph
response: response
bodySource: _requestBodySource
bodyDataDrain: _bodyDataDrain];
return AUTORELEASE(ts);
}
else
{
GSTransferState *ts;
ts = [[GSTransferState alloc] initWithURL: _url
parsedResponseHeader: h
response: nil
bodySource: _requestBodySource
bodyDataDrain: _bodyDataDrain];
return AUTORELEASE(ts);
}
}
- (BOOL) isHeaderComplete
{
return _response != nil;
}
- (NSURLResponse*) response
{
return _response;
}
- (void) setResponse: (NSURLResponse*)response
{
ASSIGN(_response, response);
}
- (id<GSURLSessionTaskBodySource>) requestBodySource
{
return _requestBodySource;
}
- (GSDataDrain*) bodyDataDrain
{
return _bodyDataDrain;
}
- (NSURL*) URL
{
return _url;
}
@end

View file

@ -61,6 +61,7 @@
- (id<GSLogDelegate>) _debugLogDelegate;
- (id) _propertyForKey: (NSString*)key;
- (void) _setProperty: (id)value forKey: (NSString*)key;
- (NSDictionary *) _insensitiveHeaders;
@end

View file

@ -1,46 +0,0 @@
#ifndef INCLUDED_GSURLSESSIONTASKBODY_H
#define INCLUDED_GSURLSESSIONTASKBODY_H
#import "common.h"
@class NSData;
@class NSError;
@class NSInputStream;
@class NSNumber;
@class NSURL;
typedef NS_ENUM(NSUInteger, GSURLSessionTaskBodyType) {
GSURLSessionTaskBodyTypeNone,
GSURLSessionTaskBodyTypeData,
// Body data is read from the given file URL
GSURLSessionTaskBodyTypeFile,
// Body data is read from the given input stream
GSURLSessionTaskBodyTypeStream,
};
@interface GSURLSessionTaskBody : NSObject
{
GSURLSessionTaskBodyType _type;
NSData *_data;
NSURL *_fileURL;
NSInputStream *_inputStream;
}
- (instancetype) init;
- (instancetype) initWithData: (NSData*)data;
- (instancetype) initWithFileURL: (NSURL*)fileURL;
- (instancetype) initWithInputStream: (NSInputStream*)inputStream;
- (GSURLSessionTaskBodyType) type;
- (NSData*) data;
- (NSURL*) fileURL;
- (NSInputStream*) inputStream;
- (NSNumber*) getBodyLengthWithError: (NSError**)error;
@end
#endif

View file

@ -1,111 +0,0 @@
#import "GSURLSessionTaskBody.h"
#import "Foundation/NSData.h"
#import "Foundation/NSFileManager.h"
#import "Foundation/NSStream.h"
#import "Foundation/NSURL.h"
#import "Foundation/NSValue.h"
@implementation GSURLSessionTaskBody
- (instancetype) init
{
if (nil != (self = [super init]))
{
_type = GSURLSessionTaskBodyTypeNone;
}
return self;
}
- (instancetype) initWithData: (NSData*)data
{
if (nil != (self = [super init]))
{
_type = GSURLSessionTaskBodyTypeData;
ASSIGN(_data, data);
}
return self;
}
- (instancetype) initWithFileURL: (NSURL*)fileURL
{
if (nil != (self = [super init]))
{
_type = GSURLSessionTaskBodyTypeFile;
ASSIGN(_fileURL, fileURL);
}
return self;
}
- (instancetype) initWithInputStream: (NSInputStream*)inputStream
{
if (nil != (self = [super init]))
{
_type = GSURLSessionTaskBodyTypeStream;
ASSIGN(_inputStream, inputStream);
}
return self;
}
- (void) dealloc
{
DESTROY(_data);
DESTROY(_fileURL);
DESTROY(_inputStream);
[super dealloc];
}
- (GSURLSessionTaskBodyType) type
{
return _type;
}
- (NSData*) data
{
return _data;
}
- (NSURL*) fileURL
{
return _fileURL;
}
- (NSInputStream*) inputStream
{
return _inputStream;
}
- (NSNumber*) getBodyLengthWithError: (NSError**)error
{
switch (_type)
{
case GSURLSessionTaskBodyTypeNone:
return [NSNumber numberWithInt: 0];
case GSURLSessionTaskBodyTypeData:
return [NSNumber numberWithUnsignedInteger: [_data length]];
case GSURLSessionTaskBodyTypeFile:
{
NSDictionary *attributes;
attributes = [[NSFileManager defaultManager]
attributesOfItemAtPath: [_fileURL path]
error: error];
if (!error)
{
NSNumber *size = [attributes objectForKey: NSFileSize];
return size;
}
else
{
return nil;
}
}
case GSURLSessionTaskBodyTypeStream:
return nil;
}
}
@end

View file

@ -1,52 +0,0 @@
#ifndef INCLUDED_GSURLSESSIONTASKBODYSOURCE_H
#define INCLUDED_GSURLSESSIONTASKBODYSOURCE_H
#import "common.h"
#import "GSDispatch.h"
@class NSFileHandle;
@class NSInputStream;
typedef NS_ENUM(NSUInteger, GSBodySourceDataChunk) {
GSBodySourceDataChunkData,
// The source is depleted.
GSBodySourceDataChunkDone,
// Retry later to get more data.
GSBodySourceDataChunkRetryLater,
GSBodySourceDataChunkError
};
/*
* A (non-blocking) source for body data.
*/
@protocol GSURLSessionTaskBodySource <NSObject>
/*
* Get the next chunck of data.
*/
- (void) getNextChunkWithLength: (NSInteger)length
completionHandler: (void (^)(GSBodySourceDataChunk chunk, NSData *data))completionHandler;
@end
@interface GSBodyStreamSource : NSObject <GSURLSessionTaskBodySource>
- (instancetype) initWithInputStream: (NSInputStream*)inputStream;
@end
@interface GSBodyDataSource : NSObject <GSURLSessionTaskBodySource>
- (instancetype)initWithData:(NSData *)data;
@end
@interface GSBodyFileSource : NSObject <GSURLSessionTaskBodySource>
- (instancetype) initWithFileURL: (NSURL*)fileURL
workQueue: (dispatch_queue_t)workQueue
dataAvailableHandler: (void (^)(void))dataAvailableHandler;
@end
#endif

View file

@ -1,152 +0,0 @@
#import "GSURLSessionTaskBodySource.h"
#import "Foundation/NSData.h"
#import "Foundation/NSStream.h"
@implementation GSBodyStreamSource
{
NSInputStream *_inputStream;
}
- (instancetype) initWithInputStream: (NSInputStream*)inputStream
{
if (nil != (self = [super init]))
{
ASSIGN(_inputStream, inputStream);
if ([_inputStream streamStatus] == NSStreamStatusNotOpen)
{
[_inputStream open];
}
}
return self;
}
- (void) dealloc
{
DESTROY(_inputStream);
[super dealloc];
}
- (void) getNextChunkWithLength: (NSInteger)length
completionHandler: (void (^)(GSBodySourceDataChunk, NSData*))completionHandler
{
if (nil == completionHandler)
{
return;
}
if (![_inputStream hasBytesAvailable])
{
completionHandler(GSBodySourceDataChunkDone, nil);
return;
}
uint8_t buffer[length];
NSInteger readBytes;
NSData *data;
readBytes = [_inputStream read: buffer maxLength: length];
if (readBytes > 0)
{
data = AUTORELEASE([[NSData alloc] initWithBytes: buffer
length: readBytes]);
completionHandler(GSBodySourceDataChunkData, data);
}
else if (readBytes == 0)
{
completionHandler(GSBodySourceDataChunkDone, nil);
}
else
{
completionHandler(GSBodySourceDataChunkError, nil);
}
}
@end
@implementation GSBodyDataSource
{
NSData *_data;
}
- (instancetype) initWithData: (NSData*)data
{
if (nil != (self = [super init]))
{
ASSIGN(_data, data);
}
return self;
}
- (void) dealloc
{
DESTROY(_data);
[super dealloc];
}
- (void) getNextChunkWithLength: (NSInteger)length
completionHandler: (void (^)(GSBodySourceDataChunk, NSData*))completionHandler
{
if (nil == completionHandler)
{
return;
}
NSUInteger remaining = [_data length];
if (remaining == 0)
{
completionHandler(GSBodySourceDataChunkDone, nil);
}
else if (remaining <= length)
{
NSData *r = AUTORELEASE([[NSData alloc] initWithData: _data]);
DESTROY(_data);
completionHandler(GSBodySourceDataChunkData, r);
}
else
{
NSData *chunk = [_data subdataWithRange: NSMakeRange(0, length)];
NSData *remainder = [_data subdataWithRange:
NSMakeRange(length - 1, [_data length] - length)];
ASSIGN(_data, remainder);
completionHandler(GSBodySourceDataChunkData, chunk);
}
}
@end
@implementation GSBodyFileSource
{
NSURL *_fileURL;
dispatch_queue_t _workQueue;
void (^_dataAvailableHandler)(void);
}
- (instancetype) initWithFileURL: (NSURL*)fileURL
workQueue: (dispatch_queue_t)workQueue
dataAvailableHandler: (void (^)(void))dataAvailableHandler
{
if (nil != (self = [super init]))
{
ASSIGN(_fileURL, fileURL);
_workQueue = workQueue;
_dataAvailableHandler = dataAvailableHandler;
}
return self;
}
- (void) dealloc
{
DESTROY(_fileURL);
[super dealloc];
}
- (void) getNextChunkWithLength: (NSInteger)length
completionHandler: (void (^)(GSBodySourceDataChunk chunk, NSData *data))completionHandler
{
//FIXME
}
@end

View file

@ -43,7 +43,9 @@
#import "Foundation/NSSet.h"
#import "Foundation/NSValue.h"
#import "Foundation/NSString.h"
#import "Foundation/NSCalendarDate.h"
#import "Foundation/NSDateFormatter.h"
#import "Foundation/NSLocale.h"
#import "Foundation/NSTimeZone.h"
#import "GNUstepBase/Unicode.h"
static NSString * const HTTPCookieHTTPOnly = @"HTTPOnly";
@ -681,10 +683,22 @@ _setCookieKey(NSMutableDictionary *dict, NSString *key, NSString *value)
else if ([[key lowercaseString] isEqual: @"expires"])
{
NSDate *expireDate;
expireDate = [NSCalendarDate dateWithString: value
calendarFormat: @"%a, %d-%b-%Y %I:%M:%S %Z"];
NSDateFormatter *formatter;
NSLocale *locale;
NSTimeZone *gmtTimeZone;
locale = [NSLocale localeWithLocaleIdentifier: @"en_US"];
gmtTimeZone = [NSTimeZone timeZoneWithAbbreviation: @"GMT"];
formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat: @"EEE, dd-MMM-yyyy HH:mm:ss zzz"];
[formatter setLocale: locale];
[formatter setTimeZone: gmtTimeZone];
expireDate = [formatter dateFromString:value];
if (expireDate)
[dict setObject: expireDate forKey: NSHTTPCookieExpires];
RELEASE(formatter);
}
else if ([[key lowercaseString] isEqual: @"max-age"])
[dict setObject: value forKey: NSHTTPCookieMaximumAge];
@ -697,7 +711,7 @@ _setCookieKey(NSMutableDictionary *dict, NSString *key, NSString *value)
else if ([[key lowercaseString] isEqual: @"secure"])
[dict setObject: [NSNumber numberWithBool: YES]
forKey: NSHTTPCookieSecure];
else if ([[key lowercaseString] isEqual:@"httponly"])
else if ([[key lowercaseString] isEqual: @"httponly"])
[dict setObject: [NSNumber numberWithBool: YES]
forKey: HTTPCookieHTTPOnly];
else if ([[key lowercaseString] isEqual: @"version"])

View file

@ -593,13 +593,15 @@ static NSArray *empty = nil;
- (void) main
{
NSEnumerator *en = [[self executionBlocks] objectEnumerator];
NSEnumerator *en = [_executionBlocks objectEnumerator];
GSBlockOperationBlock theBlock;
while ((theBlock = (GSBlockOperationBlock)[en nextObject]) != NULL)
{
CALL_NON_NULL_BLOCK_NO_ARGS(theBlock);
}
[_executionBlocks removeAllObjects];
}
@end

View file

@ -39,6 +39,7 @@ typedef struct {
NSMutableDictionary *headers;
BOOL shouldHandleCookies;
BOOL debug;
BOOL assumesHTTP3Capable;
id<GSLogDelegate> ioDelegate;
NSURL *URL;
NSURL *mainDocumentURL;
@ -56,6 +57,9 @@ typedef struct {
@interface _GSMutableInsensitiveDictionary : NSMutableDictionary
@end
@interface _GSInsensitiveDictionary : NSMutableDictionary
@end
@implementation NSURLRequest
+ (id) allocWithZone: (NSZone*)z
@ -116,6 +120,7 @@ typedef struct {
ASSIGN(inst->bodyStream, this->bodyStream);
ASSIGN(inst->method, this->method);
inst->shouldHandleCookies = this->shouldHandleCookies;
inst->assumesHTTP3Capable = this->assumesHTTP3Capable;
inst->debug = this->debug;
inst->ioDelegate = this->ioDelegate;
inst->headers = [this->headers mutableCopy];
@ -375,6 +380,11 @@ typedef struct {
return [this->headers objectForKey: field];
}
- (BOOL) assumesHTTP3Capable
{
return this->assumesHTTP3Capable;
}
@end
@ -454,6 +464,11 @@ typedef struct {
}
}
- (void)setAssumesHTTP3Capable:(BOOL)capable
{
this->assumesHTTP3Capable = capable;
}
@end
@implementation NSURLRequest (Private)
@ -468,6 +483,11 @@ typedef struct {
return this->ioDelegate;
}
- (NSDictionary *) _insensitiveHeaders
{
return [this->headers copy];
}
- (id) _propertyForKey: (NSString*)key
{
return [this->properties objectForKey: key];

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,283 @@
/**
NSURLSessionConfiguration.m
Copyright (C) 2017-2024 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Date: May 2024
Author: Hugo Melder <hugo@algoriddim.com>
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 "Foundation/NSURLSession.h"
#import "Foundation/NSHTTPCookie.h"
// TODO: This is the old implementation. It requires a rewrite!
@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 = nil;
_HTTPMaximumConnectionsPerHost = 6;
_HTTPShouldUsePipelining = YES;
_HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
_HTTPCookieStorage = nil;
_HTTPShouldSetCookies = NO;
_HTTPAdditionalHeaders = nil;
_HTTPMaximumConnectionLifetime = 0; // Zero or less means default
_timeoutIntervalForResource = 604800; // 7 days in seconds
_timeoutIntervalForRequest = 60; // 60 seconds
}
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;
}
- (NSTimeInterval)timeoutIntervalForRequest
{
return _timeoutIntervalForRequest;
}
- (void)setTimeoutIntervalForRequest:(NSTimeInterval)interval
{
_timeoutIntervalForRequest = interval;
}
- (NSTimeInterval)timeoutIntervalForResource
{
return _timeoutIntervalForResource;
}
- (void)setTimeoutIntervalForResource:(NSTimeInterval)interval
{
_timeoutIntervalForResource = interval;
}
- (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 retain];
copy->_HTTPShouldSetCookies = _HTTPShouldSetCookies;
copy->_HTTPAdditionalHeaders = [_HTTPAdditionalHeaders copyWithZone:zone];
copy->_timeoutIntervalForRequest = _timeoutIntervalForRequest;
copy->_timeoutIntervalForResource = _timeoutIntervalForResource;
}
return copy;
}
@end

View file

@ -0,0 +1,90 @@
/**
NSURLSessionPrivate.h
Copyright (C) 2017-2024 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Date: May 2024
Author: Hugo Melder <hugo@algoriddim.com>
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 "common.h"
#import "Foundation/NSURLSession.h"
#import "Foundation/NSDictionary.h"
#import <curl/curl.h>
#import <dispatch/dispatch.h>
extern NSString *GS_NSURLSESSION_DEBUG_KEY;
/* libcurl may request a full-duplex socket configuration with
* CURL_POLL_INOUT, but libdispatch distinguishes between a read and write
* socket source.
*
* We thus need to keep track of two dispatch sources. One may be set to NULL
* if not used.
*/
struct SourceInfo
{
dispatch_source_t readSocket;
dispatch_source_t writeSocket;
};
typedef NS_ENUM(NSInteger, GSURLSessionProperties) {
GSURLSessionStoresDataInMemory = (1 << 0),
GSURLSessionWritesDataToFile = (1 << 1),
GSURLSessionUpdatesDelegate = (1 << 2),
GSURLSessionHasCompletionHandler = (1 << 3),
GSURLSessionHasInputStream = (1 << 4)
};
@interface
NSURLSession (Private)
- (dispatch_queue_t)_workQueue;
- (NSData *)_certificateBlob;
- (NSString *)_certificatePath;
/* Adds the internal easy handle to the multi handle.
* Modifications are performed on the workQueue.
*/
- (void)_resumeTask:(NSURLSessionTask *)task;
/* The following methods must only be called from within callbacks dispatched on
* the workQueue.*/
- (void)_setTimer:(NSInteger)timeout;
- (void)_suspendTimer;
/* Required for manual redirects.
*/
- (void)_addHandle:(CURL *)easy;
- (void)_removeHandle:(CURL *)easy;
- (void)_removeSocket:(struct SourceInfo *)sources;
- (int)_addSocket:(curl_socket_t)socket easyHandle:(CURL *)easy what:(int)what;
- (int)_setSocket:(curl_socket_t)socket
sources:(struct SourceInfo *)sources
what:(int)what;
@end

1629
Source/NSURLSessionTask.m Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,123 @@
/**
NSURLSessionTaskPrivate.h
Copyright (C) 2017-2024 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Date: May 2024
Author: Hugo Melder <hugo@algoriddim.com>
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 "Foundation/NSDictionary.h"
#import "Foundation/NSFileHandle.h"
#import "Foundation/NSURLSession.h"
#import <curl/curl.h>
@interface
NSURLSessionTask (Private)
- (instancetype)initWithSession:(NSURLSession *)session
request:(NSURLRequest *)request
taskIdentifier:(NSUInteger)identifier;
- (CURL *)_easyHandle;
/* Enable or disable libcurl verbose output. Disabled by default. */
- (void)_setVerbose:(BOOL)flag;
/* This method is called by -[NSURLSession _checkForCompletion]
*
* We release the session (previously retained in -[NSURLSessionTask resume])
* here and inform the delegate about the transfer state.
*/
- (void)_transferFinishedWithCode:(CURLcode)code;
/* Explicitly enable data upload with an optional estimated size. Set to 0 if
* not available.
*
* This may be used when a body stream is passed at a later stage
* (see URLSession:task:needNewBodyStream:).
*/
- (void)_enableUploadWithSize:(NSInteger)size;
- (void)_setBodyStream:(NSInputStream *)stream;
- (void)_enableUploadWithData:(NSData *)data;
- (void)_enableAutomaticRedirects:(BOOL)flag;
/* Assign with copying */
- (void)_setOriginalRequest:(NSURLRequest *)request;
- (void)_setCurrentRequest:(NSURLRequest *)request;
- (void)_setResponse:(NSURLResponse *)response;
- (void)_setCookiesFromHeaders:(NSDictionary *)headers;
- (void)_setCountOfBytesSent:(int64_t)count;
- (void)_setCountOfBytesReceived:(int64_t)count;
- (void)_setCountOfBytesExpectedToSend:(int64_t)count;
- (void)_setCountOfBytesExpectedToReceive:(int64_t)count;
- (NSMutableDictionary *)_taskData;
- (NSURLSession *)_session;
/* Task specific properties.
*
* See GSURLSessionProperties in NSURLSessionPrivate.h.
*/
- (NSInteger)_properties;
- (void)_setProperties:(NSInteger)properties;
/* This value is periodically checked in progress_callback.
* We then abort the transfer in the progress_callback if this flag is set.
*/
- (BOOL)_shouldStopTransfer;
- (void)_setShouldStopTransfer:(BOOL)flag;
- (NSInteger)_numberOfRedirects;
- (void)_setNumberOfRedirects:(NSInteger)redirects;
- (NSInteger)_headerCallbackCount;
- (void)_setHeaderCallbackCount:(NSInteger)count;
- (NSFileHandle *)_createTemporaryFileHandleWithError:(NSError **)error;
@end
@interface
NSURLSessionDataTask (Private)
- (GSNSURLSessionDataCompletionHandler)_completionHandler;
- (void)_setCompletionHandler:(GSNSURLSessionDataCompletionHandler)handler;
@end
@interface
NSURLSessionDownloadTask (Private)
- (GSNSURLSessionDownloadCompletionHandler)_completionHandler;
- (int64_t)_countOfBytesWritten;
- (void)_updateCountOfBytesWritten:(int64_t)count;
- (void)_setCompletionHandler:(GSNSURLSessionDownloadCompletionHandler)handler;
@end

View file

@ -298,6 +298,7 @@ GS_DECLARE NSString* const NSFormalName = @"NSFormalName";
/* For GNUstep */
GS_DECLARE NSString* const GSLocale = @"GSLocale";
GS_DECLARE NSString *const GSCACertificateFilePath = @"GSCACertificateFilePath";
/*