diff --git a/ChangeLog b/ChangeLog index 388653730..602e57bfe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2007-05-14 Richard Frith-Macdonald + + * 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 * Source/NSURLProtocol.m: diff --git a/Headers/Additions/GNUstepBase/GSMime.h b/Headers/Additions/GNUstepBase/GSMime.h index 87b01fc66..74659d3f1 100644 --- a/Headers/Additions/GNUstepBase/GSMime.h +++ b/Headers/Additions/GNUstepBase/GSMime.h @@ -28,15 +28,16 @@ #ifndef __GSMime_h_GNUSTEP_BASE_INCLUDE #define __GSMime_h_GNUSTEP_BASE_INCLUDE -#include +#import #if OS_API_VERSION(GS_API_NONE,GS_API_LATEST) #ifdef NeXT_Foundation_LIBRARY -#include +#import #else -#include -#include +#import +#import +#import #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 diff --git a/Source/Additions/GNUmakefile b/Source/Additions/GNUmakefile index d494a77d6..540df2549 100644 --- a/Source/Additions/GNUmakefile +++ b/Source/Additions/GNUmakefile @@ -40,6 +40,7 @@ Additions_OBJC_FILES =\ GSMime.m \ GSXML.m \ GSFunctions.m \ + GSInsensitiveDictionary.m \ behavior.m ifneq ($(OBJC_RUNTIME_LIB), gnu) diff --git a/Source/Additions/GSInsensitiveDictionary.m b/Source/Additions/GSInsensitiveDictionary.m new file mode 100644 index 000000000..7f64abe82 --- /dev/null +++ b/Source/Additions/GSInsensitiveDictionary.m @@ -0,0 +1,482 @@ +/** Interface to concrete implementation of NSDictionary + Copyright (C) 2007 Free Software Foundation, Inc. + + Written by: Richard Frith-Macdonald + 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 + diff --git a/Source/Additions/GSMime.m b/Source/Additions/GSMime.m index 1e3642aa1..8a6f69831 100644 --- a/Source/Additions/GSMime.m +++ b/Source/Additions/GSMime.m @@ -52,13 +52,16 @@ */ #include "config.h" -#include -#include "GNUstepBase/GSMime.h" -#include "GNUstepBase/GSXML.h" -#include "GNUstepBase/GSCategories.h" -#include "GNUstepBase/Unicode.h" -#include -#include +#include +#include + +#import +#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.
+ * 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.
+ * 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.
+ * If given a nil or empty string argument, sets the name to 'unknown'.
+ * 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; diff --git a/Source/GSPrivate.h b/Source/GSPrivate.h index e01802f86..7ac8199dd 100644 --- a/Source/GSPrivate.h +++ b/Source/GSPrivate.h @@ -24,7 +24,10 @@ #ifndef _GSPrivate_h_ #define _GSPrivate_h_ -#include "Foundation/NSError.h" +#import "Foundation/NSError.h" + +@class _GSInsensitiveDictionary; +@class _GSMutableInsensitiveDictionary; @class NSNotification; diff --git a/Source/GSStream.m b/Source/GSStream.m index 6e5b3c17f..19f939be4 100644 --- a/Source/GSStream.m +++ b/Source/GSStream.m @@ -216,7 +216,6 @@ static RunLoopEventType typeForStream(NSStream *aStream) extra: (void*)extra forMode: (NSString*)mode { -NSLog(@"Event on %p", self); [self _dispatch]; } diff --git a/Source/NSURLProtocol.m b/Source/NSURLProtocol.m index 1ab4f080a..81fc31911 100644 --- a/Source/NSURLProtocol.m +++ b/Source/NSURLProtocol.m @@ -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 } diff --git a/Source/NSURLResponse.m b/Source/NSURLResponse.m index 21f9be1c4..453a76e61 100644 --- a/Source/NSURLResponse.m +++ b/Source/NSURLResponse.m @@ -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