diff --git a/README.md b/README.md index e9221d3e..ce9c7f06 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ console.log('status: %s, body size: %d, headers: %j', res.status, data.length, r - **_streaming_** Boolean - lets you get the `res` object when request connected, default `false`. alias `customResponse` - **_compressed_** Boolean - Accept `gzip, br` response content and auto decode it, default is `false`. - **_timing_** Boolean - Enable timing or not, default is `true`. + - **_rejectUnauthorized_** Boolean - Verify the server certificate. Default is `true`. Set `false` to allow self-signed certificates. Applies to top-level `request()` and `curl()` when neither `dispatcher` nor `socketPath` is set. For `HttpClient` instances, use `connect.rejectUnauthorized`; custom dispatchers control their own TLS settings. - **_socketPath_** String | null - request a unix socket service, default is `null`. - **_highWaterMark_** Number - default is `67108864`, 64 KiB. diff --git a/src/Request.ts b/src/Request.ts index a16a291a..a1d8e04e 100644 --- a/src/Request.ts +++ b/src/Request.ts @@ -139,6 +139,14 @@ export type RequestOptions = { ctx?: unknown; /** Request dispatcher, default is getGlobalDispatcher() */ dispatcher?: Dispatcher; + /** + * Verify the server certificate for top-level `request()` and `curl()`. Default: `true`. Set `false` to allow + * self-signed certificates. + * + * Applies when neither `dispatcher` nor `socketPath` is set. For `HttpClient` instances, configure + * `connect.rejectUnauthorized` instead. Custom dispatchers control their own TLS settings. + */ + rejectUnauthorized?: boolean; /** * Negotiate HTTP/2 with capable servers via ALPN. Enabled by default since undici@8; set `false` to force HTTP/1.1 * for this request without bypassing the active dispatcher. diff --git a/src/index.ts b/src/index.ts index eee802ee..6946e5b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,19 +31,7 @@ export function getDefaultHttpClient(rejectUnauthorized?: boolean, allowH2?: boo return client; } -interface UrllibRequestOptions extends RequestOptions { - /** - * If `true`, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if - * verification fails. Default: `true` - */ - rejectUnauthorized?: boolean; - // `allowH2` is inherited from RequestOptions. -} - -export async function request( - url: RequestURL, - options?: UrllibRequestOptions, -): Promise> { +export async function request(url: RequestURL, options?: RequestOptions): Promise> { if (options?.socketPath) { let domainSocketHttpclient = domainSocketHttpClients.get(options.socketPath); if (!domainSocketHttpclient) { @@ -63,7 +51,7 @@ export async function request( // import * as urllib from 'urllib'; // urllib.curl(url); // ``` -export async function curl(url: RequestURL, options?: UrllibRequestOptions): Promise> { +export async function curl(url: RequestURL, options?: RequestOptions): Promise> { return await request(url, options); } diff --git a/test/fixtures/ts-esm/hello.ts b/test/fixtures/ts-esm/hello.ts index 37cb79b2..d6690b04 100644 --- a/test/fixtures/ts-esm/hello.ts +++ b/test/fixtures/ts-esm/hello.ts @@ -3,11 +3,17 @@ import type { HttpClientResponse } from 'urllib'; import urllib from 'urllib'; import * as urllib2 from 'urllib'; -async function request(url: RequestURL, options: RequestOptions): Promise { +async function request( + url: RequestURL, + options: RequestOptions = { rejectUnauthorized: false }, +): Promise { return await urllib.request(url, options); } -async function request2(url: RequestURL, options: RequestOptions2): Promise { +async function request2( + url: RequestURL, + options: RequestOptions2 = { rejectUnauthorized: true }, +): Promise { return await urllib2.curl(url, options); } diff --git a/test/urllib.options.rejectUnauthorized-false.test.ts b/test/urllib.options.rejectUnauthorized-false.test.ts index d2cc7a64..224afc1a 100644 --- a/test/urllib.options.rejectUnauthorized-false.test.ts +++ b/test/urllib.options.rejectUnauthorized-false.test.ts @@ -7,6 +7,7 @@ import selfsigned from 'selfsigned'; import { describe, it, beforeAll, afterAll } from 'vite-plus/test'; import urllib, { HttpClient } from '../src/index.js'; +import type { RequestOptions } from '../src/index.js'; import { startServer } from './fixtures/server.js'; describe('urllib.options.rejectUnauthorized-false.test.ts', () => { @@ -22,13 +23,22 @@ describe('urllib.options.rejectUnauthorized-false.test.ts', () => { await close(); }); - it('should 200 on options.rejectUnauthorized = false', async () => { - const response = await urllib.request(_url, { + it.each(['request', 'curl'] as const)('should honor typed rejectUnauthorized on urllib.%s', async (method) => { + const options: RequestOptions = { rejectUnauthorized: false, dataType: 'json', - }); + }; + const response = await urllib[method](_url, options); assert.equal(response.status, 200); assert.equal(response.data.method, 'GET'); + + // An unverified connection must not be reused by requests that verify certificates. + for (const rejectUnauthorized of [true, undefined]) { + const secureOptions: RequestOptions = { rejectUnauthorized }; + await assert.rejects(urllib[method](_url, secureOptions), { + code: 'DEPTH_ZERO_SELF_SIGNED_CERT', + }); + } }); it('should 200 with H2 on options.rejectUnauthorized = false', async () => {