mirror of
https://gitlab.com/upRootNutrition/zookeeper.git
synced 2025-06-17 02:45:12 -05:00
feat: init
This commit is contained in:
parent
8379d09058
commit
2cfa016090
2929 changed files with 299087 additions and 3 deletions
12
node_modules/undici/lib/web/cookies/constants.js
generated
vendored
Normal file
12
node_modules/undici/lib/web/cookies/constants.js
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
'use strict'
|
||||
|
||||
// https://wicg.github.io/cookie-store/#cookie-maximum-attribute-value-size
|
||||
const maxAttributeValueSize = 1024
|
||||
|
||||
// https://wicg.github.io/cookie-store/#cookie-maximum-name-value-pair-size
|
||||
const maxNameValuePairSize = 4096
|
||||
|
||||
module.exports = {
|
||||
maxAttributeValueSize,
|
||||
maxNameValuePairSize
|
||||
}
|
184
node_modules/undici/lib/web/cookies/index.js
generated
vendored
Normal file
184
node_modules/undici/lib/web/cookies/index.js
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
'use strict'
|
||||
|
||||
const { parseSetCookie } = require('./parse')
|
||||
const { stringify, getHeadersList } = require('./util')
|
||||
const { webidl } = require('../fetch/webidl')
|
||||
const { Headers } = require('../fetch/headers')
|
||||
|
||||
/**
|
||||
* @typedef {Object} Cookie
|
||||
* @property {string} name
|
||||
* @property {string} value
|
||||
* @property {Date|number|undefined} expires
|
||||
* @property {number|undefined} maxAge
|
||||
* @property {string|undefined} domain
|
||||
* @property {string|undefined} path
|
||||
* @property {boolean|undefined} secure
|
||||
* @property {boolean|undefined} httpOnly
|
||||
* @property {'Strict'|'Lax'|'None'} sameSite
|
||||
* @property {string[]} unparsed
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function getCookies (headers) {
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'getCookies' })
|
||||
|
||||
webidl.brandCheck(headers, Headers, { strict: false })
|
||||
|
||||
const cookie = headers.get('cookie')
|
||||
const out = {}
|
||||
|
||||
if (!cookie) {
|
||||
return out
|
||||
}
|
||||
|
||||
for (const piece of cookie.split(';')) {
|
||||
const [name, ...value] = piece.split('=')
|
||||
|
||||
out[name.trim()] = value.join('=')
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @param {string} name
|
||||
* @param {{ path?: string, domain?: string }|undefined} attributes
|
||||
* @returns {void}
|
||||
*/
|
||||
function deleteCookie (headers, name, attributes) {
|
||||
webidl.argumentLengthCheck(arguments, 2, { header: 'deleteCookie' })
|
||||
|
||||
webidl.brandCheck(headers, Headers, { strict: false })
|
||||
|
||||
name = webidl.converters.DOMString(name)
|
||||
attributes = webidl.converters.DeleteCookieAttributes(attributes)
|
||||
|
||||
// Matches behavior of
|
||||
// https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278
|
||||
setCookie(headers, {
|
||||
name,
|
||||
value: '',
|
||||
expires: new Date(0),
|
||||
...attributes
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @returns {Cookie[]}
|
||||
*/
|
||||
function getSetCookies (headers) {
|
||||
webidl.argumentLengthCheck(arguments, 1, { header: 'getSetCookies' })
|
||||
|
||||
webidl.brandCheck(headers, Headers, { strict: false })
|
||||
|
||||
const cookies = getHeadersList(headers).cookies
|
||||
|
||||
if (!cookies) {
|
||||
return []
|
||||
}
|
||||
|
||||
// In older versions of undici, cookies is a list of name:value.
|
||||
return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Headers} headers
|
||||
* @param {Cookie} cookie
|
||||
* @returns {void}
|
||||
*/
|
||||
function setCookie (headers, cookie) {
|
||||
webidl.argumentLengthCheck(arguments, 2, { header: 'setCookie' })
|
||||
|
||||
webidl.brandCheck(headers, Headers, { strict: false })
|
||||
|
||||
cookie = webidl.converters.Cookie(cookie)
|
||||
|
||||
const str = stringify(cookie)
|
||||
|
||||
if (str) {
|
||||
headers.append('Set-Cookie', str)
|
||||
}
|
||||
}
|
||||
|
||||
webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.DOMString),
|
||||
key: 'path',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.DOMString),
|
||||
key: 'domain',
|
||||
defaultValue: null
|
||||
}
|
||||
])
|
||||
|
||||
webidl.converters.Cookie = webidl.dictionaryConverter([
|
||||
{
|
||||
converter: webidl.converters.DOMString,
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
converter: webidl.converters.DOMString,
|
||||
key: 'value'
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter((value) => {
|
||||
if (typeof value === 'number') {
|
||||
return webidl.converters['unsigned long long'](value)
|
||||
}
|
||||
|
||||
return new Date(value)
|
||||
}),
|
||||
key: 'expires',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters['long long']),
|
||||
key: 'maxAge',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.DOMString),
|
||||
key: 'domain',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.DOMString),
|
||||
key: 'path',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.boolean),
|
||||
key: 'secure',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
converter: webidl.nullableConverter(webidl.converters.boolean),
|
||||
key: 'httpOnly',
|
||||
defaultValue: null
|
||||
},
|
||||
{
|
||||
converter: webidl.converters.USVString,
|
||||
key: 'sameSite',
|
||||
allowedValues: ['Strict', 'Lax', 'None']
|
||||
},
|
||||
{
|
||||
converter: webidl.sequenceConverter(webidl.converters.DOMString),
|
||||
key: 'unparsed',
|
||||
defaultValue: []
|
||||
}
|
||||
])
|
||||
|
||||
module.exports = {
|
||||
getCookies,
|
||||
deleteCookie,
|
||||
getSetCookies,
|
||||
setCookie
|
||||
}
|
317
node_modules/undici/lib/web/cookies/parse.js
generated
vendored
Normal file
317
node_modules/undici/lib/web/cookies/parse.js
generated
vendored
Normal file
|
@ -0,0 +1,317 @@
|
|||
'use strict'
|
||||
|
||||
const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants')
|
||||
const { isCTLExcludingHtab } = require('./util')
|
||||
const { collectASequenceOfCodePointsFast } = require('../fetch/data-url')
|
||||
const assert = require('node:assert')
|
||||
|
||||
/**
|
||||
* @description Parses the field-value attributes of a set-cookie header string.
|
||||
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4
|
||||
* @param {string} header
|
||||
* @returns if the header is invalid, null will be returned
|
||||
*/
|
||||
function parseSetCookie (header) {
|
||||
// 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F
|
||||
// character (CTL characters excluding HTAB): Abort these steps and
|
||||
// ignore the set-cookie-string entirely.
|
||||
if (isCTLExcludingHtab(header)) {
|
||||
return null
|
||||
}
|
||||
|
||||
let nameValuePair = ''
|
||||
let unparsedAttributes = ''
|
||||
let name = ''
|
||||
let value = ''
|
||||
|
||||
// 2. If the set-cookie-string contains a %x3B (";") character:
|
||||
if (header.includes(';')) {
|
||||
// 1. The name-value-pair string consists of the characters up to,
|
||||
// but not including, the first %x3B (";"), and the unparsed-
|
||||
// attributes consist of the remainder of the set-cookie-string
|
||||
// (including the %x3B (";") in question).
|
||||
const position = { position: 0 }
|
||||
|
||||
nameValuePair = collectASequenceOfCodePointsFast(';', header, position)
|
||||
unparsedAttributes = header.slice(position.position)
|
||||
} else {
|
||||
// Otherwise:
|
||||
|
||||
// 1. The name-value-pair string consists of all the characters
|
||||
// contained in the set-cookie-string, and the unparsed-
|
||||
// attributes is the empty string.
|
||||
nameValuePair = header
|
||||
}
|
||||
|
||||
// 3. If the name-value-pair string lacks a %x3D ("=") character, then
|
||||
// the name string is empty, and the value string is the value of
|
||||
// name-value-pair.
|
||||
if (!nameValuePair.includes('=')) {
|
||||
value = nameValuePair
|
||||
} else {
|
||||
// Otherwise, the name string consists of the characters up to, but
|
||||
// not including, the first %x3D ("=") character, and the (possibly
|
||||
// empty) value string consists of the characters after the first
|
||||
// %x3D ("=") character.
|
||||
const position = { position: 0 }
|
||||
name = collectASequenceOfCodePointsFast(
|
||||
'=',
|
||||
nameValuePair,
|
||||
position
|
||||
)
|
||||
value = nameValuePair.slice(position.position + 1)
|
||||
}
|
||||
|
||||
// 4. Remove any leading or trailing WSP characters from the name
|
||||
// string and the value string.
|
||||
name = name.trim()
|
||||
value = value.trim()
|
||||
|
||||
// 5. If the sum of the lengths of the name string and the value string
|
||||
// is more than 4096 octets, abort these steps and ignore the set-
|
||||
// cookie-string entirely.
|
||||
if (name.length + value.length > maxNameValuePairSize) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 6. The cookie-name is the name string, and the cookie-value is the
|
||||
// value string.
|
||||
return {
|
||||
name, value, ...parseUnparsedAttributes(unparsedAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the remaining attributes of a set-cookie header
|
||||
* @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4
|
||||
* @param {string} unparsedAttributes
|
||||
* @param {[Object.<string, unknown>]={}} cookieAttributeList
|
||||
*/
|
||||
function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) {
|
||||
// 1. If the unparsed-attributes string is empty, skip the rest of
|
||||
// these steps.
|
||||
if (unparsedAttributes.length === 0) {
|
||||
return cookieAttributeList
|
||||
}
|
||||
|
||||
// 2. Discard the first character of the unparsed-attributes (which
|
||||
// will be a %x3B (";") character).
|
||||
assert(unparsedAttributes[0] === ';')
|
||||
unparsedAttributes = unparsedAttributes.slice(1)
|
||||
|
||||
let cookieAv = ''
|
||||
|
||||
// 3. If the remaining unparsed-attributes contains a %x3B (";")
|
||||
// character:
|
||||
if (unparsedAttributes.includes(';')) {
|
||||
// 1. Consume the characters of the unparsed-attributes up to, but
|
||||
// not including, the first %x3B (";") character.
|
||||
cookieAv = collectASequenceOfCodePointsFast(
|
||||
';',
|
||||
unparsedAttributes,
|
||||
{ position: 0 }
|
||||
)
|
||||
unparsedAttributes = unparsedAttributes.slice(cookieAv.length)
|
||||
} else {
|
||||
// Otherwise:
|
||||
|
||||
// 1. Consume the remainder of the unparsed-attributes.
|
||||
cookieAv = unparsedAttributes
|
||||
unparsedAttributes = ''
|
||||
}
|
||||
|
||||
// Let the cookie-av string be the characters consumed in this step.
|
||||
|
||||
let attributeName = ''
|
||||
let attributeValue = ''
|
||||
|
||||
// 4. If the cookie-av string contains a %x3D ("=") character:
|
||||
if (cookieAv.includes('=')) {
|
||||
// 1. The (possibly empty) attribute-name string consists of the
|
||||
// characters up to, but not including, the first %x3D ("=")
|
||||
// character, and the (possibly empty) attribute-value string
|
||||
// consists of the characters after the first %x3D ("=")
|
||||
// character.
|
||||
const position = { position: 0 }
|
||||
|
||||
attributeName = collectASequenceOfCodePointsFast(
|
||||
'=',
|
||||
cookieAv,
|
||||
position
|
||||
)
|
||||
attributeValue = cookieAv.slice(position.position + 1)
|
||||
} else {
|
||||
// Otherwise:
|
||||
|
||||
// 1. The attribute-name string consists of the entire cookie-av
|
||||
// string, and the attribute-value string is empty.
|
||||
attributeName = cookieAv
|
||||
}
|
||||
|
||||
// 5. Remove any leading or trailing WSP characters from the attribute-
|
||||
// name string and the attribute-value string.
|
||||
attributeName = attributeName.trim()
|
||||
attributeValue = attributeValue.trim()
|
||||
|
||||
// 6. If the attribute-value is longer than 1024 octets, ignore the
|
||||
// cookie-av string and return to Step 1 of this algorithm.
|
||||
if (attributeValue.length > maxAttributeValueSize) {
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
|
||||
}
|
||||
|
||||
// 7. Process the attribute-name and attribute-value according to the
|
||||
// requirements in the following subsections. (Notice that
|
||||
// attributes with unrecognized attribute-names are ignored.)
|
||||
const attributeNameLowercase = attributeName.toLowerCase()
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.1
|
||||
// If the attribute-name case-insensitively matches the string
|
||||
// "Expires", the user agent MUST process the cookie-av as follows.
|
||||
if (attributeNameLowercase === 'expires') {
|
||||
// 1. Let the expiry-time be the result of parsing the attribute-value
|
||||
// as cookie-date (see Section 5.1.1).
|
||||
const expiryTime = new Date(attributeValue)
|
||||
|
||||
// 2. If the attribute-value failed to parse as a cookie date, ignore
|
||||
// the cookie-av.
|
||||
|
||||
cookieAttributeList.expires = expiryTime
|
||||
} else if (attributeNameLowercase === 'max-age') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.2
|
||||
// If the attribute-name case-insensitively matches the string "Max-
|
||||
// Age", the user agent MUST process the cookie-av as follows.
|
||||
|
||||
// 1. If the first character of the attribute-value is not a DIGIT or a
|
||||
// "-" character, ignore the cookie-av.
|
||||
const charCode = attributeValue.charCodeAt(0)
|
||||
|
||||
if ((charCode < 48 || charCode > 57) && attributeValue[0] !== '-') {
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
|
||||
}
|
||||
|
||||
// 2. If the remainder of attribute-value contains a non-DIGIT
|
||||
// character, ignore the cookie-av.
|
||||
if (!/^\d+$/.test(attributeValue)) {
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
|
||||
}
|
||||
|
||||
// 3. Let delta-seconds be the attribute-value converted to an integer.
|
||||
const deltaSeconds = Number(attributeValue)
|
||||
|
||||
// 4. Let cookie-age-limit be the maximum age of the cookie (which
|
||||
// SHOULD be 400 days or less, see Section 4.1.2.2).
|
||||
|
||||
// 5. Set delta-seconds to the smaller of its present value and cookie-
|
||||
// age-limit.
|
||||
// deltaSeconds = Math.min(deltaSeconds * 1000, maxExpiresMs)
|
||||
|
||||
// 6. If delta-seconds is less than or equal to zero (0), let expiry-
|
||||
// time be the earliest representable date and time. Otherwise, let
|
||||
// the expiry-time be the current date and time plus delta-seconds
|
||||
// seconds.
|
||||
// const expiryTime = deltaSeconds <= 0 ? Date.now() : Date.now() + deltaSeconds
|
||||
|
||||
// 7. Append an attribute to the cookie-attribute-list with an
|
||||
// attribute-name of Max-Age and an attribute-value of expiry-time.
|
||||
cookieAttributeList.maxAge = deltaSeconds
|
||||
} else if (attributeNameLowercase === 'domain') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.3
|
||||
// If the attribute-name case-insensitively matches the string "Domain",
|
||||
// the user agent MUST process the cookie-av as follows.
|
||||
|
||||
// 1. Let cookie-domain be the attribute-value.
|
||||
let cookieDomain = attributeValue
|
||||
|
||||
// 2. If cookie-domain starts with %x2E ("."), let cookie-domain be
|
||||
// cookie-domain without its leading %x2E (".").
|
||||
if (cookieDomain[0] === '.') {
|
||||
cookieDomain = cookieDomain.slice(1)
|
||||
}
|
||||
|
||||
// 3. Convert the cookie-domain to lower case.
|
||||
cookieDomain = cookieDomain.toLowerCase()
|
||||
|
||||
// 4. Append an attribute to the cookie-attribute-list with an
|
||||
// attribute-name of Domain and an attribute-value of cookie-domain.
|
||||
cookieAttributeList.domain = cookieDomain
|
||||
} else if (attributeNameLowercase === 'path') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.4
|
||||
// If the attribute-name case-insensitively matches the string "Path",
|
||||
// the user agent MUST process the cookie-av as follows.
|
||||
|
||||
// 1. If the attribute-value is empty or if the first character of the
|
||||
// attribute-value is not %x2F ("/"):
|
||||
let cookiePath = ''
|
||||
if (attributeValue.length === 0 || attributeValue[0] !== '/') {
|
||||
// 1. Let cookie-path be the default-path.
|
||||
cookiePath = '/'
|
||||
} else {
|
||||
// Otherwise:
|
||||
|
||||
// 1. Let cookie-path be the attribute-value.
|
||||
cookiePath = attributeValue
|
||||
}
|
||||
|
||||
// 2. Append an attribute to the cookie-attribute-list with an
|
||||
// attribute-name of Path and an attribute-value of cookie-path.
|
||||
cookieAttributeList.path = cookiePath
|
||||
} else if (attributeNameLowercase === 'secure') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.5
|
||||
// If the attribute-name case-insensitively matches the string "Secure",
|
||||
// the user agent MUST append an attribute to the cookie-attribute-list
|
||||
// with an attribute-name of Secure and an empty attribute-value.
|
||||
|
||||
cookieAttributeList.secure = true
|
||||
} else if (attributeNameLowercase === 'httponly') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.6
|
||||
// If the attribute-name case-insensitively matches the string
|
||||
// "HttpOnly", the user agent MUST append an attribute to the cookie-
|
||||
// attribute-list with an attribute-name of HttpOnly and an empty
|
||||
// attribute-value.
|
||||
|
||||
cookieAttributeList.httpOnly = true
|
||||
} else if (attributeNameLowercase === 'samesite') {
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7
|
||||
// If the attribute-name case-insensitively matches the string
|
||||
// "SameSite", the user agent MUST process the cookie-av as follows:
|
||||
|
||||
// 1. Let enforcement be "Default".
|
||||
let enforcement = 'Default'
|
||||
|
||||
const attributeValueLowercase = attributeValue.toLowerCase()
|
||||
// 2. If cookie-av's attribute-value is a case-insensitive match for
|
||||
// "None", set enforcement to "None".
|
||||
if (attributeValueLowercase.includes('none')) {
|
||||
enforcement = 'None'
|
||||
}
|
||||
|
||||
// 3. If cookie-av's attribute-value is a case-insensitive match for
|
||||
// "Strict", set enforcement to "Strict".
|
||||
if (attributeValueLowercase.includes('strict')) {
|
||||
enforcement = 'Strict'
|
||||
}
|
||||
|
||||
// 4. If cookie-av's attribute-value is a case-insensitive match for
|
||||
// "Lax", set enforcement to "Lax".
|
||||
if (attributeValueLowercase.includes('lax')) {
|
||||
enforcement = 'Lax'
|
||||
}
|
||||
|
||||
// 5. Append an attribute to the cookie-attribute-list with an
|
||||
// attribute-name of "SameSite" and an attribute-value of
|
||||
// enforcement.
|
||||
cookieAttributeList.sameSite = enforcement
|
||||
} else {
|
||||
cookieAttributeList.unparsed ??= []
|
||||
|
||||
cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`)
|
||||
}
|
||||
|
||||
// 8. Return to Step 1 of this algorithm.
|
||||
return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseSetCookie,
|
||||
parseUnparsedAttributes
|
||||
}
|
307
node_modules/undici/lib/web/cookies/util.js
generated
vendored
Normal file
307
node_modules/undici/lib/web/cookies/util.js
generated
vendored
Normal file
|
@ -0,0 +1,307 @@
|
|||
'use strict'
|
||||
|
||||
const assert = require('node:assert')
|
||||
const { kHeadersList } = require('../../core/symbols')
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isCTLExcludingHtab (value) {
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
const code = value.charCodeAt(i)
|
||||
|
||||
if (
|
||||
(code >= 0x00 && code <= 0x08) ||
|
||||
(code >= 0x0A && code <= 0x1F) ||
|
||||
code === 0x7F
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
token = 1*<any CHAR except CTLs or separators>
|
||||
separators = "(" | ")" | "<" | ">" | "@"
|
||||
| "," | ";" | ":" | "\" | <">
|
||||
| "/" | "[" | "]" | "?" | "="
|
||||
| "{" | "}" | SP | HT
|
||||
* @param {string} name
|
||||
*/
|
||||
function validateCookieName (name) {
|
||||
for (let i = 0; i < name.length; ++i) {
|
||||
const code = name.charCodeAt(i)
|
||||
|
||||
if (
|
||||
code < 0x21 || // exclude CTLs (0-31), SP and HT
|
||||
code > 0x7E || // exclude non-ascii and DEL
|
||||
code === 0x22 || // "
|
||||
code === 0x28 || // (
|
||||
code === 0x29 || // )
|
||||
code === 0x3C || // <
|
||||
code === 0x3E || // >
|
||||
code === 0x40 || // @
|
||||
code === 0x2C || // ,
|
||||
code === 0x3B || // ;
|
||||
code === 0x3A || // :
|
||||
code === 0x5C || // \
|
||||
code === 0x2F || // /
|
||||
code === 0x5B || // [
|
||||
code === 0x5D || // ]
|
||||
code === 0x3F || // ?
|
||||
code === 0x3D || // =
|
||||
code === 0x7B || // {
|
||||
code === 0x7D // }
|
||||
) {
|
||||
throw new Error('Invalid cookie name')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
|
||||
cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
|
||||
; US-ASCII characters excluding CTLs,
|
||||
; whitespace DQUOTE, comma, semicolon,
|
||||
; and backslash
|
||||
* @param {string} value
|
||||
*/
|
||||
function validateCookieValue (value) {
|
||||
let len = value.length
|
||||
let i = 0
|
||||
|
||||
// if the value is wrapped in DQUOTE
|
||||
if (value[0] === '"') {
|
||||
if (len === 1 || value[len - 1] !== '"') {
|
||||
throw new Error('Invalid cookie value')
|
||||
}
|
||||
--len
|
||||
++i
|
||||
}
|
||||
|
||||
while (i < len) {
|
||||
const code = value.charCodeAt(i++)
|
||||
|
||||
if (
|
||||
code < 0x21 || // exclude CTLs (0-31)
|
||||
code > 0x7E || // non-ascii and DEL (127)
|
||||
code === 0x22 || // "
|
||||
code === 0x2C || // ,
|
||||
code === 0x3B || // ;
|
||||
code === 0x5C // \
|
||||
) {
|
||||
throw new Error('Invalid cookie value')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* path-value = <any CHAR except CTLs or ";">
|
||||
* @param {string} path
|
||||
*/
|
||||
function validateCookiePath (path) {
|
||||
for (let i = 0; i < path.length; ++i) {
|
||||
const code = path.charCodeAt(i)
|
||||
|
||||
if (
|
||||
code < 0x20 || // exclude CTLs (0-31)
|
||||
code === 0x7F || // DEL
|
||||
code === 0x3B // ;
|
||||
) {
|
||||
throw new Error('Invalid cookie path')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I have no idea why these values aren't allowed to be honest,
|
||||
* but Deno tests these. - Khafra
|
||||
* @param {string} domain
|
||||
*/
|
||||
function validateCookieDomain (domain) {
|
||||
if (
|
||||
domain.startsWith('-') ||
|
||||
domain.endsWith('.') ||
|
||||
domain.endsWith('-')
|
||||
) {
|
||||
throw new Error('Invalid cookie domain')
|
||||
}
|
||||
}
|
||||
|
||||
const IMFDays = [
|
||||
'Sun', 'Mon', 'Tue', 'Wed',
|
||||
'Thu', 'Fri', 'Sat'
|
||||
]
|
||||
|
||||
const IMFMonths = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
||||
]
|
||||
|
||||
const IMFPaddedNumbers = Array(61).fill(0).map((_, i) => i.toString().padStart(2, '0'))
|
||||
|
||||
/**
|
||||
* @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1
|
||||
* @param {number|Date} date
|
||||
IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT
|
||||
; fixed length/zone/capitalization subset of the format
|
||||
; see Section 3.3 of [RFC5322]
|
||||
|
||||
day-name = %x4D.6F.6E ; "Mon", case-sensitive
|
||||
/ %x54.75.65 ; "Tue", case-sensitive
|
||||
/ %x57.65.64 ; "Wed", case-sensitive
|
||||
/ %x54.68.75 ; "Thu", case-sensitive
|
||||
/ %x46.72.69 ; "Fri", case-sensitive
|
||||
/ %x53.61.74 ; "Sat", case-sensitive
|
||||
/ %x53.75.6E ; "Sun", case-sensitive
|
||||
date1 = day SP month SP year
|
||||
; e.g., 02 Jun 1982
|
||||
|
||||
day = 2DIGIT
|
||||
month = %x4A.61.6E ; "Jan", case-sensitive
|
||||
/ %x46.65.62 ; "Feb", case-sensitive
|
||||
/ %x4D.61.72 ; "Mar", case-sensitive
|
||||
/ %x41.70.72 ; "Apr", case-sensitive
|
||||
/ %x4D.61.79 ; "May", case-sensitive
|
||||
/ %x4A.75.6E ; "Jun", case-sensitive
|
||||
/ %x4A.75.6C ; "Jul", case-sensitive
|
||||
/ %x41.75.67 ; "Aug", case-sensitive
|
||||
/ %x53.65.70 ; "Sep", case-sensitive
|
||||
/ %x4F.63.74 ; "Oct", case-sensitive
|
||||
/ %x4E.6F.76 ; "Nov", case-sensitive
|
||||
/ %x44.65.63 ; "Dec", case-sensitive
|
||||
year = 4DIGIT
|
||||
|
||||
GMT = %x47.4D.54 ; "GMT", case-sensitive
|
||||
|
||||
time-of-day = hour ":" minute ":" second
|
||||
; 00:00:00 - 23:59:60 (leap second)
|
||||
|
||||
hour = 2DIGIT
|
||||
minute = 2DIGIT
|
||||
second = 2DIGIT
|
||||
*/
|
||||
function toIMFDate (date) {
|
||||
if (typeof date === 'number') {
|
||||
date = new Date(date)
|
||||
}
|
||||
|
||||
return `${IMFDays[date.getUTCDay()]}, ${IMFPaddedNumbers[date.getUTCDate()]} ${IMFMonths[date.getUTCMonth()]} ${date.getUTCFullYear()} ${IMFPaddedNumbers[date.getUTCHours()]}:${IMFPaddedNumbers[date.getUTCMinutes()]}:${IMFPaddedNumbers[date.getUTCSeconds()]} GMT`
|
||||
}
|
||||
|
||||
/**
|
||||
max-age-av = "Max-Age=" non-zero-digit *DIGIT
|
||||
; In practice, both expires-av and max-age-av
|
||||
; are limited to dates representable by the
|
||||
; user agent.
|
||||
* @param {number} maxAge
|
||||
*/
|
||||
function validateCookieMaxAge (maxAge) {
|
||||
if (maxAge < 0) {
|
||||
throw new Error('Invalid cookie max-age')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1
|
||||
* @param {import('./index').Cookie} cookie
|
||||
*/
|
||||
function stringify (cookie) {
|
||||
if (cookie.name.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
validateCookieName(cookie.name)
|
||||
validateCookieValue(cookie.value)
|
||||
|
||||
const out = [`${cookie.name}=${cookie.value}`]
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.2
|
||||
if (cookie.name.startsWith('__Secure-')) {
|
||||
cookie.secure = true
|
||||
}
|
||||
|
||||
if (cookie.name.startsWith('__Host-')) {
|
||||
cookie.secure = true
|
||||
cookie.domain = null
|
||||
cookie.path = '/'
|
||||
}
|
||||
|
||||
if (cookie.secure) {
|
||||
out.push('Secure')
|
||||
}
|
||||
|
||||
if (cookie.httpOnly) {
|
||||
out.push('HttpOnly')
|
||||
}
|
||||
|
||||
if (typeof cookie.maxAge === 'number') {
|
||||
validateCookieMaxAge(cookie.maxAge)
|
||||
out.push(`Max-Age=${cookie.maxAge}`)
|
||||
}
|
||||
|
||||
if (cookie.domain) {
|
||||
validateCookieDomain(cookie.domain)
|
||||
out.push(`Domain=${cookie.domain}`)
|
||||
}
|
||||
|
||||
if (cookie.path) {
|
||||
validateCookiePath(cookie.path)
|
||||
out.push(`Path=${cookie.path}`)
|
||||
}
|
||||
|
||||
if (cookie.expires && cookie.expires.toString() !== 'Invalid Date') {
|
||||
out.push(`Expires=${toIMFDate(cookie.expires)}`)
|
||||
}
|
||||
|
||||
if (cookie.sameSite) {
|
||||
out.push(`SameSite=${cookie.sameSite}`)
|
||||
}
|
||||
|
||||
for (const part of cookie.unparsed) {
|
||||
if (!part.includes('=')) {
|
||||
throw new Error('Invalid unparsed')
|
||||
}
|
||||
|
||||
const [key, ...value] = part.split('=')
|
||||
|
||||
out.push(`${key.trim()}=${value.join('=')}`)
|
||||
}
|
||||
|
||||
return out.join('; ')
|
||||
}
|
||||
|
||||
let kHeadersListNode
|
||||
|
||||
function getHeadersList (headers) {
|
||||
if (headers[kHeadersList]) {
|
||||
return headers[kHeadersList]
|
||||
}
|
||||
|
||||
if (!kHeadersListNode) {
|
||||
kHeadersListNode = Object.getOwnPropertySymbols(headers).find(
|
||||
(symbol) => symbol.description === 'headers list'
|
||||
)
|
||||
|
||||
assert(kHeadersListNode, 'Headers cannot be parsed')
|
||||
}
|
||||
|
||||
const headersList = headers[kHeadersListNode]
|
||||
assert(headersList)
|
||||
|
||||
return headersList
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isCTLExcludingHtab,
|
||||
validateCookieName,
|
||||
validateCookiePath,
|
||||
validateCookieValue,
|
||||
toIMFDate,
|
||||
stringify,
|
||||
getHeadersList
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue