diff --git a/Plugins/HTTPSource/HTTPSource.h b/Plugins/HTTPSource/HTTPSource.h index 88ebe93f0..4ca7b5d62 100644 --- a/Plugins/HTTPSource/HTTPSource.h +++ b/Plugins/HTTPSource/HTTPSource.h @@ -12,10 +12,20 @@ @class HTTPConnection; -@interface HTTPSource : NSObject +@interface HTTPSource : NSObject { - HTTPConnection *_connection; + NSOperationQueue * queue; + + NSURL * URL; + NSURLSession * session; + NSURLSessionDataTask * task; + + Boolean cancelled; + Boolean errorOccurred; + + NSMutableArray * bufferedData; + long _bytesBuffered; long _byteCount; NSString *_mimeType; diff --git a/Plugins/HTTPSource/HTTPSource.m b/Plugins/HTTPSource/HTTPSource.m index 7fc2da82a..450cfcf7c 100644 --- a/Plugins/HTTPSource/HTTPSource.m +++ b/Plugins/HTTPSource/HTTPSource.m @@ -3,33 +3,81 @@ // HTTPSource // // Created by Vincent Spader on 3/1/07. -// Copyright 2007 __MyCompanyName__. All rights reserved. +// Replaced by Christopher Snowhill on 3/7/20. +// Copyright 2020 __LoSnoCo__. All rights reserved. // #import "HTTPSource.h" -#import "HTTPConnection.h" #import "Logging.h" +#define BUFFER_SIZE 131072 + @implementation HTTPSource +- (NSURLSession *)createSession +{ + queue = [[NSOperationQueue alloc] init]; + [queue setMaxConcurrentOperationCount:1]; + + NSURLSession *session = nil; + session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] + delegate:self + delegateQueue:queue]; + return session; +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data{ + long bytesBuffered = 0; + if (cancelled) return; + @synchronized(bufferedData) { + [bufferedData addObject:data]; + _bytesBuffered += [data length]; + bytesBuffered = _bytesBuffered; + } + if (bytesBuffered > BUFFER_SIZE) { + [task suspend]; + } +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask + willCacheResponse:(NSCachedURLResponse *)proposedResponse + completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler{ +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didCompleteWithError:(NSError *)error{ + cancelled = YES; + errorOccurred = YES; +} + - (BOOL)open:(NSURL *)url { - _connection = [[HTTPConnection alloc] initWithURL:url]; - - // Note: The User-Agent CANNOT contain the string "Mozilla" or Shoutcast/Icecast will serve up HTML - NSString *userAgent = [NSString stringWithFormat:@"Cog %@", [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]]; - [_connection setValue:userAgent forRequestHeader:@"User-Agent"]; - [_connection setValue:@"close" forRequestHeader:@"Connection"]; - [_connection setValue:@"*/*" forRequestHeader:@"Accept"]; - - BOOL success = [_connection connect]; - if (NO == success) { - return NO; - } - - _mimeType = [[_connection valueForResponseHeader:@"Content-type"] copy]; - + cancelled = NO; + errorOccurred = NO; + + bufferedData = [[NSMutableArray alloc] init]; + + URL = url; + NSURLRequest * request = [NSURLRequest requestWithURL:url]; + session = [self createSession]; + task = [session dataTaskWithRequest:request]; + [task resume]; + + NSURLResponse * response = nil; + while (!response) { + response = [task response]; + if (response) break; + if (errorOccurred) return NO; + usleep(100); + } + if (response) { + _mimeType = [response MIMEType]; + } + return YES; } @@ -57,14 +105,55 @@ - (long)read:(void *)buffer amount:(long)amount { long totalRead = 0; + long bytesBuffered = 0; while (totalRead < amount) { - NSInteger amountReceived = [_connection receiveData:((uint8_t *)buffer) + totalRead amount:amount - totalRead]; + NSData * dataBlock = nil; + @synchronized(bufferedData) { + if ([bufferedData count]) + dataBlock = [bufferedData objectAtIndex:0]; + } + if (!dataBlock) { + if (errorOccurred) return totalRead; + if (cancelled) return 0; + usleep(1000); + continue; + } + NSInteger amountReceived = [dataBlock length]; if (amountReceived <= 0) { break; } + + NSInteger amountUsed = amountReceived; + + if (amountUsed > (amount - totalRead) ) + amountUsed = amount - totalRead; + + const void * dataBytes = [dataBlock bytes]; + memcpy(((uint8_t *)buffer) + totalRead, dataBytes, amountUsed); + + if (amountUsed < amountReceived) { + NSData * dataOut = [NSData dataWithBytes:(((uint8_t *)dataBytes) + amountUsed) length:(amountReceived - amountUsed)]; + @synchronized(bufferedData) { + [bufferedData removeObjectAtIndex:0]; + [bufferedData insertObject:dataOut atIndex:0]; + _bytesBuffered -= amountUsed; + bytesBuffered = _bytesBuffered; + } + } + else { + @synchronized(bufferedData) { + [bufferedData removeObjectAtIndex:0]; + _bytesBuffered -= amountUsed; + bytesBuffered = _bytesBuffered; + } + } + + if (bytesBuffered <= (BUFFER_SIZE * 3 / 4)) { + [task resume]; + } - totalRead += amountReceived; + totalRead += amountUsed; } _byteCount += totalRead; @@ -74,8 +163,10 @@ - (void)close { - [_connection close]; - _connection = nil; + cancelled = YES; + + [task cancel]; + task = nil; _mimeType = nil; } @@ -88,7 +179,7 @@ - (NSURL *)url { - return [_connection URL]; + return URL; } + (NSArray *)schemes diff --git a/Plugins/HTTPSource/HTTPSource.xcodeproj/project.pbxproj b/Plugins/HTTPSource/HTTPSource.xcodeproj/project.pbxproj index b1f4f608c..5525d0d41 100644 --- a/Plugins/HTTPSource/HTTPSource.xcodeproj/project.pbxproj +++ b/Plugins/HTTPSource/HTTPSource.xcodeproj/project.pbxproj @@ -7,8 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 1716067D0F624E09008FA424 /* Socket.m in Sources */ = {isa = PBXBuildFile; fileRef = 1716067C0F624E09008FA424 /* Socket.m */; }; - 1716068A0F624E27008FA424 /* HTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 171606870F624E27008FA424 /* HTTPConnection.m */; }; 1716093A0F627F02008FA424 /* HTTPSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 171609390F627F02008FA424 /* HTTPSource.m */; }; 8D5B49B4048680CD000E48DA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -29,10 +27,6 @@ 089C1672FE841209C02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 089C167FFE841241C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; - 1716067B0F624E09008FA424 /* Socket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Socket.h; sourceTree = ""; }; - 1716067C0F624E09008FA424 /* Socket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Socket.m; sourceTree = ""; }; - 171606860F624E27008FA424 /* HTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnection.h; sourceTree = ""; }; - 171606870F624E27008FA424 /* HTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPConnection.m; sourceTree = ""; }; 171609390F627F02008FA424 /* HTTPSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPSource.m; sourceTree = ""; }; 17ADB60C0B97A74800257CA2 /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HTTPSource.h; sourceTree = ""; }; 17ADB6340B97A8B400257CA2 /* Plugin.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Plugin.h; path = ../../Audio/Plugin.h; sourceTree = SOURCE_ROOT; }; @@ -59,7 +53,6 @@ isa = PBXGroup; children = ( 08FB77AFFE84173DC02AAC07 /* Classes */, - 1716067A0F624E09008FA424 /* Utils */, 32C88E010371C26100C91783 /* Other Sources */, 089C167CFE841241C02AAC07 /* Resources */, 089C1671FE841209C02AAC07 /* Frameworks and Libraries */, @@ -114,17 +107,6 @@ name = "Other Frameworks"; sourceTree = ""; }; - 1716067A0F624E09008FA424 /* Utils */ = { - isa = PBXGroup; - children = ( - 171606860F624E27008FA424 /* HTTPConnection.h */, - 171606870F624E27008FA424 /* HTTPConnection.m */, - 1716067B0F624E09008FA424 /* Socket.h */, - 1716067C0F624E09008FA424 /* Socket.m */, - ); - path = Utils; - sourceTree = ""; - }; 19C28FB8FE9D52D311CA2CBB /* Products */ = { isa = PBXGroup; children = ( @@ -209,8 +191,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1716067D0F624E09008FA424 /* Socket.m in Sources */, - 1716068A0F624E27008FA424 /* HTTPConnection.m in Sources */, 1716093A0F627F02008FA424 /* HTTPSource.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -232,6 +212,7 @@ GCC_PREFIX_HEADER = HTTPSource_Prefix.pch; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Library/Bundles"; + MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = org.cogx.httpsource; PRODUCT_NAME = HTTPSource; SDKROOT = macosx; @@ -252,6 +233,7 @@ GCC_PREFIX_HEADER = HTTPSource_Prefix.pch; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Library/Bundles"; + MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = org.cogx.httpsource; PRODUCT_NAME = HTTPSource; SDKROOT = macosx; diff --git a/Plugins/HTTPSource/Utils/HTTPConnection.h b/Plugins/HTTPSource/Utils/HTTPConnection.h deleted file mode 100644 index 778a647b8..000000000 --- a/Plugins/HTTPSource/Utils/HTTPConnection.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// HTTPConnection.h -// HTTPSource -// -// Created by Vincent Spader on 3/6/09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - -#import - -#import "Socket.h" - -@interface HTTPConnection : NSObject { - Socket *_socket; - NSURL *_URL; - - NSMutableDictionary *_requestHeaders; - NSMutableDictionary *_responseHeaders; - - uint8_t *_buffer; - NSInteger _bufferSize; -} - -- (id)initWithURL:(NSURL *)url; - -- (BOOL)connect; -- (void)close; - -- (NSInteger)receiveData:(void *)bytes amount:(NSInteger)amount; - -- (void)setValue:(NSString *)value forRequestHeader:(NSString *)header; -- (NSString *)valueForResponseHeader:(NSString *)header; - -@property(copy) NSURL *URL; - -@end diff --git a/Plugins/HTTPSource/Utils/HTTPConnection.m b/Plugins/HTTPSource/Utils/HTTPConnection.m deleted file mode 100644 index c04f915a4..000000000 --- a/Plugins/HTTPSource/Utils/HTTPConnection.m +++ /dev/null @@ -1,303 +0,0 @@ -// -// HTTPConnection.m -// HTTPSource -// -// Created by Vincent Spader on 3/6/09. -// Copyright 2009 __MyCompanyName__. All rights reserved. -// - -#import "HTTPConnection.h" - -#include - -#import "Logging.h" - -@implementation HTTPConnection - -@synthesize URL = _URL; - -- (id)initWithURL:(NSURL *)url -{ - self = [super init]; - if (self) - { - [self setURL:url]; - - _requestHeaders = nil; - _responseHeaders = nil; - _buffer = NULL; - _bufferSize = 0; - } - - return self; -} - -- (void)dealloc -{ - [self setURL:nil]; -} - -- (void)setValue:(NSString *)value forRequestHeader:(NSString *)header -{ - if (nil == _requestHeaders) { - _requestHeaders = [[NSMutableDictionary alloc] init]; - } - - [_requestHeaders setObject:value forKey:header]; -} - -// Only filled in after a successful -connect -- (NSString *)valueForResponseHeader:(NSString *)header -{ - return [_responseHeaders objectForKey:[header lowercaseString]]; -} - -- (NSString *)_receiveLine -{ - const NSInteger requestAmount = 1024; - - void *newLine = strnstr((const char *)_buffer, "\r\n", _bufferSize); - while (NULL == newLine) { - NSInteger bufferAmount = _bufferSize; - - // Note that _bufferSize may be 0 and _buffer may be NULL - _bufferSize += requestAmount; - _buffer = realloc(_buffer, _bufferSize); - if (NULL == _buffer) { - return nil; - } - - NSInteger amountReceived = [_socket receive:((uint8_t *)_buffer)+bufferAmount amount:requestAmount]; - if (amountReceived <= 0) { - // -close will free _buffer - return nil; - } - - if (amountReceived < requestAmount) { - _bufferSize = bufferAmount + amountReceived; - } - - newLine = strnstr((const char *)_buffer, "\r\n", _bufferSize); - } - - NSInteger lineLength = ((uint8_t *)newLine - (uint8_t *)_buffer); - - // We are using ASCII encoding here because some Icecast servers will insert a random 0xaa or two into the headers - // Or I'm an idiot who doesn't know how to count (fixed now), but I don't remember what site I was seeing this on, so I can't really check. - NSString *line = [[NSString alloc] initWithBytes:_buffer length:lineLength encoding:NSASCIIStringEncoding]; - DLog(@"Received line: \"%@\"", line); - - memmove(_buffer, _buffer + lineLength + 2, _bufferSize - lineLength); // + 2 to skip the newline! - - _bufferSize -= (lineLength + 2); // +2 since we also skipped the newline - - return line; -} - -- (BOOL)_readResponse -{ - // Clear out any old response headers - _responseHeaders = nil; - - // Fetch the first line so we can parse the status code - NSString *firstLine = [self _receiveLine]; - if (nil == firstLine) { - return NO; - } - - // Get the status code! - NSInteger statusCode = 0; - NSScanner *scanner = [[NSScanner alloc] initWithString:firstLine]; - - // Scan up to the space and the number afterwards - BOOL success = ([scanner scanUpToString:@" " intoString:nil] && [scanner scanInteger:&statusCode]); - if (NO == success) { - // Failed to retrieve status code. - return NO; - } - - // Prepare for response! - _responseHeaders = [[NSMutableDictionary alloc] init]; - - // Go through the response headers - BOOL foundEndOfHeaders = NO; - while (NO == foundEndOfHeaders) - { - NSString *line = [self _receiveLine]; - if (nil == line) { - // Error receiving data. Let's get out of here! - DLog(@"Headers ended prematurely"); - break; - } - - if ([@"" isEqualToString:line]) { - // We have \n\n, end of headers! - foundEndOfHeaders = YES; - continue; - } - - // Add the header to the dict - NSString *key = nil; - NSString *value = nil; - - NSScanner *scanner = [[NSScanner alloc] initWithString:line]; - BOOL success = ([scanner scanUpToString:@":" intoString:&key] && [scanner scanString:@":" intoString:nil] && [scanner scanUpToString:@"" intoString:&value]); - - if (NO == success) { - DLog(@"Could not scan header: %@", line); - continue; - } - - [_responseHeaders setObject:value forKey:[key lowercaseString]]; - } - - if (200 == statusCode || 206 == statusCode) { // OK - return YES; - } - else if (301 == statusCode || 302 == statusCode) { // Redirect - // Handle relative redirects as well - NSURL *redirectURL = [[NSURL alloc] initWithString:[self valueForResponseHeader:@"Location"] relativeToURL:[self URL]]; - [self setURL:[redirectURL absoluteURL]]; - [self close]; - return [self connect]; - } - - DLog(@"Returned status: %li", (long)statusCode); - return NO; -} - -- (BOOL)_sendRequest -{ - NSURL *url = [self URL]; - - NSString *path; - NSString *host; - NSNumber *port; - - if (NSClassFromString(@"NSURLComponents")) { - // Resolves trailing slash issue, but requires 10.9+ - - NSURLComponents * urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES]; - - path = urlComponents.path; - host = urlComponents.host; - port = urlComponents.port; - } - else { - path = [url path]; - host = [url host]; - port = [url port]; - } - - if (nil == path || [path isEqualToString:@""]) { - path = @"/"; - } - - // The initial GET - NSMutableString *requestString = [[NSMutableString alloc] initWithFormat:@"GET %@ HTTP/1.0\r\n", path]; - - // Make sure there is a Host entry - if (nil != port) { - host = [NSString stringWithFormat:@"%@:%@", host, port]; - } - - [self setValue:host forRequestHeader:@"Host"]; - - // Add the request headers - for (id header in _requestHeaders) { - id value = [_requestHeaders objectForKey:header]; - - [requestString appendFormat:@"%@: %@\r\n", header, value]; - } - - // The final newline - [requestString appendString:@"\r\n"]; - - // Get the bytes out of it - const char *requestBytes = [requestString UTF8String]; - long requestLength = strlen(requestBytes); - - // Send it off! - NSInteger sent = [_socket send:requestBytes amount:requestLength]; - if (sent != requestLength) { - return NO; - } - DLog(@"Sent:\n%@\n", requestString); - - return YES; -} - - -// Returns YES for success, NO for...not success -- (BOOL)connect -{ - NSURL *url = [self URL]; - NSString *host; - NSNumber *portNumber; - - if (NSClassFromString(@"NSURLComponents")) { - NSURLComponents * urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES]; - - host = urlComponents.host; - portNumber = urlComponents.port; - } - else { - host = [url host]; - portNumber = [url port]; - } - - // Get the port number - int port = (int) [portNumber integerValue]; - if (portNumber == nil) { - port = 80; // Default for HTTP - } - - // Cogundo...Makin' sockets - _socket = [[Socket alloc] initWithHost:host port:port]; - if (nil == _socket) { - return NO; - } - - if (![self _sendRequest]) { - return NO; - } - - if (![self _readResponse]) { - return NO; - } - - return YES; -} - -- (void)close -{ - [_socket close]; - _socket = nil; - - if (NULL != _buffer) { - free(_buffer); - _buffer = NULL; - _bufferSize = 0; - } -} - -// Returns negative on timeout or error - -- (NSInteger)receiveData:(void *)bytes amount:(NSInteger)amount -{ - NSInteger amountToRequest = amount; - NSInteger amountToCopy = MIN(amount, _bufferSize); - if (NULL != _buffer) { - memcpy(bytes, _buffer, amountToCopy); - _bufferSize -= amountToCopy; - - amountToRequest -= amountToCopy; - } - - - NSInteger bytesReceived = amountToCopy + [_socket receive:((uint8_t *)bytes) + amountToCopy amount:amountToRequest]; - - return bytesReceived; -} - -@end diff --git a/Plugins/HTTPSource/Utils/Socket.h b/Plugins/HTTPSource/Utils/Socket.h deleted file mode 100644 index e1c0bbe98..000000000 --- a/Plugins/HTTPSource/Utils/Socket.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// Socket.h -// Cog -// -// Created by Vincent Spader on 2/28/07. -// Copyright 2007 __MyCompanyName__. All rights reserved. -// - -#import - -//Rediculously simple socket wrapper -@interface Socket : NSObject { - int _socket; -} - -+ (id)socketWithHost:(NSString *)host port:(int) port; -- (id)initWithHost:(NSString *)host port:(int)port; - -- (NSInteger)send:(const void *)data amount:(NSInteger)amount; -- (NSInteger)receive:(void *)data amount:(NSInteger)amount; -- (void)close; - -@end diff --git a/Plugins/HTTPSource/Utils/Socket.m b/Plugins/HTTPSource/Utils/Socket.m deleted file mode 100644 index 926ce65a8..000000000 --- a/Plugins/HTTPSource/Utils/Socket.m +++ /dev/null @@ -1,76 +0,0 @@ -// -// Socket.m -// Cog -// -// Created by Vincent Spader on 2/28/07. -// Copyright 2007 __MyCompanyName__. All rights reserved. -// - -#import "Socket.h" -#import - -#import "Logging.h" - -@implementation Socket - -+ (id)socketWithHost:(NSString *)host port:(int)port -{ - return [[Socket alloc] initWithHost:host port:port]; -} - -- (id)initWithHost:(NSString *)host port:(int) port -{ - self = [super init]; - if (self) - { - _socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - struct sockaddr_in sin; - struct hostent *he; - - if (_socket < 0) { - return nil; - } - - sin.sin_family = AF_INET; - sin.sin_port = htons(port); - - he = gethostbyname([host UTF8String]); - if (!he) { - ALog(@"Socket error: %s\n", strerror(errno)); - close(_socket); - return nil; - } - memcpy(&sin.sin_addr, he->h_addr, 4); - - if (connect(_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) { - ALog(@"Error: %s\n", strerror(errno)); - close(_socket); - return nil; - } - } - - return self; -} - - -- (NSInteger)send:(const void *)data amount:(NSInteger)amount -{ - return send(_socket, data, amount, 0); -} - -- (NSInteger)receive:(void *)data amount:(NSInteger)amount -{ - return recv(_socket, data, amount, 0); -} - -- (void)close -{ - close(_socket); -} - -- (void)dealloc -{ - [self close]; -} - -@end