1'use strict' 2 3const { parseSetCookie } = require('./parse') 4const { stringify, getHeadersList } = require('./util') 5const { webidl } = require('../fetch/webidl') 6const { Headers } = require('../fetch/headers') 7 8/** 9 * @typedef {Object} Cookie 10 * @property {string} name 11 * @property {string} value 12 * @property {Date|number|undefined} expires 13 * @property {number|undefined} maxAge 14 * @property {string|undefined} domain 15 * @property {string|undefined} path 16 * @property {boolean|undefined} secure 17 * @property {boolean|undefined} httpOnly 18 * @property {'Strict'|'Lax'|'None'} sameSite 19 * @property {string[]} unparsed 20 */ 21 22/** 23 * @param {Headers} headers 24 * @returns {Record<string, string>} 25 */ 26function getCookies (headers) { 27 webidl.argumentLengthCheck(arguments, 1, { header: 'getCookies' }) 28 29 webidl.brandCheck(headers, Headers, { strict: false }) 30 31 const cookie = headers.get('cookie') 32 const out = {} 33 34 if (!cookie) { 35 return out 36 } 37 38 for (const piece of cookie.split(';')) { 39 const [name, ...value] = piece.split('=') 40 41 out[name.trim()] = value.join('=') 42 } 43 44 return out 45} 46 47/** 48 * @param {Headers} headers 49 * @param {string} name 50 * @param {{ path?: string, domain?: string }|undefined} attributes 51 * @returns {void} 52 */ 53function deleteCookie (headers, name, attributes) { 54 webidl.argumentLengthCheck(arguments, 2, { header: 'deleteCookie' }) 55 56 webidl.brandCheck(headers, Headers, { strict: false }) 57 58 name = webidl.converters.DOMString(name) 59 attributes = webidl.converters.DeleteCookieAttributes(attributes) 60 61 // Matches behavior of 62 // https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278 63 setCookie(headers, { 64 name, 65 value: '', 66 expires: new Date(0), 67 ...attributes 68 }) 69} 70 71/** 72 * @param {Headers} headers 73 * @returns {Cookie[]} 74 */ 75function getSetCookies (headers) { 76 webidl.argumentLengthCheck(arguments, 1, { header: 'getSetCookies' }) 77 78 webidl.brandCheck(headers, Headers, { strict: false }) 79 80 const cookies = getHeadersList(headers).cookies 81 82 if (!cookies) { 83 return [] 84 } 85 86 // In older versions of undici, cookies is a list of name:value. 87 return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair)) 88} 89 90/** 91 * @param {Headers} headers 92 * @param {Cookie} cookie 93 * @returns {void} 94 */ 95function setCookie (headers, cookie) { 96 webidl.argumentLengthCheck(arguments, 2, { header: 'setCookie' }) 97 98 webidl.brandCheck(headers, Headers, { strict: false }) 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 109webidl.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 122webidl.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 179module.exports = { 180 getCookies, 181 deleteCookie, 182 getSetCookies, 183 setCookie 184} 185