tough-cookie 0.13.0 → 2.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/README.md +189 -127
- package/generate-pubsuffix.js +60 -26
- package/lib/cookie.js +330 -171
- package/lib/memstore.js +33 -6
- package/lib/pathMatch.js +61 -0
- package/lib/permuteDomain.js +56 -0
- package/lib/pubsuffix.js +25 -6
- package/lib/store.js +5 -1
- package/package.json +5 -8
- package/public-suffix.txt +5752 -672
- package/test/api_test.js +0 -16
- package/test/cookie_sorting_test.js +66 -0
- package/test/cookie_to_json_test.js +2 -2
- package/test/domain_and_path_test.js +2 -2
- package/test/ietf_test.js +1 -2
- package/test/jar_serialization_test.js +348 -0
- package/test/parsing_test.js +71 -133
- package/test/regression_test.js +15 -0
- package/tough-cookie-deps.tsv +0 -6
package/lib/cookie.js
CHANGED
|
@@ -33,6 +33,9 @@ var net = require('net');
|
|
|
33
33
|
var urlParse = require('url').parse;
|
|
34
34
|
var pubsuffix = require('./pubsuffix');
|
|
35
35
|
var Store = require('./store').Store;
|
|
36
|
+
var MemoryCookieStore = require('./memstore').MemoryCookieStore;
|
|
37
|
+
var pathMatch = require('./pathMatch').pathMatch;
|
|
38
|
+
var VERSION = require('../package.json').version;
|
|
36
39
|
|
|
37
40
|
var punycode;
|
|
38
41
|
try {
|
|
@@ -43,17 +46,11 @@ try {
|
|
|
43
46
|
|
|
44
47
|
var DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
|
|
45
48
|
|
|
46
|
-
// From RFC2616 S2.2:
|
|
47
|
-
var TOKEN = /[\x21\x23-\x26\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
|
|
48
|
-
|
|
49
49
|
// From RFC6265 S4.1.1
|
|
50
50
|
// note that it excludes \x3B ";"
|
|
51
51
|
var COOKIE_OCTET = /[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/;
|
|
52
52
|
var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'$');
|
|
53
53
|
|
|
54
|
-
// The name/key cannot be empty but the value can (S5.2):
|
|
55
|
-
var COOKIE_PAIR_STRICT = new RegExp('^('+TOKEN.source+'+)=("?)('+COOKIE_OCTET.source+'*)\\2$');
|
|
56
|
-
|
|
57
54
|
// Double quotes are part of the value (see: S4.1.1).
|
|
58
55
|
// '\r', '\n' and '\0' should be treated as a terminator in the "relaxed" mode
|
|
59
56
|
// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60)
|
|
@@ -61,11 +58,9 @@ var COOKIE_PAIR_STRICT = new RegExp('^('+TOKEN.source+'+)=("?)('+COOKIE_OCTET.so
|
|
|
61
58
|
// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L64)
|
|
62
59
|
var COOKIE_PAIR = /^([^=;]+)\s*=\s*(("?)[^\n\r\0]*\3)/;
|
|
63
60
|
|
|
64
|
-
// RFC6265 S4.1.1 defines
|
|
61
|
+
// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
|
|
65
62
|
// Note ';' is \x3B
|
|
66
|
-
var
|
|
67
|
-
var EXTENSION_AV = NON_CTL_SEMICOLON;
|
|
68
|
-
var PATH_VALUE = NON_CTL_SEMICOLON;
|
|
63
|
+
var PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;
|
|
69
64
|
|
|
70
65
|
// Used for checking whether or not there is a trailing semi-colon
|
|
71
66
|
var TRAILING_SEMICOLON = /;+$/;
|
|
@@ -90,11 +85,9 @@ var YEAR = /^(\d{2}|\d{4})$/; // 2 to 4 digits
|
|
|
90
85
|
var MAX_TIME = 2147483647000; // 31-bit max
|
|
91
86
|
var MIN_TIME = 0; // 31-bit min
|
|
92
87
|
|
|
93
|
-
var cookiesCreated = 0; // Number of cookies created in runtime
|
|
94
|
-
|
|
95
88
|
|
|
96
89
|
// RFC6265 S5.1.1 date parser:
|
|
97
|
-
function parseDate(str
|
|
90
|
+
function parseDate(str) {
|
|
98
91
|
if (!str) {
|
|
99
92
|
return;
|
|
100
93
|
}
|
|
@@ -115,9 +108,6 @@ function parseDate(str,strict) {
|
|
|
115
108
|
var month = null;
|
|
116
109
|
var year = null;
|
|
117
110
|
|
|
118
|
-
var date = new Date();
|
|
119
|
-
date.setMilliseconds(0);
|
|
120
|
-
|
|
121
111
|
for (var i=0; i<tokens.length; i++) {
|
|
122
112
|
var token = tokens[i].trim();
|
|
123
113
|
if (!token.length) {
|
|
@@ -208,7 +198,6 @@ function parseDate(str,strict) {
|
|
|
208
198
|
if (year < 1601) {
|
|
209
199
|
return; // 5. ... the year-value is less than 1601
|
|
210
200
|
}
|
|
211
|
-
continue;
|
|
212
201
|
}
|
|
213
202
|
}
|
|
214
203
|
}
|
|
@@ -325,52 +314,19 @@ function defaultPath(path) {
|
|
|
325
314
|
return path.slice(0, rightSlash);
|
|
326
315
|
}
|
|
327
316
|
|
|
328
|
-
/*
|
|
329
|
-
* "A request-path path-matches a given cookie-path if at least one of the
|
|
330
|
-
* following conditions holds:"
|
|
331
|
-
*/
|
|
332
|
-
function pathMatch(reqPath,cookiePath) {
|
|
333
|
-
// "o The cookie-path and the request-path are identical."
|
|
334
|
-
if (cookiePath === reqPath) {
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
var idx = reqPath.indexOf(cookiePath);
|
|
339
|
-
if (idx === 0) {
|
|
340
|
-
// "o The cookie-path is a prefix of the request-path, and the last
|
|
341
|
-
// character of the cookie-path is %x2F ("/")."
|
|
342
|
-
if (cookiePath.substr(-1) === "/") {
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// " o The cookie-path is a prefix of the request-path, and the first
|
|
347
|
-
// character of the request-path that is not included in the cookie- path
|
|
348
|
-
// is a %x2F ("/") character."
|
|
349
|
-
if (reqPath.substr(cookiePath.length,1) === "/") {
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
317
|
|
|
357
|
-
function parse(str
|
|
318
|
+
function parse(str) {
|
|
358
319
|
str = str.trim();
|
|
359
320
|
|
|
360
321
|
// S4.1.1 Trailing semi-colons are not part of the specification.
|
|
361
|
-
// If we are not in strict mode we remove the trailing semi-colons.
|
|
362
322
|
var semiColonCheck = TRAILING_SEMICOLON.exec(str);
|
|
363
323
|
if (semiColonCheck) {
|
|
364
|
-
if (strict) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
324
|
str = str.slice(0, semiColonCheck.index);
|
|
368
325
|
}
|
|
369
326
|
|
|
370
327
|
// We use a regex to parse the "name-value-pair" part of S5.2
|
|
371
328
|
var firstSemi = str.indexOf(';'); // S5.2 step 1
|
|
372
|
-
var
|
|
373
|
-
var result = pairRx.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));
|
|
329
|
+
var result = COOKIE_PAIR.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));
|
|
374
330
|
|
|
375
331
|
// Rx satisfies the "the name string is empty" and "lacks a %x3D ("=")"
|
|
376
332
|
// constraints as well as trimming any whitespace.
|
|
@@ -379,10 +335,8 @@ function parse(str, strict) {
|
|
|
379
335
|
}
|
|
380
336
|
|
|
381
337
|
var c = new Cookie();
|
|
382
|
-
c.key = result[1]
|
|
383
|
-
c.value =
|
|
384
|
-
c.key = c.key.trim();
|
|
385
|
-
c.value = c.value.trim();
|
|
338
|
+
c.key = result[1].trim();
|
|
339
|
+
c.value = result[2].trim();
|
|
386
340
|
|
|
387
341
|
if (firstSemi === -1) {
|
|
388
342
|
return c;
|
|
@@ -410,13 +364,9 @@ function parse(str, strict) {
|
|
|
410
364
|
var cookie_avs = unparsed.split(/\s*;\s*/);
|
|
411
365
|
while (cookie_avs.length) {
|
|
412
366
|
var av = cookie_avs.shift();
|
|
413
|
-
|
|
414
|
-
if (strict && !EXTENSION_AV.test(av)) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
367
|
var av_sep = av.indexOf('=');
|
|
419
368
|
var av_key, av_value;
|
|
369
|
+
|
|
420
370
|
if (av_sep === -1) {
|
|
421
371
|
av_key = av;
|
|
422
372
|
av_value = null;
|
|
@@ -426,47 +376,51 @@ function parse(str, strict) {
|
|
|
426
376
|
}
|
|
427
377
|
|
|
428
378
|
av_key = av_key.trim().toLowerCase();
|
|
379
|
+
|
|
429
380
|
if (av_value) {
|
|
430
381
|
av_value = av_value.trim();
|
|
431
382
|
}
|
|
432
383
|
|
|
433
384
|
switch(av_key) {
|
|
434
385
|
case 'expires': // S5.2.1
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
386
|
+
if (av_value) {
|
|
387
|
+
var exp = parseDate(av_value);
|
|
388
|
+
// "If the attribute-value failed to parse as a cookie date, ignore the
|
|
389
|
+
// cookie-av."
|
|
390
|
+
if (exp) {
|
|
391
|
+
// over and underflow not realistically a concern: V8's getTime() seems to
|
|
392
|
+
// store something larger than a 32-bit time_t (even with 32-bit node)
|
|
393
|
+
c.expires = exp;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
443
396
|
break;
|
|
444
397
|
|
|
445
398
|
case 'max-age': // S5.2.2
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
399
|
+
if (av_value) {
|
|
400
|
+
// "If the first character of the attribute-value is not a DIGIT or a "-"
|
|
401
|
+
// character ...[or]... If the remainder of attribute-value contains a
|
|
402
|
+
// non-DIGIT character, ignore the cookie-av."
|
|
403
|
+
if (/^-?[0-9]+$/.test(av_value)) {
|
|
404
|
+
var delta = parseInt(av_value, 10);
|
|
405
|
+
// "If delta-seconds is less than or equal to zero (0), let expiry-time
|
|
406
|
+
// be the earliest representable date and time."
|
|
407
|
+
c.setMaxAge(delta);
|
|
408
|
+
}
|
|
454
409
|
}
|
|
455
|
-
// "If delta-seconds is less than or equal to zero (0), let expiry-time
|
|
456
|
-
// be the earliest representable date and time."
|
|
457
|
-
c.setMaxAge(delta);
|
|
458
410
|
break;
|
|
459
411
|
|
|
460
412
|
case 'domain': // S5.2.3
|
|
461
413
|
// "If the attribute-value is empty, the behavior is undefined. However,
|
|
462
414
|
// the user agent SHOULD ignore the cookie-av entirely."
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
415
|
+
if (av_value) {
|
|
416
|
+
// S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E
|
|
417
|
+
// (".") character."
|
|
418
|
+
var domain = av_value.trim().replace(/^\./, '');
|
|
419
|
+
if (domain) {
|
|
420
|
+
// "Convert the cookie-domain to lower case."
|
|
421
|
+
c.domain = domain.toLowerCase();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
470
424
|
break;
|
|
471
425
|
|
|
472
426
|
case 'path': // S5.2.4
|
|
@@ -480,11 +434,7 @@ function parse(str, strict) {
|
|
|
480
434
|
* We'll represent the default-path as null since it depends on the
|
|
481
435
|
* context of the parsing.
|
|
482
436
|
*/
|
|
483
|
-
|
|
484
|
-
c.path = null;
|
|
485
|
-
if(strict){return;}else{break;}
|
|
486
|
-
}
|
|
487
|
-
c.path = av_value;
|
|
437
|
+
c.path = av_value && av_value[0] === "/" ? av_value : null;
|
|
488
438
|
break;
|
|
489
439
|
|
|
490
440
|
case 'secure': // S5.2.5
|
|
@@ -493,12 +443,10 @@ function parse(str, strict) {
|
|
|
493
443
|
* the user agent MUST append an attribute to the cookie-attribute-list
|
|
494
444
|
* with an attribute-name of Secure and an empty attribute-value."
|
|
495
445
|
*/
|
|
496
|
-
if (av_value != null) { if(strict){return;} }
|
|
497
446
|
c.secure = true;
|
|
498
447
|
break;
|
|
499
448
|
|
|
500
449
|
case 'httponly': // S5.2.6 -- effectively the same as 'secure'
|
|
501
|
-
if (av_value != null) { if(strict){return;} }
|
|
502
450
|
c.httpOnly = true;
|
|
503
451
|
break;
|
|
504
452
|
|
|
@@ -509,47 +457,60 @@ function parse(str, strict) {
|
|
|
509
457
|
}
|
|
510
458
|
}
|
|
511
459
|
|
|
512
|
-
// ensure a default date for sorting:
|
|
513
|
-
c.creation = new Date();
|
|
514
|
-
//NOTE: add runtime index for the cookieCompare() to resolve the situation when Date's precision is not enough .
|
|
515
|
-
//Store initial UTC time as well, so we will be able to determine if we need to fallback to the Date object.
|
|
516
|
-
c._creationRuntimeIdx = ++cookiesCreated;
|
|
517
|
-
c._initialCreationTime = c.creation.getTime();
|
|
518
460
|
return c;
|
|
519
461
|
}
|
|
520
462
|
|
|
463
|
+
// avoid the V8 deoptimization monster!
|
|
464
|
+
function jsonParse(str) {
|
|
465
|
+
var obj;
|
|
466
|
+
try {
|
|
467
|
+
obj = JSON.parse(str);
|
|
468
|
+
} catch (e) {
|
|
469
|
+
return e;
|
|
470
|
+
}
|
|
471
|
+
return obj;
|
|
472
|
+
}
|
|
473
|
+
|
|
521
474
|
function fromJSON(str) {
|
|
522
475
|
if (!str) {
|
|
523
476
|
return null;
|
|
524
477
|
}
|
|
525
478
|
|
|
526
479
|
var obj;
|
|
527
|
-
|
|
528
|
-
obj =
|
|
529
|
-
|
|
530
|
-
|
|
480
|
+
if (typeof str === 'string') {
|
|
481
|
+
obj = jsonParse(str);
|
|
482
|
+
if (obj instanceof Error) {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
// assume it's an Object
|
|
487
|
+
obj = str;
|
|
531
488
|
}
|
|
532
489
|
|
|
533
490
|
var c = new Cookie();
|
|
534
|
-
for (var i=0; i<
|
|
535
|
-
var prop =
|
|
536
|
-
if (obj[prop]
|
|
537
|
-
|
|
491
|
+
for (var i=0; i<Cookie.serializableProperties.length; i++) {
|
|
492
|
+
var prop = Cookie.serializableProperties[i];
|
|
493
|
+
if (obj[prop] === undefined ||
|
|
494
|
+
obj[prop] === Cookie.prototype[prop])
|
|
495
|
+
{
|
|
496
|
+
continue; // leave as prototype default
|
|
538
497
|
}
|
|
498
|
+
|
|
539
499
|
if (prop === 'expires' ||
|
|
540
500
|
prop === 'creation' ||
|
|
541
501
|
prop === 'lastAccessed')
|
|
542
502
|
{
|
|
543
|
-
|
|
503
|
+
if (obj[prop] === null) {
|
|
504
|
+
c[prop] = null;
|
|
505
|
+
} else {
|
|
506
|
+
c[prop] = obj[prop] == "Infinity" ?
|
|
507
|
+
"Infinity" : new Date(obj[prop]);
|
|
508
|
+
}
|
|
544
509
|
} else {
|
|
545
510
|
c[prop] = obj[prop];
|
|
546
511
|
}
|
|
547
512
|
}
|
|
548
513
|
|
|
549
|
-
|
|
550
|
-
// ensure a default date for sorting:
|
|
551
|
-
c.creation = c.creation || new Date();
|
|
552
|
-
|
|
553
514
|
return c;
|
|
554
515
|
}
|
|
555
516
|
|
|
@@ -563,45 +524,28 @@ function fromJSON(str) {
|
|
|
563
524
|
*/
|
|
564
525
|
|
|
565
526
|
function cookieCompare(a,b) {
|
|
527
|
+
var cmp = 0;
|
|
528
|
+
|
|
566
529
|
// descending for length: b CMP a
|
|
567
|
-
var
|
|
568
|
-
|
|
569
|
-
|
|
530
|
+
var aPathLen = a.path ? a.path.length : 0;
|
|
531
|
+
var bPathLen = b.path ? b.path.length : 0;
|
|
532
|
+
cmp = bPathLen - aPathLen;
|
|
533
|
+
if (cmp !== 0) {
|
|
534
|
+
return cmp;
|
|
570
535
|
}
|
|
571
536
|
|
|
537
|
+
// ascending for time: a CMP b
|
|
572
538
|
var aTime = a.creation ? a.creation.getTime() : MAX_TIME;
|
|
573
539
|
var bTime = b.creation ? b.creation.getTime() : MAX_TIME;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if(aTime === bTime && aTime === a._initialCreationTime && bTime === b._initialCreationTime) {
|
|
578
|
-
return a._creationRuntimeIdx - b._creationRuntimeIdx;
|
|
540
|
+
cmp = aTime - bTime;
|
|
541
|
+
if (cmp !== 0) {
|
|
542
|
+
return cmp;
|
|
579
543
|
}
|
|
580
544
|
|
|
581
|
-
//
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Gives the permutation of all possible domainMatch()es of a given domain. The
|
|
586
|
-
// array is in shortest-to-longest order. Handy for indexing.
|
|
587
|
-
function permuteDomain(domain) {
|
|
588
|
-
var pubSuf = pubsuffix.getPublicSuffix(domain);
|
|
589
|
-
if (!pubSuf) {
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
if (pubSuf == domain) {
|
|
593
|
-
return [domain];
|
|
594
|
-
}
|
|
545
|
+
// break ties for the same millisecond (precision of JavaScript's clock)
|
|
546
|
+
cmp = a.creationIndex - b.creationIndex;
|
|
595
547
|
|
|
596
|
-
|
|
597
|
-
var parts = prefix.split('.').reverse();
|
|
598
|
-
var cur = pubSuf;
|
|
599
|
-
var permutations = [cur];
|
|
600
|
-
while (parts.length) {
|
|
601
|
-
cur = parts.shift()+'.'+cur;
|
|
602
|
-
permutations.push(cur);
|
|
603
|
-
}
|
|
604
|
-
return permutations;
|
|
548
|
+
return cmp;
|
|
605
549
|
}
|
|
606
550
|
|
|
607
551
|
// Gives the permutation of all possible pathMatch()es of a given path. The
|
|
@@ -627,20 +571,46 @@ function permutePath(path) {
|
|
|
627
571
|
}
|
|
628
572
|
|
|
629
573
|
function getCookieContext(url) {
|
|
630
|
-
|
|
574
|
+
if (url instanceof Object) {
|
|
575
|
+
return url;
|
|
576
|
+
}
|
|
577
|
+
// NOTE: decodeURI will throw on malformed URIs (see GH-32).
|
|
578
|
+
// Therefore, we will just skip decoding for such URIs.
|
|
579
|
+
try {
|
|
580
|
+
url = decodeURI(url);
|
|
581
|
+
}
|
|
582
|
+
catch(err) {
|
|
583
|
+
// Silently swallow error
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return urlParse(url);
|
|
631
587
|
}
|
|
632
588
|
|
|
633
|
-
function Cookie
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
589
|
+
function Cookie(opts) {
|
|
590
|
+
opts = opts || {};
|
|
591
|
+
|
|
592
|
+
Object.keys(opts).forEach(function(prop) {
|
|
593
|
+
if (Cookie.prototype.hasOwnProperty(prop) &&
|
|
594
|
+
Cookie.prototype[prop] !== opts[prop] &&
|
|
595
|
+
prop.substr(0,1) !== '_')
|
|
596
|
+
{
|
|
597
|
+
this[prop] = opts[prop];
|
|
640
598
|
}
|
|
641
|
-
}
|
|
599
|
+
}, this);
|
|
600
|
+
|
|
601
|
+
this.creation = this.creation || new Date();
|
|
602
|
+
|
|
603
|
+
// used to break creation ties in cookieCompare():
|
|
604
|
+
Object.defineProperty(this, 'creationIndex', {
|
|
605
|
+
configurable: false,
|
|
606
|
+
enumerable: false, // important for assert.deepEqual checks
|
|
607
|
+
writable: true,
|
|
608
|
+
value: ++Cookie.cookiesCreated
|
|
609
|
+
});
|
|
642
610
|
}
|
|
643
611
|
|
|
612
|
+
Cookie.cookiesCreated = 0; // incremented each time a cookie is created
|
|
613
|
+
|
|
644
614
|
Cookie.parse = parse;
|
|
645
615
|
Cookie.fromJSON = fromJSON;
|
|
646
616
|
|
|
@@ -660,17 +630,22 @@ Cookie.prototype.extensions = null;
|
|
|
660
630
|
Cookie.prototype.hostOnly = null; // boolean when set
|
|
661
631
|
Cookie.prototype.pathIsDefault = null; // boolean when set
|
|
662
632
|
Cookie.prototype.creation = null; // Date when set; defaulted by Cookie.parse
|
|
663
|
-
Cookie.prototype._initialCreationTime = null; // Used to determine if cookie.creation was modified
|
|
664
|
-
Cookie.prototype._creationRuntimeIdx = null; // Runtime index of the created cookie, used in cookieCompare()
|
|
665
633
|
Cookie.prototype.lastAccessed = null; // Date when set
|
|
634
|
+
Object.defineProperty(Cookie.prototype, 'creationIndex', {
|
|
635
|
+
configurable: true,
|
|
636
|
+
enumerable: false,
|
|
637
|
+
writable: true,
|
|
638
|
+
value: 0
|
|
639
|
+
});
|
|
666
640
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
return
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
641
|
+
Cookie.serializableProperties = Object.keys(Cookie.prototype)
|
|
642
|
+
.filter(function(prop) {
|
|
643
|
+
return !(
|
|
644
|
+
Cookie.prototype[prop] instanceof Function ||
|
|
645
|
+
prop === 'creationIndex' ||
|
|
646
|
+
prop.substr(0,1) === '_'
|
|
647
|
+
);
|
|
648
|
+
});
|
|
674
649
|
|
|
675
650
|
Cookie.prototype.inspect = function inspect() {
|
|
676
651
|
var now = Date.now();
|
|
@@ -681,11 +656,51 @@ Cookie.prototype.inspect = function inspect() {
|
|
|
681
656
|
'"';
|
|
682
657
|
};
|
|
683
658
|
|
|
659
|
+
Cookie.prototype.toJSON = function() {
|
|
660
|
+
var obj = {};
|
|
661
|
+
|
|
662
|
+
var props = Cookie.serializableProperties;
|
|
663
|
+
for (var i=0; i<props.length; i++) {
|
|
664
|
+
var prop = props[i];
|
|
665
|
+
if (this[prop] === Cookie.prototype[prop]) {
|
|
666
|
+
continue; // leave as prototype default
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (prop === 'expires' ||
|
|
670
|
+
prop === 'creation' ||
|
|
671
|
+
prop === 'lastAccessed')
|
|
672
|
+
{
|
|
673
|
+
if (this[prop] === null) {
|
|
674
|
+
obj[prop] = null;
|
|
675
|
+
} else {
|
|
676
|
+
obj[prop] = this[prop] == "Infinity" ? // intentionally not ===
|
|
677
|
+
"Infinity" : this[prop].toISOString();
|
|
678
|
+
}
|
|
679
|
+
} else if (prop === 'maxAge') {
|
|
680
|
+
if (this[prop] !== null) {
|
|
681
|
+
// again, intentionally not ===
|
|
682
|
+
obj[prop] = (this[prop] == Infinity || this[prop] == -Infinity) ?
|
|
683
|
+
this[prop].toString() : this[prop];
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
if (this[prop] !== Cookie.prototype[prop]) {
|
|
687
|
+
obj[prop] = this[prop];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return obj;
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
Cookie.prototype.clone = function() {
|
|
696
|
+
return fromJSON(this.toJSON());
|
|
697
|
+
};
|
|
698
|
+
|
|
684
699
|
Cookie.prototype.validate = function validate() {
|
|
685
700
|
if (!COOKIE_OCTETS.test(this.value)) {
|
|
686
701
|
return false;
|
|
687
702
|
}
|
|
688
|
-
if (this.expires != Infinity && !(this.expires instanceof Date) && !parseDate(this.expires
|
|
703
|
+
if (this.expires != Infinity && !(this.expires instanceof Date) && !parseDate(this.expires)) {
|
|
689
704
|
return false;
|
|
690
705
|
}
|
|
691
706
|
if (this.maxAge != null && this.maxAge <= 0) {
|
|
@@ -805,7 +820,7 @@ Cookie.prototype.TTL = function TTL(now) {
|
|
|
805
820
|
// elsewhere)
|
|
806
821
|
Cookie.prototype.expiryTime = function expiryTime(now) {
|
|
807
822
|
if (this.maxAge != null) {
|
|
808
|
-
var relativeTo = this.creation ||
|
|
823
|
+
var relativeTo = now || this.creation || new Date();
|
|
809
824
|
var age = (this.maxAge <= 0) ? -Infinity : this.maxAge*1000;
|
|
810
825
|
return relativeTo.getTime() + age;
|
|
811
826
|
}
|
|
@@ -843,16 +858,13 @@ Cookie.prototype.canonicalizedDomain = function canonicalizedDomain() {
|
|
|
843
858
|
return canonicalDomain(this.domain);
|
|
844
859
|
};
|
|
845
860
|
|
|
846
|
-
|
|
847
|
-
var memstore;
|
|
848
861
|
function CookieJar(store, rejectPublicSuffixes) {
|
|
849
862
|
if (rejectPublicSuffixes != null) {
|
|
850
863
|
this.rejectPublicSuffixes = rejectPublicSuffixes;
|
|
851
864
|
}
|
|
852
865
|
|
|
853
866
|
if (!store) {
|
|
854
|
-
|
|
855
|
-
store = new memstore.MemoryCookieStore();
|
|
867
|
+
store = new MemoryCookieStore();
|
|
856
868
|
}
|
|
857
869
|
this.store = store;
|
|
858
870
|
}
|
|
@@ -873,7 +885,7 @@ CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
|
|
|
873
885
|
|
|
874
886
|
// S5.3 step 1
|
|
875
887
|
if (!(cookie instanceof Cookie)) {
|
|
876
|
-
cookie = Cookie.parse(cookie
|
|
888
|
+
cookie = Cookie.parse(cookie);
|
|
877
889
|
}
|
|
878
890
|
if (!cookie) {
|
|
879
891
|
err = new Error("Cookie failed to parse");
|
|
@@ -958,6 +970,7 @@ CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
|
|
|
958
970
|
return cb(options.ignoreError ? null : err);
|
|
959
971
|
}
|
|
960
972
|
cookie.creation = oldCookie.creation; // step 11.3
|
|
973
|
+
cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker
|
|
961
974
|
cookie.lastAccessed = now;
|
|
962
975
|
// Step 11.4 (delete cookie) is implied by just setting the new one:
|
|
963
976
|
store.updateCookie(oldCookie, cookie, next); // step 12
|
|
@@ -1104,6 +1117,151 @@ CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {
|
|
|
1104
1117
|
this.getCookies.apply(this,args);
|
|
1105
1118
|
};
|
|
1106
1119
|
|
|
1120
|
+
CAN_BE_SYNC.push('serialize');
|
|
1121
|
+
CookieJar.prototype.serialize = function(cb) {
|
|
1122
|
+
var type = this.store.constructor.name;
|
|
1123
|
+
if (type === 'Object') {
|
|
1124
|
+
type = null;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// update README.md "Serialization Format" if you change this, please!
|
|
1128
|
+
var serialized = {
|
|
1129
|
+
// The version of tough-cookie that serialized this jar. Generally a good
|
|
1130
|
+
// practice since future versions can make data import decisions based on
|
|
1131
|
+
// known past behavior. When/if this matters, use `semver`.
|
|
1132
|
+
version: 'tough-cookie@'+VERSION,
|
|
1133
|
+
|
|
1134
|
+
// add the store type, to make humans happy:
|
|
1135
|
+
storeType: type,
|
|
1136
|
+
|
|
1137
|
+
// CookieJar configuration:
|
|
1138
|
+
rejectPublicSuffixes: !!this.rejectPublicSuffixes,
|
|
1139
|
+
|
|
1140
|
+
// this gets filled from getAllCookies:
|
|
1141
|
+
cookies: []
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
if (!(this.store.getAllCookies &&
|
|
1145
|
+
typeof this.store.getAllCookies === 'function'))
|
|
1146
|
+
{
|
|
1147
|
+
return cb(new Error('store does not support getAllCookies and cannot be serialized'));
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
this.store.getAllCookies(function(err,cookies) {
|
|
1151
|
+
if (err) {
|
|
1152
|
+
return cb(err);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
serialized.cookies = cookies.map(function(cookie) {
|
|
1156
|
+
// convert to serialized 'raw' cookies
|
|
1157
|
+
cookie = (cookie instanceof Cookie) ? cookie.toJSON() : cookie;
|
|
1158
|
+
|
|
1159
|
+
// Remove the index so new ones get assigned during deserialization
|
|
1160
|
+
delete cookie.creationIndex;
|
|
1161
|
+
|
|
1162
|
+
return cookie;
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
return cb(null, serialized);
|
|
1166
|
+
});
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
// well-known name that JSON.stringify calls
|
|
1170
|
+
CookieJar.prototype.toJSON = function() {
|
|
1171
|
+
return this.serializeSync();
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// use the class method CookieJar.deserialize instead of calling this directly
|
|
1175
|
+
CAN_BE_SYNC.push('_importCookies');
|
|
1176
|
+
CookieJar.prototype._importCookies = function(serialized, cb) {
|
|
1177
|
+
var jar = this;
|
|
1178
|
+
var cookies = serialized.cookies;
|
|
1179
|
+
if (!cookies || !Array.isArray(cookies)) {
|
|
1180
|
+
return cb(new Error('serialized jar has no cookies array'));
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
function putNext(err) {
|
|
1184
|
+
if (err) {
|
|
1185
|
+
return cb(err);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
if (!cookies.length) {
|
|
1189
|
+
return cb(err, jar);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
var cookie;
|
|
1193
|
+
try {
|
|
1194
|
+
cookie = fromJSON(cookies.shift());
|
|
1195
|
+
} catch (e) {
|
|
1196
|
+
return cb(e);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
if (cookie === null) {
|
|
1200
|
+
return putNext(null); // skip this cookie
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
jar.store.putCookie(cookie, putNext);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
putNext();
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
CookieJar.deserialize = function(strOrObj, store, cb) {
|
|
1210
|
+
if (arguments.length !== 3) {
|
|
1211
|
+
// store is optional
|
|
1212
|
+
cb = store;
|
|
1213
|
+
store = null;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
var serialized;
|
|
1217
|
+
if (typeof strOrObj === 'string') {
|
|
1218
|
+
serialized = jsonParse(strOrObj);
|
|
1219
|
+
if (serialized instanceof Error) {
|
|
1220
|
+
return cb(serialized);
|
|
1221
|
+
}
|
|
1222
|
+
} else {
|
|
1223
|
+
serialized = strOrObj;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
|
|
1227
|
+
jar._importCookies(serialized, function(err) {
|
|
1228
|
+
if (err) {
|
|
1229
|
+
return cb(err);
|
|
1230
|
+
}
|
|
1231
|
+
cb(null, jar);
|
|
1232
|
+
});
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
CookieJar.fromJSON = CookieJar.deserializeSync;
|
|
1236
|
+
CookieJar.deserializeSync = function(strOrObj, store) {
|
|
1237
|
+
var serialized = typeof strOrObj === 'string' ?
|
|
1238
|
+
JSON.parse(strOrObj) : strOrObj;
|
|
1239
|
+
var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
|
|
1240
|
+
|
|
1241
|
+
// catch this mistake early:
|
|
1242
|
+
if (!jar.store.synchronous) {
|
|
1243
|
+
throw new Error('CookieJar store is not synchronous; use async API instead.');
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
jar._importCookiesSync(serialized);
|
|
1247
|
+
return jar;
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
CAN_BE_SYNC.push('clone');
|
|
1251
|
+
CookieJar.prototype.clone = function(newStore, cb) {
|
|
1252
|
+
if (arguments.length === 1) {
|
|
1253
|
+
cb = newStore;
|
|
1254
|
+
newStore = null;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
this.serialize(function(err,serialized) {
|
|
1258
|
+
if (err) {
|
|
1259
|
+
return cb(err);
|
|
1260
|
+
}
|
|
1261
|
+
CookieJar.deserialize(newStore, serialized, cb);
|
|
1262
|
+
});
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1107
1265
|
// Use a closure to provide a true imperative API for synchronous stores.
|
|
1108
1266
|
function syncWrap(method) {
|
|
1109
1267
|
return function() {
|
|
@@ -1135,6 +1293,7 @@ module.exports = {
|
|
|
1135
1293
|
CookieJar: CookieJar,
|
|
1136
1294
|
Cookie: Cookie,
|
|
1137
1295
|
Store: Store,
|
|
1296
|
+
MemoryCookieStore: MemoryCookieStore,
|
|
1138
1297
|
parseDate: parseDate,
|
|
1139
1298
|
formatDate: formatDate,
|
|
1140
1299
|
parse: parse,
|
|
@@ -1144,7 +1303,7 @@ module.exports = {
|
|
|
1144
1303
|
pathMatch: pathMatch,
|
|
1145
1304
|
getPublicSuffix: pubsuffix.getPublicSuffix,
|
|
1146
1305
|
cookieCompare: cookieCompare,
|
|
1147
|
-
permuteDomain: permuteDomain,
|
|
1306
|
+
permuteDomain: require('./permuteDomain').permuteDomain,
|
|
1148
1307
|
permutePath: permutePath,
|
|
1149
|
-
canonicalDomain: canonicalDomain
|
|
1308
|
+
canonicalDomain: canonicalDomain
|
|
1150
1309
|
};
|