tough-cookie 1.2.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/lib/cookie.js CHANGED
@@ -35,6 +35,7 @@ var pubsuffix = require('./pubsuffix');
35
35
  var Store = require('./store').Store;
36
36
  var MemoryCookieStore = require('./memstore').MemoryCookieStore;
37
37
  var pathMatch = require('./pathMatch').pathMatch;
38
+ var VERSION = require('../package.json').version;
38
39
 
39
40
  var punycode;
40
41
  try {
@@ -84,8 +85,6 @@ var YEAR = /^(\d{2}|\d{4})$/; // 2 to 4 digits
84
85
  var MAX_TIME = 2147483647000; // 31-bit max
85
86
  var MIN_TIME = 0; // 31-bit min
86
87
 
87
- var cookiesCreated = 0; // Number of cookies created in runtime
88
-
89
88
 
90
89
  // RFC6265 S5.1.1 date parser:
91
90
  function parseDate(str) {
@@ -458,47 +457,60 @@ function parse(str) {
458
457
  }
459
458
  }
460
459
 
461
- // ensure a default date for sorting:
462
- c.creation = new Date();
463
- //NOTE: add runtime index for the cookieCompare() to resolve the situation when Date's precision is not enough .
464
- //Store initial UTC time as well, so we will be able to determine if we need to fallback to the Date object.
465
- c._creationRuntimeIdx = ++cookiesCreated;
466
- c._initialCreationTime = c.creation.getTime();
467
460
  return c;
468
461
  }
469
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
+
470
474
  function fromJSON(str) {
471
475
  if (!str) {
472
476
  return null;
473
477
  }
474
478
 
475
479
  var obj;
476
- try {
477
- obj = JSON.parse(str);
478
- } catch (e) {
479
- return null;
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;
480
488
  }
481
489
 
482
490
  var c = new Cookie();
483
- for (var i=0; i<numCookieProperties; i++) {
484
- var prop = cookieProperties[i];
485
- if (obj[prop] == null) {
486
- continue;
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
487
497
  }
498
+
488
499
  if (prop === 'expires' ||
489
500
  prop === 'creation' ||
490
501
  prop === 'lastAccessed')
491
502
  {
492
- c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]);
503
+ if (obj[prop] === null) {
504
+ c[prop] = null;
505
+ } else {
506
+ c[prop] = obj[prop] == "Infinity" ?
507
+ "Infinity" : new Date(obj[prop]);
508
+ }
493
509
  } else {
494
510
  c[prop] = obj[prop];
495
511
  }
496
512
  }
497
513
 
498
-
499
- // ensure a default date for sorting:
500
- c.creation = c.creation || new Date();
501
-
502
514
  return c;
503
515
  }
504
516
 
@@ -512,23 +524,28 @@ function fromJSON(str) {
512
524
  */
513
525
 
514
526
  function cookieCompare(a,b) {
527
+ var cmp = 0;
528
+
515
529
  // descending for length: b CMP a
516
- var deltaLen = (b.path ? b.path.length : 0) - (a.path ? a.path.length : 0);
517
- if (deltaLen !== 0) {
518
- return deltaLen;
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;
519
535
  }
520
536
 
537
+ // ascending for time: a CMP b
521
538
  var aTime = a.creation ? a.creation.getTime() : MAX_TIME;
522
539
  var bTime = b.creation ? b.creation.getTime() : MAX_TIME;
523
-
524
- // NOTE: if creation dates are equal and they were not modified from the outside,
525
- // then use _creationRuntimeIdx for the comparison.
526
- if(aTime === bTime && aTime === a._initialCreationTime && bTime === b._initialCreationTime) {
527
- return a._creationRuntimeIdx - b._creationRuntimeIdx;
540
+ cmp = aTime - bTime;
541
+ if (cmp !== 0) {
542
+ return cmp;
528
543
  }
529
544
 
530
- // ascending for time: a CMP b
531
- return aTime - bTime;
545
+ // break ties for the same millisecond (precision of JavaScript's clock)
546
+ cmp = a.creationIndex - b.creationIndex;
547
+
548
+ return cmp;
532
549
  }
533
550
 
534
551
  // Gives the permutation of all possible pathMatch()es of a given path. The
@@ -569,17 +586,31 @@ function getCookieContext(url) {
569
586
  return urlParse(url);
570
587
  }
571
588
 
572
- function Cookie (opts) {
573
- if (typeof opts !== "object") {
574
- return;
575
- }
576
- Object.keys(opts).forEach(function (key) {
577
- if (Cookie.prototype.hasOwnProperty(key)) {
578
- this[key] = opts[key] || Cookie.prototype[key];
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];
579
598
  }
580
- }.bind(this));
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
+ });
581
610
  }
