From 05adcf606bc1cbd2934ec2b4cb99665395c04307 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Wed, 11 Jan 2023 12:02:03 +0100 Subject: [PATCH 01/14] Add missing NSURLSession APIs --- ChangeLog | 6 + Headers/Foundation/NSURLSession.h | 132 ++++++++++--- Source/NSURLSession.m | 310 +++++++++++++++++++++++++++--- 3 files changed, 396 insertions(+), 52 deletions(-) diff --git a/ChangeLog b/ChangeLog index e617aabee5..9ec730005b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2023-01-13 Frederik Seiffert + + * Headers/Foundation/NSURLSession.h: + * Source/NSURLSession.m: + Add missing NSURLSession APIs. + 2022-02-09 Richard Frith-Macdonald * Source/Additions/GSMime.m: ([GSMimeHeader setValue:]) do not set diff --git a/Headers/Foundation/NSURLSession.h b/Headers/Foundation/NSURLSession.h index 59fd9ae7aa..d4b3f8f9f7 100644 --- a/Headers/Foundation/NSURLSession.h +++ b/Headers/Foundation/NSURLSession.h @@ -23,7 +23,9 @@ @class NSURLRequest; @class NSURLResponse; @class NSURLSessionConfiguration; +@class NSURLSessionTask; @class NSURLSessionDataTask; +@class NSURLSessionUploadTask; @class NSURLSessionDownloadTask; @@ -62,6 +64,10 @@ GS_EXPORT_CLASS GSMultiHandle *_multiHandle; } ++ (NSURLSession*) sharedSession; + ++ (NSURLSession*) sessionWithConfiguration: (NSURLSessionConfiguration*)configuration; + /* * Customization of NSURLSession occurs during creation of a new session. * If you do specify a delegate, the delegate will be retained until after @@ -111,11 +117,80 @@ GS_EXPORT_CLASS /* Creates a data task to retrieve the contents of the given URL. */ - (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url; +/* Not implemented */ +- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request + fromFile: (NSURL*)fileURL; + +/* Not implemented */ +- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request + fromData: (NSData*)bodyData; + +/* Not implemented */ +- (NSURLSessionUploadTask*) uploadTaskWithStreamedRequest: (NSURLRequest*)request; + /* Creates a download task with the given request. */ -- (NSURLSessionDownloadTask *) downloadTaskWithRequest: (NSURLRequest *)request; +- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request; /* Creates a download task to download the contents of the given URL. */ -- (NSURLSessionDownloadTask *) downloadTaskWithURL: (NSURL *)url; +- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL*)url; + +/* Not implemented */ +- (NSURLSessionDownloadTask *) downloadTaskWithResumeData: (NSData *)resumeData; + +- (void) getTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, NSURLSessionDataTask*) *dataTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionUploadTask*) *uploadTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionDownloadTask*) *downloadTasks))completionHandler; + +- (void) getAllTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, __kindof NSURLSessionTask*) *tasks))completionHandler; + +@end + +/* + * NSURLSession convenience routines deliver results to + * a completion handler block. These convenience routines + * are not available to NSURLSessions that are configured + * as background sessions. + * + * Task objects are always created in a suspended state and + * must be sent the -resume message before they will execute. + */ +@interface NSURLSession (NSURLSessionAsynchronousConvenience) +/* + * data task convenience methods. These methods create tasks that + * bypass the normal delegate calls for response and data delivery, + * and provide a simple cancelable asynchronous interface to receiving + * data. Errors will be returned in the NSURLErrorDomain, + * see . The delegate, if any, will still be + * called for authentication challenges. + */ +- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request + completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; +- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url + completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; + + +/* Not implemented */ +- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request + fromFile: (NSURL*)fileURL + completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; + +/* Not implemented */ +- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request + fromData: (NSData*)bodyData + completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; + +/* + * download task convenience methods. When a download successfully + * completes, the NSURL will point to a file that must be read or + * copied during the invocation of the completion routine. The file + * will be removed automatically. + */ +- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request + completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler; +- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL *)url + completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler; + +/* Not implemented */ +- (NSURLSessionDownloadTask*) downloadTaskWithResumeData: (NSData*)resumeData + completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler; @end @@ -131,6 +206,12 @@ typedef NS_ENUM(NSUInteger, NSURLSessionTaskState) { NSURLSessionTaskStateCompleted = 3, }; +GS_EXPORT const float NSURLSessionTaskPriorityDefault; +GS_EXPORT const float NSURLSessionTaskPriorityLow; +GS_EXPORT const float NSURLSessionTaskPriorityHigh; + +GS_EXPORT const int64_t NSURLSessionTransferSizeUnknown; + /* * NSURLSessionTask - a cancelable object that refers to the lifetime * of processing a given request. @@ -245,6 +326,9 @@ GS_EXPORT_CLASS - (void) suspend; - (void) resume; +- (float) priority; +- (void) setPriority: (float)priority; + @end GS_EXPORT_CLASS @@ -273,6 +357,7 @@ GS_EXPORT_CLASS GS_EXPORT_CLASS @interface NSURLSessionConfiguration : NSObject { + NSString *_identifier; NSURLCache *_URLCache; NSURLRequestCachePolicy _requestCachePolicy; NSArray *_protocolClasses; @@ -288,34 +373,40 @@ GS_EXPORT_CLASS - (NSURLRequest*) configureRequest: (NSURLRequest*)request; -@property (class, readonly, strong) - NSURLSessionConfiguration *defaultSessionConfiguration; ++ (NSURLSessionConfiguration*) defaultSessionConfiguration; ++ (NSURLSessionConfiguration*) ephemeralSessionConfiguration; ++ (NSURLSessionConfiguration*) backgroundSessionConfigurationWithIdentifier:(NSString*)identifier; - (NSDictionary*) HTTPAdditionalHeaders; +- (void) setHTTPAdditionalHeaders: (NSDictionary*)headers; - (NSHTTPCookieAcceptPolicy) HTTPCookieAcceptPolicy; +- (void) setHTTPCookieAcceptPolicy: (NSHTTPCookieAcceptPolicy)policy; - (NSHTTPCookieStorage*) HTTPCookieStorage; - -#if !NO_GNUSTEP -- (NSInteger) HTTPMaximumConnectionLifetime; -#endif +- (void) setHTTPCookieStorage: (NSHTTPCookieStorage*)storage; - (NSInteger) HTTPMaximumConnectionsPerHost; +- (void) setHTTPMaximumConnectionsPerHost: (NSInteger)n; - (BOOL) HTTPShouldSetCookies; +- (void) setHTTPShouldSetCookies: (BOOL)flag; - (BOOL) HTTPShouldUsePipelining; +- (void) setHTTPShouldUsePipelining: (BOOL)flag; + +- (NSString*) identifier; - (NSArray*) protocolClasses; - (NSURLRequestCachePolicy) requestCachePolicy; +- (void) setRequestCachePolicy: (NSURLRequestCachePolicy)policy; -- (void) setHTTPAdditionalHeaders: (NSDictionary*)headers; - -- (void) setHTTPCookieAcceptPolicy: (NSHTTPCookieAcceptPolicy)policy; +- (NSURLCache*) URLCache; +- (void) setURLCache: (NSURLCache*)cache; -- (void) setHTTPCookieStorage: (NSHTTPCookieStorage*)storage; +- (NSURLCredentialStorage*) URLCredentialStorage; +- (void) setURLCredentialStorage: (NSURLCredentialStorage*)storage; #if !NO_GNUSTEP /** Permits a session to be configured so that older connections are reused. @@ -323,25 +414,10 @@ GS_EXPORT_CLASS * reused as long as they are not older than 118 seconds, which is reasonable * for the vast majority if situations. */ +- (NSInteger) HTTPMaximumConnectionLifetime; - (void) setHTTPMaximumConnectionLifetime: (NSInteger)n; #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) { diff --git a/Source/NSURLSession.m b/Source/NSURLSession.m index 6579086a10..8b4d822328 100644 --- a/Source/NSURLSession.m +++ b/Source/NSURLSession.m @@ -11,11 +11,17 @@ #import "Foundation/NSError.h" #import "Foundation/NSException.h" #import "Foundation/NSOperation.h" +#import "Foundation/NSPredicate.h" #import "Foundation/NSURLError.h" #import "Foundation/NSURLSession.h" #import "Foundation/NSURLRequest.h" #import "Foundation/NSValue.h" +GS_DECLARE const float NSURLSessionTaskPriorityDefault = 0.5; +GS_DECLARE const float NSURLSessionTaskPriorityLow = 0.0; +GS_DECLARE const float NSURLSessionTaskPriorityHigh = 1.0; + +GS_DECLARE const int64_t NSURLSessionTransferSizeUnknown = -1; /* NSURLSession API implementation overview * @@ -66,6 +72,12 @@ - (void) getProtocolWithCompletion: (void (^)(NSURLProtocol* protocol))completio - (void) setState: (NSURLSessionTaskState)state; - (void) invalidateProtocol; + +- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler; +- (void) setDataCompletionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; + +- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler; +- (void) setDownloadCompletionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler; @end @interface NSURLSessionTask (URLProtocolClient) @@ -103,9 +115,36 @@ @implementation NSURLSession GSTaskRegistry *_taskRegistry; } -+ (NSURLSession *) sessionWithConfiguration: (NSURLSessionConfiguration*)configuration - delegate: (id )delegate - delegateQueue: (NSOperationQueue*)queue + ++ (NSURLSession*) sharedSession +{ + static NSURLSession *session = nil; + static dispatch_once_t predicate; + + dispatch_once(&predicate, ^{ + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + session = [[NSURLSession alloc] initWithConfiguration: configuration + delegate: nil + delegateQueue: nil]; + }); + + return session; +} + ++ (NSURLSession*) sessionWithConfiguration: (NSURLSessionConfiguration*)configuration +{ + NSURLSession *session; + + session = [[NSURLSession alloc] initWithConfiguration: configuration + delegate: nil + delegateQueue: nil]; + + return AUTORELEASE(session); +} + ++ (NSURLSession*) sessionWithConfiguration: (NSURLSessionConfiguration*)configuration + delegate: (id )delegate + delegateQueue: (NSOperationQueue*)queue { NSURLSession *session; @@ -284,7 +323,24 @@ - (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url return [self dataTaskWithRequest: request]; } -- (NSURLSessionDownloadTask *) downloadTaskWithRequest: (NSURLRequest *)request +- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request + fromFile: (NSURL*)fileURL +{ + return [self notImplemented: _cmd]; +} + +- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request + fromData: (NSData*)bodyData; +{ + return [self notImplemented: _cmd]; +} + +- (NSURLSessionUploadTask*) uploadTaskWithStreamedRequest: (NSURLRequest*)request +{ + return [self notImplemented: _cmd]; +} + +- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request { NSURLSessionDownloadTask *task; @@ -302,7 +358,7 @@ - (NSURLSessionDownloadTask *) downloadTaskWithRequest: (NSURLRequest *)request return AUTORELEASE(task); } -- (NSURLSessionDownloadTask *) downloadTaskWithURL: (NSURL *)url +- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL*)url { NSMutableURLRequest *request; @@ -312,6 +368,39 @@ - (NSURLSessionDownloadTask *) downloadTaskWithURL: (NSURL *)url return [self downloadTaskWithRequest: request]; } +- (NSURLSessionDownloadTask*) downloadTaskWithResumeData: (NSData*)resumeData +{ + return [self notImplemented: _cmd]; +} + +- (void) getTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, NSURLSessionDataTask*) *dataTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionUploadTask*) *uploadTasks, GS_GENERIC_CLASS(NSArray, NSURLSessionDownloadTask*) *downloadTasks))completionHandler +{ + NSArray *allTasks, *dataTasks, *uploadTasks, *downloadTasks; + + allTasks = [_taskRegistry allTasks]; + dataTasks = [allTasks filteredArrayUsingPredicate: + [NSPredicate predicateWithBlock:^BOOL(id task, NSDictionary* bindings) { + return [task isKindOfClass:[NSURLSessionDataTask class]]; + }]]; + uploadTasks = [allTasks filteredArrayUsingPredicate: + [NSPredicate predicateWithBlock:^BOOL(id task, NSDictionary* bindings) { + return [task isKindOfClass:[NSURLSessionUploadTask class]]; + }]]; + downloadTasks = [allTasks filteredArrayUsingPredicate: + [NSPredicate predicateWithBlock:^BOOL(id task, NSDictionary* bindings) { + return [task isKindOfClass:[NSURLSessionDownloadTask class]]; + }]]; + + completionHandler(dataTasks, uploadTasks, downloadTasks); +} + +- (void) getAllTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, __kindof NSURLSessionTask*) *tasks))completionHandler +{ + NSArray *allTasks = [_taskRegistry allTasks]; + + completionHandler(allTasks); +} + - (void) addTask: (NSURLSessionTask*)task { [_taskRegistry addTask: task]; @@ -324,6 +413,94 @@ - (void) removeTask: (NSURLSessionTask*)task @end +@implementation NSURLSession (NSURLSessionAsynchronousConvenience) + +- (NSURLSessionDataTask*) dataTaskWithRequest: (NSURLRequest*)request + completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler +{ + NSURLSessionDataTask *task; + + if (_invalidated) + { + return nil; + } + + task = [[NSURLSessionDataTask alloc] initWithSession: self + request: request + taskIdentifier: _nextTaskIdentifier++]; + [task setDataCompletionHandler: completionHandler]; + + [self addTask: task]; + + return AUTORELEASE(task); +} + +- (NSURLSessionDataTask*) dataTaskWithURL: (NSURL*)url + completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler +{ + NSMutableURLRequest *request; + + request = [NSMutableURLRequest requestWithURL: url]; + [request setHTTPMethod: @"POST"]; + + return [self dataTaskWithRequest: request + completionHandler: completionHandler]; +} + +- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request + fromFile: (NSURL*)fileURL + completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler +{ + return [self notImplemented: _cmd]; +} + +- (NSURLSessionUploadTask*) uploadTaskWithRequest: (NSURLRequest*)request + fromData: (NSData*)bodyData + completionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler +{ + return [self notImplemented: _cmd]; +} + +- (NSURLSessionDownloadTask*) downloadTaskWithRequest: (NSURLRequest*)request + completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler +{ + NSURLSessionDataTask *task; + + if (_invalidated) + { + return nil; + } + + task = [[NSURLSessionDataTask alloc] initWithSession: self + request: request + taskIdentifier: _nextTaskIdentifier++]; + [task setDownloadCompletionHandler: completionHandler]; + + [self addTask: task]; + + return AUTORELEASE(task); +} + +- (NSURLSessionDownloadTask*) downloadTaskWithURL: (NSURL*)url + completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler +{ + NSMutableURLRequest *request; + + request = [NSMutableURLRequest requestWithURL: url]; + [request setHTTPMethod: @"GET"]; + + return [self downloadTaskWithRequest: request + completionHandler: completionHandler]; +} + +- (NSURLSessionDownloadTask*) downloadTaskWithResumeData: (NSData*)resumeData + completionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler +{ + return [self notImplemented: _cmd]; +} + +@end + @implementation _NSURLProtocolClient @@ -503,16 +680,20 @@ - (void) URLProtocol: (NSURLProtocol *)protocol [[session delegateQueue] addOperationWithBlock: ^{ if ([delegate respondsToSelector: @selector - (URLSession:dataTask:didReceiveResponse:completionHandler:)]) + (URLSession:dataTask:didReceiveResponse:completionHandler:)]) { - NSURLSessionDataTask *dataTask = (NSURLSessionDataTask*)task; + NSURLSessionDataTask *dataTask = (NSURLSessionDataTask*)task; [(id)delegate URLSession: session - dataTask: dataTask - didReceiveResponse: response - completionHandler: - ^(NSURLSessionResponseDisposition disposition) { - NSLog(@"Ignoring disposition from completion handler."); + dataTask: dataTask + didReceiveResponse: response + completionHandler: + ^(NSURLSessionResponseDisposition disposition) { + if (disposition != NSURLSessionResponseAllow) + { + NSLog(@"Warning: ignoring disposition %d from completion handler", + (int)disposition); + } }]; } }]; @@ -547,13 +728,13 @@ - (NSURLProtectionSpace*) _protectionSpaceFrom: (NSHTTPURLResponse*)response method = [[auth componentsSeparatedByString: @" "] firstObject]; range = [auth rangeOfString: @"realm="]; realm = range.length > 0 - ? [auth substringFromIndex: NSMaxRange(range)] : @""; + ? [auth substringFromIndex: NSMaxRange(range)] : @""; space = AUTORELEASE([[NSURLProtectionSpace alloc] - initWithHost: host - port: [port integerValue] - protocol: scheme - realm: realm - authenticationMethod: method]); + initWithHost: host + port: [port integerValue] + protocol: scheme + realm: realm + authenticationMethod: method]); } return space; } @@ -566,6 +747,8 @@ - (void) URLProtocolDidFinishLoading: (NSURLProtocol *)protocol NSURLCache *cache; NSOperationQueue *delegateQueue; id delegate; + void (^dataCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error); + void (^downloadCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error); NSAssert(nil != task, @"Missing task"); @@ -577,12 +760,14 @@ - (void) URLProtocolDidFinishLoading: (NSURLProtocol *)protocol NSURLProtectionSpace *space; if (nil != (space = [self _protectionSpaceFrom: urlResponse])) - { - } + { + } } delegate = [session delegate]; delegateQueue = [session delegateQueue]; + dataCompletionHandler = [task dataCompletionHandler]; + downloadCompletionHandler = [task downloadCompletionHandler]; if (nil != (cache = [[session configuration] URLCache]) && [task isKindOfClass: [NSURLSessionDataTask class]] @@ -610,7 +795,29 @@ - (void) URLProtocolDidFinishLoading: (NSURLProtocol *)protocol RELEASE(cacheable); } - if (nil != delegate) + if (nil != dataCompletionHandler + && ([task isKindOfClass: [NSURLSessionDataTask class]] + || [task isKindOfClass: [NSURLSessionUploadTask class]])) + { + [delegateQueue addOperationWithBlock: + ^{ + dataCompletionHandler(nil, urlResponse, nil); + }]; + } + else if (nil != downloadCompletionHandler + && [task isKindOfClass: [NSURLSessionDownloadTask class]]) + { + NSURL *fileURL; + + fileURL = [NSURLProtocol propertyForKey: @"tempFileURL" + inRequest: [protocol request]]; + + [delegateQueue addOperationWithBlock: + ^{ + downloadCompletionHandler(fileURL, urlResponse, nil); + }]; + } + else if (nil != delegate) { // Send delegate with temporary fileURL if ([task isKindOfClass: [NSURLSessionDownloadTask class]] @@ -641,7 +848,7 @@ - (void) URLProtocolDidFinishLoading: (NSURLProtocol *)protocol } if ([delegate respondsToSelector: - @selector(URLSession:task:didCompleteWithError:)]) + @selector(URLSession:task:didCompleteWithError:)]) { [(id)delegate URLSession: session task: task @@ -687,7 +894,10 @@ @implementation NSURLSessionTask NSURLProtocol *_protocol; NSMutableArray *_protocolBag; Class _protocolClass; + void (^_dataCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error); + void (^_downloadCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error); BOOL _hasTriggeredResume; + float _priority; } - (instancetype) initWithSession: (NSURLSession*)session @@ -733,6 +943,7 @@ - (instancetype) initWithSession: (NSURLSession*)session _protocolState = NSURLSessionTaskProtocolStateToBeCreated; _protocol = nil; _hasTriggeredResume = NO; + _priority = NSURLSessionTaskPriorityDefault; e = [[[session configuration] protocolClasses] objectEnumerator]; while (nil != (protocolClass = [e nextObject])) { @@ -757,6 +968,7 @@ - (void) dealloc DESTROY(_protocolLock); DESTROY(_protocol); DESTROY(_protocolBag); + DESTROY(_dataCompletionHandler); DESTROY(_knownBody); [super dealloc]; } @@ -972,6 +1184,16 @@ - (void) resume }); } +- (float) priority +{ + return _priority;; +} + +- (void) setPriority: (float)priority +{ + _priority = priority; +} + - (id) copyWithZone: (NSZone*)zone { NSURLSessionTask *copy = [[[self class] alloc] init]; @@ -1139,6 +1361,26 @@ - (void) invalidateProtocol [_protocolLock unlock]; } +- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler +{ + return _dataCompletionHandler; +} + +- (void) setDataCompletionHandler: (void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler +{ + ASSIGN(_dataCompletionHandler, completionHandler); +} + +- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler +{ + return _downloadCompletionHandler; +} + +- (void) setDownloadCompletionHandler: (void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler +{ + ASSIGN(_downloadCompletionHandler, completionHandler); +} + @end @implementation NSURLSessionDataTask @@ -1170,6 +1412,19 @@ + (NSURLSessionConfiguration*) defaultSessionConfiguration return AUTORELEASE([def copy]); } ++ (NSURLSessionConfiguration*) ephemeralSessionConfiguration +{ + // return default session since we don't store any data on disk anyway + return AUTORELEASE([def copy]); +} + ++ (NSURLSessionConfiguration*) backgroundSessionConfigurationWithIdentifier:(NSString*)identifier +{ + NSURLSessionConfiguration *configuration = [def copy]; + configuration->_identifier = [identifier copy]; + return AUTORELEASE(configuration); +} + - (instancetype) init { if (nil != (self = [super init])) @@ -1190,6 +1445,7 @@ - (instancetype) init - (void) dealloc { + DESTROY(_identifier); DESTROY(_HTTPAdditionalHeaders); DESTROY(_HTTPCookieStorage); DESTROY(_protocolClasses); @@ -1198,6 +1454,11 @@ - (void) dealloc [super dealloc]; } +- (NSString*) identifier +{ + return _identifier; +} + - (NSURLCache*) URLCache { return _URLCache; @@ -1318,7 +1579,7 @@ - (NSURLRequest*) setCookiesOnRequest: (NSURLRequest*)request NSString *cookieValue; cookiesHeaderFields - = [NSHTTPCookie requestHeaderFieldsWithCookies: cookies]; + = [NSHTTPCookie requestHeaderFieldsWithCookies: cookies]; cookieValue = [cookiesHeaderFields objectForKey: @"Cookie"]; if (nil != cookieValue && [cookieValue length] > 0) { @@ -1342,6 +1603,7 @@ - (id) copyWithZone: (NSZone*)zone if (copy) { + copy->_identifier = [_identifier copy]; copy->_URLCache = [_URLCache copy]; copy->_URLCredentialStorage = [_URLCredentialStorage copy]; copy->_protocolClasses = [_protocolClasses copyWithZone: zone]; @@ -1351,7 +1613,7 @@ - (id) copyWithZone: (NSZone*)zone copy->_HTTPCookieStorage = [_HTTPCookieStorage copy]; copy->_HTTPShouldSetCookies = _HTTPShouldSetCookies; copy->_HTTPAdditionalHeaders - = [_HTTPAdditionalHeaders copyWithZone: zone]; + = [_HTTPAdditionalHeaders copyWithZone: zone]; } return copy; From e3c433541b0ace5da8bfa866901af9f84559d0c5 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Fri, 13 Jan 2023 12:48:22 +0100 Subject: [PATCH 02/14] Fix NSURLSession memory management of libdispatch objects and overrelease in GSHTTPURLProtocol. --- ChangeLog | 11 +++++++++++ Source/GSEasyHandle.m | 15 +++++++++++---- Source/GSHTTPURLProtocol.m | 11 ++++------- Source/GSMultiHandle.m | 8 ++++++++ Source/GSTimeoutSource.h | 2 ++ Source/GSTimeoutSource.m | 15 +++++++++++++-- Source/NSURLSession.m | 2 ++ 7 files changed, 51 insertions(+), 13 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9ec730005b..134232408e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2023-01-13 Frederik Seiffert + + * Source/GSEasyHandle.m: + * Source/GSMultiHandle.m: + * Source/GSTimeoutSource.h: + * Source/GSTimeoutSource.m: + * Source/NSURLSession.m: + Fix NSURLSession memory management of libdispatch objects. + * Source/GSHTTPURLProtocol.m: + Fix overrelease. + 2023-01-13 Frederik Seiffert * Headers/Foundation/NSURLSession.h: diff --git a/Source/GSEasyHandle.m b/Source/GSEasyHandle.m index 3df174162e..62729690ed 100644 --- a/Source/GSEasyHandle.m +++ b/Source/GSEasyHandle.m @@ -195,6 +195,7 @@ - (void) dealloc curl_slist_free_all(_headerList); free(_errorBuffer); DESTROY(_config); + [_timeoutTimer cancel]; DESTROY(_timeoutTimer); DESTROY(_URL); [super dealloc]; @@ -217,6 +218,7 @@ - (GSTimeoutSource*) timeoutTimer - (void) setTimeoutTimer: (GSTimeoutSource*)timer { + [_timeoutTimer cancel]; ASSIGN(_timeoutTimer, timer); } @@ -234,10 +236,15 @@ - (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]]; + if (_timeoutTimer) + { + GSTimeoutSource *oldTimer = _timeoutTimer; + [oldTimer cancel]; + _timeoutTimer = [[GSTimeoutSource alloc] initWithQueue: [oldTimer queue] + milliseconds: [oldTimer milliseconds] + handler: [oldTimer handler]]; + RELEASE(oldTimer); + } } - (void) setupCallbacks diff --git a/Source/GSHTTPURLProtocol.m b/Source/GSHTTPURLProtocol.m index 9ca820d077..8640c918dd 100644 --- a/Source/GSHTTPURLProtocol.m +++ b/Source/GSHTTPURLProtocol.m @@ -592,17 +592,14 @@ - (void) configureEasyHandleForRequest: (NSURLRequest*)request [hh addEntriesFromDictionary: [self transformLowercaseKeyForHTTPHeaders: HTTPHeaders]]; - NSArray *curlHeaders = [self curlHeadersForHTTPHeaders: hh]; + NSMutableArray *curlHeaders = [self curlHeadersForHTTPHeaders: hh]; if ([[request HTTPMethod] isEqualToString:@"POST"] && [[request HTTPBody] length] > 0 && [request valueForHTTPHeaderField: @"Content-Type"] == nil) { - NSMutableArray *temp = [curlHeaders mutableCopy]; - [temp addObject: @"Content-Type:application/x-www-form-urlencoded"]; - curlHeaders = temp; + [curlHeaders addObject: @"Content-Type:application/x-www-form-urlencoded"]; } [_easyHandle setCustomHeaders: curlHeaders]; - RELEASE(curlHeaders); NSInteger timeoutInterval = [request timeoutInterval] * 1000; GSTimeoutSource *timeoutTimer; @@ -874,7 +871,7 @@ - (NSDictionary*) transformLowercaseKeyForHTTPHeaders: (NSDictionary*)HTTPHeader // expects. // // - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html -- (NSArray*) curlHeadersForHTTPHeaders: (NSDictionary*)HTTPHeaders +- (NSMutableArray*) curlHeadersForHTTPHeaders: (NSDictionary*)HTTPHeaders { NSMutableArray *result = [NSMutableArray array]; NSMutableSet *names = [NSMutableSet set]; @@ -951,7 +948,7 @@ - (NSArray*) curlHeadersForHTTPHeaders: (NSDictionary*)HTTPHeaders [result addObject: [NSString stringWithFormat: @"%@:", k]]; } - return AUTORELEASE([result copy]); + return result; } // Any header values that should be passed to libcurl diff --git a/Source/GSMultiHandle.m b/Source/GSMultiHandle.m index 125451caf1..2f2b0ac0b3 100644 --- a/Source/GSMultiHandle.m +++ b/Source/GSMultiHandle.m @@ -108,8 +108,11 @@ - (void) dealloc NSEnumerator *e; GSEasyHandle *handle; + [_timeoutSource cancel]; DESTROY(_timeoutSource); + dispatch_release(_queue); + e = [_easyHandles objectEnumerator]; while (nil != (handle = [e nextObject])) { @@ -193,10 +196,12 @@ - (void) updateTimeoutTimerToValue: (NSInteger)value // of milliseconds. if (-1 == value) { + [_timeoutSource cancel]; DESTROY(_timeoutSource); } else if (0 == value) { + [_timeoutSource cancel]; DESTROY(_timeoutSource); dispatch_async(_queue, ^{ @@ -207,6 +212,7 @@ - (void) updateTimeoutTimerToValue: (NSInteger)value { if (nil == _timeoutSource || value != [_timeoutSource milliseconds]) { + [_timeoutSource cancel]; DESTROY(_timeoutSource); _timeoutSource = [[GSTimeoutSource alloc] initWithQueue: _queue milliseconds: value @@ -438,12 +444,14 @@ - (void) dealloc if (_readSource) { dispatch_source_cancel(_readSource); + dispatch_release(_readSource); } _readSource = NULL; if (_writeSource) { dispatch_source_cancel(_writeSource); + dispatch_release(_writeSource); } _writeSource = NULL; [super dealloc]; diff --git a/Source/GSTimeoutSource.h b/Source/GSTimeoutSource.h index 6743f1e891..03da63d043 100644 --- a/Source/GSTimeoutSource.h +++ b/Source/GSTimeoutSource.h @@ -17,6 +17,8 @@ dispatch_block_t _handler; } +- (void) cancel; + - (NSInteger) milliseconds; - (dispatch_queue_t) queue; diff --git a/Source/GSTimeoutSource.m b/Source/GSTimeoutSource.m index 26b05199e9..d80a6695c3 100644 --- a/Source/GSTimeoutSource.m +++ b/Source/GSTimeoutSource.m @@ -9,7 +9,7 @@ - (instancetype) initWithQueue: (dispatch_queue_t)queue if (nil != (self = [super init])) { _queue = queue; - _handler = handler; + _handler = Block_copy(handler); _milliseconds = milliseconds; _rawSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _queue); @@ -26,10 +26,21 @@ - (instancetype) initWithQueue: (dispatch_queue_t)queue - (void) dealloc { - dispatch_source_cancel(_rawSource); + [self cancel]; + Block_release(_handler); [super dealloc]; } +- (void) cancel +{ + if (_rawSource) + { + dispatch_source_cancel(_rawSource); + dispatch_release(_rawSource); + _rawSource = NULL; + } +} + - (NSInteger) milliseconds { return _milliseconds; diff --git a/Source/NSURLSession.m b/Source/NSURLSession.m index 8b4d822328..8c5ef0f149 100644 --- a/Source/NSURLSession.m +++ b/Source/NSURLSession.m @@ -970,6 +970,7 @@ - (void) dealloc DESTROY(_protocolBag); DESTROY(_dataCompletionHandler); DESTROY(_knownBody); + dispatch_release(_workQueue); [super dealloc]; } @@ -1212,6 +1213,7 @@ - (id) copyWithZone: (NSZone*)zone copy->_state = _state; copy->_error = [_error copyWithZone: zone]; copy->_session = _session; + dispatch_retain(_workQueue); copy->_workQueue = _workQueue; copy->_suspendCount = _suspendCount; copy->_protocolLock = [_protocolLock copy]; From cabf3a334d1a8a02e248687047a22e0e595bfd9e Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Fri, 13 Jan 2023 12:56:42 +0100 Subject: [PATCH 03/14] Fix NSURLSession header fields not always being matched case insensitive. --- ChangeLog | 8 ++++++++ Source/Additions/GSInsensitiveDictionary.m | 7 +++++++ Source/Additions/GSMime.m | 1 - Source/NSURLRequest.m | 2 +- Source/NSURLResponse.m | 9 ++++++++- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 134232408e..c806f235a2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2023-01-13 Frederik Seiffert + + * Source/Additions/GSInsensitiveDictionary.m: + * Source/NSURLRequest.m: + * Source/NSURLResponse.m: + Fix NSURLSession header fields not always being matched case + insensitive. + 2023-01-13 Frederik Seiffert * Source/GSEasyHandle.m: diff --git a/Source/Additions/GSInsensitiveDictionary.m b/Source/Additions/GSInsensitiveDictionary.m index 6400bb57b1..b383ed0009 100644 --- a/Source/Additions/GSInsensitiveDictionary.m +++ b/Source/Additions/GSInsensitiveDictionary.m @@ -378,6 +378,13 @@ - (id) copyWithZone: (NSZone*)zone return [copy initWithDictionary: self copyItems: NO]; } +- (id) mutableCopyWithZone: (NSZone*)z +{ + NSMutableDictionary *copy = [_GSMutableInsensitiveDictionary allocWithZone: z]; + + return [copy initWithDictionary: self copyItems: NO]; +} + - (id) init { return [self initWithCapacity: 0]; diff --git a/Source/Additions/GSMime.m b/Source/Additions/GSMime.m index 89f69aa7ff..2b73562d8a 100644 --- a/Source/Additions/GSMime.m +++ b/Source/Additions/GSMime.m @@ -3345,7 +3345,6 @@ - (BOOL) _scanHeaderParameters: (NSScanner*)scanner into: (GSMimeHeader*)info @end - @interface _GSMutableInsensitiveDictionary : NSMutableDictionary @end diff --git a/Source/NSURLRequest.m b/Source/NSURLRequest.m index beed313a78..b079df8f6b 100644 --- a/Source/NSURLRequest.m +++ b/Source/NSURLRequest.m @@ -118,7 +118,7 @@ - (id) copyWithZone: (NSZone*)z inst->shouldHandleCookies = this->shouldHandleCookies; inst->debug = this->debug; inst->ioDelegate = this->ioDelegate; - inst->headers = [this->headers mutableCopy]; + inst->headers = [this->headers mutableCopy]; } } return o; diff --git a/Source/NSURLResponse.m b/Source/NSURLResponse.m index c68fc2fa17..3e013b39d0 100644 --- a/Source/NSURLResponse.m +++ b/Source/NSURLResponse.m @@ -284,8 +284,15 @@ - (id) initWithURL: (NSURL*)URL textEncodingName: nil]; if (nil != self) { + NSString *k; + NSEnumerator *e = [headerFields keyEnumerator]; + while (nil != (k = [e nextObject])) + { + NSString *v = [headerFields objectForKey: k]; + [self _setValue: v forHTTPHeaderField: k]; + } + this->statusCode = statusCode; - this->headers = [headerFields copy]; [self _checkHeaders]; } return self; From 354df2ae1c30c4e59f2a69d84c7673de0eb41fb4 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Mon, 16 Jan 2023 12:13:54 +0100 Subject: [PATCH 04/14] NSURLSession: optimized timout timer and fixed memory management --- Source/GSEasyHandle.m | 16 +---- Source/GSHTTPURLProtocol.m | 2 +- Source/GSMultiHandle.h | 10 ++-- Source/GSMultiHandle.m | 120 +++++++++++++------------------------ Source/GSTimeoutSource.h | 20 +++---- Source/GSTimeoutSource.m | 69 +++++++++++++-------- 6 files changed, 101 insertions(+), 136 deletions(-) diff --git a/Source/GSEasyHandle.m b/Source/GSEasyHandle.m index 62729690ed..f51d2c9bfb 100644 --- a/Source/GSEasyHandle.m +++ b/Source/GSEasyHandle.m @@ -155,7 +155,7 @@ - (int) seekInputStreamWithOffset: (int64_t)offset text = [NSString stringWithUTF8String: data]; } - NSLog(@"%p %lu %d %@", o, [task taskIdentifier], type, text); + NSLog(@"%p %lu %d %@", o, (unsigned long)[task taskIdentifier], type, text); return 0; } @@ -234,17 +234,7 @@ - (void) transferCompletedWithError: (NSError*)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 - if (_timeoutTimer) - { - GSTimeoutSource *oldTimer = _timeoutTimer; - [oldTimer cancel]; - _timeoutTimer = [[GSTimeoutSource alloc] initWithQueue: [oldTimer queue] - milliseconds: [oldTimer milliseconds] - handler: [oldTimer handler]]; - RELEASE(oldTimer); - } + [_timeoutTimer setTimeout: [_timeoutTimer timeout]]; } - (void) setupCallbacks @@ -415,7 +405,7 @@ - (void) setConnectToHost: (NSString*)host port: (NSInteger)port else { value = [NSString stringWithFormat: @"%@:%lu:%@", - originHost, port, host]; + originHost, (unsigned long)port, host]; } struct curl_slist *connect_to = NULL; diff --git a/Source/GSHTTPURLProtocol.m b/Source/GSHTTPURLProtocol.m index 8640c918dd..c84747091a 100644 --- a/Source/GSHTTPURLProtocol.m +++ b/Source/GSHTTPURLProtocol.m @@ -605,7 +605,6 @@ - (void) configureEasyHandleForRequest: (NSURLRequest*)request GSTimeoutSource *timeoutTimer; timeoutTimer = [[GSTimeoutSource alloc] initWithQueue: [task workQueue] - milliseconds: timeoutInterval handler: ^{ NSError *urlError; @@ -622,6 +621,7 @@ - (void) configureEasyHandleForRequest: (NSURLRequest*)request [client URLProtocol: self didFailWithError: urlError]; } }]; + [timeoutTimer setTimeout: timeoutInterval]; [_easyHandle setTimeoutTimer: timeoutTimer]; RELEASE(timeoutTimer); diff --git a/Source/GSMultiHandle.h b/Source/GSMultiHandle.h index b370075dcc..c075dd9e89 100644 --- a/Source/GSMultiHandle.h +++ b/Source/GSMultiHandle.h @@ -77,12 +77,10 @@ typedef NS_ENUM(NSUInteger, GSSocketRegisterActionType) { 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; +- (dispatch_source_t) createSourceWithType: (dispatch_source_type_t)type + socket: (curl_socket_t)socket + queue: (dispatch_queue_t)queue + handler: (dispatch_block_t)handler; + (instancetype) from: (void*)socketSourcePtr; diff --git a/Source/GSMultiHandle.m b/Source/GSMultiHandle.m index 2f2b0ac0b3..7689cb396f 100644 --- a/Source/GSMultiHandle.m +++ b/Source/GSMultiHandle.m @@ -10,8 +10,7 @@ #import "Foundation/NSURLSession.h" @interface GSMultiHandle () -- (void) performActionForSocket: (int)socket; -- (void) readAndWriteAvailableDataOnSocket: (int)socket; +- (void) readAndWriteAvailableDataOnSocket: (curl_socket_t)socket; - (void) readMessages; - (void) completedTransferForEasyHandle: (CURL*)rawEasyHandle easyCode: (int)easyCode; @@ -196,44 +195,27 @@ - (void) updateTimeoutTimerToValue: (NSInteger)value // of milliseconds. if (-1 == value) { - [_timeoutSource cancel]; - DESTROY(_timeoutSource); - } - else if (0 == value) - { - [_timeoutSource cancel]; - DESTROY(_timeoutSource); - dispatch_async(_queue, - ^{ - [self timeoutTimerFired]; - }); - } + [_timeoutSource suspend]; + } else { - if (nil == _timeoutSource || value != [_timeoutSource milliseconds]) + if (!_timeoutSource) { - [_timeoutSource cancel]; - DESTROY(_timeoutSource); _timeoutSource = [[GSTimeoutSource alloc] initWithQueue: _queue - milliseconds: value handler: ^{ [self timeoutTimerFired]; }]; - } - } -} - -- (void) performActionForSocket: (int)socket -{ - [self readAndWriteAvailableDataOnSocket: socket]; + } + [_timeoutSource setTimeout: value]; + } } -- (void)timeoutTimerFired +- (void) timeoutTimerFired { [self readAndWriteAvailableDataOnSocket: CURL_SOCKET_TIMEOUT]; } -- (void) readAndWriteAvailableDataOnSocket: (int)socket +- (void) readAndWriteAvailableDataOnSocket: (curl_socket_t)socket { int runningHandlesCount = 0; @@ -312,7 +294,7 @@ - (void) completedTransferForEasyHandle: (CURL*)rawEasyHandle [handle transferCompletedWithError: err]; } -- (int32_t) registerWithSocket: (curl_socket_t)socket +- (int32_t) registerWithSocket: (curl_socket_t)socket what: (int)what socketSourcePtr: (void *)socketSourcePtr { @@ -342,6 +324,7 @@ - (int32_t) registerWithSocket: (curl_socket_t)socket && GSSocketRegisterActionTypeUnregister == [action type]) { DESTROY(socketSources); + curl_multi_assign(_rawHandle, socket, NULL); } if (nil != socketSources) @@ -350,7 +333,7 @@ - (int32_t) registerWithSocket: (curl_socket_t)socket socket: socket queue: _queue handler: ^{ - [self performActionForSocket: socket]; + [self readAndWriteAvailableDataOnSocket: socket]; }]; } @@ -400,16 +383,11 @@ - (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; + return YES; + default: + return NO; } } @@ -417,16 +395,11 @@ - (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; + return YES; + default: + return NO; } } @@ -444,16 +417,15 @@ - (void) dealloc if (_readSource) { dispatch_source_cancel(_readSource); - dispatch_release(_readSource); } _readSource = NULL; if (_writeSource) { dispatch_source_cancel(_writeSource); - dispatch_release(_writeSource); } _writeSource = NULL; + [super dealloc]; } @@ -462,50 +434,40 @@ - (void) createSourcesWithAction: (GSSocketRegisterAction*)action queue: (dispatch_queue_t)queue handler: (dispatch_block_t)handler { - if ([action needsReadSource]) + if (!_readSource && [action needsReadSource]) { - [self createReadSourceWithSocket: socket queue: queue handler: handler]; + _readSource = [self createSourceWithType: DISPATCH_SOURCE_TYPE_READ + socket: socket + queue: queue + handler: handler]; } - if ([action needsWriteSource]) + if (!_writeSource && [action needsWriteSource]) { - [self createWriteSourceWithSocket: socket queue: queue handler: handler]; + _writeSource = [self createSourceWithType: DISPATCH_SOURCE_TYPE_WRITE + socket: socket + queue: queue + handler: handler]; } } -- (void) createReadSourceWithSocket: (curl_socket_t)socket - queue: (dispatch_queue_t)queue - handler: (dispatch_block_t)handler +- (dispatch_source_t) createSourceWithType: (dispatch_source_type_t)type + socket: (curl_socket_t)socket + queue: (dispatch_queue_t)queue + handler: (dispatch_block_t)handler { - dispatch_source_t s; + dispatch_source_t source; - if (_readSource) - { - return; - } + source = dispatch_source_create(type, socket, 0, queue); + dispatch_source_set_event_handler(source, handler); + dispatch_source_set_cancel_handler(source, ^{ + dispatch_release(source); + }); + dispatch_resume(source); - s = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket, 0, queue); - dispatch_source_set_event_handler(s, handler); - _readSource = s; - dispatch_resume(s); + return source; } -- (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 { diff --git a/Source/GSTimeoutSource.h b/Source/GSTimeoutSource.h index 03da63d043..2399e7a2a5 100644 --- a/Source/GSTimeoutSource.h +++ b/Source/GSTimeoutSource.h @@ -11,23 +11,21 @@ */ @interface GSTimeoutSource : NSObject { - dispatch_source_t _rawSource; - NSInteger _milliseconds; - dispatch_queue_t _queue; - dispatch_block_t _handler; + dispatch_source_t _timer; + NSInteger _timeoutMs; + bool _isSuspended; } -- (void) cancel; -- (NSInteger) milliseconds; +- (instancetype) initWithQueue: (dispatch_queue_t)queue + handler: (dispatch_block_t)handler; -- (dispatch_queue_t) queue; +- (NSInteger) timeout; +- (void) setTimeout: (NSInteger)timeoutMs; -- (dispatch_block_t) handler; +- (void) suspend; -- (instancetype) initWithQueue: (dispatch_queue_t)queue - milliseconds: (NSInteger)milliseconds - handler: (dispatch_block_t)handler; +- (void) cancel; @end diff --git a/Source/GSTimeoutSource.m b/Source/GSTimeoutSource.m index d80a6695c3..5c5e049e43 100644 --- a/Source/GSTimeoutSource.m +++ b/Source/GSTimeoutSource.m @@ -3,23 +3,19 @@ @implementation GSTimeoutSource - (instancetype) initWithQueue: (dispatch_queue_t)queue - milliseconds: (NSInteger)milliseconds handler: (dispatch_block_t)handler { if (nil != (self = [super init])) { - _queue = queue; - _handler = Block_copy(handler); - _milliseconds = milliseconds; - _rawSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _queue); + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + dispatch_source_set_event_handler(timer, handler); + dispatch_source_set_cancel_handler(timer, ^{ + dispatch_release(timer); + }); - 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); + _timer = timer; + _timeoutMs = -1; + _isSuspended = YES; } return self; } @@ -27,33 +23,54 @@ - (instancetype) initWithQueue: (dispatch_queue_t)queue - (void) dealloc { [self cancel]; - Block_release(_handler); [super dealloc]; } -- (void) cancel +- (NSInteger) timeout { - if (_rawSource) - { - dispatch_source_cancel(_rawSource); - dispatch_release(_rawSource); - _rawSource = NULL; - } + return _timeoutMs; } -- (NSInteger) milliseconds +- (void) setTimeout: (NSInteger)timeoutMs { - return _milliseconds; + if (timeoutMs >= 0) + { + _timeoutMs = timeoutMs; + + dispatch_source_set_timer(_timer, + dispatch_time(DISPATCH_TIME_NOW, timeoutMs * NSEC_PER_MSEC), + DISPATCH_TIME_FOREVER, // don't repeat + timeoutMs * 0.05); // 5% leeway + + if (_isSuspended) + { + _isSuspended = NO; + dispatch_resume(_timer); + } + } + else + { + [self suspend]; + } } -- (dispatch_queue_t) queue +- (void)suspend { - return _queue; + if (!_isSuspended) + { + _isSuspended = YES; + _timeoutMs = -1; + dispatch_suspend(_timer); + } } -- (dispatch_block_t) handler +- (void) cancel { - return _handler; + if (_timer) + { + dispatch_source_cancel(_timer); + _timer = NULL; // released in cancel handler + } } @end \ No newline at end of file From e753c879be327f80ae17a05b105769e69fd55530 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Mon, 16 Jan 2023 12:48:28 +0100 Subject: [PATCH 05/14] NSURLSession: create dispatch queues using target and use lock instead of queue for session identifier Creating stand-alone dispatch queues without a target is discouraged. --- Source/NSURLSession.m | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/Source/NSURLSession.m b/Source/NSURLSession.m index 8c5ef0f149..ff3cb5eebe 100644 --- a/Source/NSURLSession.m +++ b/Source/NSURLSession.m @@ -2,10 +2,11 @@ #import #import "GSDispatch.h" -#import "GSMultiHandle.h" #import "GSEasyHandle.h" -#import "GSTaskRegistry.h" #import "GSHTTPURLProtocol.h" +#import "GSMultiHandle.h" +#import "GSPThread.h" +#import "GSTaskRegistry.h" #import "GSURLSessionTaskBody.h" #import "Foundation/NSError.h" @@ -91,24 +92,20 @@ typedef NS_ENUM(NSUInteger, NSURLSessionTaskProtocolState) { NSURLSessionTaskProtocolStateInvalidated = 3, }; -static dispatch_queue_t _globalVarSyncQ = NULL; -static int sessionCounter = 0; -static int nextSessionIdentifier() +static unsigned nextSessionIdentifier() { - if (NULL == _globalVarSyncQ) - { - _globalVarSyncQ = dispatch_queue_create("org.gnustep.NSURLSession.GlobalVarSyncQ", DISPATCH_QUEUE_SERIAL); - } - dispatch_sync(_globalVarSyncQ, - ^{ - sessionCounter += 1; - }); + static gs_mutex_t lock = GS_MUTEX_INIT_STATIC; + static unsigned sessionCounter = 0; + + GS_MUTEX_LOCK(lock); + sessionCounter += 1; + GS_MUTEX_UNLOCK(lock); + return sessionCounter; } @implementation NSURLSession { - int _identifier; dispatch_queue_t _workQueue; NSUInteger _nextTaskIdentifier; BOOL _invalidated; @@ -162,15 +159,21 @@ - (instancetype) initWithConfiguration: (NSURLSessionConfiguration*)configuratio if (nil != (self = [super init])) { char label[30]; + dispatch_queue_t targetQueue; _taskRegistry = [[GSTaskRegistry alloc] init]; #if defined(CURLSSLBACKEND_GNUTLS) curl_global_sslset(CURLSSLBACKEND_GNUTLS, NULL, NULL)l #endif curl_global_init(CURL_GLOBAL_SSL); - _identifier = nextSessionIdentifier(); - sprintf(label, "NSURLSession %d", _identifier); - _workQueue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL); + sprintf(label, "NSURLSession %u", nextSessionIdentifier()); + targetQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); +#if HAVE_DISPATCH_QUEUE_CREATE_WITH_TARGET + _workQueue = dispatch_queue_create_with_target(label, DISPATCH_QUEUE_SERIAL, targetQueue); +#else + _workQueue = dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL); + dispatch_set_target_queue(_queue, targetQueue); +#endif if (nil != queue) { ASSIGN(_delegateQueue, queue); @@ -596,14 +599,12 @@ - (void) URLProtocol: (NSURLProtocol *)protocol { NSURLSessionTask *task = [protocol task]; NSURLSession *session; - NSOperationQueue *delegateQueue; id delegate; NSAssert(nil != task, @"Missing task"); session = [task session]; delegate = [session delegate]; - delegateQueue = [session delegateQueue]; switch (_cachePolicy) { @@ -625,7 +626,7 @@ - (void) URLProtocol: (NSURLProtocol *)protocol && [delegate respondsToSelector: @selector(URLSession:dataTask:didReceiveData:)]) { - [delegateQueue addOperationWithBlock: + [[session delegateQueue] addOperationWithBlock: ^{ [(id)delegate URLSession: session dataTask: (NSURLSessionDataTask*)task From 719b384157b3768db77e0d6d707ccd9c9bf662ab Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Mon, 16 Jan 2023 15:30:24 +0100 Subject: [PATCH 06/14] NSURLSession: call getTasks callbacks on delegate queue Matches the documented behavior. --- Source/NSURLSession.m | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Source/NSURLSession.m b/Source/NSURLSession.m index ff3cb5eebe..e824ba41df 100644 --- a/Source/NSURLSession.m +++ b/Source/NSURLSession.m @@ -243,7 +243,8 @@ - (void) finishTasksAndInvalidate void (^invalidateSessionCallback)(void) = ^{ if (nil == _delegate) return; - [self.delegateQueue addOperationWithBlock: + + [[self delegateQueue] addOperationWithBlock: ^{ if ([_delegate respondsToSelector: @selector(URLSession:didBecomeInvalidWithError:)]) { @@ -287,7 +288,7 @@ - (void) invalidateAndCancel return; } - [_delegateQueue addOperationWithBlock: + [[self delegateQueue] addOperationWithBlock: ^{ if ([_delegate respondsToSelector: @selector(URLSession:didBecomeInvalidWithError:)]) { @@ -394,14 +395,20 @@ - (void) getTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, NSUR return [task isKindOfClass:[NSURLSessionDownloadTask class]]; }]]; - completionHandler(dataTasks, uploadTasks, downloadTasks); + [[self delegateQueue] addOperationWithBlock: + ^{ + completionHandler(dataTasks, uploadTasks, downloadTasks); + }]; } - (void) getAllTasksWithCompletionHandler: (void (^)(GS_GENERIC_CLASS(NSArray, __kindof NSURLSessionTask*) *tasks))completionHandler { NSArray *allTasks = [_taskRegistry allTasks]; - completionHandler(allTasks); + [[self delegateQueue] addOperationWithBlock: + ^{ + completionHandler(allTasks); + }]; } - (void) addTask: (NSURLSessionTask*)task From 54e0914e1e952b7043e9e20ce0e262fac6a9d879 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Mon, 16 Jan 2023 15:34:12 +0100 Subject: [PATCH 07/14] NSURLSession: use mutable data for transfer state body --- Source/GSTransferState.h | 8 +++---- Source/GSTransferState.m | 45 +++++++--------------------------------- 2 files changed, 11 insertions(+), 42 deletions(-) diff --git a/Source/GSTransferState.h b/Source/GSTransferState.h index 1a89e1c3eb..5de46551e4 100644 --- a/Source/GSTransferState.h +++ b/Source/GSTransferState.h @@ -7,6 +7,7 @@ @class GSURLSessionTaskBodySource; @class NSArray; @class NSData; +@class NSMutableData; @class NSFileHandle; @class NSHTTPURLResponse; @class NSURL; @@ -57,7 +58,7 @@ typedef NS_ENUM(NSUInteger, GSDataDrainType) { @interface GSDataDrain: NSObject { GSDataDrainType _type; - NSData *_data; + NSMutableData *_data; NSURL *_fileURL; NSFileHandle *_fileHandle; } @@ -65,14 +66,11 @@ typedef NS_ENUM(NSUInteger, GSDataDrainType) { - (GSDataDrainType) type; - (void) setType: (GSDataDrainType)type; -- (NSData*) data; -- (void) setData: (NSData*)data; +- (NSMutableData*) data; - (NSURL*) fileURL; -- (void) setFileURL: (NSURL*)url; - (NSFileHandle*) fileHandle; -- (void) setFileHandle: (NSFileHandle*)handle; @end diff --git a/Source/GSTransferState.m b/Source/GSTransferState.m index 07021ca61d..a240b11300 100644 --- a/Source/GSTransferState.m +++ b/Source/GSTransferState.m @@ -268,14 +268,14 @@ - (void) setType: (GSDataDrainType)type _type = type; } -- (NSData*) data +- (NSMutableData*) data { - return _data; -} + if (!_data) + { + _data = [[NSMutableData alloc] init]; + } -- (void) setData: (NSData*)data -{ - ASSIGN(_data, data); + return _data; } - (NSURL*) fileURL @@ -298,11 +298,6 @@ - (NSURL*) fileURL return _fileURL; } -- (void) setFileURL: (NSURL*)url -{ - ASSIGN(_fileURL, url); -} - - (NSFileHandle*) fileHandle { /* Create temporary file and open a fileHandle for writing. */ @@ -319,11 +314,6 @@ - (NSFileHandle*) fileHandle return _fileHandle; } -- (void) setFileHandle: (NSFileHandle*)handle -{ - ASSIGN(_fileHandle, handle); -} - @end @implementation GSTransferState @@ -389,27 +379,8 @@ - (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); - } + [[_bodyDataDrain data] appendData: bodyData]; + return self; case GSDataDrainTypeToFile: { NSFileHandle *fileHandle; From 8303a72d09a59e6eaaf2de5727a1fd822e3c9c09 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Mon, 16 Jan 2023 15:40:43 +0100 Subject: [PATCH 08/14] NSURLSession: implement missing body data for data completion handlers Now using the previously unused "in-memory" body data drain if a task has a completion handler, which requires the full body to be passed on completion. Also consolidated private NSURLSessionTask methods, some of which were previously implemented twice in separate categories with the same name, leading to possible undefined runtime behavior. --- Headers/Foundation/NSURLSession.h | 3 + Source/GSHTTPURLProtocol.m | 36 -------- Source/GSNativeProtocol.h | 29 ++++++ Source/GSNativeProtocol.m | 144 ++++++++++++++---------------- Source/NSURLSession.m | 57 ++++++++---- 5 files changed, 138 insertions(+), 131 deletions(-) diff --git a/Headers/Foundation/NSURLSession.h b/Headers/Foundation/NSURLSession.h index d4b3f8f9f7..02275405d9 100644 --- a/Headers/Foundation/NSURLSession.h +++ b/Headers/Foundation/NSURLSession.h @@ -278,6 +278,9 @@ GS_EXPORT_CLASS NSUInteger _suspendCount; GSURLSessionTaskBody *_knownBody; + + void (^_dataCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error); + void (^_downloadCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error); } - (NSUInteger) taskIdentifier; diff --git a/Source/GSHTTPURLProtocol.m b/Source/GSHTTPURLProtocol.m index c84747091a..643e25a4dd 100644 --- a/Source/GSHTTPURLProtocol.m +++ b/Source/GSHTTPURLProtocol.m @@ -13,44 +13,8 @@ #import "Foundation/NSStream.h" #import "Foundation/NSURL.h" #import "Foundation/NSURLError.h" -#import "Foundation/NSURLSession.h" #import "Foundation/NSValue.h" - -@interface NSURLSessionTask (Internal) - -- (void) setCountOfBytesExpectedToReceive: (int64_t)count; - -- (void) setCountOfBytesExpectedToSend: (int64_t)count; - -- (dispatch_queue_t) workQueue; - -@end - -@implementation NSURLSessionTask (Internal) - -- (void) setCountOfBytesExpectedToReceive: (int64_t)count -{ - _countOfBytesExpectedToReceive = count; -} - -- (void) setCountOfBytesExpectedToSend: (int64_t)count -{ - _countOfBytesExpectedToSend = count; -} - -- (GSURLSessionTaskBody*) knownBody -{ - return _knownBody; -} - -- (dispatch_queue_t) workQueue -{ - return _workQueue; -} - -@end - @interface GSURLCacherHelper : NSObject + (BOOL) canCacheResponse: (NSCachedURLResponse*)response diff --git a/Source/GSNativeProtocol.h b/Source/GSNativeProtocol.h index 487ad902a2..d1a0be7a0c 100644 --- a/Source/GSNativeProtocol.h +++ b/Source/GSNativeProtocol.h @@ -1,11 +1,40 @@ #ifndef INCLUDED_GSNATIVEPROTOCOL_H #define INCLUDED_GSNATIVEPROTOCOL_H +#import "GSDispatch.h" #import "GSEasyHandle.h" #import "Foundation/NSURLProtocol.h" +#import "Foundation/NSURLSession.h" @class GSTransferState; +@interface NSURLSessionTask (GSNativeProtocolInternal) + +- (void) setCurrentRequest: (NSURLRequest*)request; + +- (dispatch_queue_t) workQueue; + +- (NSUInteger) suspendCount; + +- (void) getBodyWithCompletion: (void (^)(GSURLSessionTaskBody *body))completion; + +- (GSURLSessionTaskBody*) knownBody; +- (void) setKnownBody: (GSURLSessionTaskBody*)body; + +- (void) setError: (NSError*)error; + +- (void) setCountOfBytesReceived: (int64_t)count; + +- (void) setCountOfBytesExpectedToReceive: (int64_t)count; + +- (void) setCountOfBytesExpectedToSend: (int64_t)count; + +- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler; + +- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler; + +@end + typedef NS_ENUM(NSUInteger, GSCompletionActionType) { GSCompletionActionTypeCompleteTask, GSCompletionActionTypeFailWithError, diff --git a/Source/GSNativeProtocol.m b/Source/GSNativeProtocol.m index 9fcb80147f..ff5664fed3 100644 --- a/Source/GSNativeProtocol.m +++ b/Source/GSNativeProtocol.m @@ -63,7 +63,7 @@ static BOOL isEasyHandleAddedToMultiHandle(GSNativeProtocolInternalState state) } } -@interface NSURLSession (Internal) +@interface NSURLSession (GSNativeProtocolInternal) - (void) removeHandle: (GSEasyHandle*)handle; @@ -71,7 +71,7 @@ - (void) addHandle: (GSEasyHandle*)handle; @end -@implementation NSURLSession (Internal) +@implementation NSURLSession (GSNativeProtocolInternal) - (void) removeHandle: (GSEasyHandle*)handle { @@ -85,25 +85,7 @@ - (void) addHandle: (GSEasyHandle*)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; - -- (void) setCountOfBytesReceived: (int64_t)count; - -@end - -@implementation NSURLSessionTask (Internal) +@implementation NSURLSessionTask (GSNativeProtocolInternal) - (void) setCurrentRequest: (NSURLRequest*)request { @@ -132,6 +114,11 @@ - (void) getBodyWithCompletion: (void (^)(GSURLSessionTaskBody *body))completion completion(body); } +- (GSURLSessionTaskBody*) knownBody +{ + return _knownBody; +} + - (void) setKnownBody: (GSURLSessionTaskBody*)body { ASSIGN(_knownBody, body); @@ -147,6 +134,26 @@ - (void) setCountOfBytesReceived: (int64_t)count _countOfBytesReceived = count; } +- (void) setCountOfBytesExpectedToReceive: (int64_t)count +{ + _countOfBytesExpectedToReceive = count; +} + +- (void) setCountOfBytesExpectedToSend: (int64_t)count +{ + _countOfBytesExpectedToSend = count; +} + +- (void (^)(NSData *data, NSURLResponse *response, NSError *error)) dataCompletionHandler +{ + return _dataCompletionHandler; +} + +- (void (^)(NSURL *location, NSURLResponse *response, NSError *error)) downloadCompletionHandler +{ + return _downloadCompletionHandler; +} + @end @implementation GSCompletionAction @@ -352,24 +359,29 @@ - (GSTransferState*) createTransferStateWithURL: (NSURL*)url } // The data drain. -// This depends on what the delegate need. +// This depends on what the task needs. - (GSDataDrain*) createTransferBodyDataDrain { - NSURLSession *s = [[self task] session]; + NSURLSessionTask *task = [self task]; GSDataDrain *dd = AUTORELEASE([[GSDataDrain alloc] init]); - - if (nil != [s delegate]) + + if ([task isKindOfClass: [NSURLSessionDownloadTask class]]) { - // 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; + // drain to file for download tasks + [dd setType: GSDataDrainTypeToFile]; + } + else if ([task dataCompletionHandler]) + { + // drain to memory if task has a completion handler, which requires the + // full body to be passed on completion + [dd setType: GSDataDrainInMemory]; } else { + // otherwise the data is probably sent to the delegate as it arrives [dd setType: GSDataDrainTypeIgnore]; - return dd; } + return dd; } - (void) resume @@ -512,6 +524,9 @@ - (void) notifyDelegateAboutReceivedData: (NSData*)data session = [task session]; NSAssert(nil != session, @"Missing session"); + /* Calculate received data length */ + [task setCountOfBytesReceived: (int64_t)[data length] + [task countOfBytesReceived]]; + delegate = [session delegate]; if (nil != delegate && [task isKindOfClass: [NSURLSessionDataTask class]] @@ -522,6 +537,7 @@ - (void) notifyDelegateAboutReceivedData: (NSData*)data dataDelegate = (id)delegate; dataTask = (NSURLSessionDataTask*)task; + [[session delegateQueue] addOperationWithBlock: ^{ [dataDelegate URLSession: session @@ -529,39 +545,26 @@ - (void) notifyDelegateAboutReceivedData: (NSData*)data didReceiveData: data]; }]; } - /* Don't check whether delegate respondsToSelector. - * This delegate is optional. */ + if (nil != delegate - && [task isKindOfClass: [NSURLSessionDownloadTask class]]) + && [task isKindOfClass: [NSURLSessionDownloadTask class]] + && [delegate respondsToSelector: @selector + (URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)]) { id downloadDelegate; NSURLSessionDownloadTask *downloadTask; - GSDataDrain *dataDrain; - NSFileHandle *fileHandle; downloadDelegate = (id)delegate; downloadTask = (NSURLSessionDownloadTask*)task; - dataDrain = [_transferState bodyDataDrain]; - /* Write to file. GSDataDrain opens the fileHandle. */ - fileHandle = [dataDrain fileHandle]; - [fileHandle seekToEndOfFile]; - [fileHandle writeData: data]; - - if ([delegate respondsToSelector: @selector - (URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)]) - { - /* Calculate received data length */ - [task setCountOfBytesReceived: (int64_t)[data length] + [task countOfBytesReceived]]; - [[session delegateQueue] addOperationWithBlock: - ^{ - [downloadDelegate URLSession: session - downloadTask: downloadTask - didWriteData: (int64_t)[data length] - totalBytesWritten: [task countOfBytesReceived] - totalBytesExpectedToWrite: [task countOfBytesExpectedToReceive]]; - }]; - } + [[session delegateQueue] addOperationWithBlock: + ^{ + [downloadDelegate URLSession: session + downloadTask: downloadTask + didWriteData: (int64_t)[data length] + totalBytesWritten: [task countOfBytesReceived] + totalBytesExpectedToWrite: [task countOfBytesExpectedToReceive]]; + }]; } } @@ -719,7 +722,6 @@ - (void) completeTask NSURLSessionTask *task; GSDataDrain *bodyDataDrain; id client; - id delegate; NSAssert(_internalState == GSNativeProtocolInternalStateTransferCompleted, @"Trying to complete the task, but its transfer isn't complete."); @@ -727,40 +729,26 @@ - (void) completeTask task = [self task]; [task setResponse: [_transferState response]]; client = [self client]; - delegate = [[task session] delegate]; // We don't want a timeout to be triggered after this. The timeout timer // needs to be cancelled. [_easyHandle setTimeoutTimer: nil]; + + [self setInternalState: GSNativeProtocolInternalStateTaskCompleted]; - // 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 + // Add complete data to NSURLRequestProperties if the task has a data + // completion handler 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]; + NSData *data = AUTORELEASE([[bodyDataDrain data] copy]); + [[self request] _setProperty: data + forKey: @"tempData"]; } // Add temporary file URL to NSURLRequest properties // and close the fileHandle - if (nil != delegate - && [task isKindOfClass: [NSURLSessionDownloadTask class]]) + if ([task isKindOfClass: [NSURLSessionDownloadTask class]]) { [[bodyDataDrain fileHandle] closeFile]; [[self request] _setProperty: [bodyDataDrain fileURL] diff --git a/Source/NSURLSession.m b/Source/NSURLSession.m index e824ba41df..2d2fc301df 100644 --- a/Source/NSURLSession.m +++ b/Source/NSURLSession.m @@ -806,25 +806,50 @@ - (void) URLProtocolDidFinishLoading: (NSURLProtocol *)protocol if (nil != dataCompletionHandler && ([task isKindOfClass: [NSURLSessionDataTask class]] || [task isKindOfClass: [NSURLSessionUploadTask class]])) - { - [delegateQueue addOperationWithBlock: - ^{ - dataCompletionHandler(nil, urlResponse, nil); - }]; - } + { + NSData *data = [NSURLProtocol propertyForKey: @"tempData" + inRequest: [protocol request]]; + + [delegateQueue addOperationWithBlock: + ^{ + if (NSURLSessionTaskStateCompleted == [task state]) + { + return; + } + + dataCompletionHandler(data, urlResponse, nil); + + [task setState: NSURLSessionTaskStateCompleted]; + + dispatch_async([session workQueue], + ^{ + [session removeTask: task]; + }); + }]; + } else if (nil != downloadCompletionHandler && [task isKindOfClass: [NSURLSessionDownloadTask class]]) - { - NSURL *fileURL; + { + NSURL *fileURL = [NSURLProtocol propertyForKey: @"tempFileURL" + inRequest: [protocol request]]; + + [delegateQueue addOperationWithBlock: + ^{ + if (NSURLSessionTaskStateCompleted == [task state]) + { + return; + } - fileURL = [NSURLProtocol propertyForKey: @"tempFileURL" - inRequest: [protocol request]]; + downloadCompletionHandler(fileURL, urlResponse, nil); - [delegateQueue addOperationWithBlock: - ^{ - downloadCompletionHandler(fileURL, urlResponse, nil); - }]; - } + [task setState: NSURLSessionTaskStateCompleted]; + + dispatch_async([session workQueue], + ^{ + [session removeTask: task]; + }); + }]; + } else if (nil != delegate) { // Send delegate with temporary fileURL @@ -902,8 +927,6 @@ @implementation NSURLSessionTask NSURLProtocol *_protocol; NSMutableArray *_protocolBag; Class _protocolClass; - void (^_dataCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error); - void (^_downloadCompletionHandler)(NSURL *location, NSURLResponse *response, NSError *error); BOOL _hasTriggeredResume; float _priority; } From 2117a63a3d5d8ca5bb3272e6d2c60a899cbe00d6 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Mon, 16 Jan 2023 17:07:13 +0100 Subject: [PATCH 09/14] NSURLSession: improve protocol detection --- Source/NSURLSession.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/NSURLSession.m b/Source/NSURLSession.m index 2d2fc301df..9dbec92e34 100644 --- a/Source/NSURLSession.m +++ b/Source/NSURLSession.m @@ -981,9 +981,10 @@ - (instancetype) initWithSession: (NSURLSession*)session if ([protocolClass canInitWithRequest: request]) { _protocolClass = protocolClass; + break; } } - NSAssert(nil != _protocolClass, @"Unsupported protocol"); + NSAssert(nil != _protocolClass, @"Unsupported protocol for request: %@", request); } return self; From ba0c1e1e9a188da58ac9efc29b48ff52ed8b9790 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Mon, 16 Jan 2023 17:07:32 +0100 Subject: [PATCH 10/14] NSURLSession: fix libcurl debug output --- Source/GSEasyHandle.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GSEasyHandle.m b/Source/GSEasyHandle.m index f51d2c9bfb..576a412055 100644 --- a/Source/GSEasyHandle.m +++ b/Source/GSEasyHandle.m @@ -329,7 +329,7 @@ - (void) setDebugOutput: (BOOL)flag { if (flag) { - handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGDATA, self)); + handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGDATA, task)); handleEasyCode(curl_easy_setopt(_rawHandle, CURLOPT_DEBUGFUNCTION, curl_debug_function)); } From 792b0274e8d8353203637b8890aa67f0ebed9ec3 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Tue, 17 Jan 2023 14:10:30 +0100 Subject: [PATCH 11/14] NSURLSession: fix memory management in GSTransferState --- Source/GSTransferState.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/GSTransferState.m b/Source/GSTransferState.m index a240b11300..f1be36c3aa 100644 --- a/Source/GSTransferState.m +++ b/Source/GSTransferState.m @@ -292,7 +292,7 @@ - (NSURL*) fileURL fileName = [[randomUUID UUIDString] stringByAppendingPathExtension: @"tmp"]; tempURL = [NSURL fileURLWithPath: NSTemporaryDirectory()]; - _fileURL = [NSURL fileURLWithPath: fileName relativeToURL: tempURL]; + _fileURL = RETAIN([NSURL fileURLWithPath: fileName relativeToURL: tempURL]); } return _fileURL; @@ -308,7 +308,7 @@ - (NSFileHandle*) fileHandle contents: nil attributes: nil]; - _fileHandle = [NSFileHandle fileHandleForWritingToURL: [self fileURL] error: NULL]; + _fileHandle = RETAIN([NSFileHandle fileHandleForWritingToURL: [self fileURL] error: NULL]); } return _fileHandle; From 6e1d53dfecc3f5fd850780cdb7f1a747b984ef5e Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Thu, 16 Feb 2023 16:04:10 +0100 Subject: [PATCH 12/14] NSURLSession: mark broken test as hopeful on Windows --- Tests/base/NSURLSession/test01.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/base/NSURLSession/test01.m b/Tests/base/NSURLSession/test01.m index 3fd8c8c6ed..8a2e9fe0a8 100644 --- a/Tests/base/NSURLSession/test01.m +++ b/Tests/base/NSURLSession/test01.m @@ -90,6 +90,11 @@ int main() NSString *params; MyDelegate *object; +#if defined(_WIN32) + NSLog(@"Marking nonexistant host test as hopeful on Windows as it seems to be broken"); + testHopeful = YES; +#endif + object = AUTORELEASE([MyDelegate new]); mainQueue = [NSOperationQueue mainQueue]; defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration]; @@ -126,6 +131,10 @@ int main() PASS([object->taskError code] == NSURLErrorCannotConnectToHost, "unable to connect to host") +#if defined(_WIN32) + testHopeful = NO; +#endif + #endif END_SET("NSURLSession test01") return 0; From 1b5117f992f268c18db55fe4cd6f82249f6cda49 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Fri, 17 Feb 2023 11:06:15 +0100 Subject: [PATCH 13/14] CI: add tools-make/tools-windows-msvc branch name to run name --- .github/workflows/main.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 004a931f95..300cf4f71a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,12 @@ name: CI +run-name: >- + ${{ + join(fromJSON(format('["{0}", "{1}", "{2}"]', + ((inputs.tools_make_branch != '' && inputs.tools_make_branch != 'master') || inputs.tools_windows_msvc_branch != '') && github.workflow || '', + (inputs.tools_make_branch != '' && inputs.tools_make_branch != 'master') && format('tools-make: {0}', inputs.tools_make_branch) || '', + inputs.tools_windows_msvc_branch != '' && format('tools-windows-msvc: {0}', inputs.tools_windows_msvc_branch) || '' + )), ' ') + }} on: push: From cb7e82e9dc2d71907cec36abda317c8d55d02672 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Thu, 16 Feb 2023 17:15:27 +0100 Subject: [PATCH 14/14] CI: fix MSVC builds when building dependencies --- .github/workflows/main.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 300cf4f71a..fc826934e0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -236,14 +236,19 @@ jobs: if: env.IS_WINDOWS_MSVC == 'true' with: msystem: MSYS - install: make autoconf automake libtool + install: make autoconf automake libtool pkg-config # make Windows packages like Clang available in MSYS path-type: inherit - - name: Delete MinGW gmake (MSVC) + - name: Remove Perl Strawberry installation and MinGW gmake (MSVC) if: env.IS_WINDOWS_MSVC == 'true' - # delete /c/Strawberry/c/bin/gmake built for MinGW that is found on runners, because we must use make built for MSYS - run: if GMAKE_PATH=`which gmake`; then rm -f "$GMAKE_PATH"; fi + # C:\Strawberry contains various MinGW libraries and binaries like pkg-config + # that can get picked up by configure/CMake and don't necessarily behave + # correctly when not using a MinGW environment, and more specifically we cannot + # use MinGW gmake but must use MSYS make for correctly handling of Windows paths, + # so we delete everything that could mess up our builds + run: rmdir /S /Q C:\Strawberry + shell: cmd - name: Install Windows packages (MSVC) if: env.IS_WINDOWS_MSVC == 'true'