mirror of
https://github.com/gnustep/libs-base.git
synced 2025-05-29 16:01:38 +00:00
Merge changes for NSURLSession from EngageHub (formerly Brainstorm).
This commit is contained in:
parent
d015cebbf3
commit
1b7bf26bea
70 changed files with 7980 additions and 192 deletions
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
language: cpp
|
||||
dist: xenial
|
||||
dist: focal
|
||||
jobs:
|
||||
include:
|
||||
- name: "Linux GCC"
|
||||
|
@ -44,7 +44,7 @@ before_install: |
|
|||
if [ $CC = 'gcc' ]; then
|
||||
sudo apt-get install -y gobjc
|
||||
fi
|
||||
sudo apt-get install -y libobjc-4.8-dev libblocksruntime-dev
|
||||
sudo apt-get install -y libobjc-9-dev libblocksruntime-dev
|
||||
;;
|
||||
ng-gnu-gnu)
|
||||
curl -s -o - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
|
@ -115,7 +115,7 @@ before_script: |
|
|||
export LD_LIBRARY_PATH=$DEP_ROOT/lib:$DEP_ROOT/lib64:$LD_LIBRARY_PATH
|
||||
case $LIBRARY_COMBO in
|
||||
gnu-gnu-gnu)
|
||||
export CPATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/include
|
||||
export CPATH=/usr/lib/gcc/x86_64-linux-gnu/9/include
|
||||
;;
|
||||
ng-gnu-gnu)
|
||||
export CPATH=$DEP_ROOT/include
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* Interface to debugging utilities for GNUStep and OpenStep
|
||||
Copyright (C) 1997,1999 Free Software Foundation, Inc.
|
||||
Copyright (C) 1997-2020 Free Software Foundation, Inc.
|
||||
|
||||
Written by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
|
||||
Date: August 1997
|
||||
|
@ -44,6 +44,35 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Protocol for a delegate, set as an extension in some classes, to handle
|
||||
* debug logging of low level I/O. The rationale for this protocol is that
|
||||
* on occasion debug logging may be required, but the data being logged may
|
||||
* contain sensitive information which should not be writtent to file. In
|
||||
* that situation, the delegate may filter/mask the sensitive information
|
||||
* from the logs by taking over the simpel writing to stderr that the inbuilt
|
||||
* debug logging provides.
|
||||
*/
|
||||
@protocol GSLogDelegate <NSObject>
|
||||
/** Method sent to the delegate to ask it to log a chunk of data that
|
||||
* has been read. The delegate should return YES if it has handled
|
||||
* the logging, NO if it wants the default mechanism to be used.<br />
|
||||
* The handle is the object which is performing the read operation.
|
||||
*/
|
||||
- (BOOL) getBytes: (const uint8_t*)bytes
|
||||
ofLength: (NSUInteger)length
|
||||
byHandle: (NSObject*)handle;
|
||||
|
||||
/** Method sent to the delegate to ask it to log a chunk of data that
|
||||
* has been written (or is immediately going to be written).
|
||||
* The delegate should return YES if it has handled the logging,
|
||||
* NO if it wants the default logging mechanism to be used.<br />
|
||||
* The handle is the object which is performing the write operation.
|
||||
*/
|
||||
- (BOOL) putBytes: (const uint8_t*)bytes
|
||||
ofLength: (NSUInteger)length
|
||||
byHandle: (NSObject*)handle;
|
||||
@end
|
||||
|
||||
/*
|
||||
* Functions for debugging object allocation/deallocation
|
||||
*
|
||||
|
|
|
@ -196,6 +196,19 @@ typedef enum
|
|||
|
||||
@end
|
||||
|
||||
@class NSURLSessionDataTask;
|
||||
|
||||
@interface NSURLCache (NSURLSessionTaskAdditions)
|
||||
|
||||
- (void) storeCachedResponse: (NSCachedURLResponse*)cachedResponse
|
||||
forDataTask: (NSURLSessionDataTask*)dataTask;
|
||||
|
||||
- (NSCachedURLResponse*) cachedResponseForDataTask: (NSURLSessionDataTask*)dataTask;
|
||||
|
||||
- (void) removeCachedResponseForDataTask: (NSURLSessionDataTask*)dataTask;
|
||||
|
||||
@end
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -71,6 +71,7 @@ enum
|
|||
NSURLErrorFileDoesNotExist = -1100,
|
||||
NSURLErrorFileIsDirectory = -1101,
|
||||
NSURLErrorNoPermissionsToReadFile = -1102,
|
||||
NSURLErrorDataLengthExceedsMaximum = -1103,
|
||||
NSURLErrorSecureConnectionFailed = -1200,
|
||||
NSURLErrorServerCertificateHasBadDate = -1201,
|
||||
NSURLErrorServerCertificateUntrusted = -1202,
|
||||
|
|
|
@ -44,6 +44,7 @@ extern "C" {
|
|||
@class NSURLProtocol;
|
||||
@class NSURLRequest;
|
||||
@class NSURLResponse;
|
||||
@class NSURLSessionTask;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -178,11 +179,20 @@ extern "C" {
|
|||
cachedResponse: (NSCachedURLResponse *)cachedResponse
|
||||
client: (id <NSURLProtocolClient>)client;
|
||||
|
||||
- (instancetype) initWithTask: (NSURLSessionTask*)task
|
||||
cachedResponse: (NSCachedURLResponse*)cachedResponse
|
||||
client: (id<NSURLProtocolClient>)client;
|
||||
|
||||
/**
|
||||
* Returns the request handled by the receiver.
|
||||
*/
|
||||
- (NSURLRequest *) request;
|
||||
|
||||
/**
|
||||
* Returns the task handled by the receiver.
|
||||
*/
|
||||
- (NSURLSessionTask *) task;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
|
|
|
@ -283,6 +283,25 @@ typedef NSUInteger NSURLRequestCachePolicy;
|
|||
|
||||
@end
|
||||
|
||||
@protocol GSLogDelegate;
|
||||
@interface NSMutableURLRequest (GNUstep)
|
||||
|
||||
/** Sets a flag to turn on low level debug logging for this request and the
|
||||
* corresponding response. The previous vaue of the setting is returned.
|
||||
*/
|
||||
- (int) setDebug: (int)d;
|
||||
|
||||
/** Sets a delegate object to override logging of low level I/O of the
|
||||
* request as it is sent and the corresponding response as it arrives.<br />
|
||||
* The delegate object is not retained, so it is the responsibility of the
|
||||
* caller to ensure that it persists until all I/O has completed.<br />
|
||||
* This has no effect unless debug is turned on, but if debug is turned on
|
||||
* it permits the delegate to override the default behavior of writing the
|
||||
* data to stderr.
|
||||
*/
|
||||
- (id<GSLogDelegate>) setDebugLogDelegate: (id<GSLogDelegate>)d;
|
||||
@end
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,18 +2,240 @@
|
|||
#define __NSURLSession_h_GNUSTEP_BASE_INCLUDE
|
||||
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSURLRequest.h>
|
||||
#import <Foundation/NSHTTPCookieStorage.h>
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
#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;
|
||||
@class NSURL;
|
||||
@class NSURLAuthenticationChallenge;
|
||||
@class NSURLCache;
|
||||
@class NSURLCredential;
|
||||
@class NSURLCredentialStorage;
|
||||
@class NSURLRequest;
|
||||
@class NSURLResponse;
|
||||
@class NSURLSessionConfiguration;
|
||||
@class NSURLSessionDataTask;
|
||||
|
||||
|
||||
/**
|
||||
* NSURLSession is a replacement API for NSURLConnection. It provides
|
||||
* options that affect the policy of, and various aspects of the
|
||||
* mechanism by which NSURLRequest objects are retrieved from the
|
||||
* network.<br />
|
||||
*
|
||||
* 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
|
||||
* by the consumer.
|
||||
*/
|
||||
@interface NSURLSession : NSObject
|
||||
{
|
||||
NSOperationQueue *_delegateQueue;
|
||||
id <NSURLSessionDelegate> _delegate;
|
||||
NSURLSessionConfiguration *_configuration;
|
||||
NSString *_sessionDescription;
|
||||
GSMultiHandle *_multiHandle;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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: (id <NSURLSessionDelegate>)delegate
|
||||
delegateQueue: (NSOperationQueue*)queue;
|
||||
|
||||
- (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.
|
||||
*
|
||||
* 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;
|
||||
|
||||
/* -invalidateAndCancel acts as -finishTasksAndInvalidate, but issues
|
||||
* -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.
|
||||
*/
|
||||
- (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.
|
||||
* The request may have a body stream. */
|
||||
- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request;
|
||||
|
||||
/* Creates a data task to retrieve the contents of the given URL. */
|
||||
- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSURLSessionConfiguration : NSObject <NSCopying>
|
||||
@end
|
||||
typedef NS_ENUM(NSUInteger, NSURLSessionTaskState) {
|
||||
/* The task is currently being serviced by the session */
|
||||
NSURLSessionTaskStateRunning = 0,
|
||||
NSURLSessionTaskStateSuspended = 1,
|
||||
/* 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
|
||||
* delegate notifications */
|
||||
NSURLSessionTaskStateCompleted = 3,
|
||||
};
|
||||
|
||||
/*
|
||||
* NSURLSessionTask - a cancelable object that refers to the lifetime
|
||||
* of processing a given request.
|
||||
*/
|
||||
@interface NSURLSessionTask : NSObject <NSCopying>
|
||||
{
|
||||
/** An identifier for this task, assigned by and unique
|
||||
* to the owning session
|
||||
*/
|
||||
NSUInteger _taskIdentifier;
|
||||
|
||||
/** The request this task was created to handle.
|
||||
*/
|
||||
NSURLRequest *_originalRequest;
|
||||
|
||||
/** The request this task is currently handling. This may differ from
|
||||
* originalRequest due to http server redirection
|
||||
*/
|
||||
NSURLRequest *_currentRequest;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
- (NSUInteger) taskIdentifier;
|
||||
|
||||
- (NSURLRequest*) originalRequest;
|
||||
|
||||
- (NSURLRequest*) currentRequest;
|
||||
|
||||
- (NSURLResponse*) response;
|
||||
- (void) setResponse: (NSURLResponse*)response;
|
||||
|
||||
- (int64_t) countOfBytesReceived;
|
||||
|
||||
- (int64_t) countOfBytesSent;
|
||||
|
||||
- (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.
|
||||
*/
|
||||
- (void) cancel;
|
||||
|
||||
/*
|
||||
* 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) suspend;
|
||||
- (void) resume;
|
||||
|
||||
@end
|
||||
|
||||
@interface NSURLSessionDataTask : NSURLSessionTask
|
||||
|
@ -30,11 +252,177 @@
|
|||
@end
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Configuration options for an NSURLSession. When a session is
|
||||
* created, a copy of the configuration object is made - you cannot
|
||||
* modify the configuration of a session after it has been created.
|
||||
*/
|
||||
@interface NSURLSessionConfiguration : NSObject <NSCopying>
|
||||
{
|
||||
NSURLCache *_URLCache;
|
||||
NSURLRequestCachePolicy _requestCachePolicy;
|
||||
NSArray *_protocolClasses;
|
||||
NSInteger _HTTPMaximumConnectionLifetime;
|
||||
NSInteger _HTTPMaximumConnectionsPerHost;
|
||||
BOOL _HTTPShouldUsePipelining;
|
||||
NSHTTPCookieAcceptPolicy _HTTPCookieAcceptPolicy;
|
||||
NSHTTPCookieStorage *_HTTPCookieStorage;
|
||||
NSURLCredentialStorage *_URLCredentialStorage;
|
||||
BOOL _HTTPShouldSetCookies;
|
||||
NSDictionary *_HTTPAdditionalHeaders;
|
||||
}
|
||||
|
||||
- (NSURLRequest*) configureRequest: (NSURLRequest*)request;
|
||||
|
||||
@property (class, readonly, strong)
|
||||
NSURLSessionConfiguration *defaultSessionConfiguration;
|
||||
|
||||
- (NSDictionary*) HTTPAdditionalHeaders;
|
||||
|
||||
- (NSHTTPCookieAcceptPolicy) HTTPCookieAcceptPolicy;
|
||||
|
||||
- (NSHTTPCookieStorage*) HTTPCookieStorage;
|
||||
|
||||
#if !NO_GNUSTEP
|
||||
- (NSInteger) HTTPMaximumConnectionLifetime;
|
||||
#endif
|
||||
|
||||
- (NSInteger) HTTPMaximumConnectionsPerHost;
|
||||
|
||||
- (BOOL) HTTPShouldSetCookies;
|
||||
|
||||
- (BOOL) HTTPShouldUsePipelining;
|
||||
|
||||
- (NSArray*) protocolClasses;
|
||||
|
||||
- (NSURLRequestCachePolicy) requestCachePolicy;
|
||||
|
||||
- (void) setHTTPAdditionalHeaders: (NSDictionary*)headers;
|
||||
|
||||
- (void) setHTTPCookieAcceptPolicy: (NSHTTPCookieAcceptPolicy)policy;
|
||||
|
||||
- (void) setHTTPCookieStorage: (NSHTTPCookieStorage*)storage;
|
||||
|
||||
#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.
|
||||
*/
|
||||
- (void) setHTTPMaximumConnectionLifetime: (NSInteger)n;
|
||||
#endif
|
||||
|
||||
- (void) setHTTPMaximumConnectionsPerHost: (NSInteger)n;
|
||||
|
||||
- (void) setHTTPShouldSetCookies: (BOOL)flag;
|
||||
|
||||
- (void) setHTTPShouldUsePipelining: (BOOL)flag;
|
||||
|
||||
- (void) setRequestCachePolicy: (NSURLRequestCachePolicy)policy;
|
||||
|
||||
- (void) setURLCache: (NSURLCache*)cache;
|
||||
|
||||
- (void) setURLCredentialStorage: (NSURLCredentialStorage*)storage;
|
||||
|
||||
- (NSURLCache*) URLCache;
|
||||
|
||||
- (NSURLCredentialStorage*) URLCredentialStorage;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) {
|
||||
NSURLSessionAuthChallengeUseCredential = 0,
|
||||
NSURLSessionAuthChallengePerformDefaultHandling = 1,
|
||||
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2,
|
||||
NSURLSessionAuthChallengeRejectProtectionSpace = 3
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
|
||||
NSURLSessionResponseCancel = 0,
|
||||
NSURLSessionResponseAllow = 1,
|
||||
NSURLSessionResponseBecomeDownload = 2,
|
||||
NSURLSessionResponseBecomeStream = 3
|
||||
};
|
||||
|
||||
@protocol NSURLSessionDelegate <NSObject>
|
||||
@optional
|
||||
/* The last message a session receives. A session will only become
|
||||
* 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: (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;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
|
||||
@optional
|
||||
/* 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.
|
||||
*/
|
||||
- (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;
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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
|
||||
* response to be delivered as the payload of this request. The default
|
||||
* is to follow redirections.
|
||||
*
|
||||
*/
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
willPerformHTTPRedirection: (NSHTTPURLResponse*)response
|
||||
newRequest: (NSURLRequest*)request
|
||||
completionHandler: (void (^)(NSURLRequest*))completionHandler;
|
||||
|
||||
@end
|
||||
|
||||
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
|
||||
@optional
|
||||
/* Sent when data is available for the delegate to consume.
|
||||
*/
|
||||
- (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;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -267,8 +267,10 @@ typedef struct {
|
|||
#define GS_USE_AVAHI @HAVE_AVAHI@
|
||||
#define GS_USE_MDNS @HAVE_MDNS@
|
||||
#define GS_USE_ICU @HAVE_ICU@
|
||||
#define GS_USE_LIBCURL @HAVE_LIBCURL@
|
||||
#define GS_USE_LIBDISPATCH @HAVE_LIBDISPATCH@
|
||||
#define GS_USE_LIBDISPATCH_RUNLOOP @HAVE_LIBDISPATCH_RUNLOOP@
|
||||
#define GS_HAVE_NSURLSESSION @GS_HAVE_NSURLSESSION@
|
||||
#define GS_HAVE_OBJC_ROOT_CLASS_ATTR @GS_HAVE_OBJC_ROOT_CLASS_ATTR@
|
||||
|
||||
#ifndef __has_include
|
||||
|
|
|
@ -189,6 +189,12 @@
|
|||
/* Define to 1 if you have the `ctime' function. */
|
||||
#undef HAVE_CTIME
|
||||
|
||||
/* Define to 1 if you have the <curl/curl.h> header file. */
|
||||
#undef HAVE_CURL_CURL_H
|
||||
|
||||
/* Define to 1 if you have the `curl_global_sslset' function. */
|
||||
#undef HAVE_CURL_GLOBAL_SSLSET
|
||||
|
||||
/* Define if you have currency_symbol field in struct lconv */
|
||||
#undef HAVE_CURRENCY_SYMBOL_IN_LCONV
|
||||
|
||||
|
@ -219,6 +225,10 @@
|
|||
/* Define to 1 if you have the <dispatch/private.h> header file. */
|
||||
#undef HAVE_DISPATCH_PRIVATE_H
|
||||
|
||||
/* Define to 1 if you have the `dispatch_queue_create_with_target' function.
|
||||
*/
|
||||
#undef HAVE_DISPATCH_QUEUE_CREATE_WITH_TARGET
|
||||
|
||||
/* Define to 1 if you have the `dladdr' function. */
|
||||
#undef HAVE_DLADDR
|
||||
|
||||
|
|
|
@ -2091,3 +2091,253 @@ GSClassSwizzle(id instance, Class newClass)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
GSObjCPrint(void *base, void *item)
|
||||
{
|
||||
FILE *fptr = stdout;
|
||||
Class c;
|
||||
id o;
|
||||
|
||||
if (NULL == base)
|
||||
{
|
||||
fprintf(fptr, "null\n");
|
||||
return;
|
||||
}
|
||||
if (GSObjCIsClass((Class)base))
|
||||
{
|
||||
o = nil;
|
||||
c = (Class)base;
|
||||
if (NULL == item)
|
||||
{
|
||||
fprintf(fptr, "%p is class %s {\n", base, GSNameFromClass(c));
|
||||
}
|
||||
}
|
||||
else if (GSObjCIsInstance((id)base))
|
||||
{
|
||||
o = (id)base;
|
||||
c = GSObjCClass(o);
|
||||
if (NULL == item)
|
||||
{
|
||||
fprintf(fptr, "%p is instance of class %s (%p) {\n",
|
||||
base, GSNameFromClass(c), c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(fptr, "%p is not a class or instance\n", base);
|
||||
return;
|
||||
}
|
||||
|
||||
while (c != Nil)
|
||||
{
|
||||
unsigned count;
|
||||
Ivar *ivars = class_copyIvarList(c, &count);
|
||||
|
||||
while (count-- > 0)
|
||||
{
|
||||
Ivar ivar = ivars[count];
|
||||
const char *name = ivar_getName(ivar);
|
||||
const char *type = ivar_getTypeEncoding(ivar);
|
||||
ptrdiff_t offset = ivar_getOffset(ivar);
|
||||
const char *t;
|
||||
|
||||
if (NULL == item)
|
||||
{
|
||||
fprintf(fptr, " (%ld) %s", (long)offset, name);
|
||||
}
|
||||
else if (strcmp(item, name) == 0)
|
||||
{
|
||||
continue; // not a match
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(fptr, "(%ld) %s", (long)offset, name);
|
||||
}
|
||||
|
||||
if (nil == o)
|
||||
{
|
||||
/* We have no instance ... display offset to ivar
|
||||
*/
|
||||
fprintf(fptr, "\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
fprintf(fptr, " = ");
|
||||
|
||||
t = GSSkipTypeQualifierAndLayoutInfo(type);
|
||||
switch (*t)
|
||||
{
|
||||
case _C_ID:
|
||||
{
|
||||
id v = *(id *)((char *)o + offset);
|
||||
|
||||
if (nil == v)
|
||||
{
|
||||
fprintf(fptr, "nil\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(fptr, "%s instance %p\n",
|
||||
GSNameFromClass(GSObjCClass(v)), v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_CLASS:
|
||||
{
|
||||
Class v = *(Class *)((char *)o + offset);
|
||||
|
||||
if (Nil == v)
|
||||
{
|
||||
fprintf(fptr, "Nil\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(fptr, "%s class %p\n", GSNameFromClass(v), v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_CHR:
|
||||
{
|
||||
signed char v = *(char *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%c %d\n", v, (int)v);
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_UCHR:
|
||||
{
|
||||
unsigned char v = *(unsigned char *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%c %u\n", v, (unsigned)v);
|
||||
}
|
||||
break;
|
||||
|
||||
#if __GNUC__ > 2 && defined(_C_BOOL)
|
||||
case _C_BOOL:
|
||||
{
|
||||
_Bool v = *(_Bool *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%s %u\n", (v ? "YES" : "NO"), (unsigned)v);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case _C_SHT:
|
||||
{
|
||||
short v = *(short *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%hd\n", v);
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_USHT:
|
||||
{
|
||||
unsigned short v;
|
||||
|
||||
v = *(unsigned short *)((char *)o + offset);
|
||||
fprintf(fptr, "%hu\n", v);
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_INT:
|
||||
{
|
||||
int v = *(int *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%d\n", v);
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_UINT:
|
||||
{
|
||||
unsigned int v = *(unsigned int *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%u\n", v);
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_LNG:
|
||||
{
|
||||
long v = *(long *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%ld\n", v);
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_ULNG:
|
||||
{
|
||||
unsigned long v = *(unsigned long *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%lu\n", v);
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef _C_LNG_LNG
|
||||
case _C_LNG_LNG:
|
||||
{
|
||||
long long v = *(long long *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%lld\n", v);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef _C_ULNG_LNG
|
||||
case _C_ULNG_LNG:
|
||||
{
|
||||
unsigned long long v;
|
||||
|
||||
v = *(unsigned long long *)((char *)o + offset);
|
||||
fprintf(fptr, "%llu\n", v);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case _C_FLT:
|
||||
{
|
||||
float v = *(float *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%g\n", v);
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_DBL:
|
||||
{
|
||||
double v = *(double *)((char *)o + offset);
|
||||
|
||||
fprintf(fptr, "%g\n", v);
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_VOID:
|
||||
{
|
||||
fprintf(fptr, "void ???\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case _C_STRUCT_B:
|
||||
{
|
||||
fprintf(fptr, "struct not supported\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
fprintf(fptr, "type %s not supported\n", type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ivars != NULL)
|
||||
{
|
||||
free(ivars);
|
||||
}
|
||||
c = class_getSuperclass(c);
|
||||
}
|
||||
if (NULL == item)
|
||||
{
|
||||
fprintf(fptr, "}\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,4 +67,4 @@ ifeq ($(FOUNDATION_LIB),gnu)
|
|||
endif
|
||||
|
||||
# Additional LDFLAGS to pass to the linker
|
||||
ADDITIONAL_LDFLAGS =
|
||||
ADDITIONAL_LDFLAGS =
|
||||
|
|
|
@ -331,7 +331,6 @@ NSUbiquitousKeyValueStore.m \
|
|||
NSUnarchiver.m \
|
||||
NSUndoManager.m \
|
||||
NSURL.m \
|
||||
NSURLSession.m \
|
||||
NSURLAuthenticationChallenge.m \
|
||||
NSURLCache.m \
|
||||
NSURLCredential.m \
|
||||
|
@ -362,33 +361,53 @@ objc-load.m
|
|||
|
||||
ifneq ($(GNUSTEP_TARGET_OS), mingw32)
|
||||
ifneq ($(GNUSTEP_TARGET_OS), mingw64)
|
||||
BASE_MFILES += \
|
||||
GSFileHandle.m \
|
||||
NSMessagePort.m \
|
||||
NSMessagePortNameServer.m
|
||||
BASE_MFILES += \
|
||||
GSFileHandle.m \
|
||||
NSMessagePort.m \
|
||||
NSMessagePortNameServer.m
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_BLOCKS), 1)
|
||||
ifeq ($(GNUSTEP_BASE_HAVE_LIBDISPATCH), 1)
|
||||
ifeq ($(GNUSTEP_BASE_HAVE_LIBCURL), 1)
|
||||
BASE_MFILES += \
|
||||
GSEasyHandle.m \
|
||||
GSHTTPURLProtocol.m \
|
||||
GSMultiHandle.m \
|
||||
GSNativeProtocol.m \
|
||||
GSTaskRegistry.m \
|
||||
GSTimeoutSource.m \
|
||||
GSTransferState.m \
|
||||
GSURLSessionTaskBody.m \
|
||||
GSURLSessionTaskBodySource.m \
|
||||
NSURLSession.m
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(GNUSTEP_BASE_HAVE_MDNS), 1)
|
||||
BASE_MFILES += NSNetServices.m \
|
||||
GSMDNSNetServices.m
|
||||
BASE_MFILES += \
|
||||
GSMDNSNetServices.m \
|
||||
NSNetServices.m
|
||||
endif
|
||||
|
||||
ifeq ($(GNUSTEP_BASE_HAVE_AVAHI), 1)
|
||||
BASE_MFILES += NSNetServices.m \
|
||||
GSAvahiNetService.m \
|
||||
GSAvahiNetServiceBrowser.m \
|
||||
GSAvahiClient.m \
|
||||
GSAvahiRunLoopIntegration.m
|
||||
BASE_MFILES += \
|
||||
GSAvahiNetService.m \
|
||||
GSAvahiNetServiceBrowser.m \
|
||||
GSAvahiClient.m \
|
||||
GSAvahiRunLoopIntegration.m \
|
||||
NSNetServices.m
|
||||
endif
|
||||
|
||||
ifeq ($(WITH_FFI),libffi)
|
||||
GNU_MFILES += cifframe.m
|
||||
BASE_MFILES += GSFFIInvocation.m
|
||||
GNU_MFILES += cifframe.m
|
||||
BASE_MFILES += GSFFIInvocation.m
|
||||
endif
|
||||
ifeq ($(WITH_FFI),ffcall)
|
||||
GNU_MFILES += callframe.m
|
||||
BASE_MFILES += GSFFCallInvocation.m
|
||||
GNU_MFILES += callframe.m
|
||||
BASE_MFILES += GSFFCallInvocation.m
|
||||
endif
|
||||
|
||||
BASE_OTHER_SRCFILES = \
|
||||
|
|
236
Source/GSEasyHandle.h
Normal file
236
Source/GSEasyHandle.h
Normal 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
|
756
Source/GSEasyHandle.m
Normal file
756
Source/GSEasyHandle.m
Normal file
|
@ -0,0 +1,756 @@
|
|||
#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)
|
||||
{
|
||||
if (!userdata)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
GSEasyHandle *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)
|
||||
{
|
||||
if (!userdata)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
GSEasyHandle *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)
|
||||
{
|
||||
if (!userdata)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
GSEasyHandle *handle = (GSEasyHandle*)userdata;
|
||||
double length;
|
||||
|
||||
[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)
|
||||
{
|
||||
if (!userdata)
|
||||
{
|
||||
return CURL_SEEKFUNC_FAIL;
|
||||
}
|
||||
|
||||
GSEasyHandle *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)
|
||||
{
|
||||
if (!userptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (CURLINFO_SSL_DATA_OUT == type || CURLINFO_SSL_DATA_IN == type)
|
||||
{
|
||||
return 0; // Don't log encrypted data here
|
||||
}
|
||||
|
||||
NSURLSessionTask *task = (NSURLSessionTask*)userptr;
|
||||
NSString *text = @"";
|
||||
NSURLRequest *o = [task originalRequest];
|
||||
NSURLRequest *r = [task currentRequest];
|
||||
id<GSLogDelegate> 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, [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]))
|
||||
{
|
||||
_rawHandle = curl_easy_init();
|
||||
_delegate = delegate;
|
||||
|
||||
char *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);
|
||||
DESTROY(_timeoutTimer);
|
||||
DESTROY(_URL);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (CURL*) rawHandle
|
||||
{
|
||||
return _rawHandle;
|
||||
}
|
||||
|
||||
- (char*) errorBuffer
|
||||
{
|
||||
return _errorBuffer;
|
||||
}
|
||||
|
||||
- (GSTimeoutSource*) timeoutTimer
|
||||
{
|
||||
return _timeoutTimer;
|
||||
}
|
||||
|
||||
- (void) setTimeoutTimer: (GSTimeoutSource*)timer
|
||||
{
|
||||
ASSIGN(_timeoutTimer, timer);
|
||||
}
|
||||
|
||||
- (NSURL*) URL
|
||||
{
|
||||
return _URL;
|
||||
}
|
||||
|
||||
- (void) transferCompletedWithError: (NSError*)error
|
||||
{
|
||||
[_delegate transferCompletedWithError: error];
|
||||
}
|
||||
|
||||
- (void) resetTimer
|
||||
{
|
||||
// simply create a new timer with the same queue, timeout and handler
|
||||
// this must cancel the old handler and reset the timer
|
||||
DESTROY(_timeoutTimer);
|
||||
_timeoutTimer = [[GSTimeoutSource alloc] initWithQueue: [_timeoutTimer queue]
|
||||
milliseconds: [_timeoutTimer milliseconds]
|
||||
handler: [_timeoutTimer handler]];
|
||||
}
|
||||
|
||||
- (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_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, self));
|
||||
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 = nil;
|
||||
|
||||
if (0 == port)
|
||||
{
|
||||
value = [NSString stringWithFormat: @"%@::%@", originHost, host];
|
||||
}
|
||||
else
|
||||
{
|
||||
value = [NSString stringWithFormat: @"%@:%lu:%@",
|
||||
originHost, port, host];
|
||||
}
|
||||
|
||||
struct curl_slist *connect_to = NULL;
|
||||
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
|
|
@ -108,6 +108,7 @@ static NSString *httpVersion = @"1.1";
|
|||
BOOL debug;
|
||||
BOOL keepalive;
|
||||
BOOL returnAll;
|
||||
id<GSLogDelegate> ioDelegate;
|
||||
unsigned char challenged;
|
||||
NSFileHandle *sock;
|
||||
NSTimeInterval cacheAge;
|
||||
|
@ -135,6 +136,7 @@ static NSString *httpVersion = @"1.1";
|
|||
}
|
||||
+ (void) setMaxCached: (NSUInteger)limit;
|
||||
- (void) _tryLoadInBackground: (NSURL*)fromURL;
|
||||
- (id<GSLogDelegate>) setDebugLogDelegate: (id<GSLogDelegate>)d;
|
||||
@end
|
||||
|
||||
/**
|
||||
|
@ -601,7 +603,15 @@ debugWrite(GSHTTPURLHandle *handle, NSData *data)
|
|||
/*
|
||||
* Send request to server.
|
||||
*/
|
||||
if (YES == debug) debugWrite(self, buf);
|
||||
if (debug)
|
||||
{
|
||||
if (NO == [ioDelegate putBytes: [buf bytes]
|
||||
ofLength: [buf length]
|
||||
byHandle: self])
|
||||
{
|
||||
debugWrite(self, buf);
|
||||
}
|
||||
}
|
||||
[sock writeInBackgroundAndNotify: buf];
|
||||
RELEASE(buf);
|
||||
RELEASE(s);
|
||||
|
@ -621,8 +631,16 @@ debugWrite(GSHTTPURLHandle *handle, NSData *data)
|
|||
if (debug)
|
||||
NSLog(@"%@ %p %s", NSStringFromSelector(_cmd), self, keepalive?"K":"");
|
||||
d = [dict objectForKey: NSFileHandleNotificationDataItem];
|
||||
if (YES == debug) debugRead(self, d);
|
||||
readCount = [d length];
|
||||
if (debug)
|
||||
{
|
||||
if (NO == [ioDelegate getBytes: [d bytes]
|
||||
ofLength: readCount
|
||||
byHandle: self])
|
||||
{
|
||||
debugRead(self, d);
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionState == idle)
|
||||
{
|
||||
|
@ -631,11 +649,32 @@ debugWrite(GSHTTPURLHandle *handle, NSData *data)
|
|||
* it should just be the connection being closed by the other
|
||||
* end because of a timeout etc.
|
||||
*/
|
||||
if (YES == debug && [d length] != 0)
|
||||
if (debug)
|
||||
{
|
||||
NSLog(@"%@ %p %s Unexpected data (%*.*s) from remote!",
|
||||
NSStringFromSelector(_cmd), self, keepalive?"K":"",
|
||||
(int)[d length], (int)[d length], [d bytes]);
|
||||
NSUInteger length = [d length];
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
if (nil == ioDelegate)
|
||||
{
|
||||
NSLog(@"%@ %p %s Unexpected data (%*.*s) from remote!",
|
||||
NSStringFromSelector(_cmd), self, keepalive?"K":"",
|
||||
(int)[d length], (int)[d length], (char*)[d bytes]);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"%@ %p %s Unexpected data from remote!",
|
||||
NSStringFromSelector(_cmd), self, keepalive?"K":"");
|
||||
if (NO == [ioDelegate getBytes: [d bytes]
|
||||
ofLength: length
|
||||
byHandle: self])
|
||||
{
|
||||
NSLog(@"%@ %p %s (%*.*s)",
|
||||
NSStringFromSelector(_cmd), self, keepalive?"K":"",
|
||||
(int)[d length], (int)[d length], (char*)[d bytes]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[nc removeObserver: self name: nil object: sock];
|
||||
[sock closeFile];
|
||||
|
@ -643,7 +682,7 @@ debugWrite(GSHTTPURLHandle *handle, NSData *data)
|
|||
}
|
||||
else if ([parser parse: d] == NO && [parser isComplete] == NO)
|
||||
{
|
||||
if (YES == debug)
|
||||
if (debug)
|
||||
{
|
||||
NSLog(@"HTTP parse failure - %@", parser);
|
||||
}
|
||||
|
@ -874,7 +913,7 @@ debugWrite(GSHTTPURLHandle *handle, NSData *data)
|
|||
* lost in the network or the remote end received it and
|
||||
* the response was lost.
|
||||
*/
|
||||
if (YES == debug)
|
||||
if (debug)
|
||||
{
|
||||
NSLog(@"HTTP response not received - %@", parser);
|
||||
}
|
||||
|
@ -906,7 +945,15 @@ debugWrite(GSHTTPURLHandle *handle, NSData *data)
|
|||
NSLog(@"%@ %p %s", NSStringFromSelector(_cmd), self, keepalive?"K":"");
|
||||
}
|
||||
d = [dict objectForKey: NSFileHandleNotificationDataItem];
|
||||
if (YES == debug) debugRead(self, d);
|
||||
if (debug)
|
||||
{
|
||||
if (NO == [ioDelegate getBytes: [d bytes]
|
||||
ofLength: [d length]
|
||||
byHandle: self])
|
||||
{
|
||||
debugRead(self, d);
|
||||
}
|
||||
}
|
||||
|
||||
if ([d length] > 0)
|
||||
{
|
||||
|
@ -1071,7 +1118,15 @@ debugWrite(GSHTTPURLHandle *handle, NSData *data)
|
|||
object: sock];
|
||||
|
||||
buf = [cmd dataUsingEncoding: NSASCIIStringEncoding];
|
||||
if (YES == debug) debugWrite(self, buf);
|
||||
if (debug)
|
||||
{
|
||||
if (NO == [ioDelegate putBytes: [buf bytes]
|
||||
ofLength: [buf length]
|
||||
byHandle: self])
|
||||
{
|
||||
debugWrite(self, buf);
|
||||
}
|
||||
}
|
||||
[sock writeInBackgroundAndNotify: buf];
|
||||
|
||||
when = [NSDate alloc];
|
||||
|
@ -1365,6 +1420,16 @@ debugWrite(GSHTTPURLHandle *handle, NSData *data)
|
|||
return old;
|
||||
}
|
||||
|
||||
- (id<GSLogDelegate>) setDebugLogDelegate: (id<GSLogDelegate>)d
|
||||
{
|
||||
id<GSLogDelegate> old = ioDelegate;
|
||||
|
||||
NSAssert(nil == d || [d conformsToProtocol: @protocol(GSLogDelegate)],
|
||||
NSInvalidArgumentException);
|
||||
ioDelegate = d;
|
||||
return old;
|
||||
}
|
||||
|
||||
- (void) setReturnAll: (BOOL)flag
|
||||
{
|
||||
returnAll = flag;
|
||||
|
|
9
Source/GSHTTPURLProtocol.h
Normal file
9
Source/GSHTTPURLProtocol.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#ifndef INCLUDED_GSHTTPURLPROTOCOL_H
|
||||
#define INCLUDED_GSHTTPURLPROTOCOL_H
|
||||
|
||||
#import "GSNativeProtocol.h"
|
||||
|
||||
@interface GSHTTPURLProtocol : GSNativeProtocol
|
||||
@end
|
||||
|
||||
#endif
|
1064
Source/GSHTTPURLProtocol.m
Normal file
1064
Source/GSHTTPURLProtocol.m
Normal file
File diff suppressed because it is too large
Load diff
91
Source/GSMultiHandle.h
Normal file
91
Source/GSMultiHandle.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
#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;
|
||||
- (void) createReadSourceWithSocket: (curl_socket_t)socket
|
||||
queue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler;
|
||||
- (void) createWriteSourceWithSocket: (curl_socket_t)socket
|
||||
queue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler;
|
||||
|
||||
+ (instancetype) from: (void*)socketSourcePtr;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
514
Source/GSMultiHandle.m
Normal file
514
Source/GSMultiHandle.m
Normal file
|
@ -0,0 +1,514 @@
|
|||
#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"
|
||||
|
||||
@interface GSMultiHandle ()
|
||||
- (void) performActionForSocket: (int)socket;
|
||||
- (void) readAndWriteAvailableDataOnSocket: (int)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;
|
||||
|
||||
DESTROY(_timeoutSource);
|
||||
|
||||
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)
|
||||
{
|
||||
DESTROY(_timeoutSource);
|
||||
}
|
||||
else if (0 == value)
|
||||
{
|
||||
DESTROY(_timeoutSource);
|
||||
dispatch_async(_queue,
|
||||
^{
|
||||
[self timeoutTimerFired];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nil == _timeoutSource || value != [_timeoutSource milliseconds])
|
||||
{
|
||||
DESTROY(_timeoutSource);
|
||||
_timeoutSource = [[GSTimeoutSource alloc] initWithQueue: _queue
|
||||
milliseconds: value
|
||||
handler: ^{
|
||||
[self timeoutTimerFired];
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) performActionForSocket: (int)socket
|
||||
{
|
||||
[self readAndWriteAvailableDataOnSocket: socket];
|
||||
}
|
||||
|
||||
- (void)timeoutTimerFired
|
||||
{
|
||||
[self readAndWriteAvailableDataOnSocket: CURL_SOCKET_TIMEOUT];
|
||||
}
|
||||
|
||||
- (void) readAndWriteAvailableDataOnSocket: (int)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}];
|
||||
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);
|
||||
}
|
||||
|
||||
if (nil != socketSources)
|
||||
{
|
||||
[socketSources createSourcesWithAction: action
|
||||
socket: socket
|
||||
queue: _queue
|
||||
handler: ^{
|
||||
[self performActionForSocket: 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 GSSocketRegisterActionTypeNone:
|
||||
return false;
|
||||
case GSSocketRegisterActionTypeRegisterRead:
|
||||
return true;
|
||||
case GSSocketRegisterActionTypeRegisterWrite:
|
||||
return false;
|
||||
case GSSocketRegisterActionTypeRegisterReadAndWrite:
|
||||
return true;
|
||||
case GSSocketRegisterActionTypeUnregister:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) needsWriteSource
|
||||
{
|
||||
switch (self.type)
|
||||
{
|
||||
case GSSocketRegisterActionTypeNone:
|
||||
return false;
|
||||
case GSSocketRegisterActionTypeRegisterRead:
|
||||
return false;
|
||||
case GSSocketRegisterActionTypeRegisterWrite:
|
||||
return true;
|
||||
case GSSocketRegisterActionTypeRegisterReadAndWrite:
|
||||
return true;
|
||||
case GSSocketRegisterActionTypeUnregister:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
- (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 ([action needsReadSource])
|
||||
{
|
||||
[self createReadSourceWithSocket: socket queue: queue handler: handler];
|
||||
}
|
||||
|
||||
if ([action needsWriteSource])
|
||||
{
|
||||
[self createWriteSourceWithSocket: socket queue: queue handler: handler];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) createReadSourceWithSocket: (curl_socket_t)socket
|
||||
queue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler
|
||||
{
|
||||
dispatch_source_t s;
|
||||
|
||||
if (_readSource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket, 0, queue);
|
||||
dispatch_source_set_event_handler(s, handler);
|
||||
_readSource = s;
|
||||
dispatch_resume(s);
|
||||
}
|
||||
|
||||
- (void) createWriteSourceWithSocket: (curl_socket_t)socket
|
||||
queue: (dispatch_queue_t)queue
|
||||
handler: (dispatch_block_t)handler
|
||||
{
|
||||
dispatch_source_t s;
|
||||
|
||||
if (_writeSource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket, 0, queue);
|
||||
dispatch_source_set_event_handler(s, handler);
|
||||
_writeSource = s;
|
||||
dispatch_resume(s);
|
||||
}
|
||||
|
||||
+ (instancetype) from: (void*)socketSourcePtr
|
||||
{
|
||||
if (!socketSourcePtr)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (GSSocketSources*)socketSourcePtr;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
91
Source/GSNativeProtocol.h
Normal file
91
Source/GSNativeProtocol.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
#ifndef INCLUDED_GSNATIVEPROTOCOL_H
|
||||
#define INCLUDED_GSNATIVEPROTOCOL_H
|
||||
|
||||
#import "GSEasyHandle.h"
|
||||
#import "Foundation/NSURLProtocol.h"
|
||||
|
||||
@class GSTransferState;
|
||||
|
||||
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
|
768
Source/GSNativeProtocol.m
Normal file
768
Source/GSNativeProtocol.m
Normal file
|
@ -0,0 +1,768 @@
|
|||
#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"
|
||||
|
||||
|
||||
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 (Internal)
|
||||
|
||||
- (void) removeHandle: (GSEasyHandle*)handle;
|
||||
|
||||
- (void) addHandle: (GSEasyHandle*)handle;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLSession (Internal)
|
||||
|
||||
- (void) removeHandle: (GSEasyHandle*)handle
|
||||
{
|
||||
[_multiHandle removeHandle: handle];
|
||||
}
|
||||
|
||||
- (void) addHandle: (GSEasyHandle*)handle
|
||||
{
|
||||
[_multiHandle addHandle: handle];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface NSURLSessionTask (Internal)
|
||||
|
||||
- (void) setCurrentRequest: (NSURLRequest*)request;
|
||||
|
||||
- (dispatch_queue_t) workQueue;
|
||||
|
||||
- (NSUInteger) suspendCount;
|
||||
|
||||
- (void) getBodyWithCompletion: (void (^)(GSURLSessionTaskBody *body))completion;
|
||||
|
||||
- (void) setKnownBody: (GSURLSessionTaskBody*)body;
|
||||
|
||||
- (void) setError: (NSError*)error;
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLSessionTask (Internal)
|
||||
|
||||
- (void) setCurrentRequest: (NSURLRequest*)request
|
||||
{
|
||||
ASSIGN(_currentRequest, request);
|
||||
}
|
||||
|
||||
- (dispatch_queue_t) workQueue
|
||||
{
|
||||
return _workQueue;
|
||||
}
|
||||
|
||||
- (NSUInteger) suspendCount
|
||||
{
|
||||
return _suspendCount;
|
||||
}
|
||||
|
||||
- (void) getBodyWithCompletion: (void (^)(GSURLSessionTaskBody *body))completion
|
||||
{
|
||||
if (nil != _knownBody)
|
||||
{
|
||||
completion(_knownBody);
|
||||
return;
|
||||
};
|
||||
|
||||
GSURLSessionTaskBody *body = AUTORELEASE([[GSURLSessionTaskBody alloc] init]);
|
||||
completion(body);
|
||||
}
|
||||
|
||||
- (void) setKnownBody: (GSURLSessionTaskBody*)body
|
||||
{
|
||||
ASSIGN(_knownBody, body);
|
||||
}
|
||||
|
||||
- (void) setError: (NSError*)error
|
||||
{
|
||||
ASSIGN(_error, error);
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
- (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 delegate need.
|
||||
- (GSDataDrain*) createTransferBodyDataDrain
|
||||
{
|
||||
NSURLSession *s = [[self task] session];
|
||||
GSDataDrain *dd = AUTORELEASE([[GSDataDrain alloc] init]);
|
||||
|
||||
if (nil != [s delegate])
|
||||
{
|
||||
// Data will be forwarded to the delegate as we receive it, we don't
|
||||
// need to do anything about it.
|
||||
[dd setType: GSDataDrainTypeIgnore];
|
||||
return dd;
|
||||
}
|
||||
else
|
||||
{
|
||||
[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
|
||||
{
|
||||
NSURLSessionTask *task;
|
||||
id<NSURLSessionDelegate> delegate;
|
||||
|
||||
task = [self task];
|
||||
|
||||
NSAssert(nil != task, @"Cannot notify");
|
||||
|
||||
delegate = [[task session] delegate];
|
||||
if (nil != delegate
|
||||
&& [task isKindOfClass: [NSURLSessionDataTask class]]
|
||||
&& [delegate respondsToSelector: @selector(URLSession:dataTask:didReceiveData:)])
|
||||
{
|
||||
id<NSURLSessionDataDelegate> dataDelegate;
|
||||
NSURLSessionDataTask *dataTask;
|
||||
NSURLSession *session;
|
||||
|
||||
session = [task session];
|
||||
NSAssert(nil != session, @"Missing session");
|
||||
dataDelegate = (id<NSURLSessionDataDelegate>)delegate;
|
||||
dataTask = (NSURLSessionDataTask*)task;
|
||||
[[session delegateQueue] addOperationWithBlock:
|
||||
^{
|
||||
[dataDelegate URLSession: session
|
||||
dataTask: dataTask
|
||||
didReceiveData: data];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (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];
|
||||
|
||||
// because we deregister the task with the session on internalState being set
|
||||
// to taskCompleted we need to do the latter after the delegate/handler was
|
||||
// notified/invoked
|
||||
bodyDataDrain = [_transferState bodyDataDrain];
|
||||
if (GSDataDrainInMemory == [bodyDataDrain type])
|
||||
{
|
||||
NSData *data;
|
||||
|
||||
if (nil != [bodyDataDrain data])
|
||||
{
|
||||
data = [NSData dataWithData: [bodyDataDrain data]];
|
||||
}
|
||||
else
|
||||
{
|
||||
data = [NSData data];
|
||||
}
|
||||
|
||||
if ([client respondsToSelector: @selector(URLProtocol:didLoadData:)])
|
||||
{
|
||||
[client URLProtocol: self didLoadData: data];
|
||||
}
|
||||
[self setInternalState: GSNativeProtocolInternalStateTaskCompleted];
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -2180,11 +2180,6 @@ retrieve_callback(gnutls_session_t session,
|
|||
[str appendFormat: _(@"- Certificate Type: %s\n"), tmp];
|
||||
}
|
||||
|
||||
/* print the compression algorithm (if any)
|
||||
*/
|
||||
tmp = gnutls_compression_get_name(gnutls_compression_get(session));
|
||||
[str appendFormat: _(@"- Compression: %s\n"), tmp];
|
||||
|
||||
/* print the name of the cipher used.
|
||||
* eg 3DES.
|
||||
*/
|
||||
|
|
30
Source/GSTaskRegistry.h
Normal file
30
Source/GSTaskRegistry.h
Normal 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
98
Source/GSTaskRegistry.m
Normal 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
32
Source/GSTimeoutSource.h
Normal 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 _rawSource;
|
||||
NSInteger _milliseconds;
|
||||
dispatch_queue_t _queue;
|
||||
dispatch_block_t _handler;
|
||||
}
|
||||
|
||||
- (NSInteger) milliseconds;
|
||||
|
||||
- (dispatch_queue_t) queue;
|
||||
|
||||
- (dispatch_block_t) handler;
|
||||
|
||||
- (instancetype) initWithQueue: (dispatch_queue_t)queue
|
||||
milliseconds: (NSInteger)milliseconds
|
||||
handler: (dispatch_block_t)handler;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
48
Source/GSTimeoutSource.m
Normal file
48
Source/GSTimeoutSource.m
Normal file
|
@ -0,0 +1,48 @@
|
|||
#import "GSTimeoutSource.h"
|
||||
|
||||
@implementation GSTimeoutSource
|
||||
|
||||
- (instancetype) initWithQueue: (dispatch_queue_t)queue
|
||||
milliseconds: (NSInteger)milliseconds
|
||||
handler: (dispatch_block_t)handler
|
||||
{
|
||||
if (nil != (self = [super init]))
|
||||
{
|
||||
_queue = queue;
|
||||
_handler = handler;
|
||||
_milliseconds = milliseconds;
|
||||
_rawSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _queue);
|
||||
|
||||
uint64_t delay = MAX(1, milliseconds - 1);
|
||||
|
||||
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_MSEC);
|
||||
|
||||
dispatch_source_set_timer(_rawSource, start, delay * NSEC_PER_MSEC, _milliseconds == 1 ? 1 * NSEC_PER_USEC : 1 * NSEC_PER_MSEC);
|
||||
dispatch_source_set_event_handler(_rawSource, _handler);
|
||||
dispatch_resume(_rawSource);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
dispatch_source_cancel(_rawSource);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSInteger) milliseconds
|
||||
{
|
||||
return _milliseconds;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t) queue
|
||||
{
|
||||
return _queue;
|
||||
}
|
||||
|
||||
- (dispatch_block_t) handler
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
|
||||
@end
|
142
Source/GSTransferState.h
Normal file
142
Source/GSTransferState.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
#ifndef INCLUDED_GSTRANSFERTSTATE_H
|
||||
#define INCLUDED_GSTRANSFERTSTATE_H
|
||||
|
||||
#import "GSURLSessionTaskBodySource.h"
|
||||
|
||||
|
||||
@class GSURLSessionTaskBodySource;
|
||||
@class NSArray;
|
||||
@class NSData;
|
||||
@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;
|
||||
NSData *_data;
|
||||
NSURL *_fileURL;
|
||||
NSFileHandle *_fileHandle;
|
||||
}
|
||||
|
||||
- (GSDataDrainType) type;
|
||||
- (void) setType: (GSDataDrainType)type;
|
||||
|
||||
- (NSData*) data;
|
||||
- (void) setData: (NSData*)data;
|
||||
|
||||
- (NSURL*) fileURL;
|
||||
- (void) setFileURL: (NSURL*)url;
|
||||
|
||||
- (NSFileHandle*) fileHandle;
|
||||
- (void) setFileHandle: (NSFileHandle*)handle;
|
||||
|
||||
@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
487
Source/GSTransferState.m
Normal 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"
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
- (NSData*) data
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
- (void) setData: (NSData*)data
|
||||
{
|
||||
ASSIGN(_data, data);
|
||||
}
|
||||
|
||||
- (NSURL*) fileURL
|
||||
{
|
||||
return _fileURL;
|
||||
}
|
||||
|
||||
- (void) setFileURL: (NSURL*)url
|
||||
{
|
||||
ASSIGN(_fileURL, url);
|
||||
}
|
||||
|
||||
- (NSFileHandle*) fileHandle
|
||||
{
|
||||
return _fileHandle;
|
||||
}
|
||||
|
||||
- (void) setFileHandle: (NSFileHandle*)handle
|
||||
{
|
||||
ASSIGN(_fileHandle, handle);
|
||||
}
|
||||
|
||||
@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:
|
||||
{
|
||||
NSMutableData *data;
|
||||
GSDataDrain *dataDrain;
|
||||
GSTransferState *ts;
|
||||
|
||||
data = [_bodyDataDrain data] ?
|
||||
AUTORELEASE([[_bodyDataDrain data] mutableCopy])
|
||||
: [NSMutableData data];
|
||||
|
||||
[data appendData: bodyData];
|
||||
dataDrain = AUTORELEASE([[GSDataDrain alloc] init]);
|
||||
[dataDrain setType: GSDataDrainInMemory];
|
||||
[dataDrain setData: data];
|
||||
|
||||
ts = [[GSTransferState alloc] initWithURL: _url
|
||||
parsedResponseHeader: _parsedResponseHeader
|
||||
response: _response
|
||||
bodySource: _requestBodySource
|
||||
bodyDataDrain: dataDrain];
|
||||
return AUTORELEASE(ts);
|
||||
}
|
||||
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
|
|
@ -58,6 +58,7 @@
|
|||
|
||||
@interface NSURLRequest (Private)
|
||||
- (BOOL) _debug;
|
||||
- (id<GSLogDelegate>) _debugLogDelegate;
|
||||
- (id) _propertyForKey: (NSString*)key;
|
||||
- (void) _setProperty: (id)value forKey: (NSString*)key;
|
||||
@end
|
||||
|
@ -72,11 +73,23 @@
|
|||
|
||||
|
||||
@interface NSURLProtocol (Private)
|
||||
+ (Class) _classToHandleRequest:(NSURLRequest *)request;
|
||||
+ (Class) _classToHandleRequest: (NSURLRequest *)request;
|
||||
+ (id<NSURLProtocolClient>) _ProtocolClient;
|
||||
@end
|
||||
|
||||
/*
|
||||
* Internal class for handling HTTP authentication
|
||||
|
||||
/* Internal class for handling HTTP protocol client messages
|
||||
*/
|
||||
@interface _NSURLProtocolClient : NSObject <NSURLProtocolClient>
|
||||
{
|
||||
NSURLRequestCachePolicy _cachePolicy;
|
||||
NSMutableArray *_cacheableData;
|
||||
NSURLResponse *_cacheableResponse;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
/* Internal class for handling HTTP authentication
|
||||
*/
|
||||
@class NSLock;
|
||||
@interface GSHTTPAuthentication : NSObject
|
||||
|
|
46
Source/GSURLSessionTaskBody.h
Normal file
46
Source/GSURLSessionTaskBody.h
Normal 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
|
111
Source/GSURLSessionTaskBody.m
Normal file
111
Source/GSURLSessionTaskBody.m
Normal 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
|
52
Source/GSURLSessionTaskBodySource.h
Normal file
52
Source/GSURLSessionTaskBodySource.h
Normal 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
|
152
Source/GSURLSessionTaskBodySource.m
Normal file
152
Source/GSURLSessionTaskBodySource.m
Normal 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
|
|
@ -62,7 +62,7 @@ acquireExistingMemory(const void *item,
|
|||
static NSString*
|
||||
describeString(const void *item)
|
||||
{
|
||||
return [NSString stringWithFormat: @"%s", item];
|
||||
return [NSString stringWithFormat: @"%s", (char*)item];
|
||||
}
|
||||
|
||||
static NSString*
|
||||
|
|
|
@ -687,6 +687,14 @@ recover(int sig)
|
|||
siglongjmp(jbuf()->buf, 1);
|
||||
}
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wframe-address"
|
||||
#else
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wframe-address"
|
||||
#endif
|
||||
|
||||
void *
|
||||
NSFrameAddress(NSUInteger offset)
|
||||
{
|
||||
|
@ -873,6 +881,12 @@ NSReturnAddress(NSUInteger offset)
|
|||
return env->addr;
|
||||
}
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#else
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
unsigned
|
||||
GSPrivateReturnAddresses(NSUInteger **returns)
|
||||
{
|
||||
|
|
|
@ -1412,7 +1412,7 @@ typedef struct {
|
|||
|
||||
desc = [NSString stringWithFormat:
|
||||
@"<%s %p file name %s>",
|
||||
GSClassNameFromObject(self), self, [name bytes]];
|
||||
GSClassNameFromObject(self), self, (char*)[name bytes]];
|
||||
return desc;
|
||||
}
|
||||
|
||||
|
|
|
@ -1469,7 +1469,7 @@ static Class tcpPortClass;
|
|||
* Look up an existing NSSocketPort given a host and number
|
||||
*/
|
||||
+ (NSSocketPort*) existingPortWithNumber: (uint16_t)number
|
||||
onHost: (NSHost*)aHost
|
||||
onHost: (NSHost*)aHost
|
||||
{
|
||||
NSSocketPort *port = nil;
|
||||
NSMapTable *thePorts;
|
||||
|
@ -1763,6 +1763,9 @@ static Class tcpPortClass;
|
|||
{
|
||||
NSDebugMLLog(@"NSPort",
|
||||
@"NSSocketPort 0x%"PRIxPTR" finalized", (NSUInteger)self);
|
||||
M_LOCK(tcpPortLock);
|
||||
NSMapRemove(tcpPortMap, (void*)(uintptr_t)portNum);
|
||||
M_UNLOCK(tcpPortLock);
|
||||
[self invalidate];
|
||||
if (handles != 0)
|
||||
{
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
|
||||
#import "common.h"
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
#import <Foundation/NSURLSession.h>
|
||||
#endif
|
||||
|
||||
#define EXPOSE_NSURLCache_IVARS 1
|
||||
#import "GSURLPrivate.h"
|
||||
|
||||
|
@ -215,3 +219,27 @@ static NSURLCache *shared = nil;
|
|||
|
||||
@end
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
@implementation NSURLCache (NSURLSessionTaskAdditions)
|
||||
|
||||
- (void) storeCachedResponse: (NSCachedURLResponse*)cachedResponse
|
||||
forDataTask: (NSURLSessionDataTask*)dataTask
|
||||
{
|
||||
[self storeCachedResponse: cachedResponse
|
||||
forRequest: [dataTask currentRequest]];
|
||||
}
|
||||
|
||||
- (NSCachedURLResponse*) cachedResponseForDataTask:
|
||||
(NSURLSessionDataTask*)dataTask
|
||||
{
|
||||
return [self cachedResponseForRequest: [dataTask currentRequest]];
|
||||
}
|
||||
|
||||
- (void) removeCachedResponseForDataTask: (NSURLSessionDataTask*)dataTask
|
||||
{
|
||||
[self removeCachedResponseForRequest: [dataTask currentRequest]];
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
|
|
@ -169,6 +169,11 @@ typedef struct
|
|||
DESTROY(this->_delegate);
|
||||
}
|
||||
|
||||
- (id) delegate
|
||||
{
|
||||
return this->_delegate;
|
||||
}
|
||||
|
||||
- (void) scheduleInRunLoop: (NSRunLoop *)aRunLoop
|
||||
forMode: (NSRunLoopMode)mode
|
||||
{
|
||||
|
@ -209,7 +214,9 @@ typedef struct
|
|||
}
|
||||
}
|
||||
|
||||
- (id) initWithRequest: (NSURLRequest *)request delegate: (id)delegate startImmediately: (BOOL)startImmediately
|
||||
- (id) initWithRequest: (NSURLRequest *)request
|
||||
delegate: (id)delegate
|
||||
startImmediately: (BOOL)startImmediately
|
||||
{
|
||||
if ((self = [super init]) != nil)
|
||||
{
|
||||
|
|
|
@ -25,13 +25,19 @@
|
|||
|
||||
#import "common.h"
|
||||
|
||||
#define EXPOSE_NSURLProtocol_IVARS 1
|
||||
//#define EXPOSE_NSURLProtocol_IVARS 1
|
||||
#import "Foundation/NSError.h"
|
||||
#import "Foundation/NSHost.h"
|
||||
#import "Foundation/NSNotification.h"
|
||||
#import "Foundation/NSRunLoop.h"
|
||||
#import "Foundation/NSValue.h"
|
||||
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
#import "Foundation/NSURLSession.h"
|
||||
#else
|
||||
@class NSURLSessionTask;
|
||||
#endif
|
||||
|
||||
#import "GSPrivate.h"
|
||||
#import "GSURLPrivate.h"
|
||||
#import "GNUstepBase/GSMime.h"
|
||||
|
@ -358,6 +364,7 @@ static NSLock *pairLock = nil;
|
|||
NSURLAuthenticationChallenge *_challenge;
|
||||
NSURLCredential *_credential;
|
||||
NSHTTPURLResponse *_response;
|
||||
id<GSLogDelegate> _logDelegate;
|
||||
}
|
||||
@end
|
||||
|
||||
|
@ -367,27 +374,6 @@ static NSLock *pairLock = nil;
|
|||
@interface _NSDataURLProtocol : NSURLProtocol
|
||||
@end
|
||||
|
||||
|
||||
// Internal data storage
|
||||
typedef struct {
|
||||
NSInputStream *input;
|
||||
NSOutputStream *output;
|
||||
NSCachedURLResponse *cachedResponse;
|
||||
id <NSURLProtocolClient> client;
|
||||
NSURLRequest *request;
|
||||
NSString *in;
|
||||
NSString *out;
|
||||
#if USE_ZLIB
|
||||
z_stream z; // context for decompress
|
||||
BOOL compressing; // are we compressing?
|
||||
BOOL decompressing; // are we decompressing?
|
||||
NSData *compressed; // only partially decompressed
|
||||
#endif
|
||||
} Internal;
|
||||
|
||||
#define this ((Internal*)(self->_NSURLProtocolInternal))
|
||||
#define inst ((Internal*)(o->_NSURLProtocolInternal))
|
||||
|
||||
static NSMutableArray *registered = nil;
|
||||
static NSLock *regLock = nil;
|
||||
static Class abstractClass = nil;
|
||||
|
@ -419,8 +405,44 @@ static NSURLProtocol *placeholder = nil;
|
|||
}
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation NSURLProtocol
|
||||
|
||||
#if GS_NONFRAGILE
|
||||
{
|
||||
@protected
|
||||
#else
|
||||
// Internal data storage
|
||||
typedef struct {
|
||||
#endif
|
||||
|
||||
NSInputStream *input;
|
||||
NSOutputStream *output;
|
||||
NSCachedURLResponse *cachedResponse;
|
||||
id <NSURLProtocolClient> client;
|
||||
NSURLRequest *request;
|
||||
NSURLSessionTask *task;
|
||||
NSString *in;
|
||||
NSString *out;
|
||||
#if USE_ZLIB
|
||||
z_stream z; // context for decompress
|
||||
BOOL compressing; // are we compressing?
|
||||
BOOL decompressing; // are we decompressing?
|
||||
NSData *compressed; // only partially decompressed
|
||||
#endif
|
||||
|
||||
#if GS_NONFRAGILE
|
||||
}
|
||||
#define this self
|
||||
#define inst o
|
||||
#else
|
||||
} Internal;
|
||||
#define this ((Internal*)(self->_NSURLProtocolInternal))
|
||||
#define inst ((Internal*)(o->_NSURLProtocolInternal))
|
||||
#endif
|
||||
|
||||
|
||||
+ (id) allocWithZone: (NSZone*)z
|
||||
{
|
||||
NSURLProtocol *o;
|
||||
|
@ -508,7 +530,6 @@ static NSURLProtocol *placeholder = nil;
|
|||
{
|
||||
if (this != 0)
|
||||
{
|
||||
[self stopLoading];
|
||||
if (this->input != nil)
|
||||
{
|
||||
[this->input setDelegate: nil];
|
||||
|
@ -526,7 +547,11 @@ static NSURLProtocol *placeholder = nil;
|
|||
}
|
||||
DESTROY(this->cachedResponse);
|
||||
DESTROY(this->request);
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
DESTROY(this->task);
|
||||
#endif
|
||||
DESTROY(this->client);
|
||||
|
||||
#if USE_ZLIB
|
||||
if (this->compressing == YES)
|
||||
{
|
||||
|
@ -538,8 +563,10 @@ static NSURLProtocol *placeholder = nil;
|
|||
}
|
||||
DESTROY(this->compressed);
|
||||
#endif
|
||||
#if !GS_NONFRAGILE
|
||||
NSZoneFree([self zone], this);
|
||||
_NSURLProtocolInternal = 0;
|
||||
#endif
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
@ -554,6 +581,7 @@ static NSURLProtocol *placeholder = nil;
|
|||
{
|
||||
if ((self = [super init]) != nil)
|
||||
{
|
||||
#if !GS_NONFRAGILE
|
||||
Class c = object_getClass(self);
|
||||
|
||||
if (c != abstractClass && c != placeholderClass)
|
||||
|
@ -561,13 +589,14 @@ static NSURLProtocol *placeholder = nil;
|
|||
_NSURLProtocolInternal = NSZoneCalloc([self zone],
|
||||
1, sizeof(Internal));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithRequest: (NSURLRequest *)request
|
||||
cachedResponse: (NSCachedURLResponse *)cachedResponse
|
||||
client: (id <NSURLProtocolClient>)client
|
||||
- (id) initWithRequest: (NSURLRequest*)_request
|
||||
cachedResponse: (NSCachedURLResponse*)_cachedResponse
|
||||
client: (id <NSURLProtocolClient>)_client
|
||||
{
|
||||
Class c = object_getClass(self);
|
||||
|
||||
|
@ -582,31 +611,57 @@ static NSURLProtocol *placeholder = nil;
|
|||
{
|
||||
Class proto = [registered objectAtIndex: count];
|
||||
|
||||
if ([proto canInitWithRequest: request] == YES)
|
||||
if ([proto canInitWithRequest: _request] == YES)
|
||||
{
|
||||
self = [proto alloc];
|
||||
break;
|
||||
}
|
||||
}
|
||||
[regLock unlock];
|
||||
return [self initWithRequest: request
|
||||
cachedResponse: cachedResponse
|
||||
client: client];
|
||||
return [self initWithRequest: _request
|
||||
cachedResponse: _cachedResponse
|
||||
client: _client];
|
||||
}
|
||||
if ((self = [self init]) != nil)
|
||||
{
|
||||
this->request = [request copy];
|
||||
this->cachedResponse = RETAIN(cachedResponse);
|
||||
this->client = RETAIN(client);
|
||||
this->request = [_request copy];
|
||||
this->cachedResponse = RETAIN(_cachedResponse);
|
||||
if (nil == _client)
|
||||
{
|
||||
_client = [[self class] _ProtocolClient];
|
||||
}
|
||||
this->client = RETAIN(_client);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype) initWithTask: (NSURLSessionTask*)_task
|
||||
cachedResponse: (NSCachedURLResponse*)_cachedResponse
|
||||
client: (id<NSURLProtocolClient>)_client
|
||||
{
|
||||
#if GS_HAVE_NSURLSESSION
|
||||
if (nil != (self = [self initWithRequest: [_task currentRequest]
|
||||
cachedResponse: _cachedResponse
|
||||
client: _client]))
|
||||
{
|
||||
ASSIGN(this->task, _task);
|
||||
}
|
||||
#else
|
||||
DESTROY(self);
|
||||
#endif
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSURLRequest *) request
|
||||
{
|
||||
return this->request;
|
||||
}
|
||||
|
||||
- (NSURLSessionTask*) task
|
||||
{
|
||||
return this->task;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSURLProtocol (Debug)
|
||||
|
@ -643,6 +698,13 @@ static NSURLProtocol *placeholder = nil;
|
|||
return protoClass;
|
||||
}
|
||||
|
||||
/* Internal method to return a client to handle callbacks if the protocol
|
||||
* is initialised without one.
|
||||
*/
|
||||
+ (id<NSURLProtocolClient>) _ProtocolClient
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation NSURLProtocol (Subclassing)
|
||||
|
@ -726,7 +788,14 @@ static NSURLProtocol *placeholder = nil;
|
|||
static NSDictionary *methods = nil;
|
||||
|
||||
_debug = GSDebugSet(@"NSURLProtocol");
|
||||
if (YES == [this->request _debug]) _debug = YES;
|
||||
if (YES == [this->request _debug])
|
||||
{
|
||||
_debug = YES;
|
||||
}
|
||||
if (_debug)
|
||||
{
|
||||
_logDelegate = [this->request _debugLogDelegate];
|
||||
}
|
||||
|
||||
if (methods == nil)
|
||||
{
|
||||
|
@ -795,12 +864,12 @@ static NSURLProtocol *placeholder = nil;
|
|||
}
|
||||
else
|
||||
{
|
||||
NSMutableURLRequest *request;
|
||||
NSMutableURLRequest *r;
|
||||
|
||||
request = [[this->request mutableCopy] autorelease];
|
||||
[request setURL: url];
|
||||
r = [[this->request mutableCopy] autorelease];
|
||||
[r setURL: url];
|
||||
[this->client URLProtocol: self
|
||||
wasRedirectedToRequest: request
|
||||
wasRedirectedToRequest: r
|
||||
redirectResponse: nil];
|
||||
}
|
||||
if (NO == _isLoading)
|
||||
|
@ -979,7 +1048,12 @@ static NSURLProtocol *placeholder = nil;
|
|||
}
|
||||
if (_debug)
|
||||
{
|
||||
debugRead(self, readCount, buffer);
|
||||
if (NO == [_logDelegate getBytes: buffer
|
||||
ofLength: readCount
|
||||
byHandle: self])
|
||||
{
|
||||
debugRead(self, readCount, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (_parser == nil)
|
||||
|
@ -991,7 +1065,7 @@ static NSURLProtocol *placeholder = nil;
|
|||
d = [NSData dataWithBytes: buffer length: readCount];
|
||||
if ([_parser parse: d] == NO && (_complete = [_parser isComplete]) == NO)
|
||||
{
|
||||
if (_debug == YES)
|
||||
if (_debug)
|
||||
{
|
||||
NSLog(@"%@ HTTP parse failure - %@", self, _parser);
|
||||
}
|
||||
|
@ -1113,12 +1187,12 @@ static NSURLProtocol *placeholder = nil;
|
|||
}
|
||||
else
|
||||
{
|
||||
NSMutableURLRequest *request;
|
||||
NSMutableURLRequest *r;
|
||||
|
||||
request = [[this->request mutableCopy] autorelease];
|
||||
[request setURL: url];
|
||||
r = AUTORELEASE([this->request mutableCopy]);
|
||||
[r setURL: url];
|
||||
[this->client URLProtocol: self
|
||||
wasRedirectedToRequest: request
|
||||
wasRedirectedToRequest: r
|
||||
redirectResponse: _response];
|
||||
}
|
||||
}
|
||||
|
@ -1335,18 +1409,18 @@ static NSURLProtocol *placeholder = nil;
|
|||
}
|
||||
else
|
||||
{
|
||||
NSMutableURLRequest *request;
|
||||
NSMutableURLRequest *r;
|
||||
|
||||
/* To answer the authentication challenge,
|
||||
* we must retry with a modified request and
|
||||
* with the cached response cleared.
|
||||
*/
|
||||
request = [this->request mutableCopy];
|
||||
[request setValue: auth
|
||||
r = [this->request mutableCopy];
|
||||
[r setValue: auth
|
||||
forHTTPHeaderField: @"Authorization"];
|
||||
[self stopLoading];
|
||||
[this->request release];
|
||||
this->request = request;
|
||||
RELEASE(this->request);
|
||||
this->request = r;
|
||||
DESTROY(this->cachedResponse);
|
||||
[self startLoading];
|
||||
return;
|
||||
|
@ -1426,7 +1500,7 @@ static NSURLProtocol *placeholder = nil;
|
|||
* lost in the network or the remote end received it and
|
||||
* the response was lost.
|
||||
*/
|
||||
if (_debug == YES)
|
||||
if (_debug)
|
||||
{
|
||||
NSLog(@"%@ HTTP response not received - %@", self, _parser);
|
||||
}
|
||||
|
@ -1460,7 +1534,7 @@ static NSURLProtocol *placeholder = nil;
|
|||
return;
|
||||
|
||||
case NSStreamEventOpenCompleted:
|
||||
if (_debug == YES)
|
||||
if (_debug)
|
||||
{
|
||||
NSLog(@"%@ HTTP input stream opened", self);
|
||||
}
|
||||
|
@ -1483,7 +1557,7 @@ static NSURLProtocol *placeholder = nil;
|
|||
NSURL *u;
|
||||
int l;
|
||||
|
||||
if (_debug == YES)
|
||||
if (_debug)
|
||||
{
|
||||
NSLog(@"%@ HTTP output stream opened", self);
|
||||
}
|
||||
|
@ -1624,9 +1698,14 @@ static NSURLProtocol *placeholder = nil;
|
|||
maxLength: len - _writeOffset];
|
||||
if (written > 0)
|
||||
{
|
||||
if (_debug == YES)
|
||||
if (_debug)
|
||||
{
|
||||
debugWrite(self, written, bytes + _writeOffset);
|
||||
if (NO == [_logDelegate putBytes: bytes + _writeOffset
|
||||
ofLength: written
|
||||
byHandle: self])
|
||||
{
|
||||
debugWrite(self, written, bytes + _writeOffset);
|
||||
}
|
||||
}
|
||||
_writeOffset += written;
|
||||
if (_writeOffset >= len)
|
||||
|
@ -1664,7 +1743,7 @@ static NSURLProtocol *placeholder = nil;
|
|||
len = [_body read: buffer maxLength: sizeof(buffer)];
|
||||
if (len < 0)
|
||||
{
|
||||
if (_debug == YES)
|
||||
if (_debug)
|
||||
{
|
||||
NSLog(@"%@ error reading from HTTPBody stream %@",
|
||||
self, [NSError _last]);
|
||||
|
@ -1681,9 +1760,14 @@ static NSURLProtocol *placeholder = nil;
|
|||
written = [this->output write: buffer maxLength: len];
|
||||
if (written > 0)
|
||||
{
|
||||
if (_debug == YES)
|
||||
if (_debug)
|
||||
{
|
||||
debugWrite(self, written, buffer);
|
||||
if (NO == [_logDelegate putBytes: buffer
|
||||
ofLength: written
|
||||
byHandle: self])
|
||||
{
|
||||
debugWrite(self, written, buffer);
|
||||
}
|
||||
}
|
||||
len -= written;
|
||||
if (len > 0)
|
||||
|
|
|
@ -39,6 +39,7 @@ typedef struct {
|
|||
NSMutableDictionary *headers;
|
||||
BOOL shouldHandleCookies;
|
||||
BOOL debug;
|
||||
id<GSLogDelegate> ioDelegate;
|
||||
NSURL *URL;
|
||||
NSURL *mainDocumentURL;
|
||||
NSURLRequestCachePolicy cachePolicy;
|
||||
|
@ -116,6 +117,7 @@ typedef struct {
|
|||
ASSIGN(inst->method, this->method);
|
||||
inst->shouldHandleCookies = this->shouldHandleCookies;
|
||||
inst->debug = this->debug;
|
||||
inst->ioDelegate = this->ioDelegate;
|
||||
inst->headers = [this->headers mutableCopy];
|
||||
}
|
||||
}
|
||||
|
@ -284,6 +286,16 @@ typedef struct {
|
|||
return old;
|
||||
}
|
||||
|
||||
- (id<GSLogDelegate>) setDebugLogDelegate: (id<GSLogDelegate>)d
|
||||
{
|
||||
id<GSLogDelegate> old = this->ioDelegate;
|
||||
|
||||
NSAssert(nil == d || [d conformsToProtocol: @protocol(GSLogDelegate)],
|
||||
NSInvalidArgumentException);
|
||||
this->ioDelegate = d;
|
||||
return old;
|
||||
}
|
||||
|
||||
- (NSTimeInterval) timeoutInterval
|
||||
{
|
||||
return this->timeoutInterval;
|
||||
|
@ -444,6 +456,11 @@ typedef struct {
|
|||
return this->debug;
|
||||
}
|
||||
|
||||
- (id<GSLogDelegate>) _debugLogDelegate
|
||||
{
|
||||
return this->ioDelegate;
|
||||
}
|
||||
|
||||
- (id) _propertyForKey: (NSString*)key
|
||||
{
|
||||
return [this->properties objectForKey: key];
|
||||
|
|
|
@ -221,6 +221,11 @@ typedef struct {
|
|||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString*) description
|
||||
{
|
||||
return [NSString stringWithFormat: @"%@ { URL: %@ } { Status Code: %d, Headers %@ }", [super description], this->URL, this->statusCode, this->headers];
|
||||
}
|
||||
|
||||
- (void) encodeWithCoder: (NSCoder*)aCoder
|
||||
{
|
||||
// FIXME
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -146,15 +146,15 @@
|
|||
}
|
||||
- (pid_t) processIdentifier
|
||||
{
|
||||
return (pid_t)[self notImplemented: _cmd];
|
||||
return (pid_t)(uintptr_t)[self notImplemented: _cmd];
|
||||
}
|
||||
- (uid_t) effectiveUserIdentifier
|
||||
{
|
||||
return (uid_t)[self notImplemented: _cmd];
|
||||
return (uid_t)(uintptr_t)[self notImplemented: _cmd];
|
||||
}
|
||||
- (gid_t) effectiveGroupIdentifier
|
||||
{
|
||||
return (gid_t)[self notImplemented: _cmd];
|
||||
return (gid_t)(uintptr_t)[self notImplemented: _cmd];
|
||||
}
|
||||
@end
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
|
||||
NSString *file,
|
||||
*lengthHeader;
|
||||
NSHost *host = [NSHost hostWithAddress: @"127.0.0.1"];
|
||||
NSHost *host = [NSHost hostWithName: @"localhost"];
|
||||
NSStream *serverStream;
|
||||
int port = [[defs stringForKey: @"Port"] intValue];
|
||||
int lifetime = [[defs stringForKey: @"Lifetime"] intValue];
|
||||
|
|
|
@ -48,7 +48,7 @@ int main()
|
|||
data = [u resourceDataUsingCache: NO];
|
||||
// Get status code
|
||||
str = [u propertyForKey: NSHTTPPropertyStatusCodeKey];
|
||||
PASS([data isEqual: cont], "NSURL chunked test OK");
|
||||
PASS_EQUAL(data, cont, "NSURL chunked test OK");
|
||||
// Wait for server termination
|
||||
[t terminate];
|
||||
[t waitUntilExit];
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* test.
|
||||
*
|
||||
* The test case which the NSURLConnectionTest implements by default is connecting
|
||||
* to the http://127.0.0.1:1234/ whith the HTTP method 'GET'. You can change variable
|
||||
* to the http://localhost:1234/ whith the HTTP method 'GET'. You can change variable
|
||||
* parts of process by supplying a custom dictionary to the method -[setUpTest:]
|
||||
* before the test case is started by a call of the -[startTest:]. The use pattern:
|
||||
* --------------------------------------------------------------------------
|
||||
|
@ -57,7 +57,7 @@
|
|||
* (the instance will be run within a detached thread).
|
||||
* Default: NO
|
||||
* 'Address' - the address of the remote side.
|
||||
* Default: 127.0.0.1
|
||||
* Default: localhost
|
||||
* 'Port' - the port of the remote side.
|
||||
* Default: 1234
|
||||
* 'AuxPort' - the port of the auxilliary remote side (where the connection
|
||||
|
|
|
@ -590,7 +590,7 @@ didReceiveResponse:(NSURLResponse *)response
|
|||
}
|
||||
if (nil == address)
|
||||
{
|
||||
address = @"127.0.0.1";
|
||||
address = @"localhost";
|
||||
}
|
||||
if (nil == port)
|
||||
{
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
@implementation TestCase
|
||||
|
||||
- (id)init
|
||||
- (id) init
|
||||
{
|
||||
if((self = [super init]) != nil)
|
||||
if ((self = [super init]) != nil)
|
||||
{
|
||||
[self resetFlags];
|
||||
[self setReferenceFlags: NORESULTS];
|
||||
|
@ -19,7 +19,7 @@
|
|||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
- (void) dealloc
|
||||
{
|
||||
[self tearDownTest: _extra];
|
||||
|
||||
|
@ -27,47 +27,47 @@
|
|||
}
|
||||
|
||||
/* TestProgress */
|
||||
- (void)resetFlags
|
||||
- (void) resetFlags
|
||||
{
|
||||
_flags = NORESULTS;
|
||||
}
|
||||
|
||||
- (void)setFlags:(NSUInteger)mask
|
||||
- (void) setFlags:(NSUInteger)mask
|
||||
{
|
||||
_flags = _flags | mask;
|
||||
}
|
||||
|
||||
- (void)unsetFlags:(NSUInteger)mask
|
||||
- (void) unsetFlags:(NSUInteger)mask
|
||||
{
|
||||
_flags = _flags & ~mask;
|
||||
}
|
||||
|
||||
- (BOOL)isFlagSet:(NSUInteger)mask
|
||||
- (BOOL) isFlagSet:(NSUInteger)mask
|
||||
{
|
||||
return ((_flags & mask) == mask);
|
||||
}
|
||||
|
||||
- (void)resetReferenceFlags
|
||||
- (void) resetReferenceFlags
|
||||
{
|
||||
_refFlags = NORESULTS;
|
||||
}
|
||||
|
||||
- (void)setReferenceFlags:(NSUInteger)mask
|
||||
- (void) setReferenceFlags:(NSUInteger)mask
|
||||
{
|
||||
_refFlags = _refFlags | mask;
|
||||
}
|
||||
|
||||
- (void)unsetReferenceFlags:(NSUInteger)mask
|
||||
- (void) unsetReferenceFlags:(NSUInteger)mask
|
||||
{
|
||||
_refFlags = _refFlags & ~mask;
|
||||
}
|
||||
|
||||
- (BOOL)isReferenceFlagSet:(NSUInteger)mask
|
||||
- (BOOL) isReferenceFlagSet:(NSUInteger)mask
|
||||
{
|
||||
return ((_refFlags & mask) == mask);
|
||||
}
|
||||
|
||||
- (BOOL)isSuccess
|
||||
- (BOOL) isSuccess
|
||||
{
|
||||
if(!_failed && (_flags == _refFlags))
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* It is designed to call the delegate's callbacks (if implemented) of
|
||||
* the TestWebServerDelegate protocol for every request made to
|
||||
* the SimpleWebServer's assigned address on the SimpleWebServer's assigned port
|
||||
* (by default 127.0.0.1 and 1234 respectively). However it currently doesn't
|
||||
* (by default localhost and 1234 respectively). However it currently doesn't
|
||||
* handle any request on it's own. Instead the class uses a collection of
|
||||
* handlers. Every handler makes a custom response. So the TestWebServer only
|
||||
* has to dispatch a request to a corresponding handler and then to send back
|
||||
|
@ -29,7 +29,7 @@
|
|||
* NSDictionary *extra = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
* @"https", @"Protocol",
|
||||
* nil];
|
||||
* TestWebServer *server = [[TestWebServer alloc] initWithAddress: @"127.0.0.1"
|
||||
* TestWebServer *server = [[TestWebServer alloc] initWithAddress: @"localhost"
|
||||
* port: @"1234"
|
||||
* mode: NO
|
||||
* extra: extra];
|
||||
|
@ -72,7 +72,7 @@
|
|||
* 301 "Redirect to <URL>"
|
||||
* Returns in the header 'Location' a new <URL> which by default
|
||||
* has the same protocol, address but the port is incremented by
|
||||
* 1 (e.g. http://127.0.0.1:1235/ with other parameters set to
|
||||
* 1 (e.g. http://localhost:1235/ with other parameters set to
|
||||
* their default values).
|
||||
* 400 "You have issued a request with invalid data"
|
||||
* 401 "Invalid login or password"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
/**
|
||||
* Starts the detached thread. It is the entry point of the thread.
|
||||
*/
|
||||
*/
|
||||
- (void)_startDetached:(id)extra;
|
||||
|
||||
/**
|
||||
|
@ -23,10 +23,10 @@
|
|||
- (void)_stopHTTPServer;
|
||||
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
/* default 'constants' */
|
||||
#define DEFAULTADDRESS @"127.0.0.1"
|
||||
#define DEFAULTADDRESS @"localhost"
|
||||
#define DEFAULTPORT @"1234"
|
||||
#define DEFAULTMODE NO
|
||||
#define DEFAULTLOGIN @"login"
|
||||
|
@ -45,7 +45,7 @@
|
|||
|
||||
@implementation TestWebServer
|
||||
|
||||
- (id)init
|
||||
- (id) init
|
||||
{
|
||||
return [self initWithAddress: DEFAULTADDRESS
|
||||
port: DEFAULTPORT
|
||||
|
@ -68,7 +68,7 @@
|
|||
_password = nil;
|
||||
_login = nil;
|
||||
_isSecure = NO;
|
||||
|
||||
|
||||
if ([extra isKindOfClass: [NSDictionary class]])
|
||||
{
|
||||
NSDictionary *d = extra;
|
||||
|
@ -123,7 +123,7 @@
|
|||
selector: @selector(_startDetached:)
|
||||
object: extra];
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
_serverThread = nil;
|
||||
}
|
||||
|
@ -145,36 +145,40 @@
|
|||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)start:(id)extra
|
||||
- (void) start: (id)extra
|
||||
{
|
||||
if ([_server port] != nil)
|
||||
{
|
||||
if (YES == _debug)
|
||||
if ([_server port] != nil)
|
||||
{
|
||||
if (_debug)
|
||||
{
|
||||
NSWarnMLog(@"SimpleWebServer already started");
|
||||
}
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
if (nil != _serverThread)
|
||||
{
|
||||
if (_debug)
|
||||
NSLog(@"Waiting for startup");
|
||||
if (![_serverThread isExecuting])
|
||||
{
|
||||
NSTimeInterval duration = 0.0;
|
||||
|
||||
[_serverThread start];
|
||||
|
||||
// wait for the SimpleWebServer is started
|
||||
while(![_lock tryLockWhenCondition: STARTED] &&
|
||||
duration < MAXDURATION)
|
||||
while (![_lock tryLockWhenCondition: STARTED]
|
||||
&& duration < MAXDURATION)
|
||||
{
|
||||
[[NSRunLoop currentRunLoop]
|
||||
runUntilDate: [NSDate dateWithTimeIntervalSinceNow: TIMING]];
|
||||
duration += TIMING;
|
||||
}
|
||||
[_lock unlock];
|
||||
if (duration >= MAXDURATION &&
|
||||
nil != _delegate &&
|
||||
[_delegate respondsToSelector: @selector(timeoutExceededByHandler:)])
|
||||
if (duration >= MAXDURATION
|
||||
&& nil != _delegate
|
||||
&& [_delegate respondsToSelector:
|
||||
@selector(timeoutExceededByHandler:)])
|
||||
{
|
||||
[_delegate timeoutExceededByHandler: self];
|
||||
[self stop];
|
||||
|
@ -191,7 +195,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
- (void) stop
|
||||
{
|
||||
if ([_server port] == nil)
|
||||
{
|
||||
|
@ -283,18 +287,18 @@
|
|||
_isSecure ? @"https" : @"http",
|
||||
_address,
|
||||
_port];
|
||||
|
||||
|
||||
[(HandlerIndex *)handler setURLString: urlString];
|
||||
}
|
||||
else if ([handler isKindOfClass: [Handler301 class]])
|
||||
{
|
||||
// by default http://127.0.0.1:1235/
|
||||
// by default http://localhost:1235/
|
||||
NSString *port = [NSString stringWithFormat: @"%u", [_port intValue] + 1]; // the TestWebServer's port + 1
|
||||
NSString *urlString = [NSString stringWithFormat: @"%@://%@:%@/",
|
||||
_isSecure ? @"https" : @"http",
|
||||
_address,
|
||||
port];
|
||||
|
||||
|
||||
[(Handler301 *)handler setURLString: urlString];
|
||||
}
|
||||
}
|
||||
|
@ -314,7 +318,7 @@
|
|||
[handler posthandleRequest: request
|
||||
response: response
|
||||
for: self];
|
||||
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -369,7 +373,7 @@
|
|||
|
||||
@implementation TestWebServer (Private)
|
||||
|
||||
- (void)_startHTTPServer:(id)extra
|
||||
- (void) _startHTTPServer: (id)extra
|
||||
{
|
||||
NSString *certPath;
|
||||
NSString *keyPath;
|
||||
|
@ -381,8 +385,10 @@
|
|||
[_server setDelegate: self];
|
||||
if (_isSecure)
|
||||
{
|
||||
if ([_address isEqualToString: @"127.0.0.1"] ||
|
||||
[_address isEqualToString: @"localhost"])
|
||||
NSHost *h = [NSHost hostWithAddress: _address];
|
||||
NSHost *l = [NSHost hostWithName: @"localhost"];
|
||||
|
||||
if ([h isEqual: l])
|
||||
{
|
||||
certPath = [[NSBundle bundleForClass: [self class]]
|
||||
pathForResource: @"testCert"
|
||||
|
@ -397,22 +403,31 @@
|
|||
}
|
||||
else
|
||||
{
|
||||
[NSException raise: NSInternalInconsistencyException
|
||||
format: @"The server hasn't run. Address %@ is not localhost (%@)",
|
||||
_address, l];
|
||||
// NOTE: generate corresponding certificates for any address differing
|
||||
// from 127.0.0.1
|
||||
// from localhost
|
||||
}
|
||||
}
|
||||
|
||||
status = [_server setAddress: _address port: _port secure: secure]; // run the server
|
||||
if (!status)
|
||||
if (_debug)
|
||||
{
|
||||
NSLog(@"Starting web server with address %@, port %@ %@",
|
||||
_address, _port, secure ? @" with TLS" : @"");
|
||||
}
|
||||
status = [_server setAddress: _address port: _port secure: secure];
|
||||
if (!status)
|
||||
{
|
||||
[NSException raise: NSInternalInconsistencyException
|
||||
format: @"The server hasn't run. Perhaps the port %@ is invalid", DEFAULTPORT];
|
||||
format: @"The server hasn't run. Perhaps the port %@ is invalid",
|
||||
DEFAULTPORT];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_stopHTTPServer
|
||||
{
|
||||
if (nil != _server && [_server port] != nil)
|
||||
if (nil != _server && [_server port] != nil)
|
||||
{
|
||||
[_server stop]; // shut down the server
|
||||
if (YES == _debug)
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
* Author: Sergei Golovin <Golovin.SV@gmail.com>
|
||||
*
|
||||
* Runs two TestWebServer instances to check how the class TestWebServer
|
||||
* behaves. Visit http://127.0.0.1:1234/index to see all supported resources.
|
||||
* behaves. Visit http://localhost:1234/index to see all supported resources.
|
||||
*
|
||||
* If you visit the main TestWebServer instance with the following command:
|
||||
*
|
||||
* wget -O - --user=login --password=password http://127.0.0.1:1234/301 2>&1
|
||||
* wget -O - --user=login --password=password http://localhost:1234/301 2>&1
|
||||
*
|
||||
* you should get a session log like this:
|
||||
*
|
||||
* --2014-08-13 12:08:01-- http://127.0.0.1:1234/301
|
||||
* --2014-08-13 12:08:01-- http://localhost:1234/301
|
||||
* Resolving 127.0.0.1 (localhost)... 127.0.0.1
|
||||
* Connecting to 127.0.0.1 (localhost)|127.0.0.1|:1234... connected.
|
||||
* HTTP request sent, awaiting response... 401 Unauthorized
|
||||
|
@ -64,20 +64,20 @@ int main(int argc, char **argv, char **env)
|
|||
// @"https", @"Protocol",
|
||||
nil];
|
||||
server1 = [[[testClass testWebServerClass] alloc]
|
||||
initWithAddress: @"127.0.0.1"
|
||||
initWithAddress: @"localhost"
|
||||
port: @"1234"
|
||||
mode: NO
|
||||
extra: d];
|
||||
[server1 setDebug: debug];
|
||||
[server1 start: d]; // 127.0.0.1:1234 HTTP
|
||||
[server1 start: d]; // localhost:1234 HTTP
|
||||
|
||||
server2 = [[[testClass testWebServerClass] alloc]
|
||||
initWithAddress: @"127.0.0.1"
|
||||
initWithAddress: @"localhost"
|
||||
port: @"1235"
|
||||
mode: NO
|
||||
extra: d];
|
||||
[server2 setDebug: debug];
|
||||
[server2 start: d]; // 127.0.0.1:1235 HTTP
|
||||
[server2 start: d]; // localhost:1235 HTTP
|
||||
|
||||
while (YES)
|
||||
{
|
||||
|
|
|
@ -59,7 +59,7 @@ int main(int argc, char **argv, char **env)
|
|||
|
||||
duration = 0.0;
|
||||
timing = 0.1;
|
||||
urlString = @"http://127.0.0.1:19750";
|
||||
urlString = @"http://localhost:19750";
|
||||
req = [NSURLRequest requestWithURL: [NSURL URLWithString: urlString]];
|
||||
del = [[Delegate new] autorelease];
|
||||
[NSURLConnection connectionWithRequest: req
|
||||
|
@ -76,7 +76,7 @@ int main(int argc, char **argv, char **env)
|
|||
[del reset];
|
||||
|
||||
duration = 0.0;
|
||||
urlString = @"https://127.0.0.1:19750";
|
||||
urlString = @"https://localhost:19750";
|
||||
req = [NSURLRequest requestWithURL: [NSURL URLWithString: urlString]];
|
||||
[NSURLConnection connectionWithRequest: req
|
||||
delegate: del];
|
||||
|
|
|
@ -35,7 +35,7 @@ int main(int argc, char **argv, char **env)
|
|||
// create a shared TestWebServer instance for performance
|
||||
server = [[testClass testWebServerClass] new];
|
||||
[server setDebug: debug];
|
||||
[server start: nil]; // 127.0.0.1:1234 HTTP
|
||||
[server start: nil]; // localhost:1234 HTTP
|
||||
|
||||
/*
|
||||
* Simple GET via HTTP with empty response's body and
|
||||
|
|
|
@ -40,12 +40,12 @@ int main(int argc, char **argv, char **env)
|
|||
nil];
|
||||
// create a shared TestWebServer instance for performance
|
||||
server = [[[testClass testWebServerClass] alloc]
|
||||
initWithAddress: @"127.0.0.1"
|
||||
initWithAddress: @"localhost"
|
||||
port: @"1234"
|
||||
mode: NO
|
||||
extra: d];
|
||||
[server setDebug: debug];
|
||||
[server start: d]; // 127.0.0.1:1234 HTTPS
|
||||
[server start: d]; // localhost:1234 HTTPS
|
||||
|
||||
/*
|
||||
* Simple GET via HTTPS with empty response's body and
|
||||
|
@ -59,7 +59,7 @@ int main(int argc, char **argv, char **env)
|
|||
nil];
|
||||
[testCase setUpTest: d];
|
||||
[testCase startTest: d];
|
||||
PASS([testCase isSuccess], "HTTPS... GET https://127.0.0.1:1234/");
|
||||
PASS([testCase isSuccess], "HTTPS... GET https://localhost:1234/");
|
||||
[testCase tearDownTest: d];
|
||||
DESTROY(testCase);
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ int main(int argc, char **argv, char **env)
|
|||
// create a shared TestWebServer instance for performance
|
||||
server = [[testClass testWebServerClass] new];
|
||||
[server setDebug: debug];
|
||||
[server start: nil]; // 127.0.0.1:1234 HTTP
|
||||
[server start: nil]; // localhost:1234 HTTP
|
||||
|
||||
/*
|
||||
* Simple GET via HTTP without authorization with empty response's body and
|
||||
|
|
|
@ -44,7 +44,7 @@ int main(int argc, char **argv, char **env)
|
|||
mode: NO
|
||||
extra: d];
|
||||
[server setDebug: debug];
|
||||
[server start: d]; // 127.0.0.1:1234 HTTPS
|
||||
[server start: d]; // localhost:1234 HTTPS
|
||||
|
||||
/* Simple GET via HTTPS without authorization with empty response's
|
||||
* body and the response's status code 204 (by default)
|
||||
|
|
|
@ -39,7 +39,7 @@ int main(int argc, char **argv, char **env)
|
|||
// login:password
|
||||
server = [[testClass testWebServerClass] new];
|
||||
[server setDebug: debug];
|
||||
[server start: nil]; // 127.0.0.1:1234 HTTP
|
||||
[server start: nil]; // localhost:1234 HTTP
|
||||
|
||||
/*
|
||||
* Simple GET via HTTP with some response's body and
|
||||
|
|
|
@ -45,7 +45,7 @@ int main(int argc, char **argv, char **env)
|
|||
mode: NO
|
||||
extra: d];
|
||||
[server setDebug: debug];
|
||||
[server start: d]; // 127.0.0.1:1234 HTTPS
|
||||
[server start: d]; // localhost:1234 HTTPS
|
||||
|
||||
/* Simple POST via HTTPS with the response's status code 400 and
|
||||
* non-empty response's body
|
||||
|
|
5
Tests/base/NSURLSession/GNUmakefile.preamble
Normal file
5
Tests/base/NSURLSession/GNUmakefile.preamble
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
SUBPROJECTS = ../NSURLConnection/Helpers
|
||||
|
||||
include $(GNUSTEP_MAKEFILES)/aggregate.make
|
||||
|
0
Tests/base/NSURLSession/TestInfo
Normal file
0
Tests/base/NSURLSession/TestInfo
Normal file
116
Tests/base/NSURLSession/delegate.g
Normal file
116
Tests/base/NSURLSession/delegate.g
Normal file
|
@ -0,0 +1,116 @@
|
|||
#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;
|
||||
}
|
||||
- (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);
|
||||
_finished = NO;
|
||||
}
|
||||
|
||||
- (NSURLResponse*) response
|
||||
{
|
||||
return _response;
|
||||
}
|
||||
- (NSData*) taskData
|
||||
{
|
||||
return _taskData;
|
||||
}
|
||||
- (NSError*) taskError
|
||||
{
|
||||
return _taskError;
|
||||
}
|
||||
- (NSString*) taskText
|
||||
{
|
||||
return _taskText;
|
||||
}
|
||||
|
||||
- (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);
|
||||
}
|
||||
|
||||
- (void) URLSession: (NSURLSession*)session
|
||||
task: (NSURLSessionTask*)task
|
||||
didCompleteWithError: (NSError*)error
|
||||
{
|
||||
[_order addObject: NSStringFromSelector(_cmd)];
|
||||
_finished = YES;
|
||||
|
||||
if (error == nil)
|
||||
{
|
||||
NSLog(@"Download is Succesfull");
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog(@"Error %@", [error userInfo]);
|
||||
}
|
||||
ASSIGN(_taskError, error);
|
||||
}
|
||||
@end
|
||||
|
79
Tests/base/NSURLSession/gdbinit
Normal file
79
Tests/base/NSURLSession/gdbinit
Normal 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
|
132
Tests/base/NSURLSession/test01.m
Normal file
132
Tests/base/NSURLSession/test01.m
Normal file
|
@ -0,0 +1,132 @@
|
|||
#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;
|
||||
|
||||
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")
|
||||
|
||||
#endif
|
||||
END_SET("NSURLSession test01")
|
||||
return 0;
|
||||
}
|
89
Tests/base/NSURLSession/test02.m
Normal file
89
Tests/base/NSURLSession/test02.m
Normal file
|
@ -0,0 +1,89 @@
|
|||
#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 *defaultConfigObject;
|
||||
NSURLSession *defaultSession;
|
||||
NSURLSessionDataTask *dataTask;
|
||||
NSMutableURLRequest *urlRequest;
|
||||
NSURL *url;
|
||||
NSOperationQueue *mainQueue;
|
||||
NSString *params;
|
||||
MyDelegate *object;
|
||||
|
||||
object = AUTORELEASE([MyDelegate new]);
|
||||
mainQueue = [NSOperationQueue mainQueue];
|
||||
defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject
|
||||
delegate: object
|
||||
delegateQueue: mainQueue];
|
||||
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;
|
||||
}
|
|
@ -61,7 +61,7 @@ ifeq ($(BASE_MAKE_LOADED),)
|
|||
endif
|
||||
|
||||
# Now we have definitions to show whether important dependencies have
|
||||
# been met ... if thse are 0 then some core functi0nailyt is missing.
|
||||
# been met ... if thse are 0 then some core functionality is missing.
|
||||
|
||||
# Has GNUTLS been found (for TLS/SSL support throughout)?
|
||||
GNUSTEP_BASE_HAVE_GNUTLS=@HAVE_GNUTLS@
|
||||
|
|
|
@ -35,13 +35,14 @@ endif
|
|||
GNUSTEP_INSTALL_GDOMAP_AS_SETUID=@GNUSTEP_INSTALL_GDOMAP_AS_SETUID@
|
||||
GNUSTEP_GDOMAP_PORT_OVERRIDE=@GNUSTEP_GDOMAP_PORT_OVERRIDE@
|
||||
|
||||
GNUSTEP_BASE_HAVE_LIBXML=@HAVE_LIBXML@
|
||||
GNUSTEP_BASE_HAVE_GNUTLS=@HAVE_GNUTLS@
|
||||
GNUSTEP_BASE_HAVE_MDNS=@HAVE_MDNS@
|
||||
GNUSTEP_BASE_HAVE_AVAHI=@HAVE_AVAHI@
|
||||
GNUSTEP_BASE_HAVE_GNUTLS=@HAVE_GNUTLS@
|
||||
GNUSTEP_BASE_HAVE_ICU=@HAVE_ICU@
|
||||
GNUSTEP_BASE_HAVE_LIBCURL=@HAVE_LIBCURL@
|
||||
GNUSTEP_BASE_HAVE_LIBDISPATCH=@HAVE_LIBDISPATCH@
|
||||
GNUSTEP_BASE_HAVE_LIBDISPATCH_RUNLOOP=@HAVE_LIBDISPATCH_RUNLOOP@
|
||||
GNUSTEP_BASE_HAVE_LIBXML=@HAVE_LIBXML@
|
||||
GNUSTEP_BASE_HAVE_MDNS=@HAVE_MDNS@
|
||||
|
||||
# Default to building only -baseadd
|
||||
# on non *-gnu-* library combos
|
||||
|
|
|
@ -6,15 +6,16 @@ dnl notice and this notice are preserved.
|
|||
dnl
|
||||
dnl Written by Andrew Ruder
|
||||
dnl GS_ADD_LIBRARY_PATH
|
||||
dnl Adds -L$1 -Wl,-R$1 on netbsd and -L$1 elsewhere to LDFLAGS and LDIR_FLAGS
|
||||
dnl Adds -L$1 -Wl,-R$1 on netbsd and -Wl,-rpath,$1 -L$1 elsewhere
|
||||
dnl to LDFLAGS and LDIR_FLAGS
|
||||
AC_DEFUN([GS_ADD_LIBRARY_PATH], [
|
||||
case "$target_os" in
|
||||
netbsd*)
|
||||
LDFLAGS="$LDFLAGS -L$1 -Wl,-R$1"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-R$1 -L$1";;
|
||||
*)
|
||||
LDFLAGS="$LDFLAGS -L$1"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -L$1";;
|
||||
LDFLAGS="$LDFLAGS -Wl,-rpath,$1 -L$1"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-rpath,$1 -L$1";;
|
||||
esac
|
||||
])
|
||||
|
||||
|
|
263
configure
vendored
263
configure
vendored
|
@ -632,6 +632,8 @@ WARN_FLAGS
|
|||
LDIR_FLAGS
|
||||
INCLUDE_FLAGS
|
||||
USE_GMP
|
||||
GS_HAVE_NSURLSESSION
|
||||
HAVE_LIBCURL
|
||||
HAVE_LIBDISPATCH_RUNLOOP
|
||||
HAVE_LIBDISPATCH
|
||||
HAVE_ICU
|
||||
|
@ -820,6 +822,10 @@ enable_zeroconf
|
|||
with_zeroconf_api
|
||||
enable_icu
|
||||
enable_libdispatch
|
||||
with_dispatch_include
|
||||
with_dispatch_library
|
||||
with_curl
|
||||
enable_nsurlsession
|
||||
with_gmp_include
|
||||
with_gmp_library
|
||||
with_gdomap_port
|
||||
|
@ -1500,6 +1506,7 @@ Optional Features:
|
|||
--disable-zeroconf Disable NSNetServices support
|
||||
--disable-icu Disable International Components for Unicode
|
||||
--disable-libdispatch Disable dispatching blocks via libdispatch
|
||||
--disable-nsurlsession Disable support for NSURLSession
|
||||
|
||||
--enable-setuid-gdomap Enable installing gdomap as a setuid
|
||||
executable. By default, it is installed
|
||||
|
@ -1571,6 +1578,9 @@ Optional Packages:
|
|||
--with-xml-prefix=PFX Prefix where libxml is installed (optional)
|
||||
--with-tls-prefix=PFX Prefix where libgnutls is installed (optional)
|
||||
--with-zeroconf-api=API force use of a specific zeroconf API (mdns or avahi)
|
||||
--with-dispatch-include=PATH Include path for dispatch header
|
||||
--with-dispatch-library=PATH Library path for dispatch lib
|
||||
--with-curl=<DIR> use curl installed in directory <DIR>
|
||||
--with-gmp-include=PATH include path for gmp headers
|
||||
--with-gmp-library=PATH library path for gmp libraries
|
||||
--with-gdomap-port=PORT alternative port for gdomap
|
||||
|
@ -10762,8 +10772,8 @@ case "$target_os" in
|
|||
LDFLAGS="$LDFLAGS -L${ffi_libdir} -Wl,-R${ffi_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-R${ffi_libdir} -L${ffi_libdir}";;
|
||||
*)
|
||||
LDFLAGS="$LDFLAGS -L${ffi_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -L${ffi_libdir}";;
|
||||
LDFLAGS="$LDFLAGS -Wl,-rpath,${ffi_libdir} -L${ffi_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-rpath,${ffi_libdir} -L${ffi_libdir}";;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
@ -11093,8 +11103,8 @@ case "$target_os" in
|
|||
LDFLAGS="$LDFLAGS -L${libiconv_libdir} -Wl,-R${libiconv_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-R${libiconv_libdir} -L${libiconv_libdir}";;
|
||||
*)
|
||||
LDFLAGS="$LDFLAGS -L${libiconv_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -L${libiconv_libdir}";;
|
||||
LDFLAGS="$LDFLAGS -Wl,-rpath,${libiconv_libdir} -L${libiconv_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-rpath,${libiconv_libdir} -L${libiconv_libdir}";;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
@ -11251,8 +11261,8 @@ case "$target_os" in
|
|||
LDFLAGS="$LDFLAGS -L${libiconv_libdir} -Wl,-R${libiconv_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-R${libiconv_libdir} -L${libiconv_libdir}";;
|
||||
*)
|
||||
LDFLAGS="$LDFLAGS -L${libiconv_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -L${libiconv_libdir}";;
|
||||
LDFLAGS="$LDFLAGS -Wl,-rpath,${libiconv_libdir} -L${libiconv_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-rpath,${libiconv_libdir} -L${libiconv_libdir}";;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
@ -12885,6 +12895,7 @@ fi
|
|||
# Check for libdispatch
|
||||
# See DEPENDENCIES POLICY at the start of this file.
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
HAVE_LIBDISPATCH=0
|
||||
# Check whether --enable-libdispatch was given.
|
||||
if test "${enable_libdispatch+set}" = set; then :
|
||||
|
@ -12895,6 +12906,40 @@ fi
|
|||
|
||||
|
||||
if test $enable_libdispatch = yes; then
|
||||
|
||||
# Check whether --with-dispatch-include was given.
|
||||
if test "${with_dispatch_include+set}" = set; then :
|
||||
withval=$with_dispatch_include; dispatch_incdir="$withval"
|
||||
else
|
||||
dispatch_incdir="no"
|
||||
fi
|
||||
|
||||
if test ${dispatch_incdir} != "no"; then
|
||||
CPPFLAGS="$CPPFLAGS -I${dispatch_incdir}"
|
||||
INCLUDE_FLAGS="$INCLUDE_FLAGS -I${dispatch_incdir}"
|
||||
fi
|
||||
|
||||
|
||||
# Check whether --with-dispatch-library was given.
|
||||
if test "${with_dispatch_library+set}" = set; then :
|
||||
withval=$with_dispatch_library; dispatch_libdir="$withval"
|
||||
else
|
||||
dispatch_libdir="no"
|
||||
fi
|
||||
|
||||
if test ${dispatch_libdir} != "no"; then
|
||||
|
||||
case "$target_os" in
|
||||
netbsd*)
|
||||
LDFLAGS="$LDFLAGS -L${dispatch_libdir} -Wl,-R${dispatch_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-R${dispatch_libdir} -L${dispatch_libdir}";;
|
||||
*)
|
||||
LDFLAGS="$LDFLAGS -Wl,-rpath,${dispatch_libdir} -L${dispatch_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-rpath,${dispatch_libdir} -L${dispatch_libdir}";;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
||||
for ac_header in dispatch.h
|
||||
do :
|
||||
ac_fn_c_check_header_mongrel "$LINENO" "dispatch.h" "ac_cv_header_dispatch_h" "$ac_includes_default"
|
||||
|
@ -13019,8 +13064,8 @@ $as_echo "no" >&6; };
|
|||
fi
|
||||
fi
|
||||
else
|
||||
HAVE_LIBDISPATCH=0;
|
||||
# just ignore libdispatch if it's not there
|
||||
HAVE_LIBDISPATCH=0;
|
||||
# just ignore libdispatch if it's not there
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@ -13064,6 +13109,204 @@ done
|
|||
fi
|
||||
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Check for libcurl
|
||||
# See DEPENDENCIES POLICY at the start of this file.
|
||||
#--------------------------------------------------------------------
|
||||
CURL_CONFIG="curl-config"
|
||||
|
||||
# Check whether --with-curl was given.
|
||||
if test "${with_curl+set}" = set; then :
|
||||
withval=$with_curl;
|
||||
fi
|
||||
|
||||
if test "$with_curl" != ""; then
|
||||
CURL_CONFIG="$with_curl/bin/curl-config"
|
||||
fi
|
||||
|
||||
HAVE_LIBCURL=0
|
||||
curl_all=no
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcurl" >&5
|
||||
$as_echo_n "checking for libcurl... " >&6; }
|
||||
if eval $CURL_CONFIG --version 2>/dev/null >/dev/null; then
|
||||
curl_ver=`$CURL_CONFIG --version | sed -e "s/libcurl //g"`
|
||||
curl_maj=`echo $curl_ver | sed -e "s/^\(.*\)\.\(.*\)\.\(.*\)$/\1/"`
|
||||
curl_min=`echo $curl_ver | sed -e "s/^\(.*\)\.\(.*\)\.\(.*\)$/\2/"`
|
||||
if test $curl_maj -lt 7 -o \( $curl_maj -eq 7 -a $curl_min -lt 49 \); then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: FAILED (version too old to use" >&5
|
||||
$as_echo "FAILED (version too old to use" >&6; }
|
||||
else
|
||||
CURLCFLAGS=`$CURL_CONFIG --cflags`
|
||||
CURLLIBS=`$CURL_CONFIG --libs`
|
||||
CFLAGS="$CFLAGS $CURLCFLAGS"
|
||||
LIBS="$LIBS $CURLLIBS"
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes ... version $curl_ver" >&5
|
||||
$as_echo "yes ... version $curl_ver" >&6; }
|
||||
HAVE_LIBCURL=1
|
||||
curl_all=yes
|
||||
if test \( $curl_maj -eq 7 -a $curl_min -lt 56 \); then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: LOSS OF FUNCTIONALITY (version too old for SSL backend control)" >&5
|
||||
$as_echo "$as_me: WARNING: LOSS OF FUNCTIONALITY (version too old for SSL backend control)" >&2;}
|
||||
curl_all=no
|
||||
elif test \( $curl_maj -eq 7 -a $curl_min -lt 65 \); then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: LOSS OF FUNCTIONALITY (version too old for connection lifetime control)" >&5
|
||||
$as_echo "$as_me: WARNING: LOSS OF FUNCTIONALITY (version too old for connection lifetime control)" >&2;}
|
||||
curl_all=no
|
||||
fi
|
||||
fi
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: FAILED (curl-config not found)" >&5
|
||||
$as_echo "FAILED (curl-config not found)" >&6; }
|
||||
fi
|
||||
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcurl TLS support with gnutls" >&5
|
||||
$as_echo_n "checking for libcurl TLS support with gnutls... " >&6; }
|
||||
if $CURL_CONFIG --configure | grep -q with-gnutls; then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
curl_all=no
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: LOSS OF FUNCTIONALITY (curl lacks gnutls support)" >&5
|
||||
$as_echo "$as_me: WARNING: LOSS OF FUNCTIONALITY (curl lacks gnutls support)" >&2;}
|
||||
fi
|
||||
fi
|
||||
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for curl_multi_perform in -lcurl" >&5
|
||||
$as_echo_n "checking for curl_multi_perform in -lcurl... " >&6; }
|
||||
if ${ac_cv_lib_curl_curl_multi_perform+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
ac_check_lib_save_LIBS=$LIBS
|
||||
LIBS="-lcurl $LIBS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
/* Override any GCC internal prototype to avoid an error.
|
||||
Use char because int might match the return type of a GCC
|
||||
builtin and then its argument prototype would still apply. */
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
char curl_multi_perform ();
|
||||
int
|
||||
main ()
|
||||
{
|
||||
return curl_multi_perform ();
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_c_try_link "$LINENO"; then :
|
||||
ac_cv_lib_curl_curl_multi_perform=yes
|
||||
else
|
||||
ac_cv_lib_curl_curl_multi_perform=no
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext \
|
||||
conftest$ac_exeext conftest.$ac_ext
|
||||
LIBS=$ac_check_lib_save_LIBS
|
||||
fi
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curl_curl_multi_perform" >&5
|
||||
$as_echo "$ac_cv_lib_curl_curl_multi_perform" >&6; }
|
||||
if test "x$ac_cv_lib_curl_curl_multi_perform" = xyes; then :
|
||||
curl_ok=yes
|
||||
else
|
||||
curl_ok=no
|
||||
fi
|
||||
|
||||
fi
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
for ac_header in curl/curl.h
|
||||
do :
|
||||
ac_fn_c_check_header_mongrel "$LINENO" "curl/curl.h" "ac_cv_header_curl_curl_h" "$ac_includes_default"
|
||||
if test "x$ac_cv_header_curl_curl_h" = xyes; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_CURL_CURL_H 1
|
||||
_ACEOF
|
||||
curl_ok=yes
|
||||
else
|
||||
curl_ok=no
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
fi
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
ac_fn_c_check_decl "$LINENO" "CURLOPT_CONNECT_TO" "ac_cv_have_decl_CURLOPT_CONNECT_TO" "#include <curl/curl.h>
|
||||
"
|
||||
if test "x$ac_cv_have_decl_CURLOPT_CONNECT_TO" = xyes; then :
|
||||
curl_ok=yes
|
||||
else
|
||||
curl_ok=no
|
||||
fi
|
||||
|
||||
fi
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
# If curl supports runtime selection of the TLS module we will need
|
||||
# to use curl_global_sslset to pick GNUTLS
|
||||
for ac_func in curl_global_sslset
|
||||
do :
|
||||
ac_fn_c_check_func "$LINENO" "curl_global_sslset" "ac_cv_func_curl_global_sslset"
|
||||
if test "x$ac_cv_func_curl_global_sslset" = xyes; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_CURL_GLOBAL_SSLSET 1
|
||||
_ACEOF
|
||||
|
||||
fi
|
||||
done
|
||||
|
||||
fi
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Check dependencies of NSURLSession API
|
||||
#--------------------------------------------------------------------
|
||||
nsurlsessiondefault=no
|
||||
if test "$OBJC_RUNTIME_LIB" = "ng" -a $HAVE_BLOCKS = 1 -a $HAVE_LIBDISPATCH = 1 -a $HAVE_LIBCURL = 1; then
|
||||
nsurlsessiondefault=yes
|
||||
fi
|
||||
|
||||
GS_HAVE_NSURLSESSION=0
|
||||
# Check whether --enable-nsurlsession was given.
|
||||
if test "${enable_nsurlsession+set}" = set; then :
|
||||
enableval=$enable_nsurlsession;
|
||||
else
|
||||
enable_nsurlsession=$nsurlsessiondefault
|
||||
fi
|
||||
|
||||
if test $enable_nsurlsession = yes; then
|
||||
if test "$OBJC_RUNTIME_LIB" != "ng"; then
|
||||
as_fn_error $? "Missing ng runtime (needed for NSURLSession). To build without NSURLSession support, please run configure again with the --disable-nsurlsession option." "$LINENO" 5
|
||||
fi
|
||||
if test $HAVE_BLOCKS = 0; then
|
||||
as_fn_error $? "Missing blocks support (needed for NSURLSession). To build without NSURLSession support, please run configure again with the --disable-nsurlsession option." "$LINENO" 5
|
||||
fi
|
||||
if test $HAVE_LIBDISPATCH = 0; then
|
||||
as_fn_error $? "Missing libdispatch (needed for NSURLSession). To build without NSURLSession support, please run configure again with the --disable-nsurlsession option." "$LINENO" 5
|
||||
fi
|
||||
# Check for dispatch_queue_create_with_target needed for NSURLSession
|
||||
for ac_func in dispatch_queue_create_with_target
|
||||
do :
|
||||
ac_fn_c_check_func "$LINENO" "dispatch_queue_create_with_target" "ac_cv_func_dispatch_queue_create_with_target"
|
||||
if test "x$ac_cv_func_dispatch_queue_create_with_target" = xyes; then :
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
#define HAVE_DISPATCH_QUEUE_CREATE_WITH_TARGET 1
|
||||
_ACEOF
|
||||
|
||||
fi
|
||||
done
|
||||
|
||||
if test $HAVE_LIBCURL = 0; then
|
||||
as_fn_error $? "Missing libcurl (needed for NSURLSession). To build without NSURLSession support, please run configure again with the --disable-nsurlsession option." "$LINENO" 5
|
||||
fi
|
||||
GS_HAVE_NSURLSESSION=1
|
||||
fi
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Check GMP for NSDecimal
|
||||
#--------------------------------------------------------------------
|
||||
|
@ -13098,8 +13341,8 @@ case "$target_os" in
|
|||
LDFLAGS="$LDFLAGS -L${gmp_libdir} -Wl,-R${gmp_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-R${gmp_libdir} -L${gmp_libdir}";;
|
||||
*)
|
||||
LDFLAGS="$LDFLAGS -L${gmp_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -L${gmp_libdir}";;
|
||||
LDFLAGS="$LDFLAGS -Wl,-rpath,${gmp_libdir} -L${gmp_libdir}"
|
||||
LDIR_FLAGS="$LDIR_FLAGS -Wl,-rpath,${gmp_libdir} -L${gmp_libdir}";;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
|
122
configure.ac
122
configure.ac
|
@ -2837,7 +2837,7 @@ AC_ARG_WITH(ffi-library,
|
|||
[ --with-ffi-library=PATH Library path for ffi libs],
|
||||
ffi_libdir="$withval", ffi_libdir="no")
|
||||
if test ${ffi_libdir} != "no"; then
|
||||
GS_ADD_LIBRARY_PATH([${ffi_libdir}])
|
||||
GS_ADD_LIBRARY_PATH([${ffi_libdir}])
|
||||
fi
|
||||
|
||||
if test "$do_broken_libffi" = "no"; then
|
||||
|
@ -3369,6 +3369,7 @@ AC_SUBST(HAVE_ICU)
|
|||
# Check for libdispatch
|
||||
# See DEPENDENCIES POLICY at the start of this file.
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
HAVE_LIBDISPATCH=0
|
||||
AC_ARG_ENABLE(libdispatch,
|
||||
[ --disable-libdispatch Disable dispatching blocks via libdispatch],
|
||||
|
@ -3376,6 +3377,21 @@ AC_ARG_ENABLE(libdispatch,
|
|||
enable_libdispatch=yes)
|
||||
|
||||
if test $enable_libdispatch = yes; then
|
||||
AC_ARG_WITH(dispatch-include,
|
||||
[ --with-dispatch-include=PATH Include path for dispatch header],
|
||||
dispatch_incdir="$withval", dispatch_incdir="no")
|
||||
if test ${dispatch_incdir} != "no"; then
|
||||
CPPFLAGS="$CPPFLAGS -I${dispatch_incdir}"
|
||||
INCLUDE_FLAGS="$INCLUDE_FLAGS -I${dispatch_incdir}"
|
||||
fi
|
||||
|
||||
AC_ARG_WITH(dispatch-library,
|
||||
[ --with-dispatch-library=PATH Library path for dispatch lib],
|
||||
dispatch_libdir="$withval", dispatch_libdir="no")
|
||||
if test ${dispatch_libdir} != "no"; then
|
||||
GS_ADD_LIBRARY_PATH([${dispatch_libdir}])
|
||||
fi
|
||||
|
||||
AC_CHECK_HEADERS(dispatch.h, have_dispatch=yes, have_dispatch=no)
|
||||
if test "$have_dispatch" = "no"; then
|
||||
AC_CHECK_HEADERS(dispatch/dispatch.h, have_dispatch=yes, have_dispatch=no)
|
||||
|
@ -3402,8 +3418,8 @@ if test $enable_libdispatch = yes; then
|
|||
fi
|
||||
fi
|
||||
else
|
||||
HAVE_LIBDISPATCH=0;
|
||||
# just ignore libdispatch if it's not there
|
||||
HAVE_LIBDISPATCH=0;
|
||||
# just ignore libdispatch if it's not there
|
||||
fi
|
||||
fi
|
||||
AC_SUBST(HAVE_LIBDISPATCH)
|
||||
|
@ -3425,6 +3441,106 @@ if test $HAVE_LIBDISPATCH = 1; then
|
|||
fi
|
||||
AC_SUBST(HAVE_LIBDISPATCH_RUNLOOP)
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Check for libcurl
|
||||
# See DEPENDENCIES POLICY at the start of this file.
|
||||
#--------------------------------------------------------------------
|
||||
CURL_CONFIG="curl-config"
|
||||
AC_ARG_WITH(curl, AS_HELP_STRING([--with-curl=<DIR>],
|
||||
[use curl installed in directory <DIR>]))
|
||||
if test "$with_curl" != ""; then
|
||||
CURL_CONFIG="$with_curl/bin/curl-config"
|
||||
fi
|
||||
|
||||
HAVE_LIBCURL=0
|
||||
curl_all=no
|
||||
AC_MSG_CHECKING([for libcurl])
|
||||
if eval $CURL_CONFIG --version 2>/dev/null >/dev/null; then
|
||||
curl_ver=`$CURL_CONFIG --version | sed -e "s/libcurl //g"`
|
||||
curl_maj=`echo $curl_ver | sed -e "s/^\(.*\)\.\(.*\)\.\(.*\)$/\1/"`
|
||||
curl_min=`echo $curl_ver | sed -e "s/^\(.*\)\.\(.*\)\.\(.*\)$/\2/"`
|
||||
if test $curl_maj -lt 7 -o \( $curl_maj -eq 7 -a $curl_min -lt 49 \); then
|
||||
AC_MSG_RESULT([FAILED (version too old to use])
|
||||
else
|
||||
CURLCFLAGS=`$CURL_CONFIG --cflags`
|
||||
CURLLIBS=`$CURL_CONFIG --libs`
|
||||
CFLAGS="$CFLAGS $CURLCFLAGS"
|
||||
LIBS="$LIBS $CURLLIBS"
|
||||
AC_MSG_RESULT(yes ... version $curl_ver)
|
||||
HAVE_LIBCURL=1
|
||||
curl_all=yes
|
||||
if test \( $curl_maj -eq 7 -a $curl_min -lt 56 \); then
|
||||
AC_MSG_WARN([LOSS OF FUNCTIONALITY (version too old for SSL backend control)])
|
||||
curl_all=no
|
||||
elif test \( $curl_maj -eq 7 -a $curl_min -lt 65 \); then
|
||||
AC_MSG_WARN([LOSS OF FUNCTIONALITY (version too old for connection lifetime control)])
|
||||
curl_all=no
|
||||
fi
|
||||
fi
|
||||
else
|
||||
AC_MSG_RESULT([FAILED (curl-config not found)])
|
||||
fi
|
||||
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
AC_MSG_CHECKING([for libcurl TLS support with gnutls])
|
||||
if $CURL_CONFIG --configure | grep -q with-gnutls; then
|
||||
AC_MSG_RESULT(yes)
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
curl_all=no
|
||||
AC_MSG_WARN([LOSS OF FUNCTIONALITY (curl lacks gnutls support)])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
AC_CHECK_LIB(curl, curl_multi_perform, curl_ok=yes, curl_ok=no)
|
||||
fi
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
AC_CHECK_HEADERS(curl/curl.h, curl_ok=yes, curl_ok=no)
|
||||
fi
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
AC_CHECK_DECL(CURLOPT_CONNECT_TO, curl_ok=yes, curl_ok=no,
|
||||
[#include <curl/curl.h>])
|
||||
fi
|
||||
if test $HAVE_LIBCURL = 1; then
|
||||
# If curl supports runtime selection of the TLS module we will need
|
||||
# to use curl_global_sslset to pick GNUTLS
|
||||
AC_CHECK_FUNCS(curl_global_sslset)
|
||||
fi
|
||||
AC_SUBST(HAVE_LIBCURL)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Check dependencies of NSURLSession API
|
||||
#--------------------------------------------------------------------
|
||||
nsurlsessiondefault=no
|
||||
if test "$OBJC_RUNTIME_LIB" = "ng" -a $HAVE_BLOCKS = 1 -a $HAVE_LIBDISPATCH = 1 -a $HAVE_LIBCURL = 1; then
|
||||
nsurlsessiondefault=yes
|
||||
fi
|
||||
|
||||
GS_HAVE_NSURLSESSION=0
|
||||
AC_ARG_ENABLE(nsurlsession,
|
||||
[ --disable-nsurlsession Disable support for NSURLSession],,
|
||||
enable_nsurlsession=$nsurlsessiondefault)
|
||||
if test $enable_nsurlsession = yes; then
|
||||
if test "$OBJC_RUNTIME_LIB" != "ng"; then
|
||||
AC_MSG_ERROR([Missing ng runtime (needed for NSURLSession). To build without NSURLSession support, please run configure again with the --disable-nsurlsession option.])
|
||||
fi
|
||||
if test $HAVE_BLOCKS = 0; then
|
||||
AC_MSG_ERROR([Missing blocks support (needed for NSURLSession). To build without NSURLSession support, please run configure again with the --disable-nsurlsession option.])
|
||||
fi
|
||||
if test $HAVE_LIBDISPATCH = 0; then
|
||||
AC_MSG_ERROR([Missing libdispatch (needed for NSURLSession). To build without NSURLSession support, please run configure again with the --disable-nsurlsession option.])
|
||||
fi
|
||||
# Check for dispatch_queue_create_with_target needed for NSURLSession
|
||||
AC_CHECK_FUNCS(dispatch_queue_create_with_target)
|
||||
if test $HAVE_LIBCURL = 0; then
|
||||
AC_MSG_ERROR([Missing libcurl (needed for NSURLSession). To build without NSURLSession support, please run configure again with the --disable-nsurlsession option.])
|
||||
fi
|
||||
GS_HAVE_NSURLSESSION=1
|
||||
fi
|
||||
AC_SUBST(GS_HAVE_NSURLSESSION)
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# Check GMP for NSDecimal
|
||||
#--------------------------------------------------------------------
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue