tough-cookie 6.0.2 → 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 +52 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -32
- package/dist/index.d.ts +55 -32
- package/dist/index.js +52 -9
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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,9 +1404,14 @@ 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") {
|
|
@@ -1416,7 +1445,7 @@ function isSecurePrefixConditionMet(cookie) {
|
|
|
1416
1445
|
function isHostPrefixConditionMet(cookie) {
|
|
1417
1446
|
const startsWithHostPrefix = typeof cookie.key === "string" && cookie.key.startsWith("__Host-");
|
|
1418
1447
|
return !startsWithHostPrefix || Boolean(
|
|
1419
|
-
cookie.secure && cookie.hostOnly && cookie.path != null && cookie.path === "/"
|
|
1448
|
+
cookie.secure && cookie.hostOnly && cookie.path != null && cookie.path === "/" && !cookie.pathIsDefault
|
|
1420
1449
|
);
|
|
1421
1450
|
}
|
|
1422
1451
|
function getNormalizedPrefixSecurity(prefixSecurity) {
|
|
@@ -1449,6 +1478,7 @@ var CookieJar = class _CookieJar {
|
|
|
1449
1478
|
this.enableLooseMode = options?.looseMode ?? false;
|
|
1450
1479
|
this.allowSpecialUseDomain = options?.allowSpecialUseDomain ?? true;
|
|
1451
1480
|
this.allowSecureOnLocal = options?.allowSecureOnLocal ?? true;
|
|
1481
|
+
this.enforceSecureOriginOnStorage = options?.enforceSecureOriginOnStorage ?? true;
|
|
1452
1482
|
this.prefixSecurity = getNormalizedPrefixSecurity(
|
|
1453
1483
|
options?.prefixSecurity ?? "silent"
|
|
1454
1484
|
);
|
|
@@ -1536,7 +1566,7 @@ var CookieJar = class _CookieJar {
|
|
|
1536
1566
|
allowSpecialUseDomain: this.allowSpecialUseDomain,
|
|
1537
1567
|
ignoreError: options?.ignoreError
|
|
1538
1568
|
}) : null;
|
|
1539
|
-
if (suffix == null && !IP_V6_REGEX_OBJECT.test(
|
|
1569
|
+
if (suffix == null && !IP_V4_REGEX_OBJECT.test(cdomain ?? "") && !IP_V6_REGEX_OBJECT.test(cdomain ?? "")) {
|
|
1540
1570
|
const err = new Error("Cookie has domain set to a public suffix");
|
|
1541
1571
|
return options?.ignoreError ? promiseCallback.resolve(void 0) : promiseCallback.reject(err);
|
|
1542
1572
|
}
|
|
@@ -1565,6 +1595,12 @@ var CookieJar = class _CookieJar {
|
|
|
1565
1595
|
cookie.path = defaultPath(context.pathname);
|
|
1566
1596
|
cookie.pathIsDefault = true;
|
|
1567
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
|
+
}
|
|
1568
1604
|
if (options?.http === false && cookie.httpOnly) {
|
|
1569
1605
|
const err = new Error("Cookie is HttpOnly and this isn't an HTTP API");
|
|
1570
1606
|
return options.ignoreError ? promiseCallback.resolve(void 0) : promiseCallback.reject(err);
|
|
@@ -1889,6 +1925,7 @@ var CookieJar = class _CookieJar {
|
|
|
1889
1925
|
rejectPublicSuffixes: this.rejectPublicSuffixes,
|
|
1890
1926
|
enableLooseMode: this.enableLooseMode,
|
|
1891
1927
|
allowSpecialUseDomain: this.allowSpecialUseDomain,
|
|
1928
|
+
enforceSecureOriginOnStorage: this.enforceSecureOriginOnStorage,
|
|
1892
1929
|
prefixSecurity: getNormalizedPrefixSecurity(this.prefixSecurity),
|
|
1893
1930
|
// this gets filled from getAllCookies:
|
|
1894
1931
|
cookies: []
|
|
@@ -2135,6 +2172,9 @@ var CookieJar = class _CookieJar {
|
|
|
2135
2172
|
rejectPublicSuffixes: readSerializedBoolean("rejectPublicSuffixes"),
|
|
2136
2173
|
looseMode: readSerializedBoolean("enableLooseMode"),
|
|
2137
2174
|
allowSpecialUseDomain: readSerializedBoolean("allowSpecialUseDomain"),
|
|
2175
|
+
enforceSecureOriginOnStorage: readSerializedBoolean(
|
|
2176
|
+
"enforceSecureOriginOnStorage"
|
|
2177
|
+
),
|
|
2138
2178
|
prefixSecurity: getNormalizedPrefixSecurity(
|
|
2139
2179
|
readSerializedString("prefixSecurity") ?? "silent"
|
|
2140
2180
|
)
|
|
@@ -2180,6 +2220,9 @@ var CookieJar = class _CookieJar {
|
|
|
2180
2220
|
rejectPublicSuffixes: readSerializedBoolean("rejectPublicSuffixes"),
|
|
2181
2221
|
looseMode: readSerializedBoolean("enableLooseMode"),
|
|
2182
2222
|
allowSpecialUseDomain: readSerializedBoolean("allowSpecialUseDomain"),
|
|
2223
|
+
enforceSecureOriginOnStorage: readSerializedBoolean(
|
|
2224
|
+
"enforceSecureOriginOnStorage"
|
|
2225
|
+
),
|
|
2183
2226
|
prefixSecurity: getNormalizedPrefixSecurity(
|
|
2184
2227
|
readSerializedString("prefixSecurity") ?? "silent"
|
|
2185
2228
|
)
|