UNPKG

cookie

Version:

HTTP server cookie parsing and serialization

239 lines 8.34 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parse = parse; exports.serialize = serialize; /** * RegExp to match cookie-name in RFC 6265 sec 4.1.1 * This refers out to the obsoleted definition of token in RFC 2616 sec 2.2 * which has been replaced by the token definition in RFC 7230 appendix B. * * cookie-name = token * token = 1*tchar * tchar = "!" / "#" / "$" / "%" / "&" / "'" / * "*" / "+" / "-" / "." / "^" / "_" / * "`" / "|" / "~" / DIGIT / ALPHA * * Note: Allowing more characters - https://github.com/jshttp/cookie/issues/191 * Allow same range as cookie value, except `=`, which delimits end of name. */ const cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/; /** * RegExp to match cookie-value in RFC 6265 sec 4.1.1 * * 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 * * Allowing more characters: https://github.com/jshttp/cookie/issues/191 * Comma, backslash, and DQUOTE are not part of the parsing algorithm. */ const cookieValueRegExp = /^[\u0021-\u003A\u003C-\u007E]*$/; /** * RegExp to match domain-value in RFC 6265 sec 4.1.1 * * domain-value = <subdomain> * ; defined in [RFC1034], Section 3.5, as * ; enhanced by [RFC1123], Section 2.1 * <subdomain> = <label> | <subdomain> "." <label> * <label> = <let-dig> [ [ <ldh-str> ] <let-dig> ] * Labels must be 63 characters or less. * 'let-dig' not 'letter' in the first char, per RFC1123 * <ldh-str> = <let-dig-hyp> | <let-dig-hyp> <ldh-str> * <let-dig-hyp> = <let-dig> | "-" * <let-dig> = <letter> | <digit> * <letter> = any one of the 52 alphabetic characters A through Z in * upper case and a through z in lower case * <digit> = any one of the ten digits 0 through 9 * * Keep support for leading dot: https://github.com/jshttp/cookie/issues/173 * * > (Note that a leading %x2E ("."), if present, is ignored even though that * character is not permitted, but a trailing %x2E ("."), if present, will * cause the user agent to ignore the attribute.) */ const domainValueRegExp = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i; /** * RegExp to match path-value in RFC 6265 sec 4.1.1 * * path-value = <any CHAR except CTLs or ";"> * CHAR = %x01-7F * ; defined in RFC 5234 appendix B.1 */ const pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/; const __toString = Object.prototype.toString; const NullObject = /* @__PURE__ */ (() => { const C = function () { }; C.prototype = Object.create(null); return C; })(); /** * Parse a cookie header. * * Parse the given cookie header string into an object * The object has the various cookies as keys(names) => values */ function parse(str, options) { const obj = new NullObject(); const len = str.length; // RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='. if (len < 2) return obj; const dec = options?.decode || decode; let index = 0; do { const eqIdx = str.indexOf("=", index); if (eqIdx === -1) break; // No more cookie pairs. const colonIdx = str.indexOf(";", index); const endIdx = colonIdx === -1 ? len : colonIdx; if (eqIdx > endIdx) { // backtrack on prior semicolon index = str.lastIndexOf(";", eqIdx - 1) + 1; continue; } const keyStartIdx = startIndex(str, index, eqIdx); const keyEndIdx = endIndex(str, eqIdx, keyStartIdx); const key = str.slice(keyStartIdx, keyEndIdx); // only assign once if (obj[key] === undefined) { let valStartIdx = startIndex(str, eqIdx + 1, endIdx); let valEndIdx = endIndex(str, endIdx, valStartIdx); const value = dec(str.slice(valStartIdx, valEndIdx)); obj[key] = value; } index = endIdx + 1; } while (index < len); return obj; } function startIndex(str, index, max) { do { const code = str.charCodeAt(index); if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index; } while (++index < max); return max; } function endIndex(str, index, min) { while (index > min) { const code = str.charCodeAt(--index); if (code !== 0x20 /* */ && code !== 0x09 /* \t */) return index + 1; } return min; } /** * Serialize data into a cookie header. * * Serialize a name value pair into a cookie string suitable for * http headers. An optional options object specifies cookie parameters. * * serialize('foo', 'bar', { httpOnly: true }) * => "foo=bar; httpOnly" */ function serialize(name, val, options) { const enc = options?.encode || encodeURIComponent; if (!cookieNameRegExp.test(name)) { throw new TypeError(`argument name is invalid: ${name}`); } const value = enc(val); if (!cookieValueRegExp.test(value)) { throw new TypeError(`argument val is invalid: ${val}`); } let str = name + "=" + value; if (!options) return str; if (options.maxAge !== undefined) { if (!Number.isInteger(options.maxAge)) { throw new TypeError(`option maxAge is invalid: ${options.maxAge}`); } str += "; Max-Age=" + options.maxAge; } if (options.domain) { if (!domainValueRegExp.test(options.domain)) { throw new TypeError(`option domain is invalid: ${options.domain}`); } str += "; Domain=" + options.domain; } if (options.path) { if (!pathValueRegExp.test(options.path)) { throw new TypeError(`option path is invalid: ${options.path}`); } str += "; Path=" + options.path; } if (options.expires) { if (!isDate(options.expires) || !Number.isFinite(options.expires.valueOf())) { throw new TypeError(`option expires is invalid: ${options.expires}`); } str += "; Expires=" + options.expires.toUTCString(); } if (options.httpOnly) { str += "; HttpOnly"; } if (options.secure) { str += "; Secure"; } if (options.partitioned) { str += "; Partitioned"; } if (options.priority) { const priority = typeof options.priority === "string" ? options.priority.toLowerCase() : undefined; switch (priority) { case "low": str += "; Priority=Low"; break; case "medium": str += "; Priority=Medium"; break; case "high": str += "; Priority=High"; break; default: throw new TypeError(`option priority is invalid: ${options.priority}`); } } if (options.sameSite) { const sameSite = typeof options.sameSite === "string" ? options.sameSite.toLowerCase() : options.sameSite; switch (sameSite) { case true: case "strict": str += "; SameSite=Strict"; break; case "lax": str += "; SameSite=Lax"; break; case "none": str += "; SameSite=None"; break; default: throw new TypeError(`option sameSite is invalid: ${options.sameSite}`); } } return str; } /** * URL-decode string value. Optimized to skip native call when no %. */ function decode(str) { if (str.indexOf("%") === -1) return str; try { return decodeURIComponent(str); } catch (e) { return str; } } /** * Determine if value is a Date. */ function isDate(val) { return __toString.call(val) === "[object Date]"; } //# sourceMappingURL=index.js.map