582
611
 
612
+ Cookie.cookiesCreated = 0; // incremented each time a cookie is created
613
+
583
614
  Cookie.parse = parse;
584
615
  Cookie.fromJSON = fromJSON;
585
616
 
@@ -599,17 +630,22 @@ Cookie.prototype.extensions = null;
599
630
  Cookie.prototype.hostOnly = null; // boolean when set
600
631
  Cookie.prototype.pathIsDefault = null; // boolean when set
601
632
  Cookie.prototype.creation = null; // Date when set; defaulted by Cookie.parse
602
- Cookie.prototype._initialCreationTime = null; // Used to determine if cookie.creation was modified
603
- Cookie.prototype._creationRuntimeIdx = null; // Runtime index of the created cookie, used in cookieCompare()
604
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
+ });
605
640
 
606
- var cookieProperties = Object.freeze(Object.keys(Cookie.prototype).map(function(p) {
607
- if (p instanceof Function) {
608
- return;
609
- }
610
- return p;
611
- }));
612
- var numCookieProperties = cookieProperties.length;
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
+ });
613
649
 
614
650
  Cookie.prototype.inspect = function inspect() {
615
651
  var now = Date.now();
@@ -620,6 +656,46 @@ Cookie.prototype.inspect = function inspect() {
620
656
  '"';
621
657
  };
622
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
+
623
699
  Cookie.prototype.validate = function validate() {
624
700
  if (!COOKIE_OCTETS.test(this.value)) {
625
701
  return false;
@@ -744,7 +820,7 @@ Cookie.prototype.TTL = function TTL(now) {
744
820
  // elsewhere)
745
821
  Cookie.prototype.expiryTime = function expiryTime(now) {
746
822
  if (this.maxAge != null) {
747
- var relativeTo = this.creation || now || new Date();
823
+ var relativeTo = now || this.creation || new Date();
748
824
  var age = (this.maxAge <= 0) ? -Infinity : this.maxAge*1000;
749
825
  return relativeTo.getTime() + age;
750
826
  }
@@ -782,7 +858,6 @@ Cookie.prototype.canonicalizedDomain = function canonicalizedDomain() {
782
858
  return canonicalDomain(this.domain);
783
859
  };
784
860
 
785
-
786
861
  function CookieJar(store, rejectPublicSuffixes) {
787
862
  if (rejectPublicSuffixes != null) {
788
863
  this.rejectPublicSuffixes = rejectPublicSuffixes;
@@ -895,6 +970,7 @@ CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
895
970
  return cb(options.ignoreError ? null : err);
896
971
  }
897
972
  cookie.creation = oldCookie.creation; // step 11.3
973
+ cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker
898
974
  cookie.lastAccessed = now;
899
975
  // Step 11.4 (delete cookie) is implied by just setting the new one:
900
976
  store.updateCookie(oldCookie, cookie, next); // step 12
@@ -1041,6 +1117,151 @@ CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {
1041
1117
  this.getCookies.apply(this,args);
1042
1118
  };
1043
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
+
1044
1265
  // Use a closure to provide a true imperative API for synchronous stores.
1045
1266
  function syncWrap(method) {
1046
1267
  return function() {
package/lib/memstore.js CHANGED
@@ -41,6 +41,8 @@ function MemoryCookieStore() {
41
41
  util.inherits(MemoryCookieStore, Store);
42
42
  exports.MemoryCookieStore = MemoryCookieStore;
43
43
  MemoryCookieStore.prototype.idx = null;
44
+
45
+ // Since it's just a struct in RAM, this Store is synchronous
44
46
  MemoryCookieStore.prototype.synchronous = true;
45
47
 
46
48
  // force a default depth:
@@ -116,21 +118,21 @@ MemoryCookieStore.prototype.putCookie = function(cookie, cb) {
116
118
  cb(null);
117
119
  };
118
120
 
119
- MemoryCookieStore.prototype.updateCookie = function updateCookie(oldCookie, newCookie, cb) {
121
+ MemoryCookieStore.prototype.updateCookie = function(oldCookie, newCookie, cb) {
120
122
  // updateCookie() may avoid updating cookies that are identical. For example,
121
123
  // lastAccessed may not be important to some stores and an equality
122
124
  // comparison could exclude that field.
123
125
  this.putCookie(newCookie,cb);
124
126
  };
125
127
 
126
- MemoryCookieStore.prototype.removeCookie = function removeCookie(domain, path, key, cb) {
128
+ MemoryCookieStore.prototype.removeCookie = function(domain, path, key, cb) {
127
129
  if (this.idx[domain] && this.idx[domain][path] && this.idx[domain][path][key]) {
128
130
  delete this.idx[domain][path][key];
129
131
  }
130
132
  cb(null);
131
133
  };
132
134
 
133
- MemoryCookieStore.prototype.removeCookies = function removeCookies(domain, path, cb) {
135
+ MemoryCookieStore.prototype.removeCookies = function(domain, path, cb) {
134
136
  if (this.idx[domain]) {
135
137
  if (path) {
136
138
  delete this.idx[domain][path];
@@ -140,3 +142,29 @@ MemoryCookieStore.prototype.removeCookies = function removeCookies(domain, path,
140
142
  }
141
143
  return cb(null);
142
144
  };
145
+
146
+ MemoryCookieStore.prototype.getAllCookies = function(cb) {
147
+ var cookies = [];
148
+ var idx = this.idx;
149
+
150
+ var domains = Object.keys(idx);
151
+ domains.forEach(function(domain) {
152
+ var paths = Object.keys(idx[domain]);
153
+ paths.forEach(function(path) {
154
+ var keys = Object.keys(idx[domain][path]);
155
+ keys.forEach(function(key) {
156
+ if (key !== null) {
157
+ cookies.push(idx[domain][path][key]);
158
+ }
159
+ });
160
+ });
161
+ });
162
+
163
+ // Sort by creationIndex so deserializing retains the creation order.
164
+ // When implementing your own store, this SHOULD retain the order too
165
+ cookies.sort(function(a,b) {
166
+ return (a.creationIndex||0) - (b.creationIndex||0);
167
+ });
168
+
169
+ cb(null, cookies);
170
+ };
package/lib/store.js CHANGED
@@ -62,6 +62,10 @@ Store.prototype.removeCookie = function(domain, path, key, cb) {
62
62
  throw new Error('removeCookie is not implemented');
63
63
  };
64
64
 
65
- Store.prototype.removeCookies = function removeCookies(domain, path, cb) {
65
+ Store.prototype.removeCookies = function(domain, path, cb) {
66
66
  throw new Error('removeCookies is not implemented');
67
67
  };
68
+
69
+ Store.prototype.getAllCookies = function(cb) {
70
+ throw new Error('getAllCookies is not implemented (therefore jar cannot be serialized)');
71
+ };
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "RFC6265",
14
14
  "RFC2965"
15
15
  ],
16
- "version": "1.2.0",
16
+ "version": "2.0.0",
17
17
  "homepage": "https://github.com/SalesforceEng/tough-cookie",
18
18
  "repository": {
19
19
  "type": "git",