| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758 | /*! * Copyright (c) 2015-2020, Salesforce.com, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of Salesforce.com nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */"use strict";const punycode = require("punycode/");const urlParse = require("url-parse");const pubsuffix = require("./pubsuffix-psl");const Store = require("./store").Store;const MemoryCookieStore = require("./memstore").MemoryCookieStore;const pathMatch = require("./pathMatch").pathMatch;const validators = require("./validators.js");const VERSION = require("./version");const { fromCallback } = require("universalify");const { getCustomInspectSymbol } = require("./utilHelper");// From RFC6265 S4.1.1// note that it excludes \x3B ";"const COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/;const CONTROL_CHARS = /[\x00-\x1F]/;// From Chromium // '\r', '\n' and '\0' should be treated as a terminator in// the "relaxed" mode, see:// https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60const TERMINATORS = ["\n", "\r", "\0"];// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'// Note ';' is \x3Bconst PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;// date-time parsing constants (RFC6265 S5.1.1)const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;const MONTH_TO_NUM = {  jan: 0,  feb: 1,  mar: 2,  apr: 3,  may: 4,  jun: 5,  jul: 6,  aug: 7,  sep: 8,  oct: 9,  nov: 10,  dec: 11};const MAX_TIME = 2147483647000; // 31-bit maxconst MIN_TIME = 0; // 31-bit minconst SAME_SITE_CONTEXT_VAL_ERR =  'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"';function checkSameSiteContext(value) {  validators.validate(validators.isNonEmptyString(value), value);  const context = String(value).toLowerCase();  if (context === "none" || context === "lax" || context === "strict") {    return context;  } else {    return null;  }}const PrefixSecurityEnum = Object.freeze({  SILENT: "silent",  STRICT: "strict",  DISABLED: "unsafe-disabled"});// Dumped from ip-regex@4.0.0, with the following changes:// * all capturing groups converted to non-capturing -- "(?:)"// * support for IPv6 Scoped Literal ("%eth1") removed// * lowercase hexadecimal onlyconst IP_REGEX_LOWERCASE = /(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/;const IP_V6_REGEX = `\\[?(?:(?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)|(?:[a-fA-F\\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,2}|:)|(?:[a-fA-F\\d]{1,4}:){4}(?:(?::[a-fA-F\\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,3}|:)|(?:[a-fA-F\\d]{1,4}:){3}(?:(?::[a-fA-F\\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,4}|:)|(?:[a-fA-F\\d]{1,4}:){2}(?:(?::[a-fA-F\\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,5}|:)|(?:[a-fA-F\\d]{1,4}:){1}(?:(?::[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?\\]?`  .replace(/\s*\/\/.*$/gm, "")  .replace(/\n/g, "")  .trim();const IP_V6_REGEX_OBJECT = new RegExp(`^${IP_V6_REGEX}$`);/* * Parses a Natural number (i.e., non-negative integer) with either the *    <min>*<max>DIGIT ( non-digit *OCTET ) * or *    <min>*<max>DIGIT * grammar (RFC6265 S5.1.1). * * The "trailingOK" boolean controls if the grammar accepts a * "( non-digit *OCTET )" trailer. */function parseDigits(token, minDigits, maxDigits, trailingOK) {  let count = 0;  while (count < token.length) {    const c = token.charCodeAt(count);    // "non-digit = %x00-2F / %x3A-FF"    if (c <= 0x2f || c >= 0x3a) {      break;    }    count++;  }  // constrain to a minimum and maximum number of digits.  if (count < minDigits || count > maxDigits) {    return null;  }  if (!trailingOK && count != token.length) {    return null;  }  return parseInt(token.substr(0, count), 10);}function parseTime(token) {  const parts = token.split(":");  const result = [0, 0, 0];  /* RF6256 S5.1.1:   *      time            = hms-time ( non-digit *OCTET )   *      hms-time        = time-field ":" time-field ":" time-field   *      time-field      = 1*2DIGIT   */  if (parts.length !== 3) {    return null;  }  for (let i = 0; i < 3; i++) {    // "time-field" must be strictly "1*2DIGIT", HOWEVER, "hms-time" can be    // followed by "( non-digit *OCTET )" so therefore the last time-field can    // have a trailer    const trailingOK = i == 2;    const num = parseDigits(parts[i], 1, 2, trailingOK);    if (num === null) {      return null;    }    result[i] = num;  }  return result;}function parseMonth(token) {  token = String(token)    .substr(0, 3)    .toLowerCase();  const num = MONTH_TO_NUM[token];  return num >= 0 ? num : null;}/* * RFC6265 S5.1.1 date parser (see RFC for full grammar) */function parseDate(str) {  if (!str) {    return;  }  /* RFC6265 S5.1.1:   * 2. Process each date-token sequentially in the order the date-tokens   * appear in the cookie-date   */  const tokens = str.split(DATE_DELIM);  if (!tokens) {    return;  }  let hour = null;  let minute = null;  let second = null;  let dayOfMonth = null;  let month = null;  let year = null;  for (let i = 0; i < tokens.length; i++) {    const token = tokens[i].trim();    if (!token.length) {      continue;    }    let result;    /* 2.1. If the found-time flag is not set and the token matches the time     * production, set the found-time flag and set the hour- value,     * minute-value, and second-value to the numbers denoted by the digits in     * the date-token, respectively.  Skip the remaining sub-steps and continue     * to the next date-token.     */    if (second === null) {      result = parseTime(token);      if (result) {        hour = result[0];        minute = result[1];        second = result[2];        continue;      }    }    /* 2.2. If the found-day-of-month flag is not set and the date-token matches     * the day-of-month production, set the found-day-of- month flag and set     * the day-of-month-value to the number denoted by the date-token.  Skip     * the remaining sub-steps and continue to the next date-token.     */    if (dayOfMonth === null) {      // "day-of-month = 1*2DIGIT ( non-digit *OCTET )"      result = parseDigits(token, 1, 2, true);      if (result !== null) {        dayOfMonth = result;        continue;      }    }    /* 2.3. If the found-month flag is not set and the date-token matches the     * month production, set the found-month flag and set the month-value to     * the month denoted by the date-token.  Skip the remaining sub-steps and     * continue to the next date-token.     */    if (month === null) {      result = parseMonth(token);      if (result !== null) {        month = result;        continue;      }    }    /* 2.4. If the found-year flag is not set and the date-token matches the     * year production, set the found-year flag and set the year-value to the     * number denoted by the date-token.  Skip the remaining sub-steps and     * continue to the next date-token.     */    if (year === null) {      // "year = 2*4DIGIT ( non-digit *OCTET )"      result = parseDigits(token, 2, 4, true);      if (result !== null) {        year = result;        /* From S5.1.1:         * 3.  If the year-value is greater than or equal to 70 and less         * than or equal to 99, increment the year-value by 1900.         * 4.  If the year-value is greater than or equal to 0 and less         * than or equal to 69, increment the year-value by 2000.         */        if (year >= 70 && year <= 99) {          year += 1900;        } else if (year >= 0 && year <= 69) {          year += 2000;        }      }    }  }  /* RFC 6265 S5.1.1   * "5. Abort these steps and fail to parse the cookie-date if:   *     *  at least one of the found-day-of-month, found-month, found-   *        year, or found-time flags is not set,   *     *  the day-of-month-value is less than 1 or greater than 31,   *     *  the year-value is less than 1601,   *     *  the hour-value is greater than 23,   *     *  the minute-value is greater than 59, or   *     *  the second-value is greater than 59.   *     (Note that leap seconds cannot be represented in this syntax.)"   *   * So, in order as above:   */  if (    dayOfMonth === null ||    month === null ||    year === null ||    second === null ||    dayOfMonth < 1 ||    dayOfMonth > 31 ||    year < 1601 ||    hour > 23 ||    minute > 59 ||    second > 59  ) {    return;  }  return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second));}function formatDate(date) {  validators.validate(validators.isDate(date), date);  return date.toUTCString();}// S5.1.2 Canonicalized Host Namesfunction canonicalDomain(str) {  if (str == null) {    return null;  }  str = str.trim().replace(/^\./, ""); // S4.1.2.3 & S5.2.3: ignore leading .  if (IP_V6_REGEX_OBJECT.test(str)) {    str = str.replace("[", "").replace("]", "");  }  // convert to IDN if any non-ASCII characters  if (punycode && /[^\u0001-\u007f]/.test(str)) {    str = punycode.toASCII(str);  }  return str.toLowerCase();}// S5.1.3 Domain Matchingfunction domainMatch(str, domStr, canonicalize) {  if (str == null || domStr == null) {    return null;  }  if (canonicalize !== false) {    str = canonicalDomain(str);    domStr = canonicalDomain(domStr);  }  /*   * S5.1.3:   * "A string domain-matches a given domain string if at least one of the   * following conditions hold:"   *   * " o The domain string and the string are identical. (Note that both the   * domain string and the string will have been canonicalized to lower case at   * this point)"   */  if (str == domStr) {    return true;  }  /* " o All of the following [three] conditions hold:" */  /* "* The domain string is a suffix of the string" */  const idx = str.lastIndexOf(domStr);  if (idx <= 0) {    return false; // it's a non-match (-1) or prefix (0)  }  // next, check it's a proper suffix  // e.g., "a.b.c".indexOf("b.c") === 2  // 5 === 3+2  if (str.length !== domStr.length + idx) {    return false; // it's not a suffix  }  /* "  * The last character of the string that is not included in the   * domain string is a %x2E (".") character." */  if (str.substr(idx - 1, 1) !== ".") {    return false; // doesn't align on "."  }  /* "  * The string is a host name (i.e., not an IP address)." */  if (IP_REGEX_LOWERCASE.test(str)) {    return false; // it's an IP address  }  return true;}// RFC6265 S5.1.4 Paths and Path-Match/* * "The user agent MUST use an algorithm equivalent to the following algorithm * to compute the default-path of a cookie:" * * Assumption: the path (and not query part or absolute uri) is passed in. */function defaultPath(path) {  // "2. If the uri-path is empty or if the first character of the uri-path is not  // a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.  if (!path || path.substr(0, 1) !== "/") {    return "/";  }  // "3. If the uri-path contains no more than one %x2F ("/") character, output  // %x2F ("/") and skip the remaining step."  if (path === "/") {    return path;  }  const rightSlash = path.lastIndexOf("/");  if (rightSlash === 0) {    return "/";  }  // "4. Output the characters of the uri-path from the first character up to,  // but not including, the right-most %x2F ("/")."  return path.slice(0, rightSlash);}function trimTerminator(str) {  if (validators.isEmptyString(str)) return str;  for (let t = 0; t < TERMINATORS.length; t++) {    const terminatorIdx = str.indexOf(TERMINATORS[t]);    if (terminatorIdx !== -1) {      str = str.substr(0, terminatorIdx);    }  }  return str;}function parseCookiePair(cookiePair, looseMode) {  cookiePair = trimTerminator(cookiePair);  validators.validate(validators.isString(cookiePair), cookiePair);  let firstEq = cookiePair.indexOf("=");  if (looseMode) {    if (firstEq === 0) {      // '=' is immediately at start      cookiePair = cookiePair.substr(1);      firstEq = cookiePair.indexOf("="); // might still need to split on '='    }  } else {    // non-loose mode    if (firstEq <= 0) {      // no '=' or is at start      return; // needs to have non-empty "cookie-name"    }  }  let cookieName, cookieValue;  if (firstEq <= 0) {    cookieName = "";    cookieValue = cookiePair.trim();  } else {    cookieName = cookiePair.substr(0, firstEq).trim();    cookieValue = cookiePair.substr(firstEq + 1).trim();  }  if (CONTROL_CHARS.test(cookieName) || CONTROL_CHARS.test(cookieValue)) {    return;  }  const c = new Cookie();  c.key = cookieName;  c.value = cookieValue;  return c;}function parse(str, options) {  if (!options || typeof options !== "object") {    options = {};  }  if (validators.isEmptyString(str) || !validators.isString(str)) {    return null;  }  str = str.trim();  // We use a regex to parse the "name-value-pair" part of S5.2  const firstSemi = str.indexOf(";"); // S5.2 step 1  const cookiePair = firstSemi === -1 ? str : str.substr(0, firstSemi);  const c = parseCookiePair(cookiePair, !!options.loose);  if (!c) {    return;  }  if (firstSemi === -1) {    return c;  }  // S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string  // (including the %x3B (";") in question)." plus later on in the same section  // "discard the first ";" and trim".  const unparsed = str.slice(firstSemi + 1).trim();  // "If the unparsed-attributes string is empty, skip the rest of these  // steps."  if (unparsed.length === 0) {    return c;  }  /*   * S5.2 says that when looping over the items "[p]rocess the attribute-name   * and attribute-value according to the requirements in the following   * subsections" for every item.  Plus, for many of the individual attributes   * in S5.3 it says to use the "attribute-value of the last attribute in the   * cookie-attribute-list".  Therefore, in this implementation, we overwrite   * the previous value.   */  const cookie_avs = unparsed.split(";");  while (cookie_avs.length) {    const av = cookie_avs.shift().trim();    if (av.length === 0) {      // happens if ";;" appears      continue;    }    const av_sep = av.indexOf("=");    let av_key, av_value;    if (av_sep === -1) {      av_key = av;      av_value = null;    } else {      av_key = av.substr(0, av_sep);      av_value = av.substr(av_sep + 1);    }    av_key = av_key.trim().toLowerCase();    if (av_value) {      av_value = av_value.trim();    }    switch (av_key) {      case "expires": // S5.2.1        if (av_value) {          const exp = parseDate(av_value);          // "If the attribute-value failed to parse as a cookie date, ignore the          // cookie-av."          if (exp) {            // over and underflow not realistically a concern: V8's getTime() seems to            // store something larger than a 32-bit time_t (even with 32-bit node)            c.expires = exp;          }        }        break;      case "max-age": // S5.2.2        if (av_value) {          // "If the first character of the attribute-value is not a DIGIT or a "-"          // character ...[or]... If the remainder of attribute-value contains a          // non-DIGIT character, ignore the cookie-av."          if (/^-?[0-9]+$/.test(av_value)) {            const delta = parseInt(av_value, 10);            // "If delta-seconds is less than or equal to zero (0), let expiry-time            // be the earliest representable date and time."            c.setMaxAge(delta);          }        }        break;      case "domain": // S5.2.3        // "If the attribute-value is empty, the behavior is undefined.  However,        // the user agent SHOULD ignore the cookie-av entirely."        if (av_value) {          // S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E          // (".") character."          const domain = av_value.trim().replace(/^\./, "");          if (domain) {            // "Convert the cookie-domain to lower case."            c.domain = domain.toLowerCase();          }        }        break;      case "path": // S5.2.4        /*         * "If the attribute-value is empty or if the first character of the         * attribute-value is not %x2F ("/"):         *   Let cookie-path be the default-path.         * Otherwise:         *   Let cookie-path be the attribute-value."         *         * We'll represent the default-path as null since it depends on the         * context of the parsing.         */        c.path = av_value && av_value[0] === "/" ? av_value : null;        break;      case "secure": // S5.2.5        /*         * "If the attribute-name case-insensitively matches the string "Secure",         * the user agent MUST append an attribute to the cookie-attribute-list         * with an attribute-name of Secure and an empty attribute-value."         */        c.secure = true;        break;      case "httponly": // S5.2.6 -- effectively the same as 'secure'        c.httpOnly = true;        break;      case "samesite": // RFC6265bis-02 S5.3.7        const enforcement = av_value ? av_value.toLowerCase() : "";        switch (enforcement) {          case "strict":            c.sameSite = "strict";            break;          case "lax":            c.sameSite = "lax";            break;          case "none":            c.sameSite = "none";            break;          default:            c.sameSite = undefined;            break;        }        break;      default:        c.extensions = c.extensions || [];        c.extensions.push(av);        break;    }  }  return c;}/** *  If the cookie-name begins with a case-sensitive match for the *  string "__Secure-", abort these steps and ignore the cookie *  entirely unless the cookie's secure-only-flag is true. * @param cookie * @returns boolean */function isSecurePrefixConditionMet(cookie) {  validators.validate(validators.isObject(cookie), cookie);  return !cookie.key.startsWith("__Secure-") || cookie.secure;}/** *  If the cookie-name begins with a case-sensitive match for the *  string "__Host-", abort these steps and ignore the cookie *  entirely unless the cookie meets all the following criteria: *    1.  The cookie's secure-only-flag is true. *    2.  The cookie's host-only-flag is true. *    3.  The cookie-attribute-list contains an attribute with an *        attribute-name of "Path", and the cookie's path is "/". * @param cookie * @returns boolean */function isHostPrefixConditionMet(cookie) {  validators.validate(validators.isObject(cookie));  return (    !cookie.key.startsWith("__Host-") ||    (cookie.secure &&      cookie.hostOnly &&      cookie.path != null &&      cookie.path === "/")  );}// avoid the V8 deoptimization monster!function jsonParse(str) {  let obj;  try {    obj = JSON.parse(str);  } catch (e) {    return e;  }  return obj;}function fromJSON(str) {  if (!str || validators.isEmptyString(str)) {    return null;  }  let obj;  if (typeof str === "string") {    obj = jsonParse(str);    if (obj instanceof Error) {      return null;    }  } else {    // assume it's an Object    obj = str;  }  const c = new Cookie();  for (let i = 0; i < Cookie.serializableProperties.length; i++) {    const prop = Cookie.serializableProperties[i];    if (obj[prop] === undefined || obj[prop] === cookieDefaults[prop]) {      continue; // leave as prototype default    }    if (prop === "expires" || prop === "creation" || prop === "lastAccessed") {      if (obj[prop] === null) {        c[prop] = null;      } else {        c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]);      }    } else {      c[prop] = obj[prop];    }  }  return c;}/* Section 5.4 part 2: * "*  Cookies with longer paths are listed before cookies with *     shorter paths. * *  *  Among cookies that have equal-length path fields, cookies with *     earlier creation-times are listed before cookies with later *     creation-times." */function cookieCompare(a, b) {  validators.validate(validators.isObject(a), a);  validators.validate(validators.isObject(b), b);  let cmp = 0;  // descending for length: b CMP a  const aPathLen = a.path ? a.path.length : 0;  const bPathLen = b.path ? b.path.length : 0;  cmp = bPathLen - aPathLen;  if (cmp !== 0) {    return cmp;  }  // ascending for time: a CMP b  const aTime = a.creation ? a.creation.getTime() : MAX_TIME;  const bTime = b.creation ? b.creation.getTime() : MAX_TIME;  cmp = aTime - bTime;  if (cmp !== 0) {    return cmp;  }  // break ties for the same millisecond (precision of JavaScript's clock)  cmp = a.creationIndex - b.creationIndex;  return cmp;}// Gives the permutation of all possible pathMatch()es of a given path. The// array is in longest-to-shortest order.  Handy for indexing.function permutePath(path) {  validators.validate(validators.isString(path));  if (path === "/") {    return ["/"];  }  const permutations = [path];  while (path.length > 1) {    const lindex = path.lastIndexOf("/");    if (lindex === 0) {      break;    }    path = path.substr(0, lindex);    permutations.push(path);  }  permutations.push("/");  return permutations;}function getCookieContext(url) {  if (url instanceof Object) {    return url;  }  // NOTE: decodeURI will throw on malformed URIs (see GH-32).  // Therefore, we will just skip decoding for such URIs.  try {    url = decodeURI(url);  } catch (err) {    // Silently swallow error  }  return urlParse(url);}const cookieDefaults = {  // the order in which the RFC has them:  key: "",  value: "",  expires: "Infinity",  maxAge: null,  domain: null,  path: null,  secure: false,  httpOnly: false,  extensions: null,  // set by the CookieJar:  hostOnly: null,  pathIsDefault: null,  creation: null,  lastAccessed: null,  sameSite: undefined};class Cookie {  constructor(options = {}) {    const customInspectSymbol = getCustomInspectSymbol();    if (customInspectSymbol) {      this[customInspectSymbol] = this.inspect;    }    Object.assign(this, cookieDefaults, options);    this.creation = this.creation || new Date();    // used to break creation ties in cookieCompare():    Object.defineProperty(this, "creationIndex", {      configurable: false,      enumerable: false, // important for assert.deepEqual checks      writable: true,      value: ++Cookie.cookiesCreated    });  }  inspect() {    const now = Date.now();    const hostOnly = this.hostOnly != null ? this.hostOnly : "?";    const createAge = this.creation      ? `${now - this.creation.getTime()}ms`      : "?";    const accessAge = this.lastAccessed      ? `${now - this.lastAccessed.getTime()}ms`      : "?";    return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"`;  }  toJSON() {    const obj = {};    for (const prop of Cookie.serializableProperties) {      if (this[prop] === cookieDefaults[prop]) {        continue; // leave as prototype default      }      if (        prop === "expires" ||        prop === "creation" ||        prop === "lastAccessed"      ) {        if (this[prop] === null) {          obj[prop] = null;        } else {          obj[prop] =            this[prop] == "Infinity" // intentionally not ===              ? "Infinity"              : this[prop].toISOString();        }      } else if (prop === "maxAge") {        if (this[prop] !== null) {          // again, intentionally not ===          obj[prop] =            this[prop] == Infinity || this[prop] == -Infinity              ? this[prop].toString()              : this[prop];        }      } else {        if (this[prop] !== cookieDefaults[prop]) {          obj[prop] = this[prop];        }      }    }    return obj;  }  clone() {    return fromJSON(this.toJSON());  }  validate() {    if (!COOKIE_OCTETS.test(this.value)) {      return false;    }    if (      this.expires != Infinity &&      !(this.expires instanceof Date) &&      !parseDate(this.expires)    ) {      return false;    }    if (this.maxAge != null && this.maxAge <= 0) {      return false; // "Max-Age=" non-zero-digit *DIGIT    }    if (this.path != null && !PATH_VALUE.test(this.path)) {      return false;    }    const cdomain = this.cdomain();    if (cdomain) {      if (cdomain.match(/\.$/)) {        return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this      }      const suffix = pubsuffix.getPublicSuffix(cdomain);      if (suffix == null) {        // it's a public suffix        return false;      }    }    return true;  }  setExpires(exp) {    if (exp instanceof Date) {      this.expires = exp;    } else {      this.expires = parseDate(exp) || "Infinity";    }  }  setMaxAge(age) {    if (age === Infinity || age === -Infinity) {      this.maxAge = age.toString(); // so JSON.stringify() works    } else {      this.maxAge = age;    }  }  cookieString() {    let val = this.value;    if (val == null) {      val = "";    }    if (this.key === "") {      return val;    }    return `${this.key}=${val}`;  }  // gives Set-Cookie header format  toString() {    let str = this.cookieString();    if (this.expires != Infinity) {      if (this.expires instanceof Date) {        str += `; Expires=${formatDate(this.expires)}`;      } else {        str += `; Expires=${this.expires}`;      }    }    if (this.maxAge != null && this.maxAge != Infinity) {      str += `; Max-Age=${this.maxAge}`;    }    if (this.domain && !this.hostOnly) {      str += `; Domain=${this.domain}`;    }    if (this.path) {      str += `; Path=${this.path}`;    }    if (this.secure) {      str += "; Secure";    }    if (this.httpOnly) {      str += "; HttpOnly";    }    if (this.sameSite && this.sameSite !== "none") {      const ssCanon = Cookie.sameSiteCanonical[this.sameSite.toLowerCase()];      str += `; SameSite=${ssCanon ? ssCanon : this.sameSite}`;    }    if (this.extensions) {      this.extensions.forEach(ext => {        str += `; ${ext}`;      });    }    return str;  }  // TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie()  // elsewhere)  // S5.3 says to give the "latest representable date" for which we use Infinity  // For "expired" we use 0  TTL(now) {    /* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires     * attribute, the Max-Age attribute has precedence and controls the     * expiration date of the cookie.     * (Concurs with S5.3 step 3)     */    if (this.maxAge != null) {      return this.maxAge <= 0 ? 0 : this.maxAge * 1000;    }    let expires = this.expires;    if (expires != Infinity) {      if (!(expires instanceof Date)) {        expires = parseDate(expires) || Infinity;      }      if (expires == Infinity) {        return Infinity;      }      return expires.getTime() - (now || Date.now());    }    return Infinity;  }  // expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()  // elsewhere)  expiryTime(now) {    if (this.maxAge != null) {      const relativeTo = now || this.creation || new Date();      const age = this.maxAge <= 0 ? -Infinity : this.maxAge * 1000;      return relativeTo.getTime() + age;    }    if (this.expires == Infinity) {      return Infinity;    }    return this.expires.getTime();  }  // expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()  // elsewhere), except it returns a Date  expiryDate(now) {    const millisec = this.expiryTime(now);    if (millisec == Infinity) {      return new Date(MAX_TIME);    } else if (millisec == -Infinity) {      return new Date(MIN_TIME);    } else {      return new Date(millisec);    }  }  // This replaces the "persistent-flag" parts of S5.3 step 3  isPersistent() {    return this.maxAge != null || this.expires != Infinity;  }  // Mostly S5.1.2 and S5.2.3:  canonicalizedDomain() {    if (this.domain == null) {      return null;    }    return canonicalDomain(this.domain);  }  cdomain() {    return this.canonicalizedDomain();  }}Cookie.cookiesCreated = 0;Cookie.parse = parse;Cookie.fromJSON = fromJSON;Cookie.serializableProperties = Object.keys(cookieDefaults);Cookie.sameSiteLevel = {  strict: 3,  lax: 2,  none: 1};Cookie.sameSiteCanonical = {  strict: "Strict",  lax: "Lax"};function getNormalizedPrefixSecurity(prefixSecurity) {  if (prefixSecurity != null) {    const normalizedPrefixSecurity = prefixSecurity.toLowerCase();    /* The three supported options */    switch (normalizedPrefixSecurity) {      case PrefixSecurityEnum.STRICT:      case PrefixSecurityEnum.SILENT:      case PrefixSecurityEnum.DISABLED:        return normalizedPrefixSecurity;    }  }  /* Default is SILENT */  return PrefixSecurityEnum.SILENT;}class CookieJar {  constructor(store, options = { rejectPublicSuffixes: true }) {    if (typeof options === "boolean") {      options = { rejectPublicSuffixes: options };    }    validators.validate(validators.isObject(options), options);    this.rejectPublicSuffixes = options.rejectPublicSuffixes;    this.enableLooseMode = !!options.looseMode;    this.allowSpecialUseDomain =      typeof options.allowSpecialUseDomain === "boolean"        ? options.allowSpecialUseDomain        : true;    this.store = store || new MemoryCookieStore();    this.prefixSecurity = getNormalizedPrefixSecurity(options.prefixSecurity);    this._cloneSync = syncWrap("clone");    this._importCookiesSync = syncWrap("_importCookies");    this.getCookiesSync = syncWrap("getCookies");    this.getCookieStringSync = syncWrap("getCookieString");    this.getSetCookieStringsSync = syncWrap("getSetCookieStrings");    this.removeAllCookiesSync = syncWrap("removeAllCookies");    this.setCookieSync = syncWrap("setCookie");    this.serializeSync = syncWrap("serialize");  }  setCookie(cookie, url, options, cb) {    validators.validate(validators.isUrlStringOrObject(url), cb, options);    let err;    if (validators.isFunction(url)) {      cb = url;      return cb(new Error("No URL was specified"));    }    const context = getCookieContext(url);    if (validators.isFunction(options)) {      cb = options;      options = {};    }    validators.validate(validators.isFunction(cb), cb);    if (      !validators.isNonEmptyString(cookie) &&      !validators.isObject(cookie) &&      cookie instanceof String &&      cookie.length == 0    ) {      return cb(null);    }    const host = canonicalDomain(context.hostname);    const loose = options.loose || this.enableLooseMode;    let sameSiteContext = null;    if (options.sameSiteContext) {      sameSiteContext = checkSameSiteContext(options.sameSiteContext);      if (!sameSiteContext) {        return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR));      }    }    // S5.3 step 1    if (typeof cookie === "string" || cookie instanceof String) {      cookie = Cookie.parse(cookie, { loose: loose });      if (!cookie) {        err = new Error("Cookie failed to parse");        return cb(options.ignoreError ? null : err);      }    } else if (!(cookie instanceof Cookie)) {      // If you're seeing this error, and are passing in a Cookie object,      // it *might* be a Cookie object from another loaded version of tough-cookie.      err = new Error(        "First argument to setCookie must be a Cookie object or string"      );      return cb(options.ignoreError ? null : err);    }    // S5.3 step 2    const now = options.now || new Date(); // will assign later to save effort in the face of errors    // S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie()    // S5.3 step 4: NOOP; domain is null by default    // S5.3 step 5: public suffixes    if (this.rejectPublicSuffixes && cookie.domain) {      const suffix = pubsuffix.getPublicSuffix(cookie.cdomain(), {        allowSpecialUseDomain: this.allowSpecialUseDomain,        ignoreError: options.ignoreError      });      if (suffix == null && !IP_V6_REGEX_OBJECT.test(cookie.domain)) {        // e.g. "com"        err = new Error("Cookie has domain set to a public suffix");        return cb(options.ignoreError ? null : err);      }    }    // S5.3 step 6:    if (cookie.domain) {      if (!domainMatch(host, cookie.cdomain(), false)) {        err = new Error(          `Cookie not in this host's domain. Cookie:${cookie.cdomain()} Request:${host}`        );        return cb(options.ignoreError ? null : err);      }      if (cookie.hostOnly == null) {        // don't reset if already set        cookie.hostOnly = false;      }    } else {      cookie.hostOnly = true;      cookie.domain = host;    }    //S5.2.4 If the attribute-value is empty or if the first character of the    //attribute-value is not %x2F ("/"):    //Let cookie-path be the default-path.    if (!cookie.path || cookie.path[0] !== "/") {      cookie.path = defaultPath(context.pathname);      cookie.pathIsDefault = true;    }    // S5.3 step 8: NOOP; secure attribute    // S5.3 step 9: NOOP; httpOnly attribute    // S5.3 step 10    if (options.http === false && cookie.httpOnly) {      err = new Error("Cookie is HttpOnly and this isn't an HTTP API");      return cb(options.ignoreError ? null : err);    }    // 6252bis-02 S5.4 Step 13 & 14:    if (      cookie.sameSite !== "none" &&      cookie.sameSite !== undefined &&      sameSiteContext    ) {      // "If the cookie's "same-site-flag" is not "None", and the cookie      //  is being set from a context whose "site for cookies" is not an      //  exact match for request-uri's host's registered domain, then      //  abort these steps and ignore the newly created cookie entirely."      if (sameSiteContext === "none") {        err = new Error(          "Cookie is SameSite but this is a cross-origin request"        );        return cb(options.ignoreError ? null : err);      }    }    /* 6265bis-02 S5.4 Steps 15 & 16 */    const ignoreErrorForPrefixSecurity =      this.prefixSecurity === PrefixSecurityEnum.SILENT;    const prefixSecurityDisabled =      this.prefixSecurity === PrefixSecurityEnum.DISABLED;    /* If prefix checking is not disabled ...*/    if (!prefixSecurityDisabled) {      let errorFound = false;      let errorMsg;      /* Check secure prefix condition */      if (!isSecurePrefixConditionMet(cookie)) {        errorFound = true;        errorMsg = "Cookie has __Secure prefix but Secure attribute is not set";      } else if (!isHostPrefixConditionMet(cookie)) {        /* Check host prefix condition */        errorFound = true;        errorMsg =          "Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'";      }      if (errorFound) {        return cb(          options.ignoreError || ignoreErrorForPrefixSecurity            ? null            : new Error(errorMsg)        );      }    }    const store = this.store;    if (!store.updateCookie) {      store.updateCookie = function(oldCookie, newCookie, cb) {        this.putCookie(newCookie, cb);      };    }    function withCookie(err, oldCookie) {      if (err) {        return cb(err);      }      const next = function(err) {        if (err) {          return cb(err);        } else {          cb(null, cookie);        }      };      if (oldCookie) {        // S5.3 step 11 - "If the cookie store contains a cookie with the same name,        // domain, and path as the newly created cookie:"        if (options.http === false && oldCookie.httpOnly) {          // step 11.2          err = new Error("old Cookie is HttpOnly and this isn't an HTTP API");          return cb(options.ignoreError ? null : err);        }        cookie.creation = oldCookie.creation; // step 11.3        cookie.creationIndex = oldCookie.creationIndex; // preserve tie-breaker        cookie.lastAccessed = now;        // Step 11.4 (delete cookie) is implied by just setting the new one:        store.updateCookie(oldCookie, cookie, next); // step 12      } else {        cookie.creation = cookie.lastAccessed = now;        store.putCookie(cookie, next); // step 12      }    }    store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);  }  // RFC6365 S5.4  getCookies(url, options, cb) {    validators.validate(validators.isUrlStringOrObject(url), cb, url);    const context = getCookieContext(url);    if (validators.isFunction(options)) {      cb = options;      options = {};    }    validators.validate(validators.isObject(options), cb, options);    validators.validate(validators.isFunction(cb), cb);    const host = canonicalDomain(context.hostname);    const path = context.pathname || "/";    let secure = options.secure;    if (      secure == null &&      context.protocol &&      (context.protocol == "https:" || context.protocol == "wss:")    ) {      secure = true;    }    let sameSiteLevel = 0;    if (options.sameSiteContext) {      const sameSiteContext = checkSameSiteContext(options.sameSiteContext);      sameSiteLevel = Cookie.sameSiteLevel[sameSiteContext];      if (!sameSiteLevel) {        return cb(new Error(SAME_SITE_CONTEXT_VAL_ERR));      }    }    let http = options.http;    if (http == null) {      http = true;    }    const now = options.now || Date.now();    const expireCheck = options.expire !== false;    const allPaths = !!options.allPaths;    const store = this.store;    function matchingCookie(c) {      // "Either:      //   The cookie's host-only-flag is true and the canonicalized      //   request-host is identical to the cookie's domain.      // Or:      //   The cookie's host-only-flag is false and the canonicalized      //   request-host domain-matches the cookie's domain."      if (c.hostOnly) {        if (c.domain != host) {          return false;        }      } else {        if (!domainMatch(host, c.domain, false)) {          return false;        }      }      // "The request-uri's path path-matches the cookie's path."      if (!allPaths && !pathMatch(path, c.path)) {        return false;      }      // "If the cookie's secure-only-flag is true, then the request-uri's      // scheme must denote a "secure" protocol"      if (c.secure && !secure) {        return false;      }      // "If the cookie's http-only-flag is true, then exclude the cookie if the      // cookie-string is being generated for a "non-HTTP" API"      if (c.httpOnly && !http) {        return false;      }      // RFC6265bis-02 S5.3.7      if (sameSiteLevel) {        const cookieLevel = Cookie.sameSiteLevel[c.sameSite || "none"];        if (cookieLevel > sameSiteLevel) {          // only allow cookies at or below the request level          return false;        }      }      // deferred from S5.3      // non-RFC: allow retention of expired cookies by choice      if (expireCheck && c.expiryTime() <= now) {        store.removeCookie(c.domain, c.path, c.key, () => {}); // result ignored        return false;      }      return true;    }    store.findCookies(      host,      allPaths ? null : path,      this.allowSpecialUseDomain,      (err, cookies) => {        if (err) {          return cb(err);        }        cookies = cookies.filter(matchingCookie);        // sorting of S5.4 part 2        if (options.sort !== false) {          cookies = cookies.sort(cookieCompare);        }        // S5.4 part 3        const now = new Date();        for (const cookie of cookies) {          cookie.lastAccessed = now;        }        // TODO persist lastAccessed        cb(null, cookies);      }    );  }  getCookieString(...args) {    const cb = args.pop();    validators.validate(validators.isFunction(cb), cb);    const next = function(err, cookies) {      if (err) {        cb(err);      } else {        cb(          null,          cookies            .sort(cookieCompare)            .map(c => c.cookieString())            .join("; ")        );      }    };    args.push(next);    this.getCookies.apply(this, args);  }  getSetCookieStrings(...args) {    const cb = args.pop();    validators.validate(validators.isFunction(cb), cb);    const next = function(err, cookies) {      if (err) {        cb(err);      } else {        cb(          null,          cookies.map(c => {            return c.toString();          })        );      }    };    args.push(next);    this.getCookies.apply(this, args);  }  serialize(cb) {    validators.validate(validators.isFunction(cb), cb);    let type = this.store.constructor.name;    if (validators.isObject(type)) {      type = null;    }    // update README.md "Serialization Format" if you change this, please!    const serialized = {      // The version of tough-cookie that serialized this jar. Generally a good      // practice since future versions can make data import decisions based on      // known past behavior. When/if this matters, use `semver`.      version: `tough-cookie@${VERSION}`,      // add the store type, to make humans happy:      storeType: type,      // CookieJar configuration:      rejectPublicSuffixes: !!this.rejectPublicSuffixes,      enableLooseMode: !!this.enableLooseMode,      allowSpecialUseDomain: !!this.allowSpecialUseDomain,      prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity),      // this gets filled from getAllCookies:      cookies: []    };    if (      !(        this.store.getAllCookies &&        typeof this.store.getAllCookies === "function"      )    ) {      return cb(        new Error(          "store does not support getAllCookies and cannot be serialized"        )      );    }    this.store.getAllCookies((err, cookies) => {      if (err) {        return cb(err);      }      serialized.cookies = cookies.map(cookie => {        // convert to serialized 'raw' cookies        cookie = cookie instanceof Cookie ? cookie.toJSON() : cookie;        // Remove the index so new ones get assigned during deserialization        delete cookie.creationIndex;        return cookie;      });      return cb(null, serialized);    });  }  toJSON() {    return this.serializeSync();  }  // use the class method CookieJar.deserialize instead of calling this directly  _importCookies(serialized, cb) {    let cookies = serialized.cookies;    if (!cookies || !Array.isArray(cookies)) {      return cb(new Error("serialized jar has no cookies array"));    }    cookies = cookies.slice(); // do not modify the original    const putNext = err => {      if (err) {        return cb(err);      }      if (!cookies.length) {        return cb(err, this);      }      let cookie;      try {        cookie = fromJSON(cookies.shift());      } catch (e) {        return cb(e);      }      if (cookie === null) {        return putNext(null); // skip this cookie      }      this.store.putCookie(cookie, putNext);    };    putNext();  }  clone(newStore, cb) {    if (arguments.length === 1) {      cb = newStore;      newStore = null;    }    this.serialize((err, serialized) => {      if (err) {        return cb(err);      }      CookieJar.deserialize(serialized, newStore, cb);    });  }  cloneSync(newStore) {    if (arguments.length === 0) {      return this._cloneSync();    }    if (!newStore.synchronous) {      throw new Error(        "CookieJar clone destination store is not synchronous; use async API instead."      );    }    return this._cloneSync(newStore);  }  removeAllCookies(cb) {    validators.validate(validators.isFunction(cb), cb);    const store = this.store;    // Check that the store implements its own removeAllCookies(). The default    // implementation in Store will immediately call the callback with a "not    // implemented" Error.    if (      typeof store.removeAllCookies === "function" &&      store.removeAllCookies !== Store.prototype.removeAllCookies    ) {      return store.removeAllCookies(cb);    }    store.getAllCookies((err, cookies) => {      if (err) {        return cb(err);      }      if (cookies.length === 0) {        return cb(null);      }      let completedCount = 0;      const removeErrors = [];      function removeCookieCb(removeErr) {        if (removeErr) {          removeErrors.push(removeErr);        }        completedCount++;        if (completedCount === cookies.length) {          return cb(removeErrors.length ? removeErrors[0] : null);        }      }      cookies.forEach(cookie => {        store.removeCookie(          cookie.domain,          cookie.path,          cookie.key,          removeCookieCb        );      });    });  }  static deserialize(strOrObj, store, cb) {    if (arguments.length !== 3) {      // store is optional      cb = store;      store = null;    }    validators.validate(validators.isFunction(cb), cb);    let serialized;    if (typeof strOrObj === "string") {      serialized = jsonParse(strOrObj);      if (serialized instanceof Error) {        return cb(serialized);      }    } else {      serialized = strOrObj;    }    const jar = new CookieJar(store, {      rejectPublicSuffixes: serialized.rejectPublicSuffixes,      looseMode: serialized.enableLooseMode,      allowSpecialUseDomain: serialized.allowSpecialUseDomain,      prefixSecurity: serialized.prefixSecurity    });    jar._importCookies(serialized, err => {      if (err) {        return cb(err);      }      cb(null, jar);    });  }  static deserializeSync(strOrObj, store) {    const serialized =      typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj;    const jar = new CookieJar(store, {      rejectPublicSuffixes: serialized.rejectPublicSuffixes,      looseMode: serialized.enableLooseMode    });    // catch this mistake early:    if (!jar.store.synchronous) {      throw new Error(        "CookieJar store is not synchronous; use async API instead."      );    }    jar._importCookiesSync(serialized);    return jar;  }}CookieJar.fromJSON = CookieJar.deserializeSync;[  "_importCookies",  "clone",  "getCookies",  "getCookieString",  "getSetCookieStrings",  "removeAllCookies",  "serialize",  "setCookie"].forEach(name => {  CookieJar.prototype[name] = fromCallback(CookieJar.prototype[name]);});CookieJar.deserialize = fromCallback(CookieJar.deserialize);// Use a closure to provide a true imperative API for synchronous stores.function syncWrap(method) {  return function(...args) {    if (!this.store.synchronous) {      throw new Error(        "CookieJar store is not synchronous; use async API instead."      );    }    let syncErr, syncResult;    this[method](...args, (err, result) => {      syncErr = err;      syncResult = result;    });    if (syncErr) {      throw syncErr;    }    return syncResult;  };}exports.version = VERSION;exports.CookieJar = CookieJar;exports.Cookie = Cookie;exports.Store = Store;exports.MemoryCookieStore = MemoryCookieStore;exports.parseDate = parseDate;exports.formatDate = formatDate;exports.parse = parse;exports.fromJSON = fromJSON;exports.domainMatch = domainMatch;exports.defaultPath = defaultPath;exports.pathMatch = pathMatch;exports.getPublicSuffix = pubsuffix.getPublicSuffix;exports.cookieCompare = cookieCompare;exports.permuteDomain = require("./permuteDomain").permuteDomain;exports.permutePath = permutePath;exports.canonicalDomain = canonicalDomain;exports.PrefixSecurityEnum = PrefixSecurityEnum;exports.ParameterError = validators.ParameterError;
 |