Skip to content

Commit 7ee93b2

Browse files
authored
feat: add cookie parsing ability (nodejs#1848)
1 parent 998a593 commit 7ee93b2

File tree

10 files changed

+1450
-1
lines changed

10 files changed

+1450
-1
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import mockErrors from'./types/mock-errors'
1616
import ProxyAgent from'./types/proxy-agent'
1717
import { request, pipeline, stream, connect, upgrade } from './types/api'
1818

19+
export * from './types/cookies'
1920
export * from './types/fetch'
2021
export * from './types/file'
2122
export * from './types/filereader'

index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ if (nodeMajor >= 18) {
123123
const { WebSocket } = require('./lib/websocket/websocket')
124124

125125
module.exports.WebSocket = WebSocket
126+
127+
const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/cookies')
128+
129+
module.exports.deleteCookie = deleteCookie
130+
module.exports.getCookies = getCookies
131+
module.exports.getSetCookies = getSetCookies
132+
module.exports.setCookie = setCookie
126133
}
127134

128135
module.exports.request = makeDispatcher(api.request)

lib/cookies/constants.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict'
2+
3+
// https://wicg.github.io/cookie-store/#cookie-maximum-attribute-value-size
4+
const maxAttributeValueSize = 1024
5+
6+
// https://wicg.github.io/cookie-store/#cookie-maximum-name-value-pair-size
7+
const maxNameValuePairSize = 4096
8+
9+
module.exports = {
10+
maxAttributeValueSize,
11+
maxNameValuePairSize
12+
}

lib/cookies/index.js

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
'use strict'
2+
3+
const { parseSetCookie } = require('./parse')
4+
const { stringify } = require('./util')
5+
const { webidl } = require('../fetch/webidl')
6+
const { Headers } = require('../fetch/headers')
7+
const { kHeadersList } = require('../core/symbols')
8+
9+
/**
10+
* @typedef {Object} Cookie
11+
* @property {string} name
12+
* @property {string} value
13+
* @property {Date|number|undefined} expires
14+
* @property {number|undefined} maxAge
15+
* @property {string|undefined} domain
16+
* @property {string|undefined} path
17+
* @property {boolean|undefined} secure
18+
* @property {boolean|undefined} httpOnly
19+
* @property {'Strict'|'Lax'|'None'} sameSite
20+
* @property {string[]} unparsed
21+
*/
22+
23+
/**
24+
* @param {Headers} headers
25+
* @returns {Record<string, string>}
26+
*/
27+
function getCookies (headers) {
28+
webidl.argumentLengthCheck(arguments, 1, { header: 'getCookies' })
29+
30+
webidl.brandCheck(headers, Headers)
31+
32+
const cookie = headers[kHeadersList].get('cookie')
33+
const out = {}
34+
35+
if (!cookie) {
36+
return out
37+
}
38+
39+
for (const piece of cookie.split(';')) {
40+
const [name, ...value] = piece.split('=')
41+
42+
out[name.trim()] = value.join('=')
43+
}
44+
45+
return out
46+
}
47+
48+
/**
49+
* @param {Headers} headers
50+
* @param {string} name
51+
* @param {{ path?: string, domain?: string }|undefined} attributes
52+
* @returns {void}
53+
*/
54+
function deleteCookie (headers, name, attributes) {
55+
webidl.argumentLengthCheck(arguments, 2, { header: 'deleteCookie' })
56+
57+
webidl.brandCheck(headers, Headers)
58+
59+
name = webidl.converters.DOMString(name)
60+
attributes = webidl.converters.DeleteCookieAttributes(attributes)
61+
62+
// Matches behavior of
63+
// https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278
64+
setCookie(headers, {
65+
name,
66+
value: '',
67+
expires: new Date(0),
68+
...attributes
69+
})
70+
}
71+
72+
/**
73+
* @param {Headers} headers
74+
* @returns {Cookie[]}
75+
*/
76+
function getSetCookies (headers) {
77+
webidl.argumentLengthCheck(arguments, 1, { header: 'getSetCookies' })
78+
79+
webidl.brandCheck(headers, Headers)
80+
81+
const cookies = headers[kHeadersList].cookies
82+
83+
if (!cookies) {
84+
return []
85+
}
86+
87+
return cookies.map((pair) => parseSetCookie(pair[1]))
88+
}
89+
90+
/**
91+
* @param {Headers} headers
92+
* @param {Cookie} cookie
93+
* @returns {void}
94+
*/
95+
function setCookie (headers, cookie) {
96+
webidl.argumentLengthCheck(arguments, 2, { header: 'setCookie' })
97+
98+
webidl.brandCheck(headers, Headers)
99+
100+
cookie = webidl.converters.Cookie(cookie)
101+
102+
const str = stringify(cookie)
103+
104+
if (str) {
105+
headers.append('Set-Cookie', stringify(cookie))
106+
}
107+
}
108+
109+
webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([
110+
{
111+
converter: webidl.nullableConverter(webidl.converters.DOMString),
112+
key: 'path',
113+
defaultValue: null
114+
},
115+
{
116+
converter: webidl.nullableConverter(webidl.converters.DOMString),
117+
key: 'domain',
118+
defaultValue: null
119+
}
120+
])
121+
122+
webidl.converters.Cookie = webidl.dictionaryConverter([
123+
{
124+
converter: webidl.converters.DOMString,
125+
key: 'name'
126+
},
127+
{
128+
converter: webidl.converters.DOMString,
129+
key: 'value'
130+
},
131+
{
132+
converter: webidl.nullableConverter((value) => {
133+
if (typeof value === 'number') {
134+
return webidl.converters['unsigned long long'](value)
135+
}
136+
137+
return new Date(value)
138+
}),
139+
key: 'expires',
140+
defaultValue: null
141+
},
142+
{
143+
converter: webidl.nullableConverter(webidl.converters['long long']),
144+
key: 'maxAge',
145+
defaultValue: null
146+
},
147+
{
148+
converter: webidl.nullableConverter(webidl.converters.DOMString),
149+
key: 'domain',
150+
defaultValue: null
151+
},
152+
{
153+
converter: webidl.nullableConverter(webidl.converters.DOMString),
154+
key: 'path',
155+
defaultValue: null
156+
},
157+
{
158+
converter: webidl.nullableConverter(webidl.converters.boolean),
159+
key: 'secure',
160+
defaultValue: null
161+
},
162+
{
163+
converter: webidl.nullableConverter(webidl.converters.boolean),
164+
key: 'httpOnly',
165+
defaultValue: null
166+
},
167+
{
168+
converter: webidl.converters.USVString,
169+
key: 'sameSite',
170+
allowedValues: ['Strict', 'Lax', 'None']
171+
},
172+
{
173+
converter: webidl.sequenceConverter(webidl.converters.DOMString),
174+
key: 'unparsed',
175+
defaultValue: []
176+
}
177+
])
178+
179+
module.exports = {
180+
getCookies,
181+
deleteCookie,
182+
getSetCookies,
183+
setCookie
184+
}

0 commit comments

Comments
 (0)