Skip to content
Open
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
9 changes: 7 additions & 2 deletions lib/internal/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ function ipToInt(ip) {
return result >>> 0;
}

// Follow curl's behavior: ignore a trailing dot in both the host and the NO_PROXY entries.
function stripTrailingDot(host) {
return host.length > 1 && host[host.length - 1] === '.' ? host.slice(0, -1) : host;
}

// There are two factors in play when proxying the request:
// 1. What the request protocol is, that is, whether users are sending it via
// http.request or https.request, or whether they are sending
Expand Down Expand Up @@ -137,7 +142,7 @@ class ProxyConfig {
this.auth = `Basic ${Buffer.from(auth).toString('base64')}`;
}
if (noProxyList) {
this.bypassList = noProxyList.split(',').map((entry) => entry.trim().toLowerCase());
this.bypassList = noProxyList.split(',').map((entry) => stripTrailingDot(entry.trim().toLowerCase()));
} else {
this.bypassList = []; // No bypass list provided.
}
Expand All @@ -159,7 +164,7 @@ class ProxyConfig {
return true; // No bypass list, always use the proxy.
}

const host = hostname.toLowerCase();
const host = stripTrailingDot(hostname.toLowerCase());
const hostWithPort = port ? `${host}:${port}` : host;

for (let i = 0; i < bypassList.length; i++) {
Expand Down
40 changes: 39 additions & 1 deletion test/client-proxy/test-http-proxy-request-no-proxy-domain.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { runProxiedRequest } from '../common/proxy-server.js';
const server = http.createServer(common.mustCall((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
}, 5));
}, 7));
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
server.listen(0, '127.0.0.1');
await once(server, 'listening');
Expand Down Expand Up @@ -78,6 +78,44 @@ await once(proxy, 'listening');
assert.strictEqual(signal, null);
}

// Test NO_PROXY ignores a trailing dot in the request hostname.
{
const { code, signal, stderr, stdout } = await runProxiedRequest({
NODE_USE_ENV_PROXY: 1,
REQUEST_URL: `http://test.example.com.:${server.address().port}/test`,
HTTP_PROXY: `http://localhost:${proxy.address().port}`,
RESOLVE_TO_LOCALHOST: 'test.example.com.',
NO_PROXY: 'test.example.com',
});

// The request should succeed and bypass proxy.
assert.match(stdout, /Status Code: 200/);
assert.match(stdout, /Hello World/);
assert.match(stdout, /Resolving lookup for test\.example\.com\./);
assert.strictEqual(stderr.trim(), '');
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
}

// Test NO_PROXY ignores a trailing dot in the entry.
{
const { code, signal, stderr, stdout } = await runProxiedRequest({
NODE_USE_ENV_PROXY: 1,
REQUEST_URL: `http://test.example.com:${server.address().port}/test`,
HTTP_PROXY: `http://localhost:${proxy.address().port}`,
RESOLVE_TO_LOCALHOST: 'test.example.com',
NO_PROXY: 'test.example.com.',
});

// The request should succeed and bypass proxy.
assert.match(stdout, /Status Code: 200/);
assert.match(stdout, /Hello World/);
assert.match(stdout, /Resolving lookup for test\.example\.com/);
assert.strictEqual(stderr.trim(), '');
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
}

// Test NO_PROXY with leading-dot entry should NOT match partial domain names.
// Regression test: .example.com must not match notexample.com or badexample.com.
{
Expand Down
Loading