Finish implementation

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@29893 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
fedor 2010-03-10 03:50:21 +00:00
parent a7ea17e6b8
commit 804c45c26c
4 changed files with 920 additions and 44 deletions

View file

@ -1,3 +1,8 @@
2010-03-09 Adam Fedor <fedor@gnu.org>
* Source/NSHTTPCookie.m: Finish off implementation
* Source/HSHTTPCookieStorage.m: Finish off implementation.
2010-03-10 Riccardo Mottola <rmottola@users.sf.net>
* /Source/ObjectiveC2/sync.m : use proper _XOPEN_SOURCE

View file

@ -43,8 +43,8 @@ extern "C" {
extern NSString * const NSHTTPCookieComment; /** Obtain text of the comment */
extern NSString * const NSHTTPCookieCommentURL; /** Obtain the comment URL */
extern NSString * const NSHTTPCookieDiscard; /** Obtain the sessions discard setting */
extern NSString * const NSHTTPCookieDomain; /** Obrain cookie domain */
extern NSString * const NSHTTPCookieExpires; /** Obrain cookie expiry date */
extern NSString * const NSHTTPCookieDomain; /** Obtain cookie domain */
extern NSString * const NSHTTPCookieExpires; /** Obtain cookie expiry date */
extern NSString * const NSHTTPCookieMaximumAge; /** Obtain maximum age (expiry) */
extern NSString * const NSHTTPCookieName; /** Obtain name of cookie */
extern NSString * const NSHTTPCookieOriginURL; /** Obtain cookie origin URL */
@ -108,7 +108,7 @@ extern NSString * const NSHTTPCookieVersion; /** Obtain cookie version */
/**
* Returns the domain to which the cookie should be sent.<br />
* If there is a leading dot then subdomains should also receive the
* coockie as specified in RFC 2965.
* cookie as specified in RFC 2965.
*/
- (NSString *) domain;

View file

@ -22,25 +22,44 @@
Boston, MA 02111 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 GSRangeOfCookie).
*/
#import "common.h"
#define EXPOSE_NSHTTPCookie_IVARS 1
#import "GSURLPrivate.h"
#import "Foundation/NSSet.h"
#include "Foundation/NSValue.h"
#include "Foundation/NSString.h"
#include "Foundation/NSCalendarDate.h"
#include "GNUstepBase/Unicode.h"
#import "GNUstepBase/NSObject+GNUstepBase.h"
NSString * const NSHTTPCookieComment = @"NSHTTPCookieComment";
NSString * const NSHTTPCookieCommentURL = @"NSHTTPCookieCommentURL";
NSString * const NSHTTPCookieDiscard = @"NSHTTPCookieDiscard";
NSString * const NSHTTPCookieDomain = @"NSHTTPCookieDomain";
NSString * const NSHTTPCookieExpires = @"NSHTTPCookieExpires";
NSString * const NSHTTPCookieMaximumAge = @"NSHTTPCookieMaximumAge";
NSString * const NSHTTPCookieName = @"NSHTTPCookieName";
NSString * const NSHTTPCookieOriginURL = @"NSHTTPCookieOriginURL";
NSString * const NSHTTPCookiePath = @"NSHTTPCookiePath";
NSString * const NSHTTPCookiePort = @"NSHTTPCookiePort";
NSString * const NSHTTPCookieSecure = @"NSHTTPCookieSecure";
NSString * const NSHTTPCookieValue = @"NSHTTPCookieValue";
NSString * const NSHTTPCookieVersion = @"NSHTTPCookieVersion";
NSString * const NSHTTPCookieComment = @"Comment";
NSString * const NSHTTPCookieCommentURL = @"CommentURL";
NSString * const NSHTTPCookieDiscard = @"Discard";
NSString * const NSHTTPCookieDomain = @"Domain";
NSString * const NSHTTPCookieExpires = @"Expires";
NSString * const NSHTTPCookieMaximumAge = @"MaximumAge";
NSString * const NSHTTPCookieName = @"Name";
NSString * const NSHTTPCookieOriginURL = @"OriginURL";
NSString * const NSHTTPCookiePath = @"Path";
NSString * const NSHTTPCookiePort = @"Port";
NSString * const NSHTTPCookieSecure = @"Secure";
NSString * const NSHTTPCookieValue = @"Value";
NSString * const NSHTTPCookieVersion = @"Version";
// Internal data storage
typedef struct {
@ -50,6 +69,53 @@ typedef struct {
#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);
static NSRange GSRangeOfCookie(NSString *string);
@implementation NSHTTPCookie
+ (id) allocWithZone: (NSZone*)z
@ -71,21 +137,110 @@ typedef struct {
return AUTORELEASE(o);
}
+ (NSMutableArray *) _parseField: (NSString *)field
forHeader: (NSString *)header
andURL: (NSURL *)url
{
int ckcount, pos;
int version;
NSString *defaultPath, *defaultDomain;
NSMutableArray *a = [NSMutableArray array];
ckcount = 0;
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];
/* 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. */
pos = 0;
while (1)
{
NSHTTPCookie *cookie;
NSMutableDictionary *dict;
NSString *onecookie;
NSRange range = GSRangeOfCookie(field);
if (range.location == NSNotFound)
break;
onecookie = [field substringFromRange: range];
NS_DURING
dict = GSPropertyListFromCookieFormat(onecookie);
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];
[a addObject: cookie];
}
if ([field length] <= NSMaxRange(range))
break;
field = [field substringFromIndex: NSMaxRange(range)+1];
}
return a;
}
+ (NSArray *) cookiesWithResponseHeaderFields: (NSDictionary *)headerFields
forURL: (NSURL *)URL
{
NSMutableArray *a = nil;
[self notImplemented: _cmd]; // FIXME
NSEnumerator *henum = [headerFields keyEnumerator];
NSMutableArray *a = [NSMutableArray array];
NSString *header;
while ((header = [henum nextObject]))
{
NSMutableArray *suba
= [self _parseField: [headerFields objectForKey: header]
forHeader: header andURL: URL];
if (suba)
[a addObjectsFromArray: suba];
}
return a;
}
+ (NSDictionary *) requestHeaderFieldsWithCookies: (NSArray *)cookies
{
NSMutableDictionary *d = nil;
int version;
NSString *field;
NSHTTPCookie *ck;
NSEnumerator *ckenum = [cookies objectEnumerator];
[self notImplemented: _cmd]; // FIXME
return d;
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
@ -120,11 +275,27 @@ typedef struct {
- (id) initWithProperties: (NSDictionary *)properties
{
if ((self = [super init]) != nil)
{
this->_properties = [properties copy];
// FIXME ... parse and validate
}
NSMutableDictionary *rawProps;
if ((self = [super init]) == nil)
return nil;
/* Parse a few values. Based on Mac OS X tests.
FIXME: Probably needs more checking. */
if ([properties objectForKey: NSHTTPCookiePath] == nil)
return nil;
if ([properties objectForKey: NSHTTPCookieDomain] == nil)
return nil;
rawProps = [[properties mutableCopy] autorelease];
if ([rawProps objectForKey: @"Created"] == nil)
[rawProps setObject: [NSDate date] forKey: @"Created"];
if ([rawProps objectForKey: NSHTTPCookieExpires] == nil
|| [[rawProps objectForKey: NSHTTPCookieExpires]
isKindOfClass: [NSDate class]] == NO)
[rawProps setObject: [NSNumber numberWithBool: YES]
forKey: NSHTTPCookieDiscard];
this->_properties = [rawProps copy];
return self;
}
@ -169,5 +340,511 @@ typedef struct {
return [[this->_properties objectForKey: NSHTTPCookieVersion] integerValue];
}
- (NSString *) description
{
return [NSString stringWithFormat: @"<NSHTTPCookie %p: %@=%@>", self,
[self name], [self value]];
}
@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;
}
static inline id 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 */
static inline id parseUnquotedString(pldata *pld, char endChar)
{
unsigned start = pld->pos;
unsigned i;
unsigned length;
id 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)
{
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;
}
if ([[key lowercaseString] isEqual: @"comment"])
[dict setObject: value forKey: NSHTTPCookieComment];
else if ([[key lowercaseString] isEqual: @"commenturl"])
[dict setObject: value forKey: NSHTTPCookieCommentURL];
else if ([[key lowercaseString] isEqual: @"discard"])
[dict setObject: [NSNumber numberWithBool: YES]
forKey: NSHTTPCookieDiscard];
else if ([[key lowercaseString] isEqual: @"domain"])
[dict setObject: value forKey: NSHTTPCookieDomain];
else if ([[key lowercaseString] isEqual: @"expires"])
{
NSDate *expireDate;
expireDate = [NSCalendarDate dateWithString: value
calendarFormat: @"%a, %d-%b-%Y %I:%M:%S %Z"];
if (expireDate)
[dict setObject: expireDate forKey: NSHTTPCookieExpires];
}
else if ([[key lowercaseString] isEqual: @"max-age"])
[dict setObject: value forKey: NSHTTPCookieMaximumAge];
else if ([[key lowercaseString] isEqual: @"originurl"])
[dict setObject: value forKey: NSHTTPCookieOriginURL];
else if ([[key lowercaseString] isEqual: @"path"])
[dict setObject: value forKey: NSHTTPCookiePath];
else if ([[key lowercaseString] isEqual: @"port"])
[dict setObject: value forKey: NSHTTPCookiePort];
else if ([[key lowercaseString] isEqual: @"secure"])
[dict setObject: [NSNumber numberWithBool: YES]
forKey: NSHTTPCookieSecure];
else if ([[key lowercaseString] isEqual: @"version"])
[dict setObject: value forKey: NSHTTPCookieVersion];
return YES;
}
static id
GSPropertyListFromCookieFormat(NSString *string)
{
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
{
key = parseUnquotedString(pld, '=');
}
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;
}
moreCharacters = skipSpace(pld);
if (_setCookieKey(dict, key, val) == NO)
{
pld->err = @"invalid cookie pair";
DESTROY(dict);
}
RELEASE(key);
RELEASE(val);
if (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);
}
/* 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 NSRange
GSRangeOfCookie(NSString *string)
{
pldata _pld;
pldata *pld = &_pld;
NSData *d;
NSRange range;
/*
* An empty string is a nil property list.
*/
range = NSMakeRange(NSNotFound, NSNotFound);
if ([string length] == 0)
{
return range;
}
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
while (skipSpace(pld) == YES)
{
if (pld->ptr[pld->pos] == ',')
{
/* Look ahead for something that will tell us if this is a
separate cookie or not */
unsigned saved_pos = pld->pos;
while (pld->ptr[pld->pos] != '=' && pld->ptr[pld->pos] != ';'
&& pld->ptr[pld->pos] != ',' && pld->pos < pld->end )
pld->pos++;
if (pld->ptr[pld->pos] == '=')
{
/* Separate comment */
range = NSMakeRange(0, saved_pos-1);
break;
}
pld->pos = saved_pos;
}
pld->pos++;
}
if (range.location == NSNotFound)
range = NSMakeRange(0, [string length]);
return range;
}

