Revert "NSURLSession Reimplementation (#411)"

This reverts commit 07233534e6.
This commit is contained in:
rfm 2024-07-02 19:19:14 +01:00 committed by GitHub
parent 07233534e6
commit 3fedf31c2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 7197 additions and 5391 deletions

View file

@ -65,6 +65,7 @@ SortIncludes: false
SpaceAfterCStyleCast: true
SpaceBeforeParens: ControlStatements
SpacesBeforeTrailingComments: 1
UseTab: Always
AlignEscapedNewlines: Right
AlignTrailingComments: true
AllowShortFunctionsOnASingleLine: All

6
.gitignore vendored
View file

@ -83,12 +83,6 @@ DerivedData/
### Xcode Patch ###
**/xcshareddata/WorkspaceSettings.xcsettings
# clangd cache
.cache
# compile commands
compile_commands.json
# End of https://www.gitignore.io/api/xcode
.cache

View file

@ -1,56 +1,3 @@
2024-07-02 Hugo Melder <hugo@algoriddim.com>
* Source/GSEasyHandle.h:
* Source/GSEasyHandle.m:
* Source/GSHTTPURLProtocol.h:
* Source/GSHTTPURLProtocol.m:
* Source/GSMultiHandle.h:
* Source/GSMultiHandle.m:
* Source/GSNativeProtocol.h:
* Source/GSNativeProtocol.m:
* Source/GSTaskRegistry.h:
* Source/GSTaskRegistry.m:
* Source/GSTimeoutSource.h:
* Source/GSTimeoutSource.m:
* Source/GSTransferState.h:
* Source/GSTransferState.m:
* Source/GSURLPrivate.h:
* Source/GSURLSessionTaskBody.h:
* Source/GSURLSessionTaskBody.m:
* Source/GSURLSessionTaskBodySource.h:
* Source/GSURLSessionTaskBodySource.m:
Deleted old NSURLSession implementation.
* Headers/Foundation/NSURLSession.h:
* Source/NSURLSessionPrivate.h:
* Source/NSURLSession.m:
* Source/NSURLSessionConfiguration.m:
* Source/NSURLSessionTaskPrivate.h:
* Source/NSURLSessionTask.m:
* Source/NSURLSessionConfiguration.m:
The new NSURLSession implementation was reduced to just two main classes:
NSURLSession and NSURLSessionTask. URLSession manages a libcurl multi
handle and the required plumming via libdispatch. Timeout and socket actions
are handled in the libdispatch workqueue. Delegate messages and completion
handlers are submitted to a seperate serial NSOperationQueue.
* Headers/Foundation/NSUserDefaults.h:
* Source/externs.m:
Added GSCACertificateFilePath for custom CA certificate file configuration.
* Source/NSHTTPCookie.m:
Reworked cookie expiration date parsing.
* Source/NSOperation.m:
Clear all blocks in _executionBlocks after completion.
* Headers/Foundation/NSURLRequest.h:
* Source/NSURLRequest.m:
Implemented -[NSURLRequest assumesHTTP3Capable] and -[NSURLRequest
setAssumesHTTP3Capable:] respectively. The internal case-insensitive header
dictionary is now exposed for private usage with -[NSURLRequest
_insensitiveHeaders].
2024-06-21 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSDistantObject.m: Use standard method name conventions

View file

@ -271,16 +271,6 @@ GS_EXPORT_CLASS
*/
- (NSString *) valueForHTTPHeaderField: (NSString *)field;
#if OS_API_VERSION(MAC_OS_VERSION_11_0, GS_API_LATEST)
/**
* Indicates whether the URL loading system assumes the host is HTTP/3 capable.
*
* This method returns the current assumption of the URL loading system regarding
* the server's HTTP capabilities.
*/
- (BOOL) assumesHTTP3Capable;
#endif
@end
@ -337,17 +327,6 @@ GS_EXPORT_CLASS
*/
- (void) setValue: (NSString *)value forHTTPHeaderField: (NSString *)field;
#if OS_API_VERSION(MAC_OS_VERSION_11_0, GS_API_LATEST)
/**
* Sets whether the URL loading system should assume the host is HTTP/3 capable.
*
* This method configures the URL loading system's assumptions about the
* server's HTTP capabilities, optimizing the connection process if HTTP/3 is
* supported.
*/
- (void) setAssumesHTTP3Capable: (BOOL)capable;
#endif
@end
@protocol GSLogDelegate;

View file

@ -1,10 +1,11 @@
/**
NSURLSession.h
Copyright (C) 2017-2024 Free Software Foundation, Inc.
Copyright (C) 2017-2023 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Date: May 2024
Written by: Daniel Ferreira <dtf@stanford.edu>
Date: November 2017
Author: Richard Frith-Macdonald <richard@brainstorm.co.uk>
This file is part of GNUStep-base
@ -33,18 +34,14 @@
#import <Foundation/NSObject.h>
#import <Foundation/NSURLRequest.h>
#import <Foundation/NSHTTPCookieStorage.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSOperation.h>
#import <Foundation/NSProgress.h>
#import <Foundation/NSDate.h>
#if GS_HAVE_NSURLSESSION
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9, GS_API_LATEST)
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
@protocol NSURLSessionDelegate;
@protocol NSURLSessionTaskDelegate;
@class GSMultiHandle;
@class GSURLSessionTaskBody;
@class NSError;
@class NSHTTPURLResponse;
@class NSOperationQueue;
@ -61,15 +58,6 @@
@class NSURLSessionUploadTask;
@class NSURLSessionDownloadTask;
NS_ASSUME_NONNULL_BEGIN
typedef void (^GSNSURLSessionDataCompletionHandler)(
NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable error);
typedef void (^GSNSURLSessionDownloadCompletionHandler)(
NSURL *_Nullable location, NSURLResponse *_Nullable response,
NSError *_Nullable error);
/**
* NSURLSession is a replacement API for NSURLConnection. It provides
@ -79,18 +67,18 @@ typedef void (^GSNSURLSessionDownloadCompletionHandler)(
*
* An NSURLSession may be bound to a delegate object. The delegate is
* invoked for certain events during the lifetime of a session.
*
*
* NSURLSession instances are threadsafe.
*
* An NSURLSession creates NSURLSessionTask objects which represent the
* action of a resource being loaded.
*
*
* NSURLSessionTask objects are always created in a suspended state and
* must be sent the -resume message before they will execute.
*
* Subclasses of NSURLSessionTask are used to syntactically
* differentiate between data and file downloads.
*
*
* An NSURLSessionDataTask receives the resource as a series of calls to
* the URLSession:dataTask:didReceiveData: delegate method. This is type of
* task most commonly associated with retrieving objects for immediate parsing
@ -99,156 +87,125 @@ typedef void (^GSNSURLSessionDownloadCompletionHandler)(
GS_EXPORT_CLASS
@interface NSURLSession : NSObject
{
@private
NSOperationQueue *_delegateQueue;
id<NSURLSessionDelegate> _delegate;
NSURLSessionConfiguration *_configuration;
NSString *_sessionDescription;
NSOperationQueue *_delegateQueue;
id <NSURLSessionDelegate> _delegate;
NSURLSessionConfiguration *_configuration;
NSString *_sessionDescription;
GSMultiHandle *_multiHandle;
}
+ (NSURLSession *)sharedSession;
+ (NSURLSession*) sharedSession;
+ (NSURLSession *)sessionWithConfiguration:
(NSURLSessionConfiguration *)configuration;
+ (NSURLSession*) sessionWithConfiguration: (NSURLSessionConfiguration*)configuration;
/*
* Customization of NSURLSession occurs during creation of a new session.
* If you do specify a delegate, the delegate will be retained until after
* the delegate has been sent the URLSession:didBecomeInvalidWithError: message.
*/
+ (NSURLSession *)
sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
delegate:(nullable id<NSURLSessionDelegate>)delegate
delegateQueue:(nullable NSOperationQueue *)queue;
+ (NSURLSession*) sessionWithConfiguration: (NSURLSessionConfiguration*)configuration
delegate: (id <NSURLSessionDelegate>)delegate
delegateQueue: (NSOperationQueue*)queue;
/* -finishTasksAndInvalidate returns immediately and existing tasks will be
- (NSOperationQueue*) delegateQueue;
- (id <NSURLSessionDelegate>) delegate;
- (NSURLSessionConfiguration*) configuration;
- (NSString*) sessionDescription;
- (void) setSessionDescription: (NSString*)sessionDescription;
/* -finishTasksAndInvalidate returns immediately and existing tasks will be
* allowed to run to completion. New tasks may not be created. The session
* will continue to make delegate callbacks until
* URLSession:didBecomeInvalidWithError: has been issued.
* will continue to make delegate callbacks until
* URLSession:didBecomeInvalidWithError: has been issued.
*
* When invalidating a background session, it is not safe to create another
* background session with the same identifier until
* When invalidating a background session, it is not safe to create another
* background session with the same identifier until
* URLSession:didBecomeInvalidWithError: has been issued.
*/
- (void)finishTasksAndInvalidate;
- (void) finishTasksAndInvalidate;
/* -invalidateAndCancel acts as -finishTasksAndInvalidate, but issues
* -cancel to all outstanding tasks for this session. Note task
* -cancel to all outstanding tasks for this session. Note task
* cancellation is subject to the state of the task, and some tasks may
* have already have completed at the time they are sent -cancel.
* have already have completed at the time they are sent -cancel.
*/
- (void)invalidateAndCancel;
- (void) invalidateAndCancel;
/*
/*
* NSURLSessionTask objects are always created in a suspended state and
* must be sent the -resume message before they will execute.
*/
/** Creates a data task with the given request.
/* Creates a data task with the given request.
* The request may have a body stream. */
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request;
/** Creates a data task to retrieve the contents of the given URL. */
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
/* Creates a data task to retrieve the contents of the given URL. */
- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL;
/* Not implemented */
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
fromFile: (NSURL*)fileURL;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData;
/* Not implemented */
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
fromData: (NSData*)bodyData;
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:
(NSURLRequest *)request;
/* Not implemented */
- (NSURLSessionUploadTask*) uploadTaskWithStreamedRequest: (NSURLRequest*)request;
/* Creates a download task with the given request. */
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request;
/* Creates a download task to download the contents of the given URL. */
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL*)url;
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
/* Not implemented */
- (NSURLSessionDownloadTask *) downloadTaskWithResumeData: (NSData *)resumeData;
- (void)getTasksWithCompletionHandler:
(void (^)(NSArray<NSURLSessionDataTask *> *dataTasks,
NSArray<NSURLSessionUploadTask *> *uploadTasks,
NSArray<NSURLSessionDownloadTask *> *downloadTasks))
completionHandler;
- (void) getTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, NSURLSessionDataTask*) *dataTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionUploadTask*) *uploadTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionDownloadTask*) *downloadTasks))completionHandler;
- (void)getAllTasksWithCompletionHandler:
(void (^)(GS_GENERIC_CLASS(NSArray, __kindof NSURLSessionTask *) * tasks))
completionHandler;
/**
* This serial NSOperationQueue queue is used for dispatching delegate messages
* and completion handlers.
*/
- (NSOperationQueue *)delegateQueue;
/**
* The delegate for the session. This is the object to which delegate messages
* will be sent.
*
* The session keeps a strong reference to the delegate.
*/
- (nullable id<NSURLSessionDelegate>)delegate;
/**
* The configuration object used to create the session.
*
* A copy of the configuration object is made.
* Changes to the configuration object after the session is created have no
* effect.
*/
- (NSURLSessionConfiguration *)configuration;
/**
* An App-specific description of the session.
*/
- (nullable NSString *)sessionDescription;
/**
* Sets an app-specific description of the session.
*/
- (void)setSessionDescription:(NSString *)description;
- (void) getAllTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, __kindof NSURLSessionTask*) *tasks))completionHandler;
@end
/*
* NSURLSession convenience routines deliver results to
* NSURLSession convenience routines deliver results to
* a completion handler block. These convenience routines
* are not available to NSURLSessions that are configured
* as background sessions.
*
* Task objects are always created in a suspended state and
* Task objects are always created in a suspended state and
* must be sent the -resume message before they will execute.
*/
@interface
NSURLSession (NSURLSessionAsynchronousConvenience)
@interface NSURLSession (NSURLSessionAsynchronousConvenience)
/*
* data task convenience methods. These methods create tasks that
* bypass the normal delegate calls for response and data delivery,
* and provide a simple cancelable asynchronous interface to receiving
* data. Errors will be returned in the NSURLErrorDomain,
* data. Errors will be returned in the NSURLErrorDomain,
* see <Foundation/NSURLError.h>. The delegate, if any, will still be
* called for authentication challenges.
*/
- (NSURLSessionDataTask *)
dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url
completionHandler:(GSNSURLSessionDataCompletionHandler)
completionHandler;
- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionUploadTask *)
uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler;
- (NSURLSessionUploadTask *)
uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData
completionHandler:(GSNSURLSessionDataCompletionHandler)completionHandler;
/* Not implemented */
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
fromFile: (NSURL*)fileURL
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
/* Not implemented */
- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request
fromData: (NSData*)bodyData
completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
/*
* download task convenience methods. When a download successfully
@ -256,32 +213,27 @@ NSURLSession (NSURLSessionAsynchronousConvenience)
* copied during the invocation of the completion routine. The file
* will be removed automatically.
*/
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
completionHandler:
(GSNSURLSessionDownloadCompletionHandler)
completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url
completionHandler:
(GSNSURLSessionDownloadCompletionHandler)
completionHandler;
- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL *)url
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
- (NSURLSessionDownloadTask *)
downloadTaskWithResumeData:(NSData *)resumeData
completionHandler:
(GSNSURLSessionDownloadCompletionHandler)completionHandler;
/* Not implemented */
- (NSURLSessionDownloadTask*) downloadTaskWithResumeData: (NSData*)resumeData
completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler;
@end
typedef NS_ENUM(NSUInteger, NSURLSessionTaskState) {
/* The task is currently being serviced by the session */
NSURLSessionTaskStateRunning = 0,
NSURLSessionTaskStateRunning = 0,
NSURLSessionTaskStateSuspended = 1,
/* The task has been told to cancel.
/* The task has been told to cancel.
* The session will receive URLSession:task:didCompleteWithError:. */
NSURLSessionTaskStateCanceling = 2,
/* The task has completed and the session will receive no more
NSURLSessionTaskStateCanceling = 2,
/* The task has completed and the session will receive no more
* delegate notifications */
NSURLSessionTaskStateCompleted = 3,
NSURLSessionTaskStateCompleted = 3,
};
GS_EXPORT const float NSURLSessionTaskPriorityDefault;
@ -295,81 +247,125 @@ GS_EXPORT const int64_t NSURLSessionTransferSizeUnknown;
* of processing a given request.
*/
GS_EXPORT_CLASS
@interface NSURLSessionTask : NSObject <NSCopying, NSProgressReporting>
@interface NSURLSessionTask : NSObject <NSCopying>
{
/** An identifier for this task, assigned by and unique
* to the owning session
*/
NSUInteger _taskIdentifier;
NSURLRequest *_originalRequest;
id<NSURLSessionTaskDelegate> _delegate;
NSURLSessionTaskState _state;
NSURLRequest *_currentRequest;
NSURLResponse *_response;
NSProgress *_progress;
NSDate *_earliestBeginDate;
/** The request this task was created to handle.
*/
NSURLRequest *_originalRequest;
_Atomic(int64_t) _countOfBytesClientExpectsToSend;
_Atomic(int64_t) _countOfBytesClientExpectsToReceive;
_Atomic(int64_t) _countOfBytesSent;
_Atomic(int64_t) _countOfBytesReceived;
_Atomic(int64_t) _countOfBytesExpectedToSend;
_Atomic(int64_t) _countOfBytesExpectedToReceive;
_Atomic(double) _priority;
/** The request this task is currently handling. This may differ from
* originalRequest due to http server redirection
*/
NSURLRequest *_currentRequest;
NSString *_taskDescription;
NSError *_error;
/** The response to the current request, which may be nil if no response
* has been received
*/
NSURLResponse *_response;
/** number of body bytes already received
*/
int64_t _countOfBytesReceived;
/** number of body bytes already sent
*/
int64_t _countOfBytesSent;
/** number of body bytes we expect to send, derived from
* the Content-Length of the HTTP request
*/
int64_t _countOfBytesExpectedToSend;
/** number of byte bytes we expect to receive, usually derived from the
* Content-Length header of an HTTP response.
*/
int64_t _countOfBytesExpectedToReceive;
/** a description of the current task for diagnostic purposes
*/
NSString *_taskDescription;
/** The current state of the task within the session.
*/
NSURLSessionTaskState _state;
/** The error, if any, delivered via -URLSession:task:didCompleteWithError:
* This is nil until an error has occured.
*/
NSError *_error;
/** The dispatch queue used to handle this request/response.
* This is actualy a libdispatch queue of type dispatch_queue_t, but on all
* known implementations this is a pointer, so void* is the correct size.
*/
void *_workQueue;
NSUInteger _suspendCount;
GSURLSessionTaskBody *_knownBody;
void (^_dataCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error);
void (^_downloadCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error);
}
- (NSUInteger)taskIdentifier;
- (NSUInteger) taskIdentifier;
- (nullable NSURLRequest *)originalRequest;
- (nullable NSURLRequest *)currentRequest;
- (nullable NSURLResponse *)response;
- (NSURLRequest*) originalRequest;
- (NSURLSessionTaskState)state;
- (NSProgress *)progress;
- (nullable NSError *)error;
- (NSURLRequest*) currentRequest;
- (nullable id<NSURLSessionTaskDelegate>)delegate;
- (void)setDelegate:(nullable id<NSURLSessionTaskDelegate>)delegate;
- (NSURLResponse*) response;
- (void) setResponse: (NSURLResponse*)response;
- (nullable NSDate *)earliestBeginDate;
- (void)setEarliestBeginDate:(nullable NSDate *)date;
- (int64_t) countOfBytesReceived;
- (int64_t)countOfBytesClientExpectsToSend;
- (int64_t)countOfBytesClientExpectsToReceive;
- (int64_t)countOfBytesSent;
- (int64_t)countOfBytesReceived;
- (int64_t)countOfBytesExpectedToSend;
- (int64_t)countOfBytesExpectedToReceive;
- (int64_t) countOfBytesSent;
/**
* App-specific description of the task.
- (int64_t) countOfBytesExpectedToSend;
- (int64_t) countOfBytesExpectedToReceive;
- (NSString*) taskDescription;
- (void) setTaskDescription: (NSString*)taskDescription;
- (NSURLSessionTaskState) state;
- (NSError*) error;
- (NSURLSession*) session;
/* -cancel returns immediately, but marks a task as being canceled.
* The task will signal -URLSession:task:didCompleteWithError: with an
* error value of { NSURLErrorDomain, NSURLErrorCancelled }. In some
* cases, the task may signal other work before it acknowledges the
* cancelation. -cancel may be sent to a task that has been suspended.
*/
- (nullable NSString *)taskDescription;
- (void) cancel;
/**
* Sets an app-specific description of the task.
/*
* Suspending a task will prevent the NSURLSession from continuing to
* load data. There may still be delegate calls made on behalf of
* this task (for instance, to report data received while suspending)
* but no further transmissions will be made on behalf of the task
* until -resume is sent. The timeout timer associated with the task
* will be disabled while a task is suspended.
*/
- (void)setTaskDescription:(nullable NSString *)description;
- (void) suspend;
- (void) resume;
/**
* Cancels the task and the ongoing transfer.
*/
- (void)cancel;
- (void)suspend;
- (void)resume;
- (float)priority;
- (void)setPriority:(float)priority;
- (float) priority;
- (void) setPriority: (float)priority;
@end
GS_EXPORT_CLASS
@interface NSURLSessionDataTask : NSURLSessionTask
{
void *_completionHandler;
}
@end
GS_EXPORT_CLASS
@ -378,13 +374,9 @@ GS_EXPORT_CLASS
GS_EXPORT_CLASS
@interface NSURLSessionDownloadTask : NSURLSessionTask
{
void *_completionHandler;
int64_t _countOfBytesWritten;
}
@end
#if OS_API_VERSION(MAC_OS_X_VERSION_10_11, GS_API_LATEST)
#if OS_API_VERSION(MAC_OS_X_VERSION_10_11,GS_API_LATEST)
GS_EXPORT_CLASS
@interface NSURLSessionStreamTask : NSURLSessionTask
@end
@ -398,139 +390,65 @@ GS_EXPORT_CLASS
GS_EXPORT_CLASS
@interface NSURLSessionConfiguration : NSObject <NSCopying>
{
NSString *_identifier;
NSURLCache *_URLCache;
NSString *_identifier;
NSURLCache *_URLCache;
NSURLRequestCachePolicy _requestCachePolicy;
NSArray *_protocolClasses;
NSArray *_protocolClasses;
NSInteger _HTTPMaximumConnectionLifetime;
NSInteger _HTTPMaximumConnectionsPerHost;
BOOL _HTTPShouldUsePipelining;
NSHTTPCookieAcceptPolicy _HTTPCookieAcceptPolicy;
NSHTTPCookieStorage *_HTTPCookieStorage;
NSURLCredentialStorage *_URLCredentialStorage;
NSHTTPCookieStorage *_HTTPCookieStorage;
NSURLCredentialStorage *_URLCredentialStorage;
BOOL _HTTPShouldSetCookies;
NSDictionary *_HTTPAdditionalHeaders;
NSTimeInterval _timeoutIntervalForRequest;
NSTimeInterval _timeoutIntervalForResource;
NSDictionary *_HTTPAdditionalHeaders;
}
- (NSURLRequest *)configureRequest:(NSURLRequest *)request;
- (NSURLRequest*) configureRequest: (NSURLRequest*)request;
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:
(NSString *)identifier;
+ (NSURLSessionConfiguration*) defaultSessionConfiguration;
+ (NSURLSessionConfiguration*) ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration*) backgroundSessionConfigurationWithIdentifier:(NSString*)identifier;
- (nullable NSDictionary *)HTTPAdditionalHeaders;
- (void)setHTTPAdditionalHeaders:(NSDictionary *)headers;
- (NSDictionary*) HTTPAdditionalHeaders;
- (void) setHTTPAdditionalHeaders: (NSDictionary*)headers;
/**
* Gets the timeout interval to use when waiting for additional data to arrive.
* The request timeout interval controls how long (in seconds) a task should
* wait for additional data to arrive before giving up. The timer is reset
* whenever new data arrives. When the request timer reaches the specified
* interval without receiving any new data, it triggers a timeout.
*
* Currently not used by NSURLSession.
*/
- (NSTimeInterval)timeoutIntervalForRequest;
/**
* Sets the timeout interval to use when waiting for additional data to arrive.
*/
- (void)setTimeoutIntervalForRequest:(NSTimeInterval)interval;
- (NSHTTPCookieAcceptPolicy) HTTPCookieAcceptPolicy;
- (void) setHTTPCookieAcceptPolicy: (NSHTTPCookieAcceptPolicy)policy;
/**
* Gets the maximum amount of time that a resource request should be allowed to
* take. The resource timeout interval controls how long (in seconds) to wait
* for an entire resource to transfer before giving up. The resource timer
* starts when the request is initiated and counts until either the request
* completes or this timeout interval is reached, whichever comes first.
*/
- (NSTimeInterval)timeoutIntervalForResource;
/**
* Sets the maximum amount of time that a resource request should be allowed to
* take.
*/
- (void)setTimeoutIntervalForResource:(NSTimeInterval)interval;
- (NSHTTPCookieStorage*) HTTPCookieStorage;
- (void) setHTTPCookieStorage: (NSHTTPCookieStorage*)storage;
- (NSHTTPCookieAcceptPolicy)HTTPCookieAcceptPolicy;
- (void)setHTTPCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)policy;
- (NSInteger) HTTPMaximumConnectionsPerHost;
- (void) setHTTPMaximumConnectionsPerHost: (NSInteger)n;
- (nullable NSHTTPCookieStorage *)HTTPCookieStorage;
- (void)setHTTPCookieStorage:(NSHTTPCookieStorage *)storage;
- (BOOL) HTTPShouldSetCookies;
- (void) setHTTPShouldSetCookies: (BOOL)flag;
- (NSInteger)HTTPMaximumConnectionsPerHost;
- (void)setHTTPMaximumConnectionsPerHost:(NSInteger)n;
- (BOOL) HTTPShouldUsePipelining;
- (void) setHTTPShouldUsePipelining: (BOOL)flag;
/**
* Indicates whether the session should set cookies.
*
* This property controls whether tasks within sessions based on this
* configuration should automatically include cookies from the shared cookie
* store when making requests.
*
* If set to NO, you must manually provide cookies by adding a Cookie header
* through the session's HTTPAdditionalHeaders property or on a per-request
* basis using a custom NSURLRequest object.
*
* The default value is YES.
*
* See Also:
* - HTTPCookieAcceptPolicy
* - HTTPCookieStorage
* - NSHTTPCookieStorage
* - NSHTTPCookie
*/
- (BOOL)HTTPShouldSetCookies;
- (NSString*) identifier;
/**
* Sets whether the session should set cookies.
*
* This method controls whether tasks within sessions based on this
* configuration should automatically include cookies from the shared cookie
* store when making requests.
*
* If set to NO, you must manually provide cookies by adding a Cookie header
* through the session's HTTPAdditionalHeaders property or on a per-request
* basis using a custom NSURLRequest object.
*
* The default value is YES.
*
* See Also:
* - HTTPCookieAcceptPolicy
* - HTTPCookieStorage
* - NSHTTPCookieStorage
* - NSHTTPCookie
*/
- (void)setHTTPShouldSetCookies:(BOOL)flag;
- (NSArray*) protocolClasses;
/**
* HTTP/1.1 pipelining is not implemented. This flag is ignored.
*/
- (BOOL)HTTPShouldUsePipelining;
- (void)setHTTPShouldUsePipelining:(BOOL)flag;
- (NSURLRequestCachePolicy) requestCachePolicy;
- (void) setRequestCachePolicy: (NSURLRequestCachePolicy)policy;
- (nullable NSString *)identifier;
- (NSURLCache*) URLCache;
- (void) setURLCache: (NSURLCache*)cache;
- (nullable NSArray *)protocolClasses;
- (NSURLCredentialStorage*) URLCredentialStorage;
- (void) setURLCredentialStorage: (NSURLCredentialStorage*)storage;
- (NSURLRequestCachePolicy)requestCachePolicy;
- (void)setRequestCachePolicy:(NSURLRequestCachePolicy)policy;
- (nullable NSURLCache *)URLCache;
- (void)setURLCache:(NSURLCache *)cache;
- (nullable NSURLCredentialStorage *)URLCredentialStorage;
- (void)setURLCredentialStorage:(NSURLCredentialStorage *)storage;
#if !NO_GNUSTEP
#if !NO_GNUSTEP
/** Permits a session to be configured so that older connections are reused.
* A value of zero or less uses the default behavior where connections are
* reused as long as they are not older than 118 seconds, which is reasonable
* for the vast majority if situations.
*/
- (NSInteger)HTTPMaximumConnectionLifetime;
- (void)setHTTPMaximumConnectionLifetime:(NSInteger)n;
- (NSInteger) HTTPMaximumConnectionLifetime;
- (void) setHTTPMaximumConnectionLifetime: (NSInteger)n;
#endif
@end
@ -546,7 +464,7 @@ typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
NSURLSessionResponseCancel = 0,
NSURLSessionResponseAllow = 1,
NSURLSessionResponseBecomeDownload = 2,
NSURLSessionResponseBecomeStream = 3
NSURLSessionResponseBecomeStream = 3
};
@protocol NSURLSessionDelegate <NSObject>
@ -555,72 +473,58 @@ typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
* invalid because of a systemic error or when it has been
* explicitly invalidated, in which case the error parameter will be nil.
*/
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(nullable NSError *)error;
- (void) URLSession: (NSURLSession*)session
didBecomeInvalidWithError: (NSError*)error;
/* Implementing this method permits a delegate to provide authentication
* credentials in response to a challenge from the remote server.
*/
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:
(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))handler;
- (void) URLSession: (NSURLSession*)session
didReceiveChallenge: (NSURLAuthenticationChallenge*)challenge
completionHandler: (void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))handler;
@end
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
@optional
#if OS_API_VERSION(MAC_OS_VERSION_13_0, GS_API_LATEST)
- (void)URLSession:(NSURLSession *)session
didCreateTask:(NSURLSessionTask *)task;
#endif
/* Sent as the last message related to a specific task. Error may be
* nil, which implies that no error occurred and this task is complete.
* nil, which implies that no error occurred and this task is complete.
*/
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error;
- (void ) URLSession: (NSURLSession*)session
task: (NSURLSessionTask*)task
didCompleteWithError: (NSError*)error;
/* Called to request authentication credentials from the delegate when
* an authentication request is received from the server which is specific
* to this task.
*/
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:
(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))handler;
- (void) URLSession: (NSURLSession*)session
task: (NSURLSessionTask*)task
didReceiveChallenge: (NSURLAuthenticationChallenge*)challenge
completionHandler: (void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))handler;
/* Periodically informs the delegate of the progress of sending body content
/* Periodically informs the delegate of the progress of sending body content
* to the server.
*/
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
- (void) URLSession: (NSURLSession*)session
task: (NSURLSessionTask*)task
didSendBodyData: (int64_t)bytesSent
totalBytesSent: (int64_t)totalBytesSent
totalBytesExpectedToSend: (int64_t)totalBytesExpectedToSend;
/* An HTTP request is attempting to perform a redirection to a different
* URL. You must invoke the completion routine to allow the
* redirection, allow the redirection with a modified request, or
* pass nil to the completionHandler to cause the body of the redirection
* pass nil to the completionHandler to cause the body of the redirection
* response to be delivered as the payload of this request. The default
* is to follow redirections.
* is to follow redirections.
*
*/
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler;
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler;
- (void) URLSession: (NSURLSession*)session
task: (NSURLSessionTask*)task
willPerformHTTPRedirection: (NSHTTPURLResponse*)response
newRequest: (NSURLRequest*)request
completionHandler: (void (^)(NSURLRequest*))completionHandler;
@end
@ -628,54 +532,51 @@ typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
@optional
/* Sent when data is available for the delegate to consume.
*/
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data;
- (void) URLSession: (NSURLSession*)session
dataTask: (NSURLSessionDataTask*)dataTask
didReceiveData: (NSData*)data;
/** Informs the delegate of a response. This message is sent when all the
* response headers have arrived, before the body of the response arrives.
*/
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:
(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
- (void) URLSession: (NSURLSession*)session
dataTask: (NSURLSessionDataTask*)dataTask
didReceiveResponse: (NSURLResponse*)response
completionHandler: (void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
@end
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
/* Sent when a download task that has completed a download. The delegate should
* copy or move the file at the given location to a new location as it will be
* removed when the delegate message returns.
* URLSession:task:didCompleteWithError: will still be called.
/* Sent when a download task that has completed a download. The delegate should
* copy or move the file at the given location to a new location as it will be
* removed when the delegate message returns. URLSession:task:didCompleteWithError: will
* still be called.
*/
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location;
- (void) URLSession: (NSURLSession *)session
downloadTask: (NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL: (NSURL *)location;
@optional
/* Sent periodically to notify the delegate of download progress. */
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
- (void) URLSession: (NSURLSession *)session
downloadTask: (NSURLSessionDownloadTask *)downloadTask
didWriteData: (int64_t)bytesWritten
totalBytesWritten: (int64_t)totalBytesWritten
totalBytesExpectedToWrite: (int64_t)totalBytesExpectedToWrite;
/* Sent when a download has been resumed. If a download failed with an
* error, the -userInfo dictionary of the error will contain an
* NSURLSessionDownloadTaskResumeData key, whose value is the resume
* data.
* data.
*/
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes;
- (void) URLSession: (NSURLSession *)session
downloadTask: (NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset: (int64_t)fileOffset
expectedTotalBytes: (int64_t)expectedTotalBytes;
@end
NS_ASSUME_NONNULL_END
#endif /* MAC_OS_X_VERSION_10_9 */
#endif /* GS_HAVE_NSURLSESSION */
#endif /* __NSURLSession_h_GNUSTEP_BASE_INCLUDE */
#endif
#endif
#endif

View file

@ -192,11 +192,6 @@ GS_EXPORT NSString* const GSLocale;
#endif
#endif
#if !NO_GNUSTEP
/* Path to PEM Encoded Certificate File (NSString). Used by NSURLSession if set. */
GS_EXPORT NSString *const GSCACertificateFilePath;
#endif
/* General implementation notes:
OpenStep spec currently is neither complete nor consistent. Therefore

View file

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

236
Source/GSEasyHandle.h Normal file
View file

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

773
Source/GSEasyHandle.m Normal file
View file

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

View file

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

1028
Source/GSHTTPURLProtocol.m Normal file

File diff suppressed because it is too large Load diff

89
Source/GSMultiHandle.h Normal file
View file

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

485
Source/GSMultiHandle.m Normal file
View file

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

120
Source/GSNativeProtocol.h Normal file
View file

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

818
Source/GSNativeProtocol.m Normal file
View file

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

30
Source/GSTaskRegistry.h Normal file
View file

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

98
Source/GSTaskRegistry.m Normal file
View file

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

32
Source/GSTimeoutSource.h Normal file
View file

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

76
Source/GSTimeoutSource.m Normal file
View file

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

140
Source/GSTransferState.h Normal file
View file

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

487
Source/GSTransferState.m Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -43,8 +43,7 @@
#import "Foundation/NSSet.h"
#import "Foundation/NSValue.h"
#import "Foundation/NSString.h"
#import "Foundation/NSDateFormatter.h"
#import "Foundation/NSLocale.h"
#import "Foundation/NSCalendarDate.h"
#import "GNUstepBase/Unicode.h"
static NSString * const HTTPCookieHTTPOnly = @"HTTPOnly";
@ -682,22 +681,10 @@ _setCookieKey(NSMutableDictionary *dict, NSString *key, NSString *value)
else if ([[key lowercaseString] isEqual: @"expires"])
{
NSDate *expireDate;
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];
expireDate = [NSCalendarDate dateWithString: value
calendarFormat: @"%a, %d-%b-%Y %I:%M:%S %Z"];
if (expireDate)
[dict setObject: expireDate forKey: NSHTTPCookieExpires];
RELEASE(formatter);
}
else if ([[key lowercaseString] isEqual: @"max-age"])
[dict setObject: value forKey: NSHTTPCookieMaximumAge];

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,283 +0,0 @@
/**
NSURLSessionConfiguration.m
Copyright (C) 2017-2024 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Date: May 2024
Author: Hugo Melder <hugo@algoriddim.com>
This file is part of GNUStep-base
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
If you are interested in a warranty or support for this source code,
contact Scott Christley <scottc@net-community.com> for more information.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110 USA.
*/
#import "Foundation/NSURLSession.h"
#import "Foundation/NSHTTPCookie.h"
// TODO: This is the old implementation. It requires a rewrite!
@implementation NSURLSessionConfiguration
static NSURLSessionConfiguration *def = nil;
+ (void)initialize
{
if (nil == def)
{
def = [NSURLSessionConfiguration new];
}
}
+ (NSURLSessionConfiguration *)defaultSessionConfiguration
{
return AUTORELEASE([def copy]);
}
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration
{
// return default session since we don't store any data on disk anyway
return AUTORELEASE([def copy]);
}
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:
(NSString *)identifier
{
NSURLSessionConfiguration *configuration = [def copy];
configuration->_identifier = [identifier copy];
return AUTORELEASE(configuration);
}
- (instancetype)init
{
if (nil != (self = [super init]))
{
_protocolClasses = nil;
_HTTPMaximumConnectionsPerHost = 6;
_HTTPShouldUsePipelining = YES;
_HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
_HTTPCookieStorage = nil;
_HTTPShouldSetCookies = NO;
_HTTPAdditionalHeaders = nil;
_HTTPMaximumConnectionLifetime = 0; // Zero or less means default
_timeoutIntervalForResource = 604800; // 7 days in seconds
_timeoutIntervalForRequest = 60; // 60 seconds
}
return self;
}
- (void)dealloc
{
DESTROY(_identifier);
DESTROY(_HTTPAdditionalHeaders);
DESTROY(_HTTPCookieStorage);
DESTROY(_protocolClasses);
DESTROY(_URLCache);
DESTROY(_URLCredentialStorage);
[super dealloc];
}
- (NSString *)identifier
{
return _identifier;
}
- (NSURLCache *)URLCache
{
return _URLCache;
}
- (void)setURLCache:(NSURLCache *)cache
{
ASSIGN(_URLCache, cache);
}
- (void)setURLCredentialStorage:(NSURLCredentialStorage *)storage
{
ASSIGN(_URLCredentialStorage, storage);
}
- (NSURLRequestCachePolicy)requestCachePolicy
{
return _requestCachePolicy;
}
- (void)setRequestCachePolicy:(NSURLRequestCachePolicy)policy
{
_requestCachePolicy = policy;
}
- (NSArray *)protocolClasses
{
return _protocolClasses;
}
- (NSTimeInterval)timeoutIntervalForRequest
{
return _timeoutIntervalForRequest;
}
- (void)setTimeoutIntervalForRequest:(NSTimeInterval)interval
{
_timeoutIntervalForRequest = interval;
}
- (NSTimeInterval)timeoutIntervalForResource
{
return _timeoutIntervalForResource;
}
- (void)setTimeoutIntervalForResource:(NSTimeInterval)interval
{
_timeoutIntervalForResource = interval;
}
- (NSInteger)HTTPMaximumConnectionsPerHost
{
return _HTTPMaximumConnectionsPerHost;
}
- (void)setHTTPMaximumConnectionsPerHost:(NSInteger)n
{
_HTTPMaximumConnectionsPerHost = n;
}
- (NSInteger)HTTPMaximumConnectionLifetime
{
return _HTTPMaximumConnectionLifetime;
}
- (void)setHTTPMaximumConnectionLifetime:(NSInteger)n
{
_HTTPMaximumConnectionLifetime = n;
}
- (BOOL)HTTPShouldUsePipelining
{
return _HTTPShouldUsePipelining;
}
- (void)setHTTPShouldUsePipelining:(BOOL)flag
{
_HTTPShouldUsePipelining = flag;
}
- (NSHTTPCookieAcceptPolicy)HTTPCookieAcceptPolicy
{
return _HTTPCookieAcceptPolicy;
}
- (void)setHTTPCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)policy
{
_HTTPCookieAcceptPolicy = policy;
}
- (NSHTTPCookieStorage *)HTTPCookieStorage
{
return _HTTPCookieStorage;
}
- (void)setHTTPCookieStorage:(NSHTTPCookieStorage *)storage
{
ASSIGN(_HTTPCookieStorage, storage);
}
- (BOOL)HTTPShouldSetCookies
{
return _HTTPShouldSetCookies;
}
- (void)setHTTPShouldSetCookies:(BOOL)flag
{
_HTTPShouldSetCookies = flag;
}
- (NSDictionary *)HTTPAdditionalHeaders
{
return _HTTPAdditionalHeaders;
}
- (void)setHTTPAdditionalHeaders:(NSDictionary *)headers
{
ASSIGN(_HTTPAdditionalHeaders, headers);
}
- (NSURLRequest *)configureRequest:(NSURLRequest *)request
{
return [self setCookiesOnRequest:request];
}
- (NSURLRequest *)setCookiesOnRequest:(NSURLRequest *)request
{
NSMutableURLRequest *r = AUTORELEASE([request mutableCopy]);
if (_HTTPShouldSetCookies)
{
if (nil != _HTTPCookieStorage && nil != [request URL])
{
NSArray *cookies = [_HTTPCookieStorage cookiesForURL:[request URL]];
if (nil != cookies && [cookies count] > 0)
{
NSDictionary *cookiesHeaderFields;
NSString *cookieValue;
cookiesHeaderFields =
[NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
cookieValue = [cookiesHeaderFields objectForKey:@"Cookie"];
if (nil != cookieValue && [cookieValue length] > 0)
{
[r setValue:cookieValue forHTTPHeaderField:@"Cookie"];
}
}
}
}
return AUTORELEASE([r copy]);
}
- (NSURLCredentialStorage *)URLCredentialStorage
{
return _URLCredentialStorage;
}
- (id)copyWithZone:(NSZone *)zone
{
NSURLSessionConfiguration *copy = [[[self class] alloc] init];
if (copy)
{
copy->_identifier = [_identifier copy];
copy->_URLCache = [_URLCache copy];
copy->_URLCredentialStorage = [_URLCredentialStorage copy];
copy->_protocolClasses = [_protocolClasses copyWithZone:zone];
copy->_HTTPMaximumConnectionsPerHost = _HTTPMaximumConnectionsPerHost;
copy->_HTTPShouldUsePipelining = _HTTPShouldUsePipelining;
copy->_HTTPCookieAcceptPolicy = _HTTPCookieAcceptPolicy;
copy->_HTTPCookieStorage = [_HTTPCookieStorage retain];
copy->_HTTPShouldSetCookies = _HTTPShouldSetCookies;
copy->_HTTPAdditionalHeaders = [_HTTPAdditionalHeaders copyWithZone:zone];
copy->_timeoutIntervalForRequest = _timeoutIntervalForRequest;
copy->_timeoutIntervalForResource = _timeoutIntervalForResource;
}
return copy;
}
@end

View file

@ -1,88 +0,0 @@
/**
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 "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

File diff suppressed because it is too large Load diff

View file

@ -1,123 +0,0 @@
/**
NSURLSessionTaskPrivate.h
Copyright (C) 2017-2024 Free Software Foundation, Inc.
Written by: Hugo Melder <hugo@algoriddim.com>
Date: May 2024
Author: Hugo Melder <hugo@algoriddim.com>
This file is part of GNUStep-base
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
If you are interested in a warranty or support for this source code,
contact Scott Christley <scottc@net-community.com> for more information.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110 USA.
*/
#import "Foundation/NSDictionary.h"
#import "Foundation/NSFileHandle.h"
#import "Foundation/NSURLSession.h"
#import <curl/curl.h>
@interface
NSURLSessionTask (Private)
- (instancetype)initWithSession:(NSURLSession *)session
request:(NSURLRequest *)request
taskIdentifier:(NSUInteger)identifier;
- (CURL *)_easyHandle;
/* Enable or disable libcurl verbose output. Disabled by default. */
- (void)_setVerbose:(BOOL)flag;
/* This method is called by -[NSURLSession _checkForCompletion]
*
* We release the session (previously retained in -[NSURLSessionTask resume])
* here and inform the delegate about the transfer state.
*/
- (void)_transferFinishedWithCode:(CURLcode)code;
/* Explicitly enable data upload with an optional estimated size. Set to 0 if
* not available.
*
* This may be used when a body stream is passed at a later stage
* (see URLSession:task:needNewBodyStream:).
*/
- (void)_enableUploadWithSize:(NSInteger)size;
- (void)_setBodyStream:(NSInputStream *)stream;
- (void)_enableUploadWithData:(NSData *)data;
- (void)_enableAutomaticRedirects:(BOOL)flag;
/* Assign with copying */
- (void)_setOriginalRequest:(NSURLRequest *)request;
- (void)_setCurrentRequest:(NSURLRequest *)request;
- (void)_setResponse:(NSURLResponse *)response;
- (void)_setCookiesFromHeaders:(NSDictionary *)headers;
- (void)_setCountOfBytesSent:(int64_t)count;
- (void)_setCountOfBytesReceived:(int64_t)count;
- (void)_setCountOfBytesExpectedToSend:(int64_t)count;
- (void)_setCountOfBytesExpectedToReceive:(int64_t)count;
- (NSMutableDictionary *)_taskData;
- (NSURLSession *)_session;
/* Task specific properties.
*
* See GSURLSessionProperties in NSURLSessionPrivate.h.
*/
- (NSInteger)_properties;
- (void)_setProperties:(NSInteger)properties;
/* This value is periodically checked in progress_callback.
* We then abort the transfer in the progress_callback if this flag is set.
*/
- (BOOL)_shouldStopTransfer;
- (void)_setShouldStopTransfer:(BOOL)flag;
- (NSInteger)_numberOfRedirects;
- (void)_setNumberOfRedirects:(NSInteger)redirects;
- (NSInteger)_headerCallbackCount;
- (void)_setHeaderCallbackCount:(NSInteger)count;
- (NSFileHandle *)_createTemporaryFileHandleWithError:(NSError **)error;
@end
@interface
NSURLSessionDataTask (Private)
- (GSNSURLSessionDataCompletionHandler)_completionHandler;
- (void)_setCompletionHandler:(GSNSURLSessionDataCompletionHandler)handler;
@end
@interface
NSURLSessionDownloadTask (Private)
- (GSNSURLSessionDownloadCompletionHandler)_completionHandler;
- (int64_t)_countOfBytesWritten;
- (void)_updateCountOfBytesWritten:(int64_t)count;
- (void)_setCompletionHandler:(GSNSURLSessionDownloadCompletionHandler)handler;
@end

View file

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

View file

@ -1 +0,0 @@
Helpers/HTTPServer.bundle

View file

@ -1,8 +1,5 @@
include $(GNUSTEP_MAKEFILES)/common.make
ifeq ($(the_library_combo), ng-gnu-gnu)
SUBPROJECTS = Helpers
SUBPROJECTS = ../NSURLConnection/Helpers
include $(GNUSTEP_MAKEFILES)/aggregate.make
endif

View file

@ -1,20 +0,0 @@
include $(GNUSTEP_MAKEFILES)/common.make
BUNDLE_NAME = HTTPServer
NEEDS_GUI=NO
HTTPServer_OBJC_FILES = HTTPServer.m
HTTPServer_OBJC_LIBS += -ldispatch
ifeq ($(GNUSTEP_TARGET_OS), windows)
HTTPServer_OBJC_LIBS += -lws2_32
endif
HTTPServer_OBJCFLAGS += -fobjc-arc
#HTTPServer_RESOURCE_FILES += key.pem certificate.pem
HTTPServer_PRINCIPAL_CLASS = HTTPServer
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/bundle.make
-include GNUmakefile.postamble

View file

@ -1,30 +0,0 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSURLRequest.h>
#import <Foundation/NSURLResponse.h>
typedef NSData * (^RequestHandlerBlock)(NSURLRequest *);
@interface Route : NSObject
+ (instancetype)routeWithURL:(NSURL *)url
method:(NSString *)method
handler:(RequestHandlerBlock)block;
- (NSString *)method;
- (NSURL *)url;
- (RequestHandlerBlock)block;
- (BOOL)acceptsURL:(NSURL *)url method:(NSString *)method;
@end
@interface HTTPServer : NSObject
- initWithPort:(NSInteger)port routes:(NSArray<Route *> *)routes;
- (NSInteger)port;
- (void)resume;
- (void)suspend;
- (void)setRoutes:(NSArray<Route *> *)routes;
@end

View file

@ -1,402 +0,0 @@
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
#ifdef _WIN32
#import <winsock2.h>
#define close(x) closesocket(x)
#else
#import <netinet/in.h>
#import <sys/socket.h>
#endif
#import "HTTPServer.h"
@interface
NSString (ServerAdditions)
- (void)enumerateLinesUsingBlock2:
(void (^)(NSString *line, NSUInteger lineEndIndex, BOOL *stop))block;
@end
@implementation
NSString (ServerAdditions)
- (void)enumerateLinesUsingBlock2:
(void (^)(NSString *line, NSUInteger lineEndIndex, BOOL *stop))block
{
NSUInteger length;
NSUInteger lineStart, lineEnd, contentsEnd;
NSRange currentLocationRange;
BOOL stop;
length = [self length];
lineStart = lineEnd = contentsEnd = 0;
stop = NO;
// Enumerate through the string line by line
while (lineStart < length && !stop)
{
NSString *line;
NSRange lineRange;
currentLocationRange = NSMakeRange(lineStart, 0);
[self getLineStart:&lineStart
end:&lineEnd
contentsEnd:&contentsEnd
forRange:currentLocationRange];
lineRange = NSMakeRange(lineStart, contentsEnd - lineStart);
line = [self substringWithRange:lineRange];
// Execute the block
block(line, lineEnd, &stop);
// Move to the next line
lineStart = lineEnd;
}
}
@end
/* We don't need this once toll-free bridging works */
NSData *
copyDispatchDataToNSData(dispatch_data_t dispatchData)
{
NSMutableData *mutableData =
[NSMutableData dataWithCapacity:dispatch_data_get_size(dispatchData)];
dispatch_data_apply(dispatchData, ^bool(dispatch_data_t region, size_t offset,
const void *buffer, size_t size) {
[mutableData appendBytes:buffer length:size];
return true; // Continue iterating
});
return [mutableData copy];
}
@implementation Route
{
NSString *_method;
NSURL *_url;
RequestHandlerBlock _block;
}
+ (instancetype)routeWithURL:(NSURL *)url
method:(NSString *)method
handler:(RequestHandlerBlock)block
{
return [[Route alloc] initWithURL:url method:method handler:block];
}
- (instancetype)initWithURL:(NSURL *)url
method:(NSString *)method
handler:(RequestHandlerBlock)block
{
self = [super init];
if (self)
{
_url = url;
_method = method;
_block = block;
}
return self;
}
- (NSString *)method
{
return _method;
}
- (NSURL *)url
{
return _url;
}
- (RequestHandlerBlock)block
{
return _block;
}
- (BOOL)acceptsURL:(NSURL *)url method:(NSString *)method
{
return [[_url path] isEqualTo:[url path]];
}
@end /* Route */
@implementation HTTPServer
{
_Atomic(BOOL) _stop;
int _socket;
NSInteger _port;
NSArray<Route *> *_routes;
dispatch_queue_t _queue;
dispatch_queue_t _acceptQueue;
}
- initWithPort:(NSInteger)port routes:(NSArray<Route *> *)routes
{
self = [super init];
if (!self)
{
return nil;
}
#ifdef _WIN32
WSADATA wsaData;
// Initialise WinSock2 API
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
NSLog(@"Error Creating Socket: %d", WSAGetLastError());
return nil;
}
#endif
_stop = YES;
_socket = socket(AF_INET, SOCK_STREAM, 0);
if (_socket == -1)
{
NSLog(@"Error creating socket %s", strerror(errno));
return nil;
}
_routes = [routes copy];
struct sockaddr_in serverAddr;
NSUInteger addrLen = sizeof(struct sockaddr_in);
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = NSSwapHostShortToBig(port);
serverAddr.sin_addr.s_addr = INADDR_ANY;
int rc;
int yes = 1;
rc = setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
if (rc == -1)
{
NSLog(@"Error setting socket options %s", strerror(errno));
return nil;
}
rc = bind(_socket, (struct sockaddr *) &serverAddr, sizeof(struct sockaddr));
if (rc < 0)
{
NSLog(@"Error binding to socket %s", strerror(errno));
return nil;
}
// Get Port Number
if (getsockname(_socket, (struct sockaddr *) &serverAddr, &addrLen) == -1)
{
NSLog(@"Error getting socket name %s", strerror(errno));
return nil;
}
_port = NSSwapBigShortToHost(serverAddr.sin_port);
rc = listen(_socket, 20);
if (rc < 0)
{
NSLog(@"Error listening on socket %s", strerror(errno));
return nil;
}
_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_acceptQueue = dispatch_queue_create("org.gnustep.HTTPServer.AcceptQueue",
DISPATCH_QUEUE_CONCURRENT);
return self;
}
- (void)acceptConnection
{
struct sockaddr_in clientAddr;
dispatch_io_t ioChannel;
NSUInteger sin_size;
int clientSocket;
sin_size = sizeof(struct sockaddr_in);
clientSocket = accept(_socket, (struct sockaddr *) &clientAddr, &sin_size);
if (clientSocket < 0)
{
NSLog(@"Error accepting connection %s", strerror(errno));
return;
}
ioChannel
= dispatch_io_create(DISPATCH_IO_STREAM, clientSocket, _queue,
^(int error) {
close(clientSocket);
if (error)
{
NSLog(@"Error creating dispatch I/O channel %s",
strerror(error));
return;
}
});
dispatch_io_set_low_water(ioChannel, 1);
dispatch_io_read(ioChannel, 0, SIZE_MAX, _queue,
^(bool done, dispatch_data_t data, int error) {
if (error)
{
NSLog(@"Error reading data %s", strerror(error));
dispatch_io_close(ioChannel, DISPATCH_IO_STOP);
return;
}
if (data && dispatch_data_get_size(data) != 0)
{
[self handleConnectionData:data
forSocket:clientSocket];
}
if (done)
{
dispatch_io_close(ioChannel, DISPATCH_IO_STOP);
}
});
}
- (void)handleConnectionData:(dispatch_data_t)data forSocket:(int)sock
{
NSData *reqData;
NSString *reqString;
NSRange bodyRange;
NSString *method, *url, *version;
NSURL *requestURL;
NSScanner *scanner;
Route *selectedRoute = nil;
__block NSString *firstLine = nil;
__block NSMutableURLRequest *request = [NSMutableURLRequest new];
__block NSUInteger headerEndIndex = 1;
reqData = copyDispatchDataToNSData(data);
reqString = [[NSString alloc] initWithData:reqData
encoding:NSUTF8StringEncoding];
/*
* generic-message = Request-Line
* *(message-header CRLF)
* CRLF
* [ message-body ]
* Request-Line = Method SP Request-URI SP HTTP-Version CRLF
*/
[reqString enumerateLinesUsingBlock2:^(NSString *line,
NSUInteger lineEndIndex, BOOL *stop) {
NSRange range;
NSString *key, *value;
NSCharacterSet *set;
set = [NSCharacterSet whitespaceCharacterSet];
/* Parse Request Line */
if (nil == firstLine)
{
firstLine = [line stringByTrimmingCharactersInSet:set];
return;
}
/* Reached end of message header. Stop. */
if ([line length] == 0)
{
*stop = YES;
headerEndIndex = lineEndIndex;
}
range = [line rangeOfString:@":"];
/* Ignore this line */
if (NSNotFound == range.location)
{
return;
}
key = [[line substringToIndex:range.location]
stringByTrimmingCharactersInSet:set];
value = [[line substringFromIndex:range.location + 1]
stringByTrimmingCharactersInSet:set];
[request addValue:value forHTTPHeaderField:key];
}];
/* Calculate remaining body range */
bodyRange = NSMakeRange(headerEndIndex, [reqData length] - headerEndIndex);
reqData = [reqData subdataWithRange:bodyRange];
/* Parse Request Line */
scanner = [NSScanner scannerWithString:firstLine];
[scanner scanUpToString:@" " intoString:&method];
[scanner scanUpToString:@" " intoString:&url];
[scanner scanUpToString:@" " intoString:&version];
requestURL = [NSURL URLWithString:url];
[request setURL:requestURL];
[request setHTTPMethod:method];
[request setHTTPBody:reqData];
for (Route *r in _routes)
{
if ([r acceptsURL:requestURL method:method])
{
selectedRoute = r;
break;
}
}
NSData *responseData;
if (selectedRoute)
{
responseData = [selectedRoute block]([request copy]);
}
else
{
responseData = [@"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"
dataUsingEncoding:NSASCIIStringEncoding];
}
send(sock, [responseData bytes], [responseData length], 0);
}
- (void)setRoutes:(NSArray<Route *> *)routes
{
_routes = [routes copy];
}
- (NSInteger)port
{
return _port;
}
- (void)resume
{
if (_stop)
{
_stop = NO;
dispatch_async(_acceptQueue, ^{
while (!_stop)
{
[self acceptConnection];
}
});
}
}
- (void)suspend
{
_stop = YES;
}
- (void)dealloc
{
#ifndef __APPLE__
dispatch_release(_acceptQueue);
#endif
close(_socket);
#ifdef _WIN32
WSACleanup();
#endif
}
@end

View file

@ -1,26 +0,0 @@
#import <Foundation/NSDate.h>
#import <Foundation/NSRunLoop.h>
@interface
NSRunLoop (TimeOutAdditions)
- (void)runForSeconds:(NSTimeInterval)seconds conditionBlock:(BOOL (^)())block;
@end
@implementation
NSRunLoop (TimeOutAdditions)
- (void)runForSeconds:(NSTimeInterval)seconds conditionBlock:(BOOL (^)())block
{
NSDate *startDate = [NSDate date];
NSTimeInterval endTime = [startDate timeIntervalSince1970] + seconds;
NSTimeInterval interval = 0.1; // Interval to check the condition
while (block() && [[NSDate date] timeIntervalSince1970] < endTime)
{
@autoreleasepool
{
[[NSRunLoop currentRunLoop]
runUntilDate:[NSDate dateWithTimeIntervalSinceNow:interval]];
}
}
}
@end

View file

@ -1,54 +0,0 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas volutpat justo
in aliquet venenatis. Aliquam eu odio volutpat, euismod turpis a, accumsan leo.
Aliquam mauris urna, dictum a ex vel, fermentum faucibus enim. Donec eu massa
ipsum. Vestibulum diam metus, dictum mattis urna vel, dignissim pretium magna.
Phasellus quis mattis felis. Morbi non dapibus nunc. Fusce id ligula fringilla,
varius tortor et, vulputate arcu. Quisque justo dolor, bibendum et imperdiet
ultricies, posuere vel elit. Aliquam vel risus et sapien malesuada tempor.
Aenean lorem metus, rutrum ut massa non, ultrices tincidunt neque. Nullam
viverra rhoncus sem et convallis.
Sed id ligula non mauris lacinia mattis. Donec commodo tortor sed aliquet
commodo. Curabitur auctor, nibh eu dignissim gravida, lacus eros tincidunt
metus, ac fermentum mauris ex vitae magna. Vestibulum sed egestas elit, eu
placerat enim. Vivamus ullamcorper sollicitudin ullamcorper. Mauris consequat
quam et purus malesuada, quis euismod nisl scelerisque. Sed vestibulum dictum
nisi a viverra. Ut rutrum aliquam porta. Pellentesque est augue, ullamcorper vel
ultricies quis, posuere non turpis. Aenean suscipit nulla massa, vel venenatis
felis molestie ut. Morbi in lorem quam. Quisque sed facilisis neque, et mattis
ligula. Nam non placerat odio, ac sollicitudin dolor. Donec elit neque, rhoncus
at risus fermentum, ultrices hendrerit massa. Nulla volutpat, urna id molestie
scelerisque, ligula est laoreet mauris, eget maximus leo purus id mi.
Aenean arcu metus, ornare eget est sit amet, vehicula accumsan enim. Morbi ante
diam, faucibus nec tempus molestie, vulputate auctor massa. Curabitur posuere
iaculis pulvinar. Fusce id ligula at justo iaculis accumsan eu sed velit.
Quisque condimentum augue non lobortis finibus. In quis interdum ligula, a
varius ex. Vestibulum tristique orci eget velit lacinia, at lacinia lorem
pretium. Sed ut varius mi. Vivamus elementum luctus arcu, vitae porta felis
cursus nec.
Etiam sed nisl eu nulla scelerisque fermentum. Praesent blandit eros at lacus
blandit auctor. Sed in eros sapien. Quisque ac laoreet nisi, a fringilla nunc.
Sed auctor eros non mauris elementum vestibulum. Aenean vel urna orci.
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
curae; In sed blandit nibh. Aenean condimentum, lorem a tincidunt consequat,
nulla nulla ornare lorem, eu cursus sapien mi aliquam arcu. Duis porttitor mi et
ultricies porta. Donec tempus posuere lorem sit amet tincidunt. Nam nec purus
sit amet sem accumsan consectetur. Ut a nisl volutpat, egestas diam non, feugiat
mi.
Cras sed risus eget ligula tincidunt luctus. Vestibulum ut sapien dictum,
sagittis elit vel, semper diam. Curabitur tristique ullamcorper lacus, quis
finibus ex rutrum vel. Phasellus non blandit nisl, id tincidunt augue. Integer
commodo metus at augue fringilla, sit amet posuere lectus iaculis. In nunc
lacus, pulvinar scelerisque faucibus ut, fermentum ac ante. Duis in ante
faucibus, ullamcorper est vel, convallis lacus. Nulla fermentum, nisi eget
sollicitudin sodales, augue felis gravida est, id luctus sem augue non turpis.
Sed a lorem vel diam maximus malesuada ut ut leo. Fusce venenatis, tellus a
scelerisque dictum, metus elit vehicula purus, id gravida libero libero vitae
arcu. Curabitur a cursus metus. Aenean efficitur rhoncus ante vitae imperdiet.
Etiam ante lacus, aliquam id sem non, ullamcorper interdum libero. Sed fringilla
dui sed est vehicula, vitae sollicitudin mauris tristique. Praesent ut arcu ut
neque sollicitudin finibus. Vestibulum lacus risus, egestas vel lorem eget,
pellentesque scelerisque lectus.

View file

@ -1,224 +0,0 @@
#import <Foundation/NSURLSession.h>
#import <Foundation/NSData.h>
@class URLManager;
typedef void (^URLManagerCheckBlock)(URLManager *);
@interface URLManager
: NSObject <NSURLSessionDataDelegate, NSURLSessionTaskDelegate,
NSURLSessionDelegate, NSURLSessionDownloadDelegate>
{
URLManagerCheckBlock _checkBlock;
@public
NSURLSession *currentSession;
NSURLSessionResponseDisposition responseAnswer;
NSInteger numberOfExpectedTasksBeforeCheck;
NSInteger didCreateTaskCount;
NSURLSessionTask *didCreateTask;
NSInteger didBecomeInvalidCount;
NSError *didBecomeInvalidError;
NSInteger httpRedirectionCount;
NSURLSessionTask *httpRedirectionTask;
NSHTTPURLResponse *httpRedirectionResponse;
NSURLRequest *httpRedirectionRequest;
NSInteger didCompleteCount;
NSURLSessionTask *didCompleteTask;
NSError *didCompleteError;
NSInteger didWriteDataCount;
NSURLSessionDownloadTask *didWriteDataTask;
int64_t downloadBytesWritten;
int64_t downloadTotalBytesWritten;
int64_t downloadTotalBytesExpectedToWrite;
NSInteger didFinishDownloadingCount;
NSURLSessionDownloadTask *didFinishDownloadingTask;
NSURL *didFinishDownloadingURL;
NSInteger didReceiveResponseCount;
NSURLSessionDataTask *didReceiveResponseTask;
NSURLResponse *didReceiveResponse;
NSInteger didReceiveDataCount;
NSURLSessionDataTask *didReceiveDataTask;
NSMutableData *accumulatedData;
BOOL cancelRedirect;
}
- (void)setCheckBlock:(URLManagerCheckBlock)block;
@end
@implementation URLManager
- (instancetype)init
{
self = [super init];
if (self)
{
responseAnswer = NSURLSessionResponseAllow;
accumulatedData = [[NSMutableData alloc] init];
}
return self;
}
- (void)setCheckBlock:(URLManagerCheckBlock)block
{
_checkBlock = _Block_copy(block);
}
#pragma mark - Session Lifecycle
- (void)URLSession:(NSURLSession *)session
didCreateTask:(NSURLSessionTask *)task
{
ASSIGN(currentSession, session);
didCreateTaskCount += 1;
ASSIGN(didCreateTask, task);
}
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
ASSIGN(currentSession, session);
ASSIGN(didBecomeInvalidError, error);
didBecomeInvalidCount += 1;
}
#pragma mark - Task Updates
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
ASSIGN(currentSession, session);
ASSIGN(httpRedirectionTask, task);
ASSIGN(httpRedirectionResponse, response);
ASSIGN(httpRedirectionRequest, request);
if (cancelRedirect)
{
completionHandler(NULL);
}
else
{
completionHandler(request);
}
httpRedirectionCount += 1;
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error
{
ASSIGN(currentSession, session);
ASSIGN(didCompleteTask, task);
ASSIGN(didCompleteError, error);
didCompleteCount += 1;
if (didCompleteCount == numberOfExpectedTasksBeforeCheck
&& _checkBlock != NULL)
{
_checkBlock(self);
}
}
#pragma mark - Data Updates
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:
(void (^)(NSURLSessionResponseDisposition))completionHandler
{
ASSIGN(currentSession, session);
ASSIGN(didReceiveResponseTask, dataTask);
ASSIGN(didReceiveResponse, response);
didReceiveResponseCount += 1;
completionHandler(responseAnswer);
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
ASSIGN(currentSession, session);
ASSIGN(didReceiveResponseTask, dataTask);
didReceiveDataCount += 1;
[accumulatedData appendData:data];
}
#pragma mark - Download Updates
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
ASSIGN(currentSession, session);
didWriteDataCount += 1;
downloadBytesWritten = bytesWritten;
downloadTotalBytesExpectedToWrite = totalBytesExpectedToWrite;
downloadTotalBytesExpectedToWrite = totalBytesExpectedToWrite;
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
ASSIGN(currentSession, session);
ASSIGN(didFinishDownloadingTask, downloadTask);
ASSIGN(didFinishDownloadingURL, location);
didFinishDownloadingCount += 1;
}
- (void)dealloc
{
RELEASE(currentSession);
RELEASE(didCreateTask);
RELEASE(didBecomeInvalidError);
RELEASE(httpRedirectionTask);
RELEASE(httpRedirectionResponse);
RELEASE(httpRedirectionRequest);
RELEASE(didCompleteTask);
RELEASE(didCompleteError);
RELEASE(didWriteDataTask);
RELEASE(didFinishDownloadingTask);
RELEASE(didFinishDownloadingURL);
RELEASE(didReceiveResponseTask);
RELEASE(didReceiveResponse);
RELEASE(didReceiveDataTask);
RELEASE(accumulatedData);
_Block_release(_checkBlock);
[super dealloc];
}
@end

View file

@ -0,0 +1,131 @@
#import <Foundation/Foundation.h>
#import "Testing.h"
#import "ObjectTesting.h"
@interface MyDelegate : NSObject <NSURLSessionDelegate>
{
BOOL _finished;
NSMutableArray *_order;
NSURLResponse *_response;
NSData *_taskData;
NSString *_taskText;
NSError *_taskError;
NSURL *_taskLocation;
}
- (BOOL) finished;
- (void) reset;
@end
@implementation MyDelegate
- (void) dealloc
{
[self reset];
[super dealloc];
}
- (BOOL) finished
{
return _finished;
}
- (id) init
{
if (nil != (self = [super init]))
{
_order = [NSMutableArray new];
}
return self;
}
- (NSMutableArray*) order
{
return _order;
}
- (void) reset
{
DESTROY(_order);
DESTROY(_response);
DESTROY(_taskData);
DESTROY(_taskError);
DESTROY(_taskText);
DESTROY(_taskLocation);
_finished = NO;
}
- (NSURLResponse*) response
{
return _response;
}
- (NSData*) taskData
{
return _taskData;
}
- (NSError*) taskError
{
return _taskError;
}
- (NSString*) taskText
{
return _taskText;
}
- (NSURL*) taskLocation
{
return _taskLocation;
}
- (void) URLSession: (NSURLSession*)session
dataTask: (NSURLSessionDataTask*)dataTask
didReceiveResponse: (NSURLResponse*)response
completionHandler: (void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
[_order addObject: NSStringFromSelector(_cmd)];
ASSIGN(_response, response);
completionHandler(NSURLSessionResponseAllow);
}
- (void) URLSession: (NSURLSession*)session
dataTask: (NSURLSessionDataTask*)dataTask
didReceiveData: (NSData*)data
{
[_order addObject: NSStringFromSelector(_cmd)];
NSString *text;
ASSIGN(_taskData, data);
text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
if (nil != text)
{
ASSIGN(_taskText, text);
}
RELEASE(text);
}
/* NSURLSessionDownloadDelegate */
- (void) URLSession: (NSURLSession *)session
downloadTask: (NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL: (NSURL *)location
{
ASSIGN(_taskLocation, location);
}
- (void) URLSession: (NSURLSession*)session
task: (NSURLSessionTask*)task
didCompleteWithError: (NSError*)error
{
[_order addObject: NSStringFromSelector(_cmd)];
_finished = YES;
if (error == nil)
{
NSLog(@"Download is Successful");
}
else
{
NSLog(@"Error %@", [error userInfo]);
}
ASSIGN(_taskError, error);
}
@end

View file

@ -0,0 +1,79 @@
def print_ivar
set $adr = malloc(1000)
call strcpy(($adr),($arg1))
call GSObjCPrint(($arg0),($adr))
call free($adr)
end
def print_ivars
if ((($arg0)->isa->info & 0x01) || (($arg0)->isa->info == 2816))
# arg0 is a pointer to an object
set $cls = ($arg0)->isa
else
if (($arg0)->isa->info & 0x02)
# arg0 is a pointer to a class
set $cls = ($arg0)
else
# arg0 is something else
set $cls = 0
end
end
while (($cls) != 0)
set $ivars = ($cls)->ivars
if (($ivars) != 0)
set $i = 0
while ($i < ($ivars)->count)
output ($ivars)->ivar_list[$i]
echo \n
set $i = $i + 1
end
end
set $cls = ($cls)->super_class
end
end
def pivi
print *(int *)((char *)($arg0) + ($arg1)))
end
def pivl
print *(long *)((char *)($arg0) + ($arg1)))
end
def pivp
print *(void *)((char *)($arg0) + ($arg1)))
end
def pivo
po *((id *)((char *)($arg0) + ($arg1)))
end
document print_ivars
Recursively print the instance varibles of the object or a class given
as first (and only) argument.
end
document pivi
Print the value of the an instance variable as an int.
The first argument is the pointer to the object and the second the
offset of the instance variable.
end
document pivl
Print the value of the an instance variable as a long.
The first argument is the pointer to the object and the second the
offset of the instance variable.
end
document pivp
Print the value of the an instance variable as a pointer (to void).
The first argument is the pointer to the object and the second the
offset of the instance variable.
end
document pivo
Ask an instance variable to print itself (using po).
The first argument is the pointer to the object and the second the
offset of the instance variable.
end

View file

@ -1,886 +0,0 @@
#import <Foundation/Foundation.h>
#if GS_HAVE_NSURLSESSION
#import "Helpers/HTTPServer.h"
#import "NSRunLoop+TimeOutAdditions.h"
#import "URLManager.h"
#import "Testing.h"
/* Timeout in Seconds */
static NSInteger testTimeOut = 60;
static NSTimeInterval expectedCountOfTasksToComplete = 0;
/* Accessed in delegate on different thread.
*/
static _Atomic(NSInteger) currentCountOfCompletedTasks = 0;
static NSLock *countLock;
static NSDictionary *requestCookieProperties;
static NSArray<Route *> *
createRoutes(Class routeClass, NSURL *baseURL)
{
Route *routeOKWithContent;
Route *routeTmpRedirectToOK;
Route *routeTmpRelativeRedirectToOK;
Route *routeSetCookiesOK;
Route *routeFoldedHeaders;
Route *routeIncorrectlyFoldedHeaders;
NSURL *routeOKWithContentURL;
NSURL *routeTmpRedirectToOKURL;
NSURL *routeTmpRelativeRedirectToOKURL;
NSURL *routeSetCookiesOKURL;
NSURL *routeFoldedHeadersURL;
NSURL *routeIncorrectlyFoldedHeadersURL;
routeOKWithContentURL = [NSURL URLWithString:@"/contentOK"];
routeTmpRedirectToOKURL = [NSURL URLWithString:@"/tmpRedirectToOK"];
routeTmpRelativeRedirectToOKURL =
[NSURL URLWithString:@"/tmpRelativeRedirectToOK"];
routeSetCookiesOKURL = [NSURL URLWithString:@"/setCookiesOK"];
routeFoldedHeadersURL = [NSURL URLWithString:@"/foldedHeaders"];
routeIncorrectlyFoldedHeadersURL =
[NSURL URLWithString:@"/incorrectFoldedHeaders"];
routeOKWithContent = [routeClass
routeWithURL:routeOKWithContentURL
method:@"GET"
handler:^NSData *(NSURLRequest *req) {
NSData *response;
response =
[@"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!"
dataUsingEncoding:NSASCIIStringEncoding];
return response;
}];
routeTmpRedirectToOK = [routeClass
routeWithURL:routeTmpRedirectToOKURL
method:@"GET"
handler:^NSData *(NSURLRequest *req) {
NSData *response;
NSString *responseString;
responseString = [NSString
stringWithFormat:@"HTTP/1.1 307 Temporary Redirect\r\nLocation: "
@"%@\r\nContent-Length: 0\r\n\r\n",
[baseURL
URLByAppendingPathComponent:@"contentOK"]];
response = [responseString dataUsingEncoding:NSASCIIStringEncoding];
return response;
}];
routeTmpRelativeRedirectToOK = [routeClass
routeWithURL:routeTmpRelativeRedirectToOKURL
method:@"GET"
handler:^NSData *(NSURLRequest *req) {
NSData *response;
NSString *responseString;
responseString = [NSString
stringWithFormat:@"HTTP/1.1 307 Temporary Redirect\r\nLocation: "
@"contentOK\r\nContent-Length: 0\r\n\r\n"];
response = [responseString dataUsingEncoding:NSASCIIStringEncoding];
return response;
}];
routeSetCookiesOK = [routeClass
routeWithURL:routeSetCookiesOKURL
method:@"GET"
handler:^NSData *(NSURLRequest *req) {
NSData *response;
NSString *httpResponse;
httpResponse = @"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Set-Cookie: sessionId=abc123; Expires=Wed, 09 Jun "
"2100 10:18:14 GMT; Path=/\r\n"
"Content-Length: 13\r\n"
"\r\n"
"Hello, world!";
NSString *cookie = [req allHTTPHeaderFields][@"Cookie"];
PASS(cookie != nil, "Cookie field is not nil");
PASS([cookie containsString:@"RequestCookie=1234"],
"cookie contains request cookie");
response = [httpResponse dataUsingEncoding:NSASCIIStringEncoding];
return response;
}];
routeFoldedHeaders = [routeClass
routeWithURL:routeFoldedHeadersURL
method:@"GET"
handler:^NSData *(NSURLRequest *req) {
NSData *response;
response =
[@"HTTP/1.1 200 OK\r\nContent-Length: 12\r\nFolded-Header-SP: "
@"Test\r\n ing\r\nFolded-Header-TAB: Test\r\n\ting\r\n\r\nHello "
@"World!" dataUsingEncoding:NSASCIIStringEncoding];
return response;
}];
routeIncorrectlyFoldedHeaders = [routeClass
routeWithURL:routeIncorrectlyFoldedHeadersURL
method:@"GET"
handler:^NSData *(NSURLRequest *req) {
NSData *response;
response = [@"HTTP/1.1 200 OK\r\n"
@" ing\r\nFolded-Header-TAB: Test\r\n\ting\r\n\r\nHello "
@"World!" dataUsingEncoding:NSASCIIStringEncoding];
return response;
}];
return @[
routeOKWithContent, routeTmpRedirectToOK, routeTmpRelativeRedirectToOK,
routeSetCookiesOK, routeFoldedHeaders, routeIncorrectlyFoldedHeaders
];
}
/* Block needs to be released */
static URLManagerCheckBlock
downloadCheckBlock(const char *prefix, NSURLSession *session,
NSURLSessionTask *task)
{
return _Block_copy(^(URLManager *mgr) {
NSURL *location;
NSData *data;
NSString *string;
NSFileManager *fm;
location = mgr->didFinishDownloadingURL;
fm = [NSFileManager defaultManager];
PASS_EQUAL(mgr->currentSession, session,
"%s URLManager Session is equal to session", prefix);
/* Check URLSession:didCreateTask: callback */
PASS(mgr->didCreateTaskCount == 1, "%s didCreateTask: Count is correct",
prefix);
PASS_EQUAL(mgr->didCreateTask, task,
"%s didCreateTask: task is equal to returned task", prefix);
/* Check URLSession:task:didCompleteWithError: */
PASS(nil == mgr->didCompleteError,
"%s didCompleteWithError: No error occurred", prefix)
PASS(mgr->didCompleteCount == 1,
"%s didCompleteWithError: Count is correct", prefix);
PASS_EQUAL(mgr->didCompleteTask, task,
"%s didCompleteWithError: task is equal to returned task",
prefix);
/* Check Progress Reporting */
PASS(mgr->didWriteDataCount == 1, "%s didWriteData: count is correct",
prefix);
PASS(mgr->downloadTotalBytesWritten
== mgr->downloadTotalBytesExpectedToWrite,
"%s didWriteData: Downloaded all expected data", prefix);
PASS(nil != mgr->didFinishDownloadingURL,
"%s didWriteData: Download location is not nil", prefix);
PASS([location isFileURL], "%s location is a fileURL", prefix);
data = [NSData dataWithContentsOfURL:location];
PASS(nil != data, "%s dataWithContentsOfURL is not nil", prefix)
string = [[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding];
PASS(nil != string, "%s string from data is not nil", prefix);
PASS_EQUAL(string, @"Hello World!", "%s data is correct", prefix);
[string release];
/* Remove Downloaded Item */
if (location)
{
[fm removeItemAtURL:location error:NULL];
}
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
});
}
static URLManagerCheckBlock
dataCheckBlock(const char *prefix, NSURLSession *session,
NSURLSessionTask *task)
{
return _Block_copy(^(URLManager *mgr) {
PASS_EQUAL(mgr->currentSession, session,
"%s URLManager Session is equal to session", prefix);
/* Check URLSession:didCreateTask: callback */
PASS(mgr->didCreateTaskCount == 1, "%s didCreateTask: Count is correct",
prefix);
PASS_EQUAL(mgr->didCreateTask, task,
"%s didCreateTask: task is equal to returned task", prefix);
/* Check URLSession:task:didCompleteWithError: */
PASS(nil == mgr->didCompleteError,
"%s didCompleteWithError: No error occurred", prefix)
PASS(mgr->didCompleteCount == 1,
"%s didCompleteWithError: Count is correct", prefix);
PASS_EQUAL(mgr->didCompleteTask, task,
"%s didCompleteWithError: task is equal to returned task",
prefix);
NSData *data = mgr->accumulatedData;
PASS(mgr->didReceiveDataCount == 1, "%s didReceiveData: Count is correct",
prefix);
PASS(nil != data, "%s data in didReceiveData is not nil", prefix);
NSString *string = [[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding];
PASS(nil != string, "%s string from data is not nil", prefix);
PASS_EQUAL(string, @"Hello World!", "%s data is correct", prefix);
[string release];
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
});
}
/* Block needs to be released */
static URLManagerCheckBlock
dataCheckBlockFailedRequest(const char *prefix, NSURLSession *session,
NSURLSessionTask *task)
{
return _Block_copy(^(URLManager *mgr) {
PASS_EQUAL(mgr->currentSession, session,
"%s URLManager Session is equal to session", prefix);
/* Check URLSession:didCreateTask: callback */
PASS(mgr->didCreateTaskCount == 1, "%s didCreateTask: Count is correct",
prefix);
PASS_EQUAL(mgr->didCreateTask, task,
"%s didCreateTask: task is equal to returned task", prefix);
/* Check URLSession:task:didCompleteWithError: */
PASS(nil != mgr->didCompleteError,
"%s didCompleteWithError: An error occurred", prefix)
PASS(mgr->didCompleteCount == 1,
"%s didCompleteWithError: Count is correct", prefix);
PASS_EQUAL(mgr->didCompleteTask, task,
"%s didCompleteWithError: task is equal to returned task",
prefix);
/* Check didReceiveResponse if not a canceled redirect */
if (!mgr->cancelRedirect)
{
PASS(mgr->didReceiveResponseCount == 1,
"%s didReceiveResponse: Count is correct", prefix);
PASS(nil != mgr->didReceiveResponse, "%s didReceiveResponse is not nil",
prefix);
PASS_EQUAL(mgr->didReceiveResponseTask, task,
"%s didReceiveResponse: task is equal to returned task",
prefix);
}
else
{
PASS_EQUAL([mgr->didCompleteError code], NSURLErrorCancelled,
"%s didCompleteError is NSURLErrorCancelled", prefix);
}
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
});
}
/* Creates a downloadTaskWithURL: with the /contentOK route.
*
* Delegate callbacks are checked via the URLManager checkBlock.
*/
static URLManager *
testSimpleDownloadTransfer(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionConfiguration *configuration;
NSURLSessionDownloadTask *task;
URLManager *mgr;
URLManagerCheckBlock block;
const char *prefix = "<DownloadTransfer>";
NSURL *contentOKURL;
/* URL Delegate Setup */
mgr = [URLManager new];
mgr->numberOfExpectedTasksBeforeCheck = 1;
expectedCountOfTasksToComplete += 1;
/* URL Setup */
contentOKURL = [baseURL URLByAppendingPathComponent:@"contentOK"];
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:configuration
delegate:mgr
delegateQueue:nil];
task = [session downloadTaskWithURL:contentOKURL];
PASS(nil != task, "%s Session created a valid download task", prefix);
/* Setup Check Block */
block = downloadCheckBlock(prefix, session, task);
[mgr setCheckBlock:block];
_Block_release(block);
[task resume];
return mgr;
}
static URLManager *
testDownloadTransferWithRedirect(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionConfiguration *configuration;
NSURLSessionDownloadTask *task;
URLManager *mgr;
URLManagerCheckBlock block;
const char *prefix = "<DownloadTransferWithRedirect>";
NSURL *contentOKURL;
/* URL Delegate Setup */
mgr = [URLManager new];
mgr->numberOfExpectedTasksBeforeCheck = 1;
expectedCountOfTasksToComplete += 1;
/* URL Setup */
contentOKURL = [baseURL URLByAppendingPathComponent:@"tmpRedirectToOK"];
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:configuration
delegate:mgr
delegateQueue:nil];
task = [session downloadTaskWithURL:contentOKURL];
PASS(nil != task, "%s Session created a valid download task", prefix);
/* Setup Check Block */
block = downloadCheckBlock(prefix, session, task);
[mgr setCheckBlock:block];
_Block_release(block);
[task resume];
return mgr;
}
/* This should use the build in redirection system from libcurl */
static void
testDataTransferWithRedirectAndBlock(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionDataTask *task;
NSURL *url;
expectedCountOfTasksToComplete += 1;
session = [NSURLSession sharedSession];
url = [baseURL URLByAppendingPathComponent:@"tmpRedirectToOK"];
task = [session
dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *string;
const char *prefix = "<DataTransferWithRedirectAndBlock>";
PASS(nil != data, "%s data in completion handler is not nil", prefix);
PASS(nil != response, "%s response is not nil", prefix);
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
"%s response is an NSHTTPURLResponse", prefix);
PASS(nil == error, "%s error is nil", prefix);
string = [[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding];
PASS_EQUAL(string, @"Hello World!", "%s received data is correct",
prefix);
[string release];
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
}];
[task resume];
}
static void
testDataTransferWithCanceledRedirect(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionConfiguration *configuration;
NSURLSessionDataTask *task;
URLManager *mgr;
URLManagerCheckBlock block;
const char *prefix = "<DataTransferWithCanceledRedirect>";
NSURL *contentOKURL;
/* URL Delegate Setup */
mgr = [URLManager new];
mgr->numberOfExpectedTasksBeforeCheck = 1;
mgr->cancelRedirect = YES;
expectedCountOfTasksToComplete += 1;
/* URL Setup */
contentOKURL = [baseURL URLByAppendingPathComponent:@"tmpRedirectToOK"];
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:configuration
delegate:mgr
delegateQueue:nil];
task = [session dataTaskWithURL:contentOKURL];
PASS(nil != task, "%s Session created a valid download task", prefix);
/* Setup Check Block */
block = dataCheckBlockFailedRequest(prefix, session, task);
[mgr setCheckBlock:block];
_Block_release(block);
[task resume];
}
static void
testDataTransferWithRelativeRedirect(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionConfiguration *configuration;
NSURLSessionDataTask *task;
NSURL *url;
URLManager *mgr;
URLManagerCheckBlock block;
const char *prefix = "<DataTransferWithRelativeRedirect>";
/* URL Delegate Setup */
mgr = [URLManager new];
mgr->numberOfExpectedTasksBeforeCheck = 1;
expectedCountOfTasksToComplete += 1;
session = [NSURLSession sharedSession];
url = [baseURL URLByAppendingPathComponent:@"tmpRelativeRedirectToOK"];
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:configuration
delegate:mgr
delegateQueue:nil];
task = [session dataTaskWithURL:url];
PASS(nil != task, "%s Session created a valid download task", prefix);
/* Setup Check Block */
block = dataCheckBlock(prefix, session, task);
[mgr setCheckBlock:block];
_Block_release(block);
[task resume];
}
static void
testDownloadTransferWithBlock(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionDownloadTask *task;
NSURL *url;
expectedCountOfTasksToComplete += 1;
session = [NSURLSession sharedSession];
url = [baseURL URLByAppendingPathComponent:@"contentOK"];
task = [session
downloadTaskWithURL:url
completionHandler:^(NSURL *location, NSURLResponse *response,
NSError *error) {
NSFileManager *fm;
NSData *data;
NSString *string;
const char *prefix;
prefix = "<DownloadTransferWithBlock>";
fm = [NSFileManager defaultManager];
PASS(nil != location, "%s location is not nil", prefix);
PASS(nil != response, "%s response is not nil", prefix);
PASS(nil == error, "%s error is nil", prefix);
data = [NSData dataWithContentsOfURL:location];
PASS(nil != data, "%s data is not nil", prefix);
string = [[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding];
PASS_EQUAL(string, @"Hello World!", "%s content is correct", prefix);
[fm removeItemAtURL:location error:NULL];
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
[string release];
}];
[task resume];
}
static URLManager *
testParallelDataTransfer(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionConfiguration *configuration;
URLManager *mgr;
NSURL *url;
const char *prefix = "<DataTransfer>";
NSInteger numberOfParallelTasks = 10;
/* URL Delegate Setup */
mgr = [URLManager new];
mgr->numberOfExpectedTasksBeforeCheck = numberOfParallelTasks;
expectedCountOfTasksToComplete += numberOfParallelTasks;
url = [baseURL URLByAppendingPathComponent:@"tmpRedirectToOK"];
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
[configuration setHTTPMaximumConnectionsPerHost:0]; // Unlimited
session = [NSURLSession sessionWithConfiguration:configuration
delegate:mgr
delegateQueue:nil];
/* Setup Check Block */
[mgr setCheckBlock:^(URLManager *mgr) {
PASS_EQUAL(mgr->currentSession, session,
"%s URLManager Session is equal to session", prefix);
/* Check URLSession:didCreateTask: callback */
PASS(mgr->didCreateTaskCount == numberOfParallelTasks,
"%s didCreateTask: Count is correct", prefix);
/* Check URLSession:task:didCompleteWithError: */
PASS(nil == mgr->didCompleteError,
"%s didCompleteWithError: No error occurred", prefix)
PASS(mgr->didCompleteCount == numberOfParallelTasks,
"%s didCompleteWithError: Count is correct", prefix);
[countLock lock];
currentCountOfCompletedTasks += numberOfParallelTasks;
[countLock unlock];
}];
for (NSInteger i = 0; i < numberOfParallelTasks; i++)
{
NSURLSessionDataTask *task;
task = [session dataTaskWithURL:url];
[task resume];
}
return mgr;
}
static void
testDataTaskWithBlock(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionDataTask *task;
NSURL *url;
expectedCountOfTasksToComplete += 1;
url = [baseURL URLByAppendingPathComponent:@"contentOK"];
session = [NSURLSession sharedSession];
task = [session
dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *string;
const char *prefix = "<DataTaskWithBlock>";
PASS(nil != data, "%s data in completion handler is not nil", prefix);
PASS(nil != response, "%s response is not nil", prefix);
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
"%s response is an NSHTTPURLResponse", prefix);
PASS(nil == error, "%s error is nil", prefix);
string = [[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding];
PASS_EQUAL(string, @"Hello World!", "%s received data is correct",
prefix);
[string release];
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
}];
[task resume];
}
static void
testDataTaskWithCookies(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionConfiguration *config;
NSURLSessionDataTask *task;
NSURL *url;
NSHTTPCookieStorage *cookies;
NSHTTPCookie *requestCookie;
expectedCountOfTasksToComplete += 1;
url = [baseURL URLByAppendingPathComponent:@"setCookiesOK"];
requestCookie = [NSHTTPCookie cookieWithProperties:requestCookieProperties];
cookies = [NSHTTPCookieStorage new];
[cookies setCookie:requestCookie];
config = [NSURLSessionConfiguration new];
[config setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
[config setHTTPCookieStorage:cookies];
[config setHTTPShouldSetCookies:YES];
session = [NSURLSession sessionWithConfiguration:config];
task = [session
dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *string;
NSArray<NSHTTPCookie *> *cookieArray;
NSDate *date;
const char *prefix = "<DataTaskWithCookies>";
PASS(nil != data, "%s data in completion handler is not nil", prefix);
PASS(nil != response, "%s response is not nil", prefix);
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
"%s response is an NSHTTPURLResponse", prefix);
PASS(nil == error, "%s error is nil", prefix);
string = [[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding];
PASS_EQUAL(string, @"Hello, world!", "%s received data is correct",
prefix);
cookieArray = [cookies cookiesForURL:url];
NSInteger count = 0;
for (NSHTTPCookie *ck in cookieArray)
{
if ([[ck name] isEqualToString:@"RequestCookie"])
{
PASS_EQUAL(ck, requestCookie, "RequestCookie is correct");
count += 1;
}
else if ([[ck name] isEqualToString:@"sessionId"])
{
date = [NSDate dateWithString:@"2100-06-09 10:18:14 +0000"];
PASS_EQUAL([ck name], @"sessionId", "Cookie name is correct");
PASS_EQUAL([ck value], @"abc123", "Cookie value is correct");
PASS([ck version] == 0, "Correct cookie version");
PASS([date isEqual:[ck expiresDate]],
"Cookie expiresDate is correct");
count += 1;
}
}
PASS(count == 2, "Found both cookies");
[string release];
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
}];
[task resume];
}
/* Check if NSURLSessionTask correctly unfolds folded header lines */
static void
foldedHeaderDataTaskTest(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionDataTask *task;
NSURL *url;
expectedCountOfTasksToComplete += 1;
url = [baseURL URLByAppendingPathComponent:@"foldedHeaders"];
session = [NSURLSession sharedSession];
task = [session
dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *urlResponse = (NSHTTPURLResponse *) response;
NSString *string;
NSDictionary *headerDict;
const char *prefix = "<DataTaskWithFoldedHeaders>";
headerDict = [urlResponse allHeaderFields];
PASS(nil != data, "%s data in completion handler is not nil", prefix);
PASS(nil != response, "%s response is not nil", prefix);
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
"%s response is an NSHTTPURLResponse", prefix);
PASS(nil == error, "%s error is nil", prefix);
string = [[NSString alloc] initWithData:data
encoding:NSASCIIStringEncoding];
PASS_EQUAL(string, @"Hello World!", "%s received data is correct",
prefix);
PASS_EQUAL([headerDict objectForKey:@"Folded-Header-SP"], @"Testing",
"Folded header with continuation space is parsed correctly");
PASS_EQUAL([headerDict objectForKey:@"Folded-Header-TAB"], @"Testing",
"Folded header with continuation tab is parsed correctly");
[string release];
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
}];
[task resume];
}
/* The disposition handler triggers transfer cancelation */
static void
testAbortAfterDidReceiveResponse(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionConfiguration *configuration;
NSURLSessionDataTask *task;
URLManager *mgr;
URLManagerCheckBlock block;
const char *prefix = "<AbortAfterDidReceiveResponseTest>";
NSURL *contentOKURL;
/* URL Delegate Setup */
mgr = [URLManager new];
mgr->numberOfExpectedTasksBeforeCheck = 1;
mgr->responseAnswer = NSURLSessionResponseCancel;
expectedCountOfTasksToComplete += 1;
/* URL Setup */
contentOKURL = [baseURL URLByAppendingPathComponent:@"contentOK"];
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
session = [NSURLSession sessionWithConfiguration:configuration
delegate:mgr
delegateQueue:nil];
task = [session dataTaskWithURL:contentOKURL];
PASS(nil != task, "%s Session created a valid download task", prefix);
/* Setup Check Block */
block = dataCheckBlockFailedRequest(prefix, session, task);
[mgr setCheckBlock:block];
_Block_release(block);
[task resume];
}
int
main(int argc, char *argv[])
{
@autoreleasepool
{
NSBundle *bundle;
NSString *helperPath;
NSURL *baseURL;
NSFileManager *fm;
HTTPServer *server;
Class httpServerClass;
Class routeClass;
requestCookieProperties = @{
NSHTTPCookieName : @"RequestCookie",
NSHTTPCookieValue : @"1234",
NSHTTPCookieDomain : @"127.0.0.1",
NSHTTPCookiePath : @"/",
NSHTTPCookieExpires :
[NSDate dateWithString:@"2100-06-09 12:18:14 +0000"],
NSHTTPCookieSecure : @NO,
};
fm = [NSFileManager defaultManager];
helperPath = [[fm currentDirectoryPath]
stringByAppendingString:@"/Helpers/HTTPServer.bundle"];
countLock = [[NSLock alloc] init];
bundle = [NSBundle bundleWithPath:helperPath];
if (![bundle load])
{
[NSException raise:NSInternalInconsistencyException
format:@"failed to load HTTPServer.bundle"];
}
httpServerClass = [bundle principalClass];
routeClass = [bundle classNamed:@"Route"];
/* Bind to dynamic port. Set routes after initialisation. */
server = [[httpServerClass alloc] initWithPort:0 routes:nil];
if (!server)
{
[NSException raise:NSInternalInconsistencyException
format:@"failed to initialise HTTPServer"];
}
baseURL =
[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%ld",
[server port]]];
NSLog(@"Test Server: baseURL=%@", baseURL);
[server setRoutes:createRoutes(routeClass, baseURL)];
[server resume];
// Call Test Functions here
testSimpleDownloadTransfer(baseURL);
testDownloadTransferWithBlock(baseURL);
testParallelDataTransfer(baseURL);
testDataTaskWithBlock(baseURL);
testDataTaskWithCookies(baseURL);
// Testing Header Line Unfolding
foldedHeaderDataTaskTest(baseURL);
// Redirects
testDownloadTransferWithRedirect(baseURL);
testDataTransferWithRedirectAndBlock(baseURL);
testDataTransferWithCanceledRedirect(baseURL);
testDataTransferWithRelativeRedirect(baseURL);
/* Abort in Delegate */
testAbortAfterDidReceiveResponse(baseURL);
[[NSRunLoop currentRunLoop]
runForSeconds:testTimeOut
conditionBlock:^BOOL(void) {
return expectedCountOfTasksToComplete != currentCountOfCompletedTasks;
}];
[server suspend];
PASS(expectedCountOfTasksToComplete == currentCountOfCompletedTasks,
"All transfers were completed before a timeout occurred");
[server release];
[countLock release];
}
}
#else
int
main(int argc, char *argv[])
{
return 0;
}
#endif /* GS_HAVE_NSURLSESSION */

