312 lines
10 KiB
JavaScript
312 lines
10 KiB
JavaScript
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 };
|