mirror of
https://github.com/gnustep/libs-base.git
synced 2025-05-30 08:21:25 +00:00
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:
parent
e3208590b1
commit
a1913e5540
53 changed files with 5410 additions and 7178 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -61,6 +61,7 @@
|
|||
- (id<GSLogDelegate>) _debugLogDelegate;
|
||||
- (id) _propertyForKey: (NSString*)key;
|
||||
- (void) _setProperty: (id)value forKey: (NSString*)key;
|
||||
- (NSDictionary *) _insensitiveHeaders;
|
||||
@end
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
283
Source/NSURLSessionConfiguration.m
Normal file
283
Source/NSURLSessionConfiguration.m
Normal 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
|
90
Source/NSURLSessionPrivate.h
Normal file
90
Source/NSURLSessionPrivate.h
Normal 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
1629
Source/NSURLSessionTask.m
Normal file
File diff suppressed because it is too large
Load diff
123
Source/NSURLSessionTaskPrivate.h
Normal file
123
Source/NSURLSessionTaskPrivate.h
Normal 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
|
|
@ -298,6 +298,7 @@ GS_DECLARE NSString* const NSFormalName = @"NSFormalName";
|
|||
|
||||
/* For GNUstep */
|
||||
GS_DECLARE NSString* const GSLocale = @"GSLocale";
|
||||
GS_DECLARE NSString *const GSCACertificateFilePath = @"GSCACertificateFilePath";
|
||||
|
||||
|
||||
/*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue