From 350feb3bc26767ea88732792f3b79e18a8dbbfa0 Mon Sep 17 00:00:00 2001 From: rfm Date: Fri, 11 May 2007 06:15:05 +0000 Subject: [PATCH] Commit accidentally missed changes. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@25138 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 9 + Source/GSURLPrivate.h | 7 + Source/NSURLConnection.m | 151 +++++++- Source/NSURLProtocol.m | 730 ++++++++++++++++++++++++++++++++++++++- Source/NSURLRequest.m | 14 + Source/NSURLResponse.m | 87 ++++- 6 files changed, 970 insertions(+), 28 deletions(-) diff --git a/ChangeLog b/ChangeLog index 39db14781..9d0d68731 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2007-05-11 Richard Frith-Macdonald + + * Source/NSURLResponse.m: + * Source/NSURLProtocol.m: + * Source/NSURLRequest.m: + * Source/GSURLPrivate.h: + * Source/NSURLConnection.m: + Changes accidentally missed from update on 27th april. + 2007-05-02 Nicola Pero * configure.ac: Set GNUSTEP_MAKEFILES to CURRENT_GNUSTEP_MAKEFILES diff --git a/Source/GSURLPrivate.h b/Source/GSURLPrivate.h index 6ab7ea57d..c9b73677f 100644 --- a/Source/GSURLPrivate.h +++ b/Source/GSURLPrivate.h @@ -62,6 +62,13 @@ @end +@interface NSURLResponse (Private) +- (void) _setHeaders: (id)headers; +- (void) _setStatusCode: (int)code text: (NSString*)text; +- (void) _setValue: (NSString *)value forHTTPHeaderField: (NSString *)field; +- (NSString *) _valueForHTTPHeaderField: (NSString *)field; +@end + /* diff --git a/Source/NSURLConnection.m b/Source/NSURLConnection.m index 686b83383..d6f1004e3 100644 --- a/Source/NSURLConnection.m +++ b/Source/NSURLConnection.m @@ -22,15 +22,128 @@ Boston, MA 02111 USA. */ +#include #include "GSURLPrivate.h" + +@interface _NSURLConnectionDataCollector : NSObject +{ + NSURLConnection *_connection; // Not retained + NSMutableData *_data; + NSError **_error; + NSURLResponse **_response; + BOOL _done; +} + +- (NSData*) _data; +- (BOOL) _done; +- (void) _setConnection: (NSURLConnection *)c; + +@end + +@implementation _NSURLConnectionDataCollector + +- (id) initWithResponsePointer: (NSURLResponse **)response + andErrorPointer: (NSError **)error +{ + if ((self = [super init]) != nil) + { + _response = response; + _error = error; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_data); + [super dealloc]; +} + +- (BOOL) _done +{ + return _done; +} + +- (NSData*) _data +{ + return _data; +} + +- (void) _setConnection: (NSURLConnection*)c +{ + _connection = c; +} + +// notification handler + +- (void) URLProtocol: (NSURLProtocol*)proto +cachedResponseIsValid: (NSCachedURLResponse*)resp +{ + return; +} + +- (void) URLProtocol: (NSURLProtocol*)proto +didReceiveAuthenticationChallenge: (NSURLAuthenticationChallenge *)challenge +{ + return; +} + +- (void) URLProtocol: (NSURLProtocol*)proto +didCancelAuthenticationChallenge: (NSURLAuthenticationChallenge *)challenge +{ + return; +} + +- (void) URLProtocol: (NSURLProtocol*)proto +wasRedirectedToRequest: (NSURLRequest*)request +redirectResponse: (NSURLResponse*)redirectResponse +{ + return; +} + +- (void) URLProtocol: (NSURLProtocol*)proto + didFailWithError: (NSError*)error +{ + *_error = error; + _done = YES; +} + +- (void) URLProtocol: (NSURLProtocol*)proto + didReceiveResponse: (NSURLResponse*)response + cacheStoragePolicy: (NSURLCacheStoragePolicy)policy +{ + *_response = response; +} + +- (void) URLProtocolDidFinishLoading: (NSURLProtocol*)proto +{ + _done = YES; +} + +- (void) URLProtocol: (NSURLProtocol*)proto + didLoadData: (NSData*)data +{ + if (_data != nil) + { + _data = [data mutableCopy]; + } + else + { + [_data appendData: data]; + } +} + +@end + + @interface GSURLConnection : NSObject { @public NSURLConnection *_parent; // Not retained NSURLRequest *_request; NSURLProtocol *_protocol; - id _delegate; + id _delegate; // Not retained } @end @@ -57,8 +170,7 @@ typedef struct { + (BOOL) canHandleRequest: (NSURLRequest *)request { - // FIXME - return NO; + return [NSURLProtocol canInitWithRequest: request]; } + (NSURLConnection *) connectionWithRequest: (NSURLRequest *)request @@ -87,8 +199,11 @@ typedef struct { if ((self = [super init]) != nil) { this->_request = [request copy]; - this->_delegate = [delegate retain]; - // FIXME ... start connection + this->_delegate = delegate; + this->_protocol = [[NSURLProtocol alloc] initWithRequest: this->_request + cachedResponse: nil + client: this]; + [this->_protocol startLoading]; } return self; } @@ -157,12 +272,28 @@ typedef struct { if ([self canHandleRequest: request] == YES) { - NSURLConnection *conn = [self alloc]; + _NSURLConnectionDataCollector *collector; + NSURLConnection *conn; + NSRunLoop *loop; - conn = [conn initWithRequest: request delegate: nil]; - // FIXME ... handle load and get results; + collector = [_NSURLConnectionDataCollector alloc]; + collector = [collector initWithResponsePointer: response + andErrorPointer: error]; + conn = [self alloc]; + conn = [conn initWithRequest: request delegate: AUTORELEASE(collector)]; + [collector _setConnection: conn]; + loop = [NSRunLoop currentRunLoop]; + while ([collector _done] == NO) + { + NSDate *limit; + + limit = [[NSDate alloc] initWithTimeIntervalSinceNow: 1.0]; + [loop runMode: NSDefaultRunLoopMode beforeDate: limit]; + RELEASE(limit); + } + data = RETAIN([collector _data]); } - return data; + return AUTORELEASE(data); } @end @@ -174,7 +305,6 @@ typedef struct { { RELEASE(_protocol); RELEASE(_request); - RELEASE(_delegate); [super dealloc]; } @@ -231,6 +361,7 @@ typedef struct { } else { + [_protocol stopLoading]; DESTROY(_protocol); // FIXME start new request loading } diff --git a/Source/NSURLProtocol.m b/Source/NSURLProtocol.m index d7b2e0346..8621a6ff8 100644 --- a/Source/NSURLProtocol.m +++ b/Source/NSURLProtocol.m @@ -22,15 +22,47 @@ Boston, MA 02111 USA. */ +#include +#include +#include + +#include "GSPrivate.h" #include "GSURLPrivate.h" +@interface _NSAboutURLProtocol : NSURLProtocol +@end + +@interface _NSFTPURLProtocol : NSURLProtocol +@end + +@interface _NSFileURLProtocol : NSURLProtocol +@end + +@interface _NSHTTPURLProtocol : NSURLProtocol +{ + NSMutableDictionary *_headers; // received headers + NSEnumerator *_headerEnumerator; // enumerates headers while sending + NSInputStream *_body; // for sending the body + unsigned char *_receiveBuf; // buffer while receiving header fragments + unsigned int _receiveBufLength; // how much is really used in the current buffer + unsigned int _receiveBufCapacity; // how much is allocated + unsigned _statusCode; + BOOL _readingBody; +} +@end + +@interface _NSHTTPSURLProtocol : _NSHTTPURLProtocol +@end + + + // Internal data storage typedef struct { NSInputStream *input; NSOutputStream *output; NSCachedURLResponse *cachedResponse; - id client; + id client; // Not retained NSURLRequest *request; } Internal; @@ -62,9 +94,19 @@ static NSLock *regLock = nil; { registered = [NSMutableArray new]; regLock = [NSLock new]; + [self registerClass: [_NSHTTPURLProtocol class]]; + [self registerClass: [_NSHTTPSURLProtocol class]]; + [self registerClass: [_NSFTPURLProtocol class]]; + [self registerClass: [_NSFileURLProtocol class]]; + [self registerClass: [_NSAboutURLProtocol class]]; } } ++ (id) propertyForKey: (NSString *)key inRequest: (NSURLRequest *)request +{ + return [request _propertyForKey: key]; +} + + (BOOL) registerClass: (Class)protocolClass { if ([protocolClass isSubclassOfClass: [NSURLProtocol class]] == YES) @@ -77,11 +119,6 @@ static NSLock *regLock = nil; return NO; } -+ (id) propertyForKey: (NSString *)key inRequest: (NSURLRequest *)request -{ - return [request _propertyForKey: key]; -} - + (void) setProperty: (id)value forKey: (NSString *)key inRequest: (NSMutableURLRequest *)request @@ -110,25 +147,53 @@ static NSLock *regLock = nil; { if (this != 0) { + [self stopLoading]; RELEASE(this->input); RELEASE(this->output); RELEASE(this->cachedResponse); - RELEASE(this->client); RELEASE(this->request); NSZoneFree([self zone], this); } [super dealloc]; } +- (NSString*) description +{ + return [NSString stringWithFormat:@"%@ %@", + [super description], this->request]; +} + - (id) initWithRequest: (NSURLRequest *)request cachedResponse: (NSCachedURLResponse *)cachedResponse client: (id )client { + if (isa == [NSURLProtocol class]) + { + unsigned count; + + DESTROY(self); + [regLock lock]; + count = [registered count]; + while (count-- > 0) + { + Class proto = [registered objectAtIndex: count]; + + if ([proto canInitWithRequest: request] == YES) + { + self = [proto alloc]; + break; + } + } + [regLock unlock]; + return [self initWithRequest: request + cachedResponse: cachedResponse + client: client]; + } if ((self = [super init]) != nil) { this->request = [request copy]; this->cachedResponse = RETAIN(cachedResponse); - this->client = RETAIN(client); + this->client = client; // Not retained } return self; } @@ -157,6 +222,8 @@ static NSLock *regLock = nil; + (BOOL) requestIsCacheEquivalent: (NSURLRequest *)a toRequest: (NSURLRequest *)b { + a = [self canonicalRequestForRequest: a]; + b = [self canonicalRequestForRequest: b]; return [a isEqual: b]; } @@ -172,3 +239,650 @@ static NSLock *regLock = nil; @end + + + + + +@implementation _NSHTTPURLProtocol + ++ (BOOL) canInitWithRequest: (NSURLRequest*)request +{ + return [[[request URL] scheme] isEqualToString: @"http"]; +} + ++ (NSURLRequest*) canonicalRequestForRequest: (NSURLRequest*)request +{ + return request; +} + +- (void) _didInitializeOutputStream: (NSOutputStream*)stream +{ + return; +} + +- (void) dealloc +{ + [_headers release]; // received headers + [_body release]; // for sending the body + [super dealloc]; +} + +- (void) _schedule +{ + [this->input scheduleInRunLoop: [NSRunLoop currentRunLoop] + forMode: NSDefaultRunLoopMode]; + [this->output scheduleInRunLoop: [NSRunLoop currentRunLoop] + forMode: NSDefaultRunLoopMode]; +} + +- (void) startLoading +{ + static NSDictionary *methods = nil; + + if (methods == nil) + { + methods = [[NSDictionary alloc] initWithObjectsAndKeys: + self, @"HEAD", + self, @"GET", + self, @"POST", + self, @"PUT", + self, @"DELETE", + self, @"TRACE", + self, @"OPTIONS", + self, @"CONNECT", + nil]; + } + if ([methods objectForKey: [this->request HTTPMethod]] == nil) + { + NSLog(@"Invalid HTTP Method: %@", this->request); + [this->client URLProtocol: self didFailWithError: + [NSError errorWithDomain: @"Invalid HTTP Method" + code: 0 + userInfo: nil]]; + return; + } + + if (0 && this->cachedResponse) + { + } + else + { + NSURL *url = [this->request URL]; + NSHost *host = [NSHost hostWithName: [url host]]; + int port = [[url port] intValue]; + + if (host == nil) + { + host = [NSHost hostWithAddress: [url host]]; // try dotted notation + } + if (host == nil) + { + host = [NSHost hostWithAddress: @"127.0.0.1"]; // final default + } + if (port == 0) + { + // default if not specified + port = [[url scheme] isEqualToString: @"https"] ? 433 : 80; + } + [NSStream getStreamsToHost: host + port: port + inputStream: &this->input + outputStream: &this->output]; + if (!this->input || !this->output) + { +#if 1 + NSLog(@"did not create streams for %@: %u", host, [[url port] intValue]); +#endif + [this->client URLProtocol: self didFailWithError: + [NSError errorWithDomain: @"can't connect" code: 0 userInfo: + [NSDictionary dictionaryWithObjectsAndKeys: + url, @"NSErrorFailingURLKey", + host, @"NSErrorFailingURLStringKey", + @"can't find host", @"NSLocalizedDescription", + nil]]]; + return; + } + RETAIN(this->input); + RETAIN(this->output); + [self _didInitializeOutputStream: this->output]; + [this->input setDelegate: self]; + [this->output setDelegate: self]; + [self _schedule]; + [this->input open]; + [this->output open]; + } +} + +- (void) _unschedule +{ + [this->input removeFromRunLoop: [NSRunLoop currentRunLoop] + forMode: NSDefaultRunLoopMode]; + [this->output removeFromRunLoop: [NSRunLoop currentRunLoop] + forMode: NSDefaultRunLoopMode]; +} + +- (void) stopLoading +{ +#if 1 + NSLog(@"stopLoading: %@", self); +#endif + if (this->input != nil) + { + [self _unschedule]; + [this->input close]; + [this->output close]; + DESTROY(this->input); + DESTROY(this->output); +#if 0 + // CHECKME - or does this come if the other side rejects the request? + [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"cancelled" code: 0 userInfo: + [NSDictionary dictionaryWithObjectsAndKeys: + url, @"NSErrorFailingURLKey", + host, @"NSErrorFailingURLStringKey", + @"cancelled", @"NSLocalizedDescription", + nil]]]; +#endif + } +} + +- (BOOL) _processHeaderLine: (unsigned char *) buffer length: (int) len +{ // process header line + unsigned char *c, *end; + NSString *key, *val; +#if 0 + NSLog(@"process header line len=%d", len); +#endif + // if it begins with ' ' or '\t' it is a continuation line to the previous header field + if (!_headers) + { // should be/must be the header line + unsigned major, minor; + if (sscanf((char *) buffer, "HTTP/%u.%u %u", &major, &minor, &_statusCode) == 3) + { // response header line + if (major != 1 || minor > 1) + [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"Bad HTTP version" code: 0 userInfo: nil]]; + // must be first - but must also be present and valid before we go to receive the body! + _headers=[NSMutableDictionary dictionaryWithCapacity: 10]; // start collecting headers + // if (_statusCode >= 400 && _statusCode <= 499) + NSLog(@"Client header: %.*s", len, buffer); + return NO; // process next line + } + else + ; // invalid header + return NO; // process next line + } + if (len == 0) + { // empty line, i.e. end of header + NSString *loc; + NSHTTPURLResponse *response; + + response = [[NSHTTPURLResponse alloc] initWithURL: [this->request URL] + MIMEType: nil + expectedContentLength: -1 + textEncodingName: nil]; + [response _setHeaders: _headers]; + DESTROY(_headers); + [response _setStatusCode: _statusCode text: @""]; + loc = [response _valueForHTTPHeaderField: @"location"]; + if ([loc length]) + { // Location: entry exists + NSURLRequest *request=[NSURLRequest requestWithURL: [NSURL URLWithString: loc]]; + if (!request) + [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"Invalid redirect request" code: 0 userInfo: nil]]; // error + [this->client URLProtocol: self wasRedirectedToRequest: request redirectResponse: response]; + } + else + { + NSURLCacheStoragePolicy policy=NSURLCacheStorageAllowed; // default + // read from [this->request cachePolicy]; + /* + NSURLCacheStorageAllowed, + NSURLCacheStorageAllowedInMemoryOnly + NSURLCacheStorageNotAllowed + */ + if ([self isKindOfClass: [_NSHTTPSURLProtocol class]]) + policy=NSURLCacheStorageNotAllowed; // never + [this->client URLProtocol: self didReceiveResponse: response cacheStoragePolicy: policy]; + } + return YES; + } + for (c=buffer, end=c+len; *c != ':'; c++) + { + if (c == end) + { // no colon found! + // raise bad header error or simply ignore? + return NO; // keep processing header lines + } + } + key=[[NSString stringWithCString: (char *) buffer length: c-buffer] capitalizedString]; + while(++c < end && (*c == ' ' || *c == '\t')) + ; // skip spaces + val=[NSString stringWithCString: (char *) c length: end-c]; + [_headers setObject: val forKey: [key lowercaseString]]; + return NO; // not yet done +} + +- (void) _processHeader: (unsigned char *) buffer length: (int) len +{ // next header fragment received + unsigned char *ptr, *end; +#if 0 + NSLog(@"received %d bytes", len); +#endif + if (len <= 0) + return; // ignore + if (_receiveBufLength + len > _receiveBufCapacity) + { // needs to increase capacity + _receiveBuf=objc_realloc(_receiveBuf, _receiveBufCapacity=_receiveBufLength+len+1); // creates new one if NULL + if (!_receiveBuf) + ; // FIXME allocation did fail: stop reception + } + memcpy(_receiveBuf+_receiveBufLength, buffer, len); // append to last partial block + _receiveBufLength+=len; +#if 0 + NSLog(@"len=%u capacity=%u buf=%.*s", _receiveBufLength, _receiveBufCapacity, _receiveBufLength, _receiveBuf); +#endif + ptr=_receiveBuf; // start of current line + end=_receiveBuf+_receiveBufLength; + while(YES) + { // look for complete lines + unsigned char *eol=ptr; + while(!(eol[0] == '\r' && eol[1] == '\n')) + { // search next line end + eol++; + if (eol == end) + { // no more lines found +#if 0 + NSLog(@"no CRLF"); +#endif + if (ptr != _receiveBuf) + { // remove already processed lines from buffer + memmove(_receiveBuf, ptr, end-ptr); + _receiveBufLength-=(end-ptr); + } + return; + } + } + if ([self _processHeaderLine: ptr length: eol-ptr]) + { // done + if (this->input) + { // is still open, i.e. hasn't been stopped in a client callback + if (eol+2 != end) + { // we have already received the first fragment of the body + [this->client URLProtocol: self didLoadData: [NSData dataWithBytes: eol+2 length: (end-eol)-2]]; // notify + } + } + objc_free(_receiveBuf); + _receiveBuf=NULL; + _receiveBufLength=0; + _receiveBufCapacity=0; + _readingBody=YES; + return; + } + ptr=eol+2; // go to start of next line + } +} + +/* + FIXME: + because we receive from untrustworthy sources here, we must protect against malformed headers trying to create buffer overflows. + This might also be some very lage constant for record length which wraps around the 32bit address limit (e.g. a negative record length). + Ending up in infinite loops blocking the system. + */ + +- (void) stream: (NSStream *) stream handleEvent: (NSStreamEvent) event +{ +#if 0 + NSLog(@"stream: %@ handleEvent: %x for: %@", stream, event, self); +#endif + if (stream == this->input) + { + switch(event) + { + case NSStreamEventHasBytesAvailable: + { + unsigned char buffer[512]; + int len=[(NSInputStream *) stream read: buffer maxLength: sizeof(buffer)]; + if (len < 0) + { +#if 1 + NSLog(@"receive error %@", [NSError _last]); +#endif + [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"receive error" code: 0 userInfo: nil]]; + [self _unschedule]; + return; + } + if (_readingBody) + [this->client URLProtocol: self didLoadData: [NSData dataWithBytes: buffer length: len]]; // notify + else + [self _processHeader: buffer length: len]; + return; + } + case NSStreamEventEndEncountered: // can this occur in parallel to NSStreamEventHasBytesAvailable??? + { +#if 0 + NSLog(@"end of response"); +#endif + if (!_readingBody) + [this->client URLProtocol: self didFailWithError: [NSError errorWithDomain: @"incomplete header" code: 0 userInfo: nil]]; + [this->client URLProtocolDidFinishLoading: self]; + _readingBody=NO; + [self _unschedule]; + return; + } + case NSStreamEventOpenCompleted: + { // prepare to receive header +#if 0 + NSLog(@"HTTP input stream opened"); +#endif + return; + } + default: + break; + } + } + else if (stream == this->output) + { + unsigned char *msg; +#if 0 + NSLog(@"An event occurred on the output stream."); +#endif + /* e.g. + POST /wiki/Spezial: Search HTTP/1.1 + Host: de.wikipedia.org + Content-Type: application/x-www-form-urlencoded + Content-Length: 24 + + search=Katzen&go=Artikel <- body + */ + switch(event) + { + case NSStreamEventOpenCompleted: + { +#if 0 + NSLog(@"HTTP output stream opened"); +#endif + msg=(unsigned char *) [[NSString stringWithFormat: @"%@ %@ HTTP/1.1\r\n", + [this->request HTTPMethod], + [[this->request URL] absoluteString] + ] cString]; // FIXME: UTF8??? + [(NSOutputStream *) stream write: msg maxLength: strlen((char *) msg)]; +#if 1 + NSLog(@"sent %s", msg); +#endif + _headerEnumerator=[[[this->request allHTTPHeaderFields] objectEnumerator] retain]; + return; + } + case NSStreamEventHasSpaceAvailable: + { + // FIXME: should also send out relevant Cookies + if (_headerEnumerator) + { // send next header + NSString *key; + key=[_headerEnumerator nextObject]; + if (key) + { +#if 1 + NSLog(@"sending %@: %@", key, [this->request valueForHTTPHeaderField: key]); +#endif + msg=(unsigned char *)[[NSString stringWithFormat: @"%@: %@\r\n", key, [this->request valueForHTTPHeaderField: key]] UTF8String]; + } + else + { // was last header entry + [_headerEnumerator release]; + _headerEnumerator=nil; + msg=(unsigned char *) "\r\n"; // send empty line + _body=[[this->request HTTPBodyStream] retain]; // if present + if (!_body && [this->request HTTPBody]) + _body=[[NSInputStream alloc] initWithData: [this->request HTTPBody]]; // prepare to send request body + [_body open]; + } + [(NSOutputStream *) stream write: msg maxLength: strlen((char *) msg)]; // NOTE: we might block here if header value is too long +#if 1 + NSLog(@"sent %s", msg); +#endif + return; + } + else if (_body) + { // send (next part of) body until done + if ([_body hasBytesAvailable]) + { + unsigned char buffer[512]; + int len=[_body read: buffer maxLength: sizeof(buffer)]; // read next block from stream + if (len < 0) + { +#if 1 + NSLog(@"error reading from HTTPBody stream %@", [NSError _last]); +#endif + [self _unschedule]; + return; + } + [(NSOutputStream *) stream write: buffer maxLength: len]; // send + } + else + { // done +#if 0 + NSLog(@"request sent"); +#endif + [self _unschedule]; // well, we should just unschedule the send stream + [_body close]; + [_body release]; + _body=nil; + } + } + return; // done + } + default: + break; + } + } + NSLog(@"An error %@ occurred on the event %08x of stream %@ of %@", [stream streamError], event, stream, self); + [this->client URLProtocol: self didFailWithError: [stream streamError]]; +} + +@end + +@implementation _NSHTTPSURLProtocol + ++ (BOOL) canInitWithRequest: (NSURLRequest*)request +{ + return [[[request URL] scheme] isEqualToString: @"https"]; +} + +- (void) _didInitializeOutputStream: (NSOutputStream *) stream +{ + [stream setProperty: NSStreamSocketSecurityLevelNegotiatedSSL + forKey: NSStreamSocketSecurityLevelKey]; +} + +@end + +@implementation _NSFTPURLProtocol + ++ (BOOL) canInitWithRequest: (NSURLRequest*)request +{ + return [[[request URL] scheme] isEqualToString: @"ftp"]; +} + ++ (NSURLRequest*) canonicalRequestForRequest: (NSURLRequest*)request +{ + return request; +} + +- (void) startLoading +{ + if (this->cachedResponse) + { // handle from cache + } + else + { + NSURL *url = [this->request URL]; + NSHost *host = [NSHost hostWithName: [url host]]; + + if (host == nil) + { + host = [NSHost hostWithAddress: [url host]]; + } + [NSStream getStreamsToHost: host + port: [[url port] intValue] + inputStream: &this->input + outputStream: &this->output]; + if (this->input == nil || this->output == nil) + { + [this->client URLProtocol: self didFailWithError: + [NSError errorWithDomain: @"can't connect" code: 0 userInfo: nil]]; + return; + } + RETAIN(this->input); + RETAIN(this->output); + [this->input setDelegate: self]; + [this->output setDelegate: self]; + [this->input scheduleInRunLoop: [NSRunLoop currentRunLoop] + forMode: NSDefaultRunLoopMode]; + [this->output scheduleInRunLoop: [NSRunLoop currentRunLoop] + forMode: NSDefaultRunLoopMode]; + // set socket options for ftps requests + [this->input open]; + [this->output open]; + } +} + +- (void) stopLoading +{ + if (this->input) + { + [this->input removeFromRunLoop: [NSRunLoop currentRunLoop] + forMode: NSDefaultRunLoopMode]; + [this->output removeFromRunLoop: [NSRunLoop currentRunLoop] + forMode: NSDefaultRunLoopMode]; + [this->input close]; + [this->output close]; + DESTROY(this->input); + DESTROY(this->output); + } +} + +- (void) stream: (NSStream *) stream handleEvent: (NSStreamEvent) event +{ + if (stream == this->input) + { + switch(event) + { + case NSStreamEventHasBytesAvailable: + { + NSLog(@"FTP input stream has bytes available"); + // implement FTP protocol +// [this->client URLProtocol: self didLoadData: [NSData dataWithBytes: buffer length: len]]; // notify + return; + } + case NSStreamEventEndEncountered: // can this occur in parallel to NSStreamEventHasBytesAvailable??? + NSLog(@"FTP input stream did end"); + [this->client URLProtocolDidFinishLoading: self]; + return; + case NSStreamEventOpenCompleted: + // prepare to receive header + NSLog(@"FTP input stream opened"); + return; + default: + break; + } + } + else if (stream == this->output) + { + NSLog(@"An event occurred on the output stream."); + // if successfully opened, send out FTP request header + } + NSLog(@"An error %@ occurred on the event %08x of stream %@ of %@", + [stream streamError], event, stream, self); + [this->client URLProtocol: self didFailWithError: [stream streamError]]; +} + +@end + +@implementation _NSFileURLProtocol + ++ (BOOL) canInitWithRequest: (NSURLRequest*)request +{ + return [[[request URL] scheme] isEqualToString: @"file"]; +} + ++ (NSURLRequest*) canonicalRequestForRequest: (NSURLRequest*)request +{ + return request; +} + +- (void) startLoading +{ + // check for GET/PUT/DELETE etc so that we can also write to a file + NSData *data; + NSURLResponse *r; + + data = [NSData dataWithContentsOfFile: [[this->request URL] path] + /* options: error: - don't use that because it is based on self */]; + if (data == nil) + { + [this->client URLProtocol: self didFailWithError: + [NSError errorWithDomain: @"can't load file" code: 0 userInfo: + [NSDictionary dictionaryWithObjectsAndKeys: + [this->request URL], @"URL", + [[this->request URL] path], @"path", + nil]]]; + return; + } + + /* FIXME ... maybe should infer MIME type and encoding from extension or BOM + */ + r = [[NSURLResponse alloc] initWithURL: [this->request URL] + MIMEType: @"text/html" + expectedContentLength: [data length] + textEncodingName: @"unknown"]; + [this->client URLProtocol: self + didReceiveResponse: r + cacheStoragePolicy: NSURLRequestUseProtocolCachePolicy]; + [this->client URLProtocol: self didLoadData: data]; + [this->client URLProtocolDidFinishLoading: self]; + RELEASE(r); +} + +- (void) stopLoading +{ + return; +} + +@end + +@implementation _NSAboutURLProtocol + ++ (BOOL) canInitWithRequest: (NSURLRequest*)request +{ + return [[[request URL] scheme] isEqualToString: @"about"]; +} + ++ (NSURLRequest*) canonicalRequestForRequest: (NSURLRequest*)request +{ + return request; +} + +- (void) startLoading +{ + NSURLResponse *r; + NSData *data = [NSData data]; // no data + + // we could pass different content depending on the [url path] + r = [[NSURLResponse alloc] initWithURL: [this->request URL] + MIMEType: @"text/html" + expectedContentLength: 0 + textEncodingName: @"utf-8"]; + [this->client URLProtocol: self + didReceiveResponse: r + cacheStoragePolicy: NSURLRequestUseProtocolCachePolicy]; + [this->client URLProtocol: self didLoadData: data]; + [this->client URLProtocolDidFinishLoading: self]; + RELEASE(r); +} + +- (void) stopLoading +{ + return; +} + +@end diff --git a/Source/NSURLRequest.m b/Source/NSURLRequest.m index 4d44d9e8c..2869c5814 100644 --- a/Source/NSURLRequest.m +++ b/Source/NSURLRequest.m @@ -178,6 +178,11 @@ typedef struct { return [this->URL hash]; } +- (id) init +{ + return [self initWithURL: nil]; +} + - (id) initWithURL: (NSURL *)URL { return [self initWithURL: URL @@ -195,6 +200,7 @@ typedef struct { this->cachePolicy = cachePolicy; this->timeoutInterval = timeoutInterval; this->mainDocumentURL = nil; + this->method = @"GET"; } return self; } @@ -474,6 +480,14 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks = - (void) setHTTPMethod: (NSString *)method { +/* NB. I checked MacOS-X 4.2, and this method actually lets you set any + * copyable value (including non-string classes), but setting nil is + * equivalent to resetting to the default value of 'GET' + */ + if (method == nil) + { + method = @"GET"; + } ASSIGNCOPY(this->method, method); } diff --git a/Source/NSURLResponse.m b/Source/NSURLResponse.m index aa0674903..c7bc5a3fe 100644 --- a/Source/NSURLResponse.m +++ b/Source/NSURLResponse.m @@ -84,19 +84,86 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks = NSNotAPointerMapKey }; -@interface NSURLResponse (Internal) -- (void) setStatusCode: (int)code text: (NSString*)text; -- (void) setValue: (NSString *)value forHTTPHeaderField: (NSString *)field; -- (NSString *) valueForHTTPHeaderField: (NSString *)field; -@end +@implementation NSURLResponse (Private) +- (void) _setHeaders: (id)headers +{ + NSEnumerator *e; + NSString *v; + NSString *contentLength = nil; + GSMimeHeader *contentType = nil; -@implementation NSURLResponse (Internal) -- (void) setStatusCode: (int)code text: (NSString*)text + if ([headers isKindOfClass: [NSDictionary class]] == YES) + { + NSString *k; + + e = [headers keyEnumerator]; + while ((k = [e nextObject]) != nil) + { + v = [headers objectForKey: k]; + [self _setValue: v forHTTPHeaderField: k]; + } + } + else if ([headers isKindOfClass: [NSArray class]] == YES) + { + GSMimeHeader *h; + + e = [headers objectEnumerator]; + while ((h = [e nextObject]) != nil) + { + NSString *n = [h name]; + + v = [h value]; + [self _setValue: v forHTTPHeaderField: n]; + if ([n isEqualToString: @"content-length"] == YES) + { + contentLength = v; + } + else if ([n isEqualToString: @"content-type"] == YES) + { + contentType = h; + } + } + } + + if (contentLength == nil) + { + contentLength = [self _valueForHTTPHeaderField: @"content-length"]; + } + if ([contentLength length] == 0) + { + this->expectedContentLength = -1; + } + else + { + this->expectedContentLength = [contentLength intValue]; + } + + if (contentType == nil) + { + GSMimeParser *p; + NSScanner *s; + + v = [self _valueForHTTPHeaderField: @"content-type"]; + if (v == nil) + { + v = @"text/plain"; // No content type given. + } + s = [NSScanner scannerWithString: v]; + p = [GSMimeParser new]; + contentType = AUTORELEASE([GSMimeHeader new]); + [p scanHeaderBody: s into: contentType]; + RELEASE(p); + } + ASSIGNCOPY(this->MIMEType, [contentType value]); + v = [contentType parameterForKey: @"charset"]; + ASSIGNCOPY(this->textEncodingName, v); +} +- (void) _setStatusCode: (int)code text: (NSString*)text { this->statusCode = code; ASSIGNCOPY(this->statusText, text); } -- (void) setValue: (NSString *)value forHTTPHeaderField: (NSString *)field +- (void) _setValue: (NSString *)value forHTTPHeaderField: (NSString *)field { if (this->headers == 0) { @@ -105,7 +172,7 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks = } NSMapInsert(this->headers, (void*)field, (void*)value); } -- (NSString *) valueForHTTPHeaderField: (NSString *)field +- (NSString *) _valueForHTTPHeaderField: (NSString *)field { NSString *value = nil; @@ -247,7 +314,7 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks = */ - (NSString *) suggestedFilename { - NSString *disp = [self valueForHTTPHeaderField: @"content-disposition"]; + NSString *disp = [self _valueForHTTPHeaderField: @"content-disposition"]; NSString *name = nil; if (disp != nil)