12da297ac4d20fda50d6995f3631b213b8291043
[yaffs-website] / node_modules / uncss / 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 // Used for checking whether or not there is a trailing semi-colon
72 var TRAILING_SEMICOLON = /;+$/;
73
74 var DAY_OF_MONTH = /^(\d{1,2})[^\d]*$/;
75 var TIME = /^(\d{1,2})[^\d]*:(\d{1,2})[^\d]*:(\d{1,2})[^\d]*$/;
76 var MONTH = /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i;
77
78 var MONTH_TO_NUM = {
79   jan:0, feb:1, mar:2, apr:3, may:4, jun:5,
80   jul:6, aug:7, sep:8, oct:9, nov:10, dec:11
81 };
82 var NUM_TO_MONTH = [
83   'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'
84 ];
85 var NUM_TO_DAY = [
86   'Sun','Mon','Tue','Wed','Thu','Fri','Sat'
87 ];
88
89 var YEAR = /^(\d{2}|\d{4})$/; // 2 to 4 digits
90
91 var MAX_TIME = 2147483647000; // 31-bit max
92 var MIN_TIME = 0; // 31-bit min
93
94
95 // RFC6265 S5.1.1 date parser:
96 function parseDate(str) {
97   if (!str) {
98     return;
99   }
100
101   /* RFC6265 S5.1.1:
102    * 2. Process each date-token sequentially in the order the date-tokens
103    * appear in the cookie-date
104    */
105   var tokens = str.split(DATE_DELIM);
106   if (!tokens) {
107     return;
108   }
109
110   var hour = null;
111   var minutes = null;
112   var seconds = null;
113   var day = null;
114   var month = null;
115   var year = null;
116
117   for (var i=0; i<tokens.length; i++) {
118     var token = tokens[i].trim();
119     if (!token.length) {
120       continue;
121     }
122
123     var result;
124
125     /* 2.1. If the found-time flag is not set and the token matches the time
126      * production, set the found-time flag and set the hour- value,
127      * minute-value, and second-value to the numbers denoted by the digits in
128      * the date-token, respectively.  Skip the remaining sub-steps and continue
129      * to the next date-token.
130      */
131     if (seconds === null) {
132       result = TIME.exec(token);
133       if (result) {
134         hour = parseInt(result[1], 10);
135         minutes = parseInt(result[2], 10);
136         seconds = parseInt(result[3], 10);
137         /* RFC6265 S5.1.1.5:
138          * [fail if]
139          * *  the hour-value is greater than 23,
140          * *  the minute-value is greater than 59, or
141          * *  the second-value is greater than 59.
142          */
143         if(hour > 23 || minutes > 59 || seconds > 59) {
144           return;
145         }
146
147         continue;
148       }
149     }
150
151     /* 2.2. If the found-day-of-month flag is not set and the date-token matches
152      * the day-of-month production, set the found-day-of- month flag and set
153      * the day-of-month-value to the number denoted by the date-token.  Skip
154      * the remaining sub-steps and continue to the next date-token.
155      */
156     if (day === null) {
157       result = DAY_OF_MONTH.exec(token);
158       if (result) {
159         day = parseInt(result, 10);
160         /* RFC6265 S5.1.1.5:
161          * [fail if] the day-of-month-value is less than 1 or greater than 31
162          */
163         if(day < 1 || day > 31) {
164           return;
165         }
166         continue;
167       }
168     }
169
170     /* 2.3. If the found-month flag is not set and the date-token matches the
171      * month production, set the found-month flag and set the month-value to
172      * the month denoted by the date-token.  Skip the remaining sub-steps and
173      * continue to the next date-token.
174      */
175     if (month === null) {
176       result = MONTH.exec(token);
177       if (result) {
178         month = MONTH_TO_NUM[result[1].toLowerCase()];
179         continue;
180       }
181     }
182
183     /* 2.4. If the found-year flag is not set and the date-token matches the year
184      * production, set the found-year flag and set the year-value to the number
185      * denoted by the date-token.  Skip the remaining sub-steps and continue to
186      * the next date-token.
187      */
188     if (year === null) {
189       result = YEAR.exec(token);
190       if (result) {
191         year = parseInt(result[0], 10);
192         /* From S5.1.1:
193          * 3.  If the year-value is greater than or equal to 70 and less
194          * than or equal to 99, increment the year-value by 1900.
195          * 4.  If the year-value is greater than or equal to 0 and less
196          * than or equal to 69, increment the year-value by 2000.
197          */
198         if (70 <= year && year <= 99) {
199           year += 1900;
200         } else if (0 <= year && year <= 69) {
201           year += 2000;
202         }
203
204         if (year < 1601) {
205           return; // 5. ... the year-value is less than 1601
206         }
207       }
208     }
209   }
210
211   if (seconds === null || day === null || month === null || year === null) {
212     return; // 5. ... at least one of the found-day-of-month, found-month, found-
213             // year, or found-time flags is not set,
214   }
215
216   return new Date(Date.UTC(year, month, day, hour, minutes, seconds));
217 }
218
219 function formatDate(date) {
220   var d = date.getUTCDate(); d = d >= 10 ? d : '0'+d;
221   var h = date.getUTCHours(); h = h >= 10 ? h : '0'+h;
222   var m = date.getUTCMinutes(); m = m >= 10 ? m : '0'+m;
223   var s = date.getUTCSeconds(); s = s >= 10 ? s : '0'+s;
224   return NUM_TO_DAY[date.getUTCDay()] + ', ' +
225     d+' '+ NUM_TO_MONTH[date.getUTCMonth()] +' '+ date.getUTCFullYear() +' '+
226     h+':'+m+':'+s+' GMT';
227 }
228
229 // S5.1.2 Canonicalized Host Names
230 function canonicalDomain(str) {
231   if (str == null) {
232     return null;
233   }
234   str = str.trim().replace(/^\./,''); // S4.1.2.3 & S5.2.3: ignore leading .
235
236   // convert to IDN if any non-ASCII characters
237   if (punycode && /[^\u0001-\u007f]/.test(str)) {
238     str = punycode.toASCII(str);
239   }
240
241   return str.toLowerCase();
242 }
243
244 // S5.1.3 Domain Matching
245 function domainMatch(str, domStr, canonicalize) {
246   if (str == null || domStr == null) {
247     return null;
248   }
249   if (canonicalize !== false) {
250     str = canonicalDomain(str);
251     domStr = canonicalDomain(domStr);
252   }
253
254   /*
255    * "The domain string and the string are identical. (Note that both the
256    * domain string and the string will have been canonicalized to lower case at
257    * this point)"
258    */
259   if (str == domStr) {
260     return true;
261   }
262
263   /* "All of the following [three] conditions hold:" (order adjusted from the RFC) */
264
265   /* "* The string is a host name (i.e., not an IP address)." */
266   if (net.isIP(str)) {
267     return false;
268   }
269
270   /* "* The domain string is a suffix of the string" */
271   var idx = str.indexOf(domStr);
272   if (idx <= 0) {
273     return false; // it's a non-match (-1) or prefix (0)
274   }
275
276   // e.g "a.b.c".indexOf("b.c") === 2
277   // 5 === 3+2
278   if (str.length !== domStr.length + idx) { // it's not a suffix
279     return false;
280   }
281
282   /* "* The last character of the string that is not included in the domain
283   * string is a %x2E (".") character." */
284   if (str.substr(idx-1,1) !== '.') {
285     return false;
286   }
287
288   return true;
289 }
290
291
292 // RFC6265 S5.1.4 Paths and Path-Match
293
294 /*
295  * "The user agent MUST use an algorithm equivalent to the following algorithm
296  * to compute the default-path of a cookie:"
297  *
298  * Assumption: the path (and not query part or absolute uri) is passed in.
299  */
300 function defaultPath(path) {
301   // "2. If the uri-path is empty or if the first character of the uri-path is not
302   // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.
303   if (!path || path.substr(0,1) !== "/") {
304     return "/";
305   }
306
307   // "3. If the uri-path contains no more than one %x2F ("/") character, output
308   // %x2F ("/") and skip the remaining step."
309   if (path === "/") {
310     return path;
311   }
312
313   var rightSlash = path.lastIndexOf("/");
314   if (rightSlash === 0) {
315     return "/";
316   }
317
318   // "4. Output the characters of the uri-path from the first character up to,
319   // but not including, the right-most %x2F ("/")."
320   return path.slice(0, rightSlash);
321 }
322
323
324 function parse(str, options) {
325   if (!options || typeof options !== 'object') {
326     options = {};
327   }
328   str = str.trim();
329
330   // S4.1.1 Trailing semi-colons are not part of the specification.
331   var semiColonCheck = TRAILING_SEMICOLON.exec(str);
332   if (semiColonCheck) {
333     str = str.slice(0, semiColonCheck.index);
334   }
335
336   // We use a regex to parse the "name-value-pair" part of S5.2
337   var firstSemi = str.indexOf(';'); // S5.2 step 1
338   var pairRe = options.loose ? LOOSE_COOKIE_PAIR : COOKIE_PAIR;
339   var result = pairRe.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));
340
341   // Rx satisfies the "the name string is empty" and "lacks a %x3D ("=")"
342   // constraints as well as trimming any whitespace.
343   if (!result) {
344     return;
345   }
346
347   var c = new Cookie();
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   }
357
358   if (firstSemi === -1) {
359     return c;
360   }
361
362   // S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string
363   // (including the %x3B (";") in question)." plus later on in the same section
364   // "discard the first ";" and trim".
365   var unparsed = str.slice(firstSemi).replace(/^\s*;\s*/,'').trim();
366
367   // "If the unparsed-attributes string is empty, skip the rest of these
368   // steps."
369   if (unparsed.length === 0) {
370     return c;
371   }
372
373   /*
374    * S5.2 says that when looping over the items "[p]rocess the attribute-name
375    * and attribute-value according to the requirements in the following
376    * subsections" for every item.  Plus, for many of the individual attributes
377    * in S5.3 it says to use the "attribute-value of the last attribute in the
378    * cookie-attribute-list".  Therefore, in this implementation, we overwrite
379    * the previous value.
380    */
381   var cookie_avs = unparsed.split(/\s*;\s*/);
382   while (cookie_avs.length) {
383     var av = cookie_avs.shift();
384     var av_sep = av.indexOf('=');
385     var av_key, av_value;
386
387     if (av_sep === -1) {
388       av_key = av;
389       av_value = null;
390     } else {
391       av_key = av.substr(0,av_sep);
392       av_value = av.substr(av_sep+1);
393     }
394
395     av_key = av_key.trim().toLowerCase();
396
397     if (av_value) {
398       av_value = av_value.trim();
399     }
400
401     switch(av_key) {
402     case 'expires': // S5.2.1
403       if (av_value) {
404         var exp = parseDate(av_value);
405         // "If the attribute-value failed to parse as a cookie date, ignore the
406         // cookie-av."
407         if (exp) {
408           // over and underflow not realistically a concern: V8's getTime() seems to
409           // store something larger than a 32-bit time_t (even with 32-bit node)
410           c.expires = exp;
411         }
412       }
413       break;
414
415     case 'max-age': // S5.2.2
416       if (av_value) {
417         // "If the first character of the attribute-value is not a DIGIT or a "-"
418         // character ...[or]... If the remainder of attribute-value contains a
419         // non-DIGIT character, ignore the cookie-av."
420         if (/^-?[0-9]+$/.test(av_value)) {
421           var delta = parseInt(av_value, 10);
422           // "If delta-seconds is less than or equal to zero (0), let expiry-time
423           // be the earliest representable date and time."
424           c.setMaxAge(delta);
425         }
426       }
427       break;
428
429     case 'domain': // S5.2.3
430       // "If the attribute-value is empty, the behavior is undefined.  However,
431       // the user agent SHOULD ignore the cookie-av entirely."
432       if (av_value) {
433         // S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E
434         // (".") character."
435         var domain = av_value.trim().replace(/^\./, '');
436         if (domain) {
437           // "Convert the cookie-domain to lower case."
438           c.domain = domain.toLowerCase();
439         }
440       }
441       break;
442
443     case 'path': // S5.2.4
444       /*
445        * "If the attribute-value is empty or if the first character of the
446        * attribute-value is not %x2F ("/"):
447        *   Let cookie-path be the default-path.
448        * Otherwise:
449        *   Let cookie-path be the attribute-value."
450        *
451        * We'll represent the default-path as null since it depends on the
452        * context of the parsing.
453        */
454       c.path = av_value && av_value[0] === "/" ? av_value : null;
455       break;
456
457     case 'secure': // S5.2.5
458       /*
459        * "If the attribute-name case-insensitively matches the string "Secure",
460        * the user agent MUST append an attribute to the cookie-attribute-list
461        * with an attribute-name of Secure and an empty attribute-value."
462        */
463       c.secure = true;
464       break;
465
466     case 'httponly': // S5.2.6 -- effectively the same as 'secure'
467       c.httpOnly = true;
468       break;
469
470     default:
471       c.extensions = c.extensions || [];
472       c.extensions.push(av);
473       break;
474     }
475   }
476
477   return c;
478 }
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
491 function fromJSON(str) {
492   if (!str) {
493     return null;
494   }
495
496   var obj;
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;
505   }
506
507   var c = new Cookie();
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
514     }
515
516     if (prop === 'expires' ||
517         prop === 'creation' ||
518         prop === 'lastAccessed')
519     {
520       if (obj[prop] === null) {
521         c[prop] = null;
522       } else {
523         c[prop] = obj[prop] == "Infinity" ?
524           "Infinity" : new Date(obj[prop]);
525       }
526     } else {
527       c[prop] = obj[prop];
528     }
529   }
530
531   return c;
532 }
533
534 /* Section 5.4 part 2:
535  * "*  Cookies with longer paths are listed before cookies with
536  *     shorter paths.
537  *
538  *  *  Among cookies that have equal-length path fields, cookies with
539  *     earlier creation-times are listed before cookies with later
540  *     creation-times."
541  */
542
543 function cookieCompare(a,b) {
544   var cmp = 0;
545
546   // descending for length: b CMP a
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;
552   }
553
554   // ascending for time: a CMP b
555   var aTime = a.creation ? a.creation.getTime() : MAX_TIME;
556   var bTime = b.creation ? b.creation.getTime() : MAX_TIME;
557   cmp = aTime - bTime;
558   if (cmp !== 0) {
559     return cmp;
560   }
561
562   // break ties for the same millisecond (precision of JavaScript's clock)
563   cmp = a.creationIndex - b.creationIndex;
564
565   return cmp;
566 }
567
568 // Gives the permutation of all possible pathMatch()es of a given path. The
569 // array is in longest-to-shortest order.  Handy for indexing.
570 function permutePath(path) {
571   if (path === '/') {
572     return ['/'];
573   }
574   if (path.lastIndexOf('/') === path.length-1) {
575     path = path.substr(0,path.length-1);
576   }
577   var permutations = [path];
578   while (path.length > 1) {
579     var lindex = path.lastIndexOf('/');
580     if (lindex === 0) {
581       break;
582     }
583     path = path.substr(0,lindex);
584     permutations.push(path);
585   }
586   permutations.push('/');
587   return permutations;
588 }
589
590 function getCookieContext(url) {
591   if (url instanceof Object) {
592     return url;
593   }
594   // NOTE: decodeURI will throw on malformed URIs (see GH-32).
595   // Therefore, we will just skip decoding for such URIs.
596   try {
597     url = decodeURI(url);
598   }
599   catch(err) {
600     // Silently swallow error
601   }
602
603   return urlParse(url);
604 }
605
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];
615     }
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   });
627 }
628
629 Cookie.cookiesCreated = 0; // incremented each time a cookie is created
630
631 Cookie.parse = parse;
632 Cookie.fromJSON = fromJSON;
633
634 Cookie.prototype.key = "";
635 Cookie.prototype.value = "";
636
637 // the order in which the RFC has them:
638 Cookie.prototype.expires = "Infinity"; // coerces to literal Infinity
639 Cookie.prototype.maxAge = null; // takes precedence over expires for TTL
640 Cookie.prototype.domain = null;
641 Cookie.prototype.path = null;
642 Cookie.prototype.secure = false;
643 Cookie.prototype.httpOnly = false;
644 Cookie.prototype.extensions = null;
645
646 // set by the CookieJar:
647 Cookie.prototype.hostOnly = null; // boolean when set
648 Cookie.prototype.pathIsDefault = null; // boolean when set
649 Cookie.prototype.creation = null; // Date when set; defaulted by Cookie.parse
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 });
657
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   });
666
667 Cookie.prototype.inspect = function inspect() {
668   var now = Date.now();
669   return 'Cookie="'+this.toString() +
670     '; hostOnly='+(this.hostOnly != null ? this.hostOnly : '?') +
671     '; aAge='+(this.lastAccessed ? (now-this.lastAccessed.getTime())+'ms' : '?') +
672     '; cAge='+(this.creation ? (now-this.creation.getTime())+'ms' : '?') +
673     '"';
674 };
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
716 Cookie.prototype.validate = function validate() {
717   if (!COOKIE_OCTETS.test(this.value)) {
718     return false;
719   }
720   if (this.expires != Infinity && !(this.expires instanceof Date) && !parseDate(this.expires)) {
721     return false;
722   }
723   if (this.maxAge != null && this.maxAge <= 0) {
724     return false; // "Max-Age=" non-zero-digit *DIGIT
725   }
726   if (this.path != null && !PATH_VALUE.test(this.path)) {
727     return false;
728   }
729
730   var cdomain = this.cdomain();
731   if (cdomain) {
732     if (cdomain.match(/\.$/)) {
733       return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this
734     }
735     var suffix = pubsuffix.getPublicSuffix(cdomain);
736     if (suffix == null) { // it's a public suffix
737       return false;
738     }
739   }
740   return true;
741 };
742
743 Cookie.prototype.setExpires = function setExpires(exp) {
744   if (exp instanceof Date) {
745     this.expires = exp;
746   } else {
747     this.expires = parseDate(exp) || "Infinity";
748   }
749 };
750
751 Cookie.prototype.setMaxAge = function setMaxAge(age) {
752   if (age === Infinity || age === -Infinity) {
753     this.maxAge = age.toString(); // so JSON.stringify() works
754   } else {
755     this.maxAge = age;
756   }
757 };
758
759 // gives Cookie header format
760 Cookie.prototype.cookieString = function cookieString() {
761   var val = this.value;
762   if (val == null) {
763     val = '';
764   }
765   if (this.key === '') {
766     return val;
767   }
768   return this.key+'='+val;
769 };
770
771 // gives Set-Cookie header format
772 Cookie.prototype.toString = function toString() {
773   var str = this.cookieString();
774
775   if (this.expires != Infinity) {
776     if (this.expires instanceof Date) {
777       str += '; Expires='+formatDate(this.expires);
778     } else {
779       str += '; Expires='+this.expires;
780     }
781   }
782
783   if (this.maxAge != null && this.maxAge != Infinity) {
784     str += '; Max-Age='+this.maxAge;
785   }
786
787   if (this.domain && !this.hostOnly) {
788     str += '; Domain='+this.domain;
789   }
790   if (this.path) {
791     str += '; Path='+this.path;
792   }
793
794   if (this.secure) {
795     str += '; Secure';
796   }
797   if (this.httpOnly) {
798     str += '; HttpOnly';
799   }
800   if (this.extensions) {
801     this.extensions.forEach(function(ext) {
802       str += '; '+ext;
803     });
804   }
805
806   return str;
807 };
808
809 // TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
810 // elsewhere)
811 // S5.3 says to give the "latest representable date" for which we use Infinity
812 // For "expired" we use 0
813 Cookie.prototype.TTL = function TTL(now) {
814   /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires
815    * attribute, the Max-Age attribute has precedence and controls the
816    * expiration date of the cookie.
817    * (Concurs with S5.3 step 3)
818    */
819   if (this.maxAge != null) {
820     return this.maxAge<=0 ? 0 : this.maxAge*1000;
821   }
822
823   var expires = this.expires;
824   if (expires != Infinity) {
825     if (!(expires instanceof Date)) {
826       expires = parseDate(expires) || Infinity;
827     }
828
829     if (expires == Infinity) {
830       return Infinity;
831     }
832
833     return expires.getTime() - (now || Date.now());
834   }
835
836   return Infinity;
837 };
838
839 // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
840 // elsewhere)
841 Cookie.prototype.expiryTime = function expiryTime(now) {
842   if (this.maxAge != null) {
843     var relativeTo = now || this.creation || new Date();
844     var age = (this.maxAge <= 0) ? -Infinity : this.maxAge*1000;
845     return relativeTo.getTime() + age;
846   }
847
848   if (this.expires == Infinity) {
849     return Infinity;
850   }
851   return this.expires.getTime();
852 };
853
854 // expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
855 // elsewhere), except it returns a Date
856 Cookie.prototype.expiryDate = function expiryDate(now) {
857   var millisec = this.expiryTime(now);
858   if (millisec == Infinity) {
859     return new Date(MAX_TIME);
860   } else if (millisec == -Infinity) {
861     return new Date(MIN_TIME);
862   } else {
863     return new Date(millisec);
864   }
865 };
866
867 // This replaces the "persistent-flag" parts of S5.3 step 3
868 Cookie.prototype.isPersistent = function isPersistent() {
869   return (this.maxAge != null || this.expires != Infinity);
870 };
871
872 // Mostly S5.1.2 and S5.2.3:
873 Cookie.prototype.cdomain =
874 Cookie.prototype.canonicalizedDomain = function canonicalizedDomain() {
875   if (this.domain == null) {
876     return null;
877   }
878   return canonicalDomain(this.domain);
879 };
880
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;
892   }
893
894   if (!store) {
895     store = new MemoryCookieStore();
896   }
897   this.store = store;
898 }
899 CookieJar.prototype.store = null;
900 CookieJar.prototype.rejectPublicSuffixes = true;
901 CookieJar.prototype.enableLooseMode = false;
902 var CAN_BE_SYNC = [];
903
904 CAN_BE_SYNC.push('setCookie');
905 CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
906   var err;
907   var context = getCookieContext(url);
908   if (options instanceof Function) {
909     cb = options;
910     options = {};
911   }
912
913   var host = canonicalDomain(context.hostname);
914   var loose = this.enableLooseMode;
915   if (options.loose != null) {
916     loose = options.loose;
917   }
918
919   // S5.3 step 1
920   if (!(cookie instanceof Cookie)) {
921     cookie = Cookie.parse(cookie, { loose: loose });
922   }
923   if (!cookie) {
924     err = new Error("Cookie failed to parse");
925     return cb(options.ignoreError ? null : err);
926   }
927
928   // S5.3 step 2
929   var now = options.now || new Date(); // will assign later to save effort in the face of errors
930
931   // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie()
932
933   // S5.3 step 4: NOOP; domain is null by default
934
935   // S5.3 step 5: public suffixes
936   if (this.rejectPublicSuffixes && cookie.domain) {
937     var suffix = pubsuffix.getPublicSuffix(cookie.cdomain());
938     if (suffix == null) { // e.g. "com"
939       err = new Error("Cookie has domain set to a public suffix");
940       return cb(options.ignoreError ? null : err);
941     }
942   }
943
944   // S5.3 step 6:
945   if (cookie.domain) {
946     if (!domainMatch(host, cookie.cdomain(), false)) {
947       err = new Error("Cookie not in this host's domain. Cookie:"+cookie.cdomain()+" Request:"+host);
948       return cb(options.ignoreError ? null : err);
949     }
950
951     if (cookie.hostOnly == null) { // don't reset if already set
952       cookie.hostOnly = false;
953     }
954
955   } else {
956     cookie.hostOnly = true;
957     cookie.domain = host;
958   }
959
960   //S5.2.4 If the attribute-value is empty or if the first character of the
961   //attribute-value is not %x2F ("/"):
962   //Let cookie-path be the default-path.
963   if (!cookie.path || cookie.path[0] !== '/') {
964     cookie.path = defaultPath(context.pathname);
965     cookie.pathIsDefault = true;
966   }
967
968   // S5.3 step 8: NOOP; secure attribute
969   // S5.3 step 9: NOOP; httpOnly attribute
970
971   // S5.3 step 10
972   if (options.http === false && cookie.httpOnly) {
973     err = new Error("Cookie is HttpOnly and this isn't an HTTP API");
974     return cb(options.ignoreError ? null : err);
975   }
976
977   var store = this.store;
978
979   if (!store.updateCookie) {
980     store.updateCookie = function(oldCookie, newCookie, cb) {
981       this.putCookie(newCookie, cb);
982     };
983   }
984
985   function withCookie(err, oldCookie) {
986     if (err) {
987       return cb(err);
988     }
989
990     var next = function(err) {
991       if (err) {
992         return cb(err);
993       } else {
994         cb(null, cookie);
995       }
996     };
997
998     if (oldCookie) {
999       // S5.3 step 11 - "If the cookie store contains a cookie with the same name,
1000       // domain, and path as the newly created cookie:"
1001       if (options.http === false && oldCookie.httpOnly) { // step 11.2
1002         err = new Error("old Cookie is HttpOnly and this isn't an HTTP API");
1003         return cb(options.ignoreError ? null : err);
1004       }
1005       cookie.creation = oldCookie.creation; // step 11.3
1006       cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker
1007       cookie.lastAccessed = now;
1008       // Step 11.4 (delete cookie) is implied by just setting the new one:
1009       store.updateCookie(oldCookie, cookie, next); // step 12
1010
1011     } else {
1012       cookie.creation = cookie.lastAccessed = now;
1013       store.putCookie(cookie, next); // step 12
1014     }
1015   }
1016
1017   store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);
1018 };
1019
1020 // RFC6365 S5.4
1021 CAN_BE_SYNC.push('getCookies');
1022 CookieJar.prototype.getCookies = function(url, options, cb) {
1023   var context = getCookieContext(url);
1024   if (options instanceof Function) {
1025     cb = options;
1026     options = {};
1027   }
1028
1029   var host = canonicalDomain(context.hostname);
1030   var path = context.pathname || '/';
1031
1032   var secure = options.secure;
1033   if (secure == null && context.protocol &&
1034       (context.protocol == 'https:' || context.protocol == 'wss:'))
1035   {
1036     secure = true;
1037   }
1038
1039   var http = options.http;
1040   if (http == null) {
1041     http = true;
1042   }
1043
1044   var now = options.now || Date.now();
1045   var expireCheck = options.expire !== false;
1046   var allPaths = !!options.allPaths;
1047   var store = this.store;
1048
1049   function matchingCookie(c) {
1050     // "Either:
1051     //   The cookie's host-only-flag is true and the canonicalized
1052     //   request-host is identical to the cookie's domain.
1053     // Or:
1054     //   The cookie's host-only-flag is false and the canonicalized
1055     //   request-host domain-matches the cookie's domain."
1056     if (c.hostOnly) {
1057       if (c.domain != host) {
1058         return false;
1059       }
1060     } else {
1061       if (!domainMatch(host, c.domain, false)) {
1062         return false;
1063       }
1064     }
1065
1066     // "The request-uri's path path-matches the cookie's path."
1067     if (!allPaths && !pathMatch(path, c.path)) {
1068       return false;
1069     }
1070
1071     // "If the cookie's secure-only-flag is true, then the request-uri's
1072     // scheme must denote a "secure" protocol"
1073     if (c.secure && !secure) {
1074       return false;
1075     }
1076
1077     // "If the cookie's http-only-flag is true, then exclude the cookie if the
1078     // cookie-string is being generated for a "non-HTTP" API"
1079     if (c.httpOnly && !http) {
1080       return false;
1081     }
1082
1083     // deferred from S5.3
1084     // non-RFC: allow retention of expired cookies by choice
1085     if (expireCheck && c.expiryTime() <= now) {
1086       store.removeCookie(c.domain, c.path, c.key, function(){}); // result ignored
1087       return false;
1088     }
1089
1090     return true;
1091   }
1092
1093   store.findCookies(host, allPaths ? null : path, function(err,cookies) {
1094     if (err) {
1095       return cb(err);
1096     }
1097
1098     cookies = cookies.filter(matchingCookie);
1099
1100     // sorting of S5.4 part 2
1101     if (options.sort !== false) {
1102       cookies = cookies.sort(cookieCompare);
1103     }
1104
1105     // S5.4 part 3
1106     var now = new Date();
1107     cookies.forEach(function(c) {
1108       c.lastAccessed = now;
1109     });
1110     // TODO persist lastAccessed
1111
1112     cb(null,cookies);
1113   });
1114 };
1115
1116 CAN_BE_SYNC.push('getCookieString');
1117 CookieJar.prototype.getCookieString = function(/*..., cb*/) {
1118   var args = Array.prototype.slice.call(arguments,0);
1119   var cb = args.pop();
1120   var next = function(err,cookies) {
1121     if (err) {
1122       cb(err);
1123     } else {
1124       cb(null, cookies
1125         .sort(cookieCompare)
1126         .map(function(c){
1127           return c.cookieString();
1128         })
1129         .join('; '));
1130     }
1131   };
1132   args.push(next);
1133   this.getCookies.apply(this,args);
1134 };
1135
1136 CAN_BE_SYNC.push('getSetCookieStrings');
1137 CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {
1138   var args = Array.prototype.slice.call(arguments,0);
1139   var cb = args.pop();
1140   var next = function(err,cookies) {
1141     if (err) {
1142       cb(err);
1143     } else {
1144       cb(null, cookies.map(function(c){
1145         return c.toString();
1146       }));
1147     }
1148   };
1149   args.push(next);
1150   this.getCookies.apply(this,args);
1151 };
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
1298 // Use a closure to provide a true imperative API for synchronous stores.
1299 function syncWrap(method) {
1300   return function() {
1301     if (!this.store.synchronous) {
1302       throw new Error('CookieJar store is not synchronous; use async API instead.');
1303     }
1304
1305     var args = Array.prototype.slice.call(arguments);
1306     var syncErr, syncResult;
1307     args.push(function syncCb(err, result) {
1308       syncErr = err;
1309       syncResult = result;
1310     });
1311     this[method].apply(this, args);
1312
1313     if (syncErr) {
1314       throw syncErr;
1315     }
1316     return syncResult;
1317   };
1318 }
1319
1320 // wrap all declared CAN_BE_SYNC methods in the sync wrapper
1321 CAN_BE_SYNC.forEach(function(method) {
1322   CookieJar.prototype[method+'Sync'] = syncWrap(method);
1323 });
1324
1325 module.exports = {
1326   CookieJar: CookieJar,
1327   Cookie: Cookie,
1328   Store: Store,
1329   MemoryCookieStore: MemoryCookieStore,
1330   parseDate: parseDate,
1331   formatDate: formatDate,
1332   parse: parse,
1333   fromJSON: fromJSON,
1334   domainMatch: domainMatch,
1335   defaultPath: defaultPath,
1336   pathMatch: pathMatch,
1337   getPublicSuffix: pubsuffix.getPublicSuffix,
1338   cookieCompare: cookieCompare,
1339   permuteDomain: require('./permuteDomain').permuteDomain,
1340   permutePath: permutePath,
1341   canonicalDomain: canonicalDomain
1342 };