cookie.js 50 KB

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