Don't fold Set-Cookie headers with Headers.append()

I’ve written a worker to act as a reverse proxy for a few legacy systems that live on different hosts and in different directories but need to be reachable on a single site with an altered directory structure.

One of the largest issues I ran into was rewriting location headers, because I initially create a new Headers object and then appended the headers from the response’s headers object after changing the URLs. This got me in trouble, because
headers.append(“set-cookie”, “cookie1=value1”);
headers.append(“set-cookie”, “cookie2=value2”);
Ends up becoming “set-cookie: cookie1=value1, cookie2=value2” which browsers hate.

The solution to my specific problem was creating a new response as mentioned in Alter headers · Cloudflare Workers docs (though I would though that Response.clone() should also remove the immutable guard from the headers object!), as I only needed to change the Location header and Headers.set() is fine for doing that.
Had the set-cookie headers also needed rewriting (because of a domain or path setting), I would’ve been out of luck. Just using set() on a different header leaves the set-cookie headers in peace, so if the origin sends two headers, two headers will be passed on.

Maybe I’ve missed something? I believe that the Headers object needs to either provide a method to change each value without folding them (unless the origin had folded them?), or Headers.append() needs to treat set-cookie as an exception and not fold multiple values into one header.

Giving this a quick try it would appear as if two separate headers were sent

You can check this also at Cloudflare Workers

1 Like

Thanks!

Lesson learned: I should’ve asked earlier. I’ve been focusing on the wrong side of the equation: the cookie-header gets folded when iterating over the response.headers, not when appending. While that ends up in the same state, it provides options. Splitting folded cookies is a bit painful, but definitely possible, especially when you generally know the kind of values to expect.

Thank you very much, I’ve stared at this for too long, you’ve helped me a lot.

headers.getAll("set-cookie") should actually return the cookies in a properly structured way.

Yeah, you’re right. I even read the discussion around CF implementing getAll() and the WhatWG being very opposed. I then obviously proceeded to ignore what I read, thinking “that’s all well and good, but the problem is that append() is folding them back in”.

I’ve had one of those movie-cliche revelation moments that cast all the previous scenes in a new light. At least it makes sense now and I’m not going crazy because I’m the only one in the world that sees this glaring problem while everone else is discussing the irrelevant side of the issue! :wink:

This example does not appear to be working. When I run it I do not see any set-cookie values in the response headers (screenshot below)


When I add debug logging I see the values folded as the problem describes
Adding this console log before and after updating headers I see the following output
console.log(‘response headers after mutation’, response.headers.get(‘set-cookie’))

response headers before mutation null
response headers after mutation cookie1=value1, cookie2=value2

We have similar logic in our worker. Our response from fetch() includes a set-cookie header value. When we later append a second one it just folds them together as the original problem notes.

That may be an issue with the tutorial display of the headers rather than the actual headers being sent. The Network tab of the tutorial tools folds them, but if you deploy that worker on some real route of your domain, you should not see them folded. I’ve just tried, and they aren’t, though the order of the headers seems to be changed.

I’d expect them to be on neighboring lines, but according to curl, it’s


set-cookie: cookie1=value1
cf-cache-status: DYNAMIC
cf-request-id: 04ae1c3e170000d46b7f2a7200000001
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
set-cookie: cookie2=value2