tough-cookie 1.2.0 → 2.2.1

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 {
@@ -48,14 +49,20 @@ var DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
48
49
  // From RFC6265 S4.1.1
49
50
  // note that it excludes \x3B ";"
50
51
  var COOKIE_OCTET = /[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/;
51
- var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'$');
52
+ var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'+$');
53
+
54
+ var CONTROL_CHARS = /[\x00-\x1F]/;
52
55
 
53
56
  // Double quotes are part of the value (see: S4.1.1).
54
57
  // '\r', '\n' and '\0' should be treated as a terminator in the "relaxed" mode
55
58
  // (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60)
56
59
  // '=' and ';' are attribute/values separators
57
60
  // (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L64)
58
- var COOKIE_PAIR = /^([^=;]+)\s*=\s*(("?)[^\n\r\0]*\3)/;
61
+ var COOKIE_PAIR = /^(([^=;]+))\s*=\s*(("?)[^\n\r\0]*\3)/
62
+
63
+ // Used to parse non-RFC-compliant cookies like '=abc' when given the `loose`
64
+ // option in Cookie.parse:
65
+ var LOOSE_COOKIE_PAIR = /^((?:=)?([^=;]*)\s*=\s*)?(("?)[^\n\r\0]*\3)/;
59
66
 
60
67
  // RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
61
68
  // Note ';' is \x3B
@@ -84,8 +91,6 @@ var YEAR = /^(\d{2}|\d{4})$/; // 2 to 4 digits
84
91
  var MAX_TIME = 2147483647000; // 31-bit max
85
92
  var MIN_TIME = 0; // 31-bit min
86
93
 
87
- var cookiesCreated = 0; // Number of cookies created in runtime
88
-
89
94
 
90
95
  // RFC6265 S5.1.1 date parser:
91
96
  function parseDate(str) {
@@ -316,7 +321,10 @@ function defaultPath(path) {
316
321
  }
317
322
 
318
323
 
319
- function parse(str) {
324
+ function parse(str, options) {
325
+ if (!options || typeof options !== 'object') {
326
+ options = {};
327
+ }
320
328
  str = str.trim();
321
329
 
322
330
  // S4.1.1 Trailing semi-colons are not part of the specification.
@@ -327,7 +335,8 @@ function parse(str) {
327
335
 
328
336
  // We use a regex to parse the "name-value-pair" part of S5.2
329
337
  var firstSemi = str.indexOf(';'); // S5.2 step 1
330
- var result = COOKIE_PAIR.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));
338
+ var pairRe = options.loose ? LOOSE_COOKIE_PAIR : COOKIE_PAIR;
339
+ var result = pairRe.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));
331
340
 
332
341
  // Rx satisfies the "the name string is empty" and "lacks a %x3D ("=")"
333
342
  // constraints as well as trimming any whitespace.
@@ -336,8 +345,15 @@ function parse(str) {
336
345
  }
337
346
 
338
347
  var c = new Cookie();
339
- c.key = result[1].trim();
340
- c.value = result[2].trim();
348
+ if (result[1]) {
349
+ c.key = result[2].trim();
350
+ } else {
351
+ c.key = '';
352
+ }
353
+ c.value = result[3].trim();
354
+ if (CONTROL_CHARS.test(c.key) || CONTROL_CHARS.test(c.value)) {
355
+ return;
356
+ }
341
357
 
342
358
  if (firstSemi === -1) {
343
359
  return c;
@@ -458,47 +474,60 @@ function parse(str) {
458
474
  }
459
475
  }
460
476
 
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
477
  return c;
468
478
  }
469
479
 
480
+ // avoid the V8 deoptimization monster!
481
+ function jsonParse(str) {
482
+ var obj;
483
+ try {
484
+ obj = JSON.parse(str);
485
+ } catch (e) {
486
+ return e;
487
+ }
488
+ return obj;
489
+ }
490
+
470
491
  function fromJSON(str) {
471
492
  if (!str) {
472
493
  return null;
473
494
  }
474
495
 
475
496
  var obj;
476
- try {
477
- obj = JSON.parse(str);
478
- } catch (e) {
479
- return null;
497
+ if (typeof str === 'string') {
498
+ obj = jsonParse(str);
499
+ if (obj instanceof Error) {
500
+ return null;
501
+ }
502
+ } else {
503
+ // assume it's an Object
504
+ obj = str;
480
505
  }
481
506
 
482
507
  var c = new Cookie();
483
- for (var i=0; i<numCookieProperties; i++) {
484
- var prop = cookieProperties[i];
485
- if (obj[prop] == null) {
486
- continue;
508
+ for (var i=0; i<Cookie.serializableProperties.length; i++) {
509
+ var prop = Cookie.serializableProperties[i];
510
+ if (obj[prop] === undefined ||
511
+ obj[prop] === Cookie.prototype[prop])
512
+ {
513
+ continue; // leave as prototype default
487
514
  }
515
+
488
516
  if (prop === 'expires' ||
489
517
  prop === 'creation' ||
490
518
  prop === 'lastAccessed')
491
519
  {
492
- c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]);
520
+ if (obj[prop] === null) {
521
+ c[prop] = null;
522
+ } else {
523
+ c[prop] = obj[prop] == "Infinity" ?
524
+ "Infinity" : new Date(obj[prop]);
525
+ }
493
526
  } else {
494
527
  c[prop] = obj[prop];
495
528
  }
496
529
  }
497
530
 
498
-
499
- // ensure a default date for sorting:
500
- c.creation = c.creation || new Date();
501
-
502
531
  return c;
503
532
  }
504
533
 
@@ -512,23 +541,28 @@ function fromJSON(str) {
512
541
  */
513
542
 
514
543
  function cookieCompare(a,b) {
544
+ var cmp = 0;
545
+
515
546
  // 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;
547
+ var aPathLen = a.path ? a.path.length : 0;
548
+ var bPathLen = b.path ? b.path.length : 0;
549
+ cmp = bPathLen - aPathLen;
550
+ if (cmp !== 0) {
551
+ return cmp;
519
552
  }
520
553
 
554
+ // ascending for time: a CMP b
521
555
  var aTime = a.creation ? a.creation.getTime() : MAX_TIME;
522
556
  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;
557
+ cmp = aTime - bTime;
558
+ if (cmp !== 0) {
559
+ return cmp;
528
560
  }
529
561
 
530
- // ascending for time: a CMP b
531
- return aTime - bTime;
562
+ // break ties for the same millisecond (precision of JavaScript's clock)
563
+ cmp = a.creationIndex - b.creationIndex;
564
+
565
+ return cmp;
532
566
  }
533
567
 
534
568
  // Gives the permutation of all possible pathMatch()es of a given path. The
@@ -569,17 +603,31 @@ function getCookieContext(url) {
569
603
  return urlParse(url);
570
604
  }
571
605
 
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];
606
+ function Cookie(options) {
607
+ options = options || {};
608
+
609
+ Object.keys(options).forEach(function(prop) {
610
+ if (Cookie.prototype.hasOwnProperty(prop) &&
611
+ Cookie.prototype[prop] !== options[prop] &&
612
+ prop.substr(0,1) !== '_')
613
+ {
614
+ this[prop] = options[prop];
579
615
  }
580
- }.bind(this));
616
+ }, this);
617
+
618
+ this.creation = this.creation || new Date();
619
+
620
+ // used to break creation ties in cookieCompare():
621
+ Object.defineProperty(this, 'creationIndex', {
622
+ configurable: false,
623
+ enumerable: false, // important for assert.deepEqual checks
624
+ writable: true,
625
+ value: ++Cookie.cookiesCreated
626
+ });
581
627
  }
582
628
 
629
+ Cookie.cookiesCreated = 0; // incremented each time a cookie is created
630
+
583
631
  Cookie.parse = parse;
584
632
  Cookie.fromJSON = fromJSON;
585
633
 
@@ -599,17 +647,22 @@ Cookie.prototype.extensions = null;
599
647
  Cookie.prototype.hostOnly = null; // boolean when set
600
648
  Cookie.prototype.pathIsDefault = null; // boolean when set
601
649
  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
650
  Cookie.prototype.lastAccessed = null; // Date when set
651
+ Object.defineProperty(Cookie.prototype, 'creationIndex', {
652
+ configurable: true,
653
+ enumerable: false,
654
+ writable: true,
655
+ value: 0
656
+ });
605
657
 
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;
658
+ Cookie.serializableProperties = Object.keys(Cookie.prototype)
659
+ .filter(function(prop) {
660
+ return !(
661
+ Cookie.prototype[prop] instanceof Function ||
662
+ prop === 'creationIndex' ||
663
+ prop.substr(0,1) === '_'
664
+ );
665
+ });
613
666
 
614
667
  Cookie.prototype.inspect = function inspect() {
615
668
  var now = Date.now();
@@ -620,6 +673,46 @@ Cookie.prototype.inspect = function inspect() {
620
673
  '"';
621
674
  };
622
675
 
676
+ Cookie.prototype.toJSON = function() {
677
+ var obj = {};
678
+
679
+ var props = Cookie.serializableProperties;
680
+ for (var i=0; i<props.length; i++) {
681
+ var prop = props[i];
682
+ if (this[prop] === Cookie.prototype[prop]) {
683
+ continue; // leave as prototype default
684
+ }
685
+
686
+ if (prop === 'expires' ||
687
+ prop === 'creation' ||
688
+ prop === 'lastAccessed')
689
+ {
690
+ if (this[prop] === null) {
691
+ obj[prop] = null;
692
+ } else {
693
+ obj[prop] = this[prop] == "Infinity" ? // intentionally not ===
694
+ "Infinity" : this[prop].toISOString();
695
+ }
696
+ } else if (prop === 'maxAge') {
697
+ if (this[prop] !== null) {
698
+ // again, intentionally not ===
699
+ obj[prop] = (this[prop] == Infinity || this[prop] == -Infinity) ?
700
+ this[prop].toString() : this[prop];
701
+ }
702
+ } else {
703
+ if (this[prop] !== Cookie.prototype[prop]) {
704
+ obj[prop] = this[prop];
705
+ }
706
+ }
707
+ }
708
+
709
+ return obj;
710
+ };
711
+
712
+ Cookie.prototype.clone = function() {
713
+ return fromJSON(this.toJSON());
714
+ };
715
+
623
716
  Cookie.prototype.validate = function validate() {
624
717
  if (!COOKIE_OCTETS.test(this.value)) {
625
718
  return false;
@@ -669,6 +762,9 @@ Cookie.prototype.cookieString = function cookieString() {
669
762
  if (val == null) {
670
763
  val = '';
671
764
  }
765
+ if (this.key === '') {
766
+ return val;
767
+ }
672
768
  return this.key+'='+val;
673
769
  };
674
770
 
@@ -744,7 +840,7 @@ Cookie.prototype.TTL = function TTL(now) {
744
840
  // elsewhere)
745
841
  Cookie.prototype.expiryTime = function expiryTime(now) {
746
842
  if (this.maxAge != null) {
747
- var relativeTo = this.creation || now || new Date();
843
+ var relativeTo = now || this.creation || new Date();
748
844
  var age = (this.maxAge <= 0) ? -Infinity : this.maxAge*1000;
749
845
  return relativeTo.getTime() + age;
750
846
  }
@@ -782,10 +878,17 @@ Cookie.prototype.canonicalizedDomain = function canonicalizedDomain() {
782
878
  return canonicalDomain(this.domain);
783
879
  };
784
880
 
785
-
786
- function CookieJar(store, rejectPublicSuffixes) {
787
- if (rejectPublicSuffixes != null) {
788
- this.rejectPublicSuffixes = rejectPublicSuffixes;
881
+ function CookieJar(store, options) {
882
+ if (typeof options === "boolean") {
883
+ options = {rejectPublicSuffixes: options};
884
+ } else if (options == null) {
885
+ options = {};
886
+ }
887
+ if (options.rejectPublicSuffixes != null) {
888
+ this.rejectPublicSuffixes = options.rejectPublicSuffixes;
889
+ }
890
+ if (options.looseMode != null) {
891
+ this.enableLooseMode = options.looseMode;
789
892
  }
790
893
 
791
894
  if (!store) {
@@ -795,6 +898,7 @@ function CookieJar(store, rejectPublicSuffixes) {
795
898
  }
796
899
  CookieJar.prototype.store = null;
797
900
  CookieJar.prototype.rejectPublicSuffixes = true;
901
+ CookieJar.prototype.enableLooseMode = false;
798
902
  var CAN_BE_SYNC = [];
799
903
 
800
904
  CAN_BE_SYNC.push('setCookie');
@@ -807,10 +911,14 @@ CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
807
911
  }
808
912
 
809
913
  var host = canonicalDomain(context.hostname);
914
+ var loose = this.enableLooseMode;
915
+ if (options.loose != null) {
916
+ loose = options.loose;
917
+ }
810
918
 
811
919
  // S5.3 step 1
812
920
  if (!(cookie instanceof Cookie)) {
813
- cookie = Cookie.parse(cookie);
921
+ cookie = Cookie.parse(cookie, { loose: loose });
814
922
  }
815
923
  if (!cookie) {
816
924
  err = new Error("Cookie failed to parse");
@@ -895,6 +1003,7 @@ CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
895
1003
  return cb(options.ignoreError ? null : err);
896
1004
  }
897
1005
  cookie.creation = oldCookie.creation; // step 11.3
1006
+ cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker
898
1007
  cookie.lastAccessed = now;
899
1008
  // Step 11.4 (delete cookie) is implied by just setting the new one:
900
1009
  store.updateCookie(oldCookie, cookie, next); // step 12
@@ -1041,6 +1150,151 @@ CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {
1041
1150
  this.getCookies.apply(this,args);
1042
1151
  };
1043
1152
 
1153
+ CAN_BE_SYNC.push('serialize');
1154
+ CookieJar.prototype.serialize = function(cb) {
1155
+ var type = this.store.constructor.name;
1156
+ if (type === 'Object') {
1157
+ type = null;
1158
+ }
1159
+
1160
+ // update README.md "Serialization Format" if you change this, please!
1161
+ var serialized = {
1162
+ // The version of tough-cookie that serialized this jar. Generally a good
1163
+ // practice since future versions can make data import decisions based on
1164
+ // known past behavior. When/if this matters, use `semver`.
1165
+ version: 'tough-cookie@'+VERSION,
1166
+
1167
+ // add the store type, to make humans happy:
1168
+ storeType: type,
1169
+
1170
+ // CookieJar configuration:
1171
+ rejectPublicSuffixes: !!this.rejectPublicSuffixes,
1172
+
1173
+ // this gets filled from getAllCookies:
1174
+ cookies: []
1175
+ };
1176
+
1177
+ if (!(this.store.getAllCookies &&
1178
+ typeof this.store.getAllCookies === 'function'))
1179
+ {
1180
+ return cb(new Error('store does not support getAllCookies and cannot be serialized'));
1181
+ }
1182
+
1183
+ this.store.getAllCookies(function(err,cookies) {
1184
+ if (err) {
1185
+ return cb(err);
1186
+ }
1187
+
1188
+ serialized.cookies = cookies.map(function(cookie) {
1189
+ // convert to serialized 'raw' cookies
1190
+ cookie = (cookie instanceof Cookie) ? cookie.toJSON() : cookie;
1191
+
1192
+ // Remove the index so new ones get assigned during deserialization
1193
+ delete cookie.creationIndex;
1194
+
1195
+ return cookie;
1196
+ });
1197
+
1198
+ return cb(null, serialized);
1199
+ });
1200
+ };
1201
+
1202
+ // well-known name that JSON.stringify calls
1203
+ CookieJar.prototype.toJSON = function() {
1204
+ return this.serializeSync();
1205
+ };
1206
+
1207
+ // use the class method CookieJar.deserialize instead of calling this directly
1208
+ CAN_BE_SYNC.push('_importCookies');
1209
+ CookieJar.prototype._importCookies = function(serialized, cb) {
1210
+ var jar = this;
1211
+ var cookies = serialized.cookies;
1212
+ if (!cookies || !Array.isArray(cookies)) {
1213
+ return cb(new Error('serialized jar has no cookies array'));
1214
+ }
1215
+
1216
+ function putNext(err) {
1217
+ if (err) {
1218
+ return cb(err);
1219
+ }
1220
+
1221
+ if (!cookies.length) {
1222
+ return cb(err, jar);
1223
+ }
1224
+
1225
+ var cookie;
1226
+ try {
1227
+ cookie = fromJSON(cookies.shift());
1228
+ } catch (e) {
1229
+ return cb(e);
1230
+ }
1231
+
1232
+ if (cookie === null) {
1233
+ return putNext(null); // skip this cookie
1234
+ }
1235
+
1236
+ jar.store.putCookie(cookie, putNext);
1237
+ }
1238
+
1239
+ putNext();
1240
+ };
1241
+
1242
+ CookieJar.deserialize = function(strOrObj, store, cb) {
1243
+ if (arguments.length !== 3) {
1244
+ // store is optional
1245
+ cb = store;
1246
+ store = null;
1247
+ }
1248
+
1249
+ var serialized;
1250
+ if (typeof strOrObj === 'string') {
1251
+ serialized = jsonParse(strOrObj);
1252
+ if (serialized instanceof Error) {
1253
+ return cb(serialized);
1254
+ }
1255
+ } else {
1256
+ serialized = strOrObj;
1257
+ }
1258
+
1259
+ var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
1260
+ jar._importCookies(serialized, function(err) {
1261
+ if (err) {
1262
+ return cb(err);
1263
+ }
1264
+ cb(null, jar);
1265
+ });
1266
+ };
1267
+
1268
+ CookieJar.deserializeSync = function(strOrObj, store) {
1269
+ var serialized = typeof strOrObj === 'string' ?
1270
+ JSON.parse(strOrObj) : strOrObj;
1271
+ var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
1272
+
1273
+ // catch this mistake early:
1274
+ if (!jar.store.synchronous) {
1275
+ throw new Error('CookieJar store is not synchronous; use async API instead.');
1276
+ }
1277
+
1278
+ jar._importCookiesSync(serialized);
1279
+ return jar;
1280
+ };
1281
+ CookieJar.fromJSON = CookieJar.deserializeSync;
1282
+
1283
+ CAN_BE_SYNC.push('clone');
1284
+ CookieJar.prototype.clone = function(newStore, cb) {
1285
+ if (arguments.length === 1) {
1286
+ cb = newStore;
1287
+ newStore = null;
1288
+ }
1289
+
1290
+ this.serialize(function(err,serialized) {
1291
+ if (err) {
1292
+ return cb(err);
1293
+ }
1294
+ CookieJar.deserialize(newStore, serialized, cb);
1295
+ });
1296
+ };
1297
+
1044
1298
  // Use a closure to provide a true imperative API for synchronous stores.
1045
1299
  function syncWrap(method) {
1046
1300
  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
+ };