From 1d562005d112292da3bb9e24c78c31708920320b Mon Sep 17 00:00:00 2001 From: Richard Frith-MacDonald Date: Fri, 11 May 2007 15:47:06 +0000 Subject: [PATCH] tweaks for url loading git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@25142 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 4 +- Headers/Additions/GNUstepBase/GSXML.h | 4 +- Headers/Foundation/NSURLConnection.h | 2 +- Source/Additions/GSXML.m | 22 -- Source/GSURLPrivate.h | 2 +- Source/NSURLProtocol.m | 451 ++++++++++++++------------ Source/NSURLResponse.m | 1 - Source/unix/NSStream.m | 11 +- Source/win32/NSStreamWin32.m | 2 +- 9 files changed, 259 insertions(+), 240 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5e34802d9..36a031a2b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,9 +5,11 @@ * Source/win32/NSStreamWin32.m: * Headers/Foundation/NSStream.h: First hack at extensions to get address and port properties for - network streams. + network streams. Use localhost if no host is given for connect. * Source/GSFFIInvocation.m: Attempt to use forward2 if available. + * Source/NSURLProtocol.m: + Fixup some problems with incorporation of code from mySTEP. 2007-05-11 Richard Frith-Macdonald diff --git a/Headers/Additions/GNUstepBase/GSXML.h b/Headers/Additions/GNUstepBase/GSXML.h index 5122a231e..0bb68e404 100644 --- a/Headers/Additions/GNUstepBase/GSXML.h +++ b/Headers/Additions/GNUstepBase/GSXML.h @@ -442,6 +442,7 @@ extern "C" { #include +#include @class NSArray; @class NSDictionary; @@ -504,13 +505,10 @@ extern "C" { @interface GSXMLRPC : NSObject { @private -#ifdef GNUSTEP NSURLHandle *handle; -#else NSString *connectionURL; NSURLConnection *connection; NSMutableData *response; -#endif NSTimer *timer; id result; id delegate; // Not retained. diff --git a/Headers/Foundation/NSURLConnection.h b/Headers/Foundation/NSURLConnection.h index b38ea0762..870293147 100644 --- a/Headers/Foundation/NSURLConnection.h +++ b/Headers/Foundation/NSURLConnection.h @@ -72,7 +72,7 @@ extern "C" { /** * Initialises the receiver with the specified request (performing - * a deep copy so that ithe request does not change during loading) + * a deep copy so that the request does not change during loading) * and delegate.
* This automatically initiates an asynchronous load for the request.
* Processing of the request is done in the thread which calls this diff --git a/Source/Additions/GSXML.m b/Source/Additions/GSXML.m index 010225a48..3a0ac9f81 100644 --- a/Source/Additions/GSXML.m +++ b/Source/Additions/GSXML.m @@ -52,29 +52,7 @@ #include "GNUstepBase/GSMime.h" #include "GNUstepBase/GSXML.h" -#ifndef NeXT_Foundation_LIBRARY -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#else #include -#endif /* libxml headers */ #include diff --git a/Source/GSURLPrivate.h b/Source/GSURLPrivate.h index c9b73677f..7d1cb70c8 100644 --- a/Source/GSURLPrivate.h +++ b/Source/GSURLPrivate.h @@ -66,7 +66,7 @@ - (void) _setHeaders: (id)headers; - (void) _setStatusCode: (int)code text: (NSString*)text; - (void) _setValue: (NSString *)value forHTTPHeaderField: (NSString *)field; -- (NSString *) _valueForHTTPHeaderField: (NSString *)field; +- (NSString*) _valueForHTTPHeaderField: (NSString*)field; @end diff --git a/Source/NSURLProtocol.m b/Source/NSURLProtocol.m index 8621a6ff8..2729ff46b 100644 --- a/Source/NSURLProtocol.m +++ b/Source/NSURLProtocol.m @@ -74,16 +74,27 @@ typedef struct { static NSMutableArray *registered = nil; static NSLock *regLock = nil; +static Class abstractClass = nil; +static NSURLProtocol *placeholder = nil; @implementation NSURLProtocol + (id) allocWithZone: (NSZone*)z { - NSURLProtocol *o = [super allocWithZone: z]; + NSURLProtocol *o; - if (o != nil) + if ((self == abstractClass) && (z == 0 || z == NSDefaultMallocZone())) { - o->_NSURLProtocolInternal = NSZoneCalloc(z, 1, sizeof(Internal)); + /* Return a default placeholder instance to avoid the overhead of + * creating and destroying instances of the abstract class. + */ + o = placeholder; + } + else + { + /* Create and return an instance of the concrete subclass. + */ + o = (NSURLProtocol*)NSAllocateObject(self, 0, z); } return o; } @@ -92,6 +103,9 @@ static NSLock *regLock = nil; { if (registered == nil) { + abstractClass = [NSURLProtocol class]; + placeholder = (NSURLProtocol*)NSAllocateObject(abstractClass, 0, + NSDefaultMallocZone()); registered = [NSMutableArray new]; regLock = [NSLock new]; [self registerClass: [_NSHTTPURLProtocol class]]; @@ -145,6 +159,11 @@ static NSLock *regLock = nil; - (void) dealloc { + if (self == placeholder) + { + [self retain]; + return; + } if (this != 0) { [self stopLoading]; @@ -153,6 +172,7 @@ static NSLock *regLock = nil; RELEASE(this->cachedResponse); RELEASE(this->request); NSZoneFree([self zone], this); + _NSURLProtocolInternal = 0; } [super dealloc]; } @@ -160,14 +180,27 @@ static NSLock *regLock = nil; - (NSString*) description { return [NSString stringWithFormat:@"%@ %@", - [super description], this->request]; + [super description], this ? (id)this->request : nil]; +} + +- (id) init +{ + if ((self = [super init]) != nil) + { + if (isa != abstractClass) + { + _NSURLProtocolInternal = NSZoneCalloc(GSObjCZone(self), + 1, sizeof(Internal)); + } + } + return self; } - (id) initWithRequest: (NSURLRequest *)request cachedResponse: (NSCachedURLResponse *)cachedResponse client: (id )client { - if (isa == [NSURLProtocol class]) + if (isa == abstractClass) { unsigned count; @@ -189,7 +222,7 @@ static NSLock *regLock = nil; cachedResponse: cachedResponse client: client]; } - if ((self = [super init]) != nil) + if ((self = [self init]) != nil) { this->request = [request copy]; this->cachedResponse = RETAIN(cachedResponse); @@ -390,82 +423,83 @@ static NSLock *regLock = nil; { // process header line unsigned char *c, *end; NSString *key, *val; -#if 0 +#if 1 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 - } + { // 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; + { // 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; - } + 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 +NSLog(@"Received"); + [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 - } - } + { + 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 + ; // skip spaces val=[NSString stringWithCString: (char *) c length: end-c]; [_headers setObject: val forKey: [key lowercaseString]]; - return NO; // not yet done + return NO; // not yet done } - (void) _processHeader: (unsigned char *) buffer length: (int) len { // next header fragment received unsigned char *ptr, *end; -#if 0 +#if 1 NSLog(@"received %d bytes", len); #endif if (len <= 0) @@ -534,147 +568,156 @@ static NSLock *regLock = nil; #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 (stream == this->input) + { #if 1 - NSLog(@"receive error %@", [NSError _last]); + NSLog(@"input stream handleEvent: %x for: %@", event, self); #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; - } - } + switch(event) + { + case NSStreamEventHasBytesAvailable: + { + unsigned char buffer[512]; + int len; + + 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 1 + 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 1 + 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; - } - } + { + 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]] UTF8String]; + [(NSOutputStream *) stream write: msg + maxLength: strlen((char *) msg)]; + #if 0 + NSLog(@"sent %s", msg); + #endif + _headerEnumerator = [[[this->request allHTTPHeaderFields] keyEnumerator] retain]; + return; + } + case NSStreamEventHasSpaceAvailable: + { + // FIXME: should also send out relevant Cookies + if (_headerEnumerator) + { // send next header + NSString *key; + key = [_headerEnumerator nextObject]; + if (key) + { + #if 0 + 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 0 + 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]]; } diff --git a/Source/NSURLResponse.m b/Source/NSURLResponse.m index c7bc5a3fe..21f9be1c4 100644 --- a/Source/NSURLResponse.m +++ b/Source/NSURLResponse.m @@ -399,6 +399,5 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks = { return this->statusCode; } - @end diff --git a/Source/unix/NSStream.m b/Source/unix/NSStream.m index e77b9203d..e3e7b6180 100644 --- a/Source/unix/NSStream.m +++ b/Source/unix/NSStream.m @@ -1268,7 +1268,7 @@ static void setNonblocking(int fd) inputStream: (NSInputStream **)inputStream outputStream: (NSOutputStream **)outputStream { - NSString *address = [host address]; + NSString *address = host ? (id)[host address] : (id)@"127.0.0.1"; GSSocketInputStream *ins = nil; GSSocketOutputStream *outs = nil; int sock; @@ -1281,10 +1281,10 @@ static void setNonblocking(int fd) if (!ins) { #if defined(PF_INET6) - ins = [[GSInet6InputStream alloc] initToAddr: address - port: port]; - outs = [[GSInet6OutputStream alloc] initToAddr: address - port: port]; + ins = AUTORELEASE([[GSInet6InputStream alloc] + initToAddr: address port: port]); + outs = AUTORELEASE([[GSInet6OutputStream alloc] + initToAddr: address port: port]); sock = socket(PF_INET6, SOCK_STREAM, 0); #else sock = -1; @@ -1308,7 +1308,6 @@ static void setNonblocking(int fd) [outs setSibling: ins]; *outputStream = outs; } - return; } + (void) getLocalStreamsToPath: (NSString *)path diff --git a/Source/win32/NSStreamWin32.m b/Source/win32/NSStreamWin32.m index 741335e5f..0c702860c 100644 --- a/Source/win32/NSStreamWin32.m +++ b/Source/win32/NSStreamWin32.m @@ -1926,7 +1926,7 @@ static id propertyForInet6Stream(int descriptor, NSString *key) inputStream: (NSInputStream **)inputStream outputStream: (NSOutputStream **)outputStream { - NSString *address = [host address]; + NSString *address = host ? (id)[host address] : (id)@"127.0.0.1"; GSSocketInputStream *ins = nil; GSSocketOutputStream *outs = nil; int sock;