source: node_modules/undici/lib/cookies/parse.js@ d24f17c

main
Last change on this file since d24f17c was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 12.1 KB
RevLine 
[d24f17c]1'use strict'
2
3const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants')
4const { isCTLExcludingHtab } = require('./util')
5const { collectASequenceOfCodePointsFast } = require('../fetch/dataURL')
6const assert = require('assert')
7
8/**
9 * @description Parses the field-value attributes of a set-cookie header string.
10 * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4
11 * @param {string} header
12 * @returns if the header is invalid, null will be returned
13 */
14function parseSetCookie (header) {
15 // 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F
16 // character (CTL characters excluding HTAB): Abort these steps and
17 // ignore the set-cookie-string entirely.
18 if (isCTLExcludingHtab(header)) {
19 return null
20 }
21
22 let nameValuePair = ''
23 let unparsedAttributes = ''
24 let name = ''
25 let value = ''
26
27 // 2. If the set-cookie-string contains a %x3B (";") character:
28 if (header.includes(';')) {
29 // 1. The name-value-pair string consists of the characters up to,
30 // but not including, the first %x3B (";"), and the unparsed-
31 // attributes consist of the remainder of the set-cookie-string
32 // (including the %x3B (";") in question).
33 const position = { position: 0 }
34
35 nameValuePair = collectASequenceOfCodePointsFast(';', header, position)
36 unparsedAttributes = header.slice(position.position)
37 } else {
38 // Otherwise:
39
40 // 1. The name-value-pair string consists of all the characters
41 // contained in the set-cookie-string, and the unparsed-
42 // attributes is the empty string.
43 nameValuePair = header
44 }
45
46 // 3. If the name-value-pair string lacks a %x3D ("=") character, then
47 // the name string is empty, and the value string is the value of
48 // name-value-pair.
49 if (!nameValuePair.includes('=')) {
50 value = nameValuePair
51 } else {
52 // Otherwise, the name string consists of the characters up to, but
53 // not including, the first %x3D ("=") character, and the (possibly
54 // empty) value string consists of the characters after the first
55 // %x3D ("=") character.
56 const position = { position: 0 }
57 name = collectASequenceOfCodePointsFast(
58 '=',
59 nameValuePair,
60 position
61 )
62 value = nameValuePair.slice(position.position + 1)
63 }
64
65 // 4. Remove any leading or trailing WSP characters from the name
66 // string and the value string.
67 name = name.trim()
68 value = value.trim()
69
70 // 5. If the sum of the lengths of the name string and the value string
71 // is more than 4096 octets, abort these steps and ignore the set-
72 // cookie-string entirely.
73 if (name.length + value.length > maxNameValuePairSize) {
74 return null
75 }
76
77 // 6. The cookie-name is the name string, and the cookie-value is the
78 // value string.
79 return {
80 name, value, ...parseUnparsedAttributes(unparsedAttributes)
81 }
82}
83
84/**
85 * Parses the remaining attributes of a set-cookie header
86 * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4
87 * @param {string} unparsedAttributes
88 * @param {[Object.<string, unknown>]={}} cookieAttributeList
89 */
90function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) {
91 // 1. If the unparsed-attributes string is empty, skip the rest of
92 // these steps.
93 if (unparsedAttributes.length === 0) {
94 return cookieAttributeList
95 }
96
97 // 2. Discard the first character of the unparsed-attributes (which
98 // will be a %x3B (";") character).
99 assert(unparsedAttributes[0] === ';')
100 unparsedAttributes = unparsedAttributes.slice(1)
101
102 let cookieAv = ''
103
104 // 3. If the remaining unparsed-attributes contains a %x3B (";")
105 // character:
106 if (unparsedAttributes.includes(';')) {
107 // 1. Consume the characters of the unparsed-attributes up to, but
108 // not including, the first %x3B (";") character.
109 cookieAv = collectASequenceOfCodePointsFast(
110 ';',
111 unparsedAttributes,
112 { position: 0 }
113 )
114 unparsedAttributes = unparsedAttributes.slice(cookieAv.length)
115 } else {
116 // Otherwise:
117
118 // 1. Consume the remainder of the unparsed-attributes.
119 cookieAv = unparsedAttributes
120 unparsedAttributes = ''
121 }
122
123 // Let the cookie-av string be the characters consumed in this step.
124
125 let attributeName = ''
126 let attributeValue = ''
127
128 // 4. If the cookie-av string contains a %x3D ("=") character:
129 if (cookieAv.includes('=')) {
130 // 1. The (possibly empty) attribute-name string consists of the
131 // characters up to, but not including, the first %x3D ("=")
132 // character, and the (possibly empty) attribute-value string
133 // consists of the characters after the first %x3D ("=")
134 // character.
135 const position = { position: 0 }
136
137 attributeName = collectASequenceOfCodePointsFast(
138 '=',
139 cookieAv,
140 position
141 )
142 attributeValue = cookieAv.slice(position.position + 1)
143 } else {
144 // Otherwise:
145
146 // 1. The attribute-name string consists of the entire cookie-av
147 // string, and the attribute-value string is empty.
148 attributeName = cookieAv
149 }
150
151 // 5. Remove any leading or trailing WSP characters from the attribute-
152 // name string and the attribute-value string.
153 attributeName = attributeName.trim()
154 attributeValue = attributeValue.trim()
155
156 // 6. If the attribute-value is longer than 1024 octets, ignore the
157 // cookie-av string and return to Step 1 of this algorithm.
158 if (attributeValue.length > maxAttributeValueSize) {
159 return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
160 }
161
162 // 7. Process the attribute-name and attribute-value according to the
163 // requirements in the following subsections. (Notice that
164 // attributes with unrecognized attribute-names are ignored.)
165 const attributeNameLowercase = attributeName.toLowerCase()
166
167 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.1
168 // If the attribute-name case-insensitively matches the string
169 // "Expires", the user agent MUST process the cookie-av as follows.
170 if (attributeNameLowercase === 'expires') {
171 // 1. Let the expiry-time be the result of parsing the attribute-value
172 // as cookie-date (see Section 5.1.1).
173 const expiryTime = new Date(attributeValue)
174
175 // 2. If the attribute-value failed to parse as a cookie date, ignore
176 // the cookie-av.
177
178 cookieAttributeList.expires = expiryTime
179 } else if (attributeNameLowercase === 'max-age') {
180 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.2
181 // If the attribute-name case-insensitively matches the string "Max-
182 // Age", the user agent MUST process the cookie-av as follows.
183
184 // 1. If the first character of the attribute-value is not a DIGIT or a
185 // "-" character, ignore the cookie-av.
186 const charCode = attributeValue.charCodeAt(0)
187
188 if ((charCode < 48 || charCode > 57) && attributeValue[0] !== '-') {
189 return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
190 }
191
192 // 2. If the remainder of attribute-value contains a non-DIGIT
193 // character, ignore the cookie-av.
194 if (!/^\d+$/.test(attributeValue)) {
195 return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
196 }
197
198 // 3. Let delta-seconds be the attribute-value converted to an integer.
199 const deltaSeconds = Number(attributeValue)
200
201 // 4. Let cookie-age-limit be the maximum age of the cookie (which
202 // SHOULD be 400 days or less, see Section 4.1.2.2).
203
204 // 5. Set delta-seconds to the smaller of its present value and cookie-
205 // age-limit.
206 // deltaSeconds = Math.min(deltaSeconds * 1000, maxExpiresMs)
207
208 // 6. If delta-seconds is less than or equal to zero (0), let expiry-
209 // time be the earliest representable date and time. Otherwise, let
210 // the expiry-time be the current date and time plus delta-seconds
211 // seconds.
212 // const expiryTime = deltaSeconds <= 0 ? Date.now() : Date.now() + deltaSeconds
213
214 // 7. Append an attribute to the cookie-attribute-list with an
215 // attribute-name of Max-Age and an attribute-value of expiry-time.
216 cookieAttributeList.maxAge = deltaSeconds
217 } else if (attributeNameLowercase === 'domain') {
218 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.3
219 // If the attribute-name case-insensitively matches the string "Domain",
220 // the user agent MUST process the cookie-av as follows.
221
222 // 1. Let cookie-domain be the attribute-value.
223 let cookieDomain = attributeValue
224
225 // 2. If cookie-domain starts with %x2E ("."), let cookie-domain be
226 // cookie-domain without its leading %x2E (".").
227 if (cookieDomain[0] === '.') {
228 cookieDomain = cookieDomain.slice(1)
229 }
230
231 // 3. Convert the cookie-domain to lower case.
232 cookieDomain = cookieDomain.toLowerCase()
233
234 // 4. Append an attribute to the cookie-attribute-list with an
235 // attribute-name of Domain and an attribute-value of cookie-domain.
236 cookieAttributeList.domain = cookieDomain
237 } else if (attributeNameLowercase === 'path') {
238 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.4
239 // If the attribute-name case-insensitively matches the string "Path",
240 // the user agent MUST process the cookie-av as follows.
241
242 // 1. If the attribute-value is empty or if the first character of the
243 // attribute-value is not %x2F ("/"):
244 let cookiePath = ''
245 if (attributeValue.length === 0 || attributeValue[0] !== '/') {
246 // 1. Let cookie-path be the default-path.
247 cookiePath = '/'
248 } else {
249 // Otherwise:
250
251 // 1. Let cookie-path be the attribute-value.
252 cookiePath = attributeValue
253 }
254
255 // 2. Append an attribute to the cookie-attribute-list with an
256 // attribute-name of Path and an attribute-value of cookie-path.
257 cookieAttributeList.path = cookiePath
258 } else if (attributeNameLowercase === 'secure') {
259 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.5
260 // If the attribute-name case-insensitively matches the string "Secure",
261 // the user agent MUST append an attribute to the cookie-attribute-list
262 // with an attribute-name of Secure and an empty attribute-value.
263
264 cookieAttributeList.secure = true
265 } else if (attributeNameLowercase === 'httponly') {
266 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.6
267 // If the attribute-name case-insensitively matches the string
268 // "HttpOnly", the user agent MUST append an attribute to the cookie-
269 // attribute-list with an attribute-name of HttpOnly and an empty
270 // attribute-value.
271
272 cookieAttributeList.httpOnly = true
273 } else if (attributeNameLowercase === 'samesite') {
274 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7
275 // If the attribute-name case-insensitively matches the string
276 // "SameSite", the user agent MUST process the cookie-av as follows:
277
278 // 1. Let enforcement be "Default".
279 let enforcement = 'Default'
280
281 const attributeValueLowercase = attributeValue.toLowerCase()
282 // 2. If cookie-av's attribute-value is a case-insensitive match for
283 // "None", set enforcement to "None".
284 if (attributeValueLowercase.includes('none')) {
285 enforcement = 'None'
286 }
287
288 // 3. If cookie-av's attribute-value is a case-insensitive match for
289 // "Strict", set enforcement to "Strict".
290 if (attributeValueLowercase.includes('strict')) {
291 enforcement = 'Strict'
292 }
293
294 // 4. If cookie-av's attribute-value is a case-insensitive match for
295 // "Lax", set enforcement to "Lax".
296 if (attributeValueLowercase.includes('lax')) {
297 enforcement = 'Lax'
298 }
299
300 // 5. Append an attribute to the cookie-attribute-list with an
301 // attribute-name of "SameSite" and an attribute-value of
302 // enforcement.
303 cookieAttributeList.sameSite = enforcement
304 } else {
305 cookieAttributeList.unparsed ??= []
306
307 cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`)
308 }
309
310 // 8. Return to Step 1 of this algorithm.
311 return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
312}
313
314module.exports = {
315 parseSetCookie,
316 parseUnparsedAttributes
317}
Note: See TracBrowser for help on using the repository browser.