"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/tough-cookie/lib/cookie.js" (7 Feb 2017, 37305 Bytes) of archive /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Javascript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    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 };