@e-mc/request 0.13.6 → 0.13.8

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
@@ -1,6 +1,6 @@
1
1
  # @e-mc/request
2
2
 
3
- * NodeJS 18.20.5 LTS
3
+ * NodeJS 18.20 LTS
4
4
  * ES2022
5
5
 
6
6
  ## General Usage
@@ -9,12 +9,12 @@
9
9
 
10
10
  ## Interface
11
11
 
12
- * [View Source](https://www.unpkg.com/@e-mc/types@0.13.6/lib/index.d.ts)
12
+ * [View Source](https://www.unpkg.com/@e-mc/types@0.13.8/lib/index.d.ts)
13
13
 
14
14
  ```typescript
15
15
  import type { IModule, ModuleConstructor } from "./index";
16
16
  import type { HttpAgentSettings, HttpProtocolVersion, HttpRequestClient, InternetProtocolVersion } from "./http";
17
- import type { ApplyOptions, Aria2Options, FormDataPart, HeadersOnCallback, HostConfig, IHttpAdapter, OpenOptions, PostOptions, ProxySettings, PutOptions, RcloneOptions, ReadExpectType, RequestInit, StatusOnCallback } from "./request";
17
+ import type { ApplyOptions, Aria2Options, HeadersOnCallback, HostConfig, IHttpAdapter, OpenOptions, PostFileParts, PostOptions, ProxySettings, PutOptions, RcloneOptions, ReadExpectType, RequestInit, StatusOnCallback } from "./request";
18
18
  import type { DnsLookupSettings, RequestModule, RequestSettings } from "./settings";
19
19
 
20
20
  import type { ClientRequest, OutgoingHttpHeaders } from "node:http";
@@ -51,8 +51,8 @@ interface IRequest extends IModule {
51
51
  head(uri: string | URL, options?: OpenOptions): ClientRequest;
52
52
  put(uri: string | URL, data: unknown, options: PutOptions): Promise<Buffer | string | null>;
53
53
  put(uri: string | URL, data: unknown, contentType?: string, options?: PutOptions): Promise<Buffer | string | null>;
54
- post(uri: string | URL, parts: FormDataPart[]): Promise<Buffer | string | null>;
55
- post(uri: string | URL, form: Record<string, unknown>, parts: FormDataPart[]): Promise<Buffer | string | null>;
54
+ post(uri: string | URL, parts: PostFileParts): Promise<Buffer | string | null>;
55
+ post(uri: string | URL, form: Record<string, unknown>, parts: PostFileParts): Promise<Buffer | string | null>;
56
56
  post(uri: string | URL, data: unknown, options: PostOptions): Promise<Buffer | string | null>;
57
57
  post(uri: string | URL, data: unknown, contentType?: string, options?: PostOptions): Promise<Buffer | string | null>;
58
58
  get(uri: string | URL, format: "json" | "yaml" | "json5" | "xml" | "toml"): Promise<object | null>;
@@ -252,9 +252,9 @@ instance.get("http://hostname/path/config.yml", options).then(data => {
252
252
 
253
253
  ## References
254
254
 
255
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/http.d.ts
256
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/request.d.ts
257
- - https://www.unpkg.com/@e-mc/types@0.13.6/lib/settings.d.ts
255
+ - https://www.unpkg.com/@e-mc/types@0.13.8/lib/http.d.ts
256
+ - https://www.unpkg.com/@e-mc/types@0.13.8/lib/request.d.ts
257
+ - https://www.unpkg.com/@e-mc/types@0.13.8/lib/settings.d.ts
258
258
 
259
259
  * https://www.npmjs.com/package/@types/node
260
260
 
@@ -355,7 +355,12 @@ class HttpAdapter {
355
355
  result = new (require(packageName = 'fast-xml-parser').XMLParser)(parser).parse(buffer);
356
356
  break;
357
357
  case 'toml':
358
- result = require(packageName = 'toml').parse(buffer);
358
+ try {
359
+ result = require('smol-toml').parse(buffer);
360
+ }
361
+ catch {
362
+ result = require(packageName = 'toml').parse(buffer);
363
+ }
359
364
  break;
360
365
  default:
361
366
  result = JSON.parse(buffer);
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ class HttpHostAltSvc {
3
+ host;
4
+ #location = {};
5
+ #available = [];
6
+ #errors = [];
7
+ #versionData;
8
+ constructor(host, versionData) {
9
+ this.host = host;
10
+ this.#versionData = versionData;
11
+ }
12
+ did(version) {
13
+ return this.#versionData[version].status !== -1;
14
+ }
15
+ next() {
16
+ const queue = this.#available.shift();
17
+ if (queue) {
18
+ const { hostname, port, version, expires } = queue;
19
+ const ms = expires - Date.now();
20
+ if (ms < 0) {
21
+ return this.next();
22
+ }
23
+ let timeout = null;
24
+ if (!isNaN(ms)) {
25
+ timeout = setTimeout(() => {
26
+ if (!this.next()) {
27
+ this.host.version = 1;
28
+ }
29
+ }, ms);
30
+ }
31
+ this.#location = { hostname, port, version, timeout, origin: this.host.protocol + '//' + hostname + ':' + port };
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ close(error) {
37
+ const { hostname, port, version, timeout } = this.#location;
38
+ if (hostname) {
39
+ this.#location = {};
40
+ if (timeout) {
41
+ clearTimeout(timeout);
42
+ }
43
+ if (error) {
44
+ this.#errors.push({ hostname, port, version });
45
+ return this.next();
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ clear(version) {
51
+ if (version) {
52
+ if (this.#available.length === 0) {
53
+ this.flag(version, 0);
54
+ }
55
+ else if (!this.close(true)) {
56
+ this.flag(version, -1);
57
+ }
58
+ }
59
+ else {
60
+ this.close();
61
+ this.#available = [];
62
+ this.#errors = [];
63
+ for (const item of this.#versionData) {
64
+ if (item.alpn !== 0) {
65
+ item.status = -1;
66
+ }
67
+ }
68
+ }
69
+ }
70
+ flag(version, value) {
71
+ this.#versionData[version - 1].status = value;
72
+ }
73
+ valid(hostname, port, version) {
74
+ return !this.errors.find(item => item.hostname === hostname && item.port === port && item.version === version);
75
+ }
76
+ set available(value) {
77
+ this.#available = value;
78
+ }
79
+ get errors() {
80
+ return this.#errors;
81
+ }
82
+ get hostname() {
83
+ return this.#location.hostname;
84
+ }
85
+ get port() {
86
+ return this.#location.port;
87
+ }
88
+ get origin() {
89
+ return this.#location.origin;
90
+ }
91
+ }
92
+ module.exports = HttpHostAltSvc;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  const tls = require("node:tls");
3
3
  const types_1 = require("@e-mc/types");
4
+ const altsvc_1 = require("@e-mc/request/http/host/altsvc");
4
5
  const HOST_LOCAL = new Set(['localhost']);
5
6
  const HOST_STREAM = new Map();
6
7
  const HOST_HTTP_1_1 = [];
@@ -11,6 +12,7 @@ for (const network of Object.entries(require('node:os').networkInterfaces())) {
11
12
  HOST_LOCAL.add(address);
12
13
  }
13
14
  }
15
+ const createData = () => ({ success: 0, failed: 0, errors: 0, alpn: 1, status: -1 });
14
16
  class HttpHost {
15
17
  static normalizeOrigin(value) {
16
18
  return (value = value.trim()).replace(/\/+$/, '') + (!/:\d+$/.test(value) ? ':' + (value.startsWith('https') ? '443' : '80') : '');
@@ -61,19 +63,14 @@ class HttpHost {
61
63
  localhost;
62
64
  _tlsConnect = null;
63
65
  #version;
64
- #altSvc = [];
65
- #altSvcQueue = [];
66
- #altSvcError = [];
67
66
  #protocol;
68
67
  #secure;
69
68
  #hostname;
70
69
  #port;
71
70
  #origin;
72
71
  #streamSize;
73
- #versionData = [
74
- [0, 0, 0, 1, -1],
75
- [0, 0, 0, -1, -1]
76
- ];
72
+ #versionData = [createData(), createData()];
73
+ #altSvc;
77
74
  constructor(url, httpVersion = 1) {
78
75
  const { protocol, hostname } = url;
79
76
  const secure = protocol === 'https:';
@@ -86,6 +83,7 @@ class HttpHost {
86
83
  this.#origin = url.origin;
87
84
  this.localhost = HOST_LOCAL.has(hostname);
88
85
  this.#streamSize = HOST_STREAM.get(address) || (this.localhost ? 65536 : 4096);
86
+ this.#altSvc = new altsvc_1(this, this.#versionData);
89
87
  if (protocol !== 'file:' && !HOST_HTTP_1_1.includes(address = protocol + '//' + address)) {
90
88
  if (secure) {
91
89
  this.#version = HOST_ALPN_H2.includes(address) ? 2 : httpVersion;
@@ -98,7 +96,7 @@ class HttpHost {
98
96
  }
99
97
  this.#version = 1;
100
98
  for (const version of this.#versionData) {
101
- version[4] = 0;
99
+ version.status = 0;
102
100
  }
103
101
  }
104
102
  async hasProtocol(version) {
@@ -107,17 +105,17 @@ class HttpHost {
107
105
  if (!data || !this.secure) {
108
106
  return 0;
109
107
  }
110
- const status = data[3];
111
- switch (status) {
108
+ switch (data.alpn) {
112
109
  case 0:
113
110
  case 1:
114
- return status;
111
+ return data.alpn;
115
112
  default:
116
113
  return this._tlsConnect ||= new Promise(resolve => {
117
114
  const alpn = 'h' + version;
118
115
  const socket = tls.connect(+this.port, this.hostname, { ALPNProtocols: [alpn], requestCert: true, rejectUnauthorized: false }, () => {
119
116
  this._tlsConnect = null;
120
- resolve(data[3] = alpn === socket.alpnProtocol ? 1 : 0);
117
+ data.alpn = alpn === socket.alpnProtocol ? 1 : 0;
118
+ resolve(data.alpn);
121
119
  });
122
120
  socket
123
121
  .setNoDelay(false)
@@ -127,7 +125,8 @@ class HttpHost {
127
125
  if (this._tlsConnect) {
128
126
  this._tlsConnect = null;
129
127
  if (this.error(version) >= 10) {
130
- resolve(data[3] = 0);
128
+ data.alpn = 0;
129
+ resolve(0);
131
130
  }
132
131
  else {
133
132
  resolve(2);
@@ -137,7 +136,8 @@ class HttpHost {
137
136
  .on('error', () => {
138
137
  this.failed(version);
139
138
  this._tlsConnect = null;
140
- resolve(data[3] = 0);
139
+ data.alpn = 0;
140
+ resolve(0);
141
141
  })
142
142
  .end();
143
143
  });
@@ -147,179 +147,132 @@ class HttpHost {
147
147
  }
148
148
  success(version, status) {
149
149
  const data = this.#versionData[version - 1];
150
- return status ? data[0] : ++data[0];
150
+ return status ? data.success : ++data.success;
151
151
  }
152
152
  failed(version, status) {
153
153
  const data = this.#versionData[version - 1];
154
154
  if (status) {
155
- return data[1];
155
+ return data.failed;
156
156
  }
157
- this.clearAltSvc(version);
158
- return ++data[1];
157
+ this.altSvc.clear(version);
158
+ return ++data.failed;
159
159
  }
160
160
  error(version, status) {
161
161
  const data = this.#versionData[version - 1];
162
162
  if (status) {
163
- return data[2];
163
+ return data.errors;
164
164
  }
165
- if (data[4] !== 2) {
166
- this.closeAltSvc(true);
165
+ if (data.status !== 2) {
166
+ this.altSvc.close(true);
167
167
  }
168
- return ++data[2];
168
+ return ++data.errors;
169
169
  }
170
170
  upgrade(version, altSvc) {
171
171
  if (altSvc && this.secure) {
172
172
  if (altSvc === 'clear') {
173
- this.clearAltSvc();
173
+ this.altSvc.clear();
174
174
  return;
175
175
  }
176
176
  const data = this.#versionData;
177
177
  for (let i = data.length - 1; i >= version; --i) {
178
178
  const host = data[i];
179
- if (host[4] === 0) {
179
+ if (host.status === 0) {
180
180
  continue;
181
181
  }
182
+ const h = i + 1;
182
183
  const increment = (flag) => {
183
- host[4] = flag;
184
- if (this.#version < i + 1) {
185
- this.#version = i + 1;
184
+ host.status = flag;
185
+ if (this.#version < h) {
186
+ this.#version = h;
186
187
  return true;
187
188
  }
188
189
  return false;
189
190
  };
190
- const addresses = [];
191
- const time = Date.now();
191
+ const available = [];
192
192
  const hostname = this.#hostname;
193
- const excluded = this.#altSvcError;
194
- for (const match of altSvc.matchAll(new RegExp(`h${i + 1}(?:-\\d+)?="([^:]*):(\\d+)"([^,]*)`, 'g'))) {
193
+ for (const match of altSvc.matchAll(new RegExp(`h${h}(?:-\\d+)?="([^:]*):(\\d+)"([^,]*)`, 'g'))) {
195
194
  const port = match[2];
196
195
  if (!match[1] && port === this.port) {
197
196
  increment(2);
198
- addresses.length = 0;
197
+ available.length = 0;
199
198
  break;
200
199
  }
201
200
  const address = match[1] || hostname;
202
201
  const ma = +(/ma=(\d+)/.exec(match[3])?.[1] || 86400);
203
- if (!excluded.includes(`h${i + 1}:${address}:${port}`)) {
204
- addresses.push([
205
- address,
202
+ if (this.altSvc.valid(address, port, h)) {
203
+ available.push({
204
+ hostname: address,
206
205
  port,
207
- ma >= 2592000 ? NaN : time + Math.min(ma * 1000, 2147483647),
208
- i + 1,
209
- match[3].includes('persist=1')
210
- ]);
206
+ version: h,
207
+ expires: ma >= 2592000 ? NaN : Date.now() + Math.min(ma * 1000, 2147483647),
208
+ persist: match[3].includes('persist=1')
209
+ });
211
210
  }
212
211
  }
213
- if (addresses.length > 0) {
214
- this.closeAltSvc();
215
- this.#altSvcQueue = addresses.sort((a, b) => {
216
- if (a[0] === hostname) {
212
+ if (available.length > 0) {
213
+ this.altSvc.close();
214
+ this.altSvc.available = available.sort((a, b) => {
215
+ if (a.hostname === hostname) {
217
216
  return -1;
218
217
  }
219
- if (b[0] === hostname) {
218
+ if (b.hostname === hostname) {
220
219
  return 1;
221
220
  }
222
- if (isNaN(a[2]) || a[2] > b[2]) {
221
+ if (isNaN(a.expires) || a.expires > b.expires) {
223
222
  return -1;
224
223
  }
225
- if (isNaN(b[2]) || a[2] < b[2]) {
224
+ if (isNaN(b.expires) || a.expires < b.expires) {
226
225
  return 1;
227
226
  }
228
- if (a[3] && !b[3]) {
229
- return -1;
230
- }
231
- if (!a[3] && b[3]) {
232
- return -1;
233
- }
234
227
  return 0;
235
228
  });
236
229
  if (increment(1)) {
237
- this.nextAltSvc();
230
+ this.altSvc.next();
238
231
  }
239
232
  }
240
233
  }
241
234
  }
242
235
  }
243
236
  didAltSvc(version) {
244
- return this.#versionData[version][4] !== -1;
237
+ return this.#versionData[version].status !== -1;
245
238
  }
246
239
  nextAltSvc() {
247
- const queue = this.#altSvcQueue.shift();
248
- if (queue) {
249
- const [hostname, port, expires, version] = queue;
250
- const timeout = expires - Date.now();
251
- if (timeout < 0) {
252
- return this.nextAltSvc();
253
- }
254
- let timer = null;
255
- if (!isNaN(timeout)) {
256
- timer = setTimeout(() => {
257
- if (!this.nextAltSvc()) {
258
- this.version = 1;
259
- }
260
- }, timeout);
261
- }
262
- this.#altSvc = [hostname, port, timer, version, this.protocol + '//' + hostname + ':' + port];
263
- return true;
264
- }
265
- return false;
240
+ return this.altSvc.next();
266
241
  }
267
242
  closeAltSvc(error) {
268
- const [hostname, port, timeout, version] = this.#altSvc;
269
- if (hostname) {
270
- this.#altSvc = [];
271
- if (timeout) {
272
- clearTimeout(timeout);
273
- }
274
- if (error) {
275
- this.#altSvcError.push(`h${version}:${hostname}:${port}`);
276
- return this.nextAltSvc();
277
- }
278
- }
279
- return false;
243
+ return this.altSvc.close(error);
280
244
  }
281
245
  clearAltSvc(version) {
282
- if (version) {
283
- if (this.#altSvcQueue.length === 0) {
284
- this.flagAltSvc(version, 0);
285
- }
286
- else if (!this.closeAltSvc(true)) {
287
- this.flagAltSvc(version, -1);
288
- }
289
- }
290
- else {
291
- this.closeAltSvc();
292
- this.#altSvcQueue = [];
293
- this.#altSvcError = [];
294
- for (const item of this.#versionData) {
295
- if (item[3]) {
296
- item[4] = -1;
297
- }
298
- }
299
- }
246
+ this.altSvc.clear(version);
300
247
  }
301
248
  flagAltSvc(version, value) {
302
- this.#versionData[version - 1][4] = value;
249
+ this.altSvc.flag(version, value);
303
250
  }
304
251
  reset() {
305
- this.clearAltSvc();
252
+ this.altSvc.clear();
306
253
  this.#versionData.forEach((item, index) => {
307
- item[0] = 0;
308
- item[1] = 0;
309
- item[2] = 0;
254
+ item.success = 0;
255
+ item.failed = 0;
256
+ item.errors = 0;
310
257
  if (index > 0) {
311
- item[3] = -1;
258
+ item.alpn = -1;
312
259
  }
313
260
  });
314
261
  }
262
+ v1() {
263
+ return this.#version === 1;
264
+ }
315
265
  v2() {
316
266
  return this.#version === 2;
317
267
  }
268
+ get altSvc() {
269
+ return this.#altSvc;
270
+ }
318
271
  set version(value) {
319
272
  switch (value) {
320
273
  case 1:
321
274
  case 2:
322
- this.flagAltSvc(this.#version = value, -1);
275
+ this.altSvc.flag(this.#version = value, -1);
323
276
  break;
324
277
  }
325
278
  }
@@ -333,13 +286,13 @@ class HttpHost {
333
286
  return this.#secure;
334
287
  }
335
288
  get hostname() {
336
- return this.#altSvc[0] || this.#hostname;
289
+ return this.altSvc.hostname || this.#hostname;
337
290
  }
338
291
  get port() {
339
- return this.#altSvc[1] || this.#port;
292
+ return this.altSvc.port || this.#port;
340
293
  }
341
294
  get origin() {
342
- return this.#altSvc[4] || this.#origin;
295
+ return this.altSvc.origin || this.#origin;
343
296
  }
344
297
  get streamSize() {
345
298
  return this.#streamSize;
package/index.js CHANGED
@@ -28,13 +28,14 @@ const REGEXP_GLOBWITHIN = /\\\?|(?:(?<!\\)(?:\*|\[!?[^!\]]+\]|\{(?:[^,]+,)+[^}]+
28
28
  const REGEXP_RCLONE = /^rclone:\?/i;
29
29
  const HTTP = {
30
30
  HOST: {},
31
- HEADERS: {},
31
+ HEADERS: Object.create(null),
32
+ CACHE: new WeakMap(),
32
33
  VERSION: 1,
33
34
  PROXY: null
34
35
  };
35
36
  const TLS = {
36
- TEXT: {},
37
- FILE: {}
37
+ TEXT: Object.create(null),
38
+ FILE: Object.create(null)
38
39
  };
39
40
  const DNS = {
40
41
  CACHE: Object.create(null),
@@ -61,7 +62,7 @@ const ARIA2 = {
61
62
  LOWEST_SPEED_LIMIT: null,
62
63
  ALWAYS_RESUME: false,
63
64
  FILE_ALLOCATION: 'none',
64
- PROXY: {},
65
+ PROXY: Object.create(null),
65
66
  NO_PROXY: '',
66
67
  CONF_PATH: ''
67
68
  };
@@ -115,21 +116,6 @@ let READ_TIMEOUT = 0;
115
116
  let AGENT_TIMEOUT = 0;
116
117
  let LOG_HTTP = false;
117
118
  let LOG_TIMEPROCESS = true;
118
- function getBaseHeaders(uri, headers) {
119
- let result;
120
- uri = (0, util_1.trimPath)(uri);
121
- for (const pathname in headers) {
122
- if (pathname === uri || uri.startsWith(pathname + '/')) {
123
- (result ||= []).push([pathname, headers[pathname]]);
124
- }
125
- }
126
- if (result) {
127
- if (result.length > 1) {
128
- result.sort((a, b) => b[0].length - a[0].length);
129
- }
130
- return result[0][1];
131
- }
132
- }
133
119
  function setDnsCache(hostname, value, expires) {
134
120
  expires ??= DNS.EXPIRES;
135
121
  if (expires > 0 && !DNS.CACHE[hostname]) {
@@ -146,11 +132,6 @@ function setDnsCache(hostname, value, expires) {
146
132
  }
147
133
  }
148
134
  }
149
- function setOutgoingHeaders(output, headers) {
150
- for (const href in headers) {
151
- output[href] = (0, util_1.normalizeHeaders)(headers[href]);
152
- }
153
- }
154
135
  function getProxySettings(request, agentTimeout) {
155
136
  const proxy = request.proxy;
156
137
  if (proxy && (proxy.origin || proxy.address && proxy.port)) {
@@ -175,11 +156,9 @@ function getProxySettings(request, agentTimeout) {
175
156
  return null;
176
157
  }
177
158
  function closeTorrent(pid) {
178
- if (typeof pid === 'number') {
179
- const index = ARIA2.PID_QUEUE.findIndex(value => pid === value[0]);
180
- if (index !== -1) {
181
- ARIA2.PID_QUEUE.splice(index, 1);
182
- }
159
+ const index = ARIA2.PID_QUEUE.findIndex(value => value[0] === pid);
160
+ if (index !== -1) {
161
+ ARIA2.PID_QUEUE.splice(index, 1);
183
162
  }
184
163
  }
185
164
  function clearDnsLookup() {
@@ -199,9 +178,9 @@ function resetHttpHost(version) {
199
178
  case 2:
200
179
  for (const origin in HTTP.HOST) {
201
180
  const host = HTTP.HOST[origin];
202
- if (host.secure && host.version === 1) {
181
+ if (host.secure && host.v1()) {
203
182
  const failed = host.failed(2, true);
204
- if (failed === 0 && host.failed(2, true) < 10 || failed < 3 && host.success(2, true) > 0) {
183
+ if (failed === 0 && host.error(2, true) < 10 || failed < 3 && host.success(2, true) > 0) {
205
184
  host.version = version;
206
185
  }
207
186
  }
@@ -296,50 +275,16 @@ function copySearchParams(url, base) {
296
275
  }
297
276
  });
298
277
  }
299
- function checkEncoding(request, response, statusCode, outStream, contentEncoding) {
278
+ function checkEncoding(request, statusCode, contentEncoding) {
300
279
  switch (statusCode) {
301
280
  case 206:
302
281
  request.emit('error', (0, types_1.errorValue)("Aborted", 'Partial content'));
303
282
  case 204:
304
283
  case 205:
305
284
  case 304:
306
- return;
307
- }
308
- if (!contentEncoding) {
309
- return;
310
- }
311
- contentEncoding = contentEncoding.trim().toLowerCase();
312
- const chunkSize = outStream?.writableHighWaterMark;
313
- let pipeTo;
314
- if (!contentEncoding.includes(',')) {
315
- pipeTo = decompressEncoding(contentEncoding, chunkSize);
316
- }
317
- else {
318
- for (const value of contentEncoding.split(/\s*,\s*/).reverse()) {
319
- const next = decompressEncoding(value, chunkSize);
320
- if (!next) {
321
- return;
322
- }
323
- pipeTo = pipeTo ? pipeTo.pipe(next) : next;
324
- }
325
- }
326
- if (pipeTo) {
327
- if (outStream) {
328
- stream.pipeline(response, pipeTo, outStream, err => {
329
- if (err) {
330
- response.emit('error', err);
331
- }
332
- });
333
- }
334
- else {
335
- stream.pipeline(response, pipeTo, err => {
336
- if (err) {
337
- response.emit('error', err);
338
- }
339
- });
340
- }
341
- return pipeTo;
285
+ return false;
342
286
  }
287
+ return !!contentEncoding;
343
288
  }
344
289
  function sendBody(request, options) {
345
290
  const postData = options.postData;
@@ -351,26 +296,11 @@ function sendBody(request, options) {
351
296
  }
352
297
  request.end();
353
298
  }
354
- function decompressEncoding(value, chunkSize) {
355
- switch (value) {
356
- case 'gzip':
357
- return zlib.createGunzip({ chunkSize });
358
- case 'br':
359
- return zlib.createBrotliDecompress({ chunkSize });
360
- case 'deflate':
361
- return zlib.createInflate({ chunkSize });
362
- case 'deflate-raw':
363
- return zlib.createInflateRaw({ chunkSize });
364
- case 'zstd':
365
- if (SUPPORTED_ZSTD) {
366
- return zlib.createZstdDecompress({ chunkSize });
367
- }
368
- break;
369
- }
370
- }
371
299
  function resetAria2() {
372
- clearInterval(ARIA2.PID_TIMER);
373
- ARIA2.PID_TIMER = null;
300
+ if (ARIA2.PID_TIMER) {
301
+ clearInterval(ARIA2.PID_TIMER);
302
+ ARIA2.PID_TIMER = null;
303
+ }
374
304
  }
375
305
  function escapeShellQuote(value) {
376
306
  value = value.replace(/(?<!\\)"/g, '\\"');
@@ -464,32 +394,7 @@ function setBinHeaders(args, headers) {
464
394
  args.push(...items.map(value => `--header="${name}: ${escapeShellQuote(value)}"`));
465
395
  }
466
396
  }
467
- function finalizeBinArgs(instance, name, bin, args, opts, binOpts, cmd = []) {
468
- if (binOpts) {
469
- for (const leading of binOpts) {
470
- if (leading.startsWith('-')) {
471
- const pattern = new RegExp(`^${leading}(?:=|$)`);
472
- for (let i = 0; i < opts.length; ++i) {
473
- if (pattern.test(opts[i])) {
474
- opts.splice(i--, i);
475
- break;
476
- }
477
- }
478
- }
479
- }
480
- args = binOpts.concat(args);
481
- }
482
- if (args.length > 0) {
483
- if (module_1.hasLogType(32768)) {
484
- instance.formatMessage(32768, name.toUpperCase(), [bin].concat(cmd).join(' '), args.join(' '), { ...module_1.LOG_STYLE_WARN });
485
- }
486
- else {
487
- instance.addLog(4, path.basename(bin) + ' ' + args.join(' '), name);
488
- }
489
- }
490
- return args;
491
- }
492
- function addAria2Proxy(args, protocol, host) {
397
+ function appendAria2Proxy(args, protocol, host) {
493
398
  const { origin, username, password } = host;
494
399
  args.push(`--${protocol}-proxy="${origin}"`);
495
400
  if (username) {
@@ -509,8 +414,7 @@ function createAgentOptions(options) {
509
414
  }
510
415
  }
511
416
  const hasResponse = (value) => value >= 200 && (value < 300 || value === 304);
512
- const isDirEnd = (value) => value.endsWith('/') || value.endsWith(path.sep);
513
- const trimCharEnd = (value) => value.substring(0, value.length - 1);
417
+ const isDirEnd = (value) => (value = value.at(-1), value === '/' || value === path.sep);
514
418
  const configureDns = (family, options) => family === 0 ? options : { family, hints: family === 6 ? dns.V4MAPPED : 0 };
515
419
  const ignoreOpt = (opts, ...values) => !opts?.some(opt => values.some(value => opt === value || opt.startsWith(value + '=')));
516
420
  const escapeQuote = (value) => value.replace(/[\\"]/g, capture => '\\' + capture);
@@ -583,7 +487,7 @@ class Request extends module_1 {
583
487
  try {
584
488
  for (const uri in proxy) {
585
489
  const host = new URL(uri);
586
- ARIA2.PROXY[trimCharEnd(host.protocol)] = { host };
490
+ ARIA2.PROXY[host.protocol.slice(0, -1)] = { host };
587
491
  }
588
492
  }
589
493
  catch {
@@ -686,7 +590,7 @@ class Request extends module_1 {
686
590
  if ((read_timeout = (0, util_1.fromSeconds)(read_timeout)) >= 0) {
687
591
  READ_TIMEOUT = read_timeout;
688
592
  }
689
- if ((0, types_1.isObject)(agent)) {
593
+ if (agent) {
690
594
  let { keep_alive, timeout, proxy_env } = agent;
691
595
  if ((agent_timeout = (0, util_1.fromSeconds)(timeout)) > 0) {
692
596
  AGENT_TIMEOUT = agent_timeout;
@@ -715,8 +619,10 @@ class Request extends module_1 {
715
619
  break;
716
620
  }
717
621
  }
718
- if ((0, types_1.isPlainObject)(headers)) {
719
- setOutgoingHeaders(HTTP.HEADERS, headers);
622
+ for (const href in headers) {
623
+ if ((0, types_1.isObject)(headers[href])) {
624
+ HTTP.HEADERS[href] = (0, util_1.normalizeHeaders)(headers[href]);
625
+ }
720
626
  }
721
627
  if ((0, types_1.isPlainObject)(certs)) {
722
628
  [TLS.TEXT, TLS.FILE] = validateCerts(certs);
@@ -827,9 +733,9 @@ class Request extends module_1 {
827
733
  };
828
734
  #singleton = false;
829
735
  #httpVersion = null;
736
+ #headers = null;
830
737
  #ipVersion;
831
738
  #agentTimeout;
832
- #headers = null;
833
739
  #baseUrl = null;
834
740
  #connectDns = Object.create(null);
835
741
  #pendingDns = Object.create(null);
@@ -839,7 +745,7 @@ class Request extends module_1 {
839
745
  #adapter = HTTP_ADAPTER;
840
746
  #certs = null;
841
747
  #downloading = new Set();
842
- #hostInfo = {};
748
+ #hostInfo = Object.create(null);
843
749
  #session = [Object.create(null)];
844
750
  constructor(data) {
845
751
  super();
@@ -865,9 +771,7 @@ class Request extends module_1 {
865
771
  if (proxy) {
866
772
  this.proxy = proxy;
867
773
  }
868
- if ((0, types_1.isObject)(headers)) {
869
- setOutgoingHeaders(this.#headers = {}, headers);
870
- }
774
+ this.parseHeaders(headers);
871
775
  if ((0, types_1.isObject)(certs)) {
872
776
  this.#certs = validateCerts(certs);
873
777
  }
@@ -975,9 +879,7 @@ class Request extends module_1 {
975
879
  init(config) {
976
880
  if (config) {
977
881
  const { headers, httpVersion, ipVersion, readTimeout } = config;
978
- if ((0, types_1.isObject)(headers)) {
979
- setOutgoingHeaders(this.#headers ||= {}, headers);
980
- }
882
+ this.parseHeaders(headers);
981
883
  if (httpVersion !== undefined) {
982
884
  this.httpVersion = httpVersion;
983
885
  }
@@ -1201,8 +1103,7 @@ class Request extends module_1 {
1201
1103
  }
1202
1104
  }
1203
1105
  headersOf(uri) {
1204
- const headers = this.#headers;
1205
- return headers && getBaseHeaders(uri, headers) || (this.host ? getBaseHeaders(uri, HTTP.HEADERS) : undefined);
1106
+ return this.findHeadersByUri(uri) || (this.host ? this.findHeadersByUri(uri, HTTP.HEADERS) : undefined);
1206
1107
  }
1207
1108
  async aria2c(uri, options = {}) {
1208
1109
  if (!ARIA2.BIN) {
@@ -1219,15 +1120,10 @@ class Request extends module_1 {
1219
1120
  ({ signal, silent } = options);
1220
1121
  ({ pathname, headers, binOpts } = this.parseBinOpts(options, ['--daemon'], ['--input-file']));
1221
1122
  }
1222
- try {
1223
- if (typeof uri === 'string' && module_1.isURL(uri)) {
1224
- uri = new URL(uri);
1225
- }
1226
- pathname = checkBinTarget(this, "aria2", uri, pathname, 'aria2', binOpts);
1227
- }
1228
- catch (err) {
1229
- return Promise.reject(err);
1123
+ if ((0, types_1.isString)(uri) && module_1.isURL(uri)) {
1124
+ uri = new URL(uri);
1230
1125
  }
1126
+ pathname = checkBinTarget(this, "aria2", uri, pathname, 'aria2', binOpts);
1231
1127
  silent ??= this.#singleton;
1232
1128
  return new Promise((resolve, reject) => {
1233
1129
  let protocol, origin, username, password;
@@ -1322,7 +1218,7 @@ class Request extends module_1 {
1322
1218
  }
1323
1219
  }
1324
1220
  }
1325
- switch (protocol = trimCharEnd(protocol)) {
1221
+ switch (protocol = protocol.slice(0, -1)) {
1326
1222
  case 'http':
1327
1223
  case 'https':
1328
1224
  if (ignoreOpt(binOpts, '--http-no-cache')) {
@@ -1346,12 +1242,12 @@ class Request extends module_1 {
1346
1242
  }
1347
1243
  }
1348
1244
  if (proxy) {
1349
- addAria2Proxy(args, protocol, proxy.host);
1245
+ appendAria2Proxy(args, protocol, proxy.host);
1350
1246
  }
1351
1247
  else if (ARIA2.PROXY.all) {
1352
- addAria2Proxy(opts, 'all', ARIA2.PROXY.all.host);
1248
+ appendAria2Proxy(opts, 'all', ARIA2.PROXY.all.host);
1353
1249
  }
1354
- args = finalizeBinArgs(this, "aria2", ARIA2.BIN, args, opts, binOpts);
1250
+ args = this.mergeBinOpts(args, opts, binOpts, { name: "aria2", bin: ARIA2.BIN });
1355
1251
  opts.push(`"${escapeShellQuote(uri)}"`);
1356
1252
  args = args.concat(init, opts);
1357
1253
  const startTime = Date.now();
@@ -1492,12 +1388,7 @@ class Request extends module_1 {
1492
1388
  default:
1493
1389
  return Promise.reject((0, types_1.errorMessage)("rclone", "Invalid command", command || uri));
1494
1390
  }
1495
- try {
1496
- pathname = checkBinTarget(this, "rclone", uri, pathname, command, binOpts);
1497
- }
1498
- catch (err) {
1499
- return Promise.reject(err);
1500
- }
1391
+ pathname = checkBinTarget(this, "rclone", uri, pathname, command, binOpts);
1501
1392
  silent ??= this.#singleton;
1502
1393
  return new Promise((resolve, reject) => {
1503
1394
  const init = [
@@ -1640,7 +1531,7 @@ class Request extends module_1 {
1640
1531
  setBinHeaders(args, headers);
1641
1532
  const cwd = module_1.isDir(pathname) ? pathname : path.dirname(pathname);
1642
1533
  const cmd = [source, pathname];
1643
- args = finalizeBinArgs(this, "rclone", RCLONE.BIN, args, opts, binOpts, cmd).concat(init, opts);
1534
+ args = this.mergeBinOpts(args, opts, binOpts, { name: "rclone", bin: RCLONE.BIN, cmd }).concat(init, opts);
1644
1535
  args.push(...cmd.map(value => (0, types_1.sanitizeCmd)(value)));
1645
1536
  args.unshift(command);
1646
1537
  const startTime = Date.now();
@@ -1866,14 +1757,20 @@ class Request extends module_1 {
1866
1757
  }
1867
1758
  connected = true;
1868
1759
  if (this.matchStatus(statusCode, url, response, request, options) && hasResponse(statusCode)) {
1869
- if (emitter = checkEncoding(request, request, statusCode, outStream, response['content-encoding'])) {
1870
- for (const event in listenerMap) {
1871
- const [name, type] = event.split('-');
1872
- for (const listener of listenerMap[event]) {
1873
- if (name !== 'error') {
1874
- request.removeListener(name, listener);
1760
+ const contentEncoding = response['content-encoding'];
1761
+ if (checkEncoding(request, statusCode, contentEncoding) && (emitter = this.pipeline(request, contentEncoding, outStream))) {
1762
+ if (typeof emitter === 'number') {
1763
+ this.abortHeaders(request, options, { href: url.href, statusCode: emitter, message: (0, util_1.fromStatusCode)(emitter) });
1764
+ }
1765
+ else {
1766
+ for (const event in listenerMap) {
1767
+ const [name, type] = event.split('-');
1768
+ for (const listener of listenerMap[event]) {
1769
+ if (name !== 'error') {
1770
+ request.removeListener(name, listener);
1771
+ }
1772
+ emitter[type](name === 'end' ? 'finish' : name, listener);
1875
1773
  }
1876
- emitter[type](name === 'end' ? 'finish' : name, listener);
1877
1774
  }
1878
1775
  }
1879
1776
  }
@@ -1942,7 +1839,7 @@ class Request extends module_1 {
1942
1839
  if (proxy) {
1943
1840
  keepAlive ??= proxy.keepAlive;
1944
1841
  agentTimeout ??= proxy.agentTimeout;
1945
- const proxyHeaders = this.#headers && getBaseHeaders(proxy.host.href, this.#headers) || getBaseHeaders(proxy.host.href, HTTP.HEADERS);
1842
+ const proxyHeaders = this.findHeadersByUri(proxy.host.href) || this.findHeadersByUri(proxy.host.href, HTTP.HEADERS);
1946
1843
  const pkg = secure ? 'https-proxy-agent' : 'http-proxy-agent';
1947
1844
  try {
1948
1845
  agent = require(pkg)(proxy.host, keepAlive === true || keepAlive === false && agentTimeout !== 0 || agentTimeout > 0 ? { ...agentOptions, keepAlive: keepAlive ?? true, timeout: agentTimeout, headers: proxyHeaders } : { ...agentOptions, headers: proxyHeaders });
@@ -1993,11 +1890,17 @@ class Request extends module_1 {
1993
1890
  const statusCode = response.statusCode;
1994
1891
  const incoming = response.headers;
1995
1892
  if (!expectContinue && this.matchStatus(statusCode, url, incoming, request, options) && (getting || posting) && hasResponse(statusCode)) {
1996
- let source = checkEncoding(request, response, statusCode, outStream, incoming['content-encoding']);
1997
- if (source) {
1998
- source.once('finish', () => {
1999
- request.emit('end');
2000
- });
1893
+ const contentEncoding = incoming['content-encoding'];
1894
+ let source;
1895
+ if (checkEncoding(request, statusCode, contentEncoding) && (source = this.pipeline(response, contentEncoding, outStream))) {
1896
+ if (typeof source === 'number') {
1897
+ this.abortHeaders(request, options, { href: url.href, statusCode: source, message: (0, util_1.fromStatusCode)(source) });
1898
+ }
1899
+ else {
1900
+ source.once('finish', () => {
1901
+ request.emit('end');
1902
+ });
1903
+ }
2001
1904
  }
2002
1905
  else {
2003
1906
  if (encoding) {
@@ -2042,7 +1945,7 @@ class Request extends module_1 {
2042
1945
  if (version === 2 && incoming.upgrade?.includes('h2')) {
2043
1946
  host.version = 2;
2044
1947
  }
2045
- else if (!host.didAltSvc(1)) {
1948
+ else if (!host.altSvc.did(1)) {
2046
1949
  host.upgrade(1, incoming['alt-svc']);
2047
1950
  }
2048
1951
  }
@@ -2162,13 +2065,7 @@ class Request extends module_1 {
2162
2065
  else {
2163
2066
  options = {};
2164
2067
  }
2165
- const headers = (0, util_1.parseOutgoingHeaders)(options.headers) || {};
2166
- for (const attr in headers) {
2167
- const name = attr.toLowerCase();
2168
- if (name === 'content-type' || name === 'content-length') {
2169
- delete headers[attr];
2170
- }
2171
- }
2068
+ const headers = (0, util_1.parseOutgoingHeaders)(options.headers, 'content-type', 'content-length') || {};
2172
2069
  if (!putting && (parts || contentType === "multipart/form-data" || contentType === 'form-data')) {
2173
2070
  let valid = false;
2174
2071
  if ((0, types_1.isArray)(parts)) {
@@ -2195,9 +2092,18 @@ class Request extends module_1 {
2195
2092
  addValue(name, data[name]);
2196
2093
  }
2197
2094
  }
2198
- for (let { name, data: target, value, contentType: type, filename } of parts) {
2199
- if (!name) {
2200
- continue;
2095
+ for (const part of parts) {
2096
+ let name, target, value, type, filename;
2097
+ if ((0, types_1.isPlainObject)(part)) {
2098
+ ({ name, data: target, value, contentType: type, filename } = part);
2099
+ }
2100
+ else if ((0, types_1.supported)(18, 13) && part instanceof File) {
2101
+ name = part.name;
2102
+ type = part.type;
2103
+ target = Buffer.from(await part.arrayBuffer());
2104
+ }
2105
+ if (!(0, types_1.isString)(name)) {
2106
+ throw (0, types_1.errorValue)('Name of file part was invalid', uri.toString());
2201
2107
  }
2202
2108
  if (target) {
2203
2109
  if (typeof target === 'string') {
@@ -2317,6 +2223,59 @@ class Request extends module_1 {
2317
2223
  });
2318
2224
  this.#downloading.clear();
2319
2225
  }
2226
+ pipeline(response, encoding, outStream) {
2227
+ const chunkSize = outStream?.writableHighWaterMark;
2228
+ let pipeTo;
2229
+ encoding = encoding.trim().toLowerCase();
2230
+ if (!encoding.includes(',')) {
2231
+ pipeTo = this.fromEncoding(encoding, { chunkSize });
2232
+ }
2233
+ else {
2234
+ for (const value of encoding.split(/\s*,\s*/).reverse()) {
2235
+ const next = this.fromEncoding(value, { chunkSize });
2236
+ if (!next) {
2237
+ pipeTo = undefined;
2238
+ break;
2239
+ }
2240
+ pipeTo = pipeTo ? pipeTo.pipe(next) : next;
2241
+ }
2242
+ }
2243
+ if (!pipeTo) {
2244
+ return 415;
2245
+ }
2246
+ if (outStream) {
2247
+ stream.pipeline(response, pipeTo, outStream, err => {
2248
+ if (err) {
2249
+ response.emit('error', err);
2250
+ }
2251
+ });
2252
+ }
2253
+ else {
2254
+ stream.pipeline(response, pipeTo, err => {
2255
+ if (err) {
2256
+ response.emit('error', err);
2257
+ }
2258
+ });
2259
+ }
2260
+ return pipeTo;
2261
+ }
2262
+ fromEncoding(value, options) {
2263
+ switch (value) {
2264
+ case 'gzip':
2265
+ return zlib.createGunzip(options);
2266
+ case 'br':
2267
+ return zlib.createBrotliDecompress(options);
2268
+ case 'deflate':
2269
+ return zlib.createInflate(options);
2270
+ case 'deflate-raw':
2271
+ return zlib.createInflateRaw(options);
2272
+ case 'zstd':
2273
+ if (SUPPORTED_ZSTD) {
2274
+ return zlib.createZstdDecompress(options);
2275
+ }
2276
+ break;
2277
+ }
2278
+ }
2320
2279
  matchStatus(code, url, headers, request, options) {
2321
2280
  const status = this.#statusOn?.get(code);
2322
2281
  if (status) {
@@ -2395,7 +2354,7 @@ class Request extends module_1 {
2395
2354
  }
2396
2355
  if ((0, types_1.isArray)(options.binOpts)) {
2397
2356
  let next = false;
2398
- binOpts = options.binOpts.filter((opt) => !((0, types_1.isString)(opt) && /^-[a-z].*$/i.test(opt.trim()))).map((opt) => {
2357
+ binOpts = options.binOpts.filter((opt) => !((0, types_1.isString)(opt) && /^-[a-z].*$/i.test(opt))).map((opt) => {
2399
2358
  if (next) {
2400
2359
  if (!module_1.asString(opt).startsWith('--')) {
2401
2360
  return [];
@@ -2445,6 +2404,58 @@ class Request extends module_1 {
2445
2404
  }
2446
2405
  return { pathname, headers: (0, util_1.parseOutgoingHeaders)(options.headers), binOpts };
2447
2406
  }
2407
+ mergeBinOpts(args, opts, binOpts, options) {
2408
+ if (binOpts) {
2409
+ for (const leading of binOpts) {
2410
+ if (leading.charAt(0) === '-') {
2411
+ const pattern = new RegExp(`^${leading}(?:=|$)`);
2412
+ for (let i = 0; i < opts.length; ++i) {
2413
+ if (pattern.test(opts[i])) {
2414
+ opts.splice(i--, i);
2415
+ break;
2416
+ }
2417
+ }
2418
+ }
2419
+ }
2420
+ args = binOpts.concat(args);
2421
+ }
2422
+ if (options && args.length > 0) {
2423
+ const { name, bin, cmd = [] } = options;
2424
+ if (module_1.hasLogType(32768)) {
2425
+ this.formatMessage(32768, name.toUpperCase(), [bin].concat(cmd).join(' '), args.join(' '), { ...module_1.LOG_STYLE_WARN });
2426
+ }
2427
+ else {
2428
+ this.addLog(4, path.basename(bin) + ' ' + args.join(' '), name);
2429
+ }
2430
+ }
2431
+ return args;
2432
+ }
2433
+ parseHeaders(outgoing) {
2434
+ if ((0, types_1.isPlainObject)(outgoing)) {
2435
+ Object.assign(this.#headers ||= {}, outgoing);
2436
+ }
2437
+ }
2438
+ findHeadersByUri(uri, outgoing = this.#headers) {
2439
+ if (outgoing) {
2440
+ const data = [];
2441
+ uri = (0, util_1.trimPath)(uri);
2442
+ for (const pathname in outgoing) {
2443
+ if (pathname === uri || uri.startsWith(pathname + '/')) {
2444
+ data.push([pathname, outgoing[pathname]]);
2445
+ }
2446
+ }
2447
+ if (data.length > 0) {
2448
+ data.sort((a, b) => b[0].length - a[0].length);
2449
+ const headers = data[0][1];
2450
+ let result = HTTP.CACHE.get(headers);
2451
+ if (!result) {
2452
+ result = (0, util_1.normalizeHeaders)(headers);
2453
+ HTTP.CACHE.set(headers, result);
2454
+ }
2455
+ return result;
2456
+ }
2457
+ }
2458
+ }
2448
2459
  set adapter(value) {
2449
2460
  if (adapter_1.constructorOf(value)) {
2450
2461
  this.#adapter = value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e-mc/request",
3
- "version": "0.13.6",
3
+ "version": "0.13.8",
4
4
  "description": "Request constructor for E-mc.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -19,8 +19,8 @@
19
19
  "license": "BSD-3-Clause",
20
20
  "homepage": "https://github.com/anpham6/e-mc#readme",
21
21
  "dependencies": {
22
- "@e-mc/module": "0.13.6",
23
- "@e-mc/types": "0.13.6",
22
+ "@e-mc/module": "0.13.8",
23
+ "@e-mc/types": "0.13.8",
24
24
  "combined-stream": "^1.0.8",
25
25
  "js-yaml": "^4.1.1",
26
26
  "picomatch": "^4.0.3",
package/util.d.ts CHANGED
@@ -6,15 +6,15 @@ import type { Readable, Writable } from 'node:stream';
6
6
 
7
7
  declare namespace util {
8
8
  function parseHeader<T = unknown>(headers: IncomingHttpHeaders, name: string): T | undefined;
9
- function parseOutgoingHeaders(headers: OutgoingHttpHeaders | Headers | undefined): OutgoingHttpHeaders | undefined;
10
- function normalizeHeaders(headers: OutgoingHttpHeaders): OutgoingHttpHeaders;
9
+ function parseOutgoingHeaders(headers: OutgoingHttpHeaders | Headers | undefined, ...ignore: string[]): OutgoingHttpHeaders | undefined;
10
+ function normalizeHeaders(headers: OutgoingHttpHeaders | Headers): OutgoingHttpHeaders;
11
11
  function getBasicAuth(auth: AuthValue): string;
12
12
  function getBasicAuth(username: unknown, password?: unknown): string;
13
13
  function hasBasicAuth(value: string): boolean;
14
14
  function checkRetryable(err: unknown): boolean;
15
15
  function isRetryable(value: number, timeout?: boolean): boolean;
16
- function parseHttpProxy(value?: string): HttpProxySettings | undefined;
17
- function trimPath(value: string): string;
16
+ function parseHttpProxy(value?: string, ignoreEnv?: boolean): HttpProxySettings | undefined;
17
+ function trimPath(value: string, char?: string): string;
18
18
  function asInt(value: unknown): number;
19
19
  function asFloat(value: unknown): number;
20
20
  function fromSeconds(value: unknown): number;
package/util.js CHANGED
@@ -25,45 +25,63 @@ const node_util_1 = require("node:util");
25
25
  const types_1 = require("@e-mc/types");
26
26
  const module_1 = require("@e-mc/module");
27
27
  const host_1 = require("@e-mc/request/http/host");
28
+ function setHeader(result, key, value) {
29
+ if (key === 'set-cookie') {
30
+ (result[key] ||= []).push(value);
31
+ }
32
+ else {
33
+ result[key] = value;
34
+ }
35
+ }
28
36
  const safeInt = (value) => value >= 0 ? Math.min(value, Number.MAX_SAFE_INTEGER) : NaN;
29
37
  function parseHeader(headers, name) {
30
38
  const value = headers[name];
31
- if (!value) {
32
- return;
33
- }
34
39
  switch (name.toLowerCase()) {
35
- case 'content-disposition': {
36
- let result;
37
- for (const match of value.matchAll(/\bfilename(?:\*\s*=\s*UTF-8''([^\s;]+)|\s*=\s*(?:"([^"]+)"|([^\s;]+)))/gi)) {
38
- if (match[1]) {
39
- return decodeURIComponent(match[1]).trim();
40
+ case 'content-disposition':
41
+ if ((0, types_1.isString)(value)) {
42
+ let result;
43
+ for (const match of value.matchAll(/\bfilename(?:\*\s*=\s*UTF-8''([^\s;]+)|\s*=\s*(?:"([^"]+)"|([^\s;]+)))/gi)) {
44
+ if (match[1]) {
45
+ return decodeURIComponent(match[1]).trim();
46
+ }
47
+ result = (match[2] || match[3]).trim();
40
48
  }
41
- result = (match[2] || match[3]).trim();
49
+ return result;
42
50
  }
43
- return result;
44
- }
51
+ break;
45
52
  }
46
53
  }
47
- function parseOutgoingHeaders(headers) {
54
+ function parseOutgoingHeaders(headers, ...ignore) {
48
55
  if (!headers) {
49
56
  return;
50
57
  }
51
58
  if (headers instanceof Headers) {
52
- const result = {};
59
+ const result = Object.create(null);
53
60
  headers.forEach((value, key) => {
54
- if (key === 'set-cookie') {
55
- (result[key] ||= []).push(value);
56
- }
57
- else {
58
- result[key] = value;
61
+ if (!ignore.includes(key)) {
62
+ setHeader(result, key, value);
59
63
  }
60
64
  });
61
65
  return result;
62
66
  }
67
+ if (ignore.length > 0) {
68
+ const result = {};
69
+ for (const attr in headers) {
70
+ const name = attr.toLowerCase();
71
+ if (!ignore.includes(name)) {
72
+ result[name] = headers[attr];
73
+ }
74
+ }
75
+ return result;
76
+ }
63
77
  return { ...headers };
64
78
  }
65
79
  function normalizeHeaders(headers) {
66
80
  const result = Object.create(null);
81
+ if (headers instanceof Headers) {
82
+ headers.forEach((value, key) => setHeader(result, key, value));
83
+ return result;
84
+ }
67
85
  for (const name in headers) {
68
86
  let value = headers[name];
69
87
  switch (typeof value) {
@@ -182,9 +200,11 @@ function parseHttpProxy(value, ignoreEnv) {
182
200
  }
183
201
  }
184
202
  }
185
- function trimPath(value) {
186
- const length = value.length - 1;
187
- return value[length] === '/' ? value.substring(0, length) : value;
203
+ function trimPath(value, char = '/') {
204
+ while (value.at(-1) === char) {
205
+ value = value.slice(0, -1);
206
+ }
207
+ return value;
188
208
  }
189
209
  function asInt(value) {
190
210
  switch (typeof value) {