mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 16:33:29 +00:00
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:
parent
cc25eedb9e
commit
9a44af0e80
9 changed files with 969 additions and 278 deletions
11
ChangeLog
11
ChangeLog
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ Additions_OBJC_FILES =\
|
|||
GSMime.m \
|
||||
GSXML.m \
|
||||
GSFunctions.m \
|
||||
GSInsensitiveDictionary.m \
|
||||
behavior.m
|
||||
|
||||
ifneq ($(OBJC_RUNTIME_LIB), gnu)
|
||||
|
|
482
Source/Additions/GSInsensitiveDictionary.m
Normal file
482
Source/Additions/GSInsensitiveDictionary.m
Normal 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
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -24,7 +24,10 @@
|
|||
#ifndef _GSPrivate_h_
|
||||
#define _GSPrivate_h_
|
||||
|
||||
#include "Foundation/NSError.h"
|
||||
#import "Foundation/NSError.h"
|
||||
|
||||
@class _GSInsensitiveDictionary;
|
||||
@class _GSMutableInsensitiveDictionary;
|
||||
|
||||
@class NSNotification;
|
||||
|
||||
|
|
|
@ -216,7 +216,6 @@ static RunLoopEventType typeForStream(NSStream *aStream)
|
|||
extra: (void*)extra
|
||||
forMode: (NSString*)mode
|
||||
{
|
||||
NSLog(@"Event on %p", self);
|
||||
[self _dispatch];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue