Index: src/OFHTTPClient.h ================================================================== --- src/OFHTTPClient.h +++ src/OFHTTPClient.h @@ -81,11 +81,15 @@ * callback will not be called. * * @param client The OFHTTPClient which wants to follow a redirect * @param URL The URL to which it will follow a redirect * @param statusCode The status code for the redirection - * @param request The request for which the OFHTTPClient wants to redirect + * @param request The request for which the OFHTTPClient wants to redirect. + * You are allowed to change the request's headers from this + * callback and they will be used when following the redirect + * (e.g. to set the cookies for the new URL), however, keep in + * mind that this will change the request you originally passed. * @param response The response indicating the redirect * @return A boolean whether the OFHTTPClient should follow the redirect */ - (bool)client: (OFHTTPClient *)client shouldFollowRedirect: (OFURL *)URL Index: src/OFHTTPCookie.h ================================================================== --- src/OFHTTPCookie.h +++ src/OFHTTPCookie.h @@ -77,10 +77,31 @@ * An array of other attributes. */ @property (readonly, nonatomic) OFMutableArray OF_GENERIC(OFString *) *extensions; +/*! + * @brief Parses the specified response header fields for the specified URL and + * returns an array of cookies. + * + * @param headerFields The response header fields to parse + * @param URL The URL for the response header fields to parse + * @return An array of cookies + */ ++ (OFArray OF_GENERIC(OFHTTPCookie *) *)cookiesWithResponseHeaderFields: + (OFDictionary OF_GENERIC(OFString *, OFString *) *)headerFields + forURL: (OFURL *)URL; + +/*! + * @brief Returns the request header fields for the specified cookies. + * + * @param cookies The cookies to return the request header fields for + * @return The request header fields for the specified cookies + */ ++ (OFDictionary *)requestHeaderFieldsWithCookies: + (OFArray OF_GENERIC(OFHTTPCookie *) *)cookies; + /*! * @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 @@ -89,21 +110,10 @@ */ + (instancetype)cookieWithName: (OFString *)name value: (OFString *)value domain: (OFString *)domain; -/*! - * @brief Parses the specified string and returns an array of cookies. - * - * @param headers The headers to parse - * @param URL The URL for the cookies to parse - * @return An array of cookies - */ -+ (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. Index: src/OFHTTPCookie.m ================================================================== --- src/OFHTTPCookie.m +++ src/OFHTTPCookie.m @@ -59,26 +59,17 @@ @implementation OFHTTPCookie @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 - domain: domain] autorelease]; -} - -+ (OFArray OF_GENERIC(OFHTTPCookie *) *)cookiesFromHeaders: - (OFDictionary OF_GENERIC(OFString *, OFString *) *)headers ++ (OFArray OF_GENERIC(OFHTTPCookie *) *)cookiesWithResponseHeaderFields: + (OFDictionary OF_GENERIC(OFString *, OFString *) *)headerFields forURL: (OFURL *)URL { OFMutableArray OF_GENERIC(OFHTTPCookie *) *ret = [OFMutableArray array]; void *pool = objc_autoreleasePoolPush(); - OFString *string = [headers objectForKey: @"Set-Cookie"]; + OFString *string = [headerFields 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, @@ -255,10 +246,52 @@ objc_autoreleasePoolPop(pool); return ret; } + ++ (OFDictionary *)requestHeaderFieldsWithCookies: + (OFArray OF_GENERIC(OFHTTPCookie *) *)cookies +{ + OFDictionary OF_GENERIC(OFString *, OFString *) *ret; + void *pool; + OFMutableString *cookieString; + bool first = true; + + if ([cookies count] == 0) + return [OFDictionary dictionary]; + + pool = objc_autoreleasePoolPush(); + cookieString = [OFMutableString string]; + + for (OFHTTPCookie *cookie in cookies) { + if OF_UNLIKELY (first) + first = false; + else + [cookieString appendString: @"; "]; + + [cookieString appendString: [cookie name]]; + [cookieString appendString: @"="]; + [cookieString appendString: [cookie value]]; + } + + ret = [[OFDictionary alloc] initWithObject: cookieString + forKey: @"Cookie"]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; +} + ++ (instancetype)cookieWithName: (OFString *)name + value: (OFString *)value + domain: (OFString *)domain +{ + return [[[self alloc] initWithName: name + value: value + domain: domain] autorelease]; +} - init { OF_INVALID_INIT_METHOD } Index: tests/OFHTTPCookieTests.m ================================================================== --- tests/OFHTTPCookieTests.m +++ tests/OFHTTPCookieTests.m @@ -31,25 +31,26 @@ - (void)HTTPCookieTests { OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; OFURL *URL = [OFURL URLWithString: @"http://heap.zone"]; OFHTTPCookie *cookie[2]; + OFArray OF_GENERIC(OFHTTPCookie *) *cookies; cookie[0] = [OFHTTPCookie cookieWithName: @"foo" value: @"bar" domain: @"heap.zone"]; - TEST(@"+[cookiesForString:] #1", - [[OFHTTPCookie cookiesFromHeaders: [OFDictionary + TEST(@"+[cookiesWithResponseHeaderFields:forURL:] #1", + [[OFHTTPCookie cookiesWithResponseHeaderFields: [OFDictionary dictionaryWithObject: @"foo=bar" forKey: @"Set-Cookie"] forURL: URL] isEqual: [OFArray arrayWithObject: cookie[0]]]) cookie[1] = [OFHTTPCookie cookieWithName: @"qux" value: @"cookie" domain: @"heap.zone"]; - TEST(@"+[cookiesForString:] #2", - [[OFHTTPCookie cookiesFromHeaders: [OFDictionary + TEST(@"+[cookiesWithResponseHeaderFields:forURL:] #2", + [[OFHTTPCookie cookiesWithResponseHeaderFields: [OFDictionary dictionaryWithObject: @"foo=bar,qux=cookie" forKey: @"Set-Cookie"] forURL: URL] isEqual: [OFArray arrayWithObjects: cookie[0], cookie[1], nil]]) [cookie[0] setExpires: @@ -61,17 +62,22 @@ [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 cookiesFromHeaders: [OFDictionary - dictionaryWithObject: + TEST(@"+[cookiesWithResponseHeaderFields:forURL:] #3", + [(cookies = [OFHTTPCookie cookiesWithResponseHeaderFields: + [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=webkeks.org; Path=/objfw; Secure; HTTPOnly; foo; bar" - forKey: @"Set-Cookie"] forURL: URL] isEqual: + forKey: @"Set-Cookie"] forURL: URL]) isEqual: [OFArray arrayWithObjects: cookie[0], cookie[1], nil]]) + + TEST(@"+[requestHeaderFieldsWithCookies:]", + [[OFHTTPCookie requestHeaderFieldsWithCookies: cookies] isEqual: + [OFDictionary dictionaryWithObject: @"foo=bar; qux=cookie" + forKey: @"Cookie"]]) [pool drain]; } @end