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 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.2";
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,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: url.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(cookie.domain)) {
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
  )