mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-23 00:41:02 +00:00
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:
parent
a7ea17e6b8
commit
804c45c26c
4 changed files with 920 additions and 44 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue