mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
test-https-proxy-request-invalid-char-in-url is the only client-proxy test that asserts the proxy logged no socket errors at all. Once the last response has been read the client destroys its tunnel, and if the proxy is still relaying the upstream's TLS close_notify at that point the client answers with a reset, which the proxy records as ECONNRESET on the CONNECT socket. That has been failing the test on macOS even though every request was routed to the sanitized URL. Keep asserting on other errors but leave connection resets out. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65780 Fixes: https://github.com/nodejs/node/issues/64226 Refs: https://github.com/nodejs/node/issues/59146 Refs: https://github.com/nodejs/node/issues/54534 Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
96 lines
3.7 KiB
JavaScript
96 lines
3.7 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));
|
|
// The client may reset a tunnel while the proxy is still relaying
|
|
// the upstream's TLS shutdown; that says nothing about the URLs.
|
|
const errors = logs.filter((log) => 'error' in log && log.error.code !== 'ECONNRESET');
|
|
assert.deepStrictEqual(new Set(requestLogs), expectedProxyLogs);
|
|
assert.deepStrictEqual(errors, []);
|
|
}));
|
|
}
|
|
}));
|
|
})).on('error', common.mustNotCall()).end();
|
|
}
|