Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to stub multiple response cookies? #43

Closed
evanspa opened this issue Nov 14, 2013 · 6 comments
Closed

How to stub multiple response cookies? #43

evanspa opened this issue Nov 14, 2013 · 6 comments

Comments

@evanspa
Copy link

evanspa commented Nov 14, 2013

First, thank you for this excellent library. I have a question. I don't see how to stub multiple response cookies. The OHHTTPStubsResponse class contains a dictionary for the stubbed response headers, and presumably to stub a single cookie, one would add an entry to this dictionary with the key "Set-Cookie". The problem is, an HTTP response containing several cookies will look like this on the wire:

Set-Cookie: abc=123; domain=.example.com; path=/
Set-Cookie: def=245; ...
Set-Cookie: ghi=356; ...

It's the same "Set-Cookie" header repeated for each cookie. Because NSDictionary does not allow duplicate keys, I don't see how to stub this sort of response.

Thank you,

-Paul

@AliSoftware
Copy link
Owner

Hi @evanspa

First, this question is not related to OHHTTPStubs but to the URL Loading System in general, and the HTTP standard— this question can also be applied to regular NSURLRequests for which you want to send the same header multiple times.

And contrary to what you state in your question, when you want to send a header with the same name multiple times, it won't look like you say on the wire. Instead, the RFC 2616 states that the various values of the header should be appended using a comma:

Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. The order in which header fields with the same field-name are received is therefore significant to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of these field values when a message is forwarded.

And that's exactly what the iOS URL Loading System does. For example when you build an NSMutableURLRequest and use -addValue:forHTTPHeaderField: in practice it appends the value if the field already has one, and when you request allHTTPHeaderFields (which returns an NSDictionary) it reflects this behavior:

NSMutableURLRequest* req = [NSMutableURLRequest new];
static NSString* const kHeader = @"Set-Cookie";
[req addValue:@"abc=123; domain=example.com; path=/;" forHTTPHeaderField:kHeader];
[req addValue:@"def=245;" forHTTPHeaderField:kHeader];
[req addValue:@"ghi=356;" forHTTPHeaderField:kHeader];
NSDictionary* headersRFC2616 = [req allHTTPHeaderFields];
NSLog(@"headers: %@", headersRFC2616);
// headers: { "Set-Cookie" = "abc=123; domain=example.com; path=/;,def=245;,ghi=356;"; }

You can even check that the generated headers are correctly interpreted as cookies using Apple's NSHTTPCookie class:

NSURL* cookieOriginURL = [NSURL URLWithString:@"http://example.com/"];
for (NSHTTPCookie* cookie in [NSHTTPCookie cookiesWithResponseHeaderFields:headersRFC2616 forURL:cookieOriginURL]) {
    NSLog(@"= Cookie found in header: %@",  cookie);
}
/*
 = Cookie found in header: <NSHTTPCookie version:0 name:"abc" value:"123" expiresDate:(null) created:2013-11-14 20:52:16 +0000 (4.06155e+08) sessionOnly:TRUE domain:".example.com" path:"/" isSecure:FALSE>
 = Cookie found in header: <NSHTTPCookie version:0 name:"def" value:"245" expiresDate:(null) created:2013-11-14 20:52:16 +0000 (4.06155e+08) sessionOnly:TRUE domain:"example.com" path:"/" isSecure:FALSE>
 = Cookie found in header: <NSHTTPCookie version:0 name:"ghi" value:"356" expiresDate:(null) created:2013-11-14 20:52:16 +0000 (4.06155e+08) sessionOnly:TRUE domain:"example.com" path:"/" isSecure:FALSE>
*/

So that all said, that's exactly the same for when you want to stub your requests with OHHTTPStubs, no exception here. Simply concat your cookie values in the "Set-Cookie" header, separating them with a comma, as expected by the RFC 2616.

You can of course do this either by directly build your NSDictionary statically, appending the strings directly in your code… or use an NSMutableURLRequest* req just to call addValue:forHTTPHeaderField: on it, and at the end build your OHHTTPStubsResponse using [req allHTTPHeaderFields] as the headers (you may then discard your NSURLRequest that you used for the sole purpose of building the headers more easily)


