Various URL handling improvments.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@25154 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Richard Frith-MacDonald 2007-05-14 16:55:16 +00:00
parent cc25eedb9e
commit 9a44af0e80
9 changed files with 969 additions and 278 deletions

View file

@ -1,3 +1,14 @@
2007-05-14 Richard Frith-Macdonald <rfm@gnu.org>
* Source/Additions/GSInsensitiveDictionary.m: For internal use storing
values on case insensitive keys.
* Source/Additions/GNUmakefile: Build insensitive dictionaries.
* Source/GSPrivate.h: Make insensitive dictionaries available.
* Source/NSURLResponse.m: Use insensitive dictionaries.
* Source/NSURLProtocol.m: Clean up.
* Source/Additions/GSMime.m: New stuff to retain case information.
* Headers/Additions/GNUstepBase/GSMime.h: ditto.
2007-05-14 Richard Frith-Macdonald <rfm@gnu.org>
* Source/NSURLProtocol.m:

View file

@ -28,15 +28,16 @@
#ifndef __GSMime_h_GNUSTEP_BASE_INCLUDE
#define __GSMime_h_GNUSTEP_BASE_INCLUDE
#include <GNUstepBase/GSVersionMacros.h>
#import <GNUstepBase/GSVersionMacros.h>
#if OS_API_VERSION(GS_API_NONE,GS_API_LATEST)
#ifdef NeXT_Foundation_LIBRARY
#include <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#else
#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
#import <Foundation/NSMapTable.h>
#endif
#if defined(__cplusplus)
@ -71,9 +72,10 @@ extern "C" {
NSString *name;
NSString *value;
NSMutableDictionary *objects;
NSMutableDictionary *params;
NSMutableDictionary *params;
}
+ (NSString*) makeQuoted: (NSString*)v always: (BOOL)flag;
+ (NSString*) makeToken: (NSString*)t preservingCase: (BOOL)preserve;
+ (NSString*) makeToken: (NSString*)t;
- (id) copyWithZone: (NSZone*)z;
- (id) initWithName: (NSString*)n
@ -82,11 +84,14 @@ extern "C" {
value: (NSString*)v
parameters: (NSDictionary*)p;
- (NSString*) name;
- (NSString*) namePreservingCase: (BOOL)preserve;
- (id) objectForKey: (NSString*)k;
- (NSDictionary*) objects;
- (NSString*) parameterForKey: (NSString*)k;
- (NSDictionary*) parameters;
- (NSDictionary*) parametersPreservingCase: (BOOL)preserve;
- (NSMutableData*) rawMimeData;
- (NSMutableData*) rawMimeDataPreservingCase: (BOOL)preserve;
- (void) setName: (NSString*)s;
- (void) setObject: (id)o forKey: (NSString*)k;
- (void) setParameter: (NSString*)v forKey: (NSString*)k;
@ -228,7 +233,6 @@ extern "C" {
- (NSString*) scanToken: (NSScanner*)scanner;
- (void) setBuggyQuotes: (BOOL)flag;
- (void) setDefaultCharset: (NSString*)aName;
- (void) setIsHttp;
@end

View file

@ -40,6 +40,7 @@ Additions_OBJC_FILES =\
GSMime.m \
GSXML.m \
GSFunctions.m \
GSInsensitiveDictionary.m \
behavior.m
ifneq ($(OBJC_RUNTIME_LIB), gnu)

View file

@ -0,0 +1,482 @@
/** Interface to concrete implementation of NSDictionary
Copyright (C) 2007 Free Software Foundation, Inc.
Written by: Richard Frith-Macdonald <richard@brainstorm.co.uk>
Date: May 2007
This file is part of the GNUstep Base Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111 USA.
*/
#include "config.h"
#include "Foundation/NSDictionary.h"
#include "Foundation/NSAutoreleasePool.h"
#include "Foundation/NSUtilities.h"
#include "Foundation/NSString.h"
#include "Foundation/NSException.h"
#include "Foundation/NSPortCoder.h"
#include "Foundation/NSDebug.h"
// For private method _decodeArrayOfObjectsForKey:
#include "Foundation/NSKeyedArchiver.h"
#include "GNUstepBase/GSObjCRuntime.h"
/*
* The 'Fastmap' stuff provides an inline implementation of a mapping
* table - for maximum performance.
*/
#define GSI_MAP_KTYPES GSUNION_OBJ
#define GSI_MAP_VTYPES GSUNION_OBJ
#define GSI_MAP_HASH(M, X) [[X.obj lowercaseString] hash]
#define GSI_MAP_EQUAL(M, X,Y) (([X.obj caseInsensitiveCompare: Y.obj]\
== NSOrderedSame) ? YES : NO)
#define GSI_MAP_RETAIN_KEY(M, X) ((X).obj) = \
[((id)(X).obj) copyWithZone: map->zone]
#include "GNUstepBase/GSIMap.h"
@interface _GSInsensitiveDictionary : NSDictionary
{
@public
GSIMapTable_t map;
}
@end
@interface _GSMutableInsensitiveDictionary : NSMutableDictionary
{
@public
GSIMapTable_t map;
}
@end
@interface _GSInsensitiveDictionaryKeyEnumerator : NSEnumerator
{
_GSInsensitiveDictionary *dictionary;
GSIMapEnumerator_t enumerator;
}
@end
@interface _GSInsensitiveDictionaryObjectEnumerator : _GSInsensitiveDictionaryKeyEnumerator
@end
@implementation _GSInsensitiveDictionary
static SEL nxtSel;
static SEL objSel;
+ (void) initialize
{
if (self == [_GSInsensitiveDictionary class])
{
nxtSel = @selector(nextObject);
objSel = @selector(objectForKey:);
}
}
- (id) copyWithZone: (NSZone*)zone
{
return RETAIN(self);
}
- (unsigned) count
{
return map.nodeCount;
}
- (void) dealloc
{
GSIMapEmptyMap(&map);
[super dealloc];
}
- (void) encodeWithCoder: (NSCoder*)aCoder
{
if ([aCoder allowsKeyedCoding])
{
[super encodeWithCoder: aCoder];
}
else
{
unsigned count = map.nodeCount;
SEL sel = @selector(encodeObject:);
IMP imp = [aCoder methodForSelector: sel];
GSIMapEnumerator_t enumerator = GSIMapEnumeratorForMap(&map);
GSIMapNode node = GSIMapEnumeratorNextNode(&enumerator);
[aCoder encodeValueOfObjCType: @encode(unsigned) at: &count];
while (node != 0)
{
(*imp)(aCoder, sel, node->key.obj);
(*imp)(aCoder, sel, node->value.obj);
node = GSIMapEnumeratorNextNode(&enumerator);
}
GSIMapEndEnumerator(&enumerator);
}
}
- (unsigned) hash
{
return map.nodeCount;
}
- (id) init
{
return [self initWithObjects: 0 forKeys: 0 count: 0];
}
- (id) initWithCoder: (NSCoder*)aCoder
{
if ([aCoder allowsKeyedCoding])
{
self = [super initWithCoder: aCoder];
}
else
{
unsigned count;
id key;
id value;
SEL sel = @selector(decodeValueOfObjCType:at:);
IMP imp = [aCoder methodForSelector: sel];
const char *type = @encode(id);
[aCoder decodeValueOfObjCType: @encode(unsigned)
at: &count];
GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), count);
while (count-- > 0)
{
(*imp)(aCoder, sel, type, &key);
(*imp)(aCoder, sel, type, &value);
GSIMapAddPairNoRetain(&map, (GSIMapKey)key, (GSIMapVal)value);
}
}
return self;
}
/* Designated initialiser */
- (id) initWithObjects: (id*)objs forKeys: (id*)keys count: (unsigned)c
{
unsigned int i;
GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), c);
for (i = 0; i < c; i++)
{
GSIMapNode node;
if (keys[i] == nil)
{
IF_NO_GC(AUTORELEASE(self));
[NSException raise: NSInvalidArgumentException
format: @"Tried to init dictionary with nil key"];
}
if (objs[i] == nil)
{
IF_NO_GC(AUTORELEASE(self));
[NSException raise: NSInvalidArgumentException
format: @"Tried to init dictionary with nil value"];
}
node = GSIMapNodeForKey(&map, (GSIMapKey)keys[i]);
if (node)
{
IF_NO_GC(RETAIN(objs[i]));
RELEASE(node->value.obj);
node->value.obj = objs[i];
}
else
{
GSIMapAddPair(&map, (GSIMapKey)keys[i], (GSIMapVal)objs[i]);
}
}
return self;
}
/*
* This avoids using the designated initialiser for performance reasons.
*/
- (id) initWithDictionary: (NSDictionary*)other
copyItems: (BOOL)shouldCopy
{
NSZone *z = GSObjCZone(self);
unsigned c = [other count];
GSIMapInitWithZoneAndCapacity(&map, z, c);
if (c > 0)
{
NSEnumerator *e = [other keyEnumerator];
IMP nxtObj = [e methodForSelector: nxtSel];
IMP otherObj = [other methodForSelector: objSel];
BOOL isProxy = [other isProxy];
unsigned i;
for (i = 0; i < c; i++)
{
GSIMapNode node;
id k;
id o;
if (isProxy == YES)
{
k = [e nextObject];
o = [other objectForKey: k];
}
else
{
k = (*nxtObj)(e, nxtSel);
o = (*otherObj)(other, objSel, k);
}
k = [k copyWithZone: z];
if (k == nil)
{
IF_NO_GC(AUTORELEASE(self));
[NSException raise: NSInvalidArgumentException
format: @"Tried to init dictionary with nil key"];
}
if (shouldCopy)
{
o = [o copyWithZone: z];
}
else
{
o = RETAIN(o);
}
if (o == nil)
{
IF_NO_GC(AUTORELEASE(self));
[NSException raise: NSInvalidArgumentException
format: @"Tried to init dictionary with nil value"];
}
node = GSIMapNodeForKey(&map, (GSIMapKey)k);
if (node)
{
RELEASE(node->value.obj);
node->value.obj = o;
}
else
{
GSIMapAddPairNoRetain(&map, (GSIMapKey)k, (GSIMapVal)o);
}
}
}
return self;
}
- (BOOL) isEqualToDictionary: (NSDictionary*)other
{
unsigned count;
if (other == self)
{
return YES;
}
count = map.nodeCount;
if (count == [other count])
{
if (count > 0)
{
GSIMapEnumerator_t enumerator;
GSIMapNode node;
IMP otherObj = [other methodForSelector: objSel];
enumerator = GSIMapEnumeratorForMap(&map);
while ((node = GSIMapEnumeratorNextNode(&enumerator)) != 0)
{
id o1 = node->value.obj;
id o2 = (*otherObj)(other, objSel, node->key.obj);
if (o1 != o2 && [o1 isEqual: o2] == NO)
{
GSIMapEndEnumerator(&enumerator);
return NO;
}
}
GSIMapEndEnumerator(&enumerator);
}
return YES;
}
return NO;
}
- (NSEnumerator*) keyEnumerator
{
return AUTORELEASE([[_GSInsensitiveDictionaryKeyEnumerator allocWithZone:
NSDefaultMallocZone()] initWithDictionary: self]);
}
- (NSEnumerator*) objectEnumerator
{
return AUTORELEASE([[_GSInsensitiveDictionaryObjectEnumerator allocWithZone:
NSDefaultMallocZone()] initWithDictionary: self]);
}
- (id) objectForKey: aKey
{
if (aKey != nil)
{
GSIMapNode node = GSIMapNodeForKey(&map, (GSIMapKey)aKey);
if (node)
{
return node->value.obj;
}
}
return nil;
}
@end
@implementation _GSMutableInsensitiveDictionary
+ (void) initialize
{
if (self == [_GSMutableInsensitiveDictionary class])
{
GSObjCAddClassBehavior(self, [_GSInsensitiveDictionary class]);
}
}
- (id) copyWithZone: (NSZone*)zone
{
NSDictionary *copy = [_GSInsensitiveDictionary allocWithZone: zone];
return [copy initWithDictionary: self copyItems: NO];
}
- (id) init
{
return [self initWithCapacity: 0];
}
/* Designated initialiser */
- (id) initWithCapacity: (unsigned)cap
{
GSIMapInitWithZoneAndCapacity(&map, GSObjCZone(self), cap);
return self;
}
- (id) makeImmutableCopyOnFail: (BOOL)force
{
#ifndef NDEBUG
GSDebugAllocationRemove(isa, self);
#endif
isa = [_GSInsensitiveDictionary class];
#ifndef NDEBUG
GSDebugAllocationAdd(isa, self);
#endif
return self;
}
- (void) setObject: (id)anObject forKey: (id)aKey
{
GSIMapNode node;
if (aKey == nil)
{
NSException *e;
e = [NSException exceptionWithName: NSInvalidArgumentException
reason: @"Tried to add nil key to dictionary"
userInfo: self];
[e raise];
}
if ([aKey isKindOfClass: [NSString class]] == NO)
{
NSException *e;
e = [NSException exceptionWithName: NSInvalidArgumentException
reason: @"Tried to add non-string key"
userInfo: self];
[e raise];
}
node = GSIMapNodeForKey(&map, (GSIMapKey)aKey);
if (node)
{
IF_NO_GC(RETAIN(anObject));
RELEASE(node->value.obj);
node->value.obj = anObject;
}
else
{
GSIMapAddPair(&map, (GSIMapKey)aKey, (GSIMapVal)anObject);
}
}
- (void) removeAllObjects
{
GSIMapCleanMap(&map);
}
- (void) removeObjectForKey: (id)aKey
{
if (aKey == nil)
{
NSWarnMLog(@"attempt to remove nil key from dictionary %@", self);
return;
}
GSIMapRemoveKey(&map, (GSIMapKey)aKey);
}
@end
@implementation _GSInsensitiveDictionaryKeyEnumerator
- (id) initWithDictionary: (NSDictionary*)d
{
[super init];
dictionary = (_GSInsensitiveDictionary*)RETAIN(d);
enumerator = GSIMapEnumeratorForMap(&dictionary->map);
return self;
}
- (id) nextObject
{
GSIMapNode node = GSIMapEnumeratorNextNode(&enumerator);
if (node == 0)
{
return nil;
}
return node->key.obj;
}
- (void) dealloc
{
GSIMapEndEnumerator(&enumerator);
RELEASE(dictionary);
[super dealloc];
}
@end
@implementation _GSInsensitiveDictionaryObjectEnumerator
- (id) nextObject
{
GSIMapNode node = GSIMapEnumeratorNextNode(&enumerator);
if (node == 0)
{
return nil;
}
return node->value.obj;
}
@end

