@worker-tools/request-cookie-store
Version:
An implementation of the Cookie Store API for request handlers.
97 lines • 4.17 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseCookieHeader = exports.setCookie = exports.attrsToSetCookie = void 0;
const attrsToSetCookie = (attrs) => attrs.map(att => att.join('=')).join('; ');
exports.attrsToSetCookie = attrsToSetCookie;
/**
* RegExp to match field-content in RFC 7230 sec 3.2
*
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
* obs-text = %x80-FF
*/
const RE_FIELD_CONTENT = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
/**
* Implements <https://wicg.github.io/cookie-store/#set-a-cookie>
* with some additional behaviors taken from Chrome's implementation.
*/
function setCookie(options, value, origin, encode = (x) => { var _a; return (_a = x === null || x === void 0 ? void 0 : x.toString()) !== null && _a !== void 0 ? _a : ''; }) {
const [name, val] = (typeof options === 'string'
? [options, value]
: [options.name, options.value]).map(encode);
const opts = typeof options === 'string' ? {} : options;
if (name == null || val == null)
throw TypeError("required value(s) missing");
if (!name.length && val.includes('='))
throw TypeError("Cookie value cannot contain '=' if the name is empty");
if (!name.length && !val.length)
throw TypeError("Cookie name and value both cannot be empty");
// Unspecified, emulating Chrome's current behavior
if (!RE_FIELD_CONTENT.test(name + val) || name.includes('=') || val.includes(';'))
return null;
if (val.includes(', ')) {
throw TypeError("The cookie value must not contain sequence: ', '.");
}
const attrs = [[name, val]];
const { domain, path, sameSite } = opts;
if (domain) {
// Unspecified, emulating Chrome's current behavior
if (!RE_FIELD_CONTENT.test(domain) || domain.includes(';'))
return null;
if (domain.startsWith('.'))
throw TypeError('Cookie domain cannot start with "."');
const host = origin === null || origin === void 0 ? void 0 : origin.host;
if (host && domain !== host && !domain.endsWith(`.${host}`))
throw TypeError('Cookie domain must match current host');
attrs.push(['Domain', domain]);
}
let expires = null;
if (opts.expires) {
expires = opts.expires instanceof Date
? opts.expires
: new Date(opts.expires);
attrs.push(['Expires', expires.toUTCString()]);
}
if (path) {
if (!path.toString().startsWith('/'))
throw TypeError('Cookie path must start with "/"');
// Unspecified, emulating Chrome's current behavior
if (!RE_FIELD_CONTENT.test(path) || path.includes(';'))
return null;
attrs.push(['Path', path]);
}
// Always secure, except for localhost
if (origin && origin.hostname !== 'localhost')
attrs.push(['Secure']);
if (opts.httpOnly)
attrs.push(['HttpOnly']);
switch (sameSite) {
case undefined: break;
case 'none':
attrs.push(['SameSite', 'None']);
break;
case 'lax':
attrs.push(['SameSite', 'Lax']);
break;
case 'strict':
attrs.push(['SameSite', 'Strict']);
break;
default: throw TypeError(`The provided value '${sameSite}' is not a valid enum value of type CookieSameSite.`);
}
return [attrs, expires];
}
exports.setCookie = setCookie;
/**
* A not-so-strict parser for cookie headers.
* - Allows pretty much everything in the value, including `=`
* - Trims keys and values
* - Ignores when both name and value are empty (but either empty allowed)
*
* For more on the state of allowed cookie characters,
* see <https://stackoverflow.com/a/1969339/870615>.
*/
function parseCookieHeader(cookie) {
return new Map(cookie === null || cookie === void 0 ? void 0 : cookie.split(/;\s+/).map(x => x.split('=')).map(([n, ...vs]) => [n.trim(), vs.join('=').trim()]).filter(([n, v]) => !(n === '' && v === '')));
}
exports.parseCookieHeader = parseCookieHeader;
//# sourceMappingURL=set-cookie.js.map
;