PS: Don't forget to finish your cookie headers with a semicolon ; otherwise the next cookie may be considered as a continuation of the path of the previous one! (leading with one cookie having the path "example.com,def=245" instead of separating it in the path "example.com" then a new cookie def=245)

@evanspa
Copy link
Author

evanspa commented Nov 14, 2013

Thank you AliSoftware for the detailed response! I will do that (use the comma-delimited syntax).

I would say though that a separate RFC speaks to cookies (RFC6265), and does indeed specify the wire format of having multiple "Set-Cookie" headers (also on Wikipedia). I wonder if it's because the "expires" attribute value can contain a comma, making comma-delimeted cookies too cumbersome? But I digress... Thank you again for the detail answer.

@AliSoftware
Copy link
Owner

Yes, I guess the "repeated-header syntax" may exist on the wire with some HTTP servers ; depending on the library used by those other softwares, maybe some repeat the Set-Cookie header instead of using the "comma-separated syntax". Actually, reading RFC2616, both syntax are allowed, as long as any HTTP header can be written in both syntax.

But anyway, that's not how iOS does it, as in my experiance the iOS' and OSX's URL Loading System always se the comma-separated syntax (probably because internally it uses NSDictionaries that can't have a header/key more than once so the "repeated-header syntax" would be more complex to handle otherwise…)

@evanspa
Copy link
Author

evanspa commented Nov 14, 2013

I was also thrown because of my Java background - the HttpServletResponse interface treats cookies as 1st-class objects, with an API for explicitly being able to add multiple of them. I guess I was expecting/hoping for iOS/OSX to do something similar. Live and learn!

@AliSoftware
Copy link
Owner

Well, NSHTTPCookie and NSHTTPCookieStorage quite use the same concepts as the one you point out in Java:
see [NSHTTPCookieStorage setCookies:forURL:mainDocumentURL:] method that typically can take multiple NSHTTPCookie objects as its first parameter to add them all to the shared cookie storage — or simply setCookie: that can add only one at a time — 😉

And if you need to add cookies not in the NSURLCookieStorage but directly on a NSURLRequest, you can use NSHTTPCookie objects and convert them back and forth to request headers using +[NSHTTPCookie requestHeaderFieldsWithCookies:] and +[NSHTTPCookie cookiesWithResponseHeaderFields:forURL:] 👍


The thing here is that whatever the method you use, I guess Apple chose to internally store headers in a NSDictionary, or at least they decided in the iOS&OSX URL Loading System that the header emitted uses the comma-separated syntax, so even if you add multiple cookie objects at once like in Java, the header generation will concatenate them at the end.

I think Cocoa's URL Loading System still understand/correctly interpret incoming HTTP headers that are repeated on separate lines too, so that servers generating multiple lines for each Set-Cookie header still are interpreted correctly. That's just that when it comes to issuing an HTTP request payload, it generates the comma-separated variant as output.

According to RFC2616, both are valid, as long as the headers can be written in both formats ("it MUST be possible for headers that can be repeated to also be concatenated using comma to separate them") with equivalent meanings.


Regarding your remark with RFC6265 and the possible ambiguity with expires, it completes my "PS" in my above comment: to make sure there is no ambiguity, you should terminate each attribute with a semicolon, even the last attribute of your cookie. So in abc=123; Expires=Wed, 09 Jun 2021 10:18:14 GMT;,def=245 for example, as the date is considered to be "everything between Expires= and the next semicolon", there is no ambiguity as whereas the comma after Wed is the end of the first cookie (first "Set-Cookie" header) or part of the date, because it will only consider the date attribute to be fully parsed when it hits the next semicolon (or the end of the string).

If you forget the last semicolon, as in abc=123; domain=.example.com; path=/,def=245 this will be considered to be a unique cookie whose path is /,def=245, whereas adding the semicolon after the / solves the ambiguity.

@evanspa
Copy link
Author

evanspa commented Nov 14, 2013

Ah yes - good point regarding the semicolon 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants