Skip to content

Commit ba5ef44

Browse files
authored
feat: add Headers.prototype.getSetCookie (#1915)
1 parent 0a59535 commit ba5ef44

File tree

4 files changed

+290
-9
lines changed

4 files changed

+290
-9
lines changed

lib/cookies/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ function getSetCookies (headers) {
8383
return []
8484
}
8585

86-
return cookies.map((pair) => parseSetCookie(pair[1]))
86+
// In older versions of undici, cookies is a list of name:value.
87+
return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair))
8788
}
8889

8990
/**

lib/fetch/headers.js

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
isValidHeaderValue
1212
} = require('./util')
1313
const { webidl } = require('./webidl')
14+
const assert = require('assert')
1415

1516
const kHeadersMap = Symbol('headers map')
1617
const kHeadersSortedMap = Symbol('headers map sorted')
@@ -115,7 +116,7 @@ class HeadersList {
115116

116117
if (lowercaseName === 'set-cookie') {
117118
this.cookies ??= []
118-
this.cookies.push([name, value])
119+
this.cookies.push(value)
119120
}
120121
}
121122

@@ -125,7 +126,7 @@ class HeadersList {
125126
const lowercaseName = name.toLowerCase()
126127

127128
if (lowercaseName === 'set-cookie') {
128-
this.cookies = [[name, value]]
129+
this.cookies = [value]
129130
}
130131

131132
// 1. If list contains name, then set the value of
@@ -383,18 +384,68 @@ class Headers {
383384
return this[kHeadersList].set(name, value)
384385
}
385386

387+
// https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
388+
getSetCookie () {
389+
webidl.brandCheck(this, Headers)
390+
391+
// 1. If this’s header list does not contain `Set-Cookie`, then return « ».
392+
// 2. Return the values of all headers in this’s header list whose name is
393+
// a byte-case-insensitive match for `Set-Cookie`, in order.
394+
395+
return this[kHeadersList].cookies ?? []
396+
}
397+
398+
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
386399
get [kHeadersSortedMap] () {
387-
if (!this[kHeadersList][kHeadersSortedMap]) {
388-
this[kHeadersList][kHeadersSortedMap] = new Map([...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1))
400+
if (this[kHeadersList][kHeadersSortedMap]) {
401+
return this[kHeadersList][kHeadersSortedMap]
389402
}
390-
return this[kHeadersList][kHeadersSortedMap]
403+
404+
// 1. Let headers be an empty list of headers with the key being the name
405+
// and value the value.
406+
const headers = []
407+
408+
// 2. Let names be the result of convert header names to a sorted-lowercase
409+
// set with all the names of the headers in list.
410+
const names = [...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1)
411+
const cookies = this[kHeadersList].cookies
412+
413+
// 3. For each name of names:
414+
for (const [name, value] of names) {
415+
// 1. If name is `set-cookie`, then:
416+
if (name === 'set-cookie') {
417+
// 1. Let values be a list of all values of headers in list whose name
418+
// is a byte-case-insensitive match for name, in order.
419+
420+
// 2. For each value of values:
421+
// 1. Append (name, value) to headers.
422+
for (const value of cookies) {
423+
headers.push([name, value])
424+
}
425+
} else {
426+
// 2. Otherwise:
427+
428+
// 1. Let value be the result of getting name from list.
429+
430+
// 2. Assert: value is non-null.
431+
assert(value !== null)
432+
433+
// 3. Append (name, value) to headers.
434+
headers.push([name, value])
435+
}
436+
}
437+
438+
this[kHeadersList][kHeadersSortedMap] = headers
439+
440+
// 4. Return headers.
441+
return headers
391442
}
392443

393444
keys () {
394445
webidl.brandCheck(this, Headers)
395446

396447
return makeIterator(
397-
() => [...this[kHeadersSortedMap].entries()],
448+
() => [...this[kHeadersSortedMap].values()],
398449
'Headers',
399450
'key'
400451
)
@@ -404,7 +455,7 @@ class Headers {
404455
webidl.brandCheck(this, Headers)
405456

406457
return makeIterator(
407-
() => [...this[kHeadersSortedMap].entries()],
458+
() => [...this[kHeadersSortedMap].values()],
408459
'Headers',
409460
'value'
410461
)
@@ -414,7 +465,7 @@ class Headers {
414465
webidl.brandCheck(this, Headers)
415466

416467
return makeIterator(
417-
() => [...this[kHeadersSortedMap].entries()],
468+
() => [...this[kHeadersSortedMap].values()],
418469
'Headers',
419470
'key+value'
420471
)

test/wpt/status/fetch.status.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,5 +206,10 @@
206206
"fetch() with value %1E",
207207
"fetch() with value %1F"
208208
]
209+
},
210+
"header-setcookie.any.js": {
211+
"fail": [
212+
"Set-Cookie is a forbidden response header"
213+
]
209214
}
210215
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// META: title=Headers set-cookie special cases
2+
// META: global=window,worker
3+
4+
const headerList = [
5+
["set-cookie", "foo=bar"],
6+
["Set-Cookie", "fizz=buzz; domain=example.com"],
7+
];
8+
9+
const setCookie2HeaderList = [
10+
["set-cookie2", "foo2=bar2"],
11+
["Set-Cookie2", "fizz2=buzz2; domain=example2.com"],
12+
];
13+
14+
function assert_nested_array_equals(actual, expected) {
15+
assert_equals(actual.length, expected.length, "Array length is not equal");
16+
for (let i = 0; i < expected.length; i++) {
17+
assert_array_equals(actual[i], expected[i]);
18+
}
19+
}
20+
21+
test(function () {
22+
const headers = new Headers(headerList);
23+
assert_equals(
24+
headers.get("set-cookie"),
25+
"foo=bar, fizz=buzz; domain=example.com",
26+
);
27+
}, "Headers.prototype.get combines set-cookie headers in order");
28+
29+
test(function () {
30+
const headers = new Headers(headerList);
31+
const list = [...headers];
32+
assert_nested_array_equals(list, [
33+
["set-cookie", "foo=bar"],
34+
["set-cookie", "fizz=buzz; domain=example.com"],
35+
]);
36+
}, "Headers iterator does not combine set-cookie headers");
37+
38+
test(function () {
39+
const headers = new Headers(setCookie2HeaderList);
40+
const list = [...headers];
41+
assert_nested_array_equals(list, [
42+
["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
43+
]);
44+
}, "Headers iterator does not special case set-cookie2 headers");
45+
46+
test(function () {
47+
const headers = new Headers([...headerList, ...setCookie2HeaderList]);
48+
const list = [...headers];
49+
assert_nested_array_equals(list, [
50+
["set-cookie", "foo=bar"],
51+
["set-cookie", "fizz=buzz; domain=example.com"],
52+
["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
53+
]);
54+
}, "Headers iterator does not combine set-cookie & set-cookie2 headers");
55+
56+
test(function () {
57+
// Values are in non alphabetic order, and the iterator should yield in the
58+
// headers in the exact order of the input.
59+
const headers = new Headers([
60+
["set-cookie", "z=z"],
61+
["set-cookie", "a=a"],
62+
["set-cookie", "n=n"],
63+
]);
64+
const list = [...headers];
65+
assert_nested_array_equals(list, [
66+
["set-cookie", "z=z"],
67+
["set-cookie", "a=a"],
68+
["set-cookie", "n=n"],
69+
]);
70+
}, "Headers iterator preserves set-cookie ordering");
71+
72+
test(
73+
function () {
74+
const headers = new Headers([
75+
["xylophone-header", "1"],
76+
["best-header", "2"],
77+
["set-cookie", "3"],
78+
["a-cool-header", "4"],
79+
["set-cookie", "5"],
80+
["a-cool-header", "6"],
81+
["best-header", "7"],
82+
]);
83+
const list = [...headers];
84+
assert_nested_array_equals(list, [
85+
["a-cool-header", "4, 6"],
86+
["best-header", "2, 7"],
87+
["set-cookie", "3"],
88+
["set-cookie", "5"],
89+
["xylophone-header", "1"],
90+
]);
91+
},
92+
"Headers iterator preserves per header ordering, but sorts keys alphabetically",
93+
);
94+
95+
test(
96+
function () {
97+
const headers = new Headers([
98+
["xylophone-header", "7"],
99+
["best-header", "6"],
100+
["set-cookie", "5"],
101+
["a-cool-header", "4"],
102+
["set-cookie", "3"],
103+
["a-cool-header", "2"],
104+
["best-header", "1"],
105+
]);
106+
const list = [...headers];
107+
assert_nested_array_equals(list, [
108+
["a-cool-header", "4, 2"],
109+
["best-header", "6, 1"],
110+
["set-cookie", "5"],
111+
["set-cookie", "3"],
112+
["xylophone-header", "7"],
113+
]);
114+
},
115+
"Headers iterator preserves per header ordering, but sorts keys alphabetically (and ignores value ordering)",
116+
);
117+
118+
test(function () {
119+
const headers = new Headers([["fizz", "buzz"], ["X-Header", "test"]]);
120+
const iterator = headers[Symbol.iterator]();
121+
assert_array_equals(iterator.next().value, ["fizz", "buzz"]);
122+
headers.append("Set-Cookie", "a=b");
123+
assert_array_equals(iterator.next().value, ["set-cookie", "a=b"]);
124+
headers.append("Accept", "text/html");
125+
assert_array_equals(iterator.next().value, ["set-cookie", "a=b"]);
126+
assert_array_equals(iterator.next().value, ["x-header", "test"]);
127+
headers.append("set-cookie", "c=d");
128+
assert_array_equals(iterator.next().value, ["x-header", "test"]);
129+
assert_true(iterator.next().done);
130+
}, "Headers iterator is correctly updated with set-cookie changes");
131+
132+
test(function () {
133+
const headers = new Headers(headerList);
134+
assert_true(headers.has("sEt-cOoKiE"));
135+
}, "Headers.prototype.has works for set-cookie");
136+
137+
test(function () {
138+
const headers = new Headers(setCookie2HeaderList);
139+
headers.append("set-Cookie", "foo=bar");
140+
headers.append("sEt-cOoKiE", "fizz=buzz");
141+
const list = [...headers];
142+
assert_nested_array_equals(list, [
143+
["set-cookie", "foo=bar"],
144+
["set-cookie", "fizz=buzz"],
145+
["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
146+
]);
147+
}, "Headers.prototype.append works for set-cookie");
148+
149+
test(function () {
150+
const headers = new Headers(headerList);
151+
headers.set("set-cookie", "foo2=bar2");
152+
const list = [...headers];
153+
assert_nested_array_equals(list, [
154+
["set-cookie", "foo2=bar2"],
155+
]);
156+
}, "Headers.prototype.set works for set-cookie");
157+
158+
test(function () {
159+
const headers = new Headers(headerList);
160+
headers.delete("set-Cookie");
161+
const list = [...headers];
162+
assert_nested_array_equals(list, []);
163+
}, "Headers.prototype.delete works for set-cookie");
164+
165+
test(function () {
166+
const headers = new Headers();
167+
assert_array_equals(headers.getSetCookie(), []);
168+
}, "Headers.prototype.getSetCookie with no headers present");
169+
170+
test(function () {
171+
const headers = new Headers([headerList[0]]);
172+
assert_array_equals(headers.getSetCookie(), ["foo=bar"]);
173+
}, "Headers.prototype.getSetCookie with one header");
174+
175+
test(function () {
176+
const headers = new Headers({ "Set-Cookie": "foo=bar" });
177+
assert_array_equals(headers.getSetCookie(), ["foo=bar"]);
178+
}, "Headers.prototype.getSetCookie with one header created from an object");
179+
180+
test(function () {
181+
const headers = new Headers(headerList);
182+
assert_array_equals(headers.getSetCookie(), [
183+
"foo=bar",
184+
"fizz=buzz; domain=example.com",
185+
]);
186+
}, "Headers.prototype.getSetCookie with multiple headers");
187+
188+
test(function () {
189+
const headers = new Headers([["set-cookie", ""]]);
190+
assert_array_equals(headers.getSetCookie(), [""]);
191+
}, "Headers.prototype.getSetCookie with an empty header");
192+
193+
test(function () {
194+
const headers = new Headers([["set-cookie", "x"], ["set-cookie", "x"]]);
195+
assert_array_equals(headers.getSetCookie(), ["x", "x"]);
196+
}, "Headers.prototype.getSetCookie with two equal headers");
197+
198+
test(function () {
199+
const headers = new Headers([
200+
["set-cookie2", "x"],
201+
["set-cookie", "y"],
202+
["set-cookie2", "z"],
203+
]);
204+
assert_array_equals(headers.getSetCookie(), ["y"]);
205+
}, "Headers.prototype.getSetCookie ignores set-cookie2 headers");
206+
207+
test(function () {
208+
// Values are in non alphabetic order, and the iterator should yield in the
209+
// headers in the exact order of the input.
210+
const headers = new Headers([
211+
["set-cookie", "z=z"],
212+
["set-cookie", "a=a"],
213+
["set-cookie", "n=n"],
214+
]);
215+
assert_array_equals(headers.getSetCookie(), ["z=z", "a=a", "n=n"]);
216+
}, "Headers.prototype.getSetCookie preserves header ordering");
217+
218+
test(function () {
219+
const response = new Response();
220+
response.headers.append("Set-Cookie", "foo=bar");
221+
assert_array_equals(response.headers.getSetCookie(), []);
222+
response.headers.append("sEt-cOokIe", "bar=baz");
223+
assert_array_equals(response.headers.getSetCookie(), []);
224+
}, "Set-Cookie is a forbidden response header");

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy