mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-22 08:26:27 +00:00
995 lines
22 KiB
Objective-C
995 lines
22 KiB
Objective-C
/** Implementation for NSHTTPCookie for GNUstep
|
|
Copyright (C) 2006 Software Foundation, Inc.
|
|
|
|
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
|
Date: 2006
|
|
|
|
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 Lesser 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
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA.
|
|
*/
|
|
|
|
/*
|
|
Try to handle cookies via the original Netscape specification
|
|
(http://web.archive.org/web/20070805052634/http://wp.netscape.com/newsref/std/cookie_spec.html)
|
|
and the official RFC2965 (http://tools.ietf.org/html/rfc2965).
|
|
|
|
Header fields named "Set-Cookie" are processed using either the original
|
|
spec or RFC2965. "Set-Cookie2" fields use the RFC spec. There are some
|
|
crazy things to be aware of though. Multiple cookies can be specified in the
|
|
same header and are separated by a comma. However, cookies themselves can
|
|
also contain commas, most notably in the Expires field (which is not quoted
|
|
and can contain spaces as well). The last key/value does not have to have a
|
|
semi-colon, so this can be tricky to parse if another cookie occurs
|
|
after this (See GSCookieStrings).
|
|
*/
|
|
|
|
#import "common.h"
|
|
#define EXPOSE_NSHTTPCookie_IVARS 1
|
|
#import "GSURLPrivate.h"
|
|
#import "Foundation/NSSet.h"
|
|
#import "Foundation/NSValue.h"
|
|
#import "Foundation/NSString.h"
|
|
#import "Foundation/NSDateFormatter.h"
|
|
#import "Foundation/NSLocale.h"
|
|
#import "Foundation/NSTimeZone.h"
|
|
#import "GNUstepBase/Unicode.h"
|
|
|
|
static NSString * const HTTPCookieHTTPOnly = @"HTTPOnly";
|
|
|
|
// Internal data storage
|
|
typedef struct {
|
|
NSDictionary *_properties;
|
|
} Internal;
|
|
|
|
#define this ((Internal*)(self->_NSHTTPCookieInternal))
|
|
#define inst ((Internal*)(o->_NSHTTPCookieInternal))
|
|
|
|
/* Bitmap of characters considered white space if in an old style property
|
|
* list. This is the same as the set given by the isspace() function in the
|
|
* POSIX locale, but (for cross-locale portability of property list files)
|
|
* is fixed, rather than locale dependent.
|
|
*/
|
|
static const unsigned char whitespace[32] = {
|
|
'\x00',
|
|
'\x3f',
|
|
'\x00',
|
|
'\x00',
|
|
'\x01',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
'\x00',
|
|
};
|
|
|
|
#define IS_BIT_SET(a,i) ((((a) & (1<<(i)))) > 0)
|
|
|
|
#define GS_IS_WHITESPACE(X) IS_BIT_SET(whitespace[(X)/8], (X) % 8)
|
|
|
|
static id GSPropertyListFromCookieFormat(NSString *string, int version);
|
|
static NSMutableArray *GSCookieStrings(NSString *string);
|
|
|
|
@implementation NSHTTPCookie
|
|
|
|
+ (id) allocWithZone: (NSZone*)z
|
|
{
|
|
NSHTTPCookie *o = [super allocWithZone: z];
|
|
|
|
if (o != nil)
|
|
{
|
|
o->_NSHTTPCookieInternal = NSZoneCalloc(z, 1, sizeof(Internal));
|
|
}
|
|
return o;
|
|
}
|
|
|
|
+ (id) cookieWithProperties: (NSDictionary *)properties
|
|
{
|
|
NSHTTPCookie *o;
|
|
|
|
o = [[self alloc] initWithProperties: properties];
|
|
return AUTORELEASE(o);
|
|
}
|
|
|
|
+ (NSMutableArray *) _parseField: (NSString *)field
|
|
forHeader: (NSString *)header
|
|
andURL: (NSURL *)url
|
|
{
|
|
int version;
|
|
NSString *defaultPath;
|
|
NSString *defaultDomain;
|
|
NSMutableArray *cookies;
|
|
NSUInteger count;
|
|
|
|
if ([header isEqual: @"Set-Cookie"])
|
|
{
|
|
version = 0;
|
|
}
|
|
else if ([header isEqual: @"Set-Cookie2"])
|
|
{
|
|
version = 1;
|
|
}
|
|
else
|
|
{
|
|
return nil;
|
|
}
|
|
defaultDomain = [url host];
|
|
defaultPath = [url path];
|
|
if ([[url absoluteString] hasSuffix: @"/"] == NO)
|
|
{
|
|
defaultPath = [defaultPath stringByDeletingLastPathComponent];
|
|
}
|
|
|
|
cookies = GSCookieStrings(field);
|
|
count = [cookies count];
|
|
/* We could use an NSScanner here, but this string could contain all
|
|
sorts of odd stuff. It's not quite a property list either - it has
|
|
dates and also could have tokens without values. */
|
|
while (count-- > 0)
|
|
{
|
|
NSHTTPCookie *cookie = nil;
|
|
NSMutableDictionary *dict;
|
|
NSString *onecookie = [cookies objectAtIndex: count];
|
|
|
|
NS_DURING
|
|
dict = GSPropertyListFromCookieFormat(onecookie, version);
|
|
NS_HANDLER
|
|
dict = nil;
|
|
NS_ENDHANDLER
|
|
if ([dict count])
|
|
{
|
|
if ([dict objectForKey: NSHTTPCookiePath] == nil)
|
|
{
|
|
[dict setObject: defaultPath forKey: NSHTTPCookiePath];
|
|
}
|
|
if ([dict objectForKey: NSHTTPCookieDomain] == nil)
|
|
{
|
|
[dict setObject: defaultDomain forKey: NSHTTPCookieDomain];
|
|
}
|
|
cookie = [NSHTTPCookie cookieWithProperties: dict];
|
|
}
|
|
if (cookie)
|
|
{
|
|
[cookies replaceObjectAtIndex: count withObject: cookie];
|
|
}
|
|
else
|
|
{
|
|
[cookies removeObjectAtIndex: count];
|
|
}
|
|
}
|
|
return cookies;
|
|
}
|
|
|
|
+ (NSArray *) cookiesWithResponseHeaderFields: (NSDictionary *)headerFields
|
|
forURL: (NSURL *)URL
|
|
{
|
|
NSEnumerator *henum = [headerFields keyEnumerator];
|
|
NSMutableArray *a = [NSMutableArray array];
|
|
NSString *header;
|
|
|
|
while ((header = [henum nextObject]))
|
|
{
|
|
NSString *field = [headerFields objectForKey: header];
|
|
NSMutableArray *suba = [self _parseField: field
|
|
forHeader: header
|
|
andURL: URL];
|
|
if (suba)
|
|
{
|
|
[a addObjectsFromArray: suba];
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
|
|
+ (NSDictionary *) requestHeaderFieldsWithCookies: (NSArray *)cookies
|
|
{
|
|
int version;
|
|
NSString *field;
|
|
NSHTTPCookie *ck;
|
|
NSEnumerator *ckenum = [cookies objectEnumerator];
|
|
|
|
if ([cookies count] == 0)
|
|
{
|
|
NSLog(@"NSHTTPCookie requestHeaderFieldWithCookies: empty array");
|
|
return nil;
|
|
}
|
|
/* Assume these cookies all came from the same URL so we format based
|
|
on the version of the first. */
|
|
field = nil;
|
|
version = [(NSHTTPCookie *)[cookies objectAtIndex: 0] version];
|
|
if (version)
|
|
field = @"$Version=\"1\"";
|
|
while ((ck = [ckenum nextObject]))
|
|
{
|
|
NSString *str;
|
|
str = [NSString stringWithFormat: @"%@=%@", [ck name], [ck value]];
|
|
if (field)
|
|
field = [field stringByAppendingFormat: @"; %@", str];
|
|
else
|
|
field = str;
|
|
if (version && [ck path])
|
|
field = [field stringByAppendingFormat: @"; $Path=\"%@\"", [ck path]];
|
|
}
|
|
|
|
return [NSDictionary dictionaryWithObject: field forKey: @"Cookie"];
|
|
}
|
|
|
|
- (NSString *) comment
|
|
{
|
|
return [this->_properties objectForKey: NSHTTPCookieComment];
|
|
}
|
|
|
|
- (NSURL *) commentURL
|
|
{
|
|
return [this->_properties objectForKey: NSHTTPCookieCommentURL];
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
if (this != 0)
|
|
{
|
|
RELEASE(this->_properties);
|
|
NSZoneFree([self zone], this);
|
|
}
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSString *) domain
|
|
{
|
|
return [this->_properties objectForKey: NSHTTPCookieDomain];
|
|
}
|
|
|
|
- (NSDate *) expiresDate
|
|
{
|
|
return [this->_properties objectForKey: NSHTTPCookieExpires];
|
|
}
|
|
|
|
- (BOOL) _isValidProperty: (NSString *)prop
|
|
{
|
|
return ([prop length] && [prop rangeOfString: @"\n"].location == NSNotFound);
|
|
}
|
|
|
|
- (id) initWithProperties: (NSDictionary *)properties
|
|
{
|
|
NSMutableDictionary *rawProps;
|
|
|
|
if ((self = [super init]) == nil)
|
|
return nil;
|
|
|
|
/* Check a few values. Based on Mac OS X tests. */
|
|
if (![self _isValidProperty: [properties objectForKey: NSHTTPCookiePath]]
|
|
|| ![self _isValidProperty: [properties objectForKey: NSHTTPCookieDomain]]
|
|
|| ![self _isValidProperty: [properties objectForKey: NSHTTPCookieName]]
|
|
|| ![self _isValidProperty: [properties objectForKey: NSHTTPCookieValue]]
|
|
)
|
|
{
|
|
[self release];
|
|
return nil;
|
|
}
|
|
|
|
rawProps = AUTORELEASE([properties mutableCopy]);
|
|
if ([rawProps objectForKey: @"Created"] == nil)
|
|
{
|
|
NSInteger seconds;
|
|
NSDate *now;
|
|
|
|
/* Round to whole seconds, so that a serialization/deserialisation
|
|
* cycle produces an identical object whic hcan be used to eliminate
|
|
* duplicates.
|
|
*/
|
|
seconds = [NSDate timeIntervalSinceReferenceDate];
|
|
now = [NSDate dateWithTimeIntervalSinceReferenceDate: seconds];
|
|
[rawProps setObject: now forKey: @"Created"];
|
|
}
|
|
if ([rawProps objectForKey: NSHTTPCookieExpires] == nil
|
|
|| [[rawProps objectForKey: NSHTTPCookieExpires]
|
|
isKindOfClass: [NSDate class]] == NO)
|
|
{
|
|
[rawProps setObject: [NSNumber numberWithBool: YES]
|
|
forKey: NSHTTPCookieDiscard];
|
|
}
|
|
|
|
ASSIGNCOPY(this->_properties, rawProps);
|
|
return self;
|
|
}
|
|
|
|
- (BOOL) isSecure
|
|
{
|
|
return [[this->_properties objectForKey: NSHTTPCookieSecure] boolValue];
|
|
}
|
|
|
|
- (BOOL) isHTTPOnly
|
|
{
|
|
return [[this->_properties objectForKey: HTTPCookieHTTPOnly] boolValue];
|
|
}
|
|
|
|
- (BOOL) isSessionOnly
|
|
{
|
|
return [[this->_properties objectForKey: NSHTTPCookieDiscard] boolValue];
|
|
}
|
|
|
|
- (NSString *) name
|
|
{
|
|
return [this->_properties objectForKey: NSHTTPCookieName];
|
|
}
|
|
|
|
- (NSString *) path
|
|
{
|
|
return [this->_properties objectForKey: NSHTTPCookiePath];
|
|
}
|
|
|
|
- (NSArray *) portList
|
|
{
|
|
return [[this->_properties objectForKey: NSHTTPCookiePort]
|
|
componentsSeparatedByString: @","];
|
|
}
|
|
|
|
- (NSDictionary *) properties
|
|
{
|
|
return this->_properties;
|
|
}
|
|
|
|
- (NSString *) value
|
|
{
|
|
return [this->_properties objectForKey: NSHTTPCookieValue];
|
|
}
|
|
|
|
- (NSUInteger) version
|
|
{
|
|
return [[this->_properties objectForKey: NSHTTPCookieVersion] integerValue];
|
|
}
|
|
|
|
- (NSString *) description
|
|
{
|
|
return [NSString stringWithFormat: @"<NSHTTPCookie %p: %@=%@>", self,
|
|
[self name], [self value]];
|
|
}
|
|
|
|
- (NSUInteger) hash
|
|
{
|
|
return [[self properties] hash];
|
|
}
|
|
|
|
- (BOOL) isEqual: (id)other
|
|
{
|
|
return [[other properties] isEqual: [self properties]];
|
|
}
|
|
|
|
@end
|
|
|
|
#define inrange(ch,min,max) ((ch)>=(min) && (ch)<=(max))
|
|
#define char2num(ch) \
|
|
inrange(ch,'0','9') \
|
|
? ((ch)-0x30) \
|
|
: (inrange(ch,'a','f') \
|
|
? ((ch)-0x57) : ((ch)-0x37))
|
|
|
|
typedef struct {
|
|
const unsigned char *ptr;
|
|
unsigned end;
|
|
unsigned pos;
|
|
unsigned lin;
|
|
NSString *err;
|
|
int opt;
|
|
BOOL key;
|
|
BOOL old;
|
|
} pldata;
|
|
|
|
/*
|
|
* Returns YES if there is any non-whitespace text remaining.
|
|
*/
|
|
static BOOL skipSpace(pldata *pld)
|
|
{
|
|
unsigned char c;
|
|
|
|
while (pld->pos < pld->end)
|
|
{
|
|
c = pld->ptr[pld->pos];
|
|
|
|
if (GS_IS_WHITESPACE(c) == NO)
|
|
{
|
|
return YES;
|
|
}
|
|
if (c == '\n')
|
|
{
|
|
pld->lin++;
|
|
}
|
|
pld->pos++;
|
|
}
|
|
pld->err = @"reached end of string";
|
|
return NO;
|
|
}
|
|
|
|
NS_RETURNS_RETAINED static inline NSString*
|
|
parseQuotedString(pldata* pld)
|
|
{
|
|
unsigned start = ++pld->pos;
|
|
unsigned escaped = 0;
|
|
unsigned shrink = 0;
|
|
BOOL hex = NO;
|
|
NSString *obj;
|
|
|
|
while (pld->pos < pld->end)
|
|
{
|
|
unsigned char c = pld->ptr[pld->pos];
|
|
|
|
if (escaped)
|
|
{
|
|
if (escaped == 1 && c >= '0' && c <= '7')
|
|
{
|
|
escaped = 2;
|
|
hex = NO;
|
|
}
|
|
else if (escaped == 1 && (c == 'u' || c == 'U'))
|
|
{
|
|
escaped = 2;
|
|
hex = YES;
|
|
}
|
|
else if (escaped > 1)
|
|
{
|
|
if (hex && isxdigit(c))
|
|
{
|
|
shrink++;
|
|
escaped++;
|
|
if (escaped == 6)
|
|
{
|
|
escaped = 0;
|
|
}
|
|
}
|
|
else if (c >= '0' && c <= '7')
|
|
{
|
|
shrink++;
|
|
escaped++;
|
|
if (escaped == 4)
|
|
{
|
|
escaped = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pld->pos--;
|
|
escaped = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
escaped = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (c == '\\')
|
|
{
|
|
escaped = 1;
|
|
shrink++;
|
|
}
|
|
else if (c == '"')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (c == '\n')
|
|
pld->lin++;
|
|
pld->pos++;
|
|
}
|
|
if (pld->pos >= pld->end)
|
|
{
|
|
pld->err = @"reached end of string while parsing quoted string";
|
|
return nil;
|
|
}
|
|
if (pld->pos - start - shrink == 0)
|
|
{
|
|
obj = @"";
|
|
}
|
|
else
|
|
{
|
|
unsigned length;
|
|
unichar *chars;
|
|
unichar *temp = NULL;
|
|
unsigned int temp_length = 0;
|
|
unsigned j;
|
|
unsigned k;
|
|
|
|
if (!GSToUnicode(&temp, &temp_length, &pld->ptr[start],
|
|
pld->pos - start, NSUTF8StringEncoding,
|
|
NSDefaultMallocZone(), 0))
|
|
{
|
|
pld->err = @"invalid utf8 data while parsing quoted string";
|
|
return nil;
|
|
}
|
|
length = temp_length - shrink;
|
|
chars = NSAllocateCollectable(sizeof(unichar) * length, 0);
|
|
escaped = 0;
|
|
hex = NO;
|
|
for (j = 0, k = 0; j < temp_length; j++)
|
|
{
|
|
unichar c = temp[j];
|
|
|
|
if (escaped)
|
|
{
|
|
if (escaped == 1 && c >= '0' && c <= '7')
|
|
{
|
|
chars[k] = c - '0';
|
|
hex = NO;
|
|
escaped++;
|
|
}
|
|
else if (escaped == 1 && (c == 'u' || c == 'U'))
|
|
{
|
|
chars[k] = 0;
|
|
hex = YES;
|
|
escaped++;
|
|
}
|
|
else if (escaped > 1)
|
|
{
|
|
if (hex && isxdigit(c))
|
|
{
|
|
chars[k] <<= 4;
|
|
chars[k] |= char2num(c);
|
|
escaped++;
|
|
if (escaped == 6)
|
|
{
|
|
escaped = 0;
|
|
k++;
|
|
}
|
|
}
|
|
else if (c >= '0' && c <= '7')
|
|
{
|
|
chars[k] <<= 3;
|
|
chars[k] |= (c - '0');
|
|
escaped++;
|
|
if (escaped == 4)
|
|
{
|
|
escaped = 0;
|
|
k++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
escaped = 0;
|
|
j--;
|
|
k++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
escaped = 0;
|
|
switch (c)
|
|
{
|
|
case 'a' : chars[k] = '\a'; break;
|
|
case 'b' : chars[k] = '\b'; break;
|
|
case 't' : chars[k] = '\t'; break;
|
|
case 'r' : chars[k] = '\r'; break;
|
|
case 'n' : chars[k] = '\n'; break;
|
|
case 'v' : chars[k] = '\v'; break;
|
|
case 'f' : chars[k] = '\f'; break;
|
|
default : chars[k] = c; break;
|
|
}
|
|
k++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
chars[k] = c;
|
|
if (c == '\\')
|
|
{
|
|
escaped = 1;
|
|
}
|
|
else
|
|
{
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
|
|
NSZoneFree(NSDefaultMallocZone(), temp);
|
|
length = k;
|
|
|
|
obj = [NSString alloc];
|
|
obj = [obj initWithCharactersNoCopy: chars
|
|
length: length
|
|
freeWhenDone: YES];
|
|
}
|
|
pld->pos++;
|
|
return obj;
|
|
}
|
|
|
|
/* In cookies, keys are terminated by '=' and values are terminated by ';'
|
|
or and EOL */
|
|
NS_RETURNS_RETAINED static inline NSString*
|
|
parseUnquotedString(pldata *pld, char endChar)
|
|
{
|
|
unsigned start = pld->pos;
|
|
unsigned i;
|
|
unsigned length;
|
|
NSString *obj;
|
|
unichar *chars;
|
|
|
|
while (pld->pos < pld->end)
|
|
{
|
|
if ((pld->ptr[pld->pos]) == endChar)
|
|
break;
|
|
pld->pos++;
|
|
}
|
|
|
|
length = pld->pos - start;
|
|
chars = NSAllocateCollectable(sizeof(unichar) * length, 0);
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
chars[i] = pld->ptr[start + i];
|
|
}
|
|
|
|
obj = [NSString alloc];
|
|
obj = [obj initWithCharactersNoCopy: chars
|
|
length: length
|
|
freeWhenDone: YES];
|
|
|
|
return obj;
|
|
}
|
|
|
|
static BOOL
|
|
_setCookieKey(NSMutableDictionary *dict, NSString *key, NSString *value)
|
|
{
|
|
NSString *lKey;
|
|
|
|
if ([dict count] == 0)
|
|
{
|
|
/* This must be the name=value pair */
|
|
if ([value length] == 0)
|
|
return NO;
|
|
[dict setObject: key forKey: NSHTTPCookieName];
|
|
[dict setObject: value forKey: NSHTTPCookieValue];
|
|
return YES;
|
|
}
|
|
lKey = [key lowercaseString];
|
|
if ([lKey isEqual: @"comment"])
|
|
[dict setObject: value forKey: NSHTTPCookieComment];
|
|
else if ([lKey isEqual: @"commenturl"])
|
|
[dict setObject: value forKey: NSHTTPCookieCommentURL];
|
|
else if ([lKey isEqual: @"discard"])
|
|
[dict setObject: [NSNumber numberWithBool: YES]
|
|
forKey: NSHTTPCookieDiscard];
|
|
else if ([lKey isEqual: @"domain"])
|
|
[dict setObject: value forKey: NSHTTPCookieDomain];
|
|
else if ([lKey isEqual: @"expires"])
|
|
{
|
|
NSDate *expireDate;
|
|
NSDateFormatter *formatter;
|
|
NSLocale *locale;
|
|
NSTimeZone *gmtTimeZone;
|
|
|
|
locale = [NSLocale localeWithLocaleIdentifier: @"en_US"];
|
|
gmtTimeZone = [NSTimeZone timeZoneWithAbbreviation: @"GMT"];
|
|
|
|
formatter = [[NSDateFormatter alloc] init];
|
|
[formatter setDateFormat: @"EEE, dd-MMM-yyyy HH:mm:ss zzz"];
|
|
[formatter setLocale: locale];
|
|
[formatter setTimeZone: gmtTimeZone];
|
|
|
|
expireDate = [formatter dateFromString: value];
|
|
if (expireDate)
|
|
[dict setObject: expireDate forKey: NSHTTPCookieExpires];
|
|
RELEASE(formatter);
|
|
}
|
|
else if ([lKey isEqual: @"max-age"])
|
|
[dict setObject: value forKey: NSHTTPCookieMaximumAge];
|
|
else if ([lKey isEqual: @"originurl"])
|
|
[dict setObject: value forKey: NSHTTPCookieOriginURL];
|
|
else if ([lKey isEqual: @"path"])
|
|
[dict setObject: value forKey: NSHTTPCookiePath];
|
|
else if ([lKey isEqual: @"port"])
|
|
[dict setObject: value forKey: NSHTTPCookiePort];
|
|
else if ([lKey isEqual: @"secure"])
|
|
[dict setObject: [NSNumber numberWithBool: YES]
|
|
forKey: NSHTTPCookieSecure];
|
|
else if ([lKey isEqual: @"httponly"])
|
|
[dict setObject: [NSNumber numberWithBool: YES]
|
|
forKey: HTTPCookieHTTPOnly];
|
|
else if ([lKey isEqual: @"version"])
|
|
[dict setObject: value forKey: NSHTTPCookieVersion];
|
|
return YES;
|
|
}
|
|
|
|
static id
|
|
GSPropertyListFromCookieFormat(NSString *string, int version)
|
|
{
|
|
NSMutableDictionary *dict;
|
|
pldata _pld;
|
|
pldata *pld = &_pld;
|
|
NSData *d;
|
|
BOOL moreCharacters;
|
|
|
|
/*
|
|
* An empty string is a nil property list.
|
|
*/
|
|
if ([string length] == 0)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
d = [string dataUsingEncoding: NSUTF8StringEncoding];
|
|
NSCAssert(d, @"Couldn't get utf8 data from string.");
|
|
_pld.ptr = (unsigned char*)[d bytes];
|
|
_pld.pos = 0;
|
|
_pld.end = [d length];
|
|
_pld.err = nil;
|
|
_pld.lin = 0;
|
|
_pld.opt = 0;
|
|
_pld.key = NO;
|
|
_pld.old = YES; // OpenStep style
|
|
|
|
dict = [[NSMutableDictionary allocWithZone: NSDefaultMallocZone()]
|
|
initWithCapacity: 0];
|
|
while (skipSpace(pld) == YES)
|
|
{
|
|
id key;
|
|
id val;
|
|
|
|
if (pld->ptr[pld->pos] == '"')
|
|
{
|
|
key = parseQuotedString(pld);
|
|
}
|
|
else
|
|
{
|
|
unsigned int oldpos = pld->pos;
|
|
unsigned int keyvalpos = 0;
|
|
id keyval = parseUnquotedString(pld, ';');
|
|
keyvalpos = pld->pos;
|
|
pld->pos = oldpos;
|
|
key = parseUnquotedString(pld, '=');
|
|
|
|
// Detect value-less cookies like HTTPOnly; and Secure;
|
|
if ([keyval length] < [key length])
|
|
{
|
|
pld->pos = keyvalpos;
|
|
DESTROY(key);
|
|
key = keyval;
|
|
}
|
|
else
|
|
{
|
|
DESTROY(keyval);
|
|
}
|
|
}
|
|
if (key == nil)
|
|
{
|
|
DESTROY(dict);
|
|
break;
|
|
}
|
|
moreCharacters = skipSpace(pld);
|
|
if (moreCharacters == NO || pld->ptr[pld->pos] == ';')
|
|
{
|
|
pld->pos++;
|
|
if (_setCookieKey(dict, key, @"") == NO)
|
|
{
|
|
pld->err = @"invalid cookie pair";
|
|
DESTROY(dict);
|
|
}
|
|
RELEASE(key);
|
|
}
|
|
else if (pld->ptr[pld->pos] == '=')
|
|
{
|
|
pld->pos++;
|
|
if (skipSpace(pld) == NO)
|
|
{
|
|
RELEASE(key);
|
|
DESTROY(dict);
|
|
break;
|
|
}
|
|
if (pld->ptr[pld->pos] == '"')
|
|
{
|
|
val = parseQuotedString(pld);
|
|
}
|
|
else
|
|
{
|
|
val = parseUnquotedString(pld, ';');
|
|
}
|
|
if (val == nil)
|
|
{
|
|
RELEASE(key);
|
|
DESTROY(dict);
|
|
break;
|
|
}
|
|
skipSpace(pld);
|
|
if (_setCookieKey(dict, key, val) == NO)
|
|
{
|
|
pld->err = @"invalid cookie pair";
|
|
DESTROY(dict);
|
|
}
|
|
RELEASE(key);
|
|
RELEASE(val);
|
|
if (pld->pos < pld->end && pld->ptr[pld->pos] == ';')
|
|
{
|
|
pld->pos++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pld->err = @"unexpected character (wanted '=' or ';')";
|
|
RELEASE(key);
|
|
DESTROY(dict);
|
|
break;
|
|
}
|
|
}
|
|
if (dict == nil && _pld.err != nil)
|
|
{
|
|
RELEASE(dict);
|
|
[NSException raise: NSGenericException
|
|
format: @"Parse failed at line %d (char %d) - %@",
|
|
_pld.lin + 1, _pld.pos + 1, _pld.err];
|
|
}
|
|
return AUTORELEASE(dict);
|
|
}
|
|
|
|
/* Split a string containing comma seprated cookeie settings into an array
|
|
* of individual cookie specifications.
|
|
* Look for the comma that separates cookies. Commas can also occur in
|
|
* date strings, like "expires", but perhaps it can occur other places.
|
|
* For instance, the key/value pair key=value1,value2 is not really
|
|
* valid, but should we handle it anyway? Definitely we should handle the
|
|
* perfectly normal case of:
|
|
*
|
|
* Set-Cookie: domain=test.com; expires=Thu, 12-Sep-2109 14:58:04 GMT;
|
|
* session=foo
|
|
* Set-Cookie: bar=baz
|
|
*
|
|
* which gets concatenated into something like:
|
|
*
|
|
* Set-Cookie: domain=test.com; expires=Thu, 12-Sep-2109 14:58:04 GMT;
|
|
* session=foo,bar=baz
|
|
*/
|
|
static NSMutableArray*
|
|
GSCookieStrings(NSString *string)
|
|
{
|
|
unsigned char *ptr;
|
|
unsigned pos;
|
|
unsigned end;
|
|
NSData *d;
|
|
NSMutableArray *cookies;
|
|
unsigned start = 0;
|
|
unsigned saved = 0;
|
|
|
|
if ([string length] == 0)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
d = [string dataUsingEncoding: NSUTF8StringEncoding];
|
|
NSCAssert(d, @"Couldn't get utf8 data from string.");
|
|
ptr = (unsigned char*)[d bytes];
|
|
pos = 0;
|
|
end = [d length];
|
|
|
|
cookies = [NSMutableArray arrayWithCapacity: 4];
|
|
while (pos < end)
|
|
{
|
|
while (pos < end && isspace(ptr[pos]))
|
|
{
|
|
pos++;
|
|
}
|
|
start = pos;
|
|
|
|
while (pos < end)
|
|
{
|
|
if (ptr[pos] == ',')
|
|
{
|
|
/* Look ahead for something that will tell us if this is a
|
|
* separate cookie or not. We look for something of the form
|
|
* ' token =' where a space represents any optional whitespace.
|
|
*/
|
|
saved = pos++;
|
|
while (pos < end && isspace(ptr[pos]))
|
|
{
|
|
pos++;
|
|
}
|
|
if (pos < end)
|
|
{
|
|
const char *bad = "()<>@,;:\\\"/[]?={}";
|
|
int c = ptr[pos];
|
|
|
|
/* skip past token characters.
|
|
*/
|
|
while (c > 32 && c < 128 && strchr(bad, c) == 0)
|
|
{
|
|
pos++;
|
|
if (pos < end)
|
|
{
|
|
c = ptr[pos];
|
|
}
|
|
}
|
|
while (pos < end && isspace(ptr[pos]))
|
|
{
|
|
pos++;
|
|
}
|
|
if (pos < end && '=' == ptr[pos])
|
|
{
|
|
pos = saved + 1;
|
|
/* We found a comma separator: so make the text before
|
|
* the comma a cooke string as long as it is not empty.
|
|
*/
|
|
while (saved > start
|
|
&& isspace(ptr[saved - 1]))
|
|
{
|
|
saved--;
|
|
}
|
|
if (saved > start)
|
|
{
|
|
NSString *str = [NSString alloc];
|
|
|
|
str = [str initWithBytes: ptr + start
|
|
length: saved - start
|
|
encoding: NSUTF8StringEncoding];
|
|
[cookies addObject: str];
|
|
RELEASE(str);
|
|
}
|
|
start = saved = pos;
|
|
}
|
|
}
|
|
pos = saved;
|
|
}
|
|
pos++;
|
|
}
|
|
}
|
|
if (pos > start)
|
|
{
|
|
/* There was data after the last comma; make it into a cookie string
|
|
* as long as it's not empty after removing spaces
|
|
*/
|
|
saved = pos;
|
|
while (saved > start
|
|
&& isspace(ptr[saved - 1]))
|
|
{
|
|
saved--;
|
|
}
|
|
if (saved > start)
|
|
{
|
|
NSString *str = [NSString alloc];
|
|
|
|
/* There may not be room to add a nul terminator, so we use an
|
|
* initialiser which doesn't need one.
|
|
*/
|
|
str = [str initWithBytes: ptr + start
|
|
length: saved - start
|
|
encoding: NSUTF8StringEncoding];
|
|
[cookies addObject: str];
|
|
RELEASE(str);
|
|
}
|
|
}
|
|
return cookies; // The individual cookies
|
|
}
|