rekwest 7.2.3 → 7.2.4

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
@@ -127,9 +127,8 @@ console.log(res.body);
127
127
  with the request
128
128
  * `bufferBody` **{boolean}** `Default: false` Toggles the buffering of the streamable request bodies for redirects and
129
129
  retries
130
- * `cookies` **{boolean | string[] | Array<[k, v]> | Cookies | Object | URLSearchParams}** `Default: true` The
131
- cookies to add to
132
- the request
130
+ * `cookies` **{boolean | string | string[] | [k, v][] | Cookies | Object | URLSearchParams}** `Default: true` The
131
+ cookies to add to the request. Manually set `cookie` header to override.
133
132
  * `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache
134
133
  * `credentials` **{include | omit | same-origin}** `Default: same-origin` Controls credentials in case of cross-origin
135
134
  redirects
package/dist/cookies.cjs CHANGED
@@ -3,10 +3,17 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.Cookies = void 0;
6
+ exports.splitCookie = exports.maxCookieSize = exports.maxCookieLifetimeCap = exports.isValidCookie = exports.illegalCookieChars = exports.cookieRex = exports.cookiePairRex = exports.Cookies = void 0;
7
7
  var _utils = require("./utils.cjs");
8
- const lifetimeCap = 3456e7; // 400 days
9
-
8
+ const cookieRex = exports.cookieRex = /^[\w-]+=(?:"[^"]*"|[^\p{Control};]*)(?:;\s*(?:[\w-]+=(?:"[^"]*"|[^\p{Control};]*)|[\w-]+))*$/u;
9
+ const cookiePairRex = exports.cookiePairRex = /(?:[^;"\s]+="[^"]*"|[^;]+)(?=;|$)/g;
10
+ const illegalCookieChars = exports.illegalCookieChars = /\p{Control}/u;
11
+ const isValidCookie = str => str?.constructor === String && cookieRex.test(str);
12
+ exports.isValidCookie = isValidCookie;
13
+ const maxCookieLifetimeCap = exports.maxCookieLifetimeCap = 3456e7; // 400 days
14
+ const maxCookieSize = exports.maxCookieSize = 4096;
15
+ const splitCookie = str => str.match(cookiePairRex).map(str => str.trim());
16
+ exports.splitCookie = splitCookie;
10
17
  class Cookies extends URLSearchParams {
11
18
  static #finalizers = new Set();
12
19
  static jar = new Map();
@@ -27,27 +34,39 @@ class Cookies extends URLSearchParams {
27
34
  } = {
28
35
  cookiesTTL: false
29
36
  }) {
30
- if (Array.isArray(input) && input.every(it => !Array.isArray(it))) {
31
- input = input.map(it => {
32
- if (!cookiesTTL) {
33
- return [it.split(';')[0].trim()];
34
- }
35
- const [cookie, ...attrs] = it.split(';').map(it => it.trim());
36
- const ttl = {};
37
- for (const attr of attrs) {
38
- if (/(?:expires|max-age)=/i.test(attr)) {
39
- const [key, val] = attr.toLowerCase().split('=');
40
- const ms = Number.isFinite(Number.parseInt(val, 10)) ? val * 1e3 : Date.parse(val) - Date.now();
41
- ttl[(0, _utils.toCamelCase)(key)] = Math.min(ms, lifetimeCap);
37
+ if (isValidCookie(input)) {
38
+ input = splitCookie(input);
39
+ }
40
+ const ttlMap = new Map();
41
+ if (Array.isArray(input)) {
42
+ if (input.every(it => isValidCookie(it))) {
43
+ input = input.filter(it => !illegalCookieChars.test(it) && it.length <= maxCookieSize);
44
+ input = input.map(splitCookie).map(([cookie, ...attrs]) => {
45
+ try {
46
+ cookie = cookie.split('=').map(it => decodeURIComponent(it.trim()));
47
+ return cookie;
48
+ } finally {
49
+ if (cookiesTTL) {
50
+ for (const attr of attrs) {
51
+ if (/(?:expires|max-age)=/i.test(attr)) {
52
+ const [key, val] = attr.toLowerCase().split('=');
53
+ let interval = val * 1e3 || Date.parse(val) - Date.now();
54
+ if (interval < 0 || Number.isNaN(interval)) {
55
+ interval = 0;
56
+ }
57
+ ttlMap.set(cookie[0], {
58
+ [(0, _utils.toCamelCase)(key.trim())]: Math.min(interval, maxCookieLifetimeCap)
59
+ });
60
+ }
61
+ }
62
+ }
42
63
  }
43
- }
44
- return [cookie.replace(/\u0022/g, ''), Object.keys(ttl).length ? ttl : null];
45
- });
64
+ });
65
+ }
46
66
  }
47
- super(Array.isArray(input) ? input.map(it => it[0]).join('&') : input);
48
- if (Array.isArray(input) && cookiesTTL) {
49
- for (const [cookie, ttl] of input.filter(it => it[1])) {
50
- const key = cookie.split('=')[0];
67
+ super(input);
68
+ if (ttlMap.size) {
69
+ for (const [key, attrs] of ttlMap) {
51
70
  if (this.#chronometry.has(key)) {
52
71
  clearTimeout(this.#chronometry.get(key));
53
72
  this.#chronometry.delete(key);
@@ -55,9 +74,9 @@ class Cookies extends URLSearchParams {
55
74
  const {
56
75
  expires,
57
76
  maxAge
58
- } = ttl;
59
- for (const ms of [maxAge, expires]) {
60
- if (!Number.isInteger(ms)) {
77
+ } = attrs;
78
+ for (const interval of [maxAge, expires]) {
79
+ if (!Number.isInteger(interval)) {
61
80
  continue;
62
81
  }
63
82
  const ref = new WeakRef(this);
@@ -67,7 +86,7 @@ class Cookies extends URLSearchParams {
67
86
  ctx.#chronometry.delete(key);
68
87
  ctx.delete(key);
69
88
  }
70
- }, Math.max(ms, 0));
89
+ }, Math.max(interval, 0));
71
90
  this.constructor.#register(this, tid);
72
91
  this.#chronometry.set(key, tid);
73
92
  break;
@@ -56,7 +56,7 @@ const preflight = options => {
56
56
  } else {
57
57
  cookie &&= _cookies.Cookies.jar.get(url.origin);
58
58
  }
59
- options.headers = {
59
+ headers = {
60
60
  ...(cookie && {
61
61
  [HTTP2_HEADER_COOKIE]: cookie
62
62
  }),
package/dist/retries.cjs CHANGED
@@ -35,7 +35,7 @@ const retries = (err, options) => {
35
35
  } = retry;
36
36
  if (retry.retryAfter && err.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
37
37
  interval = err.headers[HTTP2_HEADER_RETRY_AFTER];
38
- interval = Number.isFinite(Number.parseInt(interval, 10)) ? interval * 1e3 : new Date(interval) - Date.now();
38
+ interval = interval * 1e3 || Date.parse(interval) - Date.now();
39
39
  if (interval > retry.maxRetryAfter) {
40
40
  throw new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms`, {
41
41
  cause: err
@@ -44,7 +44,7 @@ const retries = (err, options) => {
44
44
  } else {
45
45
  interval = new Function('interval', `return Math.ceil(${retry.backoffStrategy});`)(interval);
46
46
  }
47
- if (interval < 0) {
47
+ if (interval < 0 || Number.isNaN(interval)) {
48
48
  interval = 0;
49
49
  }
50
50
  retry.attempts--;
package/dist/utils.cjs CHANGED
@@ -118,37 +118,43 @@ const normalize = (url, options = {}) => {
118
118
  if (!options.redirected) {
119
119
  options = cloneWith(_config.default.defaults, options);
120
120
  }
121
- if (options.trimTrailingSlashes) {
122
- url = `${url}`.replace(/(?<!:)\/+/g, '/');
123
- }
124
- if (options.stripTrailingSlash) {
125
- url = `${url}`.replace(/\/$|\/(?=#)|\/(?=\?)/g, '');
126
- }
127
121
  return Object.assign(options, {
128
122
  headers: normalizeHeaders(options.headers),
129
123
  method: options.method.toUpperCase(),
130
- url: addSearchParams(new URL(url, options.baseURL), options.params)
124
+ url: addSearchParams(normalizeUrl(new URL(url, options.baseURL), options), options.params)
131
125
  });
132
126
  };
133
127
  exports.normalize = normalize;
134
128
  const normalizeHeaders = (headers = {}) => {
135
129
  const acc = {};
136
- for (const [key, val] of Object.entries(headers)) {
137
- const name = key.toLowerCase();
130
+ for (let [key, val] of Object.entries(headers)) {
131
+ key = key.toLowerCase();
138
132
  acc[key] = val;
139
133
  if (key === HTTP2_HEADER_ACCEPT_ENCODING && !_config.isZstdSupported) {
140
- const modified = val.replace(/\s?zstd,?/gi, '').trim();
141
- if (modified) {
142
- acc[key] = modified;
134
+ val = val.replace(/\s?zstd,?/gi, '').trim();
135
+ if (val) {
136
+ acc[key] = val;
143
137
  } else {
144
- Reflect.deleteProperty(acc, name);
138
+ Reflect.deleteProperty(acc, key);
145
139
  }
146
140
  }
147
141
  }
148
142
  return acc;
149
143
  };
150
144
  exports.normalizeHeaders = normalizeHeaders;
151
- const sameOrigin = (a, b) => a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;
145
+ function normalizeUrl(url, {
146
+ trimTrailingSlashes,
147
+ stripTrailingSlash
148
+ } = {}) {
149
+ if (trimTrailingSlashes) {
150
+ url.pathname = url.pathname.replace(/\/{2,}/g, '/');
151
+ }
152
+ if (stripTrailingSlash && url.pathname !== '/') {
153
+ url.pathname = url.pathname.replace(/\/$/, '');
154
+ }
155
+ return url;
156
+ }
157
+ const sameOrigin = (a, b) => a.origin === b.origin;
152
158
  exports.sameOrigin = sameOrigin;
153
159
  const snoop = (client, req, options) => {
154
160
  req.once('close', () => client?.close());
@@ -162,9 +168,9 @@ const snoop = (client, req, options) => {
162
168
  });
163
169
  };
164
170
  exports.snoop = snoop;
165
- const stripHeaders = (headers = {}, names = []) => {
166
- names = new Set(names);
167
- return Object.fromEntries(Object.entries(headers).filter(([key]) => !names.has(key.toLowerCase())));
171
+ const stripHeaders = (headers = {}, keys = []) => {
172
+ keys = new Set(keys);
173
+ return Object.fromEntries(Object.entries(headers).filter(([key]) => !keys.has(key)));
168
174
  };
169
175
  exports.stripHeaders = stripHeaders;
170
176
  async function* tap(val) {
package/package.json CHANGED
@@ -71,5 +71,5 @@
71
71
  "test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
72
72
  },
73
73
  "type": "module",
74
- "version": "7.2.3"
74
+ "version": "7.2.4"
75
75
  }
package/src/cookies.js CHANGED
@@ -3,7 +3,13 @@ import {
3
3
  toCamelCase,
4
4
  } from './utils.js';
5
5
 
6
- const lifetimeCap = 3456e7; // 400 days
6
+ export const cookieRex = /^[\w-]+=(?:"[^"]*"|[^\p{Control};]*)(?:;\s*(?:[\w-]+=(?:"[^"]*"|[^\p{Control};]*)|[\w-]+))*$/u;
7
+ export const cookiePairRex = /(?:[^;"\s]+="[^"]*"|[^;]+)(?=;|$)/g;
8
+ export const illegalCookieChars = /\p{Control}/u;
9
+ export const isValidCookie = (str) => str?.constructor === String && cookieRex.test(str);
10
+ export const maxCookieLifetimeCap = 3456e7; // 400 days
11
+ export const maxCookieSize = 4096;
12
+ export const splitCookie = (str) => str.match(cookiePairRex).map((str) => str.trim());
7
13
 
8
14
  export class Cookies extends URLSearchParams {
9
15
 
@@ -27,49 +33,60 @@ export class Cookies extends URLSearchParams {
27
33
  }
28
34
 
29
35
  constructor(input, { cookiesTTL } = { cookiesTTL: false }) {
30
- if (Array.isArray(input) && input.every((it) => !Array.isArray(it))) {
31
- input = input.map((it) => {
32
- if (!cookiesTTL) {
33
- return [it.split(';')[0].trim()];
34
- }
35
-
36
- const [cookie, ...attrs] = it.split(';').map((it) => it.trim());
37
- const ttl = {};
38
-
39
- for (const attr of attrs) {
40
- if (/(?:expires|max-age)=/i.test(attr)) {
41
- const [key, val] = attr.toLowerCase().split('=');
42
- const ms = Number.isFinite(Number.parseInt(val, 10)) ? val * 1e3 : Date.parse(val) - Date.now();
36
+ if (isValidCookie(input)) {
37
+ input = splitCookie(input);
38
+ }
43
39
 
44
- ttl[toCamelCase(key)] = Math.min(ms, lifetimeCap);
40
+ const ttlMap = new Map();
41
+
42
+ if (Array.isArray(input)) {
43
+ if (input.every((it) => isValidCookie(it))) {
44
+ input = input.filter((it) => !illegalCookieChars.test(it) && it.length <= maxCookieSize);
45
+ input = input.map(splitCookie).map(([cookie, ...attrs]) => {
46
+ try {
47
+ cookie = cookie.split('=').map((it) => decodeURIComponent(it.trim()));
48
+
49
+ return cookie;
50
+ } finally {
51
+ if (cookiesTTL) {
52
+ for (const attr of attrs) {
53
+ if (/(?:expires|max-age)=/i.test(attr)) {
54
+ const [key, val] = attr.toLowerCase().split('=');
55
+ let interval = val * 1e3 || Date.parse(val) - Date.now();
56
+
57
+ if (interval < 0 || Number.isNaN(interval)) {
58
+ interval = 0;
59
+ }
60
+
61
+ ttlMap.set(
62
+ cookie[0],
63
+ { [toCamelCase(key.trim())]: Math.min(interval, maxCookieLifetimeCap) },
64
+ );
65
+ }
66
+ }
67
+ }
45
68
  }
46
- }
47
-
48
- return [
49
- cookie.replace(/\u0022/g, ''),
50
- Object.keys(ttl).length ? ttl : null,
51
- ];
52
- });
69
+ });
70
+ }
53
71
  }
54
72
 
55
- super(Array.isArray(input) ? input.map((it) => it[0]).join('&') : input);
73
+ super(input);
56
74
 
57
- if (Array.isArray(input) && cookiesTTL) {
58
- for (const [cookie, ttl] of input.filter((it) => it[1])) {
59
- const key = cookie.split('=')[0];
75
+ if (ttlMap.size) {
76
+ for (const [key, attrs] of ttlMap) {
60
77
 
61
78
  if (this.#chronometry.has(key)) {
62
79
  clearTimeout(this.#chronometry.get(key));
63
80
  this.#chronometry.delete(key);
64
81
  }
65
82
 
66
- const { expires, maxAge } = ttl;
83
+ const { expires, maxAge } = attrs;
67
84
 
68
- for (const ms of [
85
+ for (const interval of [
69
86
  maxAge,
70
87
  expires,
71
88
  ]) {
72
- if (!Number.isInteger(ms)) {
89
+ if (!Number.isInteger(interval)) {
73
90
  continue;
74
91
  }
75
92
 
@@ -81,7 +98,7 @@ export class Cookies extends URLSearchParams {
81
98
  ctx.#chronometry.delete(key);
82
99
  ctx.delete(key);
83
100
  }
84
- }, Math.max(ms, 0));
101
+ }, Math.max(interval, 0));
85
102
 
86
103
  this.constructor.#register(this, tid);
87
104
  this.#chronometry.set(key, tid);
package/src/preflight.js CHANGED
@@ -64,7 +64,7 @@ export const preflight = (options) => {
64
64
  cookie &&= Cookies.jar.get(url.origin);
65
65
  }
66
66
 
67
- options.headers = {
67
+ headers = {
68
68
  ...cookie && { [HTTP2_HEADER_COOKIE]: cookie },
69
69
  ...headers,
70
70
  };
package/src/retries.js CHANGED
@@ -27,7 +27,7 @@ export const retries = (err, options) => {
27
27
 
28
28
  if (retry.retryAfter && err.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
29
29
  interval = err.headers[HTTP2_HEADER_RETRY_AFTER];
30
- interval = Number.isFinite(Number.parseInt(interval, 10)) ? interval * 1e3 : new Date(interval) - Date.now();
30
+ interval = interval * 1e3 || Date.parse(interval) - Date.now();
31
31
  if (interval > retry.maxRetryAfter) {
32
32
  throw new RequestError(
33
33
  `Maximum '${ HTTP2_HEADER_RETRY_AFTER }' limit exceeded: ${ interval } ms`,
@@ -38,7 +38,7 @@ export const retries = (err, options) => {
38
38
  interval = new Function('interval', `return Math.ceil(${ retry.backoffStrategy });`)(interval);
39
39
  }
40
40
 
41
- if (interval < 0) {
41
+ if (interval < 0 || Number.isNaN(interval)) {
42
42
  interval = 0;
43
43
  }
44
44
 
package/src/utils.js CHANGED
@@ -126,36 +126,28 @@ export const normalize = (url, options = {}) => {
126
126
  options = cloneWith(config.defaults, options);
127
127
  }
128
128
 
129
- if (options.trimTrailingSlashes) {
130
- url = `${ url }`.replace(/(?<!:)\/+/g, '/');
131
- }
132
-
133
- if (options.stripTrailingSlash) {
134
- url = `${ url }`.replace(/\/$|\/(?=#)|\/(?=\?)/g, '');
135
- }
136
-
137
129
  return Object.assign(options, {
138
130
  headers: normalizeHeaders(options.headers),
139
131
  method: options.method.toUpperCase(),
140
- url: addSearchParams(new URL(url, options.baseURL), options.params),
132
+ url: addSearchParams(normalizeUrl(new URL(url, options.baseURL), options), options.params),
141
133
  });
142
134
  };
143
135
 
144
136
  export const normalizeHeaders = (headers = {}) => {
145
137
  const acc = {};
146
138
 
147
- for (const [key, val] of Object.entries(headers)) {
148
- const name = key.toLowerCase();
139
+ for (let [key, val] of Object.entries(headers)) {
140
+ key = key.toLowerCase();
149
141
 
150
142
  acc[key] = val;
151
143
 
152
144
  if (key === HTTP2_HEADER_ACCEPT_ENCODING && !isZstdSupported) {
153
- const modified = val.replace(/\s?zstd,?/gi, '').trim();
145
+ val = val.replace(/\s?zstd,?/gi, '').trim();
154
146
 
155
- if (modified) {
156
- acc[key] = modified;
147
+ if (val) {
148
+ acc[key] = val;
157
149
  } else {
158
- Reflect.deleteProperty(acc, name);
150
+ Reflect.deleteProperty(acc, key);
159
151
  }
160
152
  }
161
153
  }
@@ -163,7 +155,19 @@ export const normalizeHeaders = (headers = {}) => {
163
155
  return acc;
164
156
  };
165
157
 
166
- export const sameOrigin = (a, b) => a.protocol === b.protocol && a.hostname === b.hostname && a.port === b.port;
158
+ function normalizeUrl(url, { trimTrailingSlashes, stripTrailingSlash } = {}) {
159
+ if (trimTrailingSlashes) {
160
+ url.pathname = url.pathname.replace(/\/{2,}/g, '/');
161
+ }
162
+
163
+ if (stripTrailingSlash && url.pathname !== '/') {
164
+ url.pathname = url.pathname.replace(/\/$/, '');
165
+ }
166
+
167
+ return url;
168
+ }
169
+
170
+ export const sameOrigin = (a, b) => a.origin === b.origin;
167
171
 
168
172
  export const snoop = (client, req, options) => {
169
173
  req.once('close', () => client?.close());
@@ -177,14 +181,10 @@ export const snoop = (client, req, options) => {
177
181
  });
178
182
  };
179
183
 
180
- export const stripHeaders = (headers = {}, names = []) => {
181
- names = new Set(names);
184
+ export const stripHeaders = (headers = {}, keys = []) => {
185
+ keys = new Set(keys);
182
186
 
183
- return Object.fromEntries(
184
- Object.entries(headers).filter(
185
- ([key]) => !names.has(key.toLowerCase()),
186
- ),
187
- );
187
+ return Object.fromEntries(Object.entries(headers).filter(([key]) => !keys.has(key)));
188
188
  };
189
189
 
190
190
  export async function* tap(val) {