View file

@ -0,0 +1,141 @@
#import <Foundation/Foundation.h>
#import "Testing.h"
#import "ObjectTesting.h"
#if GS_HAVE_NSURLSESSION
@interface MyDelegate : NSObject <NSURLSessionDelegate>
{
@public
BOOL responseCompletion;
BOOL didComplete;
NSString *taskText;
NSError *taskError;
}
@end
@implementation MyDelegate
- (void) dealloc
{
RELEASE(taskText);
RELEASE(taskError);
[super dealloc];
}
- (void) URLSession: (NSURLSession*)session
dataTask: (NSURLSessionDataTask*)dataTask
didReceiveResponse: (NSURLResponse*)response
completionHandler: (void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
responseCompletion = YES;
if (NO == didComplete)
{
NSLog(@"### handler 1 before didComplete...");
}
else
{
NSLog(@"### handler 1 after didComplete...");
}
completionHandler(NSURLSessionResponseAllow);
}
- (void) URLSession: (NSURLSession*)session
dataTask: (NSURLSessionDataTask*)dataTask
didReceiveData: (NSData*)data
{
NSString *text;
text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
if (nil == text)
{
NSLog(@"Received non-utf8 %@", data);
}
else
{
ASSIGN(taskText, text);
NSLog(@"Received String %@", text);
}
RELEASE(text);
}
- (void) URLSession: (NSURLSession*)session
task: (NSURLSessionTask*)task
didCompleteWithError: (NSError*)error
{
if (error == nil)
{
NSLog(@"Download is Succesfull");
}
else
{
NSLog(@"Error %@", [error userInfo]);
}
didComplete = YES;
ASSIGN(taskError, error);
}
@end
#endif
int main()
{
START_SET("NSURLSession test01")
#if !GS_HAVE_NSURLSESSION
SKIP("library built without NSURLSession support")
#else
NSURLSessionConfiguration *defaultConfigObject;
NSURLSession *defaultSession;
NSURLSessionDataTask *dataTask;
NSMutableURLRequest *urlRequest;
NSURL *url;
NSOperationQueue *mainQueue;
NSString *params;
MyDelegate *object;
#if defined(_WIN32)
NSLog(@"Marking nonexistant host test as hopeful on Windows as it seems to be broken");
testHopeful = YES;
#endif
object = AUTORELEASE([MyDelegate new]);
mainQueue = [NSOperationQueue mainQueue];
defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject
delegate: object
delegateQueue: mainQueue];
url = [NSURL URLWithString:
@"http://localhost:12345/not-here"];
urlRequest = [NSMutableURLRequest requestWithURL: url];
[urlRequest setHTTPMethod: @"POST"];
params = @"name=Ravi&loc=India&age=31&submit=true";
[urlRequest setHTTPBody: [params dataUsingEncoding: NSUTF8StringEncoding]];
if ([urlRequest respondsToSelector: @selector(setDebug:)])
{
[urlRequest setDebug: YES];
}
dataTask = [defaultSession dataTaskWithRequest: urlRequest];
[dataTask resume];
NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 60.0];
while (object->didComplete == NO
&& [limit timeIntervalSinceNow] > 0.0)
{
ENTER_POOL
NSDate *when = [NSDate dateWithTimeIntervalSinceNow: 0.1];
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
beforeDate: when];
LEAVE_POOL
}
PASS(YES == object->didComplete, "request completed")
PASS([object->taskError code] == NSURLErrorCannotConnectToHost,
"unable to connect to host")
#if defined(_WIN32)
testHopeful = NO;
#endif
#endif
END_SET("NSURLSession test01")
return 0;
}

