Initial commit
[yaffs-website] / node_modules / tough-cookie / lib / cookie.js
1 /*!
2  * Copyright (c) 2015, Salesforce.com, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  *
15  * 3. Neither the name of Salesforce.com nor the names of its contributors may
16  * be used to endorse or promote products derived from this software without
17  * specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 'use strict';
32 var net = require('net');
33 var urlParse = require('url').parse;
34 var pubsuffix = require('./pubsuffix');
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;
39
40 var punycode;
41 try {
42   punycode = require('punycode');
43 } catch(e) {
44   console.warn("cookie: can't load punycode; won't use punycode for domain normalization");
45 }
46
47 var DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
48
49 // From RFC6265 S4.1.1
50 // note that it excludes \x3B ";"
51 var COOKIE_OCTET  = /[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/;
52 var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'+$');
53
54 var CONTROL_CHARS = /[\x00-\x1F]/;
55
56 // Double quotes are part of the value (see: S4.1.1).
57 // '\r', '\n' and '\0' should be treated as a terminator in the "relaxed" mode
58 // (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60)
59 // '=' and ';' are attribute/values separators
60 // (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L64)
61 var COOKIE_PAIR = /^(([^=;]+))\s*=\s*([^\n\r\0]*)/;
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]*)/;
66
67 // RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
68 // Note ';' is \x3B
69 var PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;
70
71 var DAY_OF_MONTH = /^(\d{1,2})[^\d]*$/;
72 var TIME = /^(\d{1,2})[^\d]*:(\d{1,2})[^\d]*:(\d{1,2})[^\d]*$/;
73 var MONTH = /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i;
74
75 var MONTH_TO_NUM = {
76   jan:0, feb:1, mar:2, apr:3, may:4, jun:5,
77   jul:6, aug:7, sep:8, oct:9, nov:10, dec:11
78 };
79 var NUM_TO_MONTH = [
80   'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'
81 ];
82 var NUM_TO_DAY = [
83   'Sun','Mon','Tue','Wed','Thu','Fri','Sat'
84 ];
85
86 var YEAR = /^(\d{2}|\d{4})$/; // 2 to 4 digits
87
88 var MAX_TIME = 2147483647000; // 31-bit max
89 var MIN_TIME = 0; // 31-bit min
90
91
92 // RFC6265 S5.1.1 date parser:
93 function parseDate(str) {
94   if (!str) {
95     return;
96   }
97
98   /* RFC6265 S5.1.1:
99    * 2. Process each date-token sequentially in the order the date-tokens
100    * appear in the cookie-date
101    */
102   var tokens = str.split(DATE_DELIM);
103   if (!tokens) {
104     return;
105   }
106
107   var hour = null;
108   var minutes = null;
109   var seconds = null;
110   var day = null;
111   var month = null;
112   var year = null;
113
114   for (var i=0; i<tokens.length; i++) {
115     var token = tokens[i].trim();
116     if (!token.length) {
117       continue;
118     }
119
120     var result;
121
122     /* 2.1. If the found-time flag is not set and the token matches the time
123      * production, set the found-time flag and set the hour- value,
124      * minute-value, and second-value to the numbers denoted by the digits in
125      * the date-token, respectively.  Skip the remaining sub-steps and continue
126      * to the next date-token.
127      */
128     if (seconds === null) {
129       result = TIME.exec(token);
130       if (result) {
131         hour = parseInt(result[1], 10);
132         minutes = parseInt(result[2], 10);
133         seconds = parseInt(result[3], 10);
134         /* RFC6265 S5.1.1.5:
135          * [fail if]
136          * *  the hour-value is greater than 23,
137          * *  the minute-value is greater than 59, or
138          * *  the second-value is greater than 59.
139          */
140         if(hour > 23 || minutes > 59 || seconds > 59) {
141           return;
142         }
143
144         continue;
145       }
146     }
147
148     /* 2.2. If the found-day-of-month flag is not set and the date-token matches
149      * the day-of-month production, set the found-day-of- month flag and set
150      * the day-of-month-value to the number denoted by the date-token.  Skip
151      * the remaining sub-steps and continue to the next date-token.
152      */
153     if (day === null) {
154       result = DAY_OF_MONTH.exec(token);
155       if (result) {
156         day = parseInt(result, 10);
157         /* RFC6265 S5.1.1.5:
158          * [fail if] the day-of-month-value is less than 1 or greater than 31
159          */
160         if(day < 1 || day > 31) {
161           return;
162         }
163         continue;
164       }
165     }
166
167     /* 2.3. If the found-month flag is not set and the date-token matches the
168      * month production, set the found-month flag and set the month-value to
169      * the month denoted by the date-token.  Skip the remaining sub-steps and
170      * continue to the next date-token.
171      */
172     if (month === null) {
173       result = MONTH.exec(token);
174       if (result) {
175         month = MONTH_TO_NUM[result[1].toLowerCase()];
176         continue;
177       }
178     }
179
180     /* 2.4. If the found-year flag is not set and the date-token matches the year
181      * production, set the found-year flag and set the year-value to the number
182      * denoted by the date-token.  Skip the remaining sub-steps and continue to
183      * the next date-token.
184      */
185     if (year === null) {
186       result = YEAR.exec(token);
187       if (result) {
188         year = parseInt(result[0], 10);
189         /* From S5.1.1:
190          * 3.  If the year-value is greater than or equal to 70 and less
191          * than or equal to 99, increment the year-value by 1900.
192          * 4.  If the year-value is greater than or equal to 0 and less
193          * than or equal to 69, increment the year-value by 2000.
194          */
195         if (70 <= year && year <= 99) {
196           year += 1900;
197         } else if (0 <= year && year <= 69) {
198           year += 2000;
199         }
200
201         if (year < 1601) {
202           return; // 5. ... the year-value is less than 1601
203         }
204       }
205     }
206   }
207
208   if (seconds === null || day === null || month === null || year === null) {
209     return; // 5. ... at least one of the found-day-of-month, found-month, found-
210             // year, or found-time flags is not set,
211   }
212
213   return new Date(Date.UTC(year, month, day, hour, minutes, seconds));
214 }
215
216 function formatDate(date) {
217   var d = date.getUTCDate(); d = d >= 10 ? d : '0'+d;
218   var h = date.getUTCHours(); h = h >= 10 ? h : '0'+h;
219   var m = date.getUTCMinutes(); m = m >= 10 ? m : '0'+m;
220   var s = date.getUTCSeconds(); s = s >= 10 ? s : '0'+s;
221   return NUM_TO_DAY[date.getUTCDay()] + ', ' +
222     d+' '+ NUM_TO_MONTH[date.getUTCMonth()] +' '+ date.getUTCFullYear() +' '+
223     h+':'+m+':'+s+' GMT';
224 }
225
226 // S5.1.2 Canonicalized Host Names
227 function canonicalDomain(str) {
228   if (str == null) {
229     return null;
230   }
231   str = str.trim().replace(/^\./,''); // S4.1.2.3 & S5.2.3: ignore leading .
232
233   // convert to IDN if any non-ASCII characters
234   if (punycode && /[^\u0001-\u007f]/.test(str)) {
235     str = punycode.toASCII(str);
236   }
237
238   return str.toLowerCase();
239 }
240
241 // S5.1.3 Domain Matching
242 function domainMatch(str, domStr, canonicalize) {
243   if (str == null || domStr == null) {
244     return null;
245   }
246   if (canonicalize !== false) {
247     str = canonicalDomain(str);
248     domStr = canonicalDomain(domStr);
249   }
250
251   /*
252    * "The domain string and the string are identical. (Note that both the
253    * domain string and the string will have been canonicalized to lower case at
254    * this point)"
255    */
256   if (str == domStr) {
257     return true;
258   }
259
260   /* "All of the following [three] conditions hold:" (order adjusted from the RFC) */
261
262   /* "* The string is a host name (i.e., not an IP address)." */
263   if (net.isIP(str)) {
264     return false;
265   }
266
267   /* "* The domain string is a suffix of the string" */
268   var idx = str.indexOf(domStr);
269   if (idx <= 0) {
270     return false; // it's a non-match (-1) or prefix (0)
271   }
272
273   // e.g "a.b.c".indexOf("b.c") === 2
274   // 5 === 3+2
275   if (str.length !== domStr.length + idx) { // it's not a suffix
276     return false;
277   }
278
279   /* "* The last character of the string that is not included in the domain
280   * string is a %x2E (".") character." */
281   if (str.substr(idx-1,1) !== '.') {
282     return false;
283   }
284
285   return true;
286 }
287
288
289 // RFC6265 S5.1.4 Paths and Path-Match
290
291 /*
292  * "The user agent MUST use an algorithm equivalent to the following algorithm
293  * to compute the default-path of a cookie:"
294  *
295  * Assumption: the path (and not query part or absolute uri) is passed in.
296  */
297 function defaultPath(path) {
298   // "2. If the uri-path is empty or if the first character of the uri-path is not
299   // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.
300   if (!path || path.substr(0,1) !== "/") {
301     return "/";
302   }
303
304   // "3. If the uri-path contains no more than one %x2F ("/") character, output
305   // %x2F ("/") and skip the remaining step."
306   if (path === "/") {
307     return path;
308   }
309
310   var rightSlash = path.lastIndexOf("/");
311   if (rightSlash === 0) {
312     return "/";
313   }
314
315   // "4. Output the characters of the uri-path from the first character up to,
316   // but not including, the right-most %x2F ("/")."
317   return path.slice(0, rightSlash);
318 }
319
320
321 function parse(str, options) {
322   if (!options || typeof options !== 'object') {
323     options = {};
324   }
325   str = str.trim();
326
327   // We use a regex to parse the "name-value-pair" part of S5.2
328   var firstSemi = str.indexOf(';'); // S5.2 step 1
329   var pairRe = options.loose ? LOOSE_COOKIE_PAIR : COOKIE_PAIR;
330   var result = pairRe.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));
331
332   // Rx satisfies the "the name string is empty" and "lacks a %x3D ("=")"
333   // constraints as well as trimming any whitespace.
334   if (!result) {
335     return;
336   }
337
338   var c = new Cookie();
339   if (result[1]) {
340     c.key = result[2].trim();
341   } else {
342     c.key = '';
343   }
344   c.value = result[3].trim();
345   if (CONTROL_CHARS.test(c.key) || CONTROL_CHARS.test(c.value)) {
346     return;
347   }
348
349   if (firstSemi === -1) {
350     return c;
351   }
352
353   // S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string
354   // (including the %x3B (";") in question)." plus later on in the same section
355   // "discard the first ";" and trim".
356   var unparsed = str.slice(firstSemi + 1).trim();
357
358   // "If the unparsed-attributes string is empty, skip the rest of these
359   // steps."
360   if (unparsed.length === 0) {
361     return c;
362   }
363
364   /*
365    * S5.2 says that when looping over the items "[p]rocess the attribute-name
366    * and attribute-value according to the requirements in the following
367    * subsections" for every item.  Plus, for many of the individual attributes
368    * in S5.3 it says to use the "attribute-value of the last attribute in the
369    * cookie-attribute-list".  Therefore, in this implementation, we overwrite
370    * the previous value.
371    */
372   var cookie_avs = unparsed.split(';');
373   while (cookie_avs.length) {
374     var av = cookie_avs.shift().trim();
375     if (av.length === 0) { // happens if ";;" appears
376       continue;
377     }
378     var av_sep = av.indexOf('=');
379     var av_key, av_value;
380
381     if (av_sep === -1) {
382       av_key = av;
383       av_value = null;
384     } else {
385       av_key = av.substr(0,av_sep);
386       av_value = av.substr(av_sep+1);
387     }
388
389     av_key = av_key.trim().toLowerCase();
390
391     if (av_value) {
392       av_value = av_value.trim();
393     }
394
395     switch(av_key) {
396     case 'expires': // S5.2.1
397       if (av_value) {
398         var exp = parseDate(av_value);
399         // "If the attribute-value failed to parse as a cookie date, ignore the
400         // cookie-av."
401         if (exp) {
402           // over and underflow not realistically a concern: V8's getTime() seems to
403           // store something larger than a 32-bit time_t (even with 32-bit node)
404           c.expires = exp;
405         }
406       }
407       break;
408
409     case 'max-age': // S5.2.2
410       if (av_value) {
411         // "If the first character of the attribute-value is not a DIGIT or a "-"
412         // character ...[or]... If the remainder of attribute-value contains a
413         // non-DIGIT character, ignore the cookie-av."
414         if (/^-?[0-9]+$/.test(av_value)) {
415           var delta = parseInt(av_value, 10);
416           // "If delta-seconds is less than or equal to zero (0), let expiry-time
417           // be the earliest representable date and time."
418           c.setMaxAge(delta);
419         }
420       }
421       break;
422
423     case 'domain': // S5.2.3
424       // "If the attribute-value is empty, the behavior is undefined.  However,
425       // the user agent SHOULD ignore the cookie-av entirely."
426       if (av_value) {
427         // S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E
428         // (".") character."
429         var domain = av_value.trim().replace(/^\./, '');
430         if (domain) {
431           // "Convert the cookie-domain to lower case."
432           c.domain = domain.toLowerCase();
433         }
434       }
435       break;
436
437     case 'path': // S5.2.4
438       /*
439        * "If the attribute-value is empty or if the first character of the
440        * attribute-value is not %x2F ("/"):
441        *   Let cookie-path be the default-path.
442        * Otherwise:
443        *   Let cookie-path be the attribute-value."
444        *
445        * We'll represent the default-path as null since it depends on the
446        * context of the parsing.
447        */
448       c.path = av_value && av_value[0] === "/" ? av_value : null;
449       break;
450
451     case 'secure': // S5.2.5
452       /*
453        * "If the attribute-name case-insensitively matches the string "Secure",
454        * the user agent MUST append an attribute to the cookie-attribute-list
455        * with an attribute-name of Secure and an empty attribute-value."
456        */
457       c.secure = true;
458       break;
459
460     case 'httponly': // S5.2.6 -- effectively the same as 'secure'
461       c.httpOnly = true;
462       break;
463
464     default:
465       c.extensions = c.extensions || [];
466       c.extensions.push(av);
467       break;
468     }
469   }
470
471   return c;
472 }
473
474 // avoid the V8 deoptimization monster!
475 function jsonParse(str) {
476   var obj;
477   try {
478     obj = JSON.parse(str);
479   } catch (e) {
480     return e;
481   }
482   return obj;
483 }
484
485 function fromJSON(str) {
486   if (!str) {
487     return null;
488   }
489
490   var obj;
491   if (typeof str === 'string') {
492     obj = jsonParse(str);
493     if (obj instanceof Error) {
494       return null;
495     }
496   } else {
497     // assume it's an Object
498     obj = str;
499   }
500
501   var c = new Cookie();
502   for (var i=0; i<Cookie.serializableProperties.length; i++) {
503     var prop = Cookie.serializableProperties[i];
504     if (obj[prop] === undefined ||
505         obj[prop] === Cookie.prototype[prop])
506     {
507       continue; // leave as prototype default
508     }
509
510     if (prop === 'expires' ||
511         prop === 'creation' ||
512         prop === 'lastAccessed')
513     {
514       if (obj[prop] === null) {
515         c[prop] = null;
516       } else {
517         c[prop] = obj[prop] == "Infinity" ?
518           "Infinity" : new Date(obj[prop]);
519       }
520     } else {
521       c[prop] = obj[prop];
522     }
523   }
524
525   return c;
526 }
527
528 /* Section 5.4 part 2:
529  * "*  Cookies with longer paths are listed before cookies with
530  *     shorter paths.
531  *
532  *  *  Among cookies that have equal-length path fields, cookies with
533  *     earlier creation-times are listed before cookies with later
534  *     creation-times."
535  */
536
537 function cookieCompare(a,b) {
538   var cmp = 0;
539
540   // descending for length: b CMP a
541   var aPathLen = a.path ? a.path.length : 0;
542   var bPathLen = b.path ? b.path.length : 0;
543   cmp = bPathLen - aPathLen;
544   if (cmp !== 0) {
545     return cmp;
546   }
547
548   // ascending for time: a CMP b
549   var aTime = a.creation ? a.creation.getTime() : MAX_TIME;
550   var bTime = b.creation ? b.creation.getTime() : MAX_TIME;
551   cmp = aTime - bTime;
552   if (cmp !== 0) {
553     return cmp;
554   }
555
556   // break ties for the same millisecond (precision of JavaScript's clock)
557   cmp = a.creationIndex - b.creationIndex;
558
559   return cmp;
560 }
561
562 // Gives the permutation of all possible pathMatch()es of a given path. The
563 // array is in longest-to-shortest order.  Handy for indexing.
564 function permutePath(path) {
565   if (path === '/') {
566     return ['/'];
567   }
568   if (path.lastIndexOf('/') === path.length-1) {
569     path = path.substr(0,path.length-1);
570   }
571   var permutations = [path];
572   while (path.length > 1) {
573     var lindex = path.lastIndexOf('/');
574     if (lindex === 0) {
575       break;
576     }
577     path = path.substr(0,lindex);
578     permutations.push(path);
579   }
580   permutations.push('/');
581   return permutations;
582 }
583
584 function getCookieContext(url) {
585   if (url instanceof Object) {
586     return url;
587   }
588   // NOTE: decodeURI will throw on malformed URIs (see GH-32).
589   // Therefore, we will just skip decoding for such URIs.
590   try {
591     url = decodeURI(url);
592   }
593   catch(err) {
594     // Silently swallow error
595   }
596
597   return urlParse(url);
598 }
599
600 function Cookie(options) {
601   options = options || {};
602
603   Object.keys(options).forEach(function(prop) {
604     if (Cookie.prototype.hasOwnProperty(prop) &&
605         Cookie.prototype[prop] !== options[prop] &&
606         prop.substr(0,1) !== '_')
607     {
608       this[prop] = options[prop];
609     }
610   }, this);
611
612   this.creation = this.creation || new Date();
613
614   // used to break creation ties in cookieCompare():
615   Object.defineProperty(this, 'creationIndex', {
616     configurable: false,
617     enumerable: false, // important for assert.deepEqual checks
618     writable: true,
619     value: ++Cookie.cookiesCreated
620   });
621 }
622
623 Cookie.cookiesCreated = 0; // incremented each time a cookie is created
624
625 Cookie.parse = parse;
626 Cookie.fromJSON = fromJSON;
627
628 Cookie.prototype.key = "";
629 Cookie.prototype.value = "";
630
631 // the order in which the RFC has them:
632 Cookie.prototype.expires = "Infinity"; // coerces to literal Infinity
633 Cookie.prototype.maxAge = null; // takes precedence over expires for TTL
634 Cookie.prototype.domain = null;
635 Cookie.prototype.path = null;
636 Cookie.prototype.secure = false;
637 Cookie.prototype.httpOnly = false;
638 Cookie.prototype.extensions = null;
639
640 // set by the CookieJar:
641 Cookie.prototype.hostOnly = null; // boolean when set
642 Cookie.prototype.pathIsDefault = null; // boolean when set
643 Cookie.prototype.creation = null; // Date when set; defaulted by Cookie.parse
644 Cookie.prototype.lastAccessed = null; // Date when set
645 Object.defineProperty(Cookie.prototype, 'creationIndex', {
646   configurable: true,
647   enumerable: false,
648   writable: true,
649   value: 0
650 });
651
652 Cookie.serializableProperties = Object.keys(Cookie.prototype)
653   .filter(function(prop) {
654     return !(
655       Cookie.prototype[prop] instanceof Function ||
656       prop === 'creationIndex' ||
657       prop.substr(0,1) === '_'
658     );
659   });
660
661 Cookie.prototype.inspect = function inspect() {
662   var now = Date.now();
663   return 'Cookie="'+this.toString() +
664     '; hostOnly='+(this.hostOnly != null ? this.hostOnly : '?') +
665     '; aAge='+(this.lastAccessed ? (now-this.lastAccessed.getTime())+'ms' : '?') +
666     '; cAge='+(this.creation ? (now-this.creation.getTime())+'ms' : '?') +
667     '"';
668 };
669
670 Cookie.prototype.toJSON = function() {
671   var obj = {};
672
673   var props = Cookie.serializableProperties;
674   for (var i=0; i<props.length; i++) {
675     var prop = props[i];
676     if (this[prop] === Cookie.prototype[prop]) {
677       continue; // leave as prototype default
678     }
679
680     if (prop === 'expires' ||
681         prop === 'creation' ||
682         prop === 'lastAccessed')
683     {
684       if (this[prop] === null) {
685         obj[prop] = null;
686       } else {
687         obj[prop] = this[prop] == "Infinity" ? // intentionally not ===
688           "Infinity" : this[prop].toISOString();
689       }
690     } else if (prop === 'maxAge') {
691       if (this[prop] !== null) {
692         // again, intentionally not ===
693         obj[prop] = (this[prop] == Infinity || this[prop] == -Infinity) ?
694           this[prop].toString() : this[prop];
695       }
696     } else {
697       if (this[prop] !== Cookie.prototype[prop]) {
698         obj[prop] = this[prop];
699       }
700     }
701   }
702
703   return obj;
704 };
705
706 Cookie.prototype.clone = function() {
707   return fromJSON(this.toJSON());
708 };
709
710 Cookie.prototype.validate = function validate() {
711   if (!COOKIE_OCTETS.test(this.value)) {
712     return false;
713   }
714   if (this.expires != Infinity && !(this.expires instanceof Date) && !parseDate(this.expires)) {
715     return false;
716   }
717   if (this.maxAge != null && this.maxAge <= 0) {
718     return false; // "Max-Age=" non-zero-digit *DIGIT
719   }
720   if (this.path != null && !PATH_VALUE.test(this.path)) {
721     return false;
722   }
723
724   var cdomain = this.cdomain();
725   if (cdomain) {
726     if (cdomain.match(/\.$/)) {
727       return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this
728     }
729     var suffix = pubsuffix.getPublicSuffix(cdomain);
730     if (suffix == null) { // it's a public suffix
731       return false;
732     }
733   }
734   return true;
735 };
736
737 Cookie.prototype.setExpires = function setExpires(exp) {
738   if (exp instanceof Date) {
739     this.expires = exp;
740   } else {
741     this.expires = parseDate(exp) || "Infinity";
742   }
743 };
744
745 Cookie.prototype.setMaxAge = function setMaxAge(age) {
746   if (age === Infinity || age === -Infinity) {
747     this.maxAge = age.toString(); // so JSON.stringify() works
748   } else {
749     this.maxAge = age;
750   }
751 };
752
753 // gives Cookie header format
754 Cookie.prototype.cookieString = function cookieString() {
755   var val = this.value;
756   if (val == null) {
757     val = '';
758   }
759   if (this.key === '') {
760     return val;
761   }
762   return this.key+'='+val;
763 };
764
765 // gives Set-Cookie header format
766 Cookie.prototype.toString = function toString() {
767   var str = this.cookieString();
768
769   if (this.expires != Infinity) {
770     if (this.expires instanceof Date) {
771       str += '; Expires='+formatDate(this.expires);
772     } else {
773       str += '; Expires='+this.expires;
774     }
775   }
776
777   if (this.maxAge != null && this.maxAge != Infinity) {
778     str += '; Max-Age='+this.maxAge;
779   }
780
781   if (this.domain && !this.hostOnly) {
782     str += '; Domain='+this.domain;
783   }
784   if (this.path) {
785     str += '; Path='+this.path;
786   }
787
788   if (this.secure) {
789     str += '; Secure';
790   }
791   if (this.httpOnly) {
792     str += '; HttpOnly';
793   }
794   if (this.extensions) {
795     this.extensions.forEach(function(ext) {
796       str += '; '+ext;
797     });
798   }
799
800   return str;
801 };
802
803 // TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
804 // elsewhere)
805 // S5.3 says to give the "latest representable date" for which we use Infinity
806 // For "expired" we use 0
807 Cookie.prototype.TTL = function TTL(now) {
808   /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires
809    * attribute, the Max-Age attribute has precedence and controls the
810    * expiration date of the cookie.
811    * (Concurs with S5.3 step 3)
812    */
813   if (this.maxAge != null) {
814     return this.maxAge<=0 ? 0 : this.maxAge*1000;
815   }
816
817   var expires = this.expires;
818   if (expires != Infinity) {
819     if (!(expires instanceof Date)) {
820       expires = parseDate(expires) || Infinity;
821     }
822
823     if (expires == Infinity) {
824       return Infinity;
825     }
826
827     return expires.getTime() - (now || Date.now());
828   }
829
830   return Infinity;
831 };
832
833 // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
834 // elsewhere)
835 Cookie.prototype.expiryTime = function expiryTime(now) {
836   if (this.maxAge != null) {
837     var relativeTo = now || this.creation || new Date();
838     var age = (this.maxAge <= 0) ? -Infinity : this.maxAge*1000;
839     return relativeTo.getTime() + age;
840   }
841
842   if (this.expires == Infinity) {
843     return Infinity;
844   }
845   return this.expires.getTime();
846 };
847
848 // expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
849 // elsewhere), except it returns a Date
850 Cookie.prototype.expiryDate = function expiryDate(now) {
851   var millisec = this.expiryTime(now);
852   if (millisec == Infinity) {
853     return new Date(MAX_TIME);
854   } else if (millisec == -Infinity) {
855     return new Date(MIN_TIME);
856   } else {
857     return new Date(millisec);
858   }
859 };
860
861 // This replaces the "persistent-flag" parts of S5.3 step 3
862 Cookie.prototype.isPersistent = function isPersistent() {
863   return (this.maxAge != null || this.expires != Infinity);
864 };
865
866 // Mostly S5.1.2 and S5.2.3:
867 Cookie.prototype.cdomain =
868 Cookie.prototype.canonicalizedDomain = function canonicalizedDomain() {
869   if (this.domain == null) {
870     return null;
871   }
872   return canonicalDomain(this.domain);
873 };
874
875 function CookieJar(store, options) {
876   if (typeof options === "boolean") {
877     options = {rejectPublicSuffixes: options};
878   } else if (options == null) {
879     options = {};
880   }
881   if (options.rejectPublicSuffixes != null) {
882     this.rejectPublicSuffixes = options.rejectPublicSuffixes;
883   }
884   if (options.looseMode != null) {
885     this.enableLooseMode = options.looseMode;
886   }
887
888   if (!store) {
889     store = new MemoryCookieStore();
890   }
891   this.store = store;
892 }
893 CookieJar.prototype.store = null;
894 CookieJar.prototype.rejectPublicSuffixes = true;
895 CookieJar.prototype.enableLooseMode = false;
896 var CAN_BE_SYNC = [];
897
898 CAN_BE_SYNC.push('setCookie');
899 CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
900   var err;
901   var context = getCookieContext(url);
902   if (options instanceof Function) {
903     cb = options;
904     options = {};
905   }
906
907   var host = canonicalDomain(context.hostname);
908   var loose = this.enableLooseMode;
909   if (options.loose != null) {
910     loose = options.loose;
911   }
912
913   // S5.3 step 1
914   if (!(cookie instanceof Cookie)) {
915     cookie = Cookie.parse(cookie, { loose: loose });
916   }
917   if (!cookie) {
918     err = new Error("Cookie failed to parse");
919     return cb(options.ignoreError ? null : err);
920   }
921
922   // S5.3 step 2
923   var now = options.now || new Date(); // will assign later to save effort in the face of errors
924
925   // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie()
926
927   // S5.3 step 4: NOOP; domain is null by default
928
929   // S5.3 step 5: public suffixes
930   if (this.rejectPublicSuffixes && cookie.domain) {
931     var suffix = pubsuffix.getPublicSuffix(cookie.cdomain());
932     if (suffix == null) { // e.g. "com"
933       err = new Error("Cookie has domain set to a public suffix");
934       return cb(options.ignoreError ? null : err);
935     }
936   }
937
938   // S5.3 step 6:
939   if (cookie.domain) {
940     if (!domainMatch(host, cookie.cdomain(), false)) {
941       err = new Error("Cookie not in this host's domain. Cookie:"+cookie.cdomain()+" Request:"+host);
942       return cb(options.ignoreError ? null : err);
943     }
944
945     if (cookie.hostOnly == null) { // don't reset if already set
946       cookie.hostOnly = false;
947     }
948
949   } else {
950     cookie.hostOnly = true;
951     cookie.domain = host;
952   }
953
954   //S5.2.4 If the attribute-value is empty or if the first character of the
955   //attribute-value is not %x2F ("/"):
956   //Let cookie-path be the default-path.
957   if (!cookie.path || cookie.path[0] !== '/') {
958     cookie.path = defaultPath(context.pathname);
959     cookie.pathIsDefault = true;
960   }
961
962   // S5.3 step 8: NOOP; secure attribute
963   // S5.3 step 9: NOOP; httpOnly attribute
964
965   // S5.3 step 10
966   if (options.http === false && cookie.httpOnly) {
967     err = new Error("Cookie is HttpOnly and this isn't an HTTP API");
968     return cb(options.ignoreError ? null : err);
969   }
970
971   var store = this.store;
972
973   if (!store.updateCookie) {
974     store.updateCookie = function(oldCookie, newCookie, cb) {
975       this.putCookie(newCookie, cb);
976     };
977   }
978
979   function withCookie(err, oldCookie) {
980     if (err) {
981       return cb(err);
982     }
983
984     var next = function(err) {
985       if (err) {
986         return cb(err);
987       } else {
988         cb(null, cookie);
989       }
990     };
991
992     if (oldCookie) {
993       // S5.3 step 11 - "If the cookie store contains a cookie with the same name,
994       // domain, and path as the newly created cookie:"
995       if (options.http === false && oldCookie.httpOnly) { // step 11.2
996         err = new Error("old Cookie is HttpOnly and this isn't an HTTP API");
997         return cb(options.ignoreError ? null : err);
998       }
999       cookie.creation = oldCookie.creation; // step 11.3
1000       cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker
1001       cookie.lastAccessed = now;
1002       // Step 11.4 (delete cookie) is implied by just setting the new one:
1003       store.updateCookie(oldCookie, cookie, next); // step 12
1004
1005     } else {
1006       cookie.creation = cookie.lastAccessed = now;
1007       store.putCookie(cookie, next); // step 12
1008     }
1009   }
1010
1011   store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);
1012 };
1013
1014 // RFC6365 S5.4
1015 CAN_BE_SYNC.push('getCookies');
1016 CookieJar.prototype.getCookies = function(url, options, cb) {
1017   var context = getCookieContext(url);
1018   if (options instanceof Function) {
1019     cb = options;
1020     options = {};
1021   }
1022
1023   var host = canonicalDomain(context.hostname);
1024   var path = context.pathname || '/';
1025
1026   var secure = options.secure;
1027   if (secure == null && context.protocol &&
1028       (context.protocol == 'https:' || context.protocol == 'wss:'))
1029   {
1030     secure = true;
1031   }
1032
1033   var http = options.http;
1034   if (http == null) {
1035     http = true;
1036   }
1037
1038   var now = options.now || Date.now();
1039   var expireCheck = options.expire !== false;
1040   var allPaths = !!options.allPaths;
1041   var store = this.store;
1042
1043   function matchingCookie(c) {
1044     // "Either:
1045     //   The cookie's host-only-flag is true and the canonicalized
1046     //   request-host is identical to the cookie's domain.
1047     // Or:
1048     //   The cookie's host-only-flag is false and the canonicalized
1049     //   request-host domain-matches the cookie's domain."
1050     if (c.hostOnly) {
1051       if (c.domain != host) {
1052         return false;
1053       }
1054     } else {
1055       if (!domainMatch(host, c.domain, false)) {
1056         return false;
1057       }
1058     }
1059
1060     // "The request-uri's path path-matches the cookie's path."
1061     if (!allPaths && !pathMatch(path, c.path)) {
1062       return false;
1063     }
1064
1065     // "If the cookie's secure-only-flag is true, then the request-uri's
1066     // scheme must denote a "secure" protocol"
1067     if (c.secure && !secure) {
1068       return false;
1069     }
1070
1071     // "If the cookie's http-only-flag is true, then exclude the cookie if the
1072     // cookie-string is being generated for a "non-HTTP" API"
1073     if (c.httpOnly && !http) {
1074       return false;
1075     }
1076
1077     // deferred from S5.3
1078     // non-RFC: allow retention of expired cookies by choice
1079     if (expireCheck && c.expiryTime() <= now) {
1080       store.removeCookie(c.domain, c.path, c.key, function(){}); // result ignored
1081       return false;
1082     }
1083
1084     return true;
1085   }
1086
1087   store.findCookies(host, allPaths ? null : path, function(err,cookies) {
1088     if (err) {
1089       return cb(err);
1090     }
1091
1092     cookies = cookies.filter(matchingCookie);
1093
1094     // sorting of S5.4 part 2
1095     if (options.sort !== false) {
1096       cookies = cookies.sort(cookieCompare);
1097     }
1098
1099     // S5.4 part 3
1100     var now = new Date();
1101     cookies.forEach(function(c) {
1102       c.lastAccessed = now;
1103     });
1104     // TODO persist lastAccessed
1105
1106     cb(null,cookies);
1107   });
1108 };
1109
1110 CAN_BE_SYNC.push('getCookieString');
1111 CookieJar.prototype.getCookieString = function(/*..., cb*/) {
1112   var args = Array.prototype.slice.call(arguments,0);
1113   var cb = args.pop();
1114   var next = function(err,cookies) {
1115     if (err) {
1116       cb(err);
1117     } else {
1118       cb(null, cookies
1119         .sort(cookieCompare)
1120         .map(function(c){
1121           return c.cookieString();
1122         })
1123         .join('; '));
1124     }
1125   };
1126   args.push(next);
1127   this.getCookies.apply(this,args);
1128 };
1129
1130 CAN_BE_SYNC.push('getSetCookieStrings');
1131 CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {
1132   var args = Array.prototype.slice.call(arguments,0);
1133   var cb = args.pop();
1134   var next = function(err,cookies) {
1135     if (err) {
1136       cb(err);
1137     } else {
1138       cb(null, cookies.map(function(c){
1139         return c.toString();
1140       }));
1141     }
1142   };
1143   args.push(next);
1144   this.getCookies.apply(this,args);
1145 };
1146
1147 CAN_BE_SYNC.push('serialize');
1148 CookieJar.prototype.serialize = function(cb) {
1149   var type = this.store.constructor.name;
1150   if (type === 'Object') {
1151     type = null;
1152   }
1153
1154   // update README.md "Serialization Format" if you change this, please!
1155   var serialized = {
1156     // The version of tough-cookie that serialized this jar. Generally a good
1157     // practice since future versions can make data import decisions based on
1158     // known past behavior. When/if this matters, use `semver`.
1159     version: 'tough-cookie@'+VERSION,
1160
1161     // add the store type, to make humans happy:
1162     storeType: type,
1163
1164     // CookieJar configuration:
1165     rejectPublicSuffixes: !!this.rejectPublicSuffixes,
1166
1167     // this gets filled from getAllCookies:
1168     cookies: []
1169   };
1170
1171   if (!(this.store.getAllCookies &&
1172         typeof this.store.getAllCookies === 'function'))
1173   {
1174     return cb(new Error('store does not support getAllCookies and cannot be serialized'));
1175   }
1176
1177   this.store.getAllCookies(function(err,cookies) {
1178     if (err) {
1179       return cb(err);
1180     }
1181
1182     serialized.cookies = cookies.map(function(cookie) {
1183       // convert to serialized 'raw' cookies
1184       cookie = (cookie instanceof Cookie) ? cookie.toJSON() : cookie;
1185
1186       // Remove the index so new ones get assigned during deserialization
1187       delete cookie.creationIndex;
1188
1189       return cookie;
1190     });
1191
1192     return cb(null, serialized);
1193   });
1194 };
1195
1196 // well-known name that JSON.stringify calls
1197 CookieJar.prototype.toJSON = function() {
1198   return this.serializeSync();
1199 };
1200
1201 // use the class method CookieJar.deserialize instead of calling this directly
1202 CAN_BE_SYNC.push('_importCookies');
1203 CookieJar.prototype._importCookies = function(serialized, cb) {
1204   var jar = this;
1205   var cookies = serialized.cookies;
1206   if (!cookies || !Array.isArray(cookies)) {
1207     return cb(new Error('serialized jar has no cookies array'));
1208   }
1209
1210   function putNext(err) {
1211     if (err) {
1212       return cb(err);
1213     }
1214
1215     if (!cookies.length) {
1216       return cb(err, jar);
1217     }
1218
1219     var cookie;
1220     try {
1221       cookie = fromJSON(cookies.shift());
1222     } catch (e) {
1223       return cb(e);
1224     }
1225
1226     if (cookie === null) {
1227       return putNext(null); // skip this cookie
1228     }
1229
1230     jar.store.putCookie(cookie, putNext);
1231   }
1232
1233   putNext();
1234 };
1235
1236 CookieJar.deserialize = function(strOrObj, store, cb) {
1237   if (arguments.length !== 3) {
1238     // store is optional
1239     cb = store;
1240     store = null;
1241   }
1242
1243   var serialized;
1244   if (typeof strOrObj === 'string') {
1245     serialized = jsonParse(strOrObj);
1246     if (serialized instanceof Error) {
1247       return cb(serialized);
1248     }
1249   } else {
1250     serialized = strOrObj;
1251   }
1252
1253   var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
1254   jar._importCookies(serialized, function(err) {
1255     if (err) {
1256       return cb(err);
1257     }
1258     cb(null, jar);
1259   });
1260 };
1261
1262 CookieJar.deserializeSync = function(strOrObj, store) {
1263   var serialized = typeof strOrObj === 'string' ?
1264     JSON.parse(strOrObj) : strOrObj;
1265   var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
1266
1267   // catch this mistake early:
1268   if (!jar.store.synchronous) {
1269     throw new Error('CookieJar store is not synchronous; use async API instead.');
1270   }
1271
1272   jar._importCookiesSync(serialized);
1273   return jar;
1274 };
1275 CookieJar.fromJSON = CookieJar.deserializeSync;
1276
1277 CAN_BE_SYNC.push('clone');
1278 CookieJar.prototype.clone = function(newStore, cb) {
1279   if (arguments.length === 1) {
1280     cb = newStore;
1281     newStore = null;
1282   }
1283
1284   this.serialize(function(err,serialized) {
1285     if (err) {
1286       return cb(err);
1287     }
1288     CookieJar.deserialize(newStore, serialized, cb);
1289   });
1290 };
1291
1292 // Use a closure to provide a true imperative API for synchronous stores.
1293 function syncWrap(method) {
1294   return function() {
1295     if (!this.store.synchronous) {
1296       throw new Error('CookieJar store is not synchronous; use async API instead.');
1297     }
1298
1299     var args = Array.prototype.slice.call(arguments);
1300     var syncErr, syncResult;
1301     args.push(function syncCb(err, result) {
1302       syncErr = err;
1303       syncResult = result;
1304     });
1305     this[method].apply(this, args);
1306
1307     if (syncErr) {
1308       throw syncErr;
1309     }
1310     return syncResult;
1311   };
1312 }
1313
1314 // wrap all declared CAN_BE_SYNC methods in the sync wrapper
1315 CAN_BE_SYNC.forEach(function(method) {
1316   CookieJar.prototype[method+'Sync'] = syncWrap(method);
1317 });
1318
1319 module.exports = {
1320   CookieJar: CookieJar,
1321   Cookie: Cookie,
1322   Store: Store,
1323   MemoryCookieStore: MemoryCookieStore,
1324   parseDate: parseDate,
1325   formatDate: formatDate,
1326   parse: parse,
1327   fromJSON: fromJSON,
1328   domainMatch: domainMatch,
1329   defaultPath: defaultPath,
1330   pathMatch: pathMatch,
1331   getPublicSuffix: pubsuffix.getPublicSuffix,
1332   cookieCompare: cookieCompare,
1333   permuteDomain: require('./permuteDomain').permuteDomain,
1334   permutePath: permutePath,
1335   canonicalDomain: canonicalDomain
1336 };