tough-cookie 6.0.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -13
- package/dist/index.cjs +61 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -32
- package/dist/index.d.ts +59 -32
- package/dist/index.js +61 -12
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -24,16 +24,20 @@ import { Cookie, CookieJar } from 'tough-cookie'
|
|
|
24
24
|
// parse a `Cookie` request header
|
|
25
25
|
const reqCookies = 'ID=298zf09hf012fh2; csrf=u32t4o3tb3gg43; _gat=1'
|
|
26
26
|
.split(';')
|
|
27
|
-
.map(Cookie.parse)
|
|
27
|
+
.map((str) => Cookie.parse(str))
|
|
28
|
+
.filter((cookie) => cookie !== undefined)
|
|
28
29
|
// generate a `Cookie` request header
|
|
29
|
-
const cookieHeader = reqCookies
|
|
30
|
+
const cookieHeader = reqCookies
|
|
31
|
+
.map((cookie) => cookie.cookieString())
|
|
32
|
+
.join('; ')
|
|
30
33
|
|
|
31
34
|
// parse a Set-Cookie response header
|
|
32
35
|
const resCookie = Cookie.parse(
|
|
33
|
-
'foo=bar; Domain=example.com; Path=/;
|
|
36
|
+
'foo=bar; Domain=example.com; Path=/; Max-Age=3600',
|
|
34
37
|
)
|
|
38
|
+
if (!resCookie) throw new Error('invalid Set-Cookie header')
|
|
35
39
|
// generate a Set-Cookie response header
|
|
36
|
-
const setCookieHeader =
|
|
40
|
+
const setCookieHeader = resCookie.toString()
|
|
37
41
|
|
|
38
42
|
// store and retrieve cookies
|
|
39
43
|
const cookieJar = new CookieJar() // uses the in-memory store by default
|
|
@@ -46,7 +50,7 @@ const matchingCookies = await cookieJar.getCookies('https://example.com/')
|
|
|
46
50
|
|
|
47
51
|
## RFC6265bis
|
|
48
52
|
|
|
49
|
-
Support for [RFC6265bis][rfc6265bis-tracker] is being developed. As these revisions to [
|
|
53
|
+
Support for [RFC6265bis][rfc6265bis-tracker] is being developed. As these revisions to [RFC6265][rfc6265-tracker] are
|
|
50
54
|
still in `Active Internet-Draft` state, the areas of support that follow are subject to change.
|
|
51
55
|
|
|
52
56
|
### SameSite Cookies
|
|
@@ -118,10 +122,11 @@ const cookieJar = new CookieJar(new MemoryCookieStore(), {
|
|
|
118
122
|
prefixSecurity: 'silent',
|
|
119
123
|
})
|
|
120
124
|
|
|
121
|
-
//
|
|
125
|
+
// ignoreError silences the general Secure-cookie origin rejection
|
|
122
126
|
await cookieJar.setCookie(
|
|
123
127
|
'__Secure-SID=12345; Domain=example.com; Secure;',
|
|
124
128
|
'http://example.com',
|
|
129
|
+
{ ignoreError: true },
|
|
125
130
|
)
|
|
126
131
|
|
|
127
132
|
// this cookie will be stored since the url is secure (https)
|
|
@@ -151,7 +156,7 @@ origins"** which are defined in the [Secure Contexts - W3C Candidate Recommendat
|
|
|
151
156
|
> Considers potentially trustworthy origins as "secure".
|
|
152
157
|
|
|
153
158
|
Since most web browsers treat `localhost` as a trustworthy origin, by default, so does `tough-cookie`. To disable this
|
|
154
|
-
behavior, the `
|
|
159
|
+
behavior, the `CookieJar` must be configured with:
|
|
155
160
|
|
|
156
161
|
```typescript
|
|
157
162
|
import { CookieJar, MemoryCookieStore } from 'tough-cookie'
|
|
@@ -165,11 +170,34 @@ const cookieJar = new CookieJar(new MemoryCookieStore(), {
|
|
|
165
170
|
// this cookie will be persisted to storage
|
|
166
171
|
await cookieJar.setCookie(
|
|
167
172
|
'SID=12345; Domain=localhost; Secure;',
|
|
168
|
-
'
|
|
173
|
+
'https://localhost',
|
|
169
174
|
)
|
|
170
175
|
|
|
171
176
|
// but, on retrieval, it will not be returned
|
|
172
|
-
await cookieJar.
|
|
177
|
+
const cookies = await cookieJar.getCookies('http://localhost')
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Secure cookies set on an untrustworthy origin are rejected, including
|
|
181
|
+
unprefixed cookies. `ignoreError: true` silently discards them; it does not
|
|
182
|
+
store them. This origin check applies independently of `prefixSecurity`.
|
|
183
|
+
|
|
184
|
+
To restore the previous behavior of storing Secure cookies regardless of the
|
|
185
|
+
origin they were set from, the `CookieJar` must be configured with the following.
|
|
186
|
+
This only affects storage; Secure cookies are still only returned for secure
|
|
187
|
+
origins on retrieval.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { CookieJar, MemoryCookieStore } from 'tough-cookie'
|
|
191
|
+
|
|
192
|
+
const cookieJar = new CookieJar(new MemoryCookieStore(), {
|
|
193
|
+
enforceSecureOriginOnStorage: false,
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// this cookie will be persisted to storage
|
|
197
|
+
await cookieJar.setCookie('SID=12345; Secure;', 'http://example.com')
|
|
198
|
+
|
|
199
|
+
// but, on retrieval, it will only be returned for secure origins
|
|
200
|
+
const cookies = await cookieJar.getCookies('https://example.com')
|
|
173
201
|
```
|
|
174
202
|
|
|
175
203
|
## Node.js Version Support
|
|
@@ -186,10 +214,10 @@ for older versions of node, and we will do so in consultation with our community
|
|
|
186
214
|
[rfc6265-tracker]: https://datatracker.ietf.org/doc/rfc6265/
|
|
187
215
|
[rfc6265bis-badge]: https://img.shields.io/badge/RFC-6265bis-flat?labelColor=000000&color=666666
|
|
188
216
|
[rfc6265bis-tracker]: https://datatracker.ietf.org/doc/draft-ietf-httpbis-rfc6265bis/
|
|
189
|
-
[samesite-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-
|
|
190
|
-
[cookie-prefixes-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-
|
|
191
|
-
[secure-connection-note]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-
|
|
192
|
-
[secure-connection-appendix-a]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-
|
|
217
|
+
[samesite-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-8.8
|
|
218
|
+
[cookie-prefixes-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-4.1.3
|
|
219
|
+
[secure-connection-note]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-5.8.3
|
|
220
|
+
[secure-connection-appendix-a]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#appendix-A
|
|
193
221
|
[potentially-trustworthy-origin]: https://www.w3.org/TR/secure-contexts/#is-origin-trustworthy
|
|
194
222
|
[prs-welcome-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
|
|
195
223
|
[yarn-repo]: https://yarnpkg.com/package?name=tough-cookie
|
package/dist/index.cjs
CHANGED
|
@@ -428,7 +428,7 @@ var ParameterError = class extends Error {
|
|
|
428
428
|
};
|
|
429
429
|
|
|
430
430
|
// lib/version.ts
|
|
431
|
-
var version = "6.0
|
|
431
|
+
var version = "6.1.0";
|
|
432
432
|
|
|
433
433
|
// lib/cookie/constants.ts
|
|
434
434
|
var PrefixSecurityEnum = {
|
|
@@ -482,6 +482,17 @@ function formatDate(date) {
|
|
|
482
482
|
return date.toUTCString();
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
+
// lib/cookie/cookieName.ts
|
|
486
|
+
var INVALID_TOKEN_CHARACTER = /[^\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
|
|
487
|
+
var CookieName = {
|
|
488
|
+
parse(input) {
|
|
489
|
+
if (input.length === 0 || INVALID_TOKEN_CHARACTER.test(input)) {
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
return input;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
485
496
|
// lib/cookie/parseDate.ts
|
|
486
497
|
function parseDate(cookieDate) {
|
|
487
498
|
if (!cookieDate) {
|
|
@@ -656,8 +667,8 @@ function parse(str, options) {
|
|
|
656
667
|
return c;
|
|
657
668
|
}
|
|
658
669
|
const cookie_avs = unparsed.split(";");
|
|
659
|
-
|
|
660
|
-
const av =
|
|
670
|
+
for (const cookie_av of cookie_avs) {
|
|
671
|
+
const av = cookie_av.trim();
|
|
661
672
|
if (av.length === 0) {
|
|
662
673
|
continue;
|
|
663
674
|
}
|
|
@@ -940,10 +951,17 @@ var _Cookie = class _Cookie {
|
|
|
940
951
|
*
|
|
941
952
|
* @remarks
|
|
942
953
|
* Works for a few things, but is by no means comprehensive.
|
|
954
|
+
* Checks that the cookie name is a non-empty token as defined in RFC 6265 Section 4.1.1.
|
|
955
|
+
* Nameless cookies (including those parsed in loose mode or created with `new Cookie({ value })`)
|
|
956
|
+
* and names containing characters outside the token grammar (such as `a b`) return `false`.
|
|
957
|
+
* Parsing and storage remain permissive; this check only applies to explicit validation.
|
|
943
958
|
*
|
|
944
959
|
* @beta
|
|
945
960
|
*/
|
|
946
961
|
validate() {
|
|
962
|
+
if (CookieName.parse(this.key) === void 0) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
947
965
|
if (!this.value || !COOKIE_OCTETS.test(this.value)) {
|
|
948
966
|
return false;
|
|
949
967
|
}
|
|
@@ -962,7 +980,7 @@ var _Cookie = class _Cookie {
|
|
|
962
980
|
return false;
|
|
963
981
|
}
|
|
964
982
|
const suffix = getPublicSuffix(cdomain);
|
|
965
|
-
if (suffix == null) {
|
|
983
|
+
if (suffix == null && !IP_V4_REGEX_OBJECT.test(cdomain) && !IP_V6_REGEX_OBJECT.test(cdomain)) {
|
|
966
984
|
return false;
|
|
967
985
|
}
|
|
968
986
|
}
|
|
@@ -1038,11 +1056,13 @@ var _Cookie = class _Cookie {
|
|
|
1038
1056
|
if (this.httpOnly) {
|
|
1039
1057
|
str += "; HttpOnly";
|
|
1040
1058
|
}
|
|
1041
|
-
if (this.sameSite
|
|
1059
|
+
if (this.sameSite) {
|
|
1042
1060
|
if (this.sameSite.toLowerCase() === _Cookie.sameSiteCanonical.lax.toLowerCase()) {
|
|
1043
1061
|
str += `; SameSite=${_Cookie.sameSiteCanonical.lax}`;
|
|
1044
1062
|
} else if (this.sameSite.toLowerCase() === _Cookie.sameSiteCanonical.strict.toLowerCase()) {
|
|
1045
1063
|
str += `; SameSite=${_Cookie.sameSiteCanonical.strict}`;
|
|
1064
|
+
} else if (this.sameSite.toLowerCase() === _Cookie.sameSiteCanonical.none.toLowerCase()) {
|
|
1065
|
+
str += `; SameSite=${_Cookie.sameSiteCanonical.none}`;
|
|
1046
1066
|
} else {
|
|
1047
1067
|
str += `; SameSite=${this.sameSite}`;
|
|
1048
1068
|
}
|
|
@@ -1089,6 +1109,9 @@ var _Cookie = class _Cookie {
|
|
|
1089
1109
|
*/
|
|
1090
1110
|
expiryTime(now) {
|
|
1091
1111
|
if (this.maxAge != null) {
|
|
1112
|
+
if (this.maxAge === "Infinity") {
|
|
1113
|
+
return Infinity;
|
|
1114
|
+
}
|
|
1092
1115
|
const relativeTo = now || this.lastAccessed || /* @__PURE__ */ new Date();
|
|
1093
1116
|
const maxAge = typeof this.maxAge === "number" ? this.maxAge : -Infinity;
|
|
1094
1117
|
const age = maxAge <= 0 ? -Infinity : maxAge * 1e3;
|
|
@@ -1221,7 +1244,8 @@ _Cookie.sameSiteLevel = {
|
|
|
1221
1244
|
*/
|
|
1222
1245
|
_Cookie.sameSiteCanonical = {
|
|
1223
1246
|
strict: "Strict",
|
|
1224
|
-
lax: "Lax"
|
|
1247
|
+
lax: "Lax",
|
|
1248
|
+
none: "None"
|
|
1225
1249
|
};
|
|
1226
1250
|
/**
|
|
1227
1251
|
* Cookie properties that will be serialized when using {@link Cookie.fromJSON} and {@link Cookie.toJSON}.
|
|
@@ -1380,23 +1404,34 @@ var defaultGetCookieOptions = {
|
|
|
1380
1404
|
var SAME_SITE_CONTEXT_VAL_ERR = 'Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"';
|
|
1381
1405
|
function getCookieContext(url) {
|
|
1382
1406
|
if (url && typeof url === "object" && "hostname" in url && typeof url.hostname === "string" && "pathname" in url && typeof url.pathname === "string" && "protocol" in url && typeof url.protocol === "string") {
|
|
1407
|
+
let pathname = url.pathname;
|
|
1408
|
+
try {
|
|
1409
|
+
pathname = decodeURI(pathname);
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1383
1412
|
return {
|
|
1384
1413
|
hostname: url.hostname,
|
|
1385
|
-
pathname
|
|
1414
|
+
pathname,
|
|
1386
1415
|
protocol: url.protocol
|
|
1387
1416
|
};
|
|
1388
1417
|
} else if (typeof url === "string") {
|
|
1418
|
+
const parsed = new URL(url);
|
|
1419
|
+
let pathname = parsed.pathname;
|
|
1389
1420
|
try {
|
|
1390
|
-
|
|
1421
|
+
pathname = decodeURI(pathname);
|
|
1391
1422
|
} catch {
|
|
1392
|
-
return new URL(url);
|
|
1393
1423
|
}
|
|
1424
|
+
return {
|
|
1425
|
+
hostname: parsed.hostname,
|
|
1426
|
+
pathname,
|
|
1427
|
+
protocol: parsed.protocol
|
|
1428
|
+
};
|
|
1394
1429
|
} else {
|
|
1395
1430
|
throw new ParameterError("`url` argument is not a string or URL.");
|
|
1396
1431
|
}
|
|
1397
1432
|
}
|
|
1398
1433
|
function checkSameSiteContext(value) {
|
|
1399
|
-
const context =
|
|
1434
|
+
const context = value.toLowerCase();
|
|
1400
1435
|
if (context === "none" || context === "lax" || context === "strict") {
|
|
1401
1436
|
return context;
|
|
1402
1437
|
} else {
|
|
@@ -1410,7 +1445,7 @@ function isSecurePrefixConditionMet(cookie) {
|
|
|
1410
1445
|
function isHostPrefixConditionMet(cookie) {
|
|
1411
1446
|
const startsWithHostPrefix = typeof cookie.key === "string" && cookie.key.startsWith("__Host-");
|
|
1412
1447
|
return !startsWithHostPrefix || Boolean(
|
|
1413
|
-
cookie.secure && cookie.hostOnly && cookie.path != null && cookie.path === "/"
|
|
1448
|
+
cookie.secure && cookie.hostOnly && cookie.path != null && cookie.path === "/" && !cookie.pathIsDefault
|
|
1414
1449
|
);
|
|
1415
1450
|
}
|
|
1416
1451
|
function getNormalizedPrefixSecurity(prefixSecurity) {
|
|
@@ -1443,6 +1478,7 @@ var CookieJar = class _CookieJar {
|
|
|
1443
1478
|
this.enableLooseMode = options?.looseMode ?? false;
|
|
1444
1479
|
this.allowSpecialUseDomain = options?.allowSpecialUseDomain ?? true;
|
|
1445
1480
|
this.allowSecureOnLocal = options?.allowSecureOnLocal ?? true;
|
|
1481
|
+
this.enforceSecureOriginOnStorage = options?.enforceSecureOriginOnStorage ?? true;
|
|
1446
1482
|
this.prefixSecurity = getNormalizedPrefixSecurity(
|
|
1447
1483
|
options?.prefixSecurity ?? "silent"
|
|
1448
1484
|
);
|
|
@@ -1530,7 +1566,7 @@ var CookieJar = class _CookieJar {
|
|
|
1530
1566
|
allowSpecialUseDomain: this.allowSpecialUseDomain,
|
|
1531
1567
|
ignoreError: options?.ignoreError
|
|
1532
1568
|
}) : null;
|
|
1533
|
-
if (suffix == null && !IP_V6_REGEX_OBJECT.test(
|
|
1569
|
+
if (suffix == null && !IP_V4_REGEX_OBJECT.test(cdomain ?? "") && !IP_V6_REGEX_OBJECT.test(cdomain ?? "")) {
|
|
1534
1570
|
const err = new Error("Cookie has domain set to a public suffix");
|
|
1535
1571
|
return options?.ignoreError ? promiseCallback.resolve(void 0) : promiseCallback.reject(err);
|
|
1536
1572
|
}
|
|
@@ -1559,6 +1595,12 @@ var CookieJar = class _CookieJar {
|
|
|
1559
1595
|
cookie.path = defaultPath(context.pathname);
|
|
1560
1596
|
cookie.pathIsDefault = true;
|
|
1561
1597
|
}
|
|
1598
|
+
if (this.enforceSecureOriginOnStorage && cookie.secure && !isPotentiallyTrustworthy(url, this.allowSecureOnLocal)) {
|
|
1599
|
+
const err = new Error(
|
|
1600
|
+
"Cookie is Secure but this is not a secure connection"
|
|
1601
|
+
);
|
|
1602
|
+
return options?.ignoreError ? promiseCallback.resolve(void 0) : promiseCallback.reject(err);
|
|
1603
|
+
}
|
|
1562
1604
|
if (options?.http === false && cookie.httpOnly) {
|
|
1563
1605
|
const err = new Error("Cookie is HttpOnly and this isn't an HTTP API");
|
|
1564
1606
|
return options.ignoreError ? promiseCallback.resolve(void 0) : promiseCallback.reject(err);
|
|
@@ -1883,6 +1925,7 @@ var CookieJar = class _CookieJar {
|
|
|
1883
1925
|
rejectPublicSuffixes: this.rejectPublicSuffixes,
|
|
1884
1926
|
enableLooseMode: this.enableLooseMode,
|
|
1885
1927
|
allowSpecialUseDomain: this.allowSpecialUseDomain,
|
|
1928
|
+
enforceSecureOriginOnStorage: this.enforceSecureOriginOnStorage,
|
|
1886
1929
|
prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity),
|
|
1887
1930
|
// this gets filled from getAllCookies:
|
|
1888
1931
|
cookies: []
|
|
@@ -2129,6 +2172,9 @@ var CookieJar = class _CookieJar {
|
|
|
2129
2172
|
rejectPublicSuffixes: readSerializedBoolean("rejectPublicSuffixes"),
|
|
2130
2173
|
looseMode: readSerializedBoolean("enableLooseMode"),
|
|
2131
2174
|
allowSpecialUseDomain: readSerializedBoolean("allowSpecialUseDomain"),
|
|
2175
|
+
enforceSecureOriginOnStorage: readSerializedBoolean(
|
|
2176
|
+
"enforceSecureOriginOnStorage"
|
|
2177
|
+
),
|
|
2132
2178
|
prefixSecurity: getNormalizedPrefixSecurity(
|
|
2133
2179
|
readSerializedString("prefixSecurity") ?? "silent"
|
|
2134
2180
|
)
|
|
@@ -2174,6 +2220,9 @@ var CookieJar = class _CookieJar {
|
|
|
2174
2220
|
rejectPublicSuffixes: readSerializedBoolean("rejectPublicSuffixes"),
|
|
2175
2221
|
looseMode: readSerializedBoolean("enableLooseMode"),
|
|
2176
2222
|
allowSpecialUseDomain: readSerializedBoolean("allowSpecialUseDomain"),
|
|
2223
|
+
enforceSecureOriginOnStorage: readSerializedBoolean(
|
|
2224
|
+
"enforceSecureOriginOnStorage"
|
|
2225
|
+
),
|
|
2177
2226
|
prefixSecurity: getNormalizedPrefixSecurity(
|
|
2178
2227
|
readSerializedString("prefixSecurity") ?? "silent"
|
|
2179
2228
|
)
|