| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- var defaultParseOptions = {
- decodeValues: true,
- map: false,
- silent: false,
- split: "auto", // auto = split strings but not arrays
- };
- function isForbiddenKey(key) {
- return typeof key !== "string" || key in {};
- }
- function createNullObj() {
- return Object.create(null);
- }
- function isNonEmptyString(str) {
- return typeof str === "string" && !!str.trim();
- }
- function parseString(setCookieValue, options) {
- var parts = setCookieValue.split(";").filter(isNonEmptyString);
- var nameValuePairStr = parts.shift();
- var parsed = parseNameValuePair(nameValuePairStr);
- var name = parsed.name;
- var value = parsed.value;
- options = options
- ? Object.assign({}, defaultParseOptions, options)
- : defaultParseOptions;
- if (isForbiddenKey(name)) {
- return null;
- }
- try {
- value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value
- } catch (e) {
- console.error(
- "set-cookie-parser: failed to decode cookie value. Set options.decodeValues=false to disable decoding.",
- e
- );
- }
- var cookie = createNullObj();
- cookie.name = name;
- cookie.value = value;
- parts.forEach(function (part) {
- var sides = part.split("=");
- var key = sides.shift().trimLeft().toLowerCase();
- if (isForbiddenKey(key)) {
- return;
- }
- var value = sides.join("=");
- if (key === "expires") {
- cookie.expires = new Date(value);
- } else if (key === "max-age") {
- var n = parseInt(value, 10);
- if (!Number.isNaN(n)) cookie.maxAge = n;
- } else if (key === "secure") {
- cookie.secure = true;
- } else if (key === "httponly") {
- cookie.httpOnly = true;
- } else if (key === "samesite") {
- cookie.sameSite = value;
- } else if (key === "partitioned") {
- cookie.partitioned = true;
- } else if (key) {
- cookie[key] = value;
- }
- });
- return cookie;
- }
- function parseNameValuePair(nameValuePairStr) {
- // Parses name-value-pair according to rfc6265bis draft
- var name = "";
- var value = "";
- var nameValueArr = nameValuePairStr.split("=");
- if (nameValueArr.length > 1) {
- name = nameValueArr.shift();
- value = nameValueArr.join("="); // everything after the first =, joined by a "=" if there was more than one part
- } else {
- value = nameValuePairStr;
- }
- return { name: name, value: value };
- }
- function parseSetCookie(input, options) {
- options = options
- ? Object.assign({}, defaultParseOptions, options)
- : defaultParseOptions;
- if (!input) {
- if (!options.map) {
- return [];
- } else {
- return createNullObj();
- }
- }
- if (input.headers) {
- if (typeof input.headers.getSetCookie === "function") {
- // for fetch responses - they combine headers of the same type in the headers array,
- // but getSetCookie returns an uncombined array
- input = input.headers.getSetCookie();
- } else if (input.headers["set-cookie"]) {
- // fast-path for node.js (which automatically normalizes header names to lower-case)
- input = input.headers["set-cookie"];
- } else {
- // slow-path for other environments - see #25
- var sch =
- input.headers[
- Object.keys(input.headers).find(function (key) {
- return key.toLowerCase() === "set-cookie";
- })
- ];
- // warn if called on a request-like object with a cookie header rather than a set-cookie header - see #34, 36
- if (!sch && input.headers.cookie && !options.silent) {
- console.warn(
- "Warning: set-cookie-parser appears to have been called on a request object. It is designed to parse Set-Cookie headers from responses, not Cookie headers from requests. Set the option {silent: true} to suppress this warning."
- );
- }
- input = sch;
- }
- }
- var split = options.split;
- var isArray = Array.isArray(input);
- if (split === "auto") {
- split = !isArray;
- }
- if (!isArray) {
- input = [input];
- }
- input = input.filter(isNonEmptyString);
- if (split) {
- input = input.map(splitCookiesString).flat();
- }
- if (!options.map) {
- return input
- .map(function (str) {
- return parseString(str, options);
- })
- .filter(Boolean);
- } else {
- var cookies = createNullObj();
- return input.reduce(function (cookies, str) {
- var cookie = parseString(str, options);
- if (cookie && !isForbiddenKey(cookie.name)) {
- cookies[cookie.name] = cookie;
- }
- return cookies;
- }, cookies);
- }
- }
- /*
- Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
- that are within a single set-cookie field-value, such as in the Expires portion.
- This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
- Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
- React Native's fetch does this for *every* header, including set-cookie.
- Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
- Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
- */
- function splitCookiesString(cookiesString) {
- if (Array.isArray(cookiesString)) {
- return cookiesString;
- }
- if (typeof cookiesString !== "string") {
- return [];
- }
- var cookiesStrings = [];
- var pos = 0;
- var start;
- var ch;
- var lastComma;
- var nextStart;
- var cookiesSeparatorFound;
- function skipWhitespace() {
- while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
- pos += 1;
- }
- return pos < cookiesString.length;
- }
- function 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 === ",") {
- // ',' is a cookie separator if we have later first '=', not ';' or ','
- lastComma = pos;
- pos += 1;
- skipWhitespace();
- nextStart = pos;
- while (pos < cookiesString.length && notSpecialChar()) {
- pos += 1;
- }
- // currently special character
- if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
- // we found cookies separator
- cookiesSeparatorFound = true;
- // pos is inside the next cookie, so back up and return it.
- pos = nextStart;
- cookiesStrings.push(cookiesString.substring(start, lastComma));
- start = pos;
- } else {
- // in param ',' or param separator ';',
- // we continue from that comma
- pos = lastComma + 1;
- }
- } else {
- pos += 1;
- }
- }
- if (!cookiesSeparatorFound || pos >= cookiesString.length) {
- cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
- }
- }
- return cookiesStrings;
- }
- // named export for CJS
- parseSetCookie.parseSetCookie = parseSetCookie;
- // for backwards compatibility
- parseSetCookie.parse = parseSetCookie;
- parseSetCookie.parseString = parseString;
- parseSetCookie.splitCookiesString = splitCookiesString;
- // EXPORTS
- // (this section is replaced by build-cjs.js)
- // named export for ESM
- export { parseSetCookie };
- // for backwards compatibility
- export default parseSetCookie;
- export { parseSetCookie as parse, parseString, splitCookiesString };
|