cookie.js 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671
  1. /*!
  2. * Copyright (c) 2015, Salesforce.com, Inc.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice,
  9. * this list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * 3. Neither the name of Salesforce.com nor the names of its contributors may
  16. * be used to endorse or promote products derived from this software without
  17. * specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  20. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  21. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  23. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  24. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  25. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  26. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  27. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  28. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  29. * POSSIBILITY OF SUCH DAMAGE.
  30. */
  31. "use strict";
  32. const punycode = require("punycode");
  33. const urlParse = require("url").parse;
  34. const util = require("util");
  35. const pubsuffix = require("./pubsuffix-psl");
  36. const Store = require("./store").Store;
  37. const MemoryCookieStore = require("./memstore").MemoryCookieStore;
  38. const pathMatch = require("./pathMatch").pathMatch;
  39. const VERSION = require("./version");
  40. const { fromCallback } = require("universalify");
  41. // From RFC6265 S4.1.1
  42. // note that it excludes \x3B ";"
  43. const COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/;
  44. const CONTROL_CHARS = /[\x00-\x1F]/;
  45. // From Chromium // '\r', '\n' and '\0' should be treated as a terminator in
  46. // the "relaxed" mode, see:
  47. // https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60
  48. const TERMINATORS = ["\n", "\r", "\0"];
  49. // RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
  50. // Note ';' is \x3B
  51. const PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;
  52. // date-time parsing constants (RFC6265 S5.1.1)
  53. const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
  54. const MONTH_TO_NUM = {
  55. jan: 0,
  56. feb: 1,
  57. mar: 2,
  58. apr: 3,
  59. may: 4,
  60. jun: 5,
  61. jul: 6,
  62. aug: 7,
  63. sep: 8,
  64. oct: 9,
  65. nov: 10,
  66. dec: 11
  67. };
  68. const MAX_TIME = 2147483647000; // 31-bit max
  69. const MIN_TIME = 0; // 31-bit min
  70. const SAME_SITE_CONTEXT_VAL_ERR =
  71. 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"';
  72. function checkSameSiteContext(value) {
  73. const context = String(value).toLowerCase();
  74. if (context === "none" || context === "lax" || context === "strict") {
  75. return context;
  76. } else {
  77. return null;
  78. }
  79. }
  80. const PrefixSecurityEnum = Object.freeze({
  81. SILENT: "silent",
  82. STRICT: "strict",
  83. DISABLED: "unsafe-disabled"
  84. });
  85. // Dumped from ip-regex@4.0.0, with the following changes:
  86. // * all capturing groups converted to non-capturing -- "(?:)"
  87. // * support for IPv6 Scoped Literal ("%eth1") removed
  88. // * lowercase hexadecimal only
  89. var IP_REGEX_LOWERCASE =/(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/;
  90. /*
  91. * Parses a Natural number (i.e., non-negative integer) with either the
  92. * <min>*<max>DIGIT ( non-digit *OCTET )
  93. * or
  94. * <min>*<max>DIGIT
  95. * grammar (RFC6265 S5.1.1).
  96. *
  97. * The "trailingOK" boolean controls if the grammar accepts a
  98. * "( non-digit *OCTET )" trailer.
  99. */
  100. function parseDigits(token, minDigits, maxDigits, trailingOK) {
  101. let count = 0;
  102. while (count < token.length) {
  103. const c = token.charCodeAt(count);
  104. // "non-digit = %x00-2F / %x3A-FF"
  105. if (c <= 0x2f || c >= 0x3a) {
  106. break;
  107. }
  108. count++;
  109. }
  110. // constrain to a minimum and maximum number of digits.
  111. if (count < minDigits || count > maxDigits) {
  112. return null;
  113. }
  114. if (!trailingOK && count != token.length) {
  115. return null;
  116. }
  117. return parseInt(token.substr(0, count), 10);
  118. }
  119. function parseTime(token) {
  120. const parts = token.split(":");
  121. const result = [0, 0, 0];
  122. /* RF6256 S5.1.1:
  123. * time = hms-time ( non-digit *OCTET )
  124. * hms-time = time-field ":" time-field ":" time-field
  125. * time-field = 1*2DIGIT
  126. */
  127. if (parts.length !== 3) {
  128. return null;
  129. }
  130. for (let i = 0; i < 3; i++) {
  131. // "time-field" must be strictly "1*2DIGIT", HOWEVER, "hms-time" can be
  132. // followed by "( non-digit *OCTET )" so therefore the last time-field can
  133. // have a trailer
  134. const trailingOK = i == 2;
  135. const num = parseDigits(parts[i], 1, 2, trailingOK);
  136. if (num === null) {
  137. return null;
  138. }
  139. result[i] = num;
  140. }
  141. return result;
  142. }
  143. function parseMonth(token) {
  144. token = String(token)
  145. .substr(0, 3)
  146. .toLowerCase();
  147. const num = MONTH_TO_NUM[token];
  148. return num >= 0 ? num : null;
  149. }
  150. /*
  151. * RFC6265 S5.1.1 date parser (see RFC for full grammar)
  152. */
  153. function parseDate(str) {
  154. if (!str) {
  155. return;
  156. }
  157. /* RFC6265 S5.1.1:
  158. * 2. Process each date-token sequentially in the order the date-tokens
  159. * appear in the cookie-date
  160. */
  161. const tokens = str.split(DATE_DELIM);
  162. if (!tokens) {
  163. return;
  164. }
  165. let hour = null;
  166. let minute = null;
  167. let second = null;
  168. let dayOfMonth = null;
  169. let month = null;
  170. let year = null;
  171. for (let i = 0; i < tokens.length; i++) {
  172. const token = tokens[i].trim();
  173. if (!token.length) {
  174. continue;
  175. }
  176. let result;
  177. /* 2.1. If the found-time flag is not set and the token matches the time
  178. * production, set the found-time flag and set the hour- value,
  179. * minute-value, and second-value to the numbers denoted by the digits in
  180. * the date-token, respectively. Skip the remaining sub-steps and continue
  181. * to the next date-token.
  182. */
  183. if (second === null) {
  184. result = parseTime(token);
  185. if (result) {
  186. hour = result[0];
  187. minute = result[1];
  188. second = result[2];
  189. continue;
  190. }
  191. }
  192. /* 2.2. If the found-day-of-month flag is not set and the date-token matches
  193. * the day-of-month production, set the found-day-of- month flag and set
  194. * the day-of-month-value to the number denoted by the date-token. Skip
  195. * the remaining sub-steps and continue to the next date-token.
  196. */
  197. if (dayOfMonth === null) {
  198. // "day-of-month = 1*2DIGIT ( non-digit *OCTET )"
  199. result = parseDigits(token, 1, 2, true);
  200. if (result !== null) {
  201. dayOfMonth = result;
  202. continue;
  203. }
  204. }
  205. /* 2.3. If the found-month flag is not set and the date-token matches the
  206. * month production, set the found-month flag and set the month-value to
  207. * the month denoted by the date-token. Skip the remaining sub-steps and
  208. * continue to the next date-token.
  209. */
  210. if (month === null) {
  211. result = parseMonth(token);
  212. if (result !== null) {
  213. month = result;
  214. continue;
  215. }
  216. }
  217. /* 2.4. If the found-year flag is not set and the date-token matches the
  218. * year production, set the found-year flag and set the year-value to the
  219. * number denoted by the date-token. Skip the remaining sub-steps and
  220. * continue to the next date-token.
  221. */
  222. if (year === null) {
  223. // "year = 2*4DIGIT ( non-digit *OCTET )"
  224. result = parseDigits(token, 2, 4, true);
  225. if (result !== null) {
  226. year = result;
  227. /* From S5.1.1:
  228. * 3. If the year-value is greater than or equal to 70 and less
  229. * than or equal to 99, increment the year-value by 1900.
  230. * 4. If the year-value is greater than or equal to 0 and less
  231. * than or equal to 69, increment the year-value by 2000.
  232. */
  233. if (year >= 70 && year <= 99) {
  234. year += 1900;
  235. } else if (year >= 0 && year <= 69) {
  236. year += 2000;
  237. }
  238. }
  239. }
  240. }
  241. /* RFC 6265 S5.1.1
  242. * "5. Abort these steps and fail to parse the cookie-date if:
  243. * * at least one of the found-day-of-month, found-month, found-
  244. * year, or found-time flags is not set,
  245. * * the day-of-month-value is less than 1 or greater than 31,
  246. * * the year-value is less than 1601,
  247. * * the hour-value is greater than 23,
  248. * * the minute-value is greater than 59, or
  249. * * the second-value is greater than 59.
  250. * (Note that leap seconds cannot be represented in this syntax.)"
  251. *
  252. * So, in order as above:
  253. */
  254. if (
  255. dayOfMonth === null ||
  256. month === null ||
  257. year === null ||
  258. second === null ||
  259. dayOfMonth < 1 ||
  260. dayOfMonth > 31 ||
  261. year < 1601 ||
  262. hour > 23 ||
  263. minute > 59 ||
  264. second > 59
  265. ) {
  266. return;
  267. }
  268. return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second));
  269. }
  270. function formatDate(date) {
  271. return date.toUTCString();
  272. }
  273. // S5.1.2 Canonicalized Host Names
  274. function canonicalDomain(str) {
  275. if (str == null) {
  276. return null;
  277. }
  278. str = str.trim().replace(/^\./, ""); // S4.1.2.3 & S5.2.3: ignore leading .
  279. // convert to IDN if any non-ASCII characters
  280. if (punycode && /[^\u0001-\u007f]/.test(str)) {
  281. str = punycode.toASCII(str);
  282. }
  283. return str.toLowerCase();
  284. }
  285. // S5.1.3 Domain Matching
  286. function domainMatch(str, domStr, canonicalize) {
  287. if (str == null || domStr == null) {
  288. return null;
  289. }
  290. if (canonicalize !== false) {
  291. str = canonicalDomain(str);
  292. domStr = canonicalDomain(domStr);
  293. }
  294. /*
  295. * S5.1.3:
  296. * "A string domain-matches a given domain string if at least one of the
  297. * following conditions hold:"
  298. *
  299. * " o The domain string and the string are identical. (Note that both the
  300. * domain string and the string will have been canonicalized to lower case at
  301. * this point)"
  302. */
  303. if (str == domStr) {
  304. return true;
  305. }
  306. /* " o All of the following [three] conditions hold:" */
  307. /* "* The domain string is a suffix of the string" */
  308. const idx = str.indexOf(domStr);
  309. if (idx <= 0) {
  310. return false; // it's a non-match (-1) or prefix (0)
  311. }
  312. // next, check it's a proper suffix
  313. // e.g., "a.b.c".indexOf("b.c") === 2
  314. // 5 === 3+2
  315. if (str.length !== domStr.length + idx) {
  316. return false; // it's not a suffix
  317. }
  318. /* " * The last character of the string that is not included in the
  319. * domain string is a %x2E (".") character." */
  320. if (str.substr(idx-1,1) !== '.') {
  321. return false; // doesn't align on "."
  322. }
  323. /* " * The string is a host name (i.e., not an IP address)." */
  324. if (IP_REGEX_LOWERCASE.test(str)) {
  325. return false; // it's an IP address
  326. }
  327. return true;
  328. }
  329. // RFC6265 S5.1.4 Paths and Path-Match
  330. /*
  331. * "The user agent MUST use an algorithm equivalent to the following algorithm
  332. * to compute the default-path of a cookie:"
  333. *
  334. * Assumption: the path (and not query part or absolute uri) is passed in.
  335. */
  336. function defaultPath(path) {
  337. // "2. If the uri-path is empty or if the first character of the uri-path is not
  338. // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.
  339. if (!path || path.substr(0, 1) !== "/") {
  340. return "/";
  341. }
  342. // "3. If the uri-path contains no more than one %x2F ("/") character, output
  343. // %x2F ("/") and skip the remaining step."
  344. if (path === "/") {
  345. return path;
  346. }
  347. const rightSlash = path.lastIndexOf("/");
  348. if (rightSlash === 0) {
  349. return "/";
  350. }
  351. // "4. Output the characters of the uri-path from the first character up to,
  352. // but not including, the right-most %x2F ("/")."
  353. return path.slice(0, rightSlash);
  354. }
  355. function trimTerminator(str) {
  356. for (let t = 0; t < TERMINATORS.length; t++) {
  357. const terminatorIdx = str.indexOf(TERMINATORS[t]);
  358. if (terminatorIdx !== -1) {
  359. str = str.substr(0, terminatorIdx);
  360. }
  361. }
  362. return str;
  363. }
  364. function parseCookiePair(cookiePair, looseMode) {
  365. cookiePair = trimTerminator(cookiePair);
  366. let firstEq = cookiePair.indexOf("=");
  367. if (looseMode) {
  368. if (firstEq === 0) {
  369. // '=' is immediately at start
  370. cookiePair = cookiePair.substr(1);
  371. firstEq = cookiePair.indexOf("="); // might still need to split on '='
  372. }
  373. } else {
  374. // non-loose mode
  375. if (firstEq <= 0) {
  376. // no '=' or is at start
  377. return; // needs to have non-empty "cookie-name"
  378. }
  379. }
  380. let cookieName, cookieValue;
  381. if (firstEq <= 0) {
  382. cookieName = "";
  383. cookieValue = cookiePair.trim();
  384. } else {
  385. cookieName = cookiePair.substr(0, firstEq).trim();
  386. cookieValue = cookiePair.substr(firstEq + 1).trim();
  387. }
  388. if (CONTROL_CHARS.test(cookieName) || CONTROL_CHARS.test(cookieValue)) {
  389. return;
  390. }
  391. const c = new Cookie();
  392. c.key = cookieName;
  393. c.value = cookieValue;
  394. return c;
  395. }
  396. function parse(str, options) {
  397. if (!options || typeof options !== "object") {
  398. options = {};
  399. }
  400. str = str.trim();
  401. // We use a regex to parse the "name-value-pair" part of S5.2
  402. const firstSemi = str.indexOf(";"); // S5.2 step 1
  403. const cookiePair = firstSemi === -1 ? str : str.substr(0, firstSemi);
  404. const c = parseCookiePair(cookiePair, !!options.loose);
  405. if (!c) {
  406. return;
  407. }
  408. if (firstSemi === -1) {
  409. return c;
  410. }
  411. // S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string
  412. // (including the %x3B (";") in question)." plus later on in the same section
  413. // "discard the first ";" and trim".
  414. const unparsed = str.slice(firstSemi + 1).trim();
  415. // "If the unparsed-attributes string is empty, skip the rest of these
  416. // steps."
  417. if (unparsed.length === 0) {
  418. return c;
  419. }
  420. /*
  421. * S5.2 says that when looping over the items "[p]rocess the attribute-name
  422. * and attribute-value according to the requirements in the following
  423. * subsections" for every item. Plus, for many of the individual attributes
  424. * in S5.3 it says to use the "attribute-value of the last attribute in the
  425. * cookie-attribute-list". Therefore, in this implementation, we overwrite
  426. * the previous value.
  427. */
  428. const cookie_avs = unparsed.split(";");
  429. while (cookie_avs.length) {
  430. const av = cookie_avs.shift().trim();
  431. if (av.length === 0) {
  432. // happens if ";;" appears
  433. continue;
  434. }
  435. const av_sep = av.indexOf("=");
  436. let av_key, av_value;
  437. if (av_sep === -1) {
  438. av_key = av;
  439. av_value = null;
  440. } else {
  441. av_key = av.substr(0, av_sep);
  442. av_value = av.substr(av_sep + 1);
  443. }
  444. av_key = av_key.trim().toLowerCase();
  445. if (av_value) {
  446. av_value = av_value.trim();
  447. }
  448. switch (av_key) {
  449. case "expires": // S5.2.1
  450. if (av_value) {
  451. const exp = parseDate(av_value);
  452. // "If the attribute-value failed to parse as a cookie date, ignore the
  453. // cookie-av."
  454. if (exp) {
  455. // over and underflow not realistically a concern: V8's getTime() seems to
  456. // store something larger than a 32-bit time_t (even with 32-bit node)
  457. c.expires = exp;
  458. }
  459. }
  460. break;
  461. case "max-age": // S5.2.2
  462. if (av_value) {
  463. // "If the first character of the attribute-value is not a DIGIT or a "-"
  464. // character ...[or]... If the remainder of attribute-value contains a
  465. // non-DIGIT character, ignore the cookie-av."
  466. if (/^-?[0-9]+$/.test(av_value)) {
  467. const delta = parseInt(av_value, 10);
  468. // "If delta-seconds is less than or equal to zero (0), let expiry-time
  469. // be the earliest representable date and time."
  470. c.setMaxAge(delta);
  471. }
  472. }
  473. break;
  474. case "domain": // S5.2.3
  475. // "If the attribute-value is empty, the behavior is undefined. However,
  476. // the user agent SHOULD ignore the cookie-av entirely."
  477. if (av_value) {
  478. // S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E
  479. // (".") character."
  480. const domain = av_value.trim().replace(/^\./, "");
  481. if (domain) {
  482. // "Convert the cookie-domain to lower case."
  483. c.domain = domain.toLowerCase();
  484. }
  485. }
  486. break;
  487. case "path": // S5.2.4
  488. /*
  489. * "If the attribute-value is empty or if the first character of the
  490. * attribute-value is not %x2F ("/"):
  491. * Let cookie-path be the default-path.
  492. * Otherwise:
  493. * Let cookie-path be the attribute-value."
  494. *
  495. * We'll represent the default-path as null since it depends on the
  496. * context of the parsing.
  497. */
  498. c.path = av_value && av_value[0] === "/" ? av_value : null;
  499. break;
  500. case "secure": // S5.2.5
  501. /*
  502. * "If the attribute-name case-insensitively matches the string "Secure",
  503. * the user agent MUST append an attribute to the cookie-attribute-list
  504. * with an attribute-name of Secure and an empty attribute-value."
  505. */
  506. c.secure = true;
  507. break;
  508. case "httponly": // S5.2.6 -- effectively the same as 'secure'
  509. c.httpOnly = true;
  510. break;
  511. case "samesite": // RFC6265bis-02 S5.3.7
  512. const enforcement = av_value ? av_value.toLowerCase() : "";
  513. switch (enforcement) {
  514. case "strict":
  515. c.sameSite = "strict";
  516. break;
  517. case "lax":
  518. c.sameSite = "lax";
  519. break;
  520. default:
  521. // RFC6265bis-02 S5.3.7 step 1:
  522. // "If cookie-av's attribute-value is not a case-insensitive match
  523. // for "Strict" or "Lax", ignore the "cookie-av"."
  524. // This effectively sets it to 'none' from the prototype.
  525. break;
  526. }
  527. break;
  528. default:
  529. c.extensions = c.extensions || [];
  530. c.extensions.push(av);
  531. break;
  532. }
  533. }
  534. return c;
  535. }
  536. /**
  537. * If the cookie-name begins with a case-sensitive match for the
  538. * string "__Secure-", abort these steps and ignore the cookie
  539. * entirely unless the cookie's secure-only-flag is true.
  540. * @param cookie
  541. * @returns boolean
  542. */
  543. function isSecurePrefixConditionMet(cookie) {
  544. return !cookie.key.startsWith("__Secure-") || cookie.secure;
  545. }
  546. /**
  547. * If the cookie-name begins with a case-sensitive match for the
  548. * string "__Host-", abort these steps and ignore the cookie
  549. * entirely unless the cookie meets all the following criteria:
  550. * 1. The cookie's secure-only-flag is true.
  551. * 2. The cookie's host-only-flag is true.
  552. * 3. The cookie-attribute-list contains an attribute with an
  553. * attribute-name of "Path", and the cookie's path is "/".
  554. * @param cookie
  555. * @returns boolean
  556. */
  557. function isHostPrefixConditionMet(cookie) {
  558. return (
  559. !cookie.key.startsWith("__Host-") ||
  560. (cookie.secure &&
  561. cookie.hostOnly &&
  562. cookie.path != null &&
  563. cookie.path === "/")
  564. );
  565. }
  566. // avoid the V8 deoptimization monster!
  567. function jsonParse(str) {
  568. let obj;
  569. try {
  570. obj = JSON.parse(str);
  571. } catch (e) {
  572. return e;
  573. }
  574. return obj;
  575. }
  576. function fromJSON(str) {
  577. if (!str) {
  578. return null;
  579. }
  580. let obj;
  581. if (typeof str === "string") {
  582. obj = jsonParse(str);
  583. if (obj instanceof Error) {
  584. return null;
  585. }
  586. } else {
  587. // assume it's an Object
  588. obj = str;
  589. }
  590. const c = new Cookie();
  591. for (let i = 0; i < Cookie.serializableProperties.length; i++) {
  592. const prop = Cookie.serializableProperties[i];
  593. if (obj[prop] === undefined || obj[prop] === cookieDefaults[prop]) {
  594. continue; // leave as prototype default
  595. }
  596. if (prop === "expires" || prop === "creation" || prop === "lastAccessed") {
  597. if (obj[prop] === null) {
  598. c[prop] = null;
  599. } else {
  600. c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]);
  601. }
  602. } else {
  603. c[prop] = obj[prop];
  604. }
  605. }
  606. return c;
  607. }
  608. /* Section 5.4 part 2:
  609. * "* Cookies with longer paths are listed before cookies with
  610. * shorter paths.
  611. *
  612. * * Among cookies that have equal-length path fields, cookies with
  613. * earlier creation-times are listed before cookies with later
  614. * creation-times."
  615. */
  616. function cookieCompare(a, b) {
  617. let cmp = 0;
  618. // descending for length: b CMP a
  619. const aPathLen = a.path ? a.path.length : 0;
  620. const bPathLen = b.path ? b.path.length : 0;
  621. cmp = bPathLen - aPathLen;
  622. if (cmp !== 0) {
  623. return cmp;
  624. }
  625. // ascending for time: a CMP b
  626. const aTime = a.creation ? a.creation.getTime() : MAX_TIME;
  627. const bTime = b.creation ? b.creation.getTime() : MAX_TIME;
  628. cmp = aTime - bTime;
  629. if (cmp !== 0) {
  630. return cmp;
  631. }
  632. // break ties for the same millisecond (precision of JavaScript's clock)
  633. cmp = a.creationIndex - b.creationIndex;
  634. return cmp;
  635. }
  636. // Gives the permutation of all possible pathMatch()es of a given path. The
  637. // array is in longest-to-shortest order. Handy for indexing.
  638. function permutePath(path) {
  639. if (path === "/") {
  640. return ["/"];
  641. }
  642. const permutations = [path];
  643. while (path.length > 1) {
  644. const lindex = path.lastIndexOf("/");
  645. if (lindex === 0) {
  646. break;
  647. }
  648. path = path.substr(0, lindex);
  649. permutations.push(path);
  650. }
  651. permutations.push("/");
  652. return permutations;
  653. }
  654. function getCookieContext(url) {
  655. if (url instanceof Object) {
  656. return url;
  657. }
  658. // NOTE: decodeURI will throw on malformed URIs (see GH-32).
  659. // Therefore, we will just skip decoding for such URIs.
  660. try {
  661. url = decodeURI(url);
  662. } catch (err) {
  663. // Silently swallow error
  664. }
  665. return urlParse(url);
  666. }
  667. const cookieDefaults = {
  668. // the order in which the RFC has them:
  669. key: "",
  670. value: "",
  671. expires: "Infinity",
  672. maxAge: null,
  673. domain: null,
  674. path: null,
  675. secure: false,
  676. httpOnly: false,
  677. extensions: null,
  678. // set by the CookieJar:
  679. hostOnly: null,
  680. pathIsDefault: null,
  681. creation: null,
  682. lastAccessed: null,
  683. sameSite: "none"
  684. };
  685. class Cookie {
  686. constructor(options = {}) {
  687. if (util.inspect.custom) {
  688. this[util.inspect.custom] = this.inspect;
  689. }
  690. Object.assign(this, cookieDefaults, options);
  691. this.creation = this.creation || new Date();
  692. // used to break creation ties in cookieCompare():
  693. Object.defineProperty(this, "creationIndex", {
  694. configurable: false,
  695. enumerable: false, // important for assert.deepEqual checks
  696. writable: true,
  697. value: ++Cookie.cookiesCreated
  698. });
  699. }
  700. inspect() {
  701. const now = Date.now();
  702. const hostOnly = this.hostOnly != null ? this.hostOnly : "?";
  703. const createAge = this.creation
  704. ? `${now - this.creation.getTime()}ms`
  705. : "?";
  706. const accessAge = this.lastAccessed
  707. ? `${now - this.lastAccessed.getTime()}ms`
  708. : "?";
  709. return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"`;
  710. }
  711. toJSON() {
  712. const obj = {};
  713. for (const prop of Cookie.serializableProperties) {
  714. if (this[prop] === cookieDefaults[prop]) {
  715. continue; // leave as prototype default
  716. }
  717. if (
  718. prop === "expires" ||
  719. prop === "creation" ||
  720. prop === "lastAccessed"
  721. ) {
  722. if (this[prop] === null) {
  723. obj[prop] = null;
  724. } else {
  725. obj[prop] =
  726. this[prop] == "Infinity" // intentionally not ===
  727. ? "Infinity"
  728. : this[prop].toISOString();
  729. }
  730. } else if (prop === "maxAge") {
  731. if (this[prop] !== null) {
  732. // again, intentionally not ===
  733. obj[prop] =
  734. this[prop] == Infinity || this[prop] == -Infinity
  735. ? this[prop].toString()
  736. : this[prop];
  737. }
  738. } else {
  739. if (this[prop] !== cookieDefaults[prop]) {
  740. obj[prop] = this[prop];
  741. }
  742. }
  743. }
  744. return obj;
  745. }
  746. clone() {
  747. return fromJSON(this.toJSON());
  748. }
  749. validate() {
  750. if (!COOKIE_OCTETS.test(this.value)) {
  751. return false;
  752. }
  753. if (
  754. this.expires != Infinity &&
  755. !(this.expires instanceof Date) &&
  756. !parseDate(this.expires)
  757. ) {
  758. return false;
  759. }
  760. if (this.maxAge != null && this.maxAge <= 0) {
  761. return false; // "Max-Age=" non-zero-digit *DIGIT
  762. }
  763. if (this.path != null && !PATH_VALUE.test(this.path)) {
  764. return false;
  765. }
  766. const cdomain = this.cdomain();
  767. if (cdomain) {
  768. if (cdomain.match(/\.$/)) {
  769. return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this
  770. }
  771. const suffix = pubsuffix.getPublicSuffix(cdomain);
  772. if (suffix == null) {
  773. // it's a public suffix
  774. return false;
  775. }
  776. }
  777. return true;
  778. }
  779. setExpires(exp) {
  780. if (exp instanceof Date) {
  781. this.expires = exp;
  782. } else {
  783. this.expires = parseDate(exp) || "Infinity";
  784. }
  785. }
  786. setMaxAge(age) {
  787. if (age === Infinity || age === -Infinity) {
  788. this.maxAge = age.toString(); // so JSON.stringify() works
  789. } else {
  790. this.maxAge = age;
  791. }
  792. }
  793. cookieString() {
  794. let val = this.value;
  795. if (val == null) {
  796. val = "";
  797. }
  798. if (this.key === "") {
  799. return val;
  800. }
  801. return `${this.key}=${val}`;
  802. }
  803. // gives Set-Cookie header format
  804. toString() {
  805. let str = this.cookieString();
  806. if (this.expires != Infinity) {
  807. if (this.expires instanceof Date) {
  808. str += `; Expires=${formatDate(this.expires)}`;
  809. } else {
  810. str += `; Expires=${this.expires}`;
  811. }
  812. }
  813. if (this.maxAge != null && this.maxAge != Infinity) {
  814. str += `; Max-Age=${this.maxAge}`;
  815. }
  816. if (this.domain && !this.hostOnly) {
  817. str += `; Domain=${this.domain}`;
  818. }
  819. if (this.path) {
  820. str += `; Path=${this.path}`;
  821. }
  822. if (this.secure) {
  823. str += "; Secure";
  824. }
  825. if (this.httpOnly) {
  826. str += "; HttpOnly";
  827. }
  828. if (this.sameSite && this.sameSite !== "none") {
  829. const ssCanon = Cookie.sameSiteCanonical[this.sameSite.toLowerCase()];
  830. str += `; SameSite=${ssCanon ? ssCanon : this.sameSite}`;
  831. }
  832. if (this.extensions) {
  833. this.extensions.forEach(ext => {
  834. str += `; ${ext}`;
  835. });
  836. }
  837. return str;
  838. }
  839. // TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
  840. // elsewhere)
  841. // S5.3 says to give the "latest representable date" for which we use Infinity
  842. // For "expired" we use 0
  843. TTL(now) {
  844. /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires
  845. * attribute, the Max-Age attribute has precedence and controls the
  846. * expiration date of the cookie.
  847. * (Concurs with S5.3 step 3)
  848. */
  849. if (this.maxAge != null) {
  850. return this.maxAge <= 0 ? 0 : this.maxAge * 1000;
  851. }
  852. let expires = this.expires;
  853. if (expires != Infinity) {
  854. if (!(expires instanceof Date)) {
  855. expires = parseDate(expires) || Infinity;
  856. }
  857. if (expires == Infinity) {
  858. return Infinity;
  859. }
  860. return expires.getTime() - (now || Date.now());
  861. }
  862. return Infinity;
  863. }
  864. // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
  865. // elsewhere)
  866. expiryTime(now) {
  867. if (this.maxAge != null) {
  868. const relativeTo = now || this.creation || new Date();
  869. const age = this.maxAge <= 0 ? -Infinity : this.maxAge * 1000;
  870. return relativeTo.getTime() + age;
  871. }
  872. if (this.expires == Infinity) {
  873. return Infinity;
  874. }
  875. return this.expires.getTime();
  876. }
  877. // expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
  878. // elsewhere), except it returns a Date
  879. expiryDate(now) {
  880. const millisec = this.expiryTime(now);
  881. if (millisec == Infinity) {
  882. return new Date(MAX_TIME);
  883. } else if (millisec == -Infinity) {
  884. return new Date(MIN_TIME);
  885. } else {
  886. return new Date(millisec);
  887. }
  888. }
  889. // This replaces the "persistent-flag" parts of S5.3 step 3
  890. isPersistent() {
  891. return this.maxAge != null || this.expires != Infinity;
  892. }
  893. // Mostly S5.1.2 and S5.2.3:
  894. canonicalizedDomain() {
  895. if (this.domain == null) {
  896. return null;
  897. }
  898. return canonicalDomain(this.domain);
  899. }
  900. cdomain() {
  901. return this.canonicalizedDomain();
  902. }
  903. }
  904. Cookie.cookiesCreated = 0;
  905. Cookie.parse = parse;
  906. Cookie.fromJSON = fromJSON;
  907. Cookie.serializableProperties = Object.keys(cookieDefaults);
  908. Cookie.sameSiteLevel = {
  909. strict: 3,
  910. lax: 2,
  911. none: 1
  912. };
  913. Cookie.sameSiteCanonical = {
  914. strict: "Strict",
  915. lax: "Lax"
  916. };
  917. function getNormalizedPrefixSecurity(prefixSecurity) {
  918. if (prefixSecurity != null) {
  919. const normalizedPrefixSecurity = prefixSecurity.toLowerCase();
  920. /* The three supported options */
  921. switch (normalizedPrefixSecurity) {
  922. case PrefixSecurityEnum.STRICT:
  923. case PrefixSecurityEnum.SILENT:
  924. case PrefixSecurityEnum.DISABLED:
  925. return normalizedPrefixSecurity;
  926. }
  927. }
  928. /* Default is SILENT */
  929. return PrefixSecurityEnum.SILENT;
  930. }
  931. class CookieJar {
  932. constructor(store, options = { rejectPublicSuffixes: true }) {
  933. if (typeof options === "boolean") {
  934. options = { rejectPublicSuffixes: options };
  935. }
  936. this.rejectPublicSuffixes = options.rejectPublicSuffixes;
  937. this.enableLooseMode = !!options.looseMode;
  938. this.allowSpecialUseDomain = !!options.allowSpecialUseDomain;
  939. this.store = store || new MemoryCookieStore();
  940. this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity);
  941. this._cloneSync = syncWrap("clone");
  942. this._importCookiesSync = syncWrap("_importCookies");
  943. this.getCookiesSync = syncWrap("getCookies");
  944. this.getCookieStringSync = syncWrap("getCookieString");
  945. this.getSetCookieStringsSync = syncWrap("getSetCookieStrings");
  946. this.removeAllCookiesSync = syncWrap("removeAllCookies");
  947. this.setCookieSync = syncWrap("setCookie");
  948. this.serializeSync = syncWrap("serialize");
  949. }
  950. setCookie(cookie, url, options, cb) {
  951. let err;
  952. const context = getCookieContext(url);
  953. if (typeof options === "function") {
  954. cb = options;
  955. options = {};
  956. }
  957. const host = canonicalDomain(context.hostname);
  958. const loose = options.loose || this.enableLooseMode;
  959. let sameSiteContext = null;
  960. if (options.sameSiteContext) {
  961. sameSiteContext = checkSameSiteContext(options.sameSiteContext);
  962. if (!sameSiteContext) {
  963. return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR));
  964. }
  965. }
  966. // S5.3 step 1
  967. if (typeof cookie === "string" || cookie instanceof String) {
  968. cookie = Cookie.parse(cookie, { loose: loose });
  969. if (!cookie) {
  970. err = new Error("Cookie failed to parse");
  971. return cb(options.ignoreError ? null : err);
  972. }
  973. } else if (!(cookie instanceof Cookie)) {
  974. // If you're seeing this error, and are passing in a Cookie object,
  975. // it *might* be a Cookie object from another loaded version of tough-cookie.
  976. err = new Error(
  977. "First argument to setCookie must be a Cookie object or string"
  978. );
  979. return cb(options.ignoreError ? null : err);
  980. }
  981. // S5.3 step 2
  982. const now = options.now || new Date(); // will assign later to save effort in the face of errors
  983. // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie()
  984. // S5.3 step 4: NOOP; domain is null by default
  985. // S5.3 step 5: public suffixes
  986. if (this.rejectPublicSuffixes && cookie.domain) {
  987. const suffix = pubsuffix.getPublicSuffix(cookie.cdomain());
  988. if (suffix == null) {
  989. // e.g. "com"
  990. err = new Error("Cookie has domain set to a public suffix");
  991. return cb(options.ignoreError ? null : err);
  992. }
  993. }
  994. // S5.3 step 6:
  995. if (cookie.domain) {
  996. if (!domainMatch(host, cookie.cdomain(), false)) {
  997. err = new Error(
  998. `Cookie not in this host's domain. Cookie:${cookie.cdomain()} Request:${host}`
  999. );
  1000. return cb(options.ignoreError ? null : err);
  1001. }
  1002. if (cookie.hostOnly == null) {
  1003. // don't reset if already set
  1004. cookie.hostOnly = false;
  1005. }
  1006. } else {
  1007. cookie.hostOnly = true;
  1008. cookie.domain = host;
  1009. }
  1010. //S5.2.4 If the attribute-value is empty or if the first character of the
  1011. //attribute-value is not %x2F ("/"):
  1012. //Let cookie-path be the default-path.
  1013. if (!cookie.path || cookie.path[0] !== "/") {
  1014. cookie.path = defaultPath(context.pathname);
  1015. cookie.pathIsDefault = true;
  1016. }
  1017. // S5.3 step 8: NOOP; secure attribute
  1018. // S5.3 step 9: NOOP; httpOnly attribute
  1019. // S5.3 step 10
  1020. if (options.http === false && cookie.httpOnly) {
  1021. err = new Error("Cookie is HttpOnly and this isn't an HTTP API");
  1022. return cb(options.ignoreError ? null : err);
  1023. }
  1024. // 6252bis-02 S5.4 Step 13 & 14:
  1025. if (cookie.sameSite !== "none" && sameSiteContext) {
  1026. // "If the cookie's "same-site-flag" is not "None", and the cookie
  1027. // is being set from a context whose "site for cookies" is not an
  1028. // exact match for request-uri's host's registered domain, then
  1029. // abort these steps and ignore the newly created cookie entirely."
  1030. if (sameSiteContext === "none") {
  1031. err = new Error(
  1032. "Cookie is SameSite but this is a cross-origin request"
  1033. );
  1034. return cb(options.ignoreError ? null : err);
  1035. }
  1036. }
  1037. /* 6265bis-02 S5.4 Steps 15 & 16 */
  1038. const ignoreErrorForPrefixSecurity =
  1039. this.prefixSecurity === PrefixSecurityEnum.SILENT;
  1040. const prefixSecurityDisabled =
  1041. this.prefixSecurity === PrefixSecurityEnum.DISABLED;
  1042. /* If prefix checking is not disabled ...*/
  1043. if (!prefixSecurityDisabled) {
  1044. let errorFound = false;
  1045. let errorMsg;
  1046. /* Check secure prefix condition */
  1047. if (!isSecurePrefixConditionMet(cookie)) {
  1048. errorFound = true;
  1049. errorMsg = "Cookie has __Secure prefix but Secure attribute is not set";
  1050. } else if (!isHostPrefixConditionMet(cookie)) {
  1051. /* Check host prefix condition */
  1052. errorFound = true;
  1053. errorMsg =
  1054. "Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'";
  1055. }
  1056. if (errorFound) {
  1057. return cb(
  1058. options.ignoreError || ignoreErrorForPrefixSecurity
  1059. ? null
  1060. : new Error(errorMsg)
  1061. );
  1062. }
  1063. }
  1064. const store = this.store;
  1065. if (!store.updateCookie) {
  1066. store.updateCookie = function(oldCookie, newCookie, cb) {
  1067. this.putCookie(newCookie, cb);
  1068. };
  1069. }
  1070. function withCookie(err, oldCookie) {
  1071. if (err) {
  1072. return cb(err);
  1073. }
  1074. const next = function(err) {
  1075. if (err) {
  1076. return cb(err);
  1077. } else {
  1078. cb(null, cookie);
  1079. }
  1080. };
  1081. if (oldCookie) {
  1082. // S5.3 step 11 - "If the cookie store contains a cookie with the same name,
  1083. // domain, and path as the newly created cookie:"
  1084. if (options.http === false && oldCookie.httpOnly) {
  1085. // step 11.2
  1086. err = new Error("old Cookie is HttpOnly and this isn't an HTTP API");
  1087. return cb(options.ignoreError ? null : err);
  1088. }
  1089. cookie.creation = oldCookie.creation; // step 11.3
  1090. cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker
  1091. cookie.lastAccessed = now;
  1092. // Step 11.4 (delete cookie) is implied by just setting the new one:
  1093. store.updateCookie(oldCookie, cookie, next); // step 12
  1094. } else {
  1095. cookie.creation = cookie.lastAccessed = now;
  1096. store.putCookie(cookie, next); // step 12
  1097. }
  1098. }
  1099. store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);
  1100. }
  1101. // RFC6365 S5.4
  1102. getCookies(url, options, cb) {
  1103. const context = getCookieContext(url);
  1104. if (typeof options === "function") {
  1105. cb = options;
  1106. options = {};
  1107. }
  1108. const host = canonicalDomain(context.hostname);
  1109. const path = context.pathname || "/";
  1110. let secure = options.secure;
  1111. if (
  1112. secure == null &&
  1113. context.protocol &&
  1114. (context.protocol == "https:" || context.protocol == "wss:")
  1115. ) {
  1116. secure = true;
  1117. }
  1118. let sameSiteLevel = 0;
  1119. if (options.sameSiteContext) {
  1120. const sameSiteContext = checkSameSiteContext(options.sameSiteContext);
  1121. sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext];
  1122. if (!sameSiteLevel) {
  1123. return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR));
  1124. }
  1125. }
  1126. let http = options.http;
  1127. if (http == null) {
  1128. http = true;
  1129. }
  1130. const now = options.now || Date.now();
  1131. const expireCheck = options.expire !== false;
  1132. const allPaths = !!options.allPaths;
  1133. const store = this.store;
  1134. function matchingCookie(c) {
  1135. // "Either:
  1136. // The cookie's host-only-flag is true and the canonicalized
  1137. // request-host is identical to the cookie's domain.
  1138. // Or:
  1139. // The cookie's host-only-flag is false and the canonicalized
  1140. // request-host domain-matches the cookie's domain."
  1141. if (c.hostOnly) {
  1142. if (c.domain != host) {
  1143. return false;
  1144. }
  1145. } else {
  1146. if (!domainMatch(host, c.domain, false)) {
  1147. return false;
  1148. }
  1149. }
  1150. // "The request-uri's path path-matches the cookie's path."
  1151. if (!allPaths && !pathMatch(path, c.path)) {
  1152. return false;
  1153. }
  1154. // "If the cookie's secure-only-flag is true, then the request-uri's
  1155. // scheme must denote a "secure" protocol"
  1156. if (c.secure && !secure) {
  1157. return false;
  1158. }
  1159. // "If the cookie's http-only-flag is true, then exclude the cookie if the
  1160. // cookie-string is being generated for a "non-HTTP" API"
  1161. if (c.httpOnly && !http) {
  1162. return false;
  1163. }
  1164. // RFC6265bis-02 S5.3.7
  1165. if (sameSiteLevel) {
  1166. const cookieLevel = Cookie.sameSiteLevel[c.sameSite || "none"];
  1167. if (cookieLevel > sameSiteLevel) {
  1168. // only allow cookies at or below the request level
  1169. return false;
  1170. }
  1171. }
  1172. // deferred from S5.3
  1173. // non-RFC: allow retention of expired cookies by choice
  1174. if (expireCheck && c.expiryTime() <= now) {
  1175. store.removeCookie(c.domain, c.path, c.key, () => {}); // result ignored
  1176. return false;
  1177. }
  1178. return true;
  1179. }
  1180. store.findCookies(
  1181. host,
  1182. allPaths ? null : path,
  1183. this.allowSpecialUseDomain,
  1184. (err, cookies) => {
  1185. if (err) {
  1186. return cb(err);
  1187. }
  1188. cookies = cookies.filter(matchingCookie);
  1189. // sorting of S5.4 part 2
  1190. if (options.sort !== false) {
  1191. cookies = cookies.sort(cookieCompare);
  1192. }
  1193. // S5.4 part 3
  1194. const now = new Date();
  1195. for (const cookie of cookies) {
  1196. cookie.lastAccessed = now;
  1197. }
  1198. // TODO persist lastAccessed
  1199. cb(null, cookies);
  1200. }
  1201. );
  1202. }
  1203. getCookieString(...args) {
  1204. const cb = args.pop();
  1205. const next = function(err, cookies) {
  1206. if (err) {
  1207. cb(err);
  1208. } else {
  1209. cb(
  1210. null,
  1211. cookies
  1212. .sort(cookieCompare)
  1213. .map(c => c.cookieString())
  1214. .join("; ")
  1215. );
  1216. }
  1217. };
  1218. args.push(next);
  1219. this.getCookies.apply(this, args);
  1220. }
  1221. getSetCookieStrings(...args) {
  1222. const cb = args.pop();
  1223. const next = function(err, cookies) {
  1224. if (err) {
  1225. cb(err);
  1226. } else {
  1227. cb(
  1228. null,
  1229. cookies.map(c => {
  1230. return c.toString();
  1231. })
  1232. );
  1233. }
  1234. };
  1235. args.push(next);
  1236. this.getCookies.apply(this, args);
  1237. }
  1238. serialize(cb) {
  1239. let type = this.store.constructor.name;
  1240. if (type === "Object") {
  1241. type = null;
  1242. }
  1243. // update README.md "Serialization Format" if you change this, please!
  1244. const serialized = {
  1245. // The version of tough-cookie that serialized this jar. Generally a good
  1246. // practice since future versions can make data import decisions based on
  1247. // known past behavior. When/if this matters, use `semver`.
  1248. version: `tough-cookie@${VERSION}`,
  1249. // add the store type, to make humans happy:
  1250. storeType: type,
  1251. // CookieJar configuration:
  1252. rejectPublicSuffixes: !!this.rejectPublicSuffixes,
  1253. // this gets filled from getAllCookies:
  1254. cookies: []
  1255. };
  1256. if (
  1257. !(
  1258. this.store.getAllCookies &&
  1259. typeof this.store.getAllCookies === "function"
  1260. )
  1261. ) {
  1262. return cb(
  1263. new Error(
  1264. "store does not support getAllCookies and cannot be serialized"
  1265. )
  1266. );
  1267. }
  1268. this.store.getAllCookies((err, cookies) => {
  1269. if (err) {
  1270. return cb(err);
  1271. }
  1272. serialized.cookies = cookies.map(cookie => {
  1273. // convert to serialized 'raw' cookies
  1274. cookie = cookie instanceof Cookie ? cookie.toJSON() : cookie;
  1275. // Remove the index so new ones get assigned during deserialization
  1276. delete cookie.creationIndex;
  1277. return cookie;
  1278. });
  1279. return cb(null, serialized);
  1280. });
  1281. }
  1282. toJSON() {
  1283. return this.serializeSync();
  1284. }
  1285. // use the class method CookieJar.deserialize instead of calling this directly
  1286. _importCookies(serialized, cb) {
  1287. let cookies = serialized.cookies;
  1288. if (!cookies || !Array.isArray(cookies)) {
  1289. return cb(new Error("serialized jar has no cookies array"));
  1290. }
  1291. cookies = cookies.slice(); // do not modify the original
  1292. const putNext = err => {
  1293. if (err) {
  1294. return cb(err);
  1295. }
  1296. if (!cookies.length) {
  1297. return cb(err, this);
  1298. }
  1299. let cookie;
  1300. try {
  1301. cookie = fromJSON(cookies.shift());
  1302. } catch (e) {
  1303. return cb(e);
  1304. }
  1305. if (cookie === null) {
  1306. return putNext(null); // skip this cookie
  1307. }
  1308. this.store.putCookie(cookie, putNext);
  1309. };
  1310. putNext();
  1311. }
  1312. clone(newStore, cb) {
  1313. if (arguments.length === 1) {
  1314. cb = newStore;
  1315. newStore = null;
  1316. }
  1317. this.serialize((err, serialized) => {
  1318. if (err) {
  1319. return cb(err);
  1320. }
  1321. CookieJar.deserialize(serialized, newStore, cb);
  1322. });
  1323. }
  1324. cloneSync(newStore) {
  1325. if (arguments.length === 0) {
  1326. return this._cloneSync();
  1327. }
  1328. if (!newStore.synchronous) {
  1329. throw new Error(
  1330. "CookieJar clone destination store is not synchronous; use async API instead."
  1331. );
  1332. }
  1333. return this._cloneSync(newStore);
  1334. }
  1335. removeAllCookies(cb) {
  1336. const store = this.store;
  1337. // Check that the store implements its own removeAllCookies(). The default
  1338. // implementation in Store will immediately call the callback with a "not
  1339. // implemented" Error.
  1340. if (
  1341. typeof store.removeAllCookies === "function" &&
  1342. store.removeAllCookies !== Store.prototype.removeAllCookies
  1343. ) {
  1344. return store.removeAllCookies(cb);
  1345. }
  1346. store.getAllCookies((err, cookies) => {
  1347. if (err) {
  1348. return cb(err);
  1349. }
  1350. if (cookies.length === 0) {
  1351. return cb(null);
  1352. }
  1353. let completedCount = 0;
  1354. const removeErrors = [];
  1355. function removeCookieCb(removeErr) {
  1356. if (removeErr) {
  1357. removeErrors.push(removeErr);
  1358. }
  1359. completedCount++;
  1360. if (completedCount === cookies.length) {
  1361. return cb(removeErrors.length ? removeErrors[0] : null);
  1362. }
  1363. }
  1364. cookies.forEach(cookie => {
  1365. store.removeCookie(
  1366. cookie.domain,
  1367. cookie.path,
  1368. cookie.key,
  1369. removeCookieCb
  1370. );
  1371. });
  1372. });
  1373. }
  1374. static deserialize(strOrObj, store, cb) {
  1375. if (arguments.length !== 3) {
  1376. // store is optional
  1377. cb = store;
  1378. store = null;
  1379. }
  1380. let serialized;
  1381. if (typeof strOrObj === "string") {
  1382. serialized = jsonParse(strOrObj);
  1383. if (serialized instanceof Error) {
  1384. return cb(serialized);
  1385. }
  1386. } else {
  1387. serialized = strOrObj;
  1388. }
  1389. const jar = new CookieJar(store, serialized.rejectPublicSuffixes);
  1390. jar._importCookies(serialized, err => {
  1391. if (err) {
  1392. return cb(err);
  1393. }
  1394. cb(null, jar);
  1395. });
  1396. }
  1397. static deserializeSync(strOrObj, store) {
  1398. const serialized =
  1399. typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj;
  1400. const jar = new CookieJar(store, serialized.rejectPublicSuffixes);
  1401. // catch this mistake early:
  1402. if (!jar.store.synchronous) {
  1403. throw new Error(
  1404. "CookieJar store is not synchronous; use async API instead."
  1405. );
  1406. }
  1407. jar._importCookiesSync(serialized);
  1408. return jar;
  1409. }
  1410. }
  1411. CookieJar.fromJSON = CookieJar.deserializeSync;
  1412. [
  1413. "_importCookies",
  1414. "clone",
  1415. "getCookies",
  1416. "getCookieString",
  1417. "getSetCookieStrings",
  1418. "removeAllCookies",
  1419. "serialize",
  1420. "setCookie"
  1421. ].forEach(name => {
  1422. CookieJar.prototype[name] = fromCallback(CookieJar.prototype[name]);
  1423. });
  1424. CookieJar.deserialize = fromCallback(CookieJar.deserialize);
  1425. // Use a closure to provide a true imperative API for synchronous stores.
  1426. function syncWrap(method) {
  1427. return function(...args) {
  1428. if (!this.store.synchronous) {
  1429. throw new Error(
  1430. "CookieJar store is not synchronous; use async API instead."
  1431. );
  1432. }
  1433. let syncErr, syncResult;
  1434. this[method](...args, (err, result) => {
  1435. syncErr = err;
  1436. syncResult = result;
  1437. });
  1438. if (syncErr) {
  1439. throw syncErr;
  1440. }
  1441. return syncResult;
  1442. };
  1443. }
  1444. exports.version = VERSION;
  1445. exports.CookieJar = CookieJar;
  1446. exports.Cookie = Cookie;
  1447. exports.Store = Store;
  1448. exports.MemoryCookieStore = MemoryCookieStore;
  1449. exports.parseDate = parseDate;
  1450. exports.formatDate = formatDate;
  1451. exports.parse = parse;
  1452. exports.fromJSON = fromJSON;
  1453. exports.domainMatch = domainMatch;
  1454. exports.defaultPath = defaultPath;
  1455. exports.pathMatch = pathMatch;
  1456. exports.getPublicSuffix = pubsuffix.getPublicSuffix;
  1457. exports.cookieCompare = cookieCompare;
  1458. exports.permuteDomain = require("./permuteDomain").permuteDomain;
  1459. exports.permutePath = permutePath;
  1460. exports.canonicalDomain = canonicalDomain;
  1461. exports.PrefixSecurityEnum = PrefixSecurityEnum;