set-cookie.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. var defaultParseOptions = {
  2. decodeValues: true,
  3. map: false,
  4. silent: false,
  5. split: "auto", // auto = split strings but not arrays
  6. };
  7. function isForbiddenKey(key) {
  8. return typeof key !== "string" || key in {};
  9. }
  10. function createNullObj() {
  11. return Object.create(null);
  12. }
  13. function isNonEmptyString(str) {
  14. return typeof str === "string" && !!str.trim();
  15. }
  16. function parseString(setCookieValue, options) {
  17. var parts = setCookieValue.split(";").filter(isNonEmptyString);
  18. var nameValuePairStr = parts.shift();
  19. var parsed = parseNameValuePair(nameValuePairStr);
  20. var name = parsed.name;
  21. var value = parsed.value;
  22. options = options
  23. ? Object.assign({}, defaultParseOptions, options)
  24. : defaultParseOptions;
  25. if (isForbiddenKey(name)) {
  26. return null;
  27. }
  28. try {
  29. value = options.decodeValues ? decodeURIComponent(value) : value; // decode cookie value
  30. } catch (e) {
  31. console.error(
  32. "set-cookie-parser: failed to decode cookie value. Set options.decodeValues=false to disable decoding.",
  33. e
  34. );
  35. }
  36. var cookie = createNullObj();
  37. cookie.name = name;
  38. cookie.value = value;
  39. parts.forEach(function (part) {
  40. var sides = part.split("=");
  41. var key = sides.shift().trimLeft().toLowerCase();
  42. if (isForbiddenKey(key)) {
  43. return;
  44. }
  45. var value = sides.join("=");
  46. if (key === "expires") {
  47. cookie.expires = new Date(value);
  48. } else if (key === "max-age") {
  49. var n = parseInt(value, 10);
  50. if (!Number.isNaN(n)) cookie.maxAge = n;
  51. } else if (key === "secure") {
  52. cookie.secure = true;
  53. } else if (key === "httponly") {
  54. cookie.httpOnly = true;
  55. } else if (key === "samesite") {
  56. cookie.sameSite = value;
  57. } else if (key === "partitioned") {
  58. cookie.partitioned = true;
  59. } else if (key) {
  60. cookie[key] = value;
  61. }
  62. });
  63. return cookie;
  64. }
  65. function parseNameValuePair(nameValuePairStr) {
  66. // Parses name-value-pair according to rfc6265bis draft
  67. var name = "";
  68. var value = "";
  69. var nameValueArr = nameValuePairStr.split("=");
  70. if (nameValueArr.length > 1) {
  71. name = nameValueArr.shift();
  72. value = nameValueArr.join("="); // everything after the first =, joined by a "=" if there was more than one part
  73. } else {
  74. value = nameValuePairStr;
  75. }
  76. return { name: name, value: value };
  77. }
  78. function parseSetCookie(input, options) {
  79. options = options
  80. ? Object.assign({}, defaultParseOptions, options)
  81. : defaultParseOptions;
  82. if (!input) {
  83. if (!options.map) {
  84. return [];
  85. } else {
  86. return createNullObj();
  87. }
  88. }
  89. if (input.headers) {
  90. if (typeof input.headers.getSetCookie === "function") {
  91. // for fetch responses - they combine headers of the same type in the headers array,
  92. // but getSetCookie returns an uncombined array
  93. input = input.headers.getSetCookie();
  94. } else if (input.headers["set-cookie"]) {
  95. // fast-path for node.js (which automatically normalizes header names to lower-case)
  96. input = input.headers["set-cookie"];
  97. } else {
  98. // slow-path for other environments - see #25
  99. var sch =
  100. input.headers[
  101. Object.keys(input.headers).find(function (key) {
  102. return key.toLowerCase() === "set-cookie";
  103. })
  104. ];
  105. // warn if called on a request-like object with a cookie header rather than a set-cookie header - see #34, 36
  106. if (!sch && input.headers.cookie && !options.silent) {
  107. console.warn(
  108. "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."
  109. );
  110. }
  111. input = sch;
  112. }
  113. }
  114. var split = options.split;
  115. var isArray = Array.isArray(input);
  116. if (split === "auto") {
  117. split = !isArray;
  118. }
  119. if (!isArray) {
  120. input = [input];
  121. }
  122. input = input.filter(isNonEmptyString);
  123. if (split) {
  124. input = input.map(splitCookiesString).flat();
  125. }
  126. if (!options.map) {
  127. return input
  128. .map(function (str) {
  129. return parseString(str, options);
  130. })
  131. .filter(Boolean);
  132. } else {
  133. var cookies = createNullObj();
  134. return input.reduce(function (cookies, str) {
  135. var cookie = parseString(str, options);
  136. if (cookie && !isForbiddenKey(cookie.name)) {
  137. cookies[cookie.name] = cookie;
  138. }
  139. return cookies;
  140. }, cookies);
  141. }
  142. }
  143. /*
  144. Set-Cookie header field-values are sometimes comma joined in one string. This splits them without choking on commas
  145. that are within a single set-cookie field-value, such as in the Expires portion.
  146. This is uncommon, but explicitly allowed - see https://tools.ietf.org/html/rfc2616#section-4.2
  147. Node.js does this for every header *except* set-cookie - see https://github.com/nodejs/node/blob/d5e363b77ebaf1caf67cd7528224b651c86815c1/lib/_http_incoming.js#L128
  148. React Native's fetch does this for *every* header, including set-cookie.
  149. Based on: https://github.com/google/j2objc/commit/16820fdbc8f76ca0c33472810ce0cb03d20efe25
  150. Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
  151. */
  152. function splitCookiesString(cookiesString) {
  153. if (Array.isArray(cookiesString)) {
  154. return cookiesString;
  155. }
  156. if (typeof cookiesString !== "string") {
  157. return [];
  158. }
  159. var cookiesStrings = [];
  160. var pos = 0;
  161. var start;
  162. var ch;
  163. var lastComma;
  164. var nextStart;
  165. var cookiesSeparatorFound;
  166. function skipWhitespace() {
  167. while (pos < cookiesString.length && /\s/.test(cookiesString.charAt(pos))) {
  168. pos += 1;
  169. }
  170. return pos < cookiesString.length;
  171. }
  172. function notSpecialChar() {
  173. ch = cookiesString.charAt(pos);
  174. return ch !== "=" && ch !== ";" && ch !== ",";
  175. }
  176. while (pos < cookiesString.length) {
  177. start = pos;
  178. cookiesSeparatorFound = false;
  179. while (skipWhitespace()) {
  180. ch = cookiesString.charAt(pos);
  181. if (ch === ",") {
  182. // ',' is a cookie separator if we have later first '=', not ';' or ','
  183. lastComma = pos;
  184. pos += 1;
  185. skipWhitespace();
  186. nextStart = pos;
  187. while (pos < cookiesString.length && notSpecialChar()) {
  188. pos += 1;
  189. }
  190. // currently special character
  191. if (pos < cookiesString.length && cookiesString.charAt(pos) === "=") {
  192. // we found cookies separator
  193. cookiesSeparatorFound = true;
  194. // pos is inside the next cookie, so back up and return it.
  195. pos = nextStart;
  196. cookiesStrings.push(cookiesString.substring(start, lastComma));
  197. start = pos;
  198. } else {
  199. // in param ',' or param separator ';',
  200. // we continue from that comma
  201. pos = lastComma + 1;
  202. }
  203. } else {
  204. pos += 1;
  205. }
  206. }
  207. if (!cookiesSeparatorFound || pos >= cookiesString.length) {
  208. cookiesStrings.push(cookiesString.substring(start, cookiesString.length));
  209. }
  210. }
  211. return cookiesStrings;
  212. }
  213. // named export for CJS
  214. parseSetCookie.parseSetCookie = parseSetCookie;
  215. // for backwards compatibility
  216. parseSetCookie.parse = parseSetCookie;
  217. parseSetCookie.parseString = parseString;
  218. parseSetCookie.splitCookiesString = splitCookiesString;
  219. // EXPORTS
  220. // (this section is replaced by build-cjs.js)
  221. // named export for ESM
  222. export { parseSetCookie };
  223. // for backwards compatibility
  224. export default parseSetCookie;
  225. export { parseSetCookie as parse, parseString, splitCookiesString };