mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
CONNECT tunnels are full-duplex. When the upstream socket receives a FIN while the client-to-upstream pipe is still draining, the default socket behavior can produce EPIPE or ECONNRESET errors. Keep the upstream socket half-open so both directions can drain. Separate request logs from transport errors to verify that teardown completes without errors. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol PR-URL: https://github.com/nodejs/node/pull/64973 Refs: https://github.com/nodejs/reliability/issues?q=%22test-https-proxy-request-invalid-char-in-url%22 Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
94 lines
3.5 KiB
JavaScript
94 lines
3.5 KiB
JavaScript
// This tests that invalid hosts or ports with carriage return or newline characters
|
|
// in HTTPS request urls are stripped away before being sent to the server.
|
|
|
|
import * as common from '../common/index.mjs';
|
|
import assert from 'node:assert';
|
|
import fixtures from '../common/fixtures.js';
|
|
import { once } from 'events';
|
|
import { inspect } from 'node:util';
|
|
import { createProxyServer } from '../common/proxy-server.js';
|
|
|
|
if (!common.hasCrypto) {
|
|
common.skip('missing crypto');
|
|
}
|
|
|
|
// https must be dynamically imported so that builds without crypto support
|
|
// can skip it.
|
|
const { default: https } = await import('node:https');
|
|
const requests = new Set();
|
|
|
|
const server = https.createServer({
|
|
cert: fixtures.readKey('agent8-cert.pem'),
|
|
key: fixtures.readKey('agent8-key.pem'),
|
|
}, (req, res) => {
|
|
console.log(`[Upstream server] responding to request for ${inspect(req.url)}`);
|
|
requests.add(`https://localhost:${server.address().port}${req.url}`);
|
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
res.end(`Response for ${inspect(req.url)}`);
|
|
});
|
|
|
|
server.listen(0);
|
|
await once(server, 'listening');
|
|
server.on('error', common.mustNotCall());
|
|
const port = server.address().port.toString();
|
|
|
|
const testCases = [
|
|
{ host: 'local\rhost', port: port, path: '/carriage-return-in-host' },
|
|
{ host: 'local\nhost', port: port, path: '/newline-in-host' },
|
|
{ host: 'local\r\nhost', port: port, path: '/crlf-in-host' },
|
|
{ host: 'localhost', port: port.substring(0, 1) + '\r' + port.substring(1), path: '/carriage-return-in-port' },
|
|
{ host: 'localhost', port: port.substring(0, 1) + '\n' + port.substring(1), path: '/newline-in-port' },
|
|
{ host: 'localhost', port: port.substring(0, 1) + '\r\n' + port.substring(1), path: '/crlf-in-port' },
|
|
];
|
|
|
|
// Start a minimal proxy server
|
|
const { proxy, logs } = createProxyServer();
|
|
proxy.listen(0);
|
|
await once(proxy, 'listening');
|
|
|
|
https.globalAgent = new https.Agent({
|
|
ca: fixtures.readKey('fake-startcom-root-cert.pem'),
|
|
proxyEnv: {
|
|
HTTPS_PROXY: `http://localhost:${proxy.address().port}`,
|
|
},
|
|
});
|
|
|
|
const severHost = `localhost:${server.address().port}`;
|
|
|
|
let counter = 0;
|
|
const expectedUrls = new Set();
|
|
const expectedProxyLogs = new Set();
|
|
for (const testCase of testCases) {
|
|
const url = `https://${testCase.host}:${testCase.port}${testCase.path}`;
|
|
// The invalid characters should all be stripped away before being sent.
|
|
expectedUrls.add(url.replaceAll(/\r|\n/g, ''));
|
|
expectedProxyLogs.add({
|
|
method: 'CONNECT',
|
|
url: severHost,
|
|
headers: { host: severHost },
|
|
});
|
|
https.request(url, common.mustCall((res) => {
|
|
res.on('error', common.mustNotCall());
|
|
res.setEncoding('utf8');
|
|
res.on('data', (data) => {
|
|
console.log(`[Proxy client] Received response from server for ${inspect(url)}: ${data.toString()}`);
|
|
});
|
|
res.on('close', common.mustCall(() => {
|
|
console.log(`[Proxy client] #${++counter} closed request for: ${inspect(url)}`);
|
|
// Finished all test cases.
|
|
if (counter === testCases.length) {
|
|
setImmediate(common.mustCall(() => {
|
|
console.log('All requests completed, shutting down.');
|
|
proxy.close();
|
|
server.close();
|
|
assert.deepStrictEqual(requests, expectedUrls);
|
|
const requestLogs = logs.filter((log) => !('error' in log));
|
|
const errors = logs.filter((log) => 'error' in log);
|
|
assert.deepStrictEqual(new Set(requestLogs), expectedProxyLogs);
|
|
assert.deepStrictEqual(errors, []);
|
|
}));
|
|
}
|
|
}));
|
|
})).on('error', common.mustNotCall()).end();
|
|
}
|