UNPKG

cookie-es

Version:

<!-- automd:badges bundlejs packagephobia codecov -->

312 lines (311 loc) 10.7 kB
const COOKIE_MAX_AGE_LIMIT = 3456e4; function endIndex(str, min, len) { const index = str.indexOf(";", min); return index === -1 ? len : index; } function eqIndex(str, min, max) { const index = str.indexOf("=", min); return index < max ? index : -1; } function valueSlice(str, min, max) { if (min === max) return ""; let start = min; let end = max; do { const code = str.charCodeAt(start); if (code !== 32 && code !== 9) break; } while (++start < end); while (end > start) { const code = str.charCodeAt(end - 1); if (code !== 32 && code !== 9) break; end--; } return str.slice(start, end); } const NullObject = /* @__PURE__ */ (() => { const C = function() {}; C.prototype = Object.create(null); return C; })(); function parse(str, options) { const obj = new NullObject(); const len = str.length; if (len < 2) return obj; const dec = options?.decode || decode; const allowMultiple = options?.allowMultiple || false; let index = 0; do { const eqIdx = eqIndex(str, index, len); if (eqIdx === -1) break; const endIdx = endIndex(str, index, len); if (eqIdx > endIdx) { index = str.lastIndexOf(";", eqIdx - 1) + 1; continue; } const key = valueSlice(str, index, eqIdx); if (options?.filter && !options.filter(key)) { index = endIdx + 1; continue; } const val = dec(valueSlice(str, eqIdx + 1, endIdx)); if (allowMultiple) { const existing = obj[key]; if (existing === void 0) obj[key] = val; else if (Array.isArray(existing)) existing.push(val); else obj[key] = [existing, val]; } else if (obj[key] === void 0) obj[key] = val; index = endIdx + 1; } while (index < len); return obj; } function decode(str) { if (!str.includes("%")) return str; try { return decodeURIComponent(str); } catch { return str; } } const cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/; const cookieValueRegExp = /^[\u0021-\u003A\u003C-\u007E]*$/; 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; const pathValueRegExp = /^[\u0020-\u003A\u003C-\u007E]*$/; const __toString = Object.prototype.toString; function stringifyCookie(cookie, options) { const enc = options?.encode || encodeURIComponent; const keys = Object.keys(cookie); let str = ""; for (const [i, name] of keys.entries()) { const val = cookie[name]; if (val === void 0) continue; if (!cookieNameRegExp.test(name)) throw new TypeError(`cookie name is invalid: ${name}`); const value = enc(val); if (!cookieValueRegExp.test(value)) throw new TypeError(`cookie val is invalid: ${val}`); if (i > 0) str += "; "; str += name + "=" + value; } return str; } function serialize(_a0, _a1, _a2) { const isObj = typeof _a0 === "object" && _a0 !== null; const options = isObj ? _a1 : _a2; const stringify = options?.stringify || JSON.stringify; const cookie = isObj ? _a0 : { ..._a2, name: _a0, value: _a1 == void 0 ? "" : typeof _a1 === "string" ? _a1 : stringify(_a1) }; const enc = options?.encode || encodeURIComponent; if (!cookieNameRegExp.test(cookie.name)) throw new TypeError(`argument name is invalid: ${cookie.name}`); const value = cookie.value ? enc(cookie.value) : ""; if (!cookieValueRegExp.test(value)) throw new TypeError(`argument val is invalid: ${cookie.value}`); if (!cookie.secure) { if (cookie.partitioned) throw new TypeError(`Partitioned cookies must have the Secure attribute`); if (cookie.sameSite && String(cookie.sameSite).toLowerCase() === "none") throw new TypeError(`SameSite=None cookies must have the Secure attribute`); if (cookie.name.length > 9 && cookie.name.charCodeAt(0) === 95 && cookie.name.charCodeAt(1) === 95) { const nameLower = cookie.name.toLowerCase(); if (nameLower.startsWith("__secure-") || nameLower.startsWith("__host-")) throw new TypeError(`${cookie.name} cookies must have the Secure attribute`); } } if (cookie.name.length > 7 && cookie.name.charCodeAt(0) === 95 && cookie.name.charCodeAt(1) === 95 && cookie.name.toLowerCase().startsWith("__host-")) { if (cookie.path !== "/") throw new TypeError(`__Host- cookies must have Path=/`); if (cookie.domain) throw new TypeError(`__Host- cookies must not have a Domain attribute`); } let str = cookie.name + "=" + value; if (cookie.maxAge !== void 0) { if (!Number.isInteger(cookie.maxAge)) throw new TypeError(`option maxAge is invalid: ${cookie.maxAge}`); str += "; Max-Age=" + Math.max(0, Math.min(cookie.maxAge, COOKIE_MAX_AGE_LIMIT)); } if (cookie.domain) { if (!domainValueRegExp.test(cookie.domain)) throw new TypeError(`option domain is invalid: ${cookie.domain}`); str += "; Domain=" + cookie.domain; } if (cookie.path) { if (!pathValueRegExp.test(cookie.path)) throw new TypeError(`option path is invalid: ${cookie.path}`); str += "; Path=" + cookie.path; } if (cookie.expires) { if (!isDate(cookie.expires) || !Number.isFinite(cookie.expires.valueOf())) throw new TypeError(`option expires is invalid: ${cookie.expires}`); str += "; Expires=" + cookie.expires.toUTCString(); } if (cookie.httpOnly) str += "; HttpOnly"; if (cookie.secure) str += "; Secure"; if (cookie.partitioned) str += "; Partitioned"; if (cookie.priority) switch (typeof cookie.priority === "string" ? cookie.priority.toLowerCase() : void 0) { 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: ${cookie.priority}`); } if (cookie.sameSite) switch (typeof cookie.sameSite === "string" ? cookie.sameSite.toLowerCase() : cookie.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: ${cookie.sameSite}`); } return str; } function isDate(val) { return __toString.call(val) === "[object Date]"; } const maxAgeRegExp = /^-?\d+$/; const _nullProto = /* @__PURE__ */ Object.getPrototypeOf({}); function parseSetCookie(str, options) { const len = str.length; let _endIdx = len; let eqIdx = -1; for (let i = 0; i < len; i++) { const c = str.charCodeAt(i); if (c === 59) { _endIdx = i; break; } if (c === 61 && eqIdx === -1) eqIdx = i; } if (eqIdx >= _endIdx) eqIdx = -1; const name = eqIdx === -1 ? "" : _trim(str, 0, eqIdx); if (name && name in _nullProto) return void 0; let value = eqIdx === -1 ? _trim(str, 0, _endIdx) : _trim(str, eqIdx + 1, _endIdx); if (!name && !value) return void 0; if (name.length + value.length > 4096) return void 0; if (options?.decode !== false) value = _decode(value, options?.decode); const setCookie = { name, value }; let index = _endIdx + 1; while (index < len) { let endIdx = len; let attrEqIdx = -1; for (let i = index; i < len; i++) { const c = str.charCodeAt(i); if (c === 59) { endIdx = i; break; } if (c === 61 && attrEqIdx === -1) attrEqIdx = i; } if (attrEqIdx >= endIdx) attrEqIdx = -1; const attr = attrEqIdx === -1 ? _trim(str, index, endIdx) : _trim(str, index, attrEqIdx); const val = attrEqIdx === -1 ? void 0 : _trim(str, attrEqIdx + 1, endIdx); if (val === void 0 || val.length <= 1024) switch (attr.toLowerCase()) { case "httponly": setCookie.httpOnly = true; break; case "secure": setCookie.secure = true; break; case "partitioned": setCookie.partitioned = true; break; case "domain": if (val) setCookie.domain = (val.charCodeAt(0) === 46 ? val.slice(1) : val).toLowerCase(); break; case "path": setCookie.path = val; break; case "max-age": if (val && maxAgeRegExp.test(val)) setCookie.maxAge = Math.min(Number(val), COOKIE_MAX_AGE_LIMIT); break; case "expires": { if (!val) break; const date = new Date(val); if (Number.isFinite(date.valueOf())) { const maxDate = new Date(Date.now() + COOKIE_MAX_AGE_LIMIT * 1e3); setCookie.expires = date > maxDate ? maxDate : date; } break; } case "priority": { if (!val) break; const priority = val.toLowerCase(); if (priority === "low" || priority === "medium" || priority === "high") setCookie.priority = priority; break; } case "samesite": { if (!val) break; const sameSite = val.toLowerCase(); if (sameSite === "lax" || sameSite === "strict" || sameSite === "none") setCookie.sameSite = sameSite; else setCookie.sameSite = "lax"; break; } default: { const attrLower = attr.toLowerCase(); if (attrLower && !(attrLower in _nullProto)) setCookie[attrLower] = val; } } index = endIdx + 1; } return setCookie; } function _trim(str, start, end) { if (start === end) return ""; let s = start; let e = end; while (s < e && (str.charCodeAt(s) === 32 || str.charCodeAt(s) === 9)) s++; while (e > s && (str.charCodeAt(e - 1) === 32 || str.charCodeAt(e - 1) === 9)) e--; return str.slice(s, e); } function _decode(value, decode) { if (!decode && !value.includes("%")) return value; try { return (decode || decodeURIComponent)(value); } catch { return value; } } function splitSetCookieString(cookiesString) { if (Array.isArray(cookiesString)) return cookiesString.flatMap((c) => splitSetCookieString(c)); if (typeof cookiesString !== "string") return []; const cookiesStrings = []; let pos = 0; let start; let ch; let lastComma; let nextStart; let cookiesSeparatorFound; const skipWhitespace = () => { while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) pos += 1; return pos < cookiesString.length; }; const notSpecialChar = () => { ch = cookiesString.charAt(pos); return ch !== "=" && ch !== ";" && ch !== ","; }; while (pos < cookiesString.length) { start = pos; cookiesSeparatorFound = false; while (skipWhitespace()) { ch = cookiesString.charAt(pos); if (ch === ",") { lastComma = pos; pos += 1; skipWhitespace(); nextStart = pos; while (pos < cookiesString.length && notSpecialChar()) pos += 1; if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") { cookiesSeparatorFound = true; pos = nextStart; cookiesStrings.push(cookiesString.slice(start, lastComma)); start = pos; } else pos = lastComma + 1; } else pos += 1; } if (!cookiesSeparatorFound || pos >= cookiesString.length) cookiesStrings.push(cookiesString.slice(start)); } return cookiesStrings; } export { parse, parse as parseCookie, parseSetCookie, serialize, serialize as serializeCookie, splitSetCookieString, stringifyCookie };