View file

@ -26,6 +26,11 @@
#define EXPOSE_NSHTTPCookieStorage_IVARS 1
#import "GSURLPrivate.h"
#import "Foundation/NSSet.h"
#include "Foundation/NSArray.h"
#include "Foundation/NSFileManager.h"
#include "Foundation/NSPathUtilities.h"
#include "Foundation/NSString.h"
#include "Foundation/NSDistributedNotificationCenter.h"
#import "GNUstepBase/NSObject+GNUstepBase.h"
NSString * const NSHTTPCookieManagerAcceptPolicyChangedNotification
@ -34,19 +39,21 @@ NSString * const NSHTTPCookieManagerAcceptPolicyChangedNotification
NSString * const NSHTTPCookieManagerCookiesChangedNotification
= @"NSHTTPCookieManagerCookiesChangedNotification";
NSString *objectObserver = @"org.GNUstep.NSHTTPCookieStorage";
// Internal data storage
typedef struct {
NSHTTPCookieAcceptPolicy _policy;
NSMutableSet *_cookies;
NSMutableArray *_cookies;
} Internal;
#define this ((Internal*)(self->_NSHTTPCookieStorageInternal))
#define inst ((Internal*)(o->_NSHTTPCookieStorageInternal))
@interface NSHTTPCookieStorage (Private)
- (void) _updateFromCookieStore;
@end
/* FIXME
* handle persistent storage and use policies.
*/
@implementation NSHTTPCookieStorage
static NSHTTPCookieStorage *storage = nil;
@ -69,8 +76,7 @@ static NSHTTPCookieStorage *storage = nil;
NSAllocateObject(self, 0, NSDefaultMallocZone());
o->_NSHTTPCookieStorageInternal = (Internal*)
NSZoneCalloc(NSDefaultMallocZone(), 1, sizeof(Internal));
inst->_policy = NSHTTPCookieAcceptPolicyAlways;
inst->_cookies = [NSMutableSet new];
[o init];
storage = o;
}
[gnustep_global_lock unlock];
@ -78,16 +84,141 @@ static NSHTTPCookieStorage *storage = nil;
return storage;
}
- init
{
this->_policy = NSHTTPCookieAcceptPolicyAlways;
this->_cookies = [NSMutableArray new];
[[NSDistributedNotificationCenter defaultCenter]
addObserver: self
selector: @selector(cookiesChangedNotification:)
name: NSHTTPCookieManagerCookiesChangedNotification
object: objectObserver];
[[NSDistributedNotificationCenter defaultCenter]
addObserver: self
selector: @selector(acceptPolicyChangeNotification:)
name: NSHTTPCookieManagerAcceptPolicyChangedNotification
object: objectObserver];
[self _updateFromCookieStore];
return self;
}
- (void) dealloc
{
if (this != 0)
{
[[NSDistributedNotificationCenter defaultCenter] removeObserver: self];
RELEASE(this->_cookies);
NSZoneFree([self zone], this);
}
[super dealloc];
}
- (NSString *) _cookieStorePath
{
BOOL isDir;
NSString *path;
NSArray *dirs;
dirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
NSUserDomainMask, YES);
path = [[dirs objectAtIndex: 0] stringByAppendingPathComponent: @"Cookies"];
if ([[NSFileManager defaultManager]
fileExistsAtPath: path isDirectory: &isDir] == NO || isDir == NO)
{
BOOL ok;
ok = [[NSFileManager defaultManager] createDirectoryAtPath: path
withIntermediateDirectories: YES attributes: nil error: NULL];
if (ok == NO)
return nil;
}
path = [path stringByAppendingPathComponent: @"Cookies.plist"];
return path;
}
/* Remove all cookies that have expired */
/* FIXME: When will we know that the user session expired? */
- (BOOL) _expireCookies: (BOOL)endUserSession
{
BOOL changed = NO;
NSDate *now = [NSDate date];
unsigned count = [this->_cookies count];
/* FIXME: Handle Max-age */
while (count-- > 0)
{
NSHTTPCookie *ck = [this->_cookies objectAtIndex: count];
NSDate *expDate = [ck expiresDate];
if ((endUserSession && expDate == nil) ||
(expDate != nil && [expDate compare: now] != NSOrderedDescending))
{
[this->_cookies removeObject: ck];
changed = YES;
}
}
return changed;
}
- (void) _updateFromCookieStore
{
int i;
NSArray *properties;
NSString *path = [self _cookieStorePath];
if (path == nil)
{
return;
}
properties = nil;
NS_DURING
properties = [[NSString stringWithContentsOfFile: path] propertyList];
NS_HANDLER
NSLog(@"NSHTTPCookieStorage: Error reading cookies plist");
NS_ENDHANDLER
if (properties == nil)
return;
for (i = 0; i < [properties count]; i++)
[this->_cookies addObject:
[NSHTTPCookie cookieWithProperties: [properties objectAtIndex: i]]];
}
- (void) _updateToCookieStore
{
int i, count;
NSMutableArray *properties;
NSString *path = [self _cookieStorePath];
if (path == nil)
{
return;
}
count = [this->_cookies count];
properties = [NSMutableArray arrayWithCapacity: count];
for (i = 0; i < count; i++)
[properties addObject: [[this->_cookies objectAtIndex: i] properties]];
[properties writeToFile: path atomically: YES];
}
- (void) _doExpireUpdateAndNotify
{
[self _expireCookies: NO];
[self _updateToCookieStore];
[[NSDistributedNotificationCenter defaultCenter]
postNotificationName: NSHTTPCookieManagerCookiesChangedNotification
object: objectObserver];
}
- (void) cookiesChangedNotification: (NSNotification *)note
{
if ([note object] == self)
return;
[self _updateFromCookieStore];
}
- (void) acceptPolicyChangeNotification: (NSNotification *)note
{
if ([note object] == self)
return;
/* FIXME: Do we need a common place to store the policy? */
}
- (NSHTTPCookieAcceptPolicy) cookieAcceptPolicy
{
return this->_policy;
@ -95,45 +226,108 @@ static NSHTTPCookieStorage *storage = nil;
- (NSArray *) cookies
{
return [this->_cookies allObjects];
return [[this->_cookies copy] autorelease];
}
- (NSArray *) cookiesForURL: (NSURL *)URL
{
[self notImplemented: _cmd]; // FIXME
return nil;
NSMutableArray *a = [NSMutableArray array];
NSEnumerator *ckenum = [this->_cookies objectEnumerator];
NSHTTPCookie *cookie;
NSString *receive_domain = [URL host];
while ((cookie = [ckenum nextObject]))
{
if ([receive_domain hasSuffix: [cookie domain]])
[a addObject: cookie];
}
return a;
}
- (void) deleteCookie: (NSHTTPCookie *)cookie
{
[this->_cookies removeObject: cookie];
if ([this->_cookies indexOfObject: cookie] != NSNotFound)
{
[this->_cookies removeObject: cookie];
[self _doExpireUpdateAndNotify];
}
else
NSLog(@"NSHTTPCookieStorage: trying to delete a cookie that is not in the storage");
}
- (void) _setCookieNoNotify: (NSHTTPCookie *)cookie
{
NSEnumerator *ckenum = [this->_cookies objectEnumerator];
NSHTTPCookie *ck, *remove_ck;
NSString *name = [cookie name];
NSString *path = [cookie path];
NSString *domain = [cookie domain];
NSAssert([cookie isKindOfClass: [NSHTTPCookie class]] == YES,
NSInvalidArgumentException);
remove_ck = nil;
while ((ck = [ckenum nextObject]))
{
if ([name isEqual: [ck name]] && [path isEqual: [ck path]])
{
/* The Apple documentation says the domain should match and
RFC 2965 says they should match, though the original Netscape docs
doesn't mention that the domain should match, so here, if the
version is explicitely set to 0, we don't require it */
id ckv = [[ck properties] objectForKey: NSHTTPCookieVersion];
if ((ckv && [ckv intValue] == 0) || [domain isEqual: [ck domain]])
{
remove_ck = ck;
break;
}
}
}
if (remove_ck)
[this->_cookies removeObject: remove_ck];
[this->_cookies addObject: cookie];
}
- (void) setCookie: (NSHTTPCookie *)cookie
{
NSAssert([cookie isKindOfClass: [NSHTTPCookie class]] == YES,
NSInvalidArgumentException);
[this->_cookies addObject: cookie];
if (this->_policy == NSHTTPCookieAcceptPolicyNever)
return;
[self _setCookieNoNotify: cookie];
[self _doExpireUpdateAndNotify];
}
- (void) setCookieAcceptPolicy: (NSHTTPCookieAcceptPolicy)cookieAcceptPolicy
{
this->_policy = cookieAcceptPolicy;
[[NSDistributedNotificationCenter defaultCenter]
postNotificationName: NSHTTPCookieManagerAcceptPolicyChangedNotification
object: objectObserver];
}
- (void) setCookies: (NSArray *)cookies
forURL: (NSURL *)URL
mainDocumentURL: (NSURL *)mainDocumentURL
{
unsigned count = [cookies count];
BOOL changed = NO;
unsigned count = [cookies count];
if (count == 0 || this->_policy == NSHTTPCookieAcceptPolicyNever)
return;
while (count-- > 0)
{
NSHTTPCookie *c = [cookies objectAtIndex: count];
NSHTTPCookie *ck = [cookies objectAtIndex: count];
// FIXME check domain matches
[this->_cookies addObject: c];
if (this->_policy == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain
&& [[URL host] hasSuffix: [mainDocumentURL host]] == NO)
continue;
[self _setCookieNoNotify: ck];
changed = YES;
}
if (changed)
[self _doExpireUpdateAndNotify];
}
@end