mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 16:33:29 +00:00
Commit accidentally missed changes.
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@25138 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
526c094ac3
commit
43d9660c0e
6 changed files with 970 additions and 28 deletions
|
@ -1,3 +1,12 @@
|
|||
2007-05-11 Richard Frith-Macdonald <rfm@gnu.org>
|
||||
|
||||
* 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 <nicola.pero@meta-innovation.com>
|
||||
|
||||
* configure.ac: Set GNUSTEP_MAKEFILES to CURRENT_GNUSTEP_MAKEFILES
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
@ -22,15 +22,128 @@
|
|||
Boston, MA 02111 USA.
|
||||
*/
|
||||
|
||||
#include <Foundation/NSRunLoop.h>
|
||||
#include "GSURLPrivate.h"
|
||||
|
||||
|
||||
@interface _NSURLConnectionDataCollector : NSObject <NSURLProtocolClient>
|
||||
{
|
||||
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 <NSURLProtocolClient>
|
||||
{
|
||||
@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
|
||||
}
|
||||
|
|
|
@ -22,15 +22,47 @@
|
|||
Boston, MA 02111 USA.
|
||||
*/
|
||||
|
||||
#include <Foundation/NSError.h>
|
||||
#include <Foundation/NSHost.h>
|
||||
#include <Foundation/NSRunLoop.h>
|
||||
|
||||
#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 <NSURLProtocolClient> client;
|
||||
id <NSURLProtocolClient> 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 <NSURLProtocolClient>)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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue