tough-cookie 2.4.3 → 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 net = require('net');
33
- var urlParse = require('url').parse;
34
- var util = require('util');
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('../package.json').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 (net.isIP(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,727 +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 (!(cookie instanceof Cookie)) {
1011
- cookie = Cookie.parse(cookie, { loose: loose });
1012
- }
1013
- if (!cookie) {
1014
- err = new Error("Cookie failed to parse");
1015
- return cb(options.ignoreError ? null : err);
1016
- }
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
+ );
1110
+ return cb(options.ignoreError ? null : err);
1111
+ }
1017
1112
 
1018
- // S5.3 step 2
1019
- 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
1020
1115
 
1021
- // 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()
1022
1117
 
1023
- // S5.3 step 4: NOOP; domain is null by default
1118
+ // S5.3 step 4: NOOP; domain is null by default
1024
1119
 
1025
- // S5.3 step 5: public suffixes
1026
- if (this.rejectPublicSuffixes && cookie.domain) {
1027
- var suffix = pubsuffix.getPublicSuffix(cookie.cdomain());
1028
- if (suffix == null) { // e.g. "com"
1029
- err = new Error("Cookie has domain set to a public suffix");
1030
- 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
+ }
1031
1128
  }
1032
- }
1033
1129
 
1034
- // S5.3 step 6:
1035
- if (cookie.domain) {
1036
- if (!domainMatch(host, cookie.cdomain(), false)) {
1037
- err = new Error("Cookie not in this host's domain. Cookie:"+cookie.cdomain()+" Request:"+host);
1038
- return cb(options.ignoreError ? null : err);
1039
- }
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
+ }
1040
1138
 
1041
- if (cookie.hostOnly == null) { // don't reset if already set
1042
- 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;
1043
1146
  }
1044
1147
 
1045
- } else {
1046
- cookie.hostOnly = true;
1047
- cookie.domain = host;
1048
- }
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
+ }
1049
1155
 
1050
- //S5.2.4 If the attribute-value is empty or if the first character of the
1051
- //attribute-value is not %x2F ("/"):
1052
- //Let cookie-path be the default-path.
1053
- if (!cookie.path || cookie.path[0] !== '/') {
1054
- cookie.path = defaultPath(context.pathname);
1055
- cookie.pathIsDefault = true;
1056
- }
1156
+ // S5.3 step 8: NOOP; secure attribute
1157
+ // S5.3 step 9: NOOP; httpOnly attribute
1057
1158
 
1058
- // S5.3 step 8: NOOP; secure attribute
1059
- // 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
+ }
1060
1164
 
1061
- // S5.3 step 10
1062
- if (options.http === false && cookie.httpOnly) {
1063
- err = new Error("Cookie is HttpOnly and this isn't an HTTP API");
1064
- return cb(options.ignoreError ? null : err);
1065
- }
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
+ }
1066
1178
 
1067
- 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
+ }
1068
1206
 
1069
- if (!store.updateCookie) {
1070
- store.updateCookie = function(oldCookie, newCookie, cb) {
1071
- this.putCookie(newCookie, cb);
1072
- };
1073
- }
1207
+ const store = this.store;
1074
1208
 
1075
- function withCookie(err, oldCookie) {
1076
- if (err) {
1077
- return cb(err);
1209
+ if (!store.updateCookie) {
1210
+ store.updateCookie = function(oldCookie, newCookie, cb) {
1211
+ this.putCookie(newCookie, cb);
1212
+ };
1078
1213
  }
1079
1214
 
1080
- var next = function(err) {
1215
+ function withCookie(err, oldCookie) {
1081
1216
  if (err) {
1082
1217
  return cb(err);
1083
- } else {
1084
- cb(null, cookie);
1085
1218
  }
1086
- };
1087
1219
 
1088
- if (oldCookie) {
1089
- // S5.3 step 11 - "If the cookie store contains a cookie with the same name,
1090
- // domain, and path as the newly created cookie:"
1091
- if (options.http === false && oldCookie.httpOnly) { // step 11.2
1092
- err = new Error("old Cookie is HttpOnly and this isn't an HTTP API");
1093
- 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
1094
1244
  }
1095
- cookie.creation = oldCookie.creation; // step 11.3
1096
- cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker
1097
- cookie.lastAccessed = now;
1098
- // Step 11.4 (delete cookie) is implied by just setting the new one:
1099
- store.updateCookie(oldCookie, cookie, next); // step 12
1100
-
1101
- } else {
1102
- cookie.creation = cookie.lastAccessed = now;
1103
- store.putCookie(cookie, next); // step 12
1104
1245
  }
1246
+
1247
+ store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);
1105
1248
  }
1106
1249
 
1107
- store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);
1108
- };
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
+ }
1109
1257
 