View file

@ -52,13 +52,16 @@
*/
#include "config.h"
#include <Foundation/Foundation.h>
#include "GNUstepBase/GSMime.h"
#include "GNUstepBase/GSXML.h"
#include "GNUstepBase/GSCategories.h"
#include "GNUstepBase/Unicode.h"
#include <string.h>
#include <ctype.h>
#include <string.h>
#include <ctype.h>
#import <Foundation/Foundation.h>
#import "GNUstepBase/GSMime.h"
#import "GNUstepBase/GSXML.h"
#import "GNUstepBase/GSCategories.h"
#import "GNUstepBase/Unicode.h"
#include "../GSPrivate.h"
static NSCharacterSet *whitespace = nil;
static NSCharacterSet *rfc822Specials = nil;
@ -1373,6 +1376,16 @@ wordData(NSString *word)
bytes = (unsigned char*)[data mutableBytes];
dataEnd = [data length];
/* If we are parsing an HTTP response, but it doesn't start
* with HTTP/ then it is a very old version where we just
* get the body and no headers.
*/
if (flags.isHttp == 1 && dataEnd > 4
&& memcmp(bytes, "HTTP/", 5) != 0)
{
flags.inBody = 1;
}
while (flags.inBody == 0)
{
if ([self _unfoldHeader] == NO)
@ -1418,7 +1431,7 @@ wordData(NSString *word)
GSMimeHeader *hdr;
info = [[document headersNamed: @"http"] lastObject];
if (info != nil)
if (info != nil && flags.isHttp == 1)
{
NSString *val;
@ -2158,6 +2171,7 @@ NSDebugMLLog(@"GSMime", @"Header parsed - %@", info);
{
flags.isHttp = 1;
}
@end
@implementation GSMimeParser (Private)
@ -2977,38 +2991,57 @@ static NSCharacterSet *tokenSet = nil;
}
/**
* Convert the supplied string to a standardized token by making it
* lowercase and removing all illegal characters.
* Convert the supplied string to a standardized token by removing
* all illegal characters. If preserve is NO then the result is
* converted to lowercase.<br />
* Returns an autoreleased (and possibly modified) copy of the original.
*/
+ (NSString*) makeToken: (NSString*)t
+ (NSString*) makeToken: (NSString*)t preservingCase: (BOOL)preserve
{
NSRange r;
NSMutableString *m = nil;
NSRange r;
t = [t lowercaseString];
r = [t rangeOfCharacterFromSet: nonToken];
if (r.length > 0)
{
NSMutableString *m = [t mutableCopy];
m = [t mutableCopy];
while (r.length > 0)
{
[m deleteCharactersInRange: r];
r = [m rangeOfCharacterFromSet: nonToken];
}
t = AUTORELEASE(m);
t = m;
}
if (preserve == YES)
{
t = [t lowercaseString];
}
else
{
t = AUTORELEASE([t copy]);
}
TEST_RELEASE(m);
return t;
}
/**
* Convert the supplied string to a standardized token by making it
* lowercase and removing all illegal characters.
*/
+ (NSString*) makeToken: (NSString*)t
{
return [self makeToken: t preservingCase: NO];
}
- (id) copyWithZone: (NSZone*)z
{
GSMimeHeader *c = [GSMimeHeader allocWithZone: z];
NSEnumerator *e;
NSString *k;
c = [c initWithName: [self name]
c = [c initWithName: [self namePreservingCase: YES]
value: [self value]
parameters: [self parameters]];
parameters: [self parametersPreservingCase: YES]];
e = [objects keyEnumerator];
while ((k = [e nextObject]) != nil)
{
@ -3021,8 +3054,8 @@ static NSCharacterSet *tokenSet = nil;
{
RELEASE(name);
RELEASE(value);
RELEASE(objects);
RELEASE(params);
TEST_RELEASE(objects);
TEST_RELEASE(params);
[super dealloc];
}
@ -3061,8 +3094,6 @@ static NSCharacterSet *tokenSet = nil;
value: (NSString*)v
parameters: (NSDictionary*)p
{
objects = [NSMutableDictionary new];
params = [NSMutableDictionary new];
[self setName: n];
[self setValue: v];
[self setParameters: p];
@ -3074,7 +3105,24 @@ static NSCharacterSet *tokenSet = nil;
*/
- (NSString*) name
{
return name;
return [self namePreservingCase: NO];
}
/**
* Returns the name of this header as originally set (without conversion
* to lowercase) if preserve is YES, but as a lowercase string if preserve
* is NO.
*/
- (NSString*) namePreservingCase: (BOOL)preserve
{
if (preserve == YES)
{
return name;
}
else
{
return [name lowercaseString];
}
}
/**
@ -3105,7 +3153,7 @@ static NSCharacterSet *tokenSet = nil;
k = [GSMimeHeader makeToken: k];
p = [params objectForKey: k];
}
return p;
return p;
}
/**
@ -3115,7 +3163,37 @@ static NSCharacterSet *tokenSet = nil;
*/
- (NSDictionary*) parameters
{
return AUTORELEASE([params copy]);
return [self parametersPreservingCase: NO];
}
/**
* Returns the parameters of this header ... a dictionary whose keys
* are strings preserving the case originally used to set the values
* or all lowercase depending on the preserve argument.
*/
- (NSDictionary*) parametersPreservingCase: (BOOL)preserve
{
NSMutableDictionary *m;
NSEnumerator *e;
NSString *k;
m = [NSMutableDictionary dictionaryWithCapacity: [params count]];
e = [params objectEnumerator];
if (preserve == YES)
{
while ((k = [e nextObject]) != nil)
{
[m setObject: [params objectForKey: k] forKey: k];
}
}
else
{
while ((k = [e nextObject]) != nil)
{
[m setObject: [params objectForKey: k] forKey: [k lowercaseString]];
}
}
return [m makeImmutableCopyOnFail: NO];
}
/**
@ -3123,48 +3201,80 @@ static NSCharacterSet *tokenSet = nil;
* and including a terminating CR-LF
*/
- (NSMutableData*) rawMimeData
{
return [self rawMimeDataPreservingCase: NO];
}
/**
* Returns the full text of the header, built from its component parts,
* and including a terminating CR-LF.<br />
* If preserve is YES then we attempt to build the text using the same
* case as it was originally parsed/set from, otherwise we use common
* onventions of capitalising the header names and using lowercase
* parameter names.
*/
- (NSMutableData*) rawMimeDataPreservingCase: (BOOL)preserve
{
NSMutableData *md = [NSMutableData dataWithCapacity: 128];
NSEnumerator *e = [params keyEnumerator];
NSString *k;
NSData *d = [[self name] dataUsingEncoding: NSASCIIStringEncoding];
NSString *n = [self namePreservingCase: preserve];
NSData *d = [n dataUsingEncoding: NSASCIIStringEncoding];
unsigned l = [d length];
char buf[l];
unsigned int i = 0;
BOOL conv = YES;
#define LIM 120
/*
* Capitalise the header name. However, the version header is a special
* case - it is defined as being literally 'MIME-Version'
*/
memcpy(buf, [d bytes], l);
if (l == 12 && memcmp(buf, "mime-version", 12) == 0)
if (preserve == YES)
{
memcpy(buf, "MIME-Version", 12);
/* Protect the user ... MIME-Version *must* have the correct case.
*/
if ([n caseInsensitiveCompare: @"MIME-Version"] == NSOrderedSame)
{
[md appendBytes: "MIME-Version" length: 12];
}
else
{
[md appendData: d];
}
}
else
{
while (i < l)
char buf[l];
unsigned i = 0;
#define LIM 120
/*
* Capitalise the header name. However, the version header is a special
* case - it is defined as being literally 'MIME-Version'
*/
memcpy(buf, [d bytes], l);
if (l == 12 && memcmp(buf, "mime-version", 12) == 0)
{
if (conv == YES)
memcpy(buf, "MIME-Version", 12);
}
else
{
while (i < l)
{
if (islower(buf[i]))
if (conv == YES)
{
buf[i] = toupper(buf[i]);
if (islower(buf[i]))
{
buf[i] = toupper(buf[i]);
}
}
if (buf[i++] == '-')
{
conv = YES;
}
else
{
conv = NO;
}
}
if (buf[i++] == '-')
{
conv = YES;
}
else
{
conv = NO;
}
}
[md appendBytes: buf length: l];
}
[md appendBytes: buf length: l];
d = wordData(value);
if ([md length] + [d length] + 2 > LIM)
{
@ -3188,6 +3298,10 @@ static NSCharacterSet *tokenSet = nil;
unsigned vl;
v = [GSMimeHeader makeQuoted: [params objectForKey: k] always: NO];
if (preserve == NO)
{
k = [k lowercaseString];
}
kd = wordData(k);
vd = wordData(v);
kl = [kd length];
@ -3216,13 +3330,14 @@ static NSCharacterSet *tokenSet = nil;
}
/**
* Sets the name of this header ... converts to lowercase and removes
* illegal characters. If given a nil or empty string argument,
* sets the name to 'unknown'.
* Sets the name of this header ... and removes illegal characters.<br />
* If given a nil or empty string argument, sets the name to 'unknown'.<br />
* NB. The value returned by the -name method will be a lowercase version
* of thae name.
*/
- (void) setName: (NSString*)s
{
s = [GSMimeHeader makeToken: s];
s = [GSMimeHeader makeToken: s preservingCase: YES];
if ([s length] == 0)
{
s = @"unknown";
@ -3244,6 +3359,10 @@ static NSCharacterSet *tokenSet = nil;
}
else
{
if (objects == nil)
{
objects = [NSMutableDictionary new];
}
[objects setObject: o forKey: k];
}
}
@ -3256,13 +3375,17 @@ static NSCharacterSet *tokenSet = nil;
*/
- (void) setParameter: (NSString*)v forKey: (NSString*)k
{
k = [GSMimeHeader makeToken: k];
k = [GSMimeHeader makeToken: k preservingCase: YES];
if (v == nil)
{
[params removeObjectForKey: k];
}
else
{
if (params == nil)
{
params = [_GSMutableInsensitiveDictionary new];
}
[params setObject: v forKey: k];
}
}
@ -3273,13 +3396,20 @@ static NSCharacterSet *tokenSet = nil;
*/
- (void) setParameters: (NSDictionary*)d
{
NSMutableDictionary *m = [NSMutableDictionary new];
NSEnumerator *e = [d keyEnumerator];
NSString *k;
NSMutableDictionary *m = nil;
unsigned c = [d count];
while ((k = [e nextObject]) != nil)
if (c > 0)
{
[m setObject: [d objectForKey: k] forKey: [GSMimeHeader makeToken: k]];
NSEnumerator *e = [d keyEnumerator];
NSString *k;
m = [[_GSMutableInsensitiveDictionary alloc] initWithCapacity: c];
while ((k = [e nextObject]) != nil)
{
[m setObject: [d objectForKey: k]
forKey: [GSMimeHeader makeToken: k preservingCase: YES]];
}
}
DESTROY(params);
params = m;

View file

@ -24,7 +24,10 @@
#ifndef _GSPrivate_h_
#define _GSPrivate_h_
#include "Foundation/NSError.h"
#import "Foundation/NSError.h"
@class _GSInsensitiveDictionary;
@class _GSMutableInsensitiveDictionary;
@class NSNotification;

View file

@ -216,7 +216,6 @@ static RunLoopEventType typeForStream(NSStream *aStream)
extra: (void*)extra
forMode: (NSString*)mode
{
NSLog(@"Event on %p", self);
[self _dispatch];
}

View file

@ -43,16 +43,16 @@
@interface _NSHTTPURLProtocol : NSURLProtocol
{
GSMimeParser *_parser; // for parsing incoming data
NSEnumerator *_headerEnumerator;
float _version;
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;
unsigned _bodyPos;
GSMimeParser *_parser; // Parser handling incoming data
unsigned _parseOffset; // Bytes of body loaded in parser.
float _version; // The HTTP version in use.
int _statusCode; // The HTTP status code returned.
NSInputStream *_body; // for sending the body
unsigned _writeOffset; // Request data to write
NSData *_writeData; // Request bytes written so far
BOOL _complete;
BOOL _debug;
BOOL _isLoading;
BOOL _shouldClose;
}
@end
@ -340,6 +340,14 @@ static NSURLProtocol *placeholder = nil;
userInfo: nil]];
return;
}
if (_isLoading == YES)
{
NSLog(@"startLoading when load in progress");
return;
}
_isLoading = YES;
_complete = NO;
_debug = YES;
if (0 && this->cachedResponse)
{
@ -350,9 +358,8 @@ static NSURLProtocol *placeholder = nil;
NSHost *host = [NSHost hostWithName: [url host]];
int port = [[url port] intValue];
_bodyPos = 0;
_parseOffset = 0;
DESTROY(_parser);
_parser = [GSMimeParser new];
if (host == nil)
{
@ -374,9 +381,10 @@ static NSURLProtocol *placeholder = nil;
outputStream: &this->output];
if (!this->input || !this->output)
{
#if 0
NSLog(@"did not create streams for %@: %u", host, [[url port] intValue]);
#endif
if (_debug == YES)
{
NSLog(@"did not create streams for %@:%@", host, [url port]);
}
[this->client URLProtocol: self didFailWithError:
[NSError errorWithDomain: @"can't connect" code: 0 userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:
@ -407,9 +415,12 @@ static NSURLProtocol *placeholder = nil;
- (void) stopLoading
{
#if 0
NSLog(@"stopLoading: %@", self);
#endif
if (_debug == YES)
{
NSLog(@"stopLoading: %@", self);
}
_isLoading = NO;
DESTROY(_writeData);
if (this->input != nil)
{
[self _unschedule];
@ -417,26 +428,10 @@ static NSURLProtocol *placeholder = nil;
[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
}
}
/*
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) _got: (NSStream*)stream
{
unsigned char buffer[BUFSIZ*64];
@ -444,7 +439,6 @@ static NSURLProtocol *placeholder = nil;
NSError *e;
NSData *d;
BOOL wasInHeaders = NO;
BOOL complete = NO;
readCount = [(NSInputStream *)stream read: buffer
maxLength: sizeof(buffer)];
@ -462,10 +456,19 @@ static NSURLProtocol *placeholder = nil;
}
return;
}
if (_debug)
{
NSLog(@"Read %d bytes: '%*.*s'", readCount, readCount, readCount, buffer);
}
if (_parser == nil)
{
_parser = [GSMimeParser new];
[_parser setIsHttp];
}
wasInHeaders = [_parser isInHeaders];
d = [NSData dataWithBytes: buffer length: readCount];
if ([_parser parse: d] == NO && (complete = [_parser isComplete]) == NO)
if ([_parser parse: d] == NO && (_complete = [_parser isComplete]) == NO)
{
if (_debug == YES)
{
@ -482,6 +485,7 @@ static NSURLProtocol *placeholder = nil;
{
BOOL isInHeaders = [_parser isInHeaders];
GSMimeDocument *document = [_parser mimeDocument];
unsigned bodyLength;
if (wasInHeaders == YES && isInHeaders == NO)
{
@ -534,26 +538,28 @@ static NSURLProtocol *placeholder = nil;
if (_statusCode == 204 || _statusCode == 304)
{
complete = YES; // No body expected.
_complete = YES; // No body expected.
}
else if ([enc isEqualToString: @"chunked"] == YES)
{
complete = NO; // Read chunked body data
_complete = NO; // Read chunked body data
}
if (complete == NO && [d length] == 0)
if (_complete == NO && [d length] == 0)
{
complete = YES; // Had EOF ... terminate
_complete = YES; // Had EOF ... terminate
}
/* Check for a redirect.
*/
s = [[document headerNamed: @"location"] value];
if ([s length] > 0)
{ // Location: entry exists
{
NSURLRequest *request;
NSURL *url;
url = [NSURL URLWithString: s];
request = [NSURLRequest requestWithURL: url];
if (request != nil)
if (request == nil)
{
NSError *e;
@ -601,7 +607,7 @@ static NSURLProtocol *placeholder = nil;
}
}
if (complete == YES)
if (_complete == YES)
{
[self _unschedule];
if (_shouldClose == YES)
@ -698,13 +704,20 @@ static NSURLProtocol *placeholder = nil;
* Tell superclass that we have successfully loaded the data.
*/
d = [_parser data];
if (_bodyPos > 0)
bodyLength = [d length];
if (bodyLength > _parseOffset)
{
d = [d subdataWithRange:
NSMakeRange(_bodyPos, [d length] - _bodyPos)];
if (_parseOffset > 0)
{
d = [d subdataWithRange:
NSMakeRange(_parseOffset, bodyLength - _parseOffset)];
}
_parseOffset = bodyLength;
if (_isLoading == YES)
{
[this->client URLProtocol: self didLoadData: d];
}
}
_bodyPos = [d length];
[this->client URLProtocol: self didLoadData: d];
if (_statusCode >= 200 && _statusCode < 300)
{
@ -725,17 +738,24 @@ static NSURLProtocol *placeholder = nil;
if ([_parser isInBody])
{
d = [_parser data];
if (_bodyPos > 0)
bodyLength = [d length];
if (bodyLength > _parseOffset)
{
d = [d subdataWithRange:
NSMakeRange(_bodyPos, [d length] - _bodyPos)];
if (_parseOffset > 0)
{
d = [d subdataWithRange:
NSMakeRange(_parseOffset, [d length] - _parseOffset)];
}
_parseOffset = bodyLength;
if (_isLoading == YES)
{
[this->client URLProtocol: self didLoadData: d];
}
}
_bodyPos = [d length];
[this->client URLProtocol: self didLoadData: d];
}
}
if (complete == NO && readCount == 0)
if (_complete == NO && readCount == 0)
{
/* The read failed ... dropped, but parsing is not complete.
* The request was sent, so we can't know whether it was
@ -772,9 +792,10 @@ static NSURLProtocol *placeholder = nil;
return;
case NSStreamEventOpenCompleted:
#if 0
NSLog(@"HTTP input stream opened");
#endif
if (_debug == YES)
{
NSLog(@"HTTP input stream opened");
}
return;
default:
@ -783,95 +804,196 @@ static NSURLProtocol *placeholder = nil;
}
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.0\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;
}
NSMutableString *m;
NSDictionary *d;
NSEnumerator *e;
NSString *s;
NSURL *u;
int l;
if (_debug == YES)
{
NSLog(@"HTTP output stream opened");
}
DESTROY(_writeData);
_writeOffset = 0;
if ([this->request HTTPBodyStream] == nil)
{
// Not streaming
l = [[this->request HTTPBody] length];
_version = 1.1;
}
else
{
// Stream and close
l = -1;
_version = 1.0;
_shouldClose = YES;
}
m = [[NSMutableString alloc] initWithCapacity: 1024];
/* The request line is of the form:
* method /path#fragment?query HTTP/version
* where the fragment and query parts may be missing
*/
[m appendString: [this->request HTTPMethod]];
[m appendString: @" "];
u = [this->request URL];
s = [u path];
if ([s hasPrefix: @"/"] == NO)
{
[m appendString: @"/"];
}
[m appendString: s];
s = [u fragment];
if ([s length] > 0)
{
[m appendString: @"#"];
[m appendString: s];
}
s = [u query];
if ([s length] > 0)
{
[m appendString: @"?"];
[m appendString: s];
}
[m appendFormat: @" HTTP/%0.1f\r\n", _version];
d = [this->request allHTTPHeaderFields];
e = [d keyEnumerator];
while ((s = [e nextObject]) != nil)
{
[m appendString: s];
[m appendString: @": "];
[m appendString: [d objectForKey: s]];
[m appendString: @"\r\n"];
}
if ([this->request valueForHTTPHeaderField: @"Host"] == nil)
{
[m appendFormat: @"Host: %@\r\n", [u host]];
}
if (l >= 0 && [this->request
valueForHTTPHeaderField: @"Content-Length"] == nil)
{
[m appendFormat: @"Content-Length: %d\r\n", l];
}
[m appendString: @"\r\n"]; // End of headers
_writeData = RETAIN([m dataUsingEncoding: NSASCIIStringEncoding]);
RELEASE(m);
} // Fall through to do the write
case NSStreamEventHasSpaceAvailable:
{
int written;
// 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 (_writeData != nil)
{
const unsigned char *bytes = [_writeData bytes];
unsigned len = [_writeData length];
written = [this->output write: bytes + _writeOffset
maxLength: len - _writeOffset];
if (written > 0)
{
#if 0
NSLog(@"error reading from HTTPBody stream %@", [NSError _last]);
#endif
[self _unschedule];
return;
if (_debug == YES)
{
NSLog(@"Wrote %d bytes: '%*.*s'", written,
written, written, bytes + _writeOffset);
}
_writeOffset += written;
if (_writeOffset >= len)
{
DESTROY(_writeData);
if (_body == nil)
{
_body = RETAIN([this->request HTTPBodyStream]);
if (_body == nil)
{
NSData *d = [this->request HTTPBody];
if (d != nil)
{
_body = [NSInputStream alloc];
_body = [_body initWithData: d];
[_body open];
}
}
}
}
}
}
else if (_body != nil)
{
if ([_body hasBytesAvailable])
{
unsigned char buffer[BUFSIZ*64];
int len;
len = [_body read: buffer maxLength: sizeof(buffer)];
if (len < 0)
{
if (_debug == YES)
{
NSLog(@"error reading from HTTPBody stream %@",
[NSError _last]);
}
[self _unschedule];
return;
}
else if (len > 0)
{
written = [this->output write: buffer maxLength: len];
if (written > 0)
{
if (_debug == YES)
{
NSLog(@"Wrote %d bytes: '%*.*s'", written,
written, written, buffer);
}
len -= written;
if (len > 0)
{
/* Couldn't write it all now, save and try
* again later.
*/
_writeData = [[NSData alloc] initWithBytes:
buffer + written length: len];
_writeOffset = 0;
}
}
}
else
{
[_body close];
DESTROY(_body);
}
}
else
{
[_body close];
DESTROY(_body);
}
}
if (_writeData == nil && _body == nil)
{
if (_debug)
{
NSLog(@"request sent");
}
if (_shouldClose == YES)
{
[this->output removeFromRunLoop:
[NSRunLoop currentRunLoop]
forMode: NSDefaultRunLoopMode];
[this->output close];
DESTROY(this->output);
}
[(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
}

View file

@ -23,9 +23,9 @@
*/
#include "GSURLPrivate.h"
#include "GSPrivate.h"
#include "Foundation/NSCoder.h"
#include "Foundation/NSMapTable.h"
#include "Foundation/NSScanner.h"
#include "NSCallBacks.h"
#include "GNUstepBase/GSMime.h"
@ -33,13 +33,13 @@
// Internal data storage
typedef struct {
long long expectedContentLength;
NSURL *URL;
NSString *MIMEType;
NSString *textEncodingName;
NSString *statusText;
NSMapTable *headers;
int statusCode;
long long expectedContentLength;
NSURL *URL;
NSString *MIMEType;
NSString *textEncodingName;
NSString *statusText;
_GSMutableInsensitiveDictionary *headers;
int statusCode;
} Internal;
typedef struct {
@ -49,41 +49,6 @@ typedef struct {
#define inst ((Internal*)(((priv*)o)->_NSURLResponseInternal))
/*
* Implement map keys for strings with case insensitive comparisons,
* so we can have case insensitive matching of http headers (correct
* behavior), but actually preserve case of headers stored and written
* in case the remote server is buggy and requires particular
* captialisation of headers (some http software is faulty like that).
*/
static unsigned int
_non_retained_id_hash(void *table, NSString* o)
{
return [[o uppercaseString] hash];
}
static BOOL
_non_retained_id_is_equal(void *table, NSString *o, NSString *p)
{
return ([o caseInsensitiveCompare: p] == NSOrderedSame) ? YES : NO;
}
typedef unsigned int (*NSMT_hash_func_t)(NSMapTable *, const void *);
typedef BOOL (*NSMT_is_equal_func_t)(NSMapTable *, const void *, const void *);
typedef void (*NSMT_retain_func_t)(NSMapTable *, const void *);
typedef void (*NSMT_release_func_t)(NSMapTable *, void *);
typedef NSString *(*NSMT_describe_func_t)(NSMapTable *, const void *);
static const NSMapTableKeyCallBacks headerKeyCallBacks =
{
(NSMT_hash_func_t) _non_retained_id_hash,
(NSMT_is_equal_func_t) _non_retained_id_is_equal,
(NSMT_retain_func_t) _NS_non_retained_id_retain,
(NSMT_release_func_t) _NS_non_retained_id_release,
(NSMT_describe_func_t) _NS_non_retained_id_describe,
NSNotAPointerMapKey
};
@implementation NSURLResponse (Private)
- (void) _setHeaders: (id)headers
{
@ -167,20 +132,13 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks =
{
if (this->headers == 0)
{
this->headers = NSCreateMapTable(headerKeyCallBacks,
NSObjectMapValueCallBacks, 8);
this->headers = [_GSMutableInsensitiveDictionary new];
}
NSMapInsert(this->headers, (void*)field, (void*)value);
[this->headers setObject: value forKey: field];
}
- (NSString *) _valueForHTTPHeaderField: (NSString *)field
{
NSString *value = nil;
if (this->headers != 0)
{
value = (NSString*)NSMapGet(this->headers, (void*)field);
}
return value;
return [this->headers objectForKey: field];
}
@end
@ -223,7 +181,7 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks =
}
else
{
inst->headers = NSCopyMapTableWithZone(this->headers, z);
inst->headers = [this->headers mutableCopy];
}
}
}
@ -238,10 +196,7 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks =
RELEASE(this->MIMEType);
RELEASE(this->textEncodingName);
RELEASE(this->statusText);
if (this->headers != 0)
{
NSFreeMapTable(this->headers);
}
RELEASE(this->headers);
NSZoneFree([self zone], this);
}
[super dealloc];
@ -376,23 +331,7 @@ static const NSMapTableKeyCallBacks headerKeyCallBacks =
- (NSDictionary *) allHeaderFields
{
NSMutableDictionary *fields;
fields = [NSMutableDictionary dictionaryWithCapacity: 8];
if (this->headers != 0)
{
NSMapEnumerator enumerator;
NSString *k;
NSString *v;
enumerator = NSEnumerateMapTable(this->headers);
while (NSNextMapEnumeratorPair(&enumerator, (void **)(&k), (void**)&v))
{
[fields setObject: v forKey: k];
}
NSEndMapTableEnumeration(&enumerator);
}
return fields;
return AUTORELEASE([this->headers copy]);
}
- (int) statusCode