Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/tools/web-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -748,9 +748,9 @@ export function collectRawHttpResponse(url: string, socket: import("node:net").S
let settled = false;
const cleanup = () => {
socket.removeListener("data", onData);
socket.removeListener("error", onError);
socket.removeListener("end", onEnd);
socket.removeListener("close", onClose);
// TLS can emit an error after end (for example during a failed handshake).
// Keep handling errors until close, even when the response has settled.
};
const finishResolve = (value: Response) => {
if (settled) return;
Expand Down Expand Up @@ -849,9 +849,12 @@ export function collectRawHttpResponse(url: string, socket: import("node:net").S
finishReject(error);
}
};
const onClose = () => finishReject(new Error("Proxy HTTPS response socket closed before the response ended"));
const onClose = () => {
finishReject(new Error("Proxy HTTPS response socket closed before the response ended"));
socket.removeListener("error", onError);
};
socket.on("data", onData);
socket.once("error", onError);
socket.on("error", onError);
socket.once("end", onEnd);
socket.once("close", onClose);
});
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/web-proxy-tls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import assert from "node:assert/strict";
import net from "node:net";
import tls from "node:tls";
import { once } from "node:events";
import test from "node:test";
import { collectRawHttpResponse } from "../../src/tools/web-tools.ts";

test("HTTPS proxy handshake disconnect rejects without an unhandled TLS error", async () => {
const server = net.createServer((socket) => {
socket.resume();
socket.end();
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
const address = server.address() as net.AddressInfo;
const socket = tls.connect({ host: "127.0.0.1", port: address.port, servername: "localhost" });
const closed = new Promise<void>((resolve) => socket.once("close", () => resolve()));
try {
await assert.rejects(collectRawHttpResponse("https://localhost/", socket),
/headers were complete|TLS connection|socket closed/);
await closed;
assert.equal(socket.listenerCount("error"), 0);
} finally {
socket.destroy();
await new Promise<void>((resolve) => server.close(() => resolve()));
}
});