source: node_modules/undici/lib/cookies/util.js

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

Initial commit

  • Property mode set to 100644
File size: 7.2 KB
Line 
1'use strict'
2
3const assert = require('assert')
4const { kHeadersList } = require('../core/symbols')
5
6function isCTLExcludingHtab (value) {
7 if (value.length === 0) {
8 return false
9 }
10
11 for (const char of value) {
12 const code = char.charCodeAt(0)
13
14 if (
15 (code >= 0x00 || code <= 0x08) ||
16 (code >= 0x0A || code <= 0x1F) ||
17 code === 0x7F
18 ) {
19 return false
20 }
21 }
22}
23
24/**
25 CHAR = <any US-ASCII character (octets 0 - 127)>
26 token = 1*<any CHAR except CTLs or separators>
27 separators = "(" | ")" | "<" | ">" | "@"
28 | "," | ";" | ":" | "\" | <">
29 | "/" | "[" | "]" | "?" | "="
30 | "{" | "}" | SP | HT
31 * @param {string} name
32 */
33function validateCookieName (name) {
34 for (const char of name) {
35 const code = char.charCodeAt(0)
36
37 if (
38 (code <= 0x20 || code > 0x7F) ||
39 char === '(' ||
40 char === ')' ||
41 char === '>' ||
42 char === '<' ||
43 char === '@' ||
44 char === ',' ||
45 char === ';' ||
46 char === ':' ||
47 char === '\\' ||
48 char === '"' ||
49 char === '/' ||
50 char === '[' ||
51 char === ']' ||
52 char === '?' ||
53 char === '=' ||
54 char === '{' ||
55 char === '}'
56 ) {
57 throw new Error('Invalid cookie name')
58 }
59 }
60}
61
62/**
63 cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
64 cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
65 ; US-ASCII characters excluding CTLs,
66 ; whitespace DQUOTE, comma, semicolon,
67 ; and backslash
68 * @param {string} value
69 */
70function validateCookieValue (value) {
71 for (const char of value) {
72 const code = char.charCodeAt(0)
73
74 if (
75 code < 0x21 || // exclude CTLs (0-31)
76 code === 0x22 ||
77 code === 0x2C ||
78 code === 0x3B ||
79 code === 0x5C ||
80 code > 0x7E // non-ascii
81 ) {
82 throw new Error('Invalid header value')
83 }
84 }
85}
86
87/**
88 * path-value = <any CHAR except CTLs or ";">
89 * @param {string} path
90 */
91function validateCookiePath (path) {
92 for (const char of path) {
93 const code = char.charCodeAt(0)
94
95 if (code < 0x21 || char === ';') {
96 throw new Error('Invalid cookie path')
97 }
98 }
99}
100
101/**
102 * I have no idea why these values aren't allowed to be honest,
103 * but Deno tests these. - Khafra
104 * @param {string} domain
105 */
106function validateCookieDomain (domain) {
107 if (
108 domain.startsWith('-') ||
109 domain.endsWith('.') ||
110 domain.endsWith('-')
111 ) {
112 throw new Error('Invalid cookie domain')
113 }
114}
115
116/**
117 * @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1
118 * @param {number|Date} date
119 IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT
120 ; fixed length/zone/capitalization subset of the format
121 ; see Section 3.3 of [RFC5322]
122
123 day-name = %x4D.6F.6E ; "Mon", case-sensitive
124 / %x54.75.65 ; "Tue", case-sensitive
125 / %x57.65.64 ; "Wed", case-sensitive
126 / %x54.68.75 ; "Thu", case-sensitive
127 / %x46.72.69 ; "Fri", case-sensitive
128 / %x53.61.74 ; "Sat", case-sensitive
129 / %x53.75.6E ; "Sun", case-sensitive
130 date1 = day SP month SP year
131 ; e.g., 02 Jun 1982
132
133 day = 2DIGIT
134 month = %x4A.61.6E ; "Jan", case-sensitive
135 / %x46.65.62 ; "Feb", case-sensitive
136 / %x4D.61.72 ; "Mar", case-sensitive
137 / %x41.70.72 ; "Apr", case-sensitive
138 / %x4D.61.79 ; "May", case-sensitive
139 / %x4A.75.6E ; "Jun", case-sensitive
140 / %x4A.75.6C ; "Jul", case-sensitive
141 / %x41.75.67 ; "Aug", case-sensitive
142 / %x53.65.70 ; "Sep", case-sensitive
143 / %x4F.63.74 ; "Oct", case-sensitive
144 / %x4E.6F.76 ; "Nov", case-sensitive
145 / %x44.65.63 ; "Dec", case-sensitive
146 year = 4DIGIT
147
148 GMT = %x47.4D.54 ; "GMT", case-sensitive
149
150 time-of-day = hour ":" minute ":" second
151 ; 00:00:00 - 23:59:60 (leap second)
152
153 hour = 2DIGIT
154 minute = 2DIGIT
155 second = 2DIGIT
156 */
157function toIMFDate (date) {
158 if (typeof date === 'number') {
159 date = new Date(date)
160 }
161
162 const days = [
163 'Sun', 'Mon', 'Tue', 'Wed',
164 'Thu', 'Fri', 'Sat'
165 ]
166
167 const months = [
168 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
169 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
170 ]
171
172 const dayName = days[date.getUTCDay()]
173 const day = date.getUTCDate().toString().padStart(2, '0')
174 const month = months[date.getUTCMonth()]
175 const year = date.getUTCFullYear()
176 const hour = date.getUTCHours().toString().padStart(2, '0')
177 const minute = date.getUTCMinutes().toString().padStart(2, '0')
178 const second = date.getUTCSeconds().toString().padStart(2, '0')
179
180 return `${dayName}, ${day} ${month} ${year} ${hour}:${minute}:${second} GMT`
181}
182
183/**
184 max-age-av = "Max-Age=" non-zero-digit *DIGIT
185 ; In practice, both expires-av and max-age-av
186 ; are limited to dates representable by the
187 ; user agent.
188 * @param {number} maxAge
189 */
190function validateCookieMaxAge (maxAge) {
191 if (maxAge < 0) {
192 throw new Error('Invalid cookie max-age')
193 }
194}
195
196/**
197 * @see https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1
198 * @param {import('./index').Cookie} cookie
199 */
200function stringify (cookie) {
201 if (cookie.name.length === 0) {
202 return null
203 }
204
205 validateCookieName(cookie.name)
206 validateCookieValue(cookie.value)
207
208 const out = [`${cookie.name}=${cookie.value}`]
209
210 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
211 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.2
212 if (cookie.name.startsWith('__Secure-')) {
213 cookie.secure = true
214 }
215
216 if (cookie.name.startsWith('__Host-')) {
217 cookie.secure = true
218 cookie.domain = null
219 cookie.path = '/'
220 }
221
222 if (cookie.secure) {
223 out.push('Secure')
224 }
225
226 if (cookie.httpOnly) {
227 out.push('HttpOnly')
228 }
229
230 if (typeof cookie.maxAge === 'number') {
231 validateCookieMaxAge(cookie.maxAge)
232 out.push(`Max-Age=${cookie.maxAge}`)
233 }
234
235 if (cookie.domain) {
236 validateCookieDomain(cookie.domain)
237 out.push(`Domain=${cookie.domain}`)
238 }
239
240 if (cookie.path) {
241 validateCookiePath(cookie.path)
242 out.push(`Path=${cookie.path}`)
243 }
244
245 if (cookie.expires && cookie.expires.toString() !== 'Invalid Date') {
246 out.push(`Expires=${toIMFDate(cookie.expires)}`)
247 }
248
249 if (cookie.sameSite) {
250 out.push(`SameSite=${cookie.sameSite}`)
251 }
252
253 for (const part of cookie.unparsed) {
254 if (!part.includes('=')) {
255 throw new Error('Invalid unparsed')
256 }
257
258 const [key, ...value] = part.split('=')
259
260 out.push(`${key.trim()}=${value.join('=')}`)
261 }
262
263 return out.join('; ')
264}
265
266let kHeadersListNode
267
268function getHeadersList (headers) {
269 if (headers[kHeadersList]) {
270 return headers[kHeadersList]
271 }
272
273 if (!kHeadersListNode) {
274 kHeadersListNode = Object.getOwnPropertySymbols(headers).find(
275 (symbol) => symbol.description === 'headers list'
276 )
277
278 assert(kHeadersListNode, 'Headers cannot be parsed')
279 }
280
281 const headersList = headers[kHeadersListNode]
282 assert(headersList)
283
284 return headersList
285}
286
287module.exports = {
288 isCTLExcludingHtab,
289 stringify,
290 getHeadersList
291}
Note: See TracBrowser for help on using the repository browser.