// Generated automatically from lib/set-cookie.js; see build-cjs.js 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(";"); // the name-value-pair is everything before the first ";", even if it is empty var nameValuePairStr = parts.shift(); if (!isNonEmptyString(nameValuePairStr)) { return null; } var parsed = parseNameValuePair(nameValuePairStr); var name = parsed.name; var value = parsed.value; options = options ? Object.assign({}, defaultParseOptions, options) : defaultParseOptions; if (isForbiddenKey(name)) { if (!options.silent) { console.warn("set-cookie-parser: dropping cookie due to name:", name); } return null; } try { value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value } catch (e) { if (!options.silent) { 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().trim().toLowerCase(); // name/value are reserved fields set from the name-value-pair; an attribute // with one of these keys must not overwrite them if (isForbiddenKey(key) || key === "name" || key === "value") { if (!options.silent) { console.warn("set-cookie-parser: ignoring attribute:", part); } return; } var value = sides.join("=").trim(); if (key === "expires") { var expires = new Date(value); if (Number.isNaN(expires.getTime())) { if (!options.silent) { console.error( "set-cookie-parser: failed to parse expires time:", value ); } } else { cookie.expires = expires; } } else if (key === "max-age") { // rfc6265 5.2.2: ignore a max-age whose value isn't all digits after an optional leading "-" if (/^-?\d+$/.test(value)) { cookie.maxAge = parseInt(value, 10); } else if (!options.silent) { console.error("set-cookie-parser: failed to parse max-age:", value); } } 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; } // rfc6265 5.2: remove any leading or trailing WSP characters from the name string and the value string return { name: trimWsp(name), value: trimWsp(value) }; } // Trims only WSP (SP / HTAB), per rfc6265. Unlike String.prototype.trim(), this leaves other // whitespace (e.g. NBSP) alone, and it is linear-time. function trimWsp(str) { var start = 0; var end = str.length; while (start < end && isWsp(str.charCodeAt(start))) { start += 1; } while (end > start && isWsp(str.charCodeAt(end - 1))) { end -= 1; } return str.substring(start, end); } function isWsp(code) { return code === 0x20 || code === 0x09; } 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." ); } 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) { 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; module.exports = parseSetCookie;