Skip to content

Commit 0b8a0b7

Browse files
authoredMay 24, 2023
[https-proxy-agent] Properly reject errors during proxy CONNECT response (#184)
Fixes #162.
1 parent c0e0d83 commit 0b8a0b7

File tree

5 files changed

+131
-70
lines changed

5 files changed

+131
-70
lines changed
 

‎.changeset/eleven-seas-rule.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"https-proxy-agent": patch
3+
---
4+
5+
Properly reject errors during proxy `CONNECT` response

‎packages/https-proxy-agent/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"@types/debug": "4",
3737
"@types/jest": "^29.5.1",
3838
"@types/node": "^14.18.45",
39-
"async-listen": "^2.1.0",
39+
"async-listen": "^3.0.0",
4040
"async-retry": "^1.3.3",
4141
"jest": "^29.5.0",
4242
"proxy": "workspace:*",

‎packages/https-proxy-agent/src/parse-proxy-response.ts

+17-9
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,17 @@ export function parseProxyResponse(
3030
function cleanup() {
3131
socket.removeListener('end', onend);
3232
socket.removeListener('error', onerror);
33-
socket.removeListener('close', onclose);
3433
socket.removeListener('readable', read);
3534
}
3635

37-
function onclose(err?: Error) {
38-
debug('onclose had error %o', err);
39-
}
40-
4136
function onend() {
37+
cleanup();
4238
debug('onend');
39+
reject(
40+
new Error(
41+
'Proxy connection ended before receiving CONNECT response'
42+
)
43+
);
4344
}
4445

4546
function onerror(err: Error) {
@@ -65,7 +66,10 @@ export function parseProxyResponse(
6566
const headerParts = buffered.toString('ascii').split('\r\n');
6667
const firstLine = headerParts.shift();
6768
if (!firstLine) {
68-
throw new Error('No header received');
69+
socket.destroy();
70+
return reject(
71+
new Error('No header received from proxy CONNECT response')
72+
);
6973
}
7074
const firstLineParts = firstLine.split(' ');
7175
const statusCode = +firstLineParts[1];
@@ -75,7 +79,12 @@ export function parseProxyResponse(
7579
if (!header) continue;
7680
const firstColon = header.indexOf(':');
7781
if (firstColon === -1) {
78-
throw new Error(`Invalid header: "${header}"`);
82+
socket.destroy();
83+
return reject(
84+
new Error(
85+
`Invalid header from proxy CONNECT response: "${header}"`
86+
)
87+
);
7988
}
8089
const key = header.slice(0, firstColon).toLowerCase();
8190
const value = header.slice(firstColon + 1).trimStart();
@@ -88,7 +97,7 @@ export function parseProxyResponse(
8897
headers[key] = value;
8998
}
9099
}
91-
debug('got proxy server response: %o', firstLine);
100+
debug('got proxy server response: %o %o', firstLine, headers);
92101
cleanup();
93102
resolve({
94103
connect: {
@@ -101,7 +110,6 @@ export function parseProxyResponse(
101110
}
102111

103112
socket.on('error', onerror);
104-
socket.on('close', onclose);
105113
socket.on('end', onend);
106114

107115
read();

‎packages/https-proxy-agent/test/test.ts

+44-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'fs';
2+
import net from 'net';
23
import http from 'http';
34
import https from 'https';
45
import assert from 'assert';
@@ -32,25 +33,25 @@ describe('HttpsProxyAgent', () => {
3233
beforeAll(async () => {
3334
// setup target HTTP server
3435
server = http.createServer();
35-
serverUrl = (await listen(server)) as URL;
36+
serverUrl = await listen(server);
3637
});
3738

3839
beforeAll(async () => {
3940
// setup HTTP proxy server
4041
proxy = createProxy();
41-
proxyUrl = (await listen(proxy)) as URL;
42+
proxyUrl = await listen(proxy);
4243
});
4344

4445
beforeAll(async () => {
4546
// setup target HTTPS server
4647
sslServer = https.createServer(sslOptions);
47-
sslServerUrl = (await listen(sslServer)) as URL;
48+
sslServerUrl = await listen(sslServer);
4849
});
4950

5051
beforeAll(async () => {
5152
// setup SSL HTTP proxy server
5253
sslProxy = createProxy(https.createServer(sslOptions));
53-
sslProxyUrl = (await listen(sslProxy)) as URL;
54+
sslProxyUrl = await listen(sslProxy);
5455
});
5556

5657
beforeEach(() => {
@@ -197,7 +198,11 @@ describe('HttpsProxyAgent', () => {
197198

198199
const connectPromise = once(server, 'connect');
199200

200-
http.get({ agent });
201+
http.get({ agent }).on('error', () => {
202+
// "error" happens because agent didn't receive proper
203+
// CONNECT response before the socket was closed.
204+
// We can safely ignore that.
205+
});
201206

202207
const [req, socket] = await connectPromise;
203208
assert.equal('CONNECT', req.method);
@@ -212,15 +217,23 @@ describe('HttpsProxyAgent', () => {
212217
});
213218

214219
const connectPromise = once(server, 'connect');
215-
http.get({ agent });
220+
http.get({ agent }).on('error', () => {
221+
// "error" happens because agent didn't receive proper
222+
// CONNECT response before the socket was closed.
223+
// We can safely ignore that.
224+
});
216225

217226
const [req, socket] = await connectPromise;
218227
assert.equal('CONNECT', req.method);
219228
assert.equal('1', req.headers.number);
220229
socket.destroy();
221230

222231
const connectPromise2 = once(server, 'connect');
223-
http.get({ agent });
232+
http.get({ agent }).on('error', () => {
233+
// "error" happens because agent didn't receive proper
234+
// CONNECT response before the socket was closed.
235+
// We can safely ignore that.
236+
});
224237

225238
const [req2, socket2] = await connectPromise2;
226239
assert.equal('CONNECT', req2.method);
@@ -252,6 +265,30 @@ describe('HttpsProxyAgent', () => {
252265
agent.destroy();
253266
}
254267
});
268+
269+
it('should emit "error" on request if proxy has invalid header', async () => {
270+
const badProxy = net.createServer((socket) => {
271+
socket.write(
272+
'HTTP/1.1 200 Connection established\r\nbadheader\r\n\r\n'
273+
);
274+
});
275+
const addr = await listen(badProxy);
276+
let err: Error | undefined;
277+
try {
278+
const agent = new HttpsProxyAgent(
279+
addr.href.replace('tcp', 'http')
280+
);
281+
await req('http://example.com', { agent });
282+
} catch (_err) {
283+
err = _err as Error;
284+
} finally {
285+
badProxy.close();
286+
}
287+
assert(err);
288+
expect(err.message).toEqual(
289+
'Invalid header from proxy CONNECT response: "badheader"'
290+
);
291+
});
255292
});
256293

257294
describe('"https" module', () => {

‎pnpm-lock.yaml

+64-53
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

1 commit comments

Comments
 (1)

vercel[bot] commented on May 24, 2023

@vercel[bot]
Please sign in to comment.