View file

@ -0,0 +1,100 @@
#import <Foundation/Foundation.h>
#import "Testing.h"
#import "ObjectTesting.h"
#import "../NSURLConnection/Helpers/TestWebServer.h"
#if GS_HAVE_NSURLSESSION
#import "delegate.g"
#endif
int main()
{
START_SET("NSURLSession http")
#if !GS_HAVE_NSURLSESSION
SKIP("library built without NSURLSession support")
#else
NSFileManager *fm;
NSBundle *bundle;
NSString *helperPath;
// load the test suite's classes
fm = [NSFileManager defaultManager];
helperPath = [[fm currentDirectoryPath] stringByAppendingPathComponent:
@"../NSURLConnection/Helpers/TestConnection.bundle"];
bundle = [NSBundle bundleWithPath: helperPath];
NSCAssert([bundle load], NSInternalInconsistencyException);
TestWebServer *server;
Class c;
BOOL debug = YES;
// create a shared TestWebServer instance for performance
c = [bundle principalClass];
server = [[c testWebServerClass] new];
NSCAssert(server != nil, NSInternalInconsistencyException);
[server setDebug: debug];
[server start: nil]; // localhost:1234 HTTP
NSURLSessionConfiguration *configuration;
NSURLSession *defaultSession;
NSURLSessionDataTask *dataTask;
NSMutableURLRequest *urlRequest;
NSURL *url;
NSOperationQueue *mainQueue;
NSString *params;
MyDelegate *object;
configuration = [[NSURLSessionConfiguration alloc] init];
[configuration setHTTPShouldUsePipelining: YES];
testHopeful=YES;
PASS_RUNS([configuration setHTTPMaximumConnectionLifetime: 42];,
"-setHTTPMaximumConnectionLifetime: support available in CURL")
testHopeful=NO;
[configuration setHTTPMaximumConnectionsPerHost: 1];
[configuration setRequestCachePolicy: NSURLCacheStorageNotAllowed];
object = AUTORELEASE([MyDelegate new]);
mainQueue = [NSOperationQueue mainQueue];
defaultSession = [NSURLSession sessionWithConfiguration: configuration
delegate: object
delegateQueue: mainQueue];
RELEASE(configuration);
url = [NSURL URLWithString: @"http://localhost:1234/xxx"];
params = @"dummy=true";
urlRequest = [NSMutableURLRequest requestWithURL: url];
[urlRequest setHTTPMethod: @"POST"];
[urlRequest setHTTPBody: [params dataUsingEncoding: NSUTF8StringEncoding]];
if ([urlRequest respondsToSelector: @selector(setDebug:)])
{
[urlRequest setDebug: YES];
}
dataTask = [defaultSession dataTaskWithRequest: urlRequest];
[dataTask resume];
NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 60.0];
while ([object finished] == NO
&& [limit timeIntervalSinceNow] > 0.0)
{
ENTER_POOL
NSDate *when = [NSDate dateWithTimeIntervalSinceNow: 0.1];
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
beforeDate: when];
LEAVE_POOL
}
PASS(YES == [object finished], "request completed")
PASS_EQUAL([object taskError], nil, "request did not error")
NSString *expect = @"Please give login and password";
PASS_EQUAL([object taskText], expect, "request returned text")
#endif
END_SET("NSURLSession http")
return 0;
}

