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

@ -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;