Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

tough-cookie

Package Overview
Dependencies
Maintainers
3
Versions
49
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tough-cookie - npm Package Compare versions

Comparing version 3.0.1 to 4.0.0

1793

lib/cookie.js

@@ -31,24 +31,18 @@ /*!

*/
'use strict';
var urlParse = require('url').parse;
var util = require('util');
var ipRegex = require('ip-regex')({ exact: true });
var pubsuffix = require('./pubsuffix-psl');
var Store = require('./store').Store;
var MemoryCookieStore = require('./memstore').MemoryCookieStore;
var pathMatch = require('./pathMatch').pathMatch;
var VERSION = require('./version');
"use strict";
const punycode = require("punycode");
const urlParse = require("url").parse;
const util = require("util");
const pubsuffix = require("./pubsuffix-psl");
const Store = require("./store").Store;
const MemoryCookieStore = require("./memstore").MemoryCookieStore;
const pathMatch = require("./pathMatch").pathMatch;
const VERSION = require("./version");
const { fromCallback } = require("universalify");
var punycode;
try {
punycode = require('punycode');
} catch(e) {
console.warn("tough-cookie: can't load punycode; won't use punycode for domain normalization");
}
// From RFC6265 S4.1.1
// note that it excludes \x3B ";"
var COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/;
const COOKIE_OCTETS = /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/;
var CONTROL_CHARS = /[\x00-\x1F]/;
const CONTROL_CHARS = /[\x00-\x1F]/;

@@ -58,26 +52,53 @@ // From Chromium // '\r', '\n' and '\0' should be treated as a terminator in

