Files
node/test/client-proxy/test-https-proxy-request-invalid-char-in-url.mjs
Joyee Cheung 5c7b83a94e test: guard write to proxy client if proxy connection is ended
In the testing proxy server for proxy client tests, the proxy
client might have already closed the connection when the upstream
connection fails. In that case, there's no need for the proxy
server to inform the proxy client about the error.

PR-URL: https://github.com/nodejs/node/pull/59742
Fixes: https://github.com/nodejs/node/issues/59741
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2025-09-17 13:59:09 +00:00

91 lines
3.3 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, (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(() => {
console.log('All requests completed, shutting down.');
proxy.close();
server.close();
assert.deepStrictEqual(requests, expectedUrls);
assert.deepStrictEqual(new Set(logs), expectedProxyLogs);
});
}
}));
}).on('error', common.mustNotCall()).end();
}