urllib 3.17.1 → 3.18.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "urllib",
3
- "version": "3.17.1",
3
+ "version": "3.18.0",
4
4
  "publishConfig": {
5
5
  "tag": "latest"
6
6
  },
@@ -68,6 +68,7 @@
68
68
  "formstream": "^1.1.1",
69
69
  "mime-types": "^2.1.35",
70
70
  "pump": "^3.0.0",
71
+ "qs": "^6.11.2",
71
72
  "undici": "^5.22.1",
72
73
  "ylru": "^1.3.2"
73
74
  },
@@ -77,6 +78,7 @@
77
78
  "@types/mime-types": "^2.1.1",
78
79
  "@types/node": "^20.2.1",
79
80
  "@types/pump": "^1.1.1",
81
+ "@types/qs": "^6.9.7",
80
82
  "@types/selfsigned": "^2.0.1",
81
83
  "@types/tar-stream": "^2.2.2",
82
84
  "@vitest/coverage-v8": "^0.32.0",
package/src/HttpAgent.ts CHANGED
@@ -37,8 +37,11 @@ export class HttpAgent extends Agent {
37
37
  /* eslint node/prefer-promises/dns: off*/
38
38
  const _lookup = options.lookup ?? dns.lookup;
39
39
  const lookup: LookupFunction = (hostname, dnsOptions, callback) => {
40
- _lookup(hostname, dnsOptions, (err, address, family) => {
41
- if (err) return callback(err, address, family);
40
+ _lookup(hostname, dnsOptions, (err, ...args: any[]) => {
41
+ // address will be array on Node.js >= 20
42
+ const address = args[0];
43
+ const family = args[1];
44
+ if (err) return (callback as any)(err, address, family);
42
45
  if (options.checkAddress) {
43
46
  // dnsOptions.all set to default on Node.js >= 20, dns.lookup will return address array object
44
47
  if (typeof address === 'string') {
@@ -55,7 +58,7 @@ export class HttpAgent extends Agent {
55
58
  }
56
59
  }
57
60
  }
58
- callback(err, address, family);
61
+ (callback as any)(err, address, family);
59
62
  });
60
63
  };
61
64
  super({
package/src/HttpClient.ts CHANGED
@@ -26,6 +26,7 @@ import { FormData as FormDataNode } from 'formdata-node';
26
26
  import { FormDataEncoder } from 'form-data-encoder';
27
27
  import createUserAgent from 'default-user-agent';
28
28
  import mime from 'mime-types';
29
+ import qs from 'qs';
29
30
  import pump from 'pump';
30
31
  // Compatible with old style formstream
31
32
  import FormStream from 'formstream';
@@ -58,6 +59,8 @@ function noop() {
58
59
  }
59
60
 
60
61
  const debug = debuglog('urllib:HttpClient');
62
+ // Node.js 14 or 16
63
+ const isNode14Or16 = /v1[46]\./.test(process.version);
61
64
 
62
65
  export type ClientOptions = {
63
66
  defaultArgs?: RequestOptions;
@@ -85,7 +88,7 @@ export type ClientOptions = {
85
88
  rejectUnauthorized?: boolean;
86
89
 
87
90
  /**
88
- * sockePath string | null (optional) - Default: null - An IPC endpoint, either Unix domain socket or Windows named pipe
91
+ * socketPath string | null (optional) - Default: null - An IPC endpoint, either Unix domain socket or Windows named pipe
89
92
  */
90
93
  socketPath?: string | null;
91
94
  },
@@ -242,14 +245,14 @@ export class HttpClient extends EventEmitter {
242
245
  // the response body and trailers have been received
243
246
  contentDownload: 0,
244
247
  };
245
- const orginalOpaque = args.opaque;
248
+ const originalOpaque = args.opaque;
246
249
  // using opaque to diagnostics channel, binding request and socket
247
250
  const internalOpaque = {
248
251
  [symbols.kRequestId]: requestId,
249
252
  [symbols.kRequestStartTime]: requestStartTime,
250
253
  [symbols.kEnableRequestTiming]: !!args.timing,
251
254
  [symbols.kRequestTiming]: timing,
252
- [symbols.kRequestOrginalOpaque]: orginalOpaque,
255
+ [symbols.kRequestOriginalOpaque]: originalOpaque,
253
256
  };
254
257
  const reqMeta = {
255
258
  requestId,
@@ -450,10 +453,17 @@ export class HttpClient extends EventEmitter {
450
453
  || isReadable(args.data);
451
454
  if (isGETOrHEAD) {
452
455
  if (!isStringOrBufferOrReadable) {
453
- for (const field in args.data) {
454
- const fieldValue = args.data[field];
455
- if (fieldValue === undefined) continue;
456
- requestUrl.searchParams.append(field, fieldValue);
456
+ if (args.nestedQuerystring) {
457
+ const querystring = qs.stringify(args.data);
458
+ // reset the requestUrl
459
+ const href = requestUrl.href;
460
+ requestUrl = new URL(href + (href.includes('?') ? '&' : '?') + querystring);
461
+ } else {
462
+ for (const field in args.data) {
463
+ const fieldValue = args.data[field];
464
+ if (fieldValue === undefined) continue;
465
+ requestUrl.searchParams.append(field, fieldValue);
466
+ }
457
467
  }
458
468
  }
459
469
  } else {
@@ -470,7 +480,11 @@ export class HttpClient extends EventEmitter {
470
480
  }
471
481
  } else {
472
482
  headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
473
- requestOptions.body = new URLSearchParams(args.data).toString();
483
+ if (args.nestedQuerystring) {
484
+ requestOptions.body = qs.stringify(args.data);
485
+ } else {
486
+ requestOptions.body = new URLSearchParams(args.data).toString();
487
+ }
474
488
  }
475
489
  }
476
490
  }
@@ -543,6 +557,9 @@ export class HttpClient extends EventEmitter {
543
557
  res = Object.assign(response.body, res);
544
558
  }
545
559
  } else if (args.writeStream) {
560
+ if (isNode14Or16 && args.writeStream.destroyed) {
561
+ throw new Error('writeStream is destroyed');
562
+ }
546
563
  if (args.compressed === true && isCompressedContent) {
547
564
  const decoder = contentEncoding === 'gzip' ? createGunzip() : createBrotliDecompress();
548
565
  await pipelinePromise(response.body, decoder, args.writeStream);
@@ -577,7 +594,7 @@ export class HttpClient extends EventEmitter {
577
594
  this.#updateSocketInfo(socketInfo, internalOpaque);
578
595
 
579
596
  const clientResponse: HttpClientResponse = {
580
- opaque: orginalOpaque,
597
+ opaque: originalOpaque,
581
598
  data,
582
599
  status: res.status,
583
600
  statusCode: res.status,
@@ -632,7 +649,7 @@ export class HttpClient extends EventEmitter {
632
649
  return await this.#requestInternal(url, options, requestContext);
633
650
  }
634
651
  }
635
- err.opaque = orginalOpaque;
652
+ err.opaque = originalOpaque;
636
653
  err.status = res.status;
637
654
  err.headers = res.headers;
638
655
  err.res = res;
package/src/Request.ts CHANGED
@@ -46,6 +46,11 @@ export type RequestOptions = {
46
46
  * Default is 'buffer'.
47
47
  */
48
48
  dataType?: 'text' | 'html' | 'json' | 'buffer' | 'stream';
49
+ /**
50
+ * urllib default use URLSearchParams to stringify form data which don't support nested object,
51
+ * will use qs instead of URLSearchParams to support nested object by set this option to true.
52
+ */
53
+ nestedQuerystring?: boolean;
49
54
  /**
50
55
  * @deprecated
51
56
  * Only for d.ts keep compatible with urllib@2, don't use it anymore.
@@ -27,7 +27,10 @@ class HttpAgent extends undici_1.Agent {
27
27
  /* eslint node/prefer-promises/dns: off*/
28
28
  const _lookup = options.lookup ?? node_dns_1.default.lookup;
29
29
  const lookup = (hostname, dnsOptions, callback) => {
30
- _lookup(hostname, dnsOptions, (err, address, family) => {
30
+ _lookup(hostname, dnsOptions, (err, ...args) => {
31
+ // address will be array on Node.js >= 20
32
+ const address = args[0];
33
+ const family = args[1];
31
34
  if (err)
32
35
  return callback(err, address, family);
33
36
  if (options.checkAddress) {
@@ -31,7 +31,7 @@ export type ClientOptions = {
31
31
  */
32
32
  rejectUnauthorized?: boolean;
33
33
  /**
34
- * sockePath string | null (optional) - Default: null - An IPC endpoint, either Unix domain socket or Windows named pipe
34
+ * socketPath string | null (optional) - Default: null - An IPC endpoint, either Unix domain socket or Windows named pipe
35
35
  */
36
36
  socketPath?: string | null;
37
37
  };
@@ -21,6 +21,7 @@ const formdata_node_1 = require("formdata-node");
21
21
  const form_data_encoder_1 = require("form-data-encoder");
22
22
  const default_user_agent_1 = __importDefault(require("default-user-agent"));
23
23
  const mime_types_1 = __importDefault(require("mime-types"));
24
+ const qs_1 = __importDefault(require("qs"));
24
25
  const pump_1 = __importDefault(require("pump"));
25
26
  // Compatible with old style formstream
26
27
  const formstream_1 = __importDefault(require("formstream"));
@@ -44,6 +45,8 @@ function noop() {
44
45
  // noop
45
46
  }
46
47
  const debug = (0, node_util_1.debuglog)('urllib:HttpClient');
48
+ // Node.js 14 or 16
49
+ const isNode14Or16 = /v1[46]\./.test(process.version);
47
50
  // https://github.com/octet-stream/form-data
48
51
  class BlobFromStream {
49
52
  #stream;
@@ -70,7 +73,7 @@ class HttpClientRequestTimeoutError extends Error {
70
73
  Error.captureStackTrace(this, this.constructor);
71
74
  }
72
75
  }
73
- exports.HEADER_USER_AGENT = (0, default_user_agent_1.default)('node-urllib', '3.17.1');
76
+ exports.HEADER_USER_AGENT = (0, default_user_agent_1.default)('node-urllib', '3.18.0');
74
77
  function getFileName(stream) {
75
78
  const filePath = stream.path;
76
79
  if (filePath) {
@@ -165,14 +168,14 @@ class HttpClient extends node_events_1.EventEmitter {
165
168
  // the response body and trailers have been received
166
169
  contentDownload: 0,
167
170
  };
168
- const orginalOpaque = args.opaque;
171
+ const originalOpaque = args.opaque;
169
172
  // using opaque to diagnostics channel, binding request and socket
170
173
  const internalOpaque = {
171
174
  [symbols_1.default.kRequestId]: requestId,
172
175
  [symbols_1.default.kRequestStartTime]: requestStartTime,
173
176
  [symbols_1.default.kEnableRequestTiming]: !!args.timing,
174
177
  [symbols_1.default.kRequestTiming]: timing,
175
- [symbols_1.default.kRequestOrginalOpaque]: orginalOpaque,
178
+ [symbols_1.default.kRequestOriginalOpaque]: originalOpaque,
176
179
  };
177
180
  const reqMeta = {
178
181
  requestId,
@@ -379,11 +382,19 @@ class HttpClient extends node_events_1.EventEmitter {
379
382
  || (0, utils_1.isReadable)(args.data);
380
383
  if (isGETOrHEAD) {
381
384
  if (!isStringOrBufferOrReadable) {
382
- for (const field in args.data) {
383
- const fieldValue = args.data[field];
384
- if (fieldValue === undefined)
385
- continue;
386
- requestUrl.searchParams.append(field, fieldValue);
385
+ if (args.nestedQuerystring) {
386
+ const querystring = qs_1.default.stringify(args.data);
387
+ // reset the requestUrl
388
+ const href = requestUrl.href;
389
+ requestUrl = new URL(href + (href.includes('?') ? '&' : '?') + querystring);
390
+ }
391
+ else {
392
+ for (const field in args.data) {
393
+ const fieldValue = args.data[field];
394
+ if (fieldValue === undefined)
395
+ continue;
396
+ requestUrl.searchParams.append(field, fieldValue);
397
+ }
387
398
  }
388
399
  }
389
400
  }
@@ -403,7 +414,12 @@ class HttpClient extends node_events_1.EventEmitter {
403
414
  }
404
415
  else {
405
416
  headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
406
- requestOptions.body = new URLSearchParams(args.data).toString();
417
+ if (args.nestedQuerystring) {
418
+ requestOptions.body = qs_1.default.stringify(args.data);
419
+ }
420
+ else {
421
+ requestOptions.body = new URLSearchParams(args.data).toString();
422
+ }
407
423
  }
408
424
  }
409
425
  }
@@ -472,6 +488,9 @@ class HttpClient extends node_events_1.EventEmitter {
472
488
  }
473
489
  }
474
490
  else if (args.writeStream) {
491
+ if (isNode14Or16 && args.writeStream.destroyed) {
492
+ throw new Error('writeStream is destroyed');
493
+ }
475
494
  if (args.compressed === true && isCompressedContent) {
476
495
  const decoder = contentEncoding === 'gzip' ? (0, node_zlib_1.createGunzip)() : (0, node_zlib_1.createBrotliDecompress)();
477
496
  await pipelinePromise(response.body, decoder, args.writeStream);
@@ -510,7 +529,7 @@ class HttpClient extends node_events_1.EventEmitter {
510
529
  // get real socket info from internalOpaque
511
530
  this.#updateSocketInfo(socketInfo, internalOpaque);
512
531
  const clientResponse = {
513
- opaque: orginalOpaque,
532
+ opaque: originalOpaque,
514
533
  data,
515
534
  status: res.status,
516
535
  statusCode: res.status,
@@ -565,7 +584,7 @@ class HttpClient extends node_events_1.EventEmitter {
565
584
  return await this.#requestInternal(url, options, requestContext);
566
585
  }
567
586
  }
568
- err.opaque = orginalOpaque;
587
+ err.opaque = originalOpaque;
569
588
  err.status = res.status;
570
589
  err.headers = res.headers;
571
590
  err.res = res;
@@ -44,6 +44,11 @@ export type RequestOptions = {
44
44
  * Default is 'buffer'.
45
45
  */
46
46
  dataType?: 'text' | 'html' | 'json' | 'buffer' | 'stream';
47
+ /**
48
+ * urllib default use URLSearchParams to stringify form data which don't support nested object,
49
+ * will use qs instead of URLSearchParams to support nested object by set this option to true.
50
+ */
51
+ nestedQuerystring?: boolean;
47
52
  /**
48
53
  * @deprecated
49
54
  * Only for d.ts keep compatible with urllib@2, don't use it anymore.
@@ -12,6 +12,6 @@ declare const _default: {
12
12
  kRequestStartTime: symbol;
13
13
  kEnableRequestTiming: symbol;
14
14
  kRequestTiming: symbol;
15
- kRequestOrginalOpaque: symbol;
15
+ kRequestOriginalOpaque: symbol;
16
16
  };
17
17
  export default _default;
@@ -14,5 +14,5 @@ exports.default = {
14
14
  kRequestStartTime: Symbol('request start time'),
15
15
  kEnableRequestTiming: Symbol('enable request timing or not'),
16
16
  kRequestTiming: Symbol('request timing'),
17
- kRequestOrginalOpaque: Symbol('request orginal opaque'),
17
+ kRequestOriginalOpaque: Symbol('request original opaque'),
18
18
  };
@@ -21,7 +21,10 @@ export class HttpAgent extends Agent {
21
21
  /* eslint node/prefer-promises/dns: off*/
22
22
  const _lookup = options.lookup ?? dns.lookup;
23
23
  const lookup = (hostname, dnsOptions, callback) => {
24
- _lookup(hostname, dnsOptions, (err, address, family) => {
24
+ _lookup(hostname, dnsOptions, (err, ...args) => {
25
+ // address will be array on Node.js >= 20
26
+ const address = args[0];
27
+ const family = args[1];
25
28
  if (err)
26
29
  return callback(err, address, family);
27
30
  if (options.checkAddress) {
@@ -31,7 +31,7 @@ export type ClientOptions = {
31
31
  */
32
32
  rejectUnauthorized?: boolean;
33
33
  /**
34
- * sockePath string | null (optional) - Default: null - An IPC endpoint, either Unix domain socket or Windows named pipe
34
+ * socketPath string | null (optional) - Default: null - An IPC endpoint, either Unix domain socket or Windows named pipe
35
35
  */
36
36
  socketPath?: string | null;
37
37
  };
@@ -15,6 +15,7 @@ import { FormData as FormDataNode } from 'formdata-node';
15
15
  import { FormDataEncoder } from 'form-data-encoder';
16
16
  import createUserAgent from 'default-user-agent';
17
17
  import mime from 'mime-types';
18
+ import qs from 'qs';
18
19
  import pump from 'pump';
19
20
  // Compatible with old style formstream
20
21
  import FormStream from 'formstream';
@@ -38,6 +39,8 @@ function noop() {
38
39
  // noop
39
40
  }
40
41
  const debug = debuglog('urllib:HttpClient');
42
+ // Node.js 14 or 16
43
+ const isNode14Or16 = /v1[46]\./.test(process.version);
41
44
  // https://github.com/octet-stream/form-data
42
45
  class BlobFromStream {
43
46
  #stream;
@@ -64,7 +67,7 @@ class HttpClientRequestTimeoutError extends Error {
64
67
  Error.captureStackTrace(this, this.constructor);
65
68
  }
66
69
  }
67
- export const HEADER_USER_AGENT = createUserAgent('node-urllib', '3.17.1');
70
+ export const HEADER_USER_AGENT = createUserAgent('node-urllib', '3.18.0');
68
71
  function getFileName(stream) {
69
72
  const filePath = stream.path;
70
73
  if (filePath) {
@@ -159,14 +162,14 @@ export class HttpClient extends EventEmitter {
159
162
  // the response body and trailers have been received
160
163
  contentDownload: 0,
161
164
  };
162
- const orginalOpaque = args.opaque;
165
+ const originalOpaque = args.opaque;
163
166
  // using opaque to diagnostics channel, binding request and socket
164
167
  const internalOpaque = {
165
168
  [symbols.kRequestId]: requestId,
166
169
  [symbols.kRequestStartTime]: requestStartTime,
167
170
  [symbols.kEnableRequestTiming]: !!args.timing,
168
171
  [symbols.kRequestTiming]: timing,
169
- [symbols.kRequestOrginalOpaque]: orginalOpaque,
172
+ [symbols.kRequestOriginalOpaque]: originalOpaque,
170
173
  };
171
174
  const reqMeta = {
172
175
  requestId,
@@ -373,11 +376,19 @@ export class HttpClient extends EventEmitter {
373
376
  || isReadable(args.data);
374
377
  if (isGETOrHEAD) {
375
378
  if (!isStringOrBufferOrReadable) {
376
- for (const field in args.data) {
377
- const fieldValue = args.data[field];
378
- if (fieldValue === undefined)
379
- continue;
380
- requestUrl.searchParams.append(field, fieldValue);
379
+ if (args.nestedQuerystring) {
380
+ const querystring = qs.stringify(args.data);
381
+ // reset the requestUrl
382
+ const href = requestUrl.href;
383
+ requestUrl = new URL(href + (href.includes('?') ? '&' : '?') + querystring);
384
+ }
385
+ else {
386
+ for (const field in args.data) {
387
+ const fieldValue = args.data[field];
388
+ if (fieldValue === undefined)
389
+ continue;
390
+ requestUrl.searchParams.append(field, fieldValue);
391
+ }
381
392
  }
382
393
  }
383
394
  }
@@ -397,7 +408,12 @@ export class HttpClient extends EventEmitter {
397
408
  }
398
409
  else {
399
410
  headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
400
- requestOptions.body = new URLSearchParams(args.data).toString();
411
+ if (args.nestedQuerystring) {
412
+ requestOptions.body = qs.stringify(args.data);
413
+ }
414
+ else {
415
+ requestOptions.body = new URLSearchParams(args.data).toString();
416
+ }
401
417
  }
402
418
  }
403
419
  }
@@ -466,6 +482,9 @@ export class HttpClient extends EventEmitter {
466
482
  }
467
483
  }
468
484
  else if (args.writeStream) {
485
+ if (isNode14Or16 && args.writeStream.destroyed) {
486
+ throw new Error('writeStream is destroyed');
487
+ }
469
488
  if (args.compressed === true && isCompressedContent) {
470
489
  const decoder = contentEncoding === 'gzip' ? createGunzip() : createBrotliDecompress();
471
490
  await pipelinePromise(response.body, decoder, args.writeStream);
@@ -504,7 +523,7 @@ export class HttpClient extends EventEmitter {
504
523
  // get real socket info from internalOpaque
505
524
  this.#updateSocketInfo(socketInfo, internalOpaque);
506
525
  const clientResponse = {
507
- opaque: orginalOpaque,
526
+ opaque: originalOpaque,
508
527
  data,
509
528
  status: res.status,
510
529
  statusCode: res.status,
@@ -559,7 +578,7 @@ export class HttpClient extends EventEmitter {
559
578
  return await this.#requestInternal(url, options, requestContext);
560
579
  }
561
580
  }
562
- err.opaque = orginalOpaque;
581
+ err.opaque = originalOpaque;
563
582
  err.status = res.status;
564
583
  err.headers = res.headers;
565
584
  err.res = res;
@@ -44,6 +44,11 @@ export type RequestOptions = {
44
44
  * Default is 'buffer'.
45
45
  */
46
46
  dataType?: 'text' | 'html' | 'json' | 'buffer' | 'stream';
47
+ /**
48
+ * urllib default use URLSearchParams to stringify form data which don't support nested object,
49
+ * will use qs instead of URLSearchParams to support nested object by set this option to true.
50
+ */
51
+ nestedQuerystring?: boolean;
47
52
  /**
48
53
  * @deprecated
49
54
  * Only for d.ts keep compatible with urllib@2, don't use it anymore.
@@ -12,6 +12,6 @@ declare const _default: {
12
12
  kRequestStartTime: symbol;
13
13
  kEnableRequestTiming: symbol;
14
14
  kRequestTiming: symbol;
15
- kRequestOrginalOpaque: symbol;
15
+ kRequestOriginalOpaque: symbol;
16
16
  };
17
17
  export default _default;
@@ -12,5 +12,5 @@ export default {
12
12
  kRequestStartTime: Symbol('request start time'),
13
13
  kEnableRequestTiming: Symbol('enable request timing or not'),
14
14
  kRequestTiming: Symbol('request timing'),
15
- kRequestOrginalOpaque: Symbol('request orginal opaque'),
15
+ kRequestOriginalOpaque: Symbol('request original opaque'),
16
16
  };
package/src/symbols.ts CHANGED
@@ -12,5 +12,5 @@ export default {
12
12
  kRequestStartTime: Symbol('request start time'),
13
13
  kEnableRequestTiming: Symbol('enable request timing or not'),
14
14
  kRequestTiming: Symbol('request timing'),
15
- kRequestOrginalOpaque: Symbol('request orginal opaque'),
15
+ kRequestOriginalOpaque: Symbol('request original opaque'),
16
16
  };