diff --git a/lib/core/proxy-headers-agent.js b/lib/core/proxy-headers-agent.js index e3623d0..5662c3c 100644 --- a/lib/core/proxy-headers-agent.js +++ b/lib/core/proxy-headers-agent.js @@ -94,7 +94,6 @@ export class ProxyHeadersAgent extends Agent { this.proxyAuth, this.proxyHeaders ); - proxySocket.write(connectRequest); }); @@ -153,7 +152,8 @@ export class ProxyHeadersAgent extends Agent { }); }); - return proxySocket; + // Do not return proxySocket - the agent must use the stream from the callback + // (tlsSocket) so the CONNECT handshake completes before any request is written. } } diff --git a/lib/core/utils.js b/lib/core/utils.js index 4e44c51..01e714b 100644 --- a/lib/core/utils.js +++ b/lib/core/utils.js @@ -56,7 +56,10 @@ export function buildConnectRequest(targetHost, targetPort, proxyAuth, proxyHead lines.push(`Proxy-Authorization: Basic ${proxyAuth}`); } - for (const [key, value] of Object.entries(proxyHeaders)) { + const entries = proxyHeaders instanceof Map + ? [...proxyHeaders.entries()] + : Object.entries(proxyHeaders || {}); + for (const [key, value] of entries) { lines.push(`${key}: ${value}`); } diff --git a/package.json b/package.json index b1111b4..d3b7e46 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "LICENSE" ], "scripts": { - "test": "node test/test_proxy_headers.js", + "test": "node test/test_proxy_headers.js core axios node-fetch got undici superagent", "test:verbose": "node test/test_proxy_headers.js -v", "lint": "eslint lib test", "prepublishOnly": "npm test" diff --git a/test/test_proxy_headers.js b/test/test_proxy_headers.js index 43420af..bf1070f 100644 --- a/test/test_proxy_headers.js +++ b/test/test_proxy_headers.js @@ -128,6 +128,20 @@ function checkHeader(headers, headerName) { return null; } +/** + * When SEND_PROXY_HEADER and SEND_PROXY_VALUE are set and we're checking the same header, + * the proxy response must echo that value (e.g. X-ProxyMesh-IP). + * @returns {string|null} Error message if expectation not met, null if OK or N/A. + */ +function validateSentHeaderValue(config, headerValue) { + if (!config.sendProxyHeader || !config.sendProxyValue) return null; + if (config.proxyHeader.toLowerCase() !== config.sendProxyHeader.toLowerCase()) return null; + const expected = String(config.sendProxyValue).trim(); + const actual = headerValue ? String(headerValue).trim() : ''; + if (actual === expected) return null; + return `Expected ${config.proxyHeader} to equal ${expected} (sent in request) but got ${actual}`; +} + const AVAILABLE_TESTS = { async core(config) { try { @@ -159,7 +173,10 @@ const AVAILABLE_TESTS = { ? checkHeader(capturedHeaders, config.proxyHeader) : null; - if (headerValue) { + const sentErr = validateSentHeaderValue(config, headerValue); + if (sentErr) { + resolve(new TestResult('core', false, null, sentErr, res.statusCode)); + } else if (headerValue) { resolve(new TestResult('core', true, headerValue, null, res.statusCode)); } else { resolve(new TestResult('core', false, null, @@ -189,9 +206,13 @@ const AVAILABLE_TESTS = { proxyHeaders: config.proxyHeadersToSend, }); - const response = await client.get(config.testUrl); + const response = await client.get(config.testUrl, { + validateStatus: () => true, + }); const headerValue = checkHeader(response.headers, config.proxyHeader); + const sentErr = validateSentHeaderValue(config, headerValue); + if (sentErr) return new TestResult('axios', false, null, sentErr); if (headerValue) { return new TestResult('axios', true, headerValue, null, response.status); } @@ -214,6 +235,8 @@ const AVAILABLE_TESTS = { const headerValue = checkHeader(response.proxyHeaders, config.proxyHeader); + const sentErr = validateSentHeaderValue(config, headerValue); + if (sentErr) return new TestResult('node-fetch', false, null, sentErr); if (headerValue) { return new TestResult('node-fetch', true, headerValue, null, response.status); } @@ -232,11 +255,14 @@ const AVAILABLE_TESTS = { const client = await createProxyGot({ proxy: config.proxyUrl, proxyHeaders: config.proxyHeadersToSend, + gotOptions: { throwHttpErrors: false }, }); const response = await client(config.testUrl); const headerValue = checkHeader(response.headers, config.proxyHeader); + const sentErr = validateSentHeaderValue(config, headerValue); + if (sentErr) return new TestResult('got', false, null, sentErr); if (headerValue) { return new TestResult('got', true, headerValue, null, response.statusCode); } @@ -259,6 +285,8 @@ const AVAILABLE_TESTS = { const headerValue = checkHeader(proxyHeaders, config.proxyHeader); + const sentErr = validateSentHeaderValue(config, headerValue); + if (sentErr) return new TestResult('undici', false, null, sentErr); if (headerValue) { return new TestResult('undici', true, headerValue, null, statusCode); } @@ -279,9 +307,11 @@ const AVAILABLE_TESTS = { proxyHeaders: config.proxyHeadersToSend, }); - const response = await client.get(config.testUrl); + const response = await client.get(config.testUrl).ok(() => true); const headerValue = checkHeader(response.headers, config.proxyHeader); + const sentErr = validateSentHeaderValue(config, headerValue); + if (sentErr) return new TestResult('superagent', false, null, sentErr); if (headerValue) { return new TestResult('superagent', true, headerValue, null, response.status); }