Index: src/OFHTTPCookie.h ================================================================== --- src/OFHTTPCookie.h +++ src/OFHTTPCookie.h @@ -14,25 +14,28 @@ * file. */ #import "OFObject.h" #import "OFString.h" -#import "OFDate.h" -#import "OFArray.h" OF_ASSUME_NONNULL_BEGIN + +@class OFArray OF_GENERIC(ObjectType); +@class OFDate; +@class OFDictionary OF_GENERIC(KeyType, ObjectType); +@class OFMutableArray OF_GENERIC(ObjectType); +@class OFURL; /*! * @class OFHTTPCookie OFHTTPCookie.h ObjFW/OFHTTPCookie.h * * @brief A class for storing and manipulating HTTP cookies. */ @interface OFHTTPCookie: OFObject { - OFString *_name, *_value; + OFString *_name, *_value, *_domain, *_path; OFDate *_expires; - OFString *_domain, *_path; bool _secure, _HTTPOnly; OFMutableArray OF_GENERIC(OFString *) *_extensions; } /*! @@ -43,24 +46,24 @@ /*! * The value of the cookie. */ @property (nonatomic, copy) OFString *value; -/*! - * The date when the cookie expires. - */ -@property OF_NULLABLE_PROPERTY (nonatomic, copy) OFDate *expires; - /*! * The domain for the cookie. */ -@property OF_NULLABLE_PROPERTY (nonatomic, copy) OFString *domain; +@property (nonatomic, copy) OFString *domain; /*! * The path for the cookie. */ -@property OF_NULLABLE_PROPERTY (nonatomic, copy) OFString *path; +@property (nonatomic, copy) OFString *path; + +/*! + * The date when the cookie expires. + */ +@property OF_NULLABLE_PROPERTY (nonatomic, copy) OFDate *expires; /*! * Whether the cookie is only to be used with HTTPS. */ @property (nonatomic, getter=isSecure) bool secure; @@ -79,33 +82,40 @@ /*! * @brief Create a new cookie with the specified name and value. * * @param name The name of the cookie * @param value The value of the cookie + * @param domain The domain for the cookie * @return A new, autoreleased OFHTTPCookie */ + (instancetype)cookieWithName: (OFString *)name - value: (OFString *)value; + value: (OFString *)value + domain: (OFString *)domain; /*! * @brief Parses the specified string and returns an array of cookies. * - * @param string The cookie string to parse + * @param headers The headers to parse + * @param URL The URL for the cookies to parse * @return An array of cookies */ -+ (OFArray OF_GENERIC(OFHTTPCookie *) *)cookiesForString: (OFString *)string; ++ (OFArray OF_GENERIC(OFHTTPCookie *) *)cookiesFromHeaders: + (OFDictionary OF_GENERIC(OFString *, OFString *) *)headers + forURL: (OFURL *)URL; - init OF_UNAVAILABLE; /*! * @brief Initializes an already allocated new cookie with the specified name * and value. * * @param name The name of the cookie * @param value The value of the cookie + * @param domain The domain for the cookie * @return An initialized OFHTTPCookie */ - initWithName: (OFString *)name - value: (OFString *)value OF_DESIGNATED_INITIALIZER; + value: (OFString *)value + domain: (OFString *)domain OF_DESIGNATED_INITIALIZER; @end OF_ASSUME_NONNULL_END Index: src/OFHTTPCookie.m ================================================================== --- src/OFHTTPCookie.m +++ src/OFHTTPCookie.m @@ -15,10 +15,14 @@ */ #include "config.h" #import "OFHTTPCookie.h" +#import "OFArray.h" +#import "OFDate.h" +#import "OFDictionary.h" +#import "OFURL.h" #import "OFInvalidFormatException.h" static void handleAttribute(OFHTTPCookie *cookie, OFString *name, OFString *value) @@ -51,25 +55,31 @@ [[cookie extensions] addObject: name]; } } @implementation OFHTTPCookie -@synthesize name = _name, value = _value, expires = _expires, domain = _domain; -@synthesize path = _path, secure = _secure, HTTPOnly = _HTTPOnly; +@synthesize name = _name, value = _value, domain = _domain, path = _path; +@synthesize expires = _expires, secure = _secure, HTTPOnly = _HTTPOnly; @synthesize extensions = _extensions; + (instancetype)cookieWithName: (OFString *)name value: (OFString *)value + domain: (OFString *)domain { return [[[self alloc] initWithName: name - value: value] autorelease]; + value: value + domain: domain] autorelease]; } -+ (OFArray OF_GENERIC(OFHTTPCookie *) *)cookiesForString: (OFString *)string ++ (OFArray OF_GENERIC(OFHTTPCookie *) *)cookiesFromHeaders: + (OFDictionary OF_GENERIC(OFString *, OFString *) *)headers + forURL: (OFURL *)URL { OFMutableArray OF_GENERIC(OFHTTPCookie *) *ret = [OFMutableArray array]; void *pool = objc_autoreleasePoolPush(); + OFString *string = [headers objectForKey: @"Set-Cookie"]; + OFString *domain = [URL host]; const of_unichar_t *characters = [string characters]; size_t length = [string length], last = 0; enum { STATE_PRE_NAME, STATE_NAME, @@ -115,11 +125,12 @@ value = [string substringWithRange: of_range(last, i - last)]; [ret addObject: [OFHTTPCookie cookieWithName: name - value: value]]; + value: value + domain: domain]]; state = (characters[i] == ';' ? STATE_PRE_ATTR_NAME : STATE_PRE_NAME); } break; @@ -127,11 +138,12 @@ if (characters[i] == '"') { value = [string substringWithRange: of_range(last, i - last)]; [ret addObject: [OFHTTPCookie cookieWithName: name - value: value]]; + value: value + domain: domain]]; state = STATE_POST_QUOTED_VALUE; } break; case STATE_POST_QUOTED_VALUE: @@ -213,16 +225,18 @@ break; case STATE_VALUE: value = [string substringWithRange: of_range(last, length - last)]; [ret addObject: [OFHTTPCookie cookieWithName: name - value: value]]; + value: value + domain: domain]]; break; /* We end up here if the cookie is just foo= */ case STATE_EXPECT_VALUE: [ret addObject: [OFHTTPCookie cookieWithName: name - value: @""]]; + value: @"" + domain: domain]]; break; case STATE_ATTR_NAME: if (last != length) { name = [string substringWithRange: of_range(last, length - last)]; @@ -249,16 +263,19 @@ OF_INVALID_INIT_METHOD } - initWithName: (OFString *)name value: (OFString *)value + domain: (OFString *)domain { self = [super init]; @try { _name = [name copy]; _value = [value copy]; + _domain = [domain copy]; + _path = @"/"; _extensions = [[OFMutableArray alloc] init]; } @catch (id e) { [self release]; @throw e; } @@ -268,13 +285,13 @@ - (void)dealloc { [_name release]; [_value release]; - [_expires release]; [_domain release]; [_path release]; + [_expires release]; [_extensions release]; [super dealloc]; } @@ -289,15 +306,15 @@ if (![_name isEqual: other->_name]) return false; if (![_value isEqual: other->_value]) return false; - if (_expires != other->_expires && ![_expires isEqual: other->_expires]) - return false; if (_domain != other->_domain && ![_domain isEqual: other->_domain]) return false; if (_path != other->_path && ![_path isEqual: other->_path]) + return false; + if (_expires != other->_expires && ![_expires isEqual: other->_expires]) return false; if (_secure != other->_secure) return false; if (_HTTPOnly != other->_HTTPOnly) return false; @@ -313,13 +330,13 @@ uint32_t hash; OF_HASH_INIT(hash); OF_HASH_ADD_HASH(hash, [_name hash]); OF_HASH_ADD_HASH(hash, [_value hash]); - OF_HASH_ADD_HASH(hash, [_expires hash]); OF_HASH_ADD_HASH(hash, [_domain hash]); OF_HASH_ADD_HASH(hash, [_path hash]); + OF_HASH_ADD_HASH(hash, [_expires hash]); OF_HASH_ADD(hash, _secure); OF_HASH_ADD(hash, _HTTPOnly); OF_HASH_ADD_HASH(hash, [_extensions hash]); OF_HASH_FINALIZE(hash); @@ -327,16 +344,16 @@ } - copy { OFHTTPCookie *copy = [[OFHTTPCookie alloc] initWithName: _name - value: _value]; + value: _value + domain: _domain]; @try { - copy->_expires = [_expires copy]; - copy->_domain = [_domain copy]; copy->_path = [_path copy]; + copy->_expires = [_expires copy]; copy->_secure = _secure; copy->_HTTPOnly = _HTTPOnly; [copy->_extensions addObjectsFromArray: _extensions]; } @catch (id e) { [copy release]; @@ -350,21 +367,17 @@ { OFMutableString *ret = [OFMutableString stringWithFormat: @"%@=%@", _name, _value]; void *pool = objc_autoreleasePoolPush(); + [ret appendFormat: @"; Domain=%@; Path=%@", _domain, _path]; + if (_expires != nil) [ret appendString: [_expires dateStringWithFormat: @"; Expires=%a, %d %b %Y " @"%H:%M:%S +0000"]]; - if (_domain != nil) - [ret appendFormat: @"; Domain=%@", _domain]; - - if (_path != nil) - [ret appendFormat: @"; Path=%@", _path]; - if (_secure) [ret appendString: @"; Secure"]; if (_HTTPOnly) [ret appendString: @"; HTTPOnly"]; Index: src/OFHTTPCookieManager.m ================================================================== --- src/OFHTTPCookieManager.m +++ src/OFHTTPCookieManager.m @@ -16,10 +16,11 @@ #include "config.h" #import "OFHTTPCookieManager.h" #import "OFArray.h" +#import "OFDate.h" #import "OFHTTPCookie.h" #import "OFURL.h" @implementation OFHTTPCookieManager + (instancetype)manager @@ -58,14 +59,11 @@ { void *pool = objc_autoreleasePoolPush(); OFString *cookieDomain, *URLHost; size_t i; - if ([cookie domain] == nil) - [cookie setDomain: [URL host]]; - - if ([cookie path] == nil || ![[cookie path] hasPrefix: @"/"]) + if (![[cookie path] hasPrefix: @"/"]) [cookie setPath: @"/"]; if ([cookie isSecure] && ![[URL scheme] isEqual: @"https"]) { objc_autoreleasePoolPop(pool); return; Index: tests/OFHTTPCookieManagerTests.m ================================================================== --- tests/OFHTTPCookieManagerTests.m +++ tests/OFHTTPCookieManagerTests.m @@ -15,10 +15,12 @@ */ #include "config.h" #import "OFHTTPCookieManager.h" +#import "OFArray.h" +#import "OFDate.h" #import "OFHTTPCookie.h" #import "OFURL.h" #import "OFAutoreleasePool.h" #import "TestsAppDelegate.h" @@ -37,31 +39,33 @@ URL[1] = [OFURL URLWithString: @"https://heap.zone/foo/bar"]; URL[2] = [OFURL URLWithString: @"https://test.heap.zone/foo/bar"]; URL[3] = [OFURL URLWithString: @"http://webkeks.org/foo/bar"]; cookie[0] = [OFHTTPCookie cookieWithName: @"test" - value: @"1"]; + value: @"1" + domain: @"heap.zone"]; TEST(@"-[addCookie:forURL:] #1", R([manager addCookie: cookie[0] forURL: URL[0]])) TEST(@"-[cookiesForURL:] #1", [[manager cookiesForURL: URL[0]] isEqual: [OFArray arrayWithObject: cookie[0]]]) cookie[1] = [OFHTTPCookie cookieWithName: @"test" - value: @"2"]; - [cookie[1] setDomain: @"webkeks.org"]; + value: @"2" + domain: @"webkeks.org"]; TEST(@"-[addCookie:forURL:] #2", R([manager addCookie: cookie[1] forURL: URL[0]])) TEST(@"-[cookiesForURL:] #2", [[manager cookiesForURL: URL[0]] isEqual: [OFArray arrayWithObject: cookie[0]]] && [[manager cookiesForURL: URL[3]] isEqual: [OFArray array]]) cookie[2] = [OFHTTPCookie cookieWithName: @"test" - value: @"3"]; + value: @"3" + domain: @"heap.zone"]; [cookie[2] setSecure: true]; TEST(@"-[addCookie:forURL:] #3", R([manager addCookie: cookie[2] forURL: URL[1]])) TEST(@"-[cookiesForURL:] #3", @@ -69,11 +73,12 @@ [OFArray arrayWithObject: cookie[2]]] && [[manager cookiesForURL: URL[0]] isEqual: [OFArray array]]) [cookie[2] setExpires: [OFDate dateWithTimeIntervalSinceNow: -1]]; cookie[3] = [OFHTTPCookie cookieWithName: @"test" - value: @"4"]; + value: @"4" + domain: @"heap.zone"]; [cookie[3] setDomain: @".heap.zone"]; TEST(@"-[addCookie:forURL:] #4", R([manager addCookie: cookie[3] forURL: URL[1]])) TEST(@"-[cookiesForURL:] #4", @@ -81,12 +86,12 @@ [OFArray arrayWithObject: cookie[3]]] && [[manager cookiesForURL: URL[2]] isEqual: [OFArray arrayWithObject: cookie[3]]]) cookie[4] = [OFHTTPCookie cookieWithName: @"bar" - value: @"5"]; - [cookie[4] setDomain: @"test.heap.zone"]; + value: @"5" + domain: @"test.heap.zone"]; TEST(@"-[addCookie:forURL:] #5", R([manager addCookie: cookie[4] forURL: URL[0]])) TEST(@"-[cookiesForURL:] #5", [[manager cookiesForURL: URL[0]] isEqual: Index: tests/OFHTTPCookieTests.m ================================================================== --- tests/OFHTTPCookieTests.m +++ tests/OFHTTPCookieTests.m @@ -15,10 +15,14 @@ */ #include "config.h" #import "OFHTTPCookie.h" +#import "OFArray.h" +#import "OFDate.h" +#import "OFDictionary.h" +#import "OFURL.h" #import "OFAutoreleasePool.h" #import "TestsAppDelegate.h" static OFString *module = @"OFHTTPCookie"; @@ -25,40 +29,49 @@ @implementation TestsAppDelegate (OFHTTPCookieTests) - (void)HTTPCookieTests { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFURL *URL = [OFURL URLWithString: @"http://heap.zone"]; OFHTTPCookie *cookie[2]; cookie[0] = [OFHTTPCookie cookieWithName: @"foo" - value: @"bar"]; + value: @"bar" + domain: @"heap.zone"]; TEST(@"+[cookiesForString:] #1", - [[OFHTTPCookie cookiesForString: @"foo=bar"] isEqual: - [OFArray arrayWithObject: cookie[0]]]) + [[OFHTTPCookie cookiesFromHeaders: [OFDictionary + dictionaryWithObject: @"foo=bar" + forKey: @"Set-Cookie"] forURL: URL] + isEqual: [OFArray arrayWithObject: cookie[0]]]) cookie[1] = [OFHTTPCookie cookieWithName: @"qux" - value: @"cookie"]; + value: @"cookie" + domain: @"heap.zone"]; TEST(@"+[cookiesForString:] #2", - [[OFHTTPCookie cookiesForString: @"foo=bar,qux=cookie"] isEqual: - [OFArray arrayWithObjects: cookie[0], cookie[1], nil]]) + [[OFHTTPCookie cookiesFromHeaders: [OFDictionary + dictionaryWithObject: @"foo=bar,qux=cookie" + forKey: @"Set-Cookie"] forURL: URL] + isEqual: [OFArray arrayWithObjects: cookie[0], cookie[1], nil]]) [cookie[0] setExpires: [OFDate dateWithTimeIntervalSince1970: 1234567890]]; [cookie[1] setExpires: [OFDate dateWithTimeIntervalSince1970: 1234567890]]; [cookie[0] setPath: @"/x"]; - [cookie[1] setDomain: @"heap.zone"]; + [cookie[1] setDomain: @"webkeks.org"]; [cookie[1] setPath: @"/objfw"]; [cookie[1] setSecure: true]; [cookie[1] setHTTPOnly: true]; [[cookie[1] extensions] addObject: @"foo"]; [[cookie[1] extensions] addObject: @"bar"]; TEST(@"+[cookiesForString:] #3", - [[OFHTTPCookie cookiesForString: + [[OFHTTPCookie cookiesFromHeaders: [OFDictionary + dictionaryWithObject: @"foo=bar; Expires=Fri, 13 Feb 2009 23:31:30 GMT; Path=/x," @"qux=cookie; Expires=Fri, 13 Feb 2009 23:31:30 GMT; " - @"Domain=heap.zone; Path=/objfw; Secure; HTTPOnly; foo; bar"] - isEqual: [OFArray arrayWithObjects: cookie[0], cookie[1], nil]]) + @"Domain=webkeks.org; Path=/objfw; Secure; HTTPOnly; foo; bar" + forKey: @"Set-Cookie"] forURL: URL] isEqual: + [OFArray arrayWithObjects: cookie[0], cookie[1], nil]]) [pool drain]; } @end