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 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.map((cookie) => cookie.cookieString()).join(';')
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=/; Expires=Tue, 21 Oct 2025 00:00:00 GMT',
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 = cookie.toString()
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 [RFC6252][rfc6265-tracker] are
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
- // this cookie will be silently ignored since the url is insecure (http)
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 `CookieStore` must be configured with:
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
- 'http://localhost',
173
+ 'https://localhost',
169
174
  )
170
175
 
171
176
  // but, on retrieval, it will not be returned
172
- await cookieJar.getCookiesSync('http://localhost')
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-02#section-8.8
190
- [cookie-prefixes-implementation]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.3
191
- [secure-connection-note]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-19#section-5.8.3-2.1.2.3.1
192
- [secure-connection-appendix-a]: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-19#appendix-A-1.7.1
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.1";
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
- while (cookie_avs.length) {
660
- const av = (cookie_avs.shift() ?? "").trim();
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 && this.sameSite !== "none") {
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: url.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
- return new URL(decodeURI(url));
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 = String(value).toLowerCase();
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(cookie.domain)) {
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
  )