View file

@ -0,0 +1,94 @@
#import <Foundation/Foundation.h>
#import "Testing.h"
#import "ObjectTesting.h"
#import "../NSURLConnection/Helpers/TestWebServer.h"
#if GS_HAVE_NSURLSESSION
#import "delegate.g"
#endif
int main()
{
START_SET("NSURLSession test03")
#if !GS_HAVE_NSURLSESSION
SKIP("library built without NSURLSession support")
#else
NSFileManager *fm;
NSBundle *bundle;
NSString *helperPath;
// load the test suite's classes
fm = [NSFileManager defaultManager];
helperPath = [[fm currentDirectoryPath] stringByAppendingPathComponent:
@"../NSURLConnection/Helpers/TestConnection.bundle"];
bundle = [NSBundle bundleWithPath: helperPath];
NSCAssert([bundle load], NSInternalInconsistencyException);
TestWebServer *server;
Class c;
BOOL debug = YES;
// create a shared TestWebServer instance for performance
c = [bundle principalClass];
server = [[c testWebServerClass] new];
NSCAssert(server != nil, NSInternalInconsistencyException);
[server setDebug: debug];
[server start: nil]; // localhost:1234 HTTP
NSURLSessionConfiguration *configuration;
NSURLSession *defaultSession;
NSURLSessionDownloadTask *downloadTask;
NSMutableURLRequest *urlRequest;
NSURL *url;
NSOperationQueue *mainQueue;
MyDelegate *object;
NSString *content;
configuration = [[NSURLSessionConfiguration alloc] init];
object = AUTORELEASE([MyDelegate new]);
mainQueue = [NSOperationQueue mainQueue];
defaultSession = [NSURLSession sessionWithConfiguration: configuration
delegate: object
delegateQueue: mainQueue];
RELEASE(configuration);
url = [NSURL URLWithString: @"http://localhost:1234/index"];
urlRequest = [NSMutableURLRequest requestWithURL: url];
if ([urlRequest respondsToSelector: @selector(setDebug:)])
{
[urlRequest setDebug: YES];
}
downloadTask = [defaultSession downloadTaskWithRequest: urlRequest];
[downloadTask resume];
NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: 60.0];
while ([object finished] == NO
&& [limit timeIntervalSinceNow] > 0.0)
{
ENTER_POOL
NSDate *when = [NSDate dateWithTimeIntervalSinceNow: 0.1];
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
beforeDate: when];
LEAVE_POOL
}
PASS(YES == [object finished], "request completed")
PASS_EQUAL([object taskError], nil, "request did not error")
/* Get content from file */
content = [NSString stringWithContentsOfFile: [[object taskLocation] path]
encoding: NSUTF8StringEncoding
error:nil];
NSString *expect = @"Please give login and password";
PASS_EQUAL(content, expect, "request returned text")
#endif
END_SET("NSURLSession test03")
return 0;
}

View file

@ -1,204 +0,0 @@
#include "Foundation/NSDate.h"
#import <Foundation/Foundation.h>
#include <Foundation/NSProgress.h>
#include <Foundation/NSString.h>
#if GS_HAVE_NSURLSESSION
#import "Helpers/HTTPServer.h"
#import "NSRunLoop+TimeOutAdditions.h"
#import "URLManager.h"
#import "Testing.h"
typedef void (^dataCompletionHandler)(NSData *data, NSURLResponse *response,
NSError *error);
/* Timeout in Seconds */
static NSInteger testTimeOut = 60;
static NSTimeInterval expectedCountOfTasksToComplete = 0;
/* Accessed in delegate on different thread.
*/
static _Atomic(NSInteger) currentCountOfCompletedTasks = 0;
static NSLock *countLock;
/* Expected Content */
static NSString *largeBodyPath;
static NSData *largeBodyContent;
static NSArray<Route *> *
createRoutes(Class routeClass)
{
Route *routeOKWithContent;
Route *routeLargeUpload;
NSURL *routeOKWithContentURL;
NSURL *routeLargeUploadURL;
routeOKWithContentURL = [NSURL URLWithString:@"/smallUploadOK"];
routeLargeUploadURL = [NSURL URLWithString:@"/largeUploadOK"];
routeOKWithContent = [routeClass
routeWithURL:routeOKWithContentURL
method:@"POST"
handler:^NSData *(NSURLRequest *req) {
NSData *response;
response =
[@"HTTP/1.1 200 OK\r\nContent-Length: 0\r\nHeader-Key: "
@"Header-Value\r\n\r\n" dataUsingEncoding:NSASCIIStringEncoding];
return response;
}];
routeLargeUpload = [routeClass
routeWithURL:routeLargeUploadURL
method:@"POST"
handler:^NSData *(NSURLRequest *req) {
NSData *response;
PASS_EQUAL([req valueForHTTPHeaderField:@"Request-Key"],
@"Request-Value",
"Request contains user-specific header line");
PASS_EQUAL([req valueForHTTPHeaderField:@"Content-Type"],
@"text/plain",
"Request contains the correct Content-Type");
PASS_EQUAL([req HTTPBody], largeBodyContent, "HTTPBody is correct");
response =
[@"HTTP/1.1 200 OK\r\nContent-Length: 0\r\nHeader-Key: "
@"Header-Value\r\n\r\n" dataUsingEncoding:NSASCIIStringEncoding];
return response;
}];
return @[ routeOKWithContent, routeLargeUpload ];
}
static void
testLargeUploadWithBlock(NSURL *baseURL)
{
NSURLSession *session;
NSURLSessionDataTask *dataTask;
NSURLSessionUploadTask *uploadTask;
NSMutableURLRequest *request;
NSURL *url;
dataCompletionHandler handler;
expectedCountOfTasksToComplete += 2;
url = [baseURL URLByAppendingPathComponent:@"largeUploadOK"];
session = [NSURLSession sharedSession];
request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPBody:largeBodyContent];
[request setHTTPMethod:@"POST"];
[request setValue:@"Request-Value" forHTTPHeaderField:@"Request-Key"];
[request setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
/* The completion handler for the two requests */
handler = ^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = response;
PASS([data length] == 0, "Received empty data object");
PASS(nil != response, "Response is not nil");
PASS([response isKindOfClass:[NSHTTPURLResponse class]],
"Response is a NSHTTPURLResponse");
PASS(nil == error, "Error is nil");
PASS_EQUAL([[httpResponse allHeaderFields] objectForKey:@"Header-Key"],
@"Header-Value", "Response contains custom header line");
[countLock lock];
currentCountOfCompletedTasks += 1;
[countLock unlock];
};
dataTask = [session dataTaskWithRequest:request completionHandler:handler];
uploadTask = [session uploadTaskWithRequest:request
fromData:largeBodyContent
completionHandler:handler];
[dataTask resume];
[uploadTask resume];
}
int
main(int argc, char *argv[])
{
@autoreleasepool
{
NSBundle *bundle;
NSString *helperPath;
NSString *currentDirectory;
NSURL *baseURL;
NSFileManager *fm;
NSArray<Route *> *routes;
HTTPServer *server;
Class httpServerClass;
Class routeClass;
fm = [NSFileManager defaultManager];
currentDirectory = [fm currentDirectoryPath];
helperPath =
[currentDirectory stringByAppendingString:@"/Helpers/HTTPServer.bundle"];
countLock = [[NSLock alloc] init];
bundle = [NSBundle bundleWithPath:helperPath];
if (![bundle load])
{
[NSException raise:NSInternalInconsistencyException
format:@"failed to load HTTPServer.bundle"];
}
/* Load Test Data */
largeBodyPath =
[currentDirectory stringByAppendingString:@"/Resources/largeBody.txt"];
largeBodyContent = [NSData dataWithContentsOfFile:largeBodyPath];
PASS(nil != largeBodyContent, "can load %s", [largeBodyPath UTF8String]);
httpServerClass = [bundle principalClass];
routeClass = [bundle classNamed:@"Route"];
routes = createRoutes(routeClass);
server = [[httpServerClass alloc] initWithPort:0 routes:routes];
if (!server)
{
[NSException raise:NSInternalInconsistencyException
format:@"failed to create HTTPServer"];
}
baseURL =
[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%ld",
[server port]]];
NSLog(@"Server started with baseURL: %@", baseURL);
[server resume];
/* Call Test Functions here! */
testLargeUploadWithBlock(baseURL);
[[NSRunLoop currentRunLoop]
runForSeconds:testTimeOut
conditionBlock:^BOOL(void) {
return expectedCountOfTasksToComplete != currentCountOfCompletedTasks;
}];
[server suspend];
PASS(expectedCountOfTasksToComplete == currentCountOfCompletedTasks,
"All transfers were completed before a timeout occurred");
[server release];
[countLock release];
}
return 0;
}
#else
int
main(int argc, char *argv[])
{
return 0;
}
#endif