tough-cookie
Advanced tools
Comparing version 5.0.0-rc.1 to 5.0.0-rc.2
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.canonicalDomain = void 0; | ||
const punycode = __importStar(require("punycode/punycode.js")); | ||
const punycode_js_1 = require("punycode/punycode.js"); | ||
const constants_1 = require("./constants"); | ||
// S5.1.2 Canonicalized Host Names | ||
function canonicalDomain(str) { | ||
if (str == null) { | ||
return null; | ||
/** | ||
* Transforms a domain name into a canonical domain name. The canonical domain name is a domain name | ||
* that has been trimmed, lowercased, stripped of leading dot, and optionally punycode-encoded | ||
* ({@link https://www.rfc-editor.org/rfc/rfc6265.html#section-5.1.2 | Section 5.1.2 of RFC 6265}). For | ||
* the most part, this function is idempotent (calling the function with the output from a previous call | ||
* returns the same output). | ||
* | ||
* @remarks | ||
* A canonicalized host name is the string generated by the following | ||
* algorithm: | ||
* | ||
* 1. Convert the host name to a sequence of individual domain name | ||
* labels. | ||
* | ||
* 2. Convert each label that is not a Non-Reserved LDH (NR-LDH) label, | ||
* to an A-label (see Section 2.3.2.1 of [RFC5890] for the former | ||
* and latter), or to a "punycode label" (a label resulting from the | ||
* "ToASCII" conversion in Section 4 of [RFC3490]), as appropriate | ||
* (see Section 6.3 of this specification). | ||
* | ||
* 3. Concatenate the resulting labels, separated by a %x2E (".") | ||
* character. | ||
* | ||
* @example | ||
* ``` | ||
* canonicalDomain('.EXAMPLE.com') === 'example.com' | ||
* ``` | ||
* | ||
* @param domainName - the domain name to generate the canonical domain from | ||
* @public | ||
*/ | ||
function canonicalDomain(domainName) { | ||
if (domainName == null) { | ||
return undefined; | ||
} | ||
let _str = str.trim().replace(/^\./, ''); // S4.1.2.3 & S5.2.3: ignore leading . | ||
let _str = domainName.trim().replace(/^\./, ''); // S4.1.2.3 & S5.2.3: ignore leading . | ||
if (constants_1.IP_V6_REGEX_OBJECT.test(_str)) { | ||
@@ -41,3 +48,3 @@ _str = _str.replace('[', '').replace(']', ''); | ||
if (/[^\u0001-\u007f]/.test(_str)) { | ||
_str = punycode.toASCII(_str); | ||
_str = (0, punycode_js_1.toASCII)(_str); | ||
} | ||
@@ -44,0 +51,0 @@ return _str.toLowerCase(); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.IP_V6_REGEX_OBJECT = exports.PrefixSecurityEnum = void 0; | ||
/** | ||
* 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. These are defined in {@link https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13#section-4.1.3 | RFC6265bis - Section 4.1.3}. | ||
* | ||
* The following values can be used to configure how a {@link CookieJar} enforces attribute restrictions for Cookie prefixes: | ||
* | ||
* - `silent` - Enable cookie prefix checking but silently ignores the cookie if conditions are not met. This is the default configuration for a {@link CookieJar}. | ||
* | ||
* - `strict` - Enables cookie prefix checking and will raise an error if conditions are not met. | ||
* | ||
* - `unsafe-disabled` - Disables cookie prefix checking. | ||
* @public | ||
*/ | ||
exports.PrefixSecurityEnum = Object.freeze({ | ||
@@ -5,0 +18,0 @@ SILENT: 'silent', |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Cookie = void 0; | ||
/*! | ||
@@ -32,29 +57,2 @@ * Copyright (c) 2015-2020, Salesforce.com, Inc. | ||
*/ | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Cookie = void 0; | ||
// This file was too big before we added max-lines, and it's ongoing work to reduce its size. | ||
/* eslint max-lines: [1, 750] */ | ||
const getPublicSuffix_1 = require("../getPublicSuffix"); | ||
@@ -127,3 +125,3 @@ const validators = __importStar(require("../validators")); | ||
if (validators.isEmptyString(str) || !validators.isString(str)) { | ||
return null; | ||
return undefined; | ||
} | ||
@@ -268,3 +266,3 @@ str = str.trim(); | ||
if (!str || validators.isEmptyString(str)) { | ||
return null; | ||
return undefined; | ||
} | ||
@@ -277,3 +275,3 @@ let obj; | ||
catch (e) { | ||
return null; | ||
return undefined; | ||
} | ||
@@ -369,6 +367,29 @@ } | ||
}; | ||
/** | ||
* An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to a user's web browser. | ||
* It is defined in {@link https://www.rfc-editor.org/rfc/rfc6265.html | RFC6265}. | ||
* @public | ||
*/ | ||
class Cookie { | ||
/** | ||
* Create a new Cookie instance. | ||
* @public | ||
* @param options - The attributes to set on the cookie | ||
*/ | ||
constructor(options = {}) { | ||
Object.assign(this, cookieDefaults, options); | ||
this.creation = options.creation ?? cookieDefaults.creation ?? new Date(); | ||
this.key = options.key ?? cookieDefaults.key; | ||
this.value = options.value ?? cookieDefaults.value; | ||
this.expires = options.expires ?? cookieDefaults.expires; | ||
this.maxAge = options.maxAge ?? cookieDefaults.maxAge; | ||
this.domain = options.domain ?? cookieDefaults.domain; | ||
this.path = options.path ?? cookieDefaults.path; | ||
this.secure = options.secure ?? cookieDefaults.secure; | ||
this.httpOnly = options.httpOnly ?? cookieDefaults.httpOnly; | ||
this.extensions = options.extensions ?? cookieDefaults.extensions; | ||
this.creation = options.creation ?? cookieDefaults.creation; | ||
this.hostOnly = options.hostOnly ?? cookieDefaults.hostOnly; | ||
this.pathIsDefault = options.pathIsDefault ?? cookieDefaults.pathIsDefault; | ||
this.lastAccessed = options.lastAccessed ?? cookieDefaults.lastAccessed; | ||
this.sameSite = options.sameSite ?? cookieDefaults.sameSite; | ||
this.creation = options.creation ?? new Date(); | ||
// used to break creation ties in cookieCompare(): | ||
@@ -381,2 +402,4 @@ Object.defineProperty(this, 'creationIndex', { | ||
}); | ||
// Duplicate operation, but it makes TypeScript happy... | ||
this.creationIndex = Cookie.cookiesCreated; | ||
} | ||
@@ -387,9 +410,18 @@ [Symbol.for('nodejs.util.inspect.custom')]() { | ||
const createAge = this.creation && this.creation !== 'Infinity' | ||
? `${now - this.creation.getTime()}ms` | ||
? `${String(now - this.creation.getTime())}ms` | ||
: '?'; | ||
const accessAge = this.lastAccessed && this.lastAccessed !== 'Infinity' | ||
? `${now - this.lastAccessed.getTime()}ms` | ||
? `${String(now - this.lastAccessed.getTime())}ms` | ||
: '?'; | ||
return `Cookie="${this.toString()}; hostOnly=${hostOnly}; aAge=${accessAge}; cAge=${createAge}"`; | ||
} | ||
/** | ||
* For convenience in using `JSON.stringify(cookie)`. Returns a plain-old Object that can be JSON-serialized. | ||
* | ||
* @remarks | ||
* - Any `Date` properties (such as {@link Cookie.expires}, {@link Cookie.creation}, and {@link Cookie.lastAccessed}) are exported in ISO format (`Date.toISOString()`). | ||
* | ||
* - Custom Cookie properties are discarded. In tough-cookie 1.x, since there was no {@link Cookie.toJSON} method explicitly defined, all enumerable properties were captured. | ||
* If you want a property to be serialized, add the property name to {@link Cookie.serializableProperties}. | ||
*/ | ||
toJSON() { | ||
@@ -457,7 +489,20 @@ const obj = {}; | ||
} | ||
/** | ||
* Does a deep clone of this cookie, implemented exactly as `Cookie.fromJSON(cookie.toJSON())`. | ||
* @public | ||
*/ | ||
clone() { | ||
return fromJSON(this.toJSON()); | ||
} | ||
/** | ||
* Validates cookie attributes for semantic correctness. Useful for "lint" checking any `Set-Cookie` headers you generate. | ||
* For now, it returns a boolean, but eventually could return a reason string. | ||
* | ||
* @remarks | ||
* Works for a few things, but is by no means comprehensive. | ||
* | ||
* @beta | ||
*/ | ||
validate() { | ||
if (this.value == null || !COOKIE_OCTETS.test(this.value)) { | ||
if (!this.value || !COOKIE_OCTETS.test(this.value)) { | ||
return false; | ||
@@ -491,2 +536,11 @@ } | ||
} | ||
/** | ||
* Sets the 'Expires' attribute on a cookie. | ||
* | ||
* @remarks | ||
* When given a `string` value it will be parsed with {@link parseDate}. If the value can't be parsed as a cookie date | ||
* then the 'Expires' attribute will be set to `"Infinity"`. | ||
* | ||
* @param exp - the new value for the 'Expires' attribute of the cookie. | ||
*/ | ||
setExpires(exp) { | ||
@@ -500,2 +554,10 @@ if (exp instanceof Date) { | ||
} | ||
/** | ||
* Sets the 'Max-Age' attribute (in seconds) on a cookie. | ||
* | ||
* @remarks | ||
* Coerces `-Infinity` to `"-Infinity"` and `Infinity` to `"Infinity"` so it can be serialized to JSON. | ||
* | ||
* @param age - the new value for the 'Max-Age' attribute (in seconds). | ||
*/ | ||
setMaxAge(age) { | ||
@@ -512,4 +574,8 @@ if (age === Infinity) { | ||
} | ||
/** | ||
* Encodes to a `Cookie` header value (specifically, the {@link Cookie.key} and {@link Cookie.value} properties joined with "="). | ||
* @public | ||
*/ | ||
cookieString() { | ||
const val = this.value ?? ''; | ||
const val = this.value || ''; | ||
if (this.key) { | ||
@@ -520,3 +586,6 @@ return `${this.key}=${val}`; | ||
} | ||
// gives Set-Cookie header format | ||
/** | ||
* Encodes to a `Set-Cookie header` value. | ||
* @public | ||
*/ | ||
toString() { | ||
@@ -530,3 +599,3 @@ let str = this.cookieString(); | ||
if (this.maxAge != null && this.maxAge != Infinity) { | ||
str += `; Max-Age=${this.maxAge}`; | ||
str += `; Max-Age=${String(this.maxAge)}`; | ||
} | ||
@@ -565,12 +634,25 @@ if (this.domain && !this.hostOnly) { | ||
} | ||
// 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 | ||
/** | ||
* Computes the TTL relative to now (milliseconds). | ||
* | ||
* @remarks | ||
* - `Infinity` is returned for cookies without an explicit expiry | ||
* | ||
* - `0` is returned if the cookie is expired. | ||
* | ||
* - Otherwise a time-to-live in milliseconds is returned. | ||
* | ||
* @param now - passing an explicit value is mostly used for testing purposes since this defaults to the `Date.now()` | ||
* @public | ||
*/ | ||
TTL(now = Date.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) | ||
*/ | ||
// 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 | ||
// ----- | ||
// 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 && typeof this.maxAge === 'number') { | ||
@@ -585,5 +667,14 @@ return this.maxAge <= 0 ? 0 : this.maxAge * 1000; | ||
} | ||
// expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() | ||
// elsewhere) | ||
/** | ||
* Computes the absolute unix-epoch milliseconds that this cookie expires. | ||
* | ||
* The "Max-Age" attribute takes precedence over "Expires" (as per the RFC). The {@link Cookie.lastAccessed} attribute | ||
* (or the `now` parameter if given) is used to offset the {@link Cookie.maxAge} attribute. | ||
* | ||
* If Expires ({@link Cookie.expires}) is set, that's returned. | ||
* | ||
* @param now - can be used to provide a time offset (instead of {@link Cookie.lastAccessed}) to use when calculating the "Max-Age" value | ||
*/ | ||
expiryTime(now) { | ||
// expiryTime() replaces the "expiry-time" parts of S5.3 step 3 (setCookie() elsewhere) | ||
if (this.maxAge != null) { | ||
@@ -603,21 +694,94 @@ const relativeTo = now || this.lastAccessed || new Date(); | ||
} | ||
// This replaces the "persistent-flag" parts of S5.3 step 3 | ||
/** | ||
* Indicates if the cookie has been persisted to a store or not. | ||
* @public | ||
*/ | ||
isPersistent() { | ||
// This replaces the "persistent-flag" parts of S5.3 step 3 | ||
return this.maxAge != null || this.expires != 'Infinity'; | ||
} | ||
// Mostly S5.1.2 and S5.2.3: | ||
/** | ||
* Calls {@link canonicalDomain} with the {@link Cookie.domain} property. | ||
* @public | ||
*/ | ||
canonicalizedDomain() { | ||
if (this.domain == null) { | ||
return null; | ||
} | ||
// Mostly S5.1.2 and S5.2.3: | ||
return (0, canonicalDomain_1.canonicalDomain)(this.domain); | ||
} | ||
/** | ||
* Alias for {@link Cookie.canonicalizedDomain} | ||
* @public | ||
*/ | ||
cdomain() { | ||
return this.canonicalizedDomain(); | ||
return (0, canonicalDomain_1.canonicalDomain)(this.domain); | ||
} | ||
/** | ||
* Parses a string into a Cookie object. | ||
* | ||
* @remarks | ||
* Note: when parsing a `Cookie` header it must be split by ';' before each Cookie string can be parsed. | ||
* | ||
* @example | ||
* ``` | ||
* // parse a `Set-Cookie` header | ||
* const setCookieHeader = 'a=bcd; Expires=Tue, 18 Oct 2011 07:05:03 GMT' | ||
* const cookie = Cookie.parse(setCookieHeader) | ||
* cookie.key === 'a' | ||
* cookie.value === 'bcd' | ||
* cookie.expires === new Date(Date.parse('Tue, 18 Oct 2011 07:05:03 GMT')) | ||
* ``` | ||
* | ||
* @example | ||
* ``` | ||
* // parse a `Cookie` header | ||
* const cookieHeader = 'name=value; name2=value2; name3=value3' | ||
* const cookies = cookieHeader.split(';').map(Cookie.parse) | ||
* cookies[0].name === 'name' | ||
* cookies[0].value === 'value' | ||
* cookies[1].name === 'name2' | ||
* cookies[1].value === 'value2' | ||
* cookies[2].name === 'name3' | ||
* cookies[2].value === 'value3' | ||
* ``` | ||
* | ||
* @param str - The `Set-Cookie` header or a Cookie string to parse. | ||
* @param options - Configures `strict` or `loose` mode for cookie parsing | ||
*/ | ||
static parse(str, options) { | ||
return parse(str, options); | ||
} | ||
/** | ||
* Does the reverse of {@link Cookie.toJSON}. | ||
* | ||
* @remarks | ||
* Any Date properties (such as .expires, .creation, and .lastAccessed) are parsed via Date.parse, not tough-cookie's parseDate, since ISO timestamps are being handled at this layer. | ||
* | ||
* @example | ||
* ``` | ||
* const json = JSON.stringify({ | ||
* key: 'alpha', | ||
* value: 'beta', | ||
* domain: 'example.com', | ||
* path: '/foo', | ||
* expires: '2038-01-19T03:14:07.000Z', | ||
* }) | ||
* const cookie = Cookie.fromJSON(json) | ||
* cookie.key === 'alpha' | ||
* cookie.value === 'beta' | ||
* cookie.domain === 'example.com' | ||
* cookie.path === '/foo' | ||
* cookie.expires === new Date(Date.parse('2038-01-19T03:14:07.000Z')) | ||
* ``` | ||
* | ||
* @param str - An unparsed JSON string or a value that has already been parsed as JSON | ||
*/ | ||
static fromJSON(str) { | ||
return fromJSON(str); | ||
} | ||
} | ||
exports.Cookie = Cookie; | ||
Cookie.parse = parse; | ||
Cookie.fromJSON = fromJSON; | ||
Cookie.cookiesCreated = 0; | ||
/** | ||
* @internal | ||
*/ | ||
Cookie.sameSiteLevel = { | ||
@@ -628,2 +792,5 @@ strict: 3, | ||
}; | ||
/** | ||
* @internal | ||
*/ | ||
Cookie.sameSiteCanonical = { | ||
@@ -633,2 +800,6 @@ strict: 'Strict', | ||
}; | ||
/** | ||
* Cookie properties that will be serialized when using {@link Cookie.fromJSON} and {@link Cookie.toJSON}. | ||
* @public | ||
*/ | ||
Cookie.serializableProperties = [ | ||
@@ -635,0 +806,0 @@ 'key', |
@@ -29,10 +29,2 @@ "use strict"; | ||
const validators = __importStar(require("../validators")); | ||
/* Section 5.4 part 2: | ||
* "* Cookies with longer paths are listed before cookies with | ||
* shorter paths. | ||
* | ||
* * Among cookies that have equal-length path fields, cookies with | ||
* earlier creation-times are listed before cookies with later | ||
* creation-times." | ||
*/ | ||
/** | ||
@@ -43,3 +35,58 @@ * The maximum timestamp a cookie, in milliseconds. The value is (2^31 - 1) seconds since the Unix | ||
const MAX_TIME = 2147483647000; | ||
/** Compares two cookies for sorting. */ | ||
/** | ||
* A comparison function that can be used with {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort | Array.sort()}, | ||
* which orders a list of cookies into the recommended order given in Step 2 of {@link https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4 | RFC6265 - Section 5.4}. | ||
* | ||
* The sort algorithm is, in order of precedence: | ||
* | ||
* - Longest {@link Cookie.path} | ||
* | ||
* - Oldest {@link Cookie.creation} (which has a 1-ms precision, same as Date) | ||
* | ||
* - Lowest {@link Cookie.creationIndex} (to get beyond the 1-ms precision) | ||
* | ||
* @remarks | ||
* ### RFC6265 - Section 5.4 - Step 2 | ||
* | ||
* The user agent SHOULD sort the cookie-list in the following order: | ||
* | ||
* - Cookies with longer paths are listed before cookies with shorter paths. | ||
* | ||
* - Among cookies that have equal-length path fields, cookies with | ||
* earlier creation-times are listed before cookies with later | ||
* creation-times. | ||
* | ||
* NOTE: Not all user agents sort the cookie-list in this order, but | ||
* this order reflects common practice when this document was | ||
* written, and, historically, there have been servers that | ||
* (erroneously) depended on this order. | ||
* | ||
* ### Custom Store Implementors | ||
* | ||
* Since the JavaScript Date is limited to a 1-ms precision, cookies within the same millisecond are entirely possible. | ||
* This is especially true when using the `now` option to `CookieJar.setCookie(...)`. The {@link Cookie.creationIndex} | ||
* property is a per-process global counter, assigned during construction with `new Cookie()`, which preserves the spirit | ||
* of the RFC sorting: older cookies go first. This works great for {@link MemoryCookieStore} since `Set-Cookie` headers | ||
* are parsed in order, but is not so great for distributed systems. | ||
* | ||
* Sophisticated Stores may wish to set this to some other | ||
* logical clock so that if cookies `A` and `B` are created in the same millisecond, but cookie `A` is created before | ||
* cookie `B`, then `A.creationIndex < B.creationIndex`. | ||
* | ||
* @example | ||
* ``` | ||
* const cookies = [ | ||
* new Cookie({ key: 'a', value: '' }), | ||
* new Cookie({ key: 'b', value: '' }), | ||
* new Cookie({ key: 'c', value: '', path: '/path' }), | ||
* new Cookie({ key: 'd', value: '', path: '/path' }), | ||
* ] | ||
* cookies.sort(cookieCompare) | ||
* // cookie sort order would be ['c', 'd', 'a', 'b'] | ||
* ``` | ||
* | ||
* @param a - the first Cookie for comparison | ||
* @param b - the second Cookie for comparison | ||
* @public | ||
*/ | ||
function cookieCompare(a, b) { | ||
@@ -64,5 +111,5 @@ validators.validate(validators.isObject(a), (0, utils_1.safeToString)(a)); | ||
// break ties for the same millisecond (precision of JavaScript's clock) | ||
cmp = (a.creationIndex ?? 0) - (b.creationIndex ?? 0); | ||
cmp = (a.creationIndex || 0) - (b.creationIndex || 0); | ||
return cmp; | ||
} | ||
exports.cookieCompare = cookieCompare; |
@@ -45,4 +45,2 @@ "use strict"; | ||
const version_1 = require("../version"); | ||
// This file was too big before we added max-lines, and it's ongoing work to reduce its size. | ||
/* eslint max-lines: [1, 1200] */ | ||
const defaultSetCookieOptions = { | ||
@@ -85,3 +83,3 @@ loose: false, | ||
else { | ||
return null; | ||
return undefined; | ||
} | ||
@@ -116,3 +114,3 @@ } | ||
return (!startsWithHostPrefix || | ||
(cookie.secure && | ||
Boolean(cookie.secure && | ||
cookie.hostOnly && | ||
@@ -123,16 +121,31 @@ cookie.path != null && | ||
function getNormalizedPrefixSecurity(prefixSecurity) { | ||
if (prefixSecurity != null) { | ||
const normalizedPrefixSecurity = prefixSecurity.toLowerCase(); | ||
/* The three supported options */ | ||
switch (normalizedPrefixSecurity) { | ||
case constants_1.PrefixSecurityEnum.STRICT: | ||
case constants_1.PrefixSecurityEnum.SILENT: | ||
case constants_1.PrefixSecurityEnum.DISABLED: | ||
return normalizedPrefixSecurity; | ||
} | ||
const normalizedPrefixSecurity = prefixSecurity.toLowerCase(); | ||
/* The three supported options */ | ||
switch (normalizedPrefixSecurity) { | ||
case constants_1.PrefixSecurityEnum.STRICT: | ||
case constants_1.PrefixSecurityEnum.SILENT: | ||
case constants_1.PrefixSecurityEnum.DISABLED: | ||
return normalizedPrefixSecurity; | ||
default: | ||
return constants_1.PrefixSecurityEnum.SILENT; | ||
} | ||
/* Default is SILENT */ | ||
return constants_1.PrefixSecurityEnum.SILENT; | ||
} | ||
/** | ||
* A CookieJar is for storage and retrieval of {@link Cookie} objects as defined in | ||
* {@link https://www.rfc-editor.org/rfc/rfc6265.html#section-5.3 | RFC6265 - Section 5.3}. | ||
* | ||
* It also supports a pluggable persistence layer via {@link Store}. | ||
* @public | ||
*/ | ||
class CookieJar { | ||
/** | ||
* Creates a new `CookieJar` instance. | ||
* | ||
* @remarks | ||
* - If a custom store is not passed to the constructor, an in-memory store ({@link MemoryCookieStore} will be created and used. | ||
* - If a boolean value is passed as the `options` parameter, this is equivalent to passing `{ rejectPublicSuffixes: <value> }` | ||
* | ||
* @param store - a custom {@link Store} implementation (defaults to {@link MemoryCookieStore}) | ||
* @param options - configures how cookies are processed by the cookie jar | ||
*/ | ||
constructor(store, options) { | ||
@@ -148,6 +161,3 @@ if (typeof options === 'boolean') { | ||
} | ||
callSync( | ||
// Using tuples is needed to check if `T` is `never` because `T extends never ? true : false` | ||
// evaluates to `never` instead of `true`. | ||
fn) { | ||
callSync(fn) { | ||
if (!this.store.synchronous) { | ||
@@ -162,7 +172,11 @@ throw new Error('CookieJar store is not synchronous; use async API instead.'); | ||
}); | ||
if (syncErr) { | ||
// These seem to be false positives; it can't detect that the value may be changed in the callback | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/only-throw-error | ||
if (syncErr) | ||
throw syncErr; | ||
} | ||
return syncResult; | ||
} | ||
/** | ||
* @internal No doc because this is the overload implementation | ||
*/ | ||
setCookie(cookie, url, options, callback) { | ||
@@ -193,3 +207,3 @@ if (typeof options === 'function') { | ||
} | ||
const host = (0, canonicalDomain_1.canonicalDomain)(context.hostname); | ||
const host = (0, canonicalDomain_1.canonicalDomain)(context.hostname) ?? null; | ||
const loose = options?.loose || this.enableLooseMode; | ||
@@ -276,3 +290,3 @@ let sameSiteContext = null; | ||
if (!cookie.path || cookie.path[0] !== '/') { | ||
cookie.path = (0, defaultPath_1.defaultPath)(context.pathname ?? undefined); | ||
cookie.path = (0, defaultPath_1.defaultPath)(context.pathname); | ||
cookie.pathIsDefault = true; | ||
@@ -285,3 +299,3 @@ } | ||
err = new Error("Cookie is HttpOnly and this isn't an HTTP API"); | ||
return options?.ignoreError | ||
return options.ignoreError | ||
? promiseCallback.resolve(undefined) | ||
@@ -330,2 +344,5 @@ : promiseCallback.reject(err); | ||
const store = this.store; | ||
// TODO: It feels weird to be manipulating the store as a side effect of a method. | ||
// We should either do it in the constructor or not at all. | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
if (!store.updateCookie) { | ||
@@ -336,3 +353,3 @@ store.updateCookie = async function (_oldCookie, newCookie, cb) { | ||
} | ||
function withCookie(err, oldCookie) { | ||
const withCookie = function withCookie(err, oldCookie) { | ||
if (err) { | ||
@@ -384,6 +401,27 @@ cb(err); | ||
} | ||
} | ||
}; | ||
// TODO: Refactor to avoid using a callback | ||
store.findCookie(cookie.domain, cookie.path, cookie.key, withCookie); | ||
return promiseCallback.promise; | ||
} | ||
/** | ||
* Synchronously attempt to set the {@link Cookie} in the {@link CookieJar}. | ||
* | ||
* <strong>Note:</strong> Only works if the configured {@link Store} is also synchronous. | ||
* | ||
* @remarks | ||
* - If successfully persisted, the {@link Cookie} will have updated | ||
* {@link Cookie.creation}, {@link Cookie.lastAccessed} and {@link Cookie.hostOnly} | ||
* properties. | ||
* | ||
* - As per the RFC, the {@link Cookie.hostOnly} flag is set if there was no `Domain={value}` | ||
* atttribute on the cookie string. The {@link Cookie.domain} property is set to the | ||
* fully-qualified hostname of `currentUrl` in this case. Matching this cookie requires an | ||
* exact hostname match (not a {@link domainMatch} as per usual) | ||
* | ||
* @param cookie - The cookie object or cookie string to store. A string value will be parsed into a cookie using {@link Cookie.parse}. | ||
* @param url - The domain to store the cookie with. | ||
* @param options - Configuration settings to use when storing the cookie. | ||
* @public | ||
*/ | ||
setCookieSync(cookie, url, options) { | ||
@@ -395,3 +433,7 @@ const setCookieFn = options | ||
} | ||
/** | ||
* @internal No doc because this is the overload implementation | ||
*/ | ||
getCookies(url, options, callback) { | ||
// RFC6365 S5.4 | ||
if (typeof options === 'function') { | ||
@@ -417,3 +459,3 @@ callback = options; | ||
let sameSiteLevel = 0; | ||
if (options?.sameSiteContext) { | ||
if (options.sameSiteContext) { | ||
const sameSiteContext = checkSameSiteContext(options.sameSiteContext); | ||
@@ -428,6 +470,6 @@ if (sameSiteContext == null) { | ||
} | ||
const http = options?.http ?? true; | ||
const http = options.http ?? true; | ||
const now = Date.now(); | ||
const expireCheck = options?.expire ?? true; | ||
const allPaths = options?.allPaths ?? false; | ||
const expireCheck = options.expire ?? true; | ||
const allPaths = options.allPaths ?? false; | ||
const store = this.store; | ||
@@ -515,5 +557,22 @@ function matchingCookie(c) { | ||
} | ||
/** | ||
* Synchronously retrieve the list of cookies that can be sent in a Cookie header for the | ||
* current URL. | ||
* | ||
* <strong>Note</strong>: Only works if the configured Store is also synchronous. | ||
* | ||
* @remarks | ||
* - The array of cookies returned will be sorted according to {@link cookieCompare}. | ||
* | ||
* - The {@link Cookie.lastAccessed} property will be updated on all returned cookies. | ||
* | ||
* @param url - The domain to store the cookie with. | ||
* @param options - Configuration settings to use when retrieving the cookies. | ||
*/ | ||
getCookiesSync(url, options) { | ||
return this.callSync(this.getCookies.bind(this, url, options)) ?? []; | ||
} | ||
/** | ||
* @internal No doc because this is the overload implementation | ||
*/ | ||
getCookieString(url, options, callback) { | ||
@@ -539,2 +598,11 @@ if (typeof options === 'function') { | ||
} | ||
/** | ||
* Synchronous version of `.getCookieString()`. Accepts the same options as `.getCookies()` but returns a string suitable for a | ||
* `Cookie` header rather than an Array. | ||
* | ||
* <strong>Note</strong>: Only works if the configured Store is also synchronous. | ||
* | ||
* @param url - The domain to store the cookie with. | ||
* @param options - Configuration settings to use when retrieving the cookies. | ||
*/ | ||
getCookieStringSync(url, options) { | ||
@@ -545,2 +613,5 @@ return (this.callSync(options | ||
} | ||
/** | ||
* @internal No doc because this is the overload implementation | ||
*/ | ||
getSetCookieStrings(url, options, callback) { | ||
@@ -556,7 +627,4 @@ if (typeof options === 'function') { | ||
} | ||
else if (cookies === undefined) { | ||
promiseCallback.callback(null, undefined); | ||
} | ||
else { | ||
promiseCallback.callback(null, cookies.map((c) => { | ||
promiseCallback.callback(null, cookies?.map((c) => { | ||
return c.toString(); | ||
@@ -569,5 +637,17 @@ })); | ||
} | ||
/** | ||
* Synchronous version of `.getSetCookieStrings()`. Returns an array of strings suitable for `Set-Cookie` headers. | ||
* Accepts the same options as `.getCookies()`. | ||
* | ||
* <strong>Note</strong>: Only works if the configured Store is also synchronous. | ||
* | ||
* @param url - The domain to store the cookie with. | ||
* @param options - Configuration settings to use when retrieving the cookies. | ||
*/ | ||
getSetCookieStringsSync(url, options = {}) { | ||
return (this.callSync(this.getSetCookieStrings.bind(this, url, options)) ?? []); | ||
} | ||
/** | ||
* @internal No doc because this is the overload implementation | ||
*/ | ||
serialize(callback) { | ||
@@ -597,4 +677,3 @@ const promiseCallback = (0, utils_1.createPromiseCallback)(callback); | ||
}; | ||
if (!(this.store.getAllCookies && | ||
typeof this.store.getAllCookies === 'function')) { | ||
if (typeof this.store.getAllCookies !== 'function') { | ||
return promiseCallback.reject(new Error('store does not support getAllCookies and cannot be serialized')); | ||
@@ -622,9 +701,23 @@ } | ||
} | ||
/** | ||
* Serialize the CookieJar if the underlying store supports `.getAllCookies`. | ||
* | ||
* <strong>Note</strong>: Only works if the configured Store is also synchronous. | ||
*/ | ||
serializeSync() { | ||
return this.callSync((callback) => this.serialize(callback)); | ||
return this.callSync((callback) => { | ||
this.serialize(callback); | ||
}); | ||
} | ||
/** | ||
* Alias of {@link CookieJar.serializeSync}. Allows the cookie to be serialized | ||
* with `JSON.stringify(cookieJar)`. | ||
*/ | ||
toJSON() { | ||
return this.serializeSync(); | ||
} | ||
// use the class method CookieJar.deserialize instead of calling this directly | ||
/** | ||
* Use the class method CookieJar.deserialize instead of calling this directly | ||
* @internal | ||
*/ | ||
_importCookies(serialized, callback) { | ||
@@ -639,3 +732,4 @@ let cookies = undefined; | ||
if (!cookies) { | ||
return callback(new Error('serialized jar has no cookies array'), undefined); | ||
callback(new Error('serialized jar has no cookies array'), undefined); | ||
return; | ||
} | ||
@@ -645,7 +739,9 @@ cookies = cookies.slice(); // do not modify the original | ||
if (err) { | ||
return callback(err, undefined); | ||
callback(err, undefined); | ||
return; | ||
} | ||
if (Array.isArray(cookies)) { | ||
if (!cookies.length) { | ||
return callback(err, this); | ||
callback(err, this); | ||
return; | ||
} | ||
@@ -657,6 +753,8 @@ let cookie; | ||
catch (e) { | ||
return callback(e instanceof Error ? e : new Error(), undefined); | ||
callback(e instanceof Error ? e : new Error(), undefined); | ||
return; | ||
} | ||
if (cookie === null) { | ||
return putNext(null); // skip this cookie | ||
if (cookie === undefined) { | ||
putNext(null); // skip this cookie | ||
return; | ||
} | ||
@@ -668,5 +766,11 @@ this.store.putCookie(cookie, putNext); | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
_importCookiesSync(serialized) { | ||
this.callSync(this._importCookies.bind(this, serialized)); | ||
} | ||
/** | ||
* @internal No doc because this is the overload implementation | ||
*/ | ||
clone(newStore, callback) { | ||
@@ -687,2 +791,5 @@ if (typeof newStore === 'function') { | ||
} | ||
/** | ||
* @internal | ||
*/ | ||
_cloneSync(newStore) { | ||
@@ -692,4 +799,21 @@ const cloneFn = newStore && typeof newStore !== 'function' | ||
: this.clone.bind(this); | ||
return this.callSync((callback) => cloneFn(callback)); | ||
return this.callSync((callback) => { | ||
cloneFn(callback); | ||
}); | ||
} | ||
/** | ||
* Produces a deep clone of this CookieJar. Modifications to the original do | ||
* not affect the clone, and vice versa. | ||
* | ||
* <strong>Note</strong>: Only works if both the configured Store and destination | ||
* Store are synchronous. | ||
* | ||
* @remarks | ||
* - When no {@link Store} is provided, a new {@link MemoryCookieStore} will be used. | ||
* | ||
* - Transferring between store types is supported so long as the source | ||
* implements `.getAllCookies()` and the destination implements `.putCookie()`. | ||
* | ||
* @param newStore - The target {@link Store} to clone cookies into. | ||
*/ | ||
cloneSync(newStore) { | ||
@@ -704,2 +828,5 @@ if (!newStore) { | ||
} | ||
/** | ||
* @internal No doc because this is the overload implementation | ||
*/ | ||
removeAllCookies(callback) { | ||
@@ -716,3 +843,3 @@ const promiseCallback = (0, utils_1.createPromiseCallback)(callback); | ||
// standard implementation `cb = (err, result) => {}`, they're essentially the same. | ||
void store.removeAllCookies(cb); | ||
store.removeAllCookies(cb); | ||
return promiseCallback.promise; | ||
@@ -734,3 +861,4 @@ } | ||
const removeErrors = []; | ||
function removeCookieCb(removeErr) { | ||
// TODO: Refactor to avoid using callback | ||
const removeCookieCb = function removeCookieCb(removeErr) { | ||
if (removeErr) { | ||
@@ -747,3 +875,3 @@ removeErrors.push(removeErr); | ||
} | ||
} | ||
}; | ||
cookies.forEach((cookie) => { | ||
@@ -755,7 +883,27 @@ store.removeCookie(cookie.domain, cookie.path, cookie.key, removeCookieCb); | ||
} | ||
/** | ||
* Removes all cookies from the CookieJar. | ||
* | ||
* <strong>Note</strong>: Only works if the configured Store is also synchronous. | ||
* | ||
* @remarks | ||
* - This is a new backwards-compatible feature of tough-cookie version 2.5, | ||
* so not all Stores will implement it efficiently. For Stores that do not | ||
* implement `removeAllCookies`, the fallback is to call `removeCookie` after | ||
* `getAllCookies`. | ||
* | ||
* - If `getAllCookies` fails or isn't implemented in the Store, an error is returned. | ||
* | ||
* - If one or more of the `removeCookie` calls fail, only the first error is returned. | ||
*/ | ||
removeAllCookiesSync() { | ||
return this.callSync((callback) => { | ||
return this.removeAllCookies(callback); | ||
this.callSync((callback) => { | ||
// `Callback<undefined>` and `ErrorCallback` are *technically* incompatible, but for the | ||
// standard implementation `cb = (err, result) => {}`, they're essentially the same. | ||
this.removeAllCookies(callback); | ||
}); | ||
} | ||
/** | ||
* @internal No doc because this is the overload implementation | ||
*/ | ||
static deserialize(strOrObj, store, callback) { | ||
@@ -809,2 +957,17 @@ if (typeof store === 'function') { | ||
} | ||
/** | ||
* A new CookieJar is created and the serialized {@link Cookie} values are added to | ||
* the underlying store. Each {@link Cookie} is added via `store.putCookie(...)` in | ||
* the order in which they appear in the serialization. | ||
* | ||
* <strong>Note</strong>: Only works if the configured Store is also synchronous. | ||
* | ||
* @remarks | ||
* - When no {@link Store} is provided, a new {@link MemoryCookieStore} will be used. | ||
* | ||
* - As a convenience, if `strOrObj` is a string, it is passed through `JSON.parse` first. | ||
* | ||
* @param strOrObj - A JSON string or object representing the deserialized cookies. | ||
* @param store - The underlying store to persist the deserialized cookies into. | ||
*/ | ||
static deserializeSync(strOrObj, store) { | ||
@@ -840,2 +1003,13 @@ const serialized = typeof strOrObj === 'string' ? JSON.parse(strOrObj) : strOrObj; | ||
} | ||
/** | ||
* Alias of {@link CookieJar.deserializeSync}. | ||
* | ||
* @remarks | ||
* - When no {@link Store} is provided, a new {@link MemoryCookieStore} will be used. | ||
* | ||
* - As a convenience, if `strOrObj` is a string, it is passed through `JSON.parse` first. | ||
* | ||
* @param jsonString - A JSON string or object representing the deserialized cookies. | ||
* @param store - The underlying store to persist the deserialized cookies into. | ||
*/ | ||
static fromJSON(jsonString, store) { | ||
@@ -842,0 +1016,0 @@ return CookieJar.deserializeSync(jsonString, store); |
"use strict"; | ||
// RFC6265 S5.1.4 Paths and Path-Match | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defaultPath = void 0; | ||
/* | ||
* "The user agent MUST use an algorithm equivalent to the following algorithm | ||
* to compute the default-path of a cookie:" | ||
/** | ||
* Given a current request/response path, gives the path appropriate for storing | ||
* in a cookie. This is basically the "directory" of a "file" in the path, but | ||
* is specified by {@link https://www.rfc-editor.org/rfc/rfc6265.html#section-5.1.4 | RFC6265 - Section 5.1.4}. | ||
* | ||
* Assumption: the path (and not query part or absolute uri) is passed in. | ||
* @remarks | ||
* ### RFC6265 - Section 5.1.4 | ||
* | ||
* The user agent MUST use an algorithm equivalent to the following algorithm to compute the default-path of a cookie: | ||
* | ||
* 1. Let uri-path be the path portion of the request-uri if such a | ||
* portion exists (and empty otherwise). For example, if the | ||
* request-uri contains just a path (and optional query string), | ||
* then the uri-path is that path (without the %x3F ("?") character | ||
* or query string), and if the request-uri contains a full | ||
* absoluteURI, the uri-path is the path component of that URI. | ||
* | ||
* 2. If the uri-path is empty or if the first character of the uri- | ||
* path is not a %x2F ("/") character, output %x2F ("/") and skip | ||
* the remaining steps. | ||
* | ||
* 3. If the uri-path contains no more than one %x2F ("/") character, | ||
* output %x2F ("/") and skip the remaining step. | ||
* | ||
* 4. Output the characters of the uri-path from the first character up | ||
* to, but not including, the right-most %x2F ("/"). | ||
* | ||
* @example | ||
* ``` | ||
* defaultPath('') === '/' | ||
* defaultPath('/some-path') === '/' | ||
* defaultPath('/some-parent-path/some-path') === '/some-parent-path' | ||
* defaultPath('relative-path') === '/' | ||
* ``` | ||
* | ||
* @param path - the path portion of the request-uri (excluding the hostname, query, fragment, and so on) | ||
* @public | ||
*/ | ||
@@ -11,0 +42,0 @@ function defaultPath(path) { |
@@ -10,6 +10,41 @@ "use strict"; | ||
const 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}|:)))$)/; | ||
// S5.1.3 Domain Matching | ||
function domainMatch(str, domStr, canonicalize) { | ||
if (str == null || domStr == null) { | ||
return null; | ||
/** | ||
* Answers "does this real domain match the domain in a cookie?". The `domain` is the "current" domain name and the | ||
* `cookieDomain` is the "cookie" domain name. Matches according to {@link https://www.rfc-editor.org/rfc/rfc6265.html#section-5.1.3 | RFC6265 - Section 5.1.3}, | ||
* but it helps to think of it as a "suffix match". | ||
* | ||
* @remarks | ||
* ### 5.1.3. Domain Matching | ||
* | ||
* A string domain-matches a given domain string if at least one of the | ||
* following conditions hold: | ||
* | ||
* - The domain string and the string are identical. (Note that both | ||
* the domain string and the string will have been canonicalized to | ||
* lower case at this point.) | ||
* | ||
* - All of the following conditions hold: | ||
* | ||
* - The domain string is a suffix of the string. | ||
* | ||
* - The last character of the string that is not included in the | ||
* domain string is a %x2E (".") character. | ||
* | ||
* - The string is a host name (i.e., not an IP address). | ||
* | ||
* @example | ||
* ``` | ||
* domainMatch('example.com', 'example.com') === true | ||
* domainMatch('eXaMpLe.cOm', 'ExAmPlE.CoM') === true | ||
* domainMatch('no.ca', 'yes.ca') === false | ||
* ``` | ||
* | ||
* @param domain - The domain string to test | ||
* @param cookieDomain - The cookie domain string to match against | ||
* @param canonicalize - The canonicalize parameter toggles whether the domain parameters get normalized with canonicalDomain or not | ||
* @public | ||
*/ | ||
function domainMatch(domain, cookieDomain, canonicalize) { | ||
if (domain == null || cookieDomain == null) { | ||
return undefined; | ||
} | ||
@@ -19,11 +54,11 @@ let _str; | ||
if (canonicalize !== false) { | ||
_str = (0, canonicalDomain_1.canonicalDomain)(str); | ||
_domStr = (0, canonicalDomain_1.canonicalDomain)(domStr); | ||
_str = (0, canonicalDomain_1.canonicalDomain)(domain); | ||
_domStr = (0, canonicalDomain_1.canonicalDomain)(cookieDomain); | ||
} | ||
else { | ||
_str = str; | ||
_domStr = domStr; | ||
_str = domain; | ||
_domStr = cookieDomain; | ||
} | ||
if (_str == null || _domStr == null) { | ||
return null; | ||
return undefined; | ||
} | ||
@@ -44,3 +79,3 @@ /* | ||
/* "* The domain string is a suffix of the string" */ | ||
const idx = _str.lastIndexOf(domStr); | ||
const idx = _str.lastIndexOf(cookieDomain); | ||
if (idx <= 0) { | ||
@@ -47,0 +82,0 @@ return false; // it's a non-match (-1) or prefix (0) |
@@ -29,3 +29,16 @@ "use strict"; | ||
const utils_1 = require("../utils"); | ||
/** Converts a Date to a UTC string representation. */ | ||
/** | ||
* Format a {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date | Date} into | ||
* the {@link https://www.rfc-editor.org/rfc/rfc2616#section-3.3.1 | preferred Internet standard format} | ||
* defined in {@link https://www.rfc-editor.org/rfc/rfc822#section-5 | RFC822} and | ||
* updated in {@link https://www.rfc-editor.org/rfc/rfc1123#page-55 | RFC1123}. | ||
* | ||
* @example | ||
* ``` | ||
* formatDate(new Date(0)) === 'Thu, 01 Jan 1970 00:00:00 GMT` | ||
* ``` | ||
* | ||
* @param date - the date value to format | ||
* @public | ||
*/ | ||
function formatDate(date) { | ||
@@ -32,0 +45,0 @@ validators.validate(validators.isDate(date), (0, utils_1.safeToString)(date)); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.fromJSON = exports.permutePath = exports.parseDate = exports.formatDate = exports.domainMatch = exports.defaultPath = exports.CookieJar = exports.cookieCompare = exports.Cookie = exports.PrefixSecurityEnum = exports.canonicalDomain = exports.version = exports.ParameterError = exports.Store = exports.getPublicSuffix = exports.permuteDomain = exports.pathMatch = exports.MemoryCookieStore = void 0; | ||
exports.fromJSON = exports.parse = exports.permutePath = exports.parseDate = exports.formatDate = exports.domainMatch = exports.defaultPath = exports.CookieJar = exports.cookieCompare = exports.Cookie = exports.PrefixSecurityEnum = exports.canonicalDomain = exports.version = exports.ParameterError = exports.Store = exports.getPublicSuffix = exports.permuteDomain = exports.pathMatch = exports.MemoryCookieStore = void 0; | ||
var memstore_1 = require("../memstore"); | ||
@@ -18,2 +18,3 @@ Object.defineProperty(exports, "MemoryCookieStore", { enumerable: true, get: function () { return memstore_1.MemoryCookieStore; } }); | ||
Object.defineProperty(exports, "version", { enumerable: true, get: function () { return version_1.version; } }); | ||
var utils_1 = require("../utils"); | ||
var canonicalDomain_1 = require("./canonicalDomain"); | ||
@@ -40,2 +41,17 @@ Object.defineProperty(exports, "canonicalDomain", { enumerable: true, get: function () { return canonicalDomain_1.canonicalDomain; } }); | ||
const cookie_2 = require("./cookie"); | ||
exports.fromJSON = cookie_2.Cookie.fromJSON; | ||
/** | ||
* {@inheritDoc Cookie.parse} | ||
* @public | ||
*/ | ||
function parse(str, options) { | ||
return cookie_2.Cookie.parse(str, options); | ||
} | ||
exports.parse = parse; | ||
/** | ||
* {@inheritDoc Cookie.fromJSON} | ||
* @public | ||
*/ | ||
function fromJSON(str) { | ||
return cookie_2.Cookie.fromJSON(str); | ||
} | ||
exports.fromJSON = fromJSON; |
"use strict"; | ||
// date-time parsing constants (RFC6265 S5.1.1) | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseDate = void 0; | ||
// date-time parsing constants (RFC6265 S5.1.1) | ||
// eslint-disable-next-line no-control-regex | ||
@@ -43,6 +43,6 @@ const DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/; | ||
if (count < minDigits || count > maxDigits) { | ||
return null; | ||
return; | ||
} | ||
if (!trailingOK && count != token.length) { | ||
return null; | ||
return; | ||
} | ||
@@ -60,3 +60,3 @@ return parseInt(token.slice(0, count), 10); | ||
if (parts.length !== 3) { | ||
return null; | ||
return; | ||
} | ||
@@ -69,8 +69,8 @@ for (let i = 0; i < 3; i++) { | ||
const numPart = parts[i]; | ||
if (numPart == null) { | ||
return null; | ||
if (numPart === undefined) { | ||
return; | ||
} | ||
const num = parseDigits(numPart, 1, 2, trailingOK); | ||
if (num === null) { | ||
return null; | ||
if (num === undefined) { | ||
return; | ||
} | ||
@@ -109,11 +109,109 @@ result[i] = num; | ||
default: | ||
return null; | ||
return; | ||
} | ||
} | ||
/* | ||
* RFC6265 S5.1.1 date parser (see RFC for full grammar) | ||
/** | ||
* Parse a cookie date string into a {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date | Date}. Parses according to | ||
* {@link https://www.rfc-editor.org/rfc/rfc6265.html#section-5.1.1 | RFC6265 - Section 5.1.1}, not | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse | Date.parse()}. | ||
* | ||
* @remarks | ||
* | ||
* ### RFC6265 - 5.1.1. Dates | ||
* | ||
* The user agent MUST use an algorithm equivalent to the following | ||
* algorithm to parse a cookie-date. Note that the various boolean | ||
* flags defined as a part of the algorithm (i.e., found-time, found- | ||
* day-of-month, found-month, found-year) are initially "not set". | ||
* | ||
* 1. Using the grammar below, divide the cookie-date into date-tokens. | ||
* | ||
* ``` | ||
* cookie-date = *delimiter date-token-list *delimiter | ||
* date-token-list = date-token *( 1*delimiter date-token ) | ||
* date-token = 1*non-delimiter | ||
* | ||
* delimiter = %x09 / %x20-2F / %x3B-40 / %x5B-60 / %x7B-7E | ||
* non-delimiter = %x00-08 / %x0A-1F / DIGIT / ":" / ALPHA / %x7F-FF | ||
* non-digit = %x00-2F / %x3A-FF | ||
* | ||
* day-of-month = 1*2DIGIT ( non-digit *OCTET ) | ||
* month = ( "jan" / "feb" / "mar" / "apr" / | ||
* "may" / "jun" / "jul" / "aug" / | ||
* "sep" / "oct" / "nov" / "dec" ) *OCTET | ||
* year = 2*4DIGIT ( non-digit *OCTET ) | ||
* time = hms-time ( non-digit *OCTET ) | ||
* hms-time = time-field ":" time-field ":" time-field | ||
* time-field = 1*2DIGIT | ||
* ``` | ||
* | ||
* 2. Process each date-token sequentially in the order the date-tokens | ||
* appear in the cookie-date: | ||
* | ||
* 1. If the found-time flag is not set and the token matches the | ||
* time production, set the found-time flag and set the hour- | ||
* value, minute-value, and second-value to the numbers denoted | ||
* by the digits in the date-token, respectively. Skip the | ||
* remaining sub-steps and continue to the next date-token. | ||
* | ||
* 2. If the found-day-of-month flag is not set and the date-token | ||
* matches the day-of-month production, set the found-day-of- | ||
* month flag and set the day-of-month-value to the number | ||
* denoted by the date-token. Skip the remaining sub-steps and | ||
* continue to the next date-token. | ||
* | ||
* 3. If the found-month flag is not set and the date-token matches | ||
* the month production, set the found-month flag and set the | ||
* month-value to the month denoted by the date-token. Skip the | ||
* remaining sub-steps and continue to the next date-token. | ||
* | ||
* 4. If the found-year flag is not set and the date-token matches | ||
* the year production, set the found-year flag and set the | ||
* year-value to the number denoted by the date-token. Skip the | ||
* remaining sub-steps and continue to the next date-token. | ||
* | ||
* 3. If the year-value is greater than or equal to 70 and less than or | ||
* equal to 99, increment the year-value by 1900. | ||
* | ||
* 4. If the year-value is greater than or equal to 0 and less than or | ||
* equal to 69, increment the year-value by 2000. | ||
* | ||
* 1. NOTE: Some existing user agents interpret two-digit years differently. | ||
* | ||
* 5. Abort these steps and fail to parse the cookie-date if: | ||
* | ||
* - at least one of the found-day-of-month, found-month, found- | ||
* year, or found-time flags is not set, | ||
* | ||
* - the day-of-month-value is less than 1 or greater than 31, | ||
* | ||
* - the year-value is less than 1601, | ||
* | ||
* - the hour-value is greater than 23, | ||
* | ||
* - the minute-value is greater than 59, or | ||
* | ||
* - the second-value is greater than 59. | ||
* | ||
* (Note that leap seconds cannot be represented in this syntax.) | ||
* | ||
* 6. Let the parsed-cookie-date be the date whose day-of-month, month, | ||
* year, hour, minute, and second (in UTC) are the day-of-month- | ||
* value, the month-value, the year-value, the hour-value, the | ||
* minute-value, and the second-value, respectively. If no such | ||
* date exists, abort these steps and fail to parse the cookie-date. | ||
* | ||
* 7. Return the parsed-cookie-date as the result of this algorithm. | ||
* | ||
* @example | ||
* ``` | ||
* parseDate('Wed, 09 Jun 2021 10:18:14 GMT') | ||
* ``` | ||
* | ||
* @param cookieDate - the cookie date string | ||
* @public | ||
*/ | ||
function parseDate(str) { | ||
if (!str) { | ||
return undefined; | ||
function parseDate(cookieDate) { | ||
if (!cookieDate) { | ||
return; | ||
} | ||
@@ -124,12 +222,9 @@ /* RFC6265 S5.1.1: | ||
*/ | ||
const tokens = str.split(DATE_DELIM); | ||
if (!tokens) { | ||
return undefined; | ||
} | ||
let hour = null; | ||
let minute = null; | ||
let second = null; | ||
let dayOfMonth = null; | ||
let month = null; | ||
let year = null; | ||
const tokens = cookieDate.split(DATE_DELIM); | ||
let hour; | ||
let minute; | ||
let second; | ||
let dayOfMonth; | ||
let month; | ||
let year; | ||
for (let i = 0; i < tokens.length; i++) { | ||
@@ -140,3 +235,2 @@ const token = (tokens[i] ?? '').trim(); | ||
} | ||
let result; | ||
/* 2.1. If the found-time flag is not set and the token matches the time | ||
@@ -148,4 +242,4 @@ * production, set the found-time flag and set the hour- value, | ||
*/ | ||
if (second === null) { | ||
result = parseTime(token); | ||
if (second === undefined) { | ||
const result = parseTime(token); | ||
if (result) { | ||
@@ -163,6 +257,6 @@ hour = result[0]; | ||
*/ | ||
if (dayOfMonth === null) { | ||
if (dayOfMonth === undefined) { | ||
// "day-of-month = 1*2DIGIT ( non-digit *OCTET )" | ||
result = parseDigits(token, 1, 2, true); | ||
if (result !== null) { | ||
const result = parseDigits(token, 1, 2, true); | ||
if (result !== undefined) { | ||
dayOfMonth = result; | ||
@@ -177,5 +271,5 @@ continue; | ||
*/ | ||
if (month === null) { | ||
result = parseMonth(token); | ||
if (result !== null) { | ||
if (month === undefined) { | ||
const result = parseMonth(token); | ||
if (result !== undefined) { | ||
month = result; | ||
@@ -190,6 +284,6 @@ continue; | ||
*/ | ||
if (year === null) { | ||
if (year === undefined) { | ||
// "year = 2*4DIGIT ( non-digit *OCTET )" | ||
result = parseDigits(token, 2, 4, true); | ||
if (result !== null) { | ||
const result = parseDigits(token, 2, 4, true); | ||
if (result !== undefined) { | ||
year = result; | ||
@@ -224,8 +318,8 @@ /* From S5.1.1: | ||
*/ | ||
if (dayOfMonth === null || | ||
month == null || | ||
year == null || | ||
hour == null || | ||
minute == null || | ||
second == null || | ||
if (dayOfMonth === undefined || | ||
month === undefined || | ||
year === undefined || | ||
hour === undefined || | ||
minute === undefined || | ||
second === undefined || | ||
dayOfMonth < 1 || | ||
@@ -237,3 +331,3 @@ dayOfMonth > 31 || | ||
second > 59) { | ||
return undefined; | ||
return; | ||
} | ||
@@ -240,0 +334,0 @@ return new Date(Date.UTC(year, month, dayOfMonth, hour, minute, second)); |
@@ -29,4 +29,13 @@ "use strict"; | ||
/** | ||
* Gives the permutation of all possible `pathMatch`es of a given path. The | ||
* array is in longest-to-shortest order. Handy for indexing. | ||
* Generates the permutation of all possible values that {@link pathMatch} the `path` parameter. | ||
* The array is in longest-to-shortest order. Useful when building custom {@link Store} implementations. | ||
* | ||
* @example | ||
* ``` | ||
* permutePath('/foo/bar/') | ||
* // ['/foo/bar/', '/foo/bar', '/foo', '/'] | ||
* ``` | ||
* | ||
* @param path - the path to generate permutations for | ||
* @public | ||
*/ | ||
@@ -33,0 +42,0 @@ function permutePath(path) { |
@@ -1,32 +0,2 @@ | ||
/*! | ||
* Copyright (c) 2018, Salesforce.com, Inc. | ||
* All rights reserved. | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions are met: | ||
* | ||
* 1. Redistributions of source code must retain the above copyright notice, | ||
* this list of conditions and the following disclaimer. | ||
* | ||
* 2. Redistributions in binary form must reproduce the above copyright notice, | ||
* this list of conditions and the following disclaimer in the documentation | ||
* and/or other materials provided with the distribution. | ||
* | ||
* 3. Neither the name of Salesforce.com nor the names of its contributors may | ||
* be used to endorse or promote products derived from this software without | ||
* specific prior written permission. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
* POSSIBILITY OF SUCH DAMAGE. | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -42,2 +12,28 @@ exports.getPublicSuffix = void 0; | ||
}; | ||
/** | ||
* Returns the public suffix of this hostname. The public suffix is the shortest domain | ||
* name upon which a cookie can be set. | ||
* | ||
* @remarks | ||
* A "public suffix" is a domain that is controlled by a | ||
* public registry, such as "com", "co.uk", and "pvt.k12.wy.us". | ||
* This step is essential for preventing attacker.com from | ||
* disrupting the integrity of example.com by setting a cookie | ||
* with a Domain attribute of "com". Unfortunately, the set of | ||
* public suffixes (also known as "registry controlled domains") | ||
* changes over time. If feasible, user agents SHOULD use an | ||
* up-to-date public suffix list, such as the one maintained by | ||
* the Mozilla project at http://publicsuffix.org/. | ||
* (See {@link https://www.rfc-editor.org/rfc/rfc6265.html#section-5.3 | RFC6265 - Section 5.3}) | ||
* | ||
* @example | ||
* ``` | ||
* getPublicSuffix('www.example.com') === 'example.com' | ||
* getPublicSuffix('www.subdomain.example.com') === 'example.com' | ||
* ``` | ||
* | ||
* @param domain - the domain attribute of a cookie | ||
* @param options - optional configuration for controlling how the public suffix is determined | ||
* @public | ||
*/ | ||
function getPublicSuffix(domain, options = {}) { | ||
@@ -62,3 +58,3 @@ options = { ...defaultGetPublicSuffixOptions, ...options }; | ||
// MAY pass them to name resolution APIs as they would for other domain names." | ||
return `${topLevelDomain}`; | ||
return topLevelDomain; | ||
} | ||
@@ -69,9 +65,11 @@ } | ||
SPECIAL_USE_DOMAINS.includes(topLevelDomain)) { | ||
throw new Error(`Cookie has domain set to the public suffix "${topLevelDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`); | ||
throw new Error(`Cookie has domain set to the public suffix "${topLevelDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain: true, rejectPublicSuffixes: false}.`); | ||
} | ||
return (0, tldts_1.getDomain)(domain, { | ||
const publicSuffix = (0, tldts_1.getDomain)(domain, { | ||
allowIcannDomains: true, | ||
allowPrivateDomains: true, | ||
}); | ||
if (publicSuffix) | ||
return publicSuffix; | ||
} | ||
exports.getPublicSuffix = getPublicSuffix; |
@@ -1,32 +0,2 @@ | ||
/*! | ||
* Copyright (c) 2015, Salesforce.com, Inc. | ||
* All rights reserved. | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions are met: | ||
* | ||
* 1. Redistributions of source code must retain the above copyright notice, | ||
* this list of conditions and the following disclaimer. | ||
* | ||
* 2. Redistributions in binary form must reproduce the above copyright notice, | ||
* this list of conditions and the following disclaimer in the documentation | ||
* and/or other materials provided with the distribution. | ||
* | ||
* 3. Neither the name of Salesforce.com nor the names of its contributors may | ||
* be used to endorse or promote products derived from this software without | ||
* specific prior written permission. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
* POSSIBILITY OF SUCH DAMAGE. | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -38,3 +8,11 @@ exports.MemoryCookieStore = void 0; | ||
const utils_1 = require("./utils"); | ||
/** | ||
* An in-memory {@link Store} implementation for {@link CookieJar}. This is the default implementation used by | ||
* {@link CookieJar} and supports both async and sync operations. Also supports serialization, getAllCookies, and removeAllCookies. | ||
* @public | ||
*/ | ||
class MemoryCookieStore extends store_1.Store { | ||
/** | ||
* Create a new {@link MemoryCookieStore}. | ||
*/ | ||
constructor() { | ||
@@ -45,2 +23,5 @@ super(); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
findCookie(domain, path, key, callback) { | ||
@@ -51,5 +32,8 @@ const promiseCallback = (0, utils_1.createPromiseCallback)(callback); | ||
} | ||
const result = this.idx?.[domain]?.[path]?.[key]; | ||
const result = this.idx[domain]?.[path]?.[key]; | ||
return promiseCallback.resolve(result); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
findCookies(domain, path, allowSpecialUseDomain = false, callback) { | ||
@@ -110,5 +94,9 @@ if (typeof allowSpecialUseDomain === 'function') { | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
putCookie(cookie, callback) { | ||
const promiseCallback = (0, utils_1.createPromiseCallback)(callback); | ||
const { domain, path, key } = cookie; | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
if (domain == null || path == null || key == null) { | ||
@@ -126,2 +114,5 @@ return promiseCallback.resolve(undefined); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
updateCookie(_oldCookie, newCookie, callback) { | ||
@@ -131,6 +122,11 @@ // updateCookie() may avoid updating cookies that are identical. For example, | ||
// comparison could exclude that field. | ||
return callback | ||
? this.putCookie(newCookie, callback) | ||
: this.putCookie(newCookie); | ||
// Don't return a value when using a callback, so that the return type is truly "void" | ||
if (callback) | ||
this.putCookie(newCookie, callback); | ||
else | ||
return this.putCookie(newCookie); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
removeCookie(domain, path, key, callback) { | ||
@@ -141,2 +137,5 @@ const promiseCallback = (0, utils_1.createPromiseCallback)(callback); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
removeCookies(domain, path, callback) { | ||
@@ -147,6 +146,8 @@ const promiseCallback = (0, utils_1.createPromiseCallback)(callback); | ||
if (path) { | ||
delete domainEntry?.[path]; | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete domainEntry[path]; | ||
} | ||
else { | ||
delete this.idx?.[domain]; | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete this.idx[domain]; | ||
} | ||
@@ -156,2 +157,5 @@ } | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
removeAllCookies(callback) { | ||
@@ -162,2 +166,5 @@ const promiseCallback = (0, utils_1.createPromiseCallback)(callback); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
getAllCookies(callback) { | ||
@@ -164,0 +171,0 @@ const promiseCallback = (0, utils_1.createPromiseCallback)(callback); |
"use strict"; | ||
/*! | ||
* Copyright (c) 2015, Salesforce.com, Inc. | ||
* All rights reserved. | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.pathMatch = void 0; | ||
/** | ||
* Answers "does the request-path path-match a given cookie-path?" as per {@link https://www.rfc-editor.org/rfc/rfc6265.html#section-5.1.4 | RFC6265 Section 5.1.4}. | ||
* This is essentially a prefix-match where cookiePath is a prefix of reqPath. | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions are met: | ||
* @remarks | ||
* A request-path path-matches a given cookie-path if at least one of | ||
* the following conditions holds: | ||
* | ||
* 1. Redistributions of source code must retain the above copyright notice, | ||
* this list of conditions and the following disclaimer. | ||
* - The cookie-path and the request-path are identical. | ||
* - The cookie-path is a prefix of the request-path, and the last character of the cookie-path is %x2F ("/"). | ||
* - The cookie-path is a prefix of the request-path, and the first character of the request-path that is not included in the cookie-path is a %x2F ("/") character. | ||
* | ||
* 2. Redistributions in binary form must reproduce the above copyright notice, | ||
* this list of conditions and the following disclaimer in the documentation | ||
* and/or other materials provided with the distribution. | ||
* | ||
* 3. Neither the name of Salesforce.com nor the names of its contributors may | ||
* be used to endorse or promote products derived from this software without | ||
* specific prior written permission. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
* POSSIBILITY OF SUCH DAMAGE. | ||
* @param reqPath - the path of the request | ||
* @param cookiePath - the path of the cookie | ||
* @public | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.pathMatch = void 0; | ||
/* | ||
* "A request-path path-matches a given cookie-path if at least one of the | ||
* following conditions holds:" | ||
*/ | ||
function pathMatch(reqPath, cookiePath) { | ||
@@ -39,0 +21,0 @@ // "o The cookie-path and the request-path are identical." |
@@ -1,37 +0,19 @@ | ||
/*! | ||
* Copyright (c) 2015, Salesforce.com, Inc. | ||
* All rights reserved. | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.permuteDomain = void 0; | ||
const getPublicSuffix_1 = require("./getPublicSuffix"); | ||
/** | ||
* Generates the permutation of all possible values that {@link domainMatch} the given `domain` parameter. The | ||
* array is in shortest-to-longest order. Useful when building custom {@link Store} implementations. | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions are met: | ||
* @example | ||
* ``` | ||
* permuteDomain('foo.bar.example.com') | ||
* // ['example.com', 'bar.example.com', 'foo.bar.example.com'] | ||
* ``` | ||
* | ||
* 1. Redistributions of source code must retain the above copyright notice, | ||
* this list of conditions and the following disclaimer. | ||
* | ||
* 2. Redistributions in binary form must reproduce the above copyright notice, | ||
* this list of conditions and the following disclaimer in the documentation | ||
* and/or other materials provided with the distribution. | ||
* | ||
* 3. Neither the name of Salesforce.com nor the names of its contributors may | ||
* be used to endorse or promote products derived from this software without | ||
* specific prior written permission. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
* POSSIBILITY OF SUCH DAMAGE. | ||
* @public | ||
* @param domain - the domain to generate permutations for | ||
* @param allowSpecialUseDomain - flag to control if {@link https://www.rfc-editor.org/rfc/rfc6761.html | Special Use Domains} such as `localhost` should be allowed | ||
*/ | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.permuteDomain = void 0; | ||
const getPublicSuffix_1 = require("./getPublicSuffix"); | ||
// 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, allowSpecialUseDomain) { | ||
@@ -42,3 +24,3 @@ const pubSuf = (0, getPublicSuffix_1.getPublicSuffix)(domain, { | ||
if (!pubSuf) { | ||
return null; | ||
return undefined; | ||
} | ||
@@ -45,0 +27,0 @@ if (pubSuf == domain) { |
@@ -1,37 +0,21 @@ | ||
/*! | ||
* Copyright (c) 2015, Salesforce.com, Inc. | ||
* All rights reserved. | ||
"use strict"; | ||
// disabling this lint on this whole file because Store should be abstract | ||
// but we have implementations in the wild that may not implement all features | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Store = void 0; | ||
/** | ||
* Base class for {@link CookieJar} stores. | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions are met: | ||
* The storage model for each {@link CookieJar} instance can be replaced with a custom implementation. The default is | ||
* {@link MemoryCookieStore}. | ||
* | ||
* 1. Redistributions of source code must retain the above copyright notice, | ||
* this list of conditions and the following disclaimer. | ||
* @remarks | ||
* - Stores should inherit from the base Store class, which is available as a top-level export. | ||
* | ||
* 2. Redistributions in binary form must reproduce the above copyright notice, | ||
* this list of conditions and the following disclaimer in the documentation | ||
* and/or other materials provided with the distribution. | ||
* - Stores are asynchronous by default, but if {@link Store.synchronous} is set to true, then the `*Sync` methods | ||
* of the containing {@link CookieJar} can be used. | ||
* | ||
* 3. Neither the name of Salesforce.com nor the names of its contributors may | ||
* be used to endorse or promote products derived from this software without | ||
* specific prior written permission. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
* POSSIBILITY OF SUCH DAMAGE. | ||
* @public | ||
*/ | ||
// disabling this lint on this whole file because Store should be abstract | ||
// but we have implementations in the wild that may not implement all features | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Store = void 0; | ||
class Store { | ||
@@ -41,11 +25,23 @@ constructor() { | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
findCookie(_domain, _path, _key, _callback) { | ||
throw new Error('findCookie is not implemented'); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
findCookies(_domain, _path, _allowSpecialUseDomain = false, _callback) { | ||
throw new Error('findCookies is not implemented'); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
putCookie(_cookie, _callback) { | ||
throw new Error('putCookie is not implemented'); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
updateCookie(_oldCookie, _newCookie, _callback) { | ||
@@ -56,11 +52,23 @@ // recommended default implementation: | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
removeCookie(_domain, _path, _key, _callback) { | ||
throw new Error('removeCookie is not implemented'); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
removeCookies(_domain, _path, _callback) { | ||
throw new Error('removeCookies is not implemented'); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
removeAllCookies(_callback) { | ||
throw new Error('removeAllCookies is not implemented'); | ||
} | ||
/** | ||
* @internal No doc because this is an overload that supports the implementation | ||
*/ | ||
getAllCookies(_callback) { | ||
@@ -67,0 +75,0 @@ throw new Error('getAllCookies is not implemented (therefore jar cannot be serialized)'); |
@@ -71,3 +71,3 @@ "use strict"; | ||
return; // Validation passes | ||
const cb = typeof cbOrMessage === 'function' ? cbOrMessage : null; | ||
const cb = typeof cbOrMessage === 'function' ? cbOrMessage : undefined; | ||
let options = typeof cbOrMessage === 'function' ? message : cbOrMessage; | ||
@@ -85,4 +85,8 @@ // The default message prior to v5 was '[object Object]' due to a bug, and the message is kept | ||
exports.validate = validate; | ||
/** | ||
* Represents a validation error. | ||
* @public | ||
*/ | ||
class ParameterError extends Error { | ||
} | ||
exports.ParameterError = ParameterError; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.version = void 0; | ||
// Generated by genversion. | ||
exports.version = '5.0.0-rc.1'; | ||
/** | ||
* The version of `tough-cookie` | ||
* @public | ||
*/ | ||
exports.version = '5.0.0-rc.2'; |
@@ -70,3 +70,3 @@ { | ||
], | ||
"version": "5.0.0-rc.1", | ||
"version": "5.0.0-rc.2", | ||
"homepage": "https://github.com/salesforce/tough-cookie", | ||
@@ -88,11 +88,15 @@ "repository": { | ||
"scripts": { | ||
"api:dev": "npm run build && npm run api:extract -- --local && npm run api:docs", | ||
"api:docs": "api-documenter markdown --input-folder ./tmp --output-folder ./api/docs", | ||
"api:extract": "api-extractor run --verbose", | ||
"build": "npm run clean && tsc", | ||
"clean": "rm -rf dist", | ||
"version": "genversion --es6 lib/version.ts && git add lib/version.ts", | ||
"version": "genversion --template version-template.ejs --force lib/version.ts && git add lib/version.ts", | ||
"test": "npm run test:ts && npm run test:legacy", | ||
"test:ts": "jest", | ||
"test:legacy": "npm run build -- --declaration false && vows test/*_test.js", | ||
"test:legacy": "npm run build -- --declaration false && ./test/scripts/vows.js test/*_test.js", | ||
"typecheck": "tsc --noEmit", | ||
"cover": "jest --coverage", | ||
"eslint": "eslint --env node --ext .ts .", | ||
"lint": "eslint .", | ||
"eslint": "eslint .", | ||
"prettier": "prettier '**/*.{json,ts,yaml,md}'", | ||
@@ -105,26 +109,28 @@ "format": "npm run eslint -- --fix" | ||
"devDependencies": { | ||
"@types/jest": "^29.5.10", | ||
"@eslint/js": "^8.57.0", | ||
"@microsoft/api-documenter": "^7.23.37", | ||
"@microsoft/api-extractor": "^7.42.3", | ||
"@types/jest": "^29.5.12", | ||
"@types/node": "^14.18.63", | ||
"@types/psl": "^1.1.3", | ||
"@types/punycode": "^2.1.4", | ||
"@types/url-parse": "^1.4.11", | ||
"@typescript-eslint/eslint-plugin": "^6.13.1", | ||
"@typescript-eslint/parser": "^6.13.1", | ||
"async": "3.2.5", | ||
"eslint": "^8.54.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-prettier": "^5.0.1", | ||
"genversion": "^3.1.1", | ||
"eslint": "^8.57.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"eslint-plugin-prettier": "^5.1.3", | ||
"genversion": "^3.2.0", | ||
"globals": "^14.0.0", | ||
"jest": "^29.7.0", | ||
"prettier": "^3.1.0", | ||
"ts-jest": "^29.1.1", | ||
"ts-node": "^10.9.1", | ||
"prettier": "^3.2.5", | ||
"ts-jest": "^29.1.2", | ||
"ts-node": "^10.9.2", | ||
"typescript": "4.9.5", | ||
"typescript-eslint": "^7.3.1", | ||
"vows": "^0.8.3" | ||
}, | ||
"dependencies": { | ||
"tldts": "^6.1.0", | ||
"punycode": "^2.3.1", | ||
"tldts": "^6.1.14", | ||
"url-parse": "^1.5.10" | ||
} | ||
} |
632
README.md
@@ -1,586 +0,144 @@ | ||
# tough-cookie | ||
# Tough Cookie · [![RFC6265][rfc6265-badge]][rfc6265-tracker] [![RFC6265bis][rfc6265bis-badge]][rfc6265bis-tracker] [![npm version][npm-badge]][npm-repo] [![CI on Github Actions: salesforce/tough-cookie][ci-badge]][ci-url] ![PRs Welcome][prs-welcome-badge] | ||
[RFC 6265](https://tools.ietf.org/html/rfc6265) Cookies and CookieJar for Node.js | ||
A Node.js implementation of [RFC6265][rfc6265-tracker] for cookie parsing, storage, and retrieval. | ||
[![npm package](https://nodei.co/npm/tough-cookie.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/tough-cookie/) | ||
## Getting Started | ||
[![Build Status](https://travis-ci.org/salesforce/tough-cookie.svg?branch=master)](https://travis-ci.org/salesforce/tough-cookie) | ||
Install Tough Cookie using [`npm`][npm-repo]: | ||
## Synopsis | ||
```javascript | ||
var tough = require('tough-cookie') | ||
var Cookie = tough.Cookie | ||
var cookie = Cookie.parse(header) | ||
cookie.value = 'somethingdifferent' | ||
header = cookie.toString() | ||
var cookiejar = new tough.CookieJar() | ||
// Asynchronous! | ||
var cookie = await cookiejar.setCookie( | ||
cookie, | ||
'https://currentdomain.example.com/path', | ||
) | ||
var cookies = await cookiejar.getCookies('https://example.com/otherpath') | ||
// Or with callbacks! | ||
cookiejar.setCookie( | ||
cookie, | ||
'https://currentdomain.example.com/path', | ||
function (err, cookie) { | ||
/* ... */ | ||
}, | ||
) | ||
cookiejar.getCookies('http://example.com/otherpath', function (err, cookies) { | ||
/* ... */ | ||
}) | ||
``` | ||
Why the name? NPM modules `cookie`, `cookies` and `cookiejar` were already taken. | ||
## Installation | ||
It's _so_ easy! Install with `npm` or your preferred package manager. | ||
```sh | ||
```shell | ||
npm install tough-cookie | ||
``` | ||
## Node.js Version Support | ||
or [`yarn`][yarn-repo]: | ||
We follow the [node.js release schedule](https://github.com/nodejs/Release#release-schedule) and support all versions that are in Active LTS or Maintenance. We will always do a major release when dropping support for older versions of node, and we will do so in consultation with our community. | ||
## API | ||
### tough | ||
The top-level exports from `require('tough-cookie')` can all be used as pure functions and don't need to be bound. | ||
#### `parseDate(string)` | ||
Parse a cookie date string into a `Date`. Parses according to [RFC 6265 Section 5.1.1](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.1), not `Date.parse()`. | ||
#### `formatDate(date)` | ||
Format a `Date` into an [RFC 822](https://datatracker.ietf.org/doc/html/rfc822#section-5) string (the RFC 6265 recommended format). | ||
#### `canonicalDomain(str)` | ||
Transforms a domain name into a canonical domain name. The canonical domain name is a domain name that has been trimmed, lowercased, stripped of leading dot, and optionally punycode-encoded ([Section 5.1.2 of RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.2)). For the most part, this function is idempotent (calling the function with the output from a previous call returns the same output). | ||
#### `domainMatch(str, domStr[, canonicalize=true])` | ||
Answers "does this real domain match the domain in a cookie?". The `str` is the "current" domain name and the `domStr` is the "cookie" domain name. Matches according to [RFC 6265 Section 5.1.3](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3), but it helps to think of it as a "suffix match". | ||
The `canonicalize` parameter toggles whether the domain parameters get normalized with `canonicalDomain` or not. | ||
#### `defaultPath(path)` | ||
Given a current request/response path, gives the path appropriate for storing in a cookie. This is basically the "directory" of a "file" in the path, but is specified by [Section 5.1.4 of the RFC](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4). | ||
The `path` parameter MUST be _only_ the pathname part of a URI (excluding the hostname, query, fragment, and so on). This is the `.pathname` property of node's `uri.parse()` output. | ||
#### `pathMatch(reqPath, cookiePath)` | ||
Answers "does the request-path path-match a given cookie-path?" as per [RFC 6265 Section 5.1.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4). Returns a boolean. | ||
This is essentially a prefix-match where `cookiePath` is a prefix of `reqPath`. | ||
#### `parse(cookieString[, options])` | ||
Alias for [`Cookie.parse(cookieString[, options])`](#cookieparsecookiestring-options). | ||
#### `fromJSON(string)` | ||
Alias for [`Cookie.fromJSON(string)`](#cookiefromjsonstrorobj). | ||
#### `getPublicSuffix(hostname)` | ||
Returns the public suffix of this hostname. The public suffix is the shortest domain name upon which a cookie can be set. Returns `null` if the hostname cannot have cookies set for it. | ||
For example: `www.example.com` and `www.subdomain.example.com` both have public suffix `example.com`. | ||
For further information, see the [Public Suffix List](http://publicsuffix.org/). This module derives its list from that site. This call is a wrapper around [`psl`](https://www.npmjs.com/package/psl)'s [`get` method](https://www.npmjs.com/package/psl##pslgetdomain). | ||
#### `cookieCompare(a, b)` | ||
For use with `.sort()`, sorts a list of cookies into the recommended order given in step 2 of ([RFC 6265 Section 5.4](https://datatracker.ietf.org/doc/html/rfc6265#section-5.4)). The sort algorithm is, in order of precedence: | ||
- Longest `.path` | ||
- oldest `.creation` (which has a 1-ms precision, same as `Date`) | ||
- lowest `.creationIndex` (to get beyond the 1-ms precision) | ||
```javascript | ||
var cookies = [ | ||
/* unsorted array of Cookie objects */ | ||
] | ||
cookies = cookies.sort(cookieCompare) | ||
```shell | ||
yarn add tough-cookie | ||
``` | ||
> **Note**: Since the JavaScript `Date` is limited to a 1-ms precision, cookies within the same millisecond are entirely possible. This is especially true when using the `now` option to `.setCookie()`. The `.creationIndex` property is a per-process global counter, assigned during construction with `new Cookie()`, which preserves the spirit of the RFC sorting: older cookies go first. This works great for `MemoryCookieStore` since `Set-Cookie` headers are parsed in order, but is not so great for distributed systems. Sophisticated `Store`s may wish to set this to some other _logical clock_ so that if cookies A and B are created in the same millisecond, but cookie A is created before cookie B, then `A.creationIndex < B.creationIndex`. If you want to alter the global counter, which you probably _shouldn't_ do, it's stored in `Cookie.cookiesCreated`. | ||
## Usage | ||
#### `permuteDomain(domain)` | ||
```typescript | ||
import { Cookie, CookieJar } from 'tough-cookie' | ||
Generates a list of all possible domains that `domainMatch()` the parameter. Can be handy for implementing cookie stores. | ||
// parse a `Cookie` request header | ||
const reqCookies = 'ID=298zf09hf012fh2; csrf=u32t4o3tb3gg43; _gat=1'.split(';').map(Cookie.parse) | ||
// generate a `Cookie` request header | ||
const cookieHeader = reqCookies.map(cookie => cookie.cookieString()).join(';') | ||
#### `permutePath(path)` | ||
// parse a Set-Cookie response header | ||
const resCookie = Cookie.parse('foo=bar; Domain=example.com; Path=/; Expires=Tue, 21 Oct 2025 00:00:00 GMT') | ||
// generate a Set-Cookie response header | ||
const setCookieHeader = cookie.toString() | ||
Generates a list of all possible paths that `pathMatch()` the parameter. Can be handy for implementing cookie stores. | ||
### Cookie | ||
Exported via `tough.Cookie`. | ||
#### `Cookie.parse(cookieString[, options])` | ||
Parses a single Cookie or Set-Cookie HTTP header into a `Cookie` object. Returns `undefined` if the string can't be parsed. | ||
The options parameter is not required and currently has only one property: | ||
- _loose_ - boolean - if `true` enable parsing of keyless cookies like `=abc` and `=`, which are not RFC-compliant. | ||
If options is not an object it is ignored, which means it can be used with [`Array#map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). | ||
To process the Set-Cookie header(s) on a node HTTP/HTTPS response: | ||
```javascript | ||
if (Array.isArray(res.headers['set-cookie'])) | ||
cookies = res.headers['set-cookie'].map(Cookie.parse) | ||
else cookies = [Cookie.parse(res.headers['set-cookie'])] | ||
// store and retrieve cookies | ||
const cookieJar = new CookieJar() // uses the in-memory store by default | ||
await cookieJar.setCookie(resCookie, 'https://example.com/') | ||
const matchingCookies = await cookieJar.getCookies('https://example.com/') | ||
``` | ||
_Note:_ In version 2.3.3, tough-cookie limited the number of spaces before the `=` to 256 characters. This limitation was removed in version 2.3.4. | ||
For more details, see [issue #92](https://github.com/salesforce/tough-cookie/issues/92). | ||
> [!IMPORTANT] | ||
> For more detailed usage information, refer to the [API docs](./api/docs/tough-cookie.md). | ||
#### Properties | ||
## RFC6265bis | ||
Cookie object properties: | ||
Support for [RFC6265bis][rfc6265bis-tracker] is being developed. As these revisions to [RFC6252][rfc6265-tracker] are | ||
still in `Active Internet-Draft` state, the areas of support that follow are subject to change. | ||
- _key_ - string - the name or key of the cookie (default `""`) | ||
- _value_ - string - the value of the cookie (default `""`) | ||
- _expires_ - `Date` - if set, the `Expires=` attribute of the cookie (defaults to the string `"Infinity"`). See `setExpires()` | ||
- _maxAge_ - seconds - if set, the `Max-Age=` attribute _in seconds_ of the cookie. Can also be set to strings `"Infinity"` and `"-Infinity"` for non-expiry and immediate-expiry, respectively. See `setMaxAge()` | ||
- _domain_ - string - the `Domain=` attribute of the cookie | ||
- _path_ - string - the `Path=` of the cookie | ||
- _secure_ - boolean - the `Secure` cookie flag | ||
- _httpOnly_ - boolean - the `HttpOnly` cookie flag | ||
- _sameSite_ - string - the `SameSite` cookie attribute (from [RFC 6265bis](#rfc-6265bis)); must be one of `none`, `lax`, or `strict` | ||
- _extensions_ - `Array` - any unrecognized cookie attributes as strings (even if equal-signs inside) | ||
- _creation_ - `Date` - when this cookie was constructed | ||
- _creationIndex_ - number - set at construction, used to provide greater sort precision (see `cookieCompare(a,b)` for a full explanation) | ||
### SameSite Cookies | ||
After a cookie has been passed through `CookieJar.setCookie()` it has the following additional attributes: | ||
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. | ||
- _hostOnly_ - boolean - is this a host-only cookie (that is, no Domain field was set, but was instead implied). | ||
- _pathIsDefault_ - boolean - if true, there was no Path field on the cookie and `defaultPath()` was used to derive one. | ||
- _creation_ - `Date` - **modified** from construction to when the cookie was added to the jar. | ||
- _lastAccessed_ - `Date` - last time the cookie got accessed. Affects cookie cleaning after it is implemented. Using `cookiejar.getCookies(...)` updates this attribute. | ||
#### Example | ||
#### `new Cookie([properties])` | ||
```typescript | ||
import { CookieJar } from 'tough-cookie' | ||
Receives an options object that can contain any of the above Cookie properties. Uses the default for unspecified properties. | ||
const cookieJar = new CookieJar() // uses the in-memory store by default | ||
#### `.toString()` | ||
// storing cookies with various SameSite attributes | ||
await cookieJar.setCookie('strict=authorized; SameSite=strict', 'http://example.com/index.html') | ||
await cookieJar.setCookie('lax=okay; SameSite=lax', 'http://example.com/index.html') | ||
await cookieJar.setCookie('normal=whatever', 'http://example.com/index.html') | ||
Encodes to a Set-Cookie header value. The Expires cookie field is set using `formatDate()`, but is omitted entirely if `.expires` is `Infinity`. | ||
#### `.cookieString()` | ||
Encodes to a Cookie header value (specifically, the `.key` and `.value` properties joined with `"="`). | ||
#### `.setExpires(string)` | ||
Sets the expiry based on a date-string passed through `parseDate()`. If parseDate returns `null` (that is, can't parse this date string), `.expires` is set to `"Infinity"` (a string). | ||
#### `.setMaxAge(number)` | ||
Sets the maxAge in seconds. Coerces `-Infinity` to `"-Infinity"` and `Infinity` to `"Infinity"` so it correctly serializes to JSON. | ||
#### `.expiryDate([now=Date.now()])` | ||
`expiryTime()` computes the absolute unix-epoch milliseconds that this cookie expires. `expiryDate()` works similarly, except it returns a `Date` object. Note that in both cases the `now` parameter should be milliseconds. | ||
Max-Age takes precedence over Expires (as per the RFC). The `.creation` attribute -- or, by default, the `now` parameter -- is used to offset the `.maxAge` attribute. | ||
If Expires (`.expires`) is set, that's returned. | ||
Otherwise, `expiryTime()` returns `Infinity` and `expiryDate()` returns a `Date` object for "Tue, 19 Jan 2038 03:14:07 GMT" (latest date that can be expressed by a 32-bit `time_t`; the common limit for most user-agents). | ||
#### `.TTL([now=Date.now()])` | ||
Computes the TTL relative to `now` (milliseconds). The same precedence rules as for `expiryTime`/`expiryDate` apply. | ||
`Infinity` is returned for cookies without an explicit expiry and `0` is returned if the cookie is expired. Otherwise a time-to-live in milliseconds is returned. | ||
#### `.canonicalizedDomain()` | ||
#### `.cdomain()` | ||
Returns the canonicalized `.domain` field. This is lower-cased and punycode ([RFC 3490](https://datatracker.ietf.org/doc/html/rfc3490)) encoded if the domain has any non-ASCII characters. | ||
#### `.toJSON()` | ||
For convenience in using `JSON.stringify(cookie)`. Returns a plain-old `Object` that can be JSON-serialized. | ||
Any `Date` properties (such as `.expires`, `.creation`, and `.lastAccessed`) are exported in ISO format (`.toISOString()`). | ||
> **NOTE**: Custom `Cookie` properties are discarded. In tough-cookie 1.x, since there was no `.toJSON` method explicitly defined, all enumerable properties were captured. If you want a property to be serialized, add the property name to the `Cookie.serializableProperties` Array. | ||
#### `Cookie.fromJSON(strOrObj)` | ||
Does the reverse of `cookie.toJSON()`. If passed a string, will `JSON.parse()` that first. | ||
Any `Date` properties (such as `.expires`, `.creation`, and `.lastAccessed`) are parsed via [`Date.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse), not tough-cookie's `parseDate`, since ISO timestamps are being handled at this layer. | ||
Returns `null` upon a JSON parsing error. | ||
#### `.clone()` | ||
Does a deep clone of this cookie, implemented exactly as `Cookie.fromJSON(cookie.toJSON())`. | ||
#### `.validate()` | ||
Status: _IN PROGRESS_. Works for a few things, but is by no means comprehensive. | ||
Validates cookie attributes for semantic correctness. Useful for "lint" checking any Set-Cookie headers you generate. For now, it returns a boolean, but eventually could return a reason string. Future-proof with this construct: | ||
```javascript | ||
if (cookie.validate() === true) { | ||
// it's tasty | ||
} else { | ||
// yuck! | ||
} | ||
// retrieving cookies using a SameSite context | ||
const laxCookies = await cookieJar.getCookies('http://example.com/index.html', { | ||
// the first cookie (strict=authorized) will not be returned if the context is 'lax' | ||
// but the other two cookies will be returned | ||
sameSiteContext: 'lax', | ||
}) | ||
``` | ||
### CookieJar | ||
> [!NOTE] | ||
> It is highly recommended that you read [RFC6265bis - Section 8.8][samesite-implementation] for more details on SameSite cookies, security considerations, and defense in depth. | ||
Exported via `tough.CookieJar`. | ||
### Cookie Prefixes | ||
#### `CookieJar([store][, options])` | ||
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. | ||
Simply use `new CookieJar()`. If a custom store is not passed to the constructor, a [`MemoryCookieStore`](#memorycookiestore) is created and used. | ||
Two prefixes are defined: | ||
The `options` object can be omitted and can have the following properties: | ||
- `"__Secure-"` | ||
- _rejectPublicSuffixes_ - boolean - default `true` - reject cookies with domains like "com" and "co.uk" | ||
- _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](#cookie-prefixes) below. | ||
- _allowSpecialUseDomain_ - boolean - default `true` - 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. | ||
If a cookie's name begins with a case-sensitive match for the string `__Secure-`, then the cookie was set with a "Secure" attribute. | ||
#### `.setCookie(cookieOrString, currentUrl[, options][, callback(err, cookie)])` | ||
- `"__Host-"` | ||
Attempt to set the cookie in the cookie jar. The cookie has updated `.creation`, `.lastAccessed` and `.hostOnly` properties. And returns a promise if a callback is not provided. | ||
If a cookie's name begins with a case-sensitive match for the string `__Host-`, then the cookie was set with a "Secure" attribute, a "Path" attribute with a value of "/", and no "Domain" attribute. | ||
The `options` object can be omitted and can have the following properties: | ||
If `prefixSecurity` is enabled for `CookieJar`, then cookies that match the prefixes defined above but do | ||
not obey the attribute restrictions are not added. | ||
- _http_ - boolean - default `true` - indicates if this is an HTTP or non-HTTP API. Affects `HttpOnly` cookies. | ||
- _secure_ - boolean - autodetect from URL - indicates if this is a "Secure" API. If the currentUrl starts with `https:` or `wss:` this defaults to `true`, otherwise `false`. | ||
- _now_ - Date - default `new Date()` - what to use for the creation or access time of cookies. | ||
- _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](#samesite-cookies) below. | ||
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). | ||
#### `.setCookieSync(cookieOrString, currentUrl[, options])` | ||
Synchronous version of [`setCookie`](#setcookiecookieorstring-currenturl-options-callbackerr-cookie); only works with synchronous stores (that is, the default `MemoryCookieStore`). | ||
#### `.getCookies(currentUrl[, options][, callback(err, cookies)])` | ||
Retrieve the list of cookies that can be sent in a Cookie header for the current URL. Returns a promise if a callback is not provided. | ||
Returns an array of `Cookie` objects, sorted by default using [`cookieCompare`](#cookiecomparea-b). | ||
If an error is encountered it's passed as `err` to the callback, otherwise an array of `Cookie` objects is passed. The array is sorted with `cookieCompare()` unless the `{sort:false}` option is given. | ||
The `options` object can be omitted and can have the following properties: | ||
- _http_ - boolean - default `true` - indicates if this is an HTTP or non-HTTP API. Affects `HttpOnly` cookies. | ||
- _secure_ - boolean - autodetect from URL - indicates if this is a "Secure" API. If the currentUrl starts with `https:` or `wss:` then this is defaulted to `true`, otherwise `false`. | ||
- _now_ - Date - default `new Date()` - what to use for the creation or access time of cookies | ||
- _expire_ - boolean - default `true` - perform expiry-time checking of cookies and asynchronously remove expired cookies from the store. Using `false` returns expired cookies and does **not** remove them from the store (which is potentially useful for replaying Set-Cookie headers). | ||
- _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 retrieval. See [SameSite Cookies](#samesite-cookies) below. | ||
- _sort_ - boolean - whether to sort the list of cookies. | ||
The `.lastAccessed` property of the returned cookies will have been updated. | ||
#### `.getCookiesSync(currentUrl, [{options}])` | ||
Synchronous version of [`getCookies`](#getcookiescurrenturl-options-callbackerr-cookies); only works with synchronous stores (for example, the default `MemoryCookieStore`). | ||
#### `.getCookieString(...)` | ||
Accepts the same options as [`.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies) but returns a string suitable for a Cookie header rather than an Array. | ||
#### `.getCookieStringSync(...)` | ||
Synchronous version of [`getCookieString`](#getcookiestring); only works with synchronous stores (for example, the default `MemoryCookieStore`). | ||
#### `.getSetCookieStrings(...)` | ||
Returns an array of strings suitable for **Set-Cookie** headers. Accepts the same options as [`.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies). Simply maps the cookie array via `.toString()`. | ||
#### `.getSetCookieStringsSync(...)` | ||
Synchronous version of [`getSetCookieStrings`](#getsetcookiestrings); only works with synchronous stores (for example, the default `MemoryCookieStore`). | ||
#### `.serialize([callback(err, serializedObject)])` | ||
Returns a promise if a callback is not provided. | ||
Serialize the Jar if the underlying store supports `.getAllCookies`. | ||
> **NOTE**: Custom `Cookie` properties are discarded. If you want a property to be serialized, add the property name to the `Cookie.serializableProperties` Array. | ||
See [Serialization Format](#serialization-format). | ||
#### `.serializeSync()` | ||
Synchronous version of [`serialize`](#serializecallbackerr-serializedobject); only works with synchronous stores (for example, the default `MemoryCookieStore`). | ||
#### `.toJSON()` | ||
Alias of [`.serializeSync()`](#serializesync) for the convenience of `JSON.stringify(cookiejar)`. | ||
#### `CookieJar.deserialize(serialized[, store][, callback(err, object)])` | ||
A new Jar is created and the serialized Cookies are added to the underlying store. Each `Cookie` is added via `store.putCookie` in the order in which they appear in the serialization. A promise is returned if a callback is not provided. | ||
The `store` argument is optional, but should be an instance of `Store`. By default, a new instance of `MemoryCookieStore` is created. | ||
As a convenience, if `serialized` is a string, it is passed through `JSON.parse` first. | ||
#### `CookieJar.deserializeSync(serialized[, store])` | ||
Sync version of [`.deserialize`](#cookiejardeserializeserialized-store-callbackerr-object); only works with synchronous stores (for example, the default `MemoryCookieStore`). | ||
#### `CookieJar.fromJSON(string)` | ||
Alias of [`.deserializeSync`](#cookiejardeserializesyncserialized-store) to provide consistency with [`Cookie.fromJSON()`](#cookiefromjsonstrorobj). | ||
#### `.clone([store][, callback(err, cloned))` | ||
Produces a deep clone of this jar. Modifications to the original do not affect the clone, and vice versa. Returns a promise if a callback is not provided. | ||
The `store` argument is optional, but should be an instance of `Store`. By default, a new instance of `MemoryCookieStore` is created. Transferring between store types is supported so long as the source implements `.getAllCookies()` and the destination implements `.putCookie()`. | ||
#### `.cloneSync([store])` | ||
Synchronous version of [`.clone`](#clonestore-callbackerr-cloned), returning a new `CookieJar` instance. | ||
The `store` argument is optional, but must be a _synchronous_ `Store` instance if specified. If not passed, a new instance of `MemoryCookieStore` is used. | ||
The _source_ and _destination_ must both be synchronous `Store`s. If one or both stores are asynchronous, use `.clone` instead. Recall that `MemoryCookieStore` supports both synchronous and asynchronous API calls. | ||
#### `.removeAllCookies([callback(err)])` | ||
Removes all cookies from the jar. Returns a promise if a callback is not provided. | ||
This is a new backwards-compatible feature of `tough-cookie` version 2.5, so not all Stores will implement it efficiently. For Stores that do not implement `removeAllCookies`, the fallback is to call `removeCookie` after `getAllCookies`. If `getAllCookies` fails or isn't implemented in the Store, that error is returned. If one or more of the `removeCookie` calls fail, only the first error is returned. | ||
#### `.removeAllCookiesSync()` | ||
Sync version of [`.removeAllCookies()`](#removeallcookiescallbackerr); only works with synchronous stores (for example, the default `MemoryCookieStore`). | ||
### Store | ||
Base class for CookieJar stores. Available as `tough.Store`. | ||
### Store API | ||
The storage model for each `CookieJar` instance can be replaced with a custom implementation. The default is `MemoryCookieStore` which can be found in [`lib/memstore.ts`](https://github.com/salesforce/tough-cookie/blob/master/lib/memstore.ts). The API uses continuation-passing-style to allow for asynchronous stores. | ||
Stores should inherit from the base `Store` class, which is available as a top-level export. | ||
Stores are asynchronous by default, but if `store.synchronous` is set to `true`, then the `*Sync` methods of the containing `CookieJar` can be used. | ||
All `domain` parameters are normalized before calling. | ||
The Cookie store must have all of the following methods. Note that asynchronous implementations **must** support callback parameters. | ||
#### `store.findCookie(domain, path, key, callback(err, cookie))` | ||
Retrieve a cookie with the given domain, path, and key (name). The RFC maintains that exactly one of these cookies should exist in a store. If the store is using versioning, this means that the latest or newest such cookie should be returned. | ||
Callback takes an error and the resulting `Cookie` object. If no cookie is found then `null` MUST be passed instead (that is, not an error). | ||
#### `store.findCookies(domain, path, allowSpecialUseDomain, callback(err, cookies))` | ||
Locates cookies matching the given domain and path. This is most often called in the context of [`cookiejar.getCookies()`](#getcookiescurrenturl-options-callbackerr-cookies). | ||
If no cookies are found, the callback MUST be passed an empty array. | ||
The resulting list is checked for applicability to the current request according to the RFC (domain-match, path-match, http-only-flag, secure-flag, expiry, and so on), so it's OK to use an optimistic search algorithm when implementing this method. However, the search algorithm used SHOULD try to find cookies that `domainMatch()` the domain and `pathMatch()` the path in order to limit the amount of checking that needs to be done. | ||
As of version 0.9.12, the `allPaths` option to `cookiejar.getCookies()` above causes the path here to be `null`. If the path is `null`, path-matching MUST NOT be performed (that is, domain-matching only). | ||
#### `store.putCookie(cookie, callback(err))` | ||
Adds a new cookie to the store. The implementation SHOULD replace any existing cookie with the same `.domain`, `.path`, and `.key` properties. Depending on the nature of the implementation, it's possible that between the call to `fetchCookie` and `putCookie` that a duplicate `putCookie` can occur. | ||
The `cookie` object MUST NOT be modified; as the caller has already updated the `.creation` and `.lastAccessed` properties. | ||
Pass an error if the cookie cannot be stored. | ||
#### `store.updateCookie(oldCookie, newCookie, callback(err))` | ||
Update an existing cookie. The implementation MUST update the `.value` for a cookie with the same `domain`, `.path`, and `.key`. The implementation SHOULD check that the old value in the store is equivalent to `oldCookie` - how the conflict is resolved is up to the store. | ||
The `.lastAccessed` property is always different between the two objects (to the precision possible via JavaScript's clock). Both `.creation` and `.creationIndex` are guaranteed to be the same. Stores MAY ignore or defer the `.lastAccessed` change at the cost of affecting how cookies are selected for automatic deletion (for example, least-recently-used, which is up to the store to implement). | ||
Stores may wish to optimize changing the `.value` of the cookie in the store versus storing a new cookie. If the implementation doesn't define this method, a stub that calls [`putCookie`](#storeputcookiecookie-callbackerr) is added to the store object. | ||
The `newCookie` and `oldCookie` objects MUST NOT be modified. | ||
Pass an error if the newCookie cannot be stored. | ||
#### `store.removeCookie(domain, path, key, callback(err))` | ||
Remove a cookie from the store (see notes on [`findCookie`](#storefindcookiedomain-path-key-callbackerr-cookie) about the uniqueness constraint). | ||
The implementation MUST NOT pass an error if the cookie doesn't exist, and only pass an error due to the failure to remove an existing cookie. | ||
#### `store.removeCookies(domain, path, callback(err))` | ||
Removes matching cookies from the store. The `path` parameter is optional and if missing, means all paths in a domain should be removed. | ||
Pass an error ONLY if removing any existing cookies failed. | ||
#### `store.removeAllCookies(callback(err))` | ||
_Optional_. Removes all cookies from the store. | ||
Pass an error if one or more cookies can't be removed. | ||
#### `store.getAllCookies(callback(err, cookies))` | ||
_Optional_. Produces an `Array` of all cookies during [`jar.serialize()`](#serializecallbackerr-serializedobject). The items in the array can be true `Cookie` objects or generic `Object`s with the [Serialization Format](#serialization-format) data structure. | ||
Cookies SHOULD be returned in creation order to preserve sorting via [`compareCookie()`](#cookiecomparea-b). For reference, `MemoryCookieStore` sorts by `.creationIndex` since it uses true `Cookie` objects internally. If you don't return the cookies in creation order, they'll still be sorted by creation time, but this only has a precision of 1-ms. See `cookieCompare` for more detail. | ||
Pass an error if retrieval fails. | ||
**Note**: Not all Stores can implement this due to technical limitations, so it is optional. | ||
### MemoryCookieStore | ||
Inherits from `Store`. | ||
A just-in-memory CookieJar synchronous store implementation, used by default. Despite being a synchronous implementation, it's usable with both the synchronous and asynchronous forms of the `CookieJar` API. Supports serialization, `getAllCookies`, and `removeAllCookies`. | ||
## Serialization Format | ||
**NOTE**: If you want to have custom `Cookie` properties serialized, add the property name to `Cookie.serializableProperties`. | ||
```js | ||
{ | ||
// The version of tough-cookie that serialized this jar. | ||
version: 'tough-cookie@1.x.y', | ||
// add the store type, to make humans happy: | ||
storeType: 'MemoryCookieStore', | ||
// CookieJar configuration: | ||
rejectPublicSuffixes: true, | ||
// ... future items go here | ||
// Gets filled from jar.store.getAllCookies(): | ||
cookies: [ | ||
{ | ||
key: 'string', | ||
value: 'string', | ||
// ... | ||
/* other Cookie.serializableProperties go here */ | ||
} | ||
] | ||
} | ||
``` | ||
## RFC 6265bis | ||
Support for RFC 6265bis revision 02 is being developed. Since this is a bit of an omnibus revision to the RFC 6252, 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 or set the `.sameSite` attribute, which is serialized into the `SameSite=` cookie attribute. When unset or `undefined`, no `SameSite=` attribute is serialized. The valid values of this attribute are `'none'`, `'lax'`, or `'strict'`. Other values are 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` parses 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 more information), 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, such as, `<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 **is 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 was 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 was 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 are not added. | ||
You can define this functionality by passing in the `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 are not met. Default. | ||
1. `silent`: (**default**) Enable cookie prefix checking but silently fail to add the cookie if conditions are not met. | ||
2. `strict`: Enable cookie prefix checking and error out if conditions are not met. | ||
3. `unsafe-disabled`: Disable cookie prefix checking. | ||
Note that if `ignoreError` is passed in as `true` then the error is silent regardless of the `prefixSecurity` option (assuming it's enabled). | ||
> If `ignoreError` is passed in as `true` when setting a cookie then the error is silent regardless of the `prefixSecurity` option (assuming it's enabled). | ||
## Copyright and License | ||
#### Example | ||
BSD-3-Clause: | ||
```typescript | ||
import { CookieJar, MemoryCookieStore } from 'tough-cookie' | ||
```text | ||
Copyright (c) 2015, Salesforce.com, Inc. | ||
All rights reserved. | ||
const cookieJar = new CookieJar(new MemoryCookieStore(), { | ||
prefixSecurity: 'silent' | ||
}) | ||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
// this cookie will be silently ignored since the url is insecure (http) | ||
await cookieJar.setCookie( | ||
'__Secure-SID=12345; Domain=example.com; Secure;', | ||
'http://example.com', | ||
) | ||
1. Redistributions of source code must retain the above copyright notice, | ||
this list of conditions and the following disclaimer. | ||
// this cookie will be stored since the url is secure (https) | ||
await cookieJar.setCookie( | ||
'__Secure-SID=12345; Domain=example.com; Secure;', | ||
'https://example.com', | ||
) | ||
``` | ||
2. Redistributions in binary form must reproduce the above copyright notice, | ||
this list of conditions and the following disclaimer in the documentation | ||
and/or other materials provided with the distribution. | ||
> [!NOTE] | ||
> It is highly recommended that you read [RFC6265bis - Section 4.1.3][cookie-prefixes-implementation] for more details on Cookie Prefixes. | ||
3. Neither the name of Salesforce.com nor the names of its contributors may | ||
be used to endorse or promote products derived from this software without | ||
specific prior written permission. | ||
## Node.js Version Support | ||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | ||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
POSSIBILITY OF SUCH DAMAGE. | ||
``` | ||
We follow the [Node.js release schedule](https://github.com/nodejs/Release#release-schedule) and support | ||
all versions that are in Active LTS or Maintenance. We will always do a major release when dropping support | ||
for older versions of node, and we will do so in consultation with our community. | ||
[npm-badge]: https://img.shields.io/npm/v/tough-cookie.svg?style=flat | ||
[npm-repo]: https://www.npmjs.com/package/tough-cookie | ||
[ci-badge]: https://github.com/salesforce/tough-cookie/actions/workflows/integration.yaml/badge.svg | ||
[ci-url]: https://github.com/salesforce/tough-cookie/actions/workflows/integration.yaml | ||
[rfc6265-badge]: https://img.shields.io/badge/RFC-6265-flat?labelColor=000000&color=666666 | ||
[rfc6265-tracker]: https://datatracker.ietf.org/doc/rfc6265/ | ||
[rfc6265bis-badge]: https://img.shields.io/badge/RFC-6265bis-flat?labelColor=000000&color=666666 | ||
[rfc6265bis-tracker]: https://datatracker.ietf.org/doc/draft-ietf-httpbis-rfc6265bis/ | ||
[samesite-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-8.8 | ||
[cookie-prefixes-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.3 | ||
[prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg | ||
[yarn-repo]: https://yarnpkg.com/package?name=tough-cookie |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
3238
0
142745
20
22
145
Updatedtldts@^6.1.14