Skip to content

Commit 88785e1

Browse files
authored
fix: allow set rejectUnauthorized = true on urllib.request options (#561)
closes #531 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes - **New Features** - Enhanced HTTP client configuration with options for handling HTTP/2 and unauthorized requests. - Introduced new tests for validating behavior with `rejectUnauthorized` set to false. - **Bug Fixes** - Improved error handling and address validation in HTTP client tests. - **Documentation** - Updated comments for clarity and consistency in the `HttpClient` class. - **Chores** - Updated dependency management for improved security and functionality. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 9dde585 commit 88785e1

8 files changed

Lines changed: 167 additions & 19 deletions

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,9 @@
7070
"cross-env": "^7.0.3",
7171
"eslint": "8",
7272
"eslint-config-egg": "14",
73-
"https-pem": "^3.0.0",
7473
"iconv-lite": "^0.6.3",
7574
"proxy": "^1.0.2",
76-
"selfsigned": "^2.0.1",
75+
"selfsigned": "^2.4.1",
7776
"tar-stream": "^2.2.0",
7877
"tshy": "^3.0.0",
7978
"tshy-after": "^1.0.0",

src/HttpClient.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ export type ClientOptions = {
100100
*/
101101
cert?: string | Buffer;
102102
/**
103-
* If true, the server certificate is verified against the list of supplied CAs.
104-
* An 'error' event is emitted if verification fails.Default: true.
103+
* If `true`, the server certificate is verified against the list of supplied CAs.
104+
* An 'error' event is emitted if verification fails.
105+
* Default: `true`
105106
*/
106107
rejectUnauthorized?: boolean;
107108
/**

src/index.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,62 @@ import { HttpClient, HEADER_USER_AGENT } from './HttpClient.js';
77
import { RequestOptions, RequestURL } from './Request.js';
88

99
let httpClient: HttpClient;
10+
let allowH2HttpClient: HttpClient;
11+
let allowUnauthorizedHttpClient: HttpClient;
12+
let allowH2AndUnauthorizedHttpClient: HttpClient;
1013
const domainSocketHttpClients = new LRU(50);
1114

12-
export function getDefaultHttpClient(): HttpClient {
15+
export function getDefaultHttpClient(rejectUnauthorized?: boolean, allowH2?: boolean): HttpClient {
16+
if (rejectUnauthorized === false) {
17+
if (allowH2) {
18+
if (!allowH2AndUnauthorizedHttpClient) {
19+
allowH2AndUnauthorizedHttpClient = new HttpClient({
20+
allowH2,
21+
connect: {
22+
rejectUnauthorized,
23+
},
24+
});
25+
}
26+
return allowH2AndUnauthorizedHttpClient;
27+
}
28+
29+
if (!allowUnauthorizedHttpClient) {
30+
allowUnauthorizedHttpClient = new HttpClient({
31+
connect: {
32+
rejectUnauthorized,
33+
},
34+
});
35+
}
36+
return allowUnauthorizedHttpClient;
37+
}
38+
39+
if (allowH2) {
40+
if (!allowH2HttpClient) {
41+
allowH2HttpClient = new HttpClient({
42+
allowH2,
43+
});
44+
}
45+
return allowH2HttpClient;
46+
}
47+
1348
if (!httpClient) {
1449
httpClient = new HttpClient();
1550
}
1651
return httpClient;
1752
}
1853

19-
export async function request<T = any>(url: RequestURL, options?: RequestOptions) {
54+
interface UrllibRequestOptions extends RequestOptions {
55+
/**
56+
* If `true`, the server certificate is verified against the list of supplied CAs.
57+
* An 'error' event is emitted if verification fails.
58+
* Default: `true`
59+
*/
60+
rejectUnauthorized?: boolean;
61+
/** Allow to use HTTP2 first. Default is `false` */
62+
allowH2?: boolean;
63+
}
64+
65+
export async function request<T = any>(url: RequestURL, options?: UrllibRequestOptions) {
2066
if (options?.socketPath) {
2167
let domainSocketHttpclient = domainSocketHttpClients.get<HttpClient>(options.socketPath);
2268
if (!domainSocketHttpclient) {
@@ -28,15 +74,15 @@ export async function request<T = any>(url: RequestURL, options?: RequestOptions
2874
return await domainSocketHttpclient.request<T>(url, options);
2975
}
3076

31-
return await getDefaultHttpClient().request<T>(url, options);
77+
return await getDefaultHttpClient(options?.rejectUnauthorized, options?.allowH2).request<T>(url, options);
3278
}
3379

3480
// export curl method is keep compatible with urllib.curl()
3581
// ```ts
3682
// import * as urllib from 'urllib';
3783
// urllib.curl(url);
3884
// ```
39-
export async function curl<T = any>(url: RequestURL, options?: RequestOptions) {
85+
export async function curl<T = any>(url: RequestURL, options?: UrllibRequestOptions) {
4086
return await request<T>(url, options);
4187
}
4288

test/HttpClient.test.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { sensitiveHeaders, createSecureServer } from 'node:http2';
55
import { PerformanceObserver } from 'node:perf_hooks';
66
import { setTimeout as sleep } from 'node:timers/promises';
77
import { describe, it, beforeAll, afterAll } from 'vitest';
8-
import pem from 'https-pem';
8+
import selfsigned from 'selfsigned';
99
import { HttpClient, RawResponseWithMeta, getGlobalDispatcher } from '../src/index.js';
1010
import { startServer } from './fixtures/server.js';
1111

@@ -118,7 +118,11 @@ describe('HttpClient.test.ts', () => {
118118
});
119119

120120
it('should not exit after other side closed error', async () => {
121-
const server = createSecureServer(pem);
121+
const pem = selfsigned.generate();
122+
const server = createSecureServer({
123+
key: pem.private,
124+
cert: pem.cert,
125+
});
122126

123127
let count = 0;
124128
server.on('stream', (stream, headers) => {
@@ -172,7 +176,11 @@ describe('HttpClient.test.ts', () => {
172176
});
173177

174178
it('should auto redirect work', async () => {
175-
const server = createSecureServer(pem);
179+
const pem = selfsigned.generate();
180+
const server = createSecureServer({
181+
key: pem.private,
182+
cert: pem.cert,
183+
});
176184

177185
let count = 0;
178186
server.on('stream', (stream, headers) => {
@@ -452,13 +460,13 @@ describe('HttpClient.test.ts', () => {
452460
});
453461

454462
it('should allow hostname check', async () => {
455-
let hostname: string;
463+
let hostname = '';
456464
const httpclient = new HttpClient({
457-
checkAddress(ip, family, aHostname) {
465+
checkAddress(_ip, _family, aHostname) {
458466
hostname = aHostname;
459467
return true;
460468
},
461-
lookup(hostname, options, callback) {
469+
lookup(_hostname, _options, callback) {
462470
if (process.version.startsWith('v18')) {
463471
return callback(null, '127.0.0.1', 4);
464472
}

test/fixtures/server.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,10 @@ export async function startServer(options?: {
370370
};
371371

372372
if (options?.https) {
373-
const pems = selfsigned.generate();
373+
const pem = selfsigned.generate();
374374
server = createHttpsServer({
375-
key: pems.private,
376-
cert: pems.cert,
375+
key: pem.private,
376+
cert: pem.cert,
377377
}, requestHandler);
378378
} else {
379379
server = createServer(requestHandler);

test/options.timeout.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { strict as assert } from 'node:assert';
22
import { createSecureServer } from 'node:http2';
33
import { once } from 'node:events';
4-
import pem from 'https-pem';
4+
import selfsigned from 'selfsigned';
55
import { describe, it, beforeAll, afterAll } from 'vitest';
66
import urllib, { HttpClientRequestTimeoutError, HttpClient } from '../src/index.js';
77
import { startServer } from './fixtures/server.js';
@@ -46,7 +46,11 @@ describe('options.timeout.test.ts', () => {
4646
rejectUnauthorized: false,
4747
},
4848
});
49-
const server = createSecureServer(pem);
49+
const pem = selfsigned.generate();
50+
const server = createSecureServer({
51+
key: pem.private,
52+
cert: pem.cert,
53+
});
5054

5155
server.on('stream', () => {
5256
// wait for timeout
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { strict as assert } from 'node:assert';
2+
import { describe, it } from 'vitest';
3+
import urllib from '../src/index.js';
4+
5+
describe('urllib.options.allowH2.test.ts', () => {
6+
it('should 200 on options.allowH2 = true', async () => {
7+
let response = await urllib.request('https://registry.npmmirror.com', {
8+
allowH2: true,
9+
dataType: 'json',
10+
});
11+
assert.equal(response.status, 200);
12+
13+
response = await urllib.curl('https://registry.npmmirror.com', {
14+
allowH2: true,
15+
dataType: 'json',
16+
});
17+
assert.equal(response.status, 200);
18+
});
19+
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { strict as assert } from 'node:assert';
2+
import { once } from 'node:events';
3+
import { createSecureServer } from 'node:http2';
4+
import { describe, it, beforeAll, afterAll } from 'vitest';
5+
import selfsigned from 'selfsigned';
6+
import urllib, { HttpClient } from '../src/index.js';
7+
import { startServer } from './fixtures/server.js';
8+
9+
describe('urllib.options.rejectUnauthorized-false.test.ts', () => {
10+
let close: any;
11+
let _url: string;
12+
beforeAll(async () => {
13+
const { closeServer, url } = await startServer({ https: true });
14+
close = closeServer;
15+
_url = url;
16+
});
17+
18+
afterAll(async () => {
19+
await close();
20+
});
21+
22+
it('should 200 on options.rejectUnauthorized = false', async () => {
23+
const response = await urllib.request(_url, {
24+
rejectUnauthorized: false,
25+
dataType: 'json',
26+
});
27+
assert.equal(response.status, 200);
28+
assert.equal(response.data.method, 'GET');
29+
});
30+
31+
it('should 200 with H2 on options.rejectUnauthorized = false', async () => {
32+
const pem = selfsigned.generate();
33+
const server = createSecureServer({
34+
key: pem.private,
35+
cert: pem.cert,
36+
});
37+
38+
server.on('stream', (stream, headers) => {
39+
assert.equal(headers[':method'], 'GET');
40+
stream.respond({
41+
'content-type': 'text/plain; charset=utf-8',
42+
'x-custom-h2': 'hello',
43+
':status': 200,
44+
});
45+
stream.end('hello h2!');
46+
});
47+
48+
server.listen(0);
49+
await once(server, 'listening');
50+
51+
const httpClient = new HttpClient({
52+
allowH2: true,
53+
connect: {
54+
rejectUnauthorized: false,
55+
},
56+
});
57+
58+
const url = `https://localhost:${server.address()!.port}`;
59+
const response1 = await httpClient.request(url, {});
60+
assert.equal(response1.status, 200);
61+
assert.equal(response1.data.toString(), 'hello h2!');
62+
63+
const response2 = await urllib.request(url, {
64+
rejectUnauthorized: false,
65+
allowH2: true,
66+
dataType: 'text',
67+
});
68+
assert.equal(response2.status, 200);
69+
assert.equal(response2.data, 'hello h2!');
70+
});
71+
});

0 commit comments

Comments
 (0)