// https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60
var TERMINATORS = ['\n', '\r', '\0'];
const TERMINATORS = ["\n", "\r", "\0"];
// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
// Note ';' is \x3B
var PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;
const PATH_VALUE = /[\x20-\x3A\x3C-\x7E]+/;
// date-time parsing constants (RFC6265 S5.1.1)
var DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
var 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 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
};
var NUM_TO_MONTH = [
'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'
];
var NUM_TO_DAY = [
'Sun','Mon','Tue','Wed','Thu','Fri','Sat'
];
var MAX_TIME = 2147483647000; // 31-bit max
var MIN_TIME = 0; // 31-bit min
const MAX_TIME = 2147483647000; // 31-bit max
const MIN_TIME = 0; // 31-bit min
const SAME_SITE_CONTEXT_VAL_ERR =
'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"';
function checkSameSiteContext(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 only
var 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}|:)))$)/;
/*

@@ -94,7 +115,7 @@ * Parses a Natural number (i.e., non-negative integer) with either the

function parseDigits(token, minDigits, maxDigits, trailingOK) {
var count = 0;
let count = 0;
while (count < token.length) {
var c = token.charCodeAt(count);
const c = token.charCodeAt(count);
// "non-digit = %x00-2F / %x3A-FF"
if (c <= 0x2F || c >= 0x3A) {
if (c <= 0x2f || c >= 0x3a) {
break;

@@ -114,8 +135,8 @@ }

return parseInt(token.substr(0,count), 10);
return parseInt(token.substr(0, count), 10);
}
function parseTime(token) {
var parts = token.split(':');
var result = [0,0,0];
const parts = token.split(":");
const result = [0, 0, 0];

@@ -132,8 +153,8 @@ /* RF6256 S5.1.1:

for (var i = 0; i < 3; i++) {
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
var trailingOK = (i == 2);
var num = parseDigits(parts[i], 1, 2, trailingOK);
const trailingOK = i == 2;
const num = parseDigits(parts[i], 1, 2, trailingOK);
if (num === null) {

@@ -149,4 +170,6 @@ return null;

function parseMonth(token) {
token = String(token).substr(0,3).toLowerCase();
var num = MONTH_TO_NUM[token];
token = String(token)
.substr(0, 3)
.toLowerCase();
const num = MONTH_TO_NUM[token];
return num >= 0 ? num : null;

@@ -167,3 +190,3 @@ }

*/
var tokens = str.split(DATE_DELIM);
const tokens = str.split(DATE_DELIM);
if (!tokens) {

@@ -173,11 +196,11 @@ return;

var hour = null;
var minute = null;
var second = null;
var dayOfMonth = null;
var month = null;
var year = null;
let hour = null;
let minute = null;
let second = null;
let dayOfMonth = null;
let month = null;
let year = null;
for (var i=0; i<tokens.length; i++) {
var token = tokens[i].trim();
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i].trim();
if (!token.length) {

@@ -187,3 +210,3 @@ continue;

var result;
let result;

@@ -272,4 +295,8 @@ /* 2.1. If the found-time flag is not set and the token matches the time

if (
dayOfMonth === null || month === null || year === null || second === null ||
dayOfMonth < 1 || dayOfMonth > 31 ||
dayOfMonth === null ||
month === null ||
year === null ||
second === null ||
dayOfMonth < 1 ||
dayOfMonth > 31 ||
year < 1601 ||

@@ -287,9 +314,3 @@ hour > 23 ||

function formatDate(date) {
var d = date.getUTCDate(); d = d >= 10 ? d : '0'+d;
var h = date.getUTCHours(); h = h >= 10 ? h : '0'+h;
var m = date.getUTCMinutes(); m = m >= 10 ? m : '0'+m;
var s = date.getUTCSeconds(); s = s >= 10 ? s : '0'+s;
return NUM_TO_DAY[date.getUTCDay()] + ', ' +
d+' '+ NUM_TO_MONTH[date.getUTCMonth()] +' '+ date.getUTCFullYear() +' '+
h+':'+m+':'+s+' GMT';
return date.toUTCString();
}

@@ -302,3 +323,3 @@

}
str = str.trim().replace(/^\./,''); // S4.1.2.3 & S5.2.3: ignore leading .
str = str.trim().replace(/^\./, ""); // S4.1.2.3 & S5.2.3: ignore leading .

@@ -324,3 +345,7 @@ // convert to IDN if any non-ASCII characters

/*
* "The domain string and the string are identical. (Note that both the
* 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

@@ -333,11 +358,6 @@ * this point)"

/* "All of the following [three] conditions hold:" (order adjusted from the RFC) */
/* " o All of the following [three] conditions hold:" */
/* "* The string is a host name (i.e., not an IP address)." */
if (ipRegex.test(str)) {
return false;
}
/* "* The domain string is a suffix of the string" */
var idx = str.indexOf(domStr);
const idx = str.indexOf(domStr);
if (idx <= 0) {

@@ -347,18 +367,23 @@ return false; // it's a non-match (-1) or prefix (0)

// e.g "a.b.c".indexOf("b.c") === 2
// 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) { // it's not a suffix
return false;
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." */
/* " * 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;
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

@@ -375,3 +400,3 @@

// a %x2F ("/") character, output %x2F ("/") and skip the remaining steps.
if (!path || path.substr(0,1) !== "/") {
if (!path || path.substr(0, 1) !== "/") {
return "/";

@@ -386,3 +411,3 @@ }

var rightSlash = path.lastIndexOf("/");
const rightSlash = path.lastIndexOf("/");
if (rightSlash === 0) {

@@ -398,6 +423,6 @@ return "/";

function trimTerminator(str) {
for (var t = 0; t < TERMINATORS.length; t++) {
var terminatorIdx = str.indexOf(TERMINATORS[t]);
for (let t = 0; t < TERMINATORS.length; t++) {
const terminatorIdx = str.indexOf(TERMINATORS[t]);
if (terminatorIdx !== -1) {
str = str.substr(0,terminatorIdx);
str = str.substr(0, terminatorIdx);
}

@@ -412,10 +437,13 @@ }

var firstEq = cookiePair.indexOf('=');
let firstEq = cookiePair.indexOf("=");
if (looseMode) {
if (firstEq === 0) { // '=' is immediately at start
if (firstEq === 0) {
// '=' is immediately at start
cookiePair = cookiePair.substr(1);
firstEq = cookiePair.indexOf('='); // might still need to split on '='
firstEq = cookiePair.indexOf("="); // might still need to split on '='
}
} else { // non-loose mode
if (firstEq <= 0) { // no '=' or is at start
} else {
// non-loose mode
if (firstEq <= 0) {
// no '=' or is at start
return; // needs to have non-empty "cookie-name"

@@ -425,3 +453,3 @@ }

var cookieName, cookieValue;
let cookieName, cookieValue;
if (firstEq <= 0) {

@@ -432,3 +460,3 @@ cookieName = "";

cookieName = cookiePair.substr(0, firstEq).trim();
cookieValue = cookiePair.substr(firstEq+1).trim();
cookieValue = cookiePair.substr(firstEq + 1).trim();
}

@@ -440,3 +468,3 @@

var c = new Cookie();
const c = new Cookie();
c.key = cookieName;

@@ -448,3 +476,3 @@ c.value = cookieValue;

function parse(str, options) {
if (!options || typeof options !== 'object') {
if (!options || typeof options !== "object") {
options = {};

@@ -455,5 +483,5 @@ }

// We use a regex to parse the "name-value-pair" part of S5.2
var firstSemi = str.indexOf(';'); // S5.2 step 1
var cookiePair = (firstSemi === -1) ? str : str.substr(0, firstSemi);
var c = parseCookiePair(cookiePair, !!options.loose);
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) {

@@ -470,3 +498,3 @@ return;

// "discard the first ";" and trim".
var unparsed = str.slice(firstSemi + 1).trim();
const unparsed = str.slice(firstSemi + 1).trim();

@@ -487,10 +515,11 @@ // "If the unparsed-attributes string is empty, skip the rest of these

*/
var cookie_avs = unparsed.split(';');
const cookie_avs = unparsed.split(";");
while (cookie_avs.length) {
var av = cookie_avs.shift().trim();
if (av.length === 0) { // happens if ";;" appears
const av = cookie_avs.shift().trim();
if (av.length === 0) {
// happens if ";;" appears
continue;
}
var av_sep = av.indexOf('=');
var av_key, av_value;
const av_sep = av.indexOf("=");
let av_key, av_value;

@@ -501,4 +530,4 @@ if (av_sep === -1) {

} else {
av_key = av.substr(0,av_sep);
av_value = av.substr(av_sep+1);
av_key = av.substr(0, av_sep);
av_value = av.substr(av_sep + 1);
}

@@ -512,75 +541,93 @@

switch(av_key) {
case 'expires': // S5.2.1
if (av_value) {
var 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;
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;
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)) {
var 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);
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;
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."
var domain = av_value.trim().replace(/^\./, '');
if (domain) {
// "Convert the cookie-domain to lower case."
c.domain = domain.toLowerCase();
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;
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 "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 "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 "httponly": // S5.2.6 -- effectively the same as 'secure'
c.httpOnly = true;
break;
default:
c.extensions = c.extensions || [];
c.extensions.push(av);
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;
default:
// RFC6265bis-02 S5.3.7 step 1:
// "If cookie-av's attribute-value is not a case-insensitive match
// for "Strict" or "Lax", ignore the "cookie-av"."
// This effectively sets it to 'none' from the prototype.
break;
}
break;
default:
c.extensions = c.extensions || [];
c.extensions.push(av);
break;
}

@@ -592,5 +639,37 @@ }

/**
* 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) {
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) {
return (
!cookie.key.startsWith("__Host-") ||
(cookie.secure &&
cookie.hostOnly &&
cookie.path != null &&
cookie.path === "/")
);
}
// avoid the V8 deoptimization monster!
function jsonParse(str) {
var obj;
let obj;
try {

@@ -609,4 +688,4 @@ obj = JSON.parse(str);

var obj;
if (typeof str === 'string') {
let obj;
if (typeof str === "string") {
obj = jsonParse(str);

@@ -621,20 +700,14 @@ if (obj instanceof Error) {

var c = new Cookie();
for (var i=0; i<Cookie.serializableProperties.length; i++) {
var prop = Cookie.serializableProperties[i];
if (obj[prop] === undefined ||
obj[prop] === Cookie.prototype[prop])
{
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 (prop === "expires" || prop === "creation" || prop === "lastAccessed") {
if (obj[prop] === null) {
c[prop] = null;
} else {
c[prop] = obj[prop] == "Infinity" ?
"Infinity" : new Date(obj[prop]);
c[prop] = obj[prop] == "Infinity" ? "Infinity" : new Date(obj[prop]);
}

@@ -658,8 +731,8 @@ } else {

function cookieCompare(a,b) {
var cmp = 0;
function cookieCompare(a, b) {
let cmp = 0;
// descending for length: b CMP a
var aPathLen = a.path ? a.path.length : 0;
var bPathLen = b.path ? b.path.length : 0;
const aPathLen = a.path ? a.path.length : 0;
const bPathLen = b.path ? b.path.length : 0;
cmp = bPathLen - aPathLen;

@@ -671,4 +744,4 @@ if (cmp !== 0) {

// ascending for time: a CMP b
var aTime = a.creation ? a.creation.getTime() : MAX_TIME;
var bTime = b.creation ? b.creation.getTime() : MAX_TIME;
const aTime = a.creation ? a.creation.getTime() : MAX_TIME;
const bTime = b.creation ? b.creation.getTime() : MAX_TIME;
cmp = aTime - bTime;

@@ -688,18 +761,15 @@ if (cmp !== 0) {

function permutePath(path) {
if (path === '/') {
return ['/'];
if (path === "/") {
return ["/"];
}
if (path.lastIndexOf('/') === path.length-1) {
path = path.substr(0,path.length-1);
}
var permutations = [path];
const permutations = [path];
while (path.length > 1) {
var lindex = path.lastIndexOf('/');
const lindex = path.lastIndexOf("/");
if (lindex === 0) {
break;
}
path = path.substr(0,lindex);
path = path.substr(0, lindex);
permutations.push(path);
}
permutations.push('/');
permutations.push("/");
return permutations;

@@ -716,4 +786,3 @@ }

url = decodeURI(url);
}
catch(err) {
} catch (err) {
// Silently swallow error

@@ -725,771 +794,889 @@ }

function Cookie(options) {
options = options || {};
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: "none"
};
Object.keys(options).forEach(function(prop) {
if (Cookie.prototype.hasOwnProperty(prop) &&
Cookie.prototype[prop] !== options[prop] &&
prop.substr(0,1) !== '_')
{
this[prop] = options[prop];
class Cookie {
constructor(options = {}) {
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect;
}
}, this);
this.creation = this.creation || new Date();
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
});
}
// 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
});
}
Cookie.cookiesCreated = 0; // incremented each time a cookie is created
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}"`;
}
Cookie.parse = parse;
Cookie.fromJSON = fromJSON;
toJSON() {
const obj = {};
Cookie.prototype.key = "";
Cookie.prototype.value = "";
for (const prop of Cookie.serializableProperties) {
if (this[prop] === cookieDefaults[prop]) {
continue; // leave as prototype default
}
// the order in which the RFC has them:
Cookie.prototype.expires = "Infinity"; // coerces to literal Infinity
Cookie.prototype.maxAge = null; // takes precedence over expires for TTL
Cookie.prototype.domain = null;
Cookie.prototype.path = null;
Cookie.prototype.secure = false;
Cookie.prototype.httpOnly = false;
Cookie.prototype.extensions = null;
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];
}
}
}
// set by the CookieJar:
Cookie.prototype.hostOnly = null; // boolean when set
Cookie.prototype.pathIsDefault = null; // boolean when set
Cookie.prototype.creation = null; // Date when set; defaulted by Cookie.parse
Cookie.prototype.lastAccessed = null; // Date when set
Object.defineProperty(Cookie.prototype, 'creationIndex', {
configurable: true,
enumerable: false,
writable: true,
value: 0
});
return obj;
}
Cookie.serializableProperties = Object.keys(Cookie.prototype)
.filter(function(prop) {
return !(
Cookie.prototype[prop] instanceof Function ||
prop === 'creationIndex' ||
prop.substr(0,1) === '_'
);
});
clone() {
return fromJSON(this.toJSON());
}
Cookie.prototype.inspect = function inspect() {
var now = Date.now();
return 'Cookie="'+this.toString() +
'; hostOnly='+(this.hostOnly != null ? this.hostOnly : '?') +
'; aAge='+(this.lastAccessed ? (now-this.lastAccessed.getTime())+'ms' : '?') +
'; cAge='+(this.creation ? (now-this.creation.getTime())+'ms' : '?') +
'"';
};
// Use the new custom inspection symbol to add the custom inspect function if
// available.
if (util.inspect.custom) {
Cookie.prototype[util.inspect.custom] = Cookie.prototype.inspect;
}
Cookie.prototype.toJSON = function() {
var obj = {};
var props = Cookie.serializableProperties;
for (var i=0; i<props.length; i++) {
var prop = props[i];
if (this[prop] === Cookie.prototype[prop]) {
continue; // leave as prototype default
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;
}
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();
const cdomain = this.cdomain();
if (cdomain) {
if (cdomain.match(/\.$/)) {
return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this
}
} else if (prop === 'maxAge') {
if (this[prop] !== null) {
// again, intentionally not ===
obj[prop] = (this[prop] == Infinity || this[prop] == -Infinity) ?
this[prop].toString() : this[prop];
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 {
if (this[prop] !== Cookie.prototype[prop]) {
obj[prop] = this[prop];
}
this.expires = parseDate(exp) || "Infinity";
}
}
return obj;
};
Cookie.prototype.clone = function() {
return fromJSON(this.toJSON());
};
Cookie.prototype.validate = function validate() {
if (!COOKIE_OCTETS.test(this.value)) {
return false;
setMaxAge(age) {
if (age === Infinity || age === -Infinity) {
this.maxAge = age.toString(); // so JSON.stringify() works
} else {
this.maxAge = age;
}
}
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;
}
var cdomain = this.cdomain();
if (cdomain) {
if (cdomain.match(/\.$/)) {
return false; // S4.1.2.3 suggests that this is bad. domainMatch() tests confirm this
cookieString() {
let val = this.value;
if (val == null) {
val = "";
}
var suffix = pubsuffix.getPublicSuffix(cdomain);
if (suffix == null) { // it's a public suffix
return false;
if (this.key === "") {
return val;
}
return `${this.key}=${val}`;
}
return true;
};
Cookie.prototype.setExpires = function setExpires(exp) {
if (exp instanceof Date) {
this.expires = exp;
} else {
this.expires = parseDate(exp) || "Infinity";
}
};
// gives Set-Cookie header format
toString() {
let str = this.cookieString();
Cookie.prototype.setMaxAge = function setMaxAge(age) {
if (age === Infinity || age === -Infinity) {
this.maxAge = age.toString(); // so JSON.stringify() works
} else {
this.maxAge = age;
}
};
if (this.expires != Infinity) {
if (this.expires instanceof Date) {
str += `; Expires=${formatDate(this.expires)}`;
} else {
str += `; Expires=${this.expires}`;
}
}
// gives Cookie header format
Cookie.prototype.cookieString = function cookieString() {
var val = this.value;
if (val == null) {
val = '';
}
if (this.key === '') {
return val;
}
return this.key+'='+val;
};
if (this.maxAge != null && this.maxAge != Infinity) {
str += `; Max-Age=${this.maxAge}`;
}
// gives Set-Cookie header format
Cookie.prototype.toString = function toString() {
var str = this.cookieString();
if (this.domain && !this.hostOnly) {
str += `; Domain=${this.domain}`;
}
if (this.path) {
str += `; Path=${this.path}`;
}
if (this.expires != Infinity) {
if (this.expires instanceof Date) {
str += '; Expires='+formatDate(this.expires);
} else {
str += '; Expires='+this.expires;
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}`;
});
}
if (this.maxAge != null && this.maxAge != Infinity) {
str += '; Max-Age='+this.maxAge;
return str;
}
if (this.domain && !this.hostOnly) {
str += '; Domain='+this.domain;
}
if (this.path) {
str += '; Path='+this.path;
}
// 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;
}
if (this.secure) {
str += '; Secure';
}
if (this.httpOnly) {
str += '; HttpOnly';
}
if (this.extensions) {
this.extensions.forEach(function(ext) {
str += '; '+ext;
});
}
let expires = this.expires;
if (expires != Infinity) {
if (!(expires instanceof Date)) {
expires = parseDate(expires) || Infinity;
}
return str;
};
if (expires == Infinity) {
return Infinity;
}
// 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
Cookie.prototype.TTL = function 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;
return expires.getTime() - (now || Date.now());
}
return Infinity;
}
var expires = this.expires;
if (expires != Infinity) {
if (!(expires instanceof Date)) {
expires = parseDate(expires) || 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 (expires == Infinity) {
if (this.expires == Infinity) {
return Infinity;
}
return this.expires.getTime();
}
return expires.getTime() - (now || Date.now());
// 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);
}
}
return Infinity;
};
// This replaces the "persistent-flag" parts of S5.3 step 3
isPersistent() {
return this.maxAge != null || this.expires != Infinity;
}
// expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
// elsewhere)
Cookie.prototype.expiryTime = function expiryTime(now) {
if (this.maxAge != null) {
var relativeTo = now || this.creation || new Date();
var age = (this.maxAge <= 0) ? -Infinity : this.maxAge*1000;
return relativeTo.getTime() + age;
// Mostly S5.1.2 and S5.2.3:
canonicalizedDomain() {
if (this.domain == null) {
return null;
}
return canonicalDomain(this.domain);
}
if (this.expires == Infinity) {
return Infinity;
cdomain() {
return this.canonicalizedDomain();
}
return this.expires.getTime();
};
}
// expiryDate() replaces the "expiry-time" parts of S5.3 step 3 (setCookie()
// elsewhere), except it returns a Date
Cookie.prototype.expiryDate = function expiryDate(now) {
var 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);
}
Cookie.cookiesCreated = 0;
Cookie.parse = parse;
Cookie.fromJSON = fromJSON;
Cookie.serializableProperties = Object.keys(cookieDefaults);
Cookie.sameSiteLevel = {
strict: 3,
lax: 2,
none: 1
};
// This replaces the "persistent-flag" parts of S5.3 step 3
Cookie.prototype.isPersistent = function isPersistent() {
return (this.maxAge != null || this.expires != Infinity);
Cookie.sameSiteCanonical = {
strict: "Strict",
lax: "Lax"
};
// Mostly S5.1.2 and S5.2.3:
Cookie.prototype.cdomain =
Cookie.prototype.canonicalizedDomain = function canonicalizedDomain() {
if (this.domain == null) {
return null;
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;
}
}
return canonicalDomain(this.domain);
};
/* Default is SILENT */
return PrefixSecurityEnum.SILENT;
}
function CookieJar(store, options) {
if (typeof options === "boolean") {
options = {rejectPublicSuffixes: options};
} else if (options == null) {
options = {};
}
if (options.rejectPublicSuffixes != null) {
class CookieJar {
constructor(store, options = { rejectPublicSuffixes: true }) {
if (typeof options === "boolean") {
options = { rejectPublicSuffixes: options };
}
this.rejectPublicSuffixes = options.rejectPublicSuffixes;
this.enableLooseMode = !!options.looseMode;
this.allowSpecialUseDomain = !!options.allowSpecialUseDomain;
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");
}
if (options.looseMode != null) {
this.enableLooseMode = options.looseMode;
}
if (!store) {
store = new MemoryCookieStore();
}
this.store = store;
}
CookieJar.prototype.store = null;
CookieJar.prototype.rejectPublicSuffixes = true;
CookieJar.prototype.enableLooseMode = false;
var CAN_BE_SYNC = [];
setCookie(cookie, url, options, cb) {
let err;
const context = getCookieContext(url);
if (typeof options === "function") {
cb = options;
options = {};
}
CAN_BE_SYNC.push('setCookie');
CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
var err;
var context = getCookieContext(url);
if (options instanceof Function) {
cb = options;
options = {};
}
const host = canonicalDomain(context.hostname);
const loose = options.loose || this.enableLooseMode;
var host = canonicalDomain(context.hostname);
var loose = this.enableLooseMode;
if (options.loose != null) {
loose = options.loose;
}
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");
// 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);
}
}
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
var now = options.now || new Date(); // will assign later to save effort in the face of errors
// 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 3: NOOP; persistent-flag and expiry-time is handled by getCookie()
// S5.3 step 4: NOOP; domain is null by default
// S5.3 step 4: NOOP; domain is null by default
// S5.3 step 5: public suffixes
if (this.rejectPublicSuffixes && cookie.domain) {
var suffix = pubsuffix.getPublicSuffix(cookie.cdomain());
if (suffix == null) { // e.g. "com"
err = new Error("Cookie has domain set to a public suffix");
return cb(options.ignoreError ? null : err);
// S5.3 step 5: public suffixes
if (this.rejectPublicSuffixes && cookie.domain) {
const suffix = pubsuffix.getPublicSuffix(cookie.cdomain());
if (suffix == null) {
// 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);
// 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;
}
if (cookie.hostOnly == null) { // don't reset if already set
cookie.hostOnly = false;
//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;
}
} else {
cookie.hostOnly = true;
cookie.domain = host;
}
// S5.3 step 8: NOOP; secure attribute
// S5.3 step 9: NOOP; httpOnly attribute
//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 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);
}
// S5.3 step 8: NOOP; secure attribute
// S5.3 step 9: NOOP; httpOnly attribute
// 6252bis-02 S5.4 Step 13 & 14:
if (cookie.sameSite !== "none" && 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);
}
}
// 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);
}
/* 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)
);
}
}
var store = this.store;
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);
if (!store.updateCookie) {
store.updateCookie = function(oldCookie, newCookie, cb) {
this.putCookie(newCookie, cb);
};
}
var next = function(err) {
function withCookie(err, oldCookie) {
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);
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
}
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);
}
store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie);
};
// RFC6365 S5.4
getCookies(url, options, cb) {
const context = getCookieContext(url);
if (typeof options === "function") {
cb = options;
options = {};
}
// RFC6365 S5.4
CAN_BE_SYNC.push('getCookies');
CookieJar.prototype.getCookies = function(url, options, cb) {
var context = getCookieContext(url);
if (options instanceof Function) {
cb = options;
options = {};
}
const host = canonicalDomain(context.hostname);
const path = context.pathname || "/";
var host = canonicalDomain(context.hostname);
var path = context.pathname || '/';
let secure = options.secure;
if (
secure == null &&
context.protocol &&
(context.protocol == "https:" || context.protocol == "wss:")
) {
secure = true;
}
var 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));
}
}
var http = options.http;
if (http == null) {
http = true;
}
let http = options.http;
if (http == null) {
http = true;
}
var now = options.now || Date.now();
var expireCheck = options.expire !== false;
var allPaths = !!options.allPaths;
var store = this.store;
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) {
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;
}
} else {
if (!domainMatch(host, c.domain, 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;
}
}
// "The request-uri's path path-matches the cookie's path."
if (!allPaths && !pathMatch(path, c.path)) {
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;
}
// "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;
}
// 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;
}
}
// "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;
}
// 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;
}
// 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, function(){}); // result ignored
return false;
return true;
}
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);
}
);
}
store.findCookies(host, allPaths ? null : path, function(err,cookies) {
if (err) {
return cb(err);
}
getCookieString(...args) {
const cb = args.pop();
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);
}
cookies = cookies.filter(matchingCookie);
getSetCookieStrings(...args) {
const cb = args.pop();
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);
}
// sorting of S5.4 part 2
if (options.sort !== false) {
cookies = cookies.sort(cookieCompare);
serialize(cb) {
let type = this.store.constructor.name;
if (type === "Object") {
type = null;
}
// S5.4 part 3
var now = new Date();
cookies.forEach(function(c) {
c.lastAccessed = now;
});
// TODO persist lastAccessed
// 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}`,
cb(null,cookies);
});
};
// add the store type, to make humans happy:
storeType: type,
CAN_BE_SYNC.push('getCookieString');
CookieJar.prototype.getCookieString = function(/*..., cb*/) {
var args = Array.prototype.slice.call(arguments,0);
var cb = args.pop();
var next = function(err,cookies) {
if (err) {
cb(err);
} else {
cb(null, cookies
.sort(cookieCompare)
.map(function(c){
return c.cookieString();
})
.join('; '));
}
};
args.push(next);
this.getCookies.apply(this,args);
};
// CookieJar configuration:
rejectPublicSuffixes: !!this.rejectPublicSuffixes,
CAN_BE_SYNC.push('getSetCookieStrings');
CookieJar.prototype.getSetCookieStrings = function(/*..., cb*/) {
var args = Array.prototype.slice.call(arguments,0);
var cb = args.pop();
var next = function(err,cookies) {
if (err) {
cb(err);
} else {
cb(null, cookies.map(function(c){
return c.toString();
}));
// 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"
)
);
}
};
args.push(next);
this.getCookies.apply(this,args);
};
CAN_BE_SYNC.push('serialize');
CookieJar.prototype.serialize = function(cb) {
var type = this.store.constructor.name;
if (type === 'Object') {
type = null;
}
this.store.getAllCookies((err, cookies) => {
if (err) {
return cb(err);
}
// update README.md "Serialization Format" if you change this, please!
var 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,
serialized.cookies = cookies.map(cookie => {
// convert to serialized 'raw' cookies
cookie = cookie instanceof Cookie ? cookie.toJSON() : cookie;
// add the store type, to make humans happy:
storeType: type,
// Remove the index so new ones get assigned during deserialization
delete cookie.creationIndex;
// CookieJar configuration:
rejectPublicSuffixes: !!this.rejectPublicSuffixes,
return cookie;
});
// this gets filled from getAllCookies:
cookies: []
};
return cb(null, serialized);
});
}
if (!(this.store.getAllCookies &&
typeof this.store.getAllCookies === 'function'))
{
return cb(new Error('store does not support getAllCookies and cannot be serialized'));
toJSON() {
return this.serializeSync();
}
this.store.getAllCookies(function(err,cookies) {
if (err) {
return cb(err);
// 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
serialized.cookies = cookies.map(function(cookie) {
// convert to serialized 'raw' cookies
cookie = (cookie instanceof Cookie) ? cookie.toJSON() : cookie;
const putNext = err => {
if (err) {
return cb(err);
}
// Remove the index so new ones get assigned during deserialization
delete cookie.creationIndex;
if (!cookies.length) {
return cb(err, this);
}
return cookie;
});
let cookie;
try {
cookie = fromJSON(cookies.shift());
} catch (e) {
return cb(e);
}
return cb(null, serialized);
});
};
if (cookie === null) {
return putNext(null); // skip this cookie
}
// well-known name that JSON.stringify calls
CookieJar.prototype.toJSON = function() {
return this.serializeSync();
};
this.store.putCookie(cookie, putNext);
};
// use the class method CookieJar.deserialize instead of calling this directly
CAN_BE_SYNC.push('_importCookies');
CookieJar.prototype._importCookies = function(serialized, cb) {
var jar = this;
var cookies = serialized.cookies;
if (!cookies || !Array.isArray(cookies)) {
return cb(new Error('serialized jar has no cookies array'));
putNext();
}
cookies = cookies.slice(); // do not modify the original
function putNext(err) {
if (err) {
return cb(err);
clone(newStore, cb) {
if (arguments.length === 1) {
cb = newStore;
newStore = null;
}
if (!cookies.length) {
return cb(err, jar);
}
this.serialize((err, serialized) => {
if (err) {
return cb(err);
}
CookieJar.deserialize(serialized, newStore, cb);
});
}
var cookie;
try {
cookie = fromJSON(cookies.shift());
} catch (e) {
return cb(e);
cloneSync(newStore) {
if (arguments.length === 0) {
return this._cloneSync();
}
if (cookie === null) {
return putNext(null); // skip this cookie
if (!newStore.synchronous) {
throw new Error(
"CookieJar clone destination store is not synchronous; use async API instead."
);
}
jar.store.putCookie(cookie, putNext);
return this._cloneSync(newStore);
}
putNext();
};
removeAllCookies(cb) {
const store = this.store;
CookieJar.deserialize = function(strOrObj, store, cb) {
if (arguments.length !== 3) {
// store is optional
cb = store;
store = null;
}
var serialized;
if (typeof strOrObj === 'string') {
serialized = jsonParse(strOrObj);
if (serialized instanceof Error) {
return cb(serialized);
// 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);
}
} else {
serialized = strOrObj;
}
var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
jar._importCookies(serialized, function(err) {
if (err) {
return cb(err);
}
cb(null, jar);
});
};
store.getAllCookies((err, cookies) => {
if (err) {
return cb(err);
}
CookieJar.deserializeSync = function(strOrObj, store) {
var serialized = typeof strOrObj === 'string' ?
JSON.parse(strOrObj) : strOrObj;
var jar = new CookieJar(store, serialized.rejectPublicSuffixes);
if (cookies.length === 0) {
return cb(null);
}
// catch this mistake early:
if (!jar.store.synchronous) {
throw new Error('CookieJar store is not synchronous; use async API instead.');
}
let completedCount = 0;
const removeErrors = [];
jar._importCookiesSync(serialized);
return jar;
};
CookieJar.fromJSON = CookieJar.deserializeSync;
function removeCookieCb(removeErr) {
if (removeErr) {
removeErrors.push(removeErr);
}
CookieJar.prototype.clone = function(newStore, cb) {
if (arguments.length === 1) {
cb = newStore;
newStore = null;
}
completedCount++;
this.serialize(function(err,serialized) {
if (err) {
return cb(err);
}
CookieJar.deserialize(serialized, newStore, cb);
});
};
if (completedCount === cookies.length) {
return cb(removeErrors.length ? removeErrors[0] : null);
}
}
CAN_BE_SYNC.push('removeAllCookies');
CookieJar.prototype.removeAllCookies = function(cb) {
var 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 (store.removeAllCookies instanceof Function &&
store.removeAllCookies !== Store.prototype.removeAllCookies)
{
return store.removeAllCookies(cb);
cookies.forEach(cookie => {
store.removeCookie(
cookie.domain,
cookie.path,
cookie.key,
removeCookieCb
);
});
});
}
store.getAllCookies(function(err, cookies) {
if (err) {
return cb(err);
static deserialize(strOrObj, store, cb) {
if (arguments.length !== 3) {
// store is optional
cb = store;
store = null;
}
if (cookies.length === 0) {
return cb(null);
let serialized;
if (typeof strOrObj === "string") {
serialized = jsonParse(strOrObj);
if (serialized instanceof Error) {
return cb(serialized);
}
} else {
serialized = strOrObj;
}
var completedCount = 0;
var removeErrors = [];
function removeCookieCb(removeErr) {
if (removeErr) {
removeErrors.push(removeErr);
const jar = new CookieJar(store, serialized.rejectPublicSuffixes);
jar._importCookies(serialized, err => {
if (err) {
return cb(err);
}
cb(null, jar);
});
}
completedCount++;
static deserializeSync(strOrObj, store) {
const serialized =
typeof strOrObj === "string" ? JSON.parse(strOrObj) : strOrObj;
const jar = new CookieJar(store, serialized.rejectPublicSuffixes);
if (completedCount === cookies.length) {
return cb(removeErrors.length ? removeErrors[0] : null);
}
// catch this mistake early:
if (!jar.store.synchronous) {
throw new Error(
"CookieJar store is not synchronous; use async API instead."
);
}
cookies.forEach(function(cookie) {
store.removeCookie(cookie.domain, cookie.path, cookie.key, removeCookieCb);
});
});
};
CookieJar.prototype._cloneSync = syncWrap('clone');
CookieJar.prototype.cloneSync = function(newStore) {
if (!newStore.synchronous) {
throw new Error('CookieJar clone destination store is not synchronous; use async API instead.');
jar._importCookiesSync(serialized);
return jar;
}
return this._cloneSync(newStore);
};
}
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() {
return function(...args) {
if (!this.store.synchronous) {
throw new Error('CookieJar store is not synchronous; use async API instead.');
throw new Error(
"CookieJar store is not synchronous; use async API instead."
);
}
var args = Array.prototype.slice.call(arguments);
var syncErr, syncResult;
args.push(function syncCb(err, result) {
let syncErr, syncResult;
this[method](...args, (err, result) => {
syncErr = err;
syncResult = result;
});
this[method].apply(this, args);

@@ -1503,7 +1690,2 @@ if (syncErr) {

// wrap all declared CAN_BE_SYNC methods in the sync wrapper
CAN_BE_SYNC.forEach(function(method) {
CookieJar.prototype[method+'Sync'] = syncWrap(method);
});
exports.version = VERSION;

@@ -1523,4 +1705,5 @@ exports.CookieJar = CookieJar;

exports.cookieCompare = cookieCompare;
exports.permuteDomain = require('./permuteDomain').permuteDomain;
exports.permuteDomain = require("./permuteDomain").permuteDomain;
exports.permutePath = permutePath;
exports.canonicalDomain = canonicalDomain;
exports.PrefixSecurityEnum = PrefixSecurityEnum;

@@ -31,152 +31,161 @@ /*!

*/
'use strict';
var Store = require('./store').Store;
var permuteDomain = require('./permuteDomain').permuteDomain;
var pathMatch = require('./pathMatch').pathMatch;
var util = require('util');
"use strict";
const { fromCallback } = require("universalify");
const Store = require("./store").Store;
const permuteDomain = require("./permuteDomain").permuteDomain;
const pathMatch = require("./pathMatch").pathMatch;
const util = require("util");
function MemoryCookieStore() {
Store.call(this);
this.idx = {};
}
util.inherits(MemoryCookieStore, Store);
exports.MemoryCookieStore = MemoryCookieStore;
MemoryCookieStore.prototype.idx = null;
class MemoryCookieStore extends Store {
constructor() {
super();
this.synchronous = true;
this.idx = {};
if (util.inspect.custom) {
this[util.inspect.custom] = this.inspect;
}
}
// Since it's just a struct in RAM, this Store is synchronous
MemoryCookieStore.prototype.synchronous = true;
// force a default depth:
MemoryCookieStore.prototype.inspect = function() {
return "{ idx: "+util.inspect(this.idx, false, 2)+' }';
};
// Use the new custom inspection symbol to add the custom inspect function if
// available.
if (util.inspect.custom) {
MemoryCookieStore.prototype[util.inspect.custom] = MemoryCookieStore.prototype.inspect;
}
MemoryCookieStore.prototype.findCookie = function(domain, path, key, cb) {
if (!this.idx[domain]) {
return cb(null,undefined);
inspect() {
return `{ idx: ${util.inspect(this.idx, false, 2)} }`;
}
if (!this.idx[domain][path]) {
return cb(null,undefined);
}
return cb(null,this.idx[domain][path][key]||null);
};
MemoryCookieStore.prototype.findCookies = function(domain, path, cb) {
var results = [];
if (!domain) {
return cb(null,[]);
findCookie(domain, path, key, cb) {
if (!this.idx[domain]) {
return cb(null, undefined);
}
if (!this.idx[domain][path]) {
return cb(null, undefined);
}
return cb(null, this.idx[domain][path][key] || null);
}
findCookies(domain, path, allowSpecialUseDomain, cb) {
const results = [];
if (typeof allowSpecialUseDomain === "function") {
cb = allowSpecialUseDomain;
allowSpecialUseDomain = false;
}
if (!domain) {
return cb(null, []);
}
var pathMatcher;
if (!path) {
// null means "all paths"
pathMatcher = function matchAll(domainIndex) {
for (var curPath in domainIndex) {
var pathIndex = domainIndex[curPath];
for (var key in pathIndex) {
results.push(pathIndex[key]);
let pathMatcher;
if (!path) {
// null means "all paths"
pathMatcher = function matchAll(domainIndex) {
for (const curPath in domainIndex) {
const pathIndex = domainIndex[curPath];
for (const key in pathIndex) {
results.push(pathIndex[key]);
}
}
};
} else {
pathMatcher = function matchRFC(domainIndex) {
//NOTE: we should use path-match algorithm from S5.1.4 here
//(see : https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/canonical_cookie.cc#L299)
Object.keys(domainIndex).forEach(cookiePath => {
if (pathMatch(path, cookiePath)) {
const pathIndex = domainIndex[cookiePath];
for (const key in pathIndex) {
results.push(pathIndex[key]);
}
}
});
};
}
const domains = permuteDomain(domain, allowSpecialUseDomain) || [domain];
const idx = this.idx;
domains.forEach(curDomain => {
const domainIndex = idx[curDomain];
if (!domainIndex) {
return;
}
};
pathMatcher(domainIndex);
});
} else {
pathMatcher = function matchRFC(domainIndex) {
//NOTE: we should use path-match algorithm from S5.1.4 here
//(see : https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/canonical_cookie.cc#L299)
Object.keys(domainIndex).forEach(function (cookiePath) {
if (pathMatch(path, cookiePath)) {
var pathIndex = domainIndex[cookiePath];
for (var key in pathIndex) {
results.push(pathIndex[key]);
}
}
});
};
cb(null, results);
}
var domains = permuteDomain(domain) || [domain];
var idx = this.idx;
domains.forEach(function(curDomain) {
var domainIndex = idx[curDomain];
if (!domainIndex) {
return;
putCookie(cookie, cb) {
if (!this.idx[cookie.domain]) {
this.idx[cookie.domain] = {};
}
pathMatcher(domainIndex);
});
cb(null,results);
};
MemoryCookieStore.prototype.putCookie = function(cookie, cb) {
if (!this.idx[cookie.domain]) {
this.idx[cookie.domain] = {};
if (!this.idx[cookie.domain][cookie.path]) {
this.idx[cookie.domain][cookie.path] = {};
}
this.idx[cookie.domain][cookie.path][cookie.key] = cookie;
cb(null);
}
if (!this.idx[cookie.domain][cookie.path]) {
this.idx[cookie.domain][cookie.path] = {};
updateCookie(oldCookie, newCookie, cb) {
// updateCookie() may avoid updating cookies that are identical. For example,
// lastAccessed may not be important to some stores and an equality
// comparison could exclude that field.
this.putCookie(newCookie, cb);
}
this.idx[cookie.domain][cookie.path][cookie.key] = cookie;
cb(null);
};
MemoryCookieStore.prototype.updateCookie = function(oldCookie, newCookie, cb) {
// updateCookie() may avoid updating cookies that are identical. For example,
// lastAccessed may not be important to some stores and an equality
// comparison could exclude that field.
this.putCookie(newCookie,cb);
};
MemoryCookieStore.prototype.removeCookie = function(domain, path, key, cb) {
if (this.idx[domain] && this.idx[domain][path] && this.idx[domain][path][key]) {
delete this.idx[domain][path][key];
removeCookie(domain, path, key, cb) {
if (
this.idx[domain] &&
this.idx[domain][path] &&
this.idx[domain][path][key]
) {
delete this.idx[domain][path][key];
}
cb(null);
}
cb(null);
};
MemoryCookieStore.prototype.removeCookies = function(domain, path, cb) {
if (this.idx[domain]) {
if (path) {
delete this.idx[domain][path];
} else {
delete this.idx[domain];
removeCookies(domain, path, cb) {
if (this.idx[domain]) {
if (path) {
delete this.idx[domain][path];
} else {
delete this.idx[domain];
}
}
return cb(null);
}
return cb(null);
};
removeAllCookies(cb) {
this.idx = {};
return cb(null);
}
getAllCookies(cb) {
const cookies = [];
const idx = this.idx;
MemoryCookieStore.prototype.removeAllCookies = function(cb) {
this.idx = {};
return cb(null);
}
MemoryCookieStore.prototype.getAllCookies = function(cb) {
var cookies = [];
var idx = this.idx;
var domains = Object.keys(idx);
domains.forEach(function(domain) {
var paths = Object.keys(idx[domain]);
paths.forEach(function(path) {
var keys = Object.keys(idx[domain][path]);
keys.forEach(function(key) {
if (key !== null) {
cookies.push(idx[domain][path][key]);
}
const domains = Object.keys(idx);
domains.forEach(domain => {
const paths = Object.keys(idx[domain]);
paths.forEach(path => {
const keys = Object.keys(idx[domain][path]);
keys.forEach(key => {
if (key !== null) {
cookies.push(idx[domain][path][key]);
}
});
});
});
});
// Sort by creationIndex so deserializing retains the creation order.
// When implementing your own store, this SHOULD retain the order too
cookies.sort(function(a,b) {
return (a.creationIndex||0) - (b.creationIndex||0);
});
// Sort by creationIndex so deserializing retains the creation order.
// When implementing your own store, this SHOULD retain the order too
cookies.sort((a, b) => {
return (a.creationIndex || 0) - (b.creationIndex || 0);
});
cb(null, cookies);
};
cb(null, cookies);
}
}
[
"findCookie",
"findCookies",
"putCookie",
"updateCookie",
"removeCookie",
"removeCookies",
"removeAllCookies",
"getAllCookies"
].forEach(name => {
MemoryCookieStore[name] = fromCallback(MemoryCookieStore.prototype[name]);
});
exports.MemoryCookieStore = MemoryCookieStore;

@@ -36,3 +36,3 @@ /*!

*/
function pathMatch (reqPath, cookiePath) {
function pathMatch(reqPath, cookiePath) {
// "o The cookie-path and the request-path are identical."

@@ -43,3 +43,3 @@ if (cookiePath === reqPath) {

var idx = reqPath.indexOf(cookiePath);
const idx = reqPath.indexOf(cookiePath);
if (idx === 0) {

@@ -46,0 +46,0 @@ // "o The cookie-path is a prefix of the request-path, and the last

@@ -32,8 +32,22 @@ /*!

"use strict";
var pubsuffix = require('./pubsuffix-psl');
const pubsuffix = require("./pubsuffix-psl");
// Gives the permutation of all possible domainMatch()es of a given domain. The
// array is in shortest-to-longest order. Handy for indexing.
function permuteDomain (domain) {
var pubSuf = pubsuffix.getPublicSuffix(domain);
const SPECIAL_USE_DOMAINS = ["local"]; // RFC 6761
function permuteDomain(domain, allowSpecialUseDomain) {
let pubSuf = null;
if (allowSpecialUseDomain) {
const domainParts = domain.split(".");
if (SPECIAL_USE_DOMAINS.includes(domainParts[domainParts.length - 1])) {
pubSuf = `${domainParts[domainParts.length - 2]}.${
domainParts[domainParts.length - 1]
}`;
} else {
pubSuf = pubsuffix.getPublicSuffix(domain);
}
} else {
pubSuf = pubsuffix.getPublicSuffix(domain);
}
if (!pubSuf) {

@@ -46,8 +60,8 @@ return null;

var prefix = domain.slice(0, -(pubSuf.length + 1)); // ".example.com"
var parts = prefix.split('.').reverse();
var cur = pubSuf;
var permutations = [cur];
const prefix = domain.slice(0, -(pubSuf.length + 1)); // ".example.com"
const parts = prefix.split(".").reverse();
let cur = pubSuf;
const permutations = [cur];
while (parts.length) {
cur = parts.shift() + '.' + cur;
cur = `${parts.shift()}.${cur}`;
permutations.push(cur);

@@ -54,0 +68,0 @@ }

@@ -31,4 +31,4 @@ /*!

*/
'use strict';
var psl = require('psl');
"use strict";
const psl = require("psl");

@@ -35,0 +35,0 @@ function getPublicSuffix(domain) {

@@ -31,46 +31,47 @@ /*!

*/
'use strict';
"use strict";
/*jshint unused:false */
function Store() {
}
exports.Store = Store;
class Store {
constructor() {
this.synchronous = false;
}
// Stores may be synchronous, but are still required to use a
// Continuation-Passing Style API. The CookieJar itself will expose a "*Sync"
// API that converts from synchronous-callbacks to imperative style.
Store.prototype.synchronous = false;
findCookie(domain, path, key, cb) {
throw new Error("findCookie is not implemented");
}
Store.prototype.findCookie = function(domain, path, key, cb) {
throw new Error('findCookie is not implemented');
};
findCookies(domain, path, allowSpecialUseDomain, cb) {
throw new Error("findCookies is not implemented");
}
Store.prototype.findCookies = function(domain, path, cb) {
throw new Error('findCookies is not implemented');
};
putCookie(cookie, cb) {
throw new Error("putCookie is not implemented");
}
Store.prototype.putCookie = function(cookie, cb) {
throw new Error('putCookie is not implemented');
};
updateCookie(oldCookie, newCookie, cb) {
// recommended default implementation:
// return this.putCookie(newCookie, cb);
throw new Error("updateCookie is not implemented");
}
Store.prototype.updateCookie = function(oldCookie, newCookie, cb) {
// recommended default implementation:
// return this.putCookie(newCookie, cb);
throw new Error('updateCookie is not implemented');
};
removeCookie(domain, path, key, cb) {
throw new Error("removeCookie is not implemented");
}
Store.prototype.removeCookie = function(domain, path, key, cb) {
throw new Error('removeCookie is not implemented');
};
removeCookies(domain, path, cb) {
throw new Error("removeCookies is not implemented");
}
Store.prototype.removeCookies = function(domain, path, cb) {
throw new Error('removeCookies is not implemented');
};
removeAllCookies(cb) {
throw new Error("removeAllCookies is not implemented");
}
Store.prototype.removeAllCookies = function(cb) {
throw new Error('removeAllCookies is not implemented');
getAllCookies(cb) {
throw new Error(
"getAllCookies is not implemented (therefore jar cannot be serialized)"
);
}
}
Store.prototype.getAllCookies = function(cb) {
throw new Error('getAllCookies is not implemented (therefore jar cannot be serialized)');
};
exports.Store = Store;
// generated by genversion
module.exports = '3.0.1'
module.exports = '4.0.0'

@@ -9,6 +9,14 @@ {

{
"name": "Alexander Savin",
"website": "https://github.com/apsavin"
"name": "Ivan Nikulin",
"website": "https://github.com/inikulin"
},
{
"name": "Shivan Kaul Sahib",
"website": "https://github.com/ShivanKaul"
},
{
"name": "Clint Ruoho",
"website": "https://github.com/ruoho"
},
{
"name": "Ian Livingstone",

@@ -18,6 +26,26 @@ "website": "https://github.com/ianlivingstone"

{
"name": "Ivan Nikulin",
"website": "https://github.com/inikulin"
"name": "Andrew Waterman",
"website": "https://github.com/awaterma"
},
{
"name": "Michael de Libero ",
"website": "https://github.com/medelibero-sfdc"
},
{
"name": "Jonathan Stewmon",
"website": "https://github.com/jstewmon"
},
{
"name": "Miguel Roncancio",
"website": "https://github.com/miggs125"
},
{
"name": "Sebastian Mayr",
"website": "https://github.com/Sebmaster"
},
{
"name": "Alexander Savin",
"website": "https://github.com/apsavin"
},
{
"name": "Lalit Kapoor",

@@ -29,6 +57,2 @@ "website": "https://github.com/lalitkapoor"

"website": "https://github.com/sambthompson"
},
{
"name": "Sebastian Mayr",
"website": "https://github.com/Sebmaster"
}

@@ -49,3 +73,3 @@ ],

],
"version": "3.0.1",
"version": "4.0.0",
"homepage": "https://github.com/salesforce/tough-cookie",

@@ -66,3 +90,6 @@ "repository": {

"test": "vows test/*_test.js",
"cover": "nyc --reporter=lcov --reporter=html vows test/*_test.js"
"cover": "nyc --reporter=lcov --reporter=html vows test/*_test.js",
"eslint": "eslint --env node --ext .js .",
"prettier": "prettier '**/*.{json,ts,yaml,md}'",
"format": "npm run eslint -- --fix"
},

@@ -73,13 +100,16 @@ "engines": {

"devDependencies": {
"async": "^1.4.2",
"async": "^2.6.2",
"eslint": "^5.16.0",
"eslint-config-prettier": "^4.2.0",
"eslint-plugin-prettier": "^3.0.1",
"genversion": "^2.1.0",
"nyc": "^11.6.0",
"string.prototype.repeat": "^0.2.0",
"nyc": "^14.0.0",
"prettier": "^1.17.0",
"vows": "^0.8.2"
},
"dependencies": {
"ip-regex": "^2.1.0",
"psl": "^1.1.28",
"punycode": "^2.1.1"
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.1.2"
}
}

@@ -5,3 +5,3 @@ [RFC6265](https://tools.ietf.org/html/rfc6265) Cookies and CookieJar for Node.js

[![Build Status](https://travis-ci.org/salesforce/tough-cookie.png?branch=master)](https://travis-ci.org/salesforce/tough-cookie)
[![Build Status](https://travis-ci.org/salesforce/tough-cookie.svg?branch=master)](https://travis-ci.org/salesforce/tough-cookie)

@@ -153,2 +153,3 @@ # Synopsis

* _httpOnly_ - boolean - the `HttpOnly` cookie flag
* _sameSite_ - string - the `SameSite` cookie attribute (from [RFC6265bis]); must be one of `none`, `lax`, or `strict`
* _extensions_ - `Array` - any unrecognized cookie attributes as strings (even if equal-signs inside)

@@ -256,2 +257,4 @@ * _creation_ - `Date` - when this cookie was constructed

* _looseMode_ - boolean - default `false` - accept malformed cookies like `bar` and `=bar`, which have an implied empty name.
* _prefixSecurity_ - string - default `silent` - set to `'unsafe-disabled'`, `'silent'`, or `'strict'`. See [Cookie Prefixes] below.
* _allowSpecialUseDomain_ - boolean - default `false` - accepts special-use domain suffixes, such as `local`. Useful for testing purposes.
This is not in the standard, but is used sometimes on the web and is accepted by (most) browsers.

@@ -271,2 +274,3 @@

* _ignoreError_ - boolean - default `false` - silently ignore things like parse errors and invalid domains. `Store` errors aren't ignored by this option.
* _sameSiteContext_ - string - default unset - set to `'none'`, `'lax'`, or `'strict'` See [SameSite Cookies] below.

@@ -292,2 +296,3 @@ As per the RFC, the `.hostOnly` property is set if there was no "Domain=" parameter in the cookie string (or `.domain` was null on the Cookie object). The `.domain` property is set to the fully-qualified hostname of `currentUrl` in this case. Matching this cookie requires an exact hostname match (not a `domainMatch` as per usual).

* _allPaths_ - boolean - default `false` - if `true`, do not scope cookies by path. The default uses RFC-compliant path scoping. **Note**: may not be supported by the underlying store (the default `MemoryCookieStore` supports it).
* _sameSiteContext_ - string - default unset - Set this to `'none'`, `'lax'` or `'strict'` to enforce SameSite cookies upon retrival. See [SameSite Cookies] below.

@@ -499,2 +504,52 @@ The `.lastAccessed` property of the returned cookies will have been updated.

# RFC6265bis
Support for RFC6265bis revision 02 is being developed. Since this is a bit of an omnibus revision to the RFC6252, support is broken up into the functional areas.
## Leave Secure Cookies Alone
Not yet supported.
This change makes it so that if a cookie is sent from the server to the client with a `Secure` attribute, the channel must also be secure or the cookie is ignored.
## SameSite Cookies
Supported.
This change makes it possible for servers, and supporting clients, to mitigate certain types of CSRF attacks by disallowing `SameSite` cookies from being sent cross-origin.
On the Cookie object itself, you can get/set the `.sameSite` attribute, which will be serialized into the `SameSite=` cookie attribute. When unset or `undefined`, no `SameSite=` attribute will be serialized. The valid values of this attribute are `'none'`, `'lax'`, or `'strict'`. Other values will be serialized as-is.
When parsing cookies with a `SameSite` cookie attribute, values other than `'lax'` or `'strict'` are parsed as `'none'`. For example, `SomeCookie=SomeValue; SameSite=garbage` will parse so that `cookie.sameSite === 'none'`.
In order to support SameSite cookies, you must provide a `sameSiteContext` option to _both_ `setCookie` and `getCookies`. Valid values for this option are just like for the Cookie object, but have particular meanings:
1. `'strict'` mode - If the request is on the same "site for cookies" (see the RFC draft for what this means), pass this option to add a layer of defense against CSRF.
2. `'lax'` mode - If the request is from another site, _but_ is directly because of navigation by the user, e.g., `<link type=prefetch>` or `<a href="...">`, pass `sameSiteContext: 'lax'`.
3. `'none'` - Otherwise, pass `sameSiteContext: 'none'` (this indicates a cross-origin request).
4. unset/`undefined` - SameSite **will not** be enforced! This can be a valid use-case for when CSRF isn't in the threat model of the system being built.
It is highly recommended that you read RFC 6265bis for fine details on SameSite cookies. In particular [Section 8.8](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-02#section-8.8) discusses security considerations and defense in depth.
## Cookie Prefixes
Supported.
Cookie prefixes are a way to indicate that a given cookie was set with a set of attributes simply by inspecting the first few characters of the cookie's name.
Cookie prefixes are defined in [Section 4.1.3 of 6265bis](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.3). Two prefixes are defined:
1. `"__Secure-" Prefix`: If a cookie's name begins with a case-sensitive match for the string "__Secure-", then the cookie will have been set with a "Secure" attribute.
2. `"__Host-" Prefix`: If a cookie's name begins with a case-sensitive match for the string "__Host-", then the cookie will have been set with a "Secure" attribute, a "Path" attribute with a value of "/", and no "Domain" attribute.
If `prefixSecurity` is enabled for `CookieJar`, then cookies that match the prefixes defined above but do not obey the attribute restrictions will not be added.
You can define this functionality by passing in `prefixSecurity` option to `CookieJar`. It can be one of 3 values:
1. `silent`: Enable cookie prefix checking but silently fail to add the cookie if conditions not met. Default.
2. `strict`: Enable cookie prefix checking and error out if conditions not met.
3. `unsafe-disabled`: Disable cookie prefix checking.
Note that if `ignoreError` is passed in as `true` then the error will be silent regardless of `prefixSecurity` option (assuming it's enabled).
# Copyright and License

@@ -501,0 +556,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc