kleinigkeiten

This commit is contained in:
2026-08-09 21:14:15 +02:00
parent 3b1828291a
commit fe52b3cf5c
7530 changed files with 758084 additions and 5 deletions
+265
View File
@@ -0,0 +1,265 @@
/**
* Parse options.
*/
interface CookieParseOptions {
/**
* Specifies a function that will be used to decode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1).
* Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to decode
* a previously-encoded cookie value into a JavaScript string.
*
* The default function is the global `decodeURIComponent`, wrapped in a `try..catch`. If an error
* is thrown it will return the cookie's original value. If you provide your own encode/decode
* scheme you must ensure errors are appropriately handled.
*
* @default decode
*/
decode?: (str: string) => string | undefined;
/**
* Custom function to filter parsing specific keys.
*/
filter?(key: string): boolean;
/**
* When enabled, duplicate cookie names will return an array of values
* instead of only the first value.
*/
allowMultiple?: boolean;
}
/**
* Cookies object.
*/
type Cookies = Record<string, string | undefined>;
/**
* Cookies object when `allowMultiple` is enabled.
*/
type MultiCookies = Record<string, string | string[] | undefined>;
/**
* Stringify options.
*/
interface CookieStringifyOptions {
/**
* Specifies a function that will be used to encode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1).
* Since value of a cookie has a limited character set (and must be a simple string), this function can be used to encode
* a value into a string suited for a cookie's value, and should mirror `decode` when parsing.
*
* @default encodeURIComponent
*/
encode?: (str: string) => string;
/**
* Specifies a function that will be used to coerce non-string values to a string.
*
* @default JSON.stringify
*/
stringify?: (value: unknown) => string;
}
/**
* Set-Cookie object.
*/
interface SetCookie {
/**
* Specifies the name of the cookie.
*/
name: string;
/**
* Specifies the string to be the value for the cookie.
*/
value: string | undefined;
/**
* Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.2).
*
* The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and
* `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this,
* so if both are set, they should point to the same date and time.
*/
maxAge?: number;
/**
* Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1).
* When no expiration is set, clients consider this a "non-persistent cookie" and delete it when the current session is over.
*
* The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and
* `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this,
* so if both are set, they should point to the same date and time.
*/
expires?: Date;
/**
* Specifies the value for the [`Domain` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3).
* When no domain is set, clients consider the cookie to apply to the current domain only.
*/
domain?: string;
/**
* Specifies the value for the [`Path` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4).
* When no path is set, the path is considered the ["default path"](https://tools.ietf.org/html/rfc6265#section-5.1.4).
*/
path?: string;
/**
* Enables the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6).
* When enabled, clients will not allow client-side JavaScript to see the cookie in `document.cookie`.
*/
httpOnly?: boolean;
/**
* Enables the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5).
* When enabled, clients will only send the cookie back if the browser has an HTTPS connection.
*/
secure?: boolean;
/**
* Enables the [`Partitioned` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-cutler-httpbis-partitioned-cookies/).
* When enabled, clients will only send the cookie back when the current domain _and_ top-level domain matches.
*
* This is an attribute that has not yet been fully standardized, and may change in the future.
* This also means clients may ignore this attribute until they understand it. More information
* about can be found in [the proposal](https://github.com/privacycg/CHIPS).
*/
partitioned?: boolean;
/**
* Specifies the value for the [`Priority` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1).
*
* - `'low'` will set the `Priority` attribute to `Low`.
* - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set.
* - `'high'` will set the `Priority` attribute to `High`.
*
* More information about priority levels can be found in [the specification](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1).
*/
priority?: "low" | "medium" | "high";
/**
* Specifies the value for the [`SameSite` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7).
*
* - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
* - `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
* - `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
* - `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
*
* More information about enforcement levels can be found in [the specification](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7).
*/
sameSite?: boolean | "lax" | "strict" | "none";
}
/**
* Backward compatibility serialize options.
*/
type CookieSerializeOptions = CookieStringifyOptions & Omit<SetCookie, "name" | "value">;
/**
* Parse a `Cookie` header string into an object.
*
* The object has cookie names as keys and decoded values as values.
* First occurrence wins for duplicate names unless `allowMultiple` is set.
*
* @param str - The `Cookie` header string to parse.
* @param options - Parsing options (`decode`, `filter`, `allowMultiple`).
* @returns A prototype-less object of cookie name-value pairs.
*/
declare function parse(str: string, options: CookieParseOptions & {
allowMultiple: true;
}): MultiCookies;
declare function parse(str: string, options?: CookieParseOptions): Cookies;
/**
* Stringify a cookies object into an HTTP `Cookie` header string.
*
* @param cookie - An object of cookie name-value pairs.
* @param options - Stringify options (`encode`).
* @returns A `Cookie` header string (e.g. `"foo=bar; baz=qux"`).
*/
declare function stringifyCookie(cookie: Cookies, options?: CookieStringifyOptions): string;
/**
* Serialize a cookie into a `Set-Cookie` header string.
*
* Accepts either a name-value pair with options or a `SetCookie` object.
* Non-string values are coerced to strings. Validates name, value, domain,
* and path against RFC 6265bis.
*
* @example
* ```js
* serialize("foo", "bar", { httpOnly: true });
* // => "foo=bar; HttpOnly"
*
* serialize({ name: "foo", value: "bar", secure: true });
* // => "foo=bar; Secure"
* ```
*/
declare function serialize(cookie: SetCookie, options?: CookieStringifyOptions): string;
declare function serialize(name: string, val: unknown, options?: CookieSerializeOptions): string;
interface SetCookieParseOptions {
/**
* Custom decode function to use on cookie values.
*
* By default, `decodeURIComponent` is used.
*
* **Note:** If decoding fails, the original (undecoded) value will be used
*/
decode?: false | ((value: string) => string);
}
interface SetCookie$1 {
/**
* Cookie name
*/
name: string;
/**
* Cookie value
*/
value: string;
/**
* Cookie path
*/
path?: string | undefined;
/**
* Absolute expiration date for the cookie
*/
expires?: Date | undefined;
/**
* Relative max age of the cookie in seconds from when the client receives it (integer or undefined)
*
* Note: when using with express's res.cookie() method, multiply maxAge by 1000 to convert to milliseconds
*/
maxAge?: number | undefined;
/**
* Domain for the cookie,
* May begin with "." to indicate the named domain or any subdomain of it
*/
domain?: string | undefined;
/**
* Indicates that this cookie should only be sent over HTTPs
*/
secure?: boolean | undefined;
/**
* Indicates that this cookie should not be accessible to client-side JavaScript
*/
httpOnly?: boolean | undefined;
/**
* Indicates a cookie ought not to be sent along with cross-site requests
*/
sameSite?: true | false | "lax" | "strict" | "none" | undefined;
/**
* Indicates that the cookie should be stored using partitioned storage
*
* See https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies
*/
partitioned?: boolean | undefined;
/**
* Indicates the priority of the cookie
*
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#prioritylow_medium_high
*/
priority?: "low" | "medium" | "high" | undefined;
[key: string]: unknown;
}
/**
* Parse a [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) header string into an object.
*
* Returns `undefined` for cookies with forbidden names (prototype pollution protection)
* or when both name and value are empty (RFC 6265bis sec 5.7).
*
* @param str - The `Set-Cookie` header string to parse.
* @param options - Parsing options (`decode`).
* @returns A `SetCookie` object with all parsed attributes, or `undefined`.
*/
declare function parseSetCookie(str: string, options?: SetCookieParseOptions): SetCookie$1 | undefined;
/**
* Split comma-joined `Set-Cookie` header strings into individual cookie strings.
*
* Correctly handles commas within cookie attributes like `Expires` dates
* by checking for `=` after a comma to determine if it's a cookie separator.
*
* @param cookiesString - A comma-joined `Set-Cookie` string or array of strings.
* @returns An array of individual `Set-Cookie` strings.
*
* @see https://tools.ietf.org/html/rfc2616#section-4.2
*/
declare function splitSetCookieString(cookiesString: string | string[]): string[];
export { type CookieParseOptions, type CookieSerializeOptions, type CookieStringifyOptions, type Cookies, type MultiCookies, type SetCookie, type SetCookieParseOptions, parse, parse as parseCookie, parseSetCookie, serialize, serialize as serializeCookie, splitSetCookieString, stringifyCookie };
+311
View File
@@ -0,0 +1,311 @@
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 };