tough-cookie 3.0.1 → 4.0.0

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