123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- // "use strict" is not required in ES6 modules as they are always in strict mode
- // Exporting functions and constants as named exports and default export
- /**
- * 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 = (() => {
- 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
- */
- export function parse(input, options) {
- const obj = new NullObject();
- const dec = options?.decode || decode;
- // If input is an array, process each string
- const headers = Array.isArray(input) ? input : [input];
- // console.log(headers);
- for (const str of headers) {
- const len = str.length;
- if (len < 2) continue;
- let index = 0;
- do {
- const eqIdx = str.indexOf("=", index);
- if (eqIdx === -1) break;
- const colonIdx = str.indexOf(";", index);
- const endIdx = colonIdx === -1 ? len : colonIdx;
- if (eqIdx > endIdx) {
- 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); // Keep original case
- let valStartIdx = startIndex(str, eqIdx + 1, endIdx);
- let valEndIdx = endIndex(str, endIdx, valStartIdx);
- const value = dec(str.slice(valStartIdx, valEndIdx));
- // Always overwrite with the latest value for the same key
- 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) 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) 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.
- */
- export 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]";
- }
- export function stringify(obj, encode = 0) {
- return Object.entries(obj)
- .map(([key, value]) => encode ? serialize(key, value) : decodeURIComponent(serialize(key, value)))
- .join("; ")
- }
- // Export as default object for named and default usage
- const COOKIE = {parse, serialize, stringify};
- export default COOKIE;
|