1110
- // RFC6365 S5.4
1111
- CAN_BE_SYNC.push('getCookies');
1112
- CookieJar.prototype.getCookies = function(url, options, cb) {
1113
- var context = getCookieContext(url);
1114
- if (options instanceof Function) {
1115
- cb = options;
1116
- options = {};
1117
- }
1258
+ const host = canonicalDomain(context.hostname);
1259
+ const path = context.pathname || "/";
1118
1260
 
1119
- var host = canonicalDomain(context.hostname);
1120
- 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
+ }
1121
1269
 
1122
- var secure = options.secure;
1123
- if (secure == null && context.protocol &&
1124
- (context.protocol == 'https:' || context.protocol == 'wss:'))
1125
- {
1126
- secure = true;
1127
- }
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
+ }
1128
1278
 
1129
- var http = options.http;
1130
- if (http == null) {
1131
- http = true;
1132
- }
1279
+ let http = options.http;
1280
+ if (http == null) {
1281
+ http = true;
1282
+ }
1133
1283
 
1134
- var now = options.now || Date.now();
1135
- var expireCheck = options.expire !== false;
1136
- var allPaths = !!options.allPaths;
1137
- 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
+ }
1138
1305
 
1139
- function matchingCookie(c) {
1140
- // "Either:
1141
- // The cookie's host-only-flag is true and the canonicalized
1142
- // request-host is identical to the cookie's domain.
1143
- // Or:
1144
- // The cookie's host-only-flag is false and the canonicalized
1145
- // request-host domain-matches the cookie's domain."
1146
- if (c.hostOnly) {
1147
- if (c.domain != host) {
1306
+ // "The request-uri's path path-matches the cookie's path."
1307
+ if (!allPaths && !pathMatch(path, c.path)) {
1148
1308
  return false;
1149
1309
  }
1150
- } else {
1151
- 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) {
1152
1314
  return false;
1153
1315
  }
1154
- }
1155
1316
 
1156
- // "The request-uri's path path-matches the cookie's path."
1157
- if (!allPaths && !pathMatch(path, c.path)) {
1158
- return false;
1159
- }
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
+ }
1160
1322
 
1161
- // "If the cookie's secure-only-flag is true, then the request-uri's
1162
- // scheme must denote a "secure" protocol"
1163
- if (c.secure && !secure) {
1164
- return false;
1165
- }
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
+ }
1166
1331
 
1167
- // "If the cookie's http-only-flag is true, then exclude the cookie if the
1168
- // cookie-string is being generated for a "non-HTTP" API"
1169
- if (c.httpOnly && !http) {
1170
- return false;
1171
- }
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
+ }
1172
1338
 
1173
- // deferred from S5.3
1174
- // non-RFC: allow retention of expired cookies by choice
1175
- if (expireCheck && c.expiryTime() <= now) {
1176
- store.removeCookie(c.domain, c.path, c.key, function(){}); // result ignored
1177
- return false;
1339
+ return true;
1178
1340
  }
1179
1341
 
1180
- return true;
1342
+ store.findCookies(
1343
+ host,
1344
+ allPaths ? null : path,
1345
+ this.allowSpecialUseDomain,
1346
+ (err, cookies) => {
1347
+ if (err) {
1348
+ return cb(err);
1349
+ }
1350
+
1351
+ cookies = cookies.filter(matchingCookie);
1352
+
1353
+ // sorting of S5.4 part 2
1354
+ if (options.sort !== false) {
1355
+ cookies = cookies.sort(cookieCompare);
1356
+ }
1357
+
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
1364
+
1365
+ cb(null, cookies);
1366
+ }
1367
+ );
1181
1368
  }
1182
1369
 
1183
- store.findCookies(host, allPaths ? null : path, function(err,cookies) {
1184
- if (err) {
1185
- return cb(err);
1186
- }
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
+ }
1187
1388
 
1188
- cookies = cookies.filter(matchingCookie);
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);
1405
+ }
1189
1406
 
1190
- // sorting of S5.4 part 2
1191
- if (options.sort !== false) {
1192
- cookies = cookies.sort(cookieCompare);
1407
+ serialize(cb) {
1408
+ let type = this.store.constructor.name;
1409
+ if (type === "Object") {
1410
+ type = null;
1193
1411
  }
1194
1412
 
1195
- // S5.4 part 3
1196
- var now = new Date();
1197
- cookies.forEach(function(c) {
1198
- c.lastAccessed = now;
1199
- });
1200
- // TODO persist lastAccessed
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}`,
1201
1419
 
1202
- cb(null,cookies);
1203
- });
1204
- };
1420
+ // add the store type, to make humans happy:
1421
+ storeType: type,
1205
1422
 
1206
- CAN_BE_SYNC.push('getCookieString');
1207
- CookieJar.prototype.getCookieString = function(/*..., cb*/) {
1208
- var args = Array.prototype.slice.call(arguments,0);
1209
- var cb = args.pop();
1210
- var next = function(err,cookies) {
1211
- if (err) {
1212
- cb(err);
1213
- } else {
1214
- cb(null, cookies
1215
- .sort(cookieCompare)
1216
- .map(function(c){
1217
- return c.cookieString();
1218
- })
1219
- .join('; '));
1220
- }
1221
- };
1222
- args.push(next);
1223
- this.getCookies.apply(this,args);
1224
- };
1423
+ // CookieJar configuration:
1424
+ rejectPublicSuffixes: !!this.rejectPublicSuffixes,
1225
1425
 
1226
- CAN_BE_SYNC.push('getSetCookieStrings');
1227
- CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {
1228
- var args = Array.prototype.slice.call(arguments,0);
1229
- var cb = args.pop();
1230
- var next = function(err,cookies) {
1231
- if (err) {
1232
- cb(err);
1233
- } else {
1234
- cb(null, cookies.map(function(c){
1235
- return c.toString();
1236
- }));
1426
+ // this gets filled from getAllCookies:
1427
+ cookies: []
1428
+ };
1429
+
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
+ );
1237
1441
  }
1238
- };
1239
- args.push(next);
1240
- this.getCookies.apply(this,args);
1241
- };
1242
1442
 
1243
- CAN_BE_SYNC.push('serialize');
1244
- CookieJar.prototype.serialize = function(cb) {
1245
- var type = this.store.constructor.name;
1246
- if (type === 'Object') {
1247
- type = null;
1248
- }
1443
+ this.store.getAllCookies((err, cookies) => {
1444
+ if (err) {
1445
+ return cb(err);
1446
+ }
1249
1447
 
1250
- // update README.md "Serialization Format" if you change this, please!
1251
- var serialized = {
1252
- // The version of tough-cookie that serialized this jar. Generally a good
1253
- // practice since future versions can make data import decisions based on
1254
- // known past behavior. When/if this matters, use `semver`.
1255
- version: 'tough-cookie@'+VERSION,
1448
+ serialized.cookies = cookies.map(cookie => {
1449
+ // convert to serialized 'raw' cookies
1450
+ cookie = cookie instanceof Cookie ? cookie.toJSON() : cookie;
1256
1451
 
1257
- // add the store type, to make humans happy:
1258
- storeType: type,
1452
+ // Remove the index so new ones get assigned during deserialization
1453
+ delete cookie.creationIndex;
1259
1454
 
1260
- // CookieJar configuration:
1261
- rejectPublicSuffixes: !!this.rejectPublicSuffixes,
1455
+ return cookie;
1456
+ });
1262
1457
 
1263
- // this gets filled from getAllCookies:
1264
- cookies: []
1265
- };
1458
+ return cb(null, serialized);
1459
+ });
1460
+ }
1266
1461
 
1267
- if (!(this.store.getAllCookies &&
1268
- typeof this.store.getAllCookies === 'function'))
1269
- {
1270
- return cb(new Error('store does not support getAllCookies and cannot be serialized'));
1462
+ toJSON() {
1463
+ return this.serializeSync();
1271
1464
  }
1272
1465
 
1273
- this.store.getAllCookies(function(err,cookies) {
1274
- if (err) {
1275
- 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"));
1276
1471
  }
1472
+ cookies = cookies.slice(); // do not modify the original
1277
1473
 
1278
- serialized.cookies = cookies.map(function(cookie) {
1279
- // convert to serialized 'raw' cookies
1280
- cookie = (cookie instanceof Cookie) ? cookie.toJSON() : cookie;
1474
+ const putNext = err => {
1475
+ if (err) {
1476
+ return cb(err);
1477
+ }
1281
1478
 
1282
- // Remove the index so new ones get assigned during deserialization
1283
- delete cookie.creationIndex;
1479
+ if (!cookies.length) {
1480
+ return cb(err, this);
1481
+ }
1284
1482
 
1285
- return cookie;
1286
- });
1483
+ let cookie;
1484
+ try {
1485
+ cookie = fromJSON(cookies.shift());
1486
+ } catch (e) {
1487
+ return cb(e);
1488
+ }
1287
1489
 
1288
- return cb(null, serialized);
1289
- });
1290
- };
1490
+ if (cookie === null) {
1491
+ return putNext(null); // skip this cookie
1492
+ }
1291
1493
 
1292
- // well-known name that JSON.stringify calls
1293
- CookieJar.prototype.toJSON = function() {
1294
- return this.serializeSync();
1295
- };
1494
+ this.store.putCookie(cookie, putNext);
1495
+ };
1296
1496
 
1297
- // use the class method CookieJar.deserialize instead of calling this directly
1298
- CAN_BE_SYNC.push('_importCookies');
1299
- CookieJar.prototype._importCookies = function(serialized, cb) {
1300
- var jar = this;
1301
- var cookies = serialized.cookies;
1302
- if (!cookies || !Array.isArray(cookies)) {
1303
- return cb(new Error('serialized jar has no cookies array'));
1497
+ putNext();
1304
1498
  }
1305
- cookies = cookies.slice(); // do not modify the original
1306
1499
 
1307
- function putNext(err) {
1308
- if (err) {
1309
- return cb(err);
1500
+ clone(newStore, cb) {
1501
+ if (arguments.length === 1) {
1502
+ cb = newStore;
1503
+ newStore = null;
1310
1504
  }
1311
1505
 
1312
- if (!cookies.length) {
1313
- return cb(err, jar);
1314
- }
1506
+ this.serialize((err, serialized) => {
1507
+ if (err) {
1508
+ return cb(err);
1509
+ }
1510
+ CookieJar.deserialize(serialized, newStore, cb);
1511
+ });
1512
+ }
1315
1513
 
1316
- var cookie;
1317
- try {
1318
- cookie = fromJSON(cookies.shift());
1319
- } catch (e) {
1320
- return cb(e);
1514
+ cloneSync(newStore) {
1515
+ if (arguments.length === 0) {
1516
+ return this._cloneSync();
1517
+ }
1518
+ if (!newStore.synchronous) {
1519
+ throw new Error(
1520
+ "CookieJar clone destination store is not synchronous; use async API instead."
1521
+ );
1321
1522
  }
1523
+ return this._cloneSync(newStore);
1524
+ }
1525
+
1526
+ removeAllCookies(cb) {
1527
+ const store = this.store;
1322
1528
 
1323
- if (cookie === null) {
1324
- return putNext(null); // skip this cookie
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);
1325
1537
  }
1326
1538
 
1327
- jar.store.putCookie(cookie, putNext);
1328
- }
1539
+ store.getAllCookies((err, cookies) => {
1540
+ if (err) {
1541
+ return cb(err);
1542
+ }
1329
1543
 
1330
- putNext();
1331
- };
1544
+ if (cookies.length === 0) {
1545
+ return cb(null);
1546
+ }
1332
1547
 
1333
- CookieJar.deserialize = function(strOrObj, store, cb) {
1334
- if (arguments.length !== 3) {
1335
- // store is optional
1336
- cb = store;
1337
- store = null;
1338
- }
1548
+ let completedCount = 0;
1549
+ const removeErrors = [];
1339
1550
 
1340
- var serialized;
1341
- if (typeof strOrObj === 'string') {
1342
- serialized = jsonParse(strOrObj);
1343
- if (serialized instanceof Error) {
1344
- return cb(serialized);
1345
- }
1346
- } else {
1347
- serialized = strOrObj;
1551
+ function removeCookieCb(removeErr) {
1552
+ if (removeErr) {
1553
+ removeErrors.push(removeErr);
1554
+ }
1555
+
1556
+ completedCount++;
1557
+
1558
+ if (completedCount === cookies.length) {
1559
+ return cb(removeErrors.length ? removeErrors[0] : null);
1560
+ }
1561
+ }
1562
+
1563
+ cookies.forEach(cookie => {
1564
+ store.removeCookie(
1565
+ cookie.domain,
1566
+ cookie.path,
1567
+ cookie.key,
1568
+ removeCookieCb
1569
+ );
1570
+ });
1571
+ });
1348
1572
  }
1349
1573
 
1350
- var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
1351
- jar._importCookies(serialized, function(err) {
1352
- if (err) {
1353
- return cb(err);
1574
+ static deserialize(strOrObj, store, cb) {
1575
+ if (arguments.length !== 3) {
1576
+ // store is optional
1577
+ cb = store;
1578
+ store = null;
1354
1579
  }
1355
- cb(null, jar);
1356
- });
1357
- };
1358
1580
 
1359
- CookieJar.deserializeSync = function(strOrObj, store) {
1360
- var serialized = typeof strOrObj === 'string' ?
1361
- JSON.parse(strOrObj) : strOrObj;
1362
- var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
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;
1589
+ }
1363
1590
 
1364
- // catch this mistake early:
1365
- if (!jar.store.synchronous) {
1366
- throw new Error('CookieJar store is not synchronous; use async API instead.');
1591
+ const jar = new CookieJar(store, serialized.rejectPublicSuffixes);
1592
+ jar._importCookies(serialized, err => {
1593
+ if (err) {
1594
+ return cb(err);
1595
+ }
1596
+ cb(null, jar);
1597
+ });
1367
1598
  }
1368
1599
 
1369
- jar._importCookiesSync(serialized);
1370
- return jar;
1371
- };
1372
- CookieJar.fromJSON = CookieJar.deserializeSync;
1600
+ static deserializeSync(strOrObj, store) {
1601
+ const serialized =
1602
+ typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj;
1603
+ const jar = new CookieJar(store, serialized.rejectPublicSuffixes);
1604
+
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
+ );
1610
+ }
1373
1611
 
1374
- CAN_BE_SYNC.push('clone');
1375
- CookieJar.prototype.clone = function(newStore, cb) {
1376
- if (arguments.length === 1) {
1377
- cb = newStore;
1378
- newStore = null;
1612
+ jar._importCookiesSync(serialized);
1613
+ return jar;
1379
1614
  }
1615
+ }
1616
+ CookieJar.fromJSON = CookieJar.deserializeSync;
1380
1617
 
1381
- this.serialize(function(err,serialized) {
1382
- if (err) {
1383
- return cb(err);
1384
- }
1385
- CookieJar.deserialize(newStore, serialized, cb);
1386
- });
1387
- };
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);
1388
1631
 
1389
1632
  // Use a closure to provide a true imperative API for synchronous stores.
1390
1633
  function syncWrap(method) {
1391
- return function() {
1634
+ return function(...args) {
1392
1635
  if (!this.store.synchronous) {
1393
- 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
+ );
1394
1639
  }
1395
1640
 
1396
- var args = Array.prototype.slice.call(arguments);
1397
- var syncErr, syncResult;
1398
- args.push(function syncCb(err, result) {
1641
+ let syncErr, syncResult;
1642
+ this[method](...args, (err, result) => {
1399
1643
  syncErr = err;
1400
1644
  syncResult = result;
1401
1645
  });
1402
- this[method].apply(this, args);
1403
1646
 
1404
1647
  if (syncErr) {
1405
1648
  throw syncErr;
@@ -1408,11 +1651,7 @@ function syncWrap(method) {
1408
1651
  };
1409
1652
  }
1410
1653
 
1411
- // wrap all declared CAN_BE_SYNC methods in the sync wrapper
1412
- CAN_BE_SYNC.forEach(function(method) {
1413
- CookieJar.prototype[method+'Sync'] = syncWrap(method);
1414
- });
1415
-
1654
+ exports.version = VERSION;
1416
1655
  exports.CookieJar = CookieJar;
1417
1656
  exports.Cookie = Cookie;
1418
1657
  exports.Store = Store;
@@ -1426,6 +1665,7 @@ exports.defaultPath = defaultPath;
1426
1665
  exports.pathMatch = pathMatch;
1427
1666
  exports.getPublicSuffix = pubsuffix.getPublicSuffix;
1428
1667
  exports.cookieCompare = cookieCompare;
1429
- exports.permuteDomain = require('./permuteDomain').permuteDomain;
1668
+ exports.permuteDomain = require("./permuteDomain").permuteDomain;
1430
1669
  exports.permutePath = permutePath;
1431
1670
  exports.canonicalDomain = canonicalDomain;
1671
+ exports.PrefixSecurityEnum = PrefixSecurityEnum;