diff --git a/Plugins/HTTPSource/HTTPSource.h b/Plugins/HTTPSource/HTTPSource.h index 43213a896..88ebe93f0 100644 --- a/Plugins/HTTPSource/HTTPSource.h +++ b/Plugins/HTTPSource/HTTPSource.h @@ -8,21 +8,19 @@ #import -#import - #import "Plugin.h" +@class HTTPConnection; + @interface HTTPSource : NSObject { - NSURLConnection *_connection; - - JNL_HTTPGet *_get; + HTTPConnection *_connection; long _byteCount; NSString *_mimeType; - - NSURL *_url; } + + @end diff --git a/Plugins/HTTPSource/HTTPSource.mm b/Plugins/HTTPSource/HTTPSource.mm index af845c6d5..a8652ba4d 100644 --- a/Plugins/HTTPSource/HTTPSource.mm +++ b/Plugins/HTTPSource/HTTPSource.mm @@ -7,45 +7,25 @@ // #import "HTTPSource.h" -#include +#import "HTTPConnection.h" @implementation HTTPSource - (BOOL)open:(NSURL *)url { - _url = [url copy]; + _connection = [[HTTPConnection alloc] initWithURL:url]; - JNL::open_socketlib(); - - _get = new JNL_HTTPGet(); - - NSString *userAgent = [NSString stringWithFormat:@"User-Agent:Cog %@", [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]]; - _get->addheader([userAgent UTF8String]); - _get->addheader("Connection:close"); - _get->addheader("Accept:*/*"); - - _get->connect([[url absoluteString] UTF8String]); - for(;;) { - int status = _get->get_status(); - - if (status != 0 && status != 1) { - break; - } - - if (_get->run() < 0) { - return 0; - } - } - - int st = _get->run(); - if (st < 0) { + 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; } - - const char *mimeType = _get->getheader("content-type"); - if (NULL != mimeType) { - _mimeType = [[NSString alloc] initWithUTF8String:mimeType]; - } + + _mimeType = [[_connection valueForResponseHeader:@"Content-type"] copy]; return YES; } @@ -75,36 +55,13 @@ { int totalRead = 0; - NSTimeInterval timeout = 30; - BOOL timingOut = NO; - while (totalRead < amount) { - int result = _get->run(); - int amountRead = _get->get_bytes((char *)((uint8_t *)buffer) + totalRead, amount - totalRead); - - if (0 == amountRead) { - if (0 != result) break; - - if (NO == timingOut) { - timingOut = YES; - timeout = [NSDate timeIntervalSinceReferenceDate] + 30; - } - else { - if (timeout < [NSDate timeIntervalSinceReferenceDate]) { - NSLog(@"Timed out!"); - break; - } - - // Sigh. We should just use blocking IO. - usleep(250); - } - - } - else { - timingOut = NO; + NSInteger amountReceived = [_connection receiveData:((uint8_t *)buffer) + totalRead amount:amount - totalRead]; + if (amountReceived <= 0) { + break; } - totalRead += amountRead; + totalRead += amountReceived; } _byteCount += totalRead; @@ -114,13 +71,9 @@ - (void)close { - if (NULL != _get) { - delete _get; - _get = NULL; - } - - [_url release]; - _url = nil; + [_connection close]; + [_connection release]; + _connection = nil; [_mimeType release]; _mimeType = nil; @@ -136,7 +89,7 @@ - (NSURL *)url { - return _url; + return [_connection URL]; } + (NSArray *)schemes diff --git a/Plugins/HTTPSource/HTTPSource.xcodeproj/project.pbxproj b/Plugins/HTTPSource/HTTPSource.xcodeproj/project.pbxproj index ae7c774de..8b72c9f94 100644 --- a/Plugins/HTTPSource/HTTPSource.xcodeproj/project.pbxproj +++ b/Plugins/HTTPSource/HTTPSource.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ 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 */; }; 17ADB6100B97A74800257CA2 /* HTTPSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 17ADB60D0B97A74800257CA2 /* HTTPSource.mm */; }; 17F6C6540F5F9E83000D9DA9 /* JNetLib.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 17F6C6450F5F9E3F000D9DA9 /* JNetLib.framework */; }; 17F6C6550F5F9E86000D9DA9 /* JNetLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17F6C6450F5F9E3F000D9DA9 /* JNetLib.framework */; }; @@ -47,6 +49,10 @@ 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 = ""; }; 17ADB60C0B97A74800257CA2 /* HTTPSource.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HTTPSource.h; sourceTree = ""; }; 17ADB60D0B97A74800257CA2 /* HTTPSource.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = HTTPSource.mm; sourceTree = ""; }; 17ADB6340B97A8B400257CA2 /* Plugin.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = Plugin.h; path = ../../Audio/Plugin.h; sourceTree = SOURCE_ROOT; }; @@ -74,6 +80,7 @@ isa = PBXGroup; children = ( 08FB77AFFE84173DC02AAC07 /* Classes */, + 1716067A0F624E09008FA424 /* Utils */, 32C88E010371C26100C91783 /* Other Sources */, 089C167CFE841241C02AAC07 /* Resources */, 089C1671FE841209C02AAC07 /* Frameworks and Libraries */, @@ -128,6 +135,17 @@ name = "Other Frameworks"; sourceTree = ""; }; + 1716067A0F624E09008FA424 /* Utils */ = { + isa = PBXGroup; + children = ( + 171606860F624E27008FA424 /* HTTPConnection.h */, + 171606870F624E27008FA424 /* HTTPConnection.m */, + 1716067B0F624E09008FA424 /* Socket.h */, + 1716067C0F624E09008FA424 /* Socket.m */, + ); + path = Utils; + sourceTree = ""; + }; 17F6C6410F5F9E3F000D9DA9 /* Products */ = { isa = PBXGroup; children = ( @@ -224,6 +242,8 @@ buildActionMask = 2147483647; files = ( 17ADB6100B97A74800257CA2 /* HTTPSource.mm in Sources */, + 1716067D0F624E09008FA424 /* Socket.m in Sources */, + 1716068A0F624E27008FA424 /* HTTPConnection.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -282,7 +302,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; - SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SDKROOT = /Developer/SDKs/MacOSX10.5.sdk; SYMROOT = ../../build; }; name = Debug; @@ -293,7 +313,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PREBINDING = NO; - SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + SDKROOT = /Developer/SDKs/MacOSX10.5.sdk; SYMROOT = ../../build; }; name = Release; diff --git a/Plugins/HTTPSource/Utils/HTTPConnection.h b/Plugins/HTTPSource/Utils/HTTPConnection.h new file mode 100644 index 000000000..778a647b8 --- /dev/null +++ b/Plugins/HTTPSource/Utils/HTTPConnection.h @@ -0,0 +1,36 @@ +// +// 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 new file mode 100644 index 000000000..add4a24fb --- /dev/null +++ b/Plugins/HTTPSource/Utils/HTTPConnection.m @@ -0,0 +1,273 @@ +// +// HTTPConnection.m +// HTTPSource +// +// Created by Vincent Spader on 3/6/09. +// Copyright 2009 __MyCompanyName__. All rights reserved. +// + +#import "HTTPConnection.h" + +#include + + +@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 +{ + [_requestHeaders release]; + [_responseHeaders release]; + + [self setURL:nil]; + + [super dealloc]; +} + +- (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); + NSString *line = [[NSString alloc] initWithBytes:_buffer length:lineLength encoding:NSUTF8StringEncoding]; + NSLog(@"Received line: !%@!", line); + memmove(_buffer, _buffer + lineLength + 2, _bufferSize - lineLength); // + 2 to skip the newline! + _bufferSize -= lineLength; + + return [line autorelease]; +} + +- (BOOL)_readResponse +{ + // Clear out any old response headers + [_responseHeaders release]; + _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]); + [scanner release]; + 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! + NSLog(@"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]); + [scanner release]; + + if (NO == success) { + NSLog(@"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 + NSURL *redirectURL = [[NSURL alloc] initWithString:[self valueForResponseHeader:@"Location"]]; + [self setURL:redirectURL]; + [self close]; + return [self connect]; + } + + NSLog(@"Returned status: %i", statusCode); + return NO; +} + +- (BOOL)_sendRequest +{ + NSURL *url = [self URL]; + + // The initial GET + NSMutableString *requestString = [[NSMutableString alloc] initWithFormat:@"GET %@ HTTP/1.0\r\n", [url path]]; + + // Make sure there is a Host entry + NSString *host = [url host]; + if (nil != [url port]) { + host = [NSString stringWithFormat:@"%@:%@", [url host], [url 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]; + int requestLength = strlen(requestBytes); + + // Send it off! + NSInteger sent = [_socket send:requestBytes amount:requestLength]; + if (sent != requestLength) { + return NO; + } + NSLog(@"Sent:\n%@\n", requestString); + [requestString release]; + + return YES; +} + + +// Returns YES for success, NO for...not success +- (BOOL)connect +{ + NSURL *url = [self URL]; + NSString *host = [url host]; + + // Get the port number + NSNumber *portNumber = [url port]; + NSInteger port = [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 release]; + _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 new file mode 100644 index 000000000..e1c0bbe98 --- /dev/null +++ b/Plugins/HTTPSource/Utils/Socket.h @@ -0,0 +1,23 @@ +// +// 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 new file mode 100644 index 000000000..6bef8a439 --- /dev/null +++ b/Plugins/HTTPSource/Utils/Socket.m @@ -0,0 +1,76 @@ +// +// Socket.m +// Cog +// +// Created by Vincent Spader on 2/28/07. +// Copyright 2007 __MyCompanyName__. All rights reserved. +// + +#import "Socket.h" +#import + +@implementation Socket + ++ (id)socketWithHost:(NSString *)host port:(NSInteger)port +{ + return [[[Socket alloc] initWithHost:host port:port] autorelease]; +} + +- (id)initWithHost:(NSString *)host port:(NSInteger) 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) { + NSLog(@"Socket error."); + close(_socket); + return nil; + } + memcpy(&sin.sin_addr, he->h_addr, 4); + + if (connect(_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + NSLog(@"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]; + + [super dealloc]; +} + +@end