Skip to content

Commit

Permalink
feat: add cookie parsing ability (nodejs#1848)
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev authored and anonrig committed Apr 4, 2023
1 parent 8db8ebb commit 721cbb5
Show file tree
Hide file tree
Showing 10 changed files with 1,450 additions and 1 deletion.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import mockErrors from'./types/mock-errors'
import ProxyAgent from'./types/proxy-agent'
import { request, pipeline, stream, connect, upgrade } from './types/api'

export * from './types/cookies'
export * from './types/fetch'
export * from './types/file'
export * from './types/filereader'
Expand Down
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ if (nodeMajor >= 18) {
const { WebSocket } = require('./lib/websocket/websocket')

module.exports.WebSocket = WebSocket

const { deleteCookie, getCookies, getSetCookies, setCookie } = require('./lib/cookies')

module.exports.deleteCookie = deleteCookie
module.exports.getCookies = getCookies
module.exports.getSetCookies = getSetCookies
module.exports.setCookie = setCookie
}

module.exports.request = makeDispatcher(api.request)
Expand Down
12 changes: 12 additions & 0 deletions lib/cookies/constants.js
Original file line number Diff line number Diff line change
@@ -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 changes: 184 additions & 0 deletions lib/cookies/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
'use strict'

const { parseSetCookie } = require('./parse')
const { stringify } = require('./util')
const { webidl } = require('../fetch/webidl')
const { Headers } = require('../fetch/headers')
const { kHeadersList } = require('../core/symbols')

/**
* @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)

const cookie = headers[kHeadersList].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)

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)

const cookies = headers[kHeadersList].cookies

if (!cookies) {
return []
}

return cookies.map((pair) => parseSetCookie(pair[1]))
}

/**
* @param {Headers} headers
* @param {Cookie} cookie
* @returns {void}
*/
function setCookie (headers, cookie) {
webidl.argumentLengthCheck(arguments, 2, { header: 'setCookie' })

webidl.brandCheck(headers, Headers)

cookie = webidl.converters.Cookie(cookie)

const str = stringify(cookie)

if (str) {
headers.append('Set-Cookie', stringify(cookie))
}
}

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
}
Loading

0 comments on commit 721cbb5

Please sign in to comment.