react / wstein / node_modules / jest-cli / node_modules / jsdom / node_modules / request / node_modules / tough-cookie / lib / cookie.js
81146 views/*!1* Copyright (c) 2015, Salesforce.com, Inc.2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions are met:6*7* 1. Redistributions of source code must retain the above copyright notice,8* this list of conditions and the following disclaimer.9*10* 2. Redistributions in binary form must reproduce the above copyright notice,11* this list of conditions and the following disclaimer in the documentation12* and/or other materials provided with the distribution.13*14* 3. Neither the name of Salesforce.com nor the names of its contributors may15* be used to endorse or promote products derived from this software without16* specific prior written permission.17*18* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"19* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE20* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE21* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE22* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR23* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF24* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS25* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN26* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)27* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE28* POSSIBILITY OF SUCH DAMAGE.29*/30'use strict';31var net = require('net');32var urlParse = require('url').parse;33var pubsuffix = require('./pubsuffix');34var Store = require('./store').Store;35var MemoryCookieStore = require('./memstore').MemoryCookieStore;36var pathMatch = require('./pathMatch').pathMatch;3738var punycode;39try {40punycode = require('punycode');41} catch(e) {42console.warn("cookie: can't load punycode; won't use punycode for domain normalization");43}4445var DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;4647// From RFC6265 S4.1.148// note that it excludes \x3B ";"49var COOKIE_OCTET = /[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/;50var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'$');5152// Double quotes are part of the value (see: S4.1.1).53// '\r', '\n' and '\0' should be treated as a terminator in the "relaxed" mode54// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60)55// '=' and ';' are attribute/values separators56// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L64)57var COOKIE_PAIR = /^([^=;]+)\s*=\s*(("?)[^\n\r\0]*\3)/;5859// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'60// Note ';' is \x3B61var PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;6263// Used for checking whether or not there is a trailing semi-colon64var TRAILING_SEMICOLON = /;+$/;6566var DAY_OF_MONTH = /^(\d{1,2})[^\d]*$/;67var TIME = /^(\d{1,2})[^\d]*:(\d{1,2})[^\d]*:(\d{1,2})[^\d]*$/;68var MONTH = /^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i;6970var MONTH_TO_NUM = {71jan:0, feb:1, mar:2, apr:3, may:4, jun:5,72jul:6, aug:7, sep:8, oct:9, nov:10, dec:1173};74var NUM_TO_MONTH = [75'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'76];77var NUM_TO_DAY = [78'Sun','Mon','Tue','Wed','Thu','Fri','Sat'79];8081var YEAR = /^(\d{2}|\d{4})$/; // 2 to 4 digits8283var MAX_TIME = 2147483647000; // 31-bit max84var MIN_TIME = 0; // 31-bit min8586var cookiesCreated = 0; // Number of cookies created in runtime878889// RFC6265 S5.1.1 date parser:90function parseDate(str) {91if (!str) {92return;93}9495/* RFC6265 S5.1.1:96* 2. Process each date-token sequentially in the order the date-tokens97* appear in the cookie-date98*/99var tokens = str.split(DATE_DELIM);100if (!tokens) {101return;102}103104var hour = null;105var minutes = null;106var seconds = null;107var day = null;108var month = null;109var year = null;110111for (var i=0; i<tokens.length; i++) {112var token = tokens[i].trim();113if (!token.length) {114continue;115}116117var result;118119/* 2.1. If the found-time flag is not set and the token matches the time120* production, set the found-time flag and set the hour- value,121* minute-value, and second-value to the numbers denoted by the digits in122* the date-token, respectively. Skip the remaining sub-steps and continue123* to the next date-token.124*/125if (seconds === null) {126result = TIME.exec(token);127if (result) {128hour = parseInt(result[1], 10);129minutes = parseInt(result[2], 10);130seconds = parseInt(result[3], 10);131/* RFC6265 S5.1.1.5:132* [fail if]133* * the hour-value is greater than 23,134* * the minute-value is greater than 59, or135* * the second-value is greater than 59.136*/137if(hour > 23 || minutes > 59 || seconds > 59) {138return;139}140141continue;142}143}144145/* 2.2. If the found-day-of-month flag is not set and the date-token matches146* the day-of-month production, set the found-day-of- month flag and set147* the day-of-month-value to the number denoted by the date-token. Skip148* the remaining sub-steps and continue to the next date-token.149*/150if (day === null) {151result = DAY_OF_MONTH.exec(token);152if (result) {153day = parseInt(result, 10);154/* RFC6265 S5.1.1.5:155* [fail if] the day-of-month-value is less than 1 or greater than 31156*/157if(day < 1 || day > 31) {158return;159}160continue;161}162}163164/* 2.3. If the found-month flag is not set and the date-token matches the165* month production, set the found-month flag and set the month-value to166* the month denoted by the date-token. Skip the remaining sub-steps and167* continue to the next date-token.168*/169if (month === null) {170result = MONTH.exec(token);171if (result) {172month = MONTH_TO_NUM[result[1].toLowerCase()];173continue;174}175}176177/* 2.4. If the found-year flag is not set and the date-token matches the year178* production, set the found-year flag and set the year-value to the number179* denoted by the date-token. Skip the remaining sub-steps and continue to180* the next date-token.181*/182if (year === null) {183result = YEAR.exec(token);184if (result) {185year = parseInt(result[0], 10);186/* From S5.1.1:187* 3. If the year-value is greater than or equal to 70 and less188* than or equal to 99, increment the year-value by 1900.189* 4. If the year-value is greater than or equal to 0 and less190* than or equal to 69, increment the year-value by 2000.191*/192if (70 <= year && year <= 99) {193year += 1900;194} else if (0 <= year && year <= 69) {195year += 2000;196}197198if (year < 1601) {199return; // 5. ... the year-value is less than 1601200}201}202}203}204205if (seconds === null || day === null || month === null || year === null) {206return; // 5. ... at least one of the found-day-of-month, found-month, found-207// year, or found-time flags is not set,208}209210return new Date(Date.UTC(year, month, day, hour, minutes, seconds));211}212213function formatDate(date) {214var d = date.getUTCDate(); d = d >= 10 ? d : '0'+d;215var h = date.getUTCHours(); h = h >= 10 ? h : '0'+h;216var m = date.getUTCMinutes(); m = m >= 10 ? m : '0'+m;217var s = date.getUTCSeconds(); s = s >= 10 ? s : '0'+s;218return NUM_TO_DAY[date.getUTCDay()] + ', ' +219d+' '+ NUM_TO_MONTH[date.getUTCMonth()] +' '+ date.getUTCFullYear() +' '+220h+':'+m+':'+s+' GMT';221}222223// S5.1.2 Canonicalized Host Names224function canonicalDomain(str) {225if (str == null) {226return null;227}228str = str.trim().replace(/^\./,''); // S4.1.2.3 & S5.2.3: ignore leading .229230// convert to IDN if any non-ASCII characters231if (punycode && /[^\u0001-\u007f]/.test(str)) {232str = punycode.toASCII(str);233}234235return str.toLowerCase();236}237238// S5.1.3 Domain Matching239function domainMatch(str, domStr, canonicalize) {240if (str == null || domStr == null) {241return null;242}243if (canonicalize !== false) {244str = canonicalDomain(str);245domStr = canonicalDomain(domStr);246}247248/*249* "The domain string and the string are identical. (Note that both the250* domain string and the string will have been canonicalized to lower case at251* this point)"252*/253if (str == domStr) {254return true;255}256257/* "All of the following [three] conditions hold:" (order adjusted from the RFC) */258259/* "* The string is a host name (i.e., not an IP address)." */260if (net.isIP(str)) {261return false;262}263264/* "* The domain string is a suffix of the string" */265var idx = str.indexOf(domStr);266if (idx <= 0) {267return false; // it's a non-match (-1) or prefix (0)268}269270// e.g "a.b.c".indexOf("b.c") === 2271// 5 === 3+2272if (str.length !== domStr.length + idx) { // it's not a suffix273return false;274}275276/* "* The last character of the string that is not included in the domain277* string is a %x2E (".") character." */278if (str.substr(idx-1,1) !== '.') {279return false;280}281282return true;283}284285286// RFC6265 S5.1.4 Paths and Path-Match287288/*289* "The user agent MUST use an algorithm equivalent to the following algorithm290* to compute the default-path of a cookie:"291*292* Assumption: the path (and not query part or absolute uri) is passed in.293*/294function defaultPath(path) {295// "2. If the uri-path is empty or if the first character of the uri-path is not296// a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.297if (!path || path.substr(0,1) !== "/") {298return "/";299}300301// "3. If the uri-path contains no more than one %x2F ("/") character, output302// %x2F ("/") and skip the remaining step."303if (path === "/") {304return path;305}306307var rightSlash = path.lastIndexOf("/");308if (rightSlash === 0) {309return "/";310}311312// "4. Output the characters of the uri-path from the first character up to,313// but not including, the right-most %x2F ("/")."314return path.slice(0, rightSlash);315}316317318function parse(str) {319str = str.trim();320321// S4.1.1 Trailing semi-colons are not part of the specification.322var semiColonCheck = TRAILING_SEMICOLON.exec(str);323if (semiColonCheck) {324str = str.slice(0, semiColonCheck.index);325}326327// We use a regex to parse the "name-value-pair" part of S5.2328var firstSemi = str.indexOf(';'); // S5.2 step 1329var result = COOKIE_PAIR.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));330331// Rx satisfies the "the name string is empty" and "lacks a %x3D ("=")"332// constraints as well as trimming any whitespace.333if (!result) {334return;335}336337var c = new Cookie();338c.key = result[1].trim();339c.value = result[2].trim();340341if (firstSemi === -1) {342return c;343}344345// S5.2.3 "unparsed-attributes consist of the remainder of the set-cookie-string346// (including the %x3B (";") in question)." plus later on in the same section347// "discard the first ";" and trim".348var unparsed = str.slice(firstSemi).replace(/^\s*;\s*/,'').trim();349350// "If the unparsed-attributes string is empty, skip the rest of these351// steps."352if (unparsed.length === 0) {353return c;354}355356/*357* S5.2 says that when looping over the items "[p]rocess the attribute-name358* and attribute-value according to the requirements in the following359* subsections" for every item. Plus, for many of the individual attributes360* in S5.3 it says to use the "attribute-value of the last attribute in the361* cookie-attribute-list". Therefore, in this implementation, we overwrite362* the previous value.363*/364var cookie_avs = unparsed.split(/\s*;\s*/);365while (cookie_avs.length) {366var av = cookie_avs.shift();367var av_sep = av.indexOf('=');368var av_key, av_value;369370if (av_sep === -1) {371av_key = av;372av_value = null;373} else {374av_key = av.substr(0,av_sep);375av_value = av.substr(av_sep+1);376}377378av_key = av_key.trim().toLowerCase();379380if (av_value) {381av_value = av_value.trim();382}383384switch(av_key) {385case 'expires': // S5.2.1386if (av_value) {387var exp = parseDate(av_value);388// "If the attribute-value failed to parse as a cookie date, ignore the389// cookie-av."390if (exp) {391// over and underflow not realistically a concern: V8's getTime() seems to392// store something larger than a 32-bit time_t (even with 32-bit node)393c.expires = exp;394}395}396break;397398case 'max-age': // S5.2.2399if (av_value) {400// "If the first character of the attribute-value is not a DIGIT or a "-"401// character ...[or]... If the remainder of attribute-value contains a402// non-DIGIT character, ignore the cookie-av."403if (/^-?[0-9]+$/.test(av_value)) {404var delta = parseInt(av_value, 10);405// "If delta-seconds is less than or equal to zero (0), let expiry-time406// be the earliest representable date and time."407c.setMaxAge(delta);408}409}410break;411412case 'domain': // S5.2.3413// "If the attribute-value is empty, the behavior is undefined. However,414// the user agent SHOULD ignore the cookie-av entirely."415if (av_value) {416// S5.2.3 "Let cookie-domain be the attribute-value without the leading %x2E417// (".") character."418var domain = av_value.trim().replace(/^\./, '');419if (domain) {420// "Convert the cookie-domain to lower case."421c.domain = domain.toLowerCase();422}423}424break;425426case 'path': // S5.2.4427/*428* "If the attribute-value is empty or if the first character of the429* attribute-value is not %x2F ("/"):430* Let cookie-path be the default-path.431* Otherwise:432* Let cookie-path be the attribute-value."433*434* We'll represent the default-path as null since it depends on the435* context of the parsing.436*/437c.path = av_value && av_value[0] === "/" ? av_value : null;438break;439440case 'secure': // S5.2.5441/*442* "If the attribute-name case-insensitively matches the string "Secure",443* the user agent MUST append an attribute to the cookie-attribute-list444* with an attribute-name of Secure and an empty attribute-value."445*/446c.secure = true;447break;448449case 'httponly': // S5.2.6 -- effectively the same as 'secure'450c.httpOnly = true;451break;452453default:454c.extensions = c.extensions || [];455c.extensions.push(av);456break;457}458}459460// ensure a default date for sorting:461c.creation = new Date();462//NOTE: add runtime index for the cookieCompare() to resolve the situation when Date's precision is not enough .463//Store initial UTC time as well, so we will be able to determine if we need to fallback to the Date object.464c._creationRuntimeIdx = ++cookiesCreated;465c._initialCreationTime = c.creation.getTime();466return c;467}468469function fromJSON(str) {470if (!str) {471return null;472}473474var obj;475try {476obj = JSON.parse(str);477} catch (e) {478return null;479}480481var c = new Cookie();482for (var i=0; i<numCookieProperties; i++) {483var prop = cookieProperties[i];484if (obj[prop] == null) {485continue;486}487if (prop === 'expires' ||488prop === 'creation' ||489prop === 'lastAccessed')490{491c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]);492} else {493c[prop] = obj[prop];494}495}496497498// ensure a default date for sorting:499c.creation = c.creation || new Date();500501return c;502}503504/* Section 5.4 part 2:505* "* Cookies with longer paths are listed before cookies with506* shorter paths.507*508* * Among cookies that have equal-length path fields, cookies with509* earlier creation-times are listed before cookies with later510* creation-times."511*/512513function cookieCompare(a,b) {514// descending for length: b CMP a515var deltaLen = (b.path ? b.path.length : 0) - (a.path ? a.path.length : 0);516if (deltaLen !== 0) {517return deltaLen;518}519520var aTime = a.creation ? a.creation.getTime() : MAX_TIME;521var bTime = b.creation ? b.creation.getTime() : MAX_TIME;522523// NOTE: if creation dates are equal and they were not modified from the outside,524// then use _creationRuntimeIdx for the comparison.525if(aTime === bTime && aTime === a._initialCreationTime && bTime === b._initialCreationTime) {526return a._creationRuntimeIdx - b._creationRuntimeIdx;527}528529// ascending for time: a CMP b530return aTime - bTime;531}532533// Gives the permutation of all possible pathMatch()es of a given path. The534// array is in longest-to-shortest order. Handy for indexing.535function permutePath(path) {536if (path === '/') {537return ['/'];538}539if (path.lastIndexOf('/') === path.length-1) {540path = path.substr(0,path.length-1);541}542var permutations = [path];543while (path.length > 1) {544var lindex = path.lastIndexOf('/');545if (lindex === 0) {546break;547}548path = path.substr(0,lindex);549permutations.push(path);550}551permutations.push('/');552return permutations;553}554555function getCookieContext(url) {556if (url instanceof Object) {557return url;558}559// NOTE: decodeURI will throw on malformed URIs (see GH-32).560// Therefore, we will just skip decoding for such URIs.561try {562url = decodeURI(url);563}564catch(err) {565// Silently swallow error566}567568return urlParse(url);569}570571function Cookie (opts) {572if (typeof opts !== "object") {573return;574}575Object.keys(opts).forEach(function (key) {576if (Cookie.prototype.hasOwnProperty(key)) {577this[key] = opts[key] || Cookie.prototype[key];578}579}.bind(this));580}581582Cookie.parse = parse;583Cookie.fromJSON = fromJSON;584585Cookie.prototype.key = "";586Cookie.prototype.value = "";587588// the order in which the RFC has them:589Cookie.prototype.expires = "Infinity"; // coerces to literal Infinity590Cookie.prototype.maxAge = null; // takes precedence over expires for TTL591Cookie.prototype.domain = null;592Cookie.prototype.path = null;593Cookie.prototype.secure = false;594Cookie.prototype.httpOnly = false;595Cookie.prototype.extensions = null;596597// set by the CookieJar:598Cookie.prototype.hostOnly = null; // boolean when set599Cookie.prototype.pathIsDefault = null; // boolean when set600Cookie.prototype.creation = null; // Date when set; defaulted by Cookie.parse601Cookie.prototype._initialCreationTime = null; // Used to determine if cookie.creation was modified602Cookie.prototype._creationRuntimeIdx = null; // Runtime index of the created cookie, used in cookieCompare()603Cookie.prototype.lastAccessed = null; // Date when set604605var cookieProperties = Object.freeze(Object.keys(Cookie.prototype).map(function(p) {606if (p instanceof Function) {607return;608}609return p;610}));611var numCookieProperties = cookieProperties.length;612613Cookie.prototype.inspect = function inspect() {614var now = Date.now();615return 'Cookie="'+this.toString() +616'; hostOnly='+(this.hostOnly != null ? this.hostOnly : '?') +617'; aAge='+(this.lastAccessed ? (now-this.lastAccessed.getTime())+'ms' : '?') +618'; cAge='+(this.creation ? (now-this.creation.getTime())+'ms' : '?') +619'"';620};621622Cookie.prototype.validate = function validate() {623if (!COOKIE_OCTETS.test(this.value)) {624return false;625}626if (this.expires != Infinity && !(this.expires instanceof Date) && !parseDate(this.expires)) {627return false;628}629if (this.maxAge != null && this.maxAge <= 0) {630return false; // "Max-Age=" non-zero-digit *DIGIT631}632if (this.path != null && !PATH_VALUE.test(this.path)) {633return false;634}635636var cdomain = this.cdomain();637if (cdomain) {638if (cdomain.match(/\.$/)) {639return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this640}641var suffix = pubsuffix.getPublicSuffix(cdomain);642if (suffix == null) { // it's a public suffix643return false;644}645}646return true;647};648649Cookie.prototype.setExpires = function setExpires(exp) {650if (exp instanceof Date) {651this.expires = exp;652} else {653this.expires = parseDate(exp) || "Infinity";654}655};656657Cookie.prototype.setMaxAge = function setMaxAge(age) {658if (age === Infinity || age === -Infinity) {659this.maxAge = age.toString(); // so JSON.stringify() works660} else {661this.maxAge = age;662}663};664665// gives Cookie header format666Cookie.prototype.cookieString = function cookieString() {667var val = this.value;668if (val == null) {669val = '';670}671return this.key+'='+val;672};673674// gives Set-Cookie header format675Cookie.prototype.toString = function toString() {676var str = this.cookieString();677678if (this.expires != Infinity) {679if (this.expires instanceof Date) {680str += '; Expires='+formatDate(this.expires);681} else {682str += '; Expires='+this.expires;683}684}685686if (this.maxAge != null && this.maxAge != Infinity) {687str += '; Max-Age='+this.maxAge;688}689690if (this.domain && !this.hostOnly) {691str += '; Domain='+this.domain;692}693if (this.path) {694str += '; Path='+this.path;695}696697if (this.secure) {698str += '; Secure';699}700if (this.httpOnly) {701str += '; HttpOnly';702}703if (this.extensions) {704this.extensions.forEach(function(ext) {705str += '; '+ext;706});707}708709return str;710};711712// TTL() partially replaces the "expiry-time" parts of S5.3 step 3 (setCookie()713// elsewhere)714// S5.3 says to give the "latest representable date" for which we use Infinity715// For "expired" we use 0716Cookie.prototype.TTL = function TTL(now) {717/* RFC6265 S4.1.2.2 If a cookie has both the Max-Age and the Expires718* attribute, the Max-Age attribute has precedence and controls the719* expiration date of the cookie.720* (Concurs with S5.3 step 3)721*/722if (this.maxAge != null) {723return this.maxAge<=0 ? 0 : this.maxAge*1000;724}725726var expires = this.expires;727if (expires != Infinity) {728if (!(expires instanceof Date)) {729expires = parseDate(expires) || Infinity;730}731732if (expires == Infinity) {733return Infinity;734}735736return expires.getTime() - (now || Date.now());737}738739return Infinity;740};741742// expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()743// elsewhere)744Cookie.prototype.expiryTime = function expiryTime(now) {745if (this.maxAge != null) {746var relativeTo = this.creation || now || new Date();747var age = (this.maxAge <= 0) ? -Infinity : this.maxAge*1000;748return relativeTo.getTime() + age;749}750751if (this.expires == Infinity) {752return Infinity;753}754return this.expires.getTime();755};756757// expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()758// elsewhere), except it returns a Date759Cookie.prototype.expiryDate = function expiryDate(now) {760var millisec = this.expiryTime(now);761if (millisec == Infinity) {762return new Date(MAX_TIME);763} else if (millisec == -Infinity) {764return new Date(MIN_TIME);765} else {766return new Date(millisec);767}768};769770// This replaces the "persistent-flag" parts of S5.3 step 3771Cookie.prototype.isPersistent = function isPersistent() {772return (this.maxAge != null || this.expires != Infinity);773};774775// Mostly S5.1.2 and S5.2.3:776Cookie.prototype.cdomain =777Cookie.prototype.canonicalizedDomain = function canonicalizedDomain() {778if (this.domain == null) {779return null;780}781return canonicalDomain(this.domain);782};783784785function CookieJar(store, rejectPublicSuffixes) {786if (rejectPublicSuffixes != null) {787this.rejectPublicSuffixes = rejectPublicSuffixes;788}789790if (!store) {791store = new MemoryCookieStore();792}793this.store = store;794}795CookieJar.prototype.store = null;796CookieJar.prototype.rejectPublicSuffixes = true;797var CAN_BE_SYNC = [];798799CAN_BE_SYNC.push('setCookie');800CookieJar.prototype.setCookie = function(cookie, url, options, cb) {801var err;802var context = getCookieContext(url);803if (options instanceof Function) {804cb = options;805options = {};806}807808var host = canonicalDomain(context.hostname);809810// S5.3 step 1811if (!(cookie instanceof Cookie)) {812cookie = Cookie.parse(cookie);813}814if (!cookie) {815err = new Error("Cookie failed to parse");816return cb(options.ignoreError ? null : err);817}818819// S5.3 step 2820var now = options.now || new Date(); // will assign later to save effort in the face of errors821822// S5.3 step 3: NOOP; persistent-flag and expiry-time is handled by getCookie()823824// S5.3 step 4: NOOP; domain is null by default825826// S5.3 step 5: public suffixes827if (this.rejectPublicSuffixes && cookie.domain) {828var suffix = pubsuffix.getPublicSuffix(cookie.cdomain());829if (suffix == null) { // e.g. "com"830err = new Error("Cookie has domain set to a public suffix");831return cb(options.ignoreError ? null : err);832}833}834835// S5.3 step 6:836if (cookie.domain) {837if (!domainMatch(host, cookie.cdomain(), false)) {838err = new Error("Cookie not in this host's domain. Cookie:"+cookie.cdomain()+" Request:"+host);839return cb(options.ignoreError ? null : err);840}841842if (cookie.hostOnly == null) { // don't reset if already set843cookie.hostOnly = false;844}845846} else {847cookie.hostOnly = true;848cookie.domain = host;849}850851//S5.2.4 If the attribute-value is empty or if the first character of the852//attribute-value is not %x2F ("/"):853//Let cookie-path be the default-path.854if (!cookie.path || cookie.path[0] !== '/') {855cookie.path = defaultPath(context.pathname);856cookie.pathIsDefault = true;857}858859// S5.3 step 8: NOOP; secure attribute860// S5.3 step 9: NOOP; httpOnly attribute861862// S5.3 step 10863if (options.http === false && cookie.httpOnly) {864err = new Error("Cookie is HttpOnly and this isn't an HTTP API");865return cb(options.ignoreError ? null : err);866}867868var store = this.store;869870if (!store.updateCookie) {871store.updateCookie = function(oldCookie, newCookie, cb) {872this.putCookie(newCookie, cb);873};874}875876function withCookie(err, oldCookie) {877if (err) {878return cb(err);879}880881var next = function(err) {882if (err) {883return cb(err);884} else {885cb(null, cookie);886}887};888889if (oldCookie) {890// S5.3 step 11 - "If the cookie store contains a cookie with the same name,891// domain, and path as the newly created cookie:"892if (options.http === false && oldCookie.httpOnly) { // step 11.2893err = new Error("old Cookie is HttpOnly and this isn't an HTTP API");894return cb(options.ignoreError ? null : err);895}896cookie.creation = oldCookie.creation; // step 11.3897cookie.lastAccessed = now;898// Step 11.4 (delete cookie) is implied by just setting the new one:899store.updateCookie(oldCookie, cookie, next); // step 12900901} else {902cookie.creation = cookie.lastAccessed = now;903store.putCookie(cookie, next); // step 12904}905}906907store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);908};909910// RFC6365 S5.4911CAN_BE_SYNC.push('getCookies');912CookieJar.prototype.getCookies = function(url, options, cb) {913var context = getCookieContext(url);914if (options instanceof Function) {915cb = options;916options = {};917}918919var host = canonicalDomain(context.hostname);920var path = context.pathname || '/';921922var secure = options.secure;923if (secure == null && context.protocol &&924(context.protocol == 'https:' || context.protocol == 'wss:'))925{926secure = true;927}928929var http = options.http;930if (http == null) {931http = true;932}933934var now = options.now || Date.now();935var expireCheck = options.expire !== false;936var allPaths = !!options.allPaths;937var store = this.store;938939function matchingCookie(c) {940// "Either:941// The cookie's host-only-flag is true and the canonicalized942// request-host is identical to the cookie's domain.943// Or:944// The cookie's host-only-flag is false and the canonicalized945// request-host domain-matches the cookie's domain."946if (c.hostOnly) {947if (c.domain != host) {948return false;949}950} else {951if (!domainMatch(host, c.domain, false)) {952return false;953}954}955956// "The request-uri's path path-matches the cookie's path."957if (!allPaths && !pathMatch(path, c.path)) {958return false;959}960961// "If the cookie's secure-only-flag is true, then the request-uri's962// scheme must denote a "secure" protocol"963if (c.secure && !secure) {964return false;965}966967// "If the cookie's http-only-flag is true, then exclude the cookie if the968// cookie-string is being generated for a "non-HTTP" API"969if (c.httpOnly && !http) {970return false;971}972973// deferred from S5.3974// non-RFC: allow retention of expired cookies by choice975if (expireCheck && c.expiryTime() <= now) {976store.removeCookie(c.domain, c.path, c.key, function(){}); // result ignored977return false;978}979980return true;981}982983store.findCookies(host, allPaths ? null : path, function(err,cookies) {984if (err) {985return cb(err);986}987988cookies = cookies.filter(matchingCookie);989990// sorting of S5.4 part 2991if (options.sort !== false) {992cookies = cookies.sort(cookieCompare);993}994995// S5.4 part 3996var now = new Date();997cookies.forEach(function(c) {998c.lastAccessed = now;999});1000// TODO persist lastAccessed10011002cb(null,cookies);1003});1004};10051006CAN_BE_SYNC.push('getCookieString');1007CookieJar.prototype.getCookieString = function(/*..., cb*/) {1008var args = Array.prototype.slice.call(arguments,0);1009var cb = args.pop();1010var next = function(err,cookies) {1011if (err) {1012cb(err);1013} else {1014cb(null, cookies1015.sort(cookieCompare)1016.map(function(c){1017return c.cookieString();1018})1019.join('; '));1020}1021};1022args.push(next);1023this.getCookies.apply(this,args);1024};10251026CAN_BE_SYNC.push('getSetCookieStrings');1027CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {1028var args = Array.prototype.slice.call(arguments,0);1029var cb = args.pop();1030var next = function(err,cookies) {1031if (err) {1032cb(err);1033} else {1034cb(null, cookies.map(function(c){1035return c.toString();1036}));1037}1038};1039args.push(next);1040this.getCookies.apply(this,args);1041};10421043// Use a closure to provide a true imperative API for synchronous stores.1044function syncWrap(method) {1045return function() {1046if (!this.store.synchronous) {1047throw new Error('CookieJar store is not synchronous; use async API instead.');1048}10491050var args = Array.prototype.slice.call(arguments);1051var syncErr, syncResult;1052args.push(function syncCb(err, result) {1053syncErr = err;1054syncResult = result;1055});1056this[method].apply(this, args);10571058if (syncErr) {1059throw syncErr;1060}1061return syncResult;1062};1063}10641065// wrap all declared CAN_BE_SYNC methods in the sync wrapper1066CAN_BE_SYNC.forEach(function(method) {1067CookieJar.prototype[method+'Sync'] = syncWrap(method);1068});10691070module.exports = {1071CookieJar: CookieJar,1072Cookie: Cookie,1073Store: Store,1074MemoryCookieStore: MemoryCookieStore,1075parseDate: parseDate,1076formatDate: formatDate,1077parse: parse,1078fromJSON: fromJSON,1079domainMatch: domainMatch,1080defaultPath: defaultPath,1081pathMatch: pathMatch,1082getPublicSuffix: pubsuffix.getPublicSuffix,1083cookieCompare: cookieCompare,1084permuteDomain: require('./permuteDomain').permuteDomain,1085permutePath: permutePath,1086canonicalDomain: canonicalDomain1087};108810891090