Commit c0681e51796 for nodejs
commit c0681e51796caf400cb59b2a7ed5698d302fb36e
Author: Efe Karasakal <hi@efe.dev>
Date: Sat Sep 26 16:02:54 2026 +0200
http: normalize CONNECT request paths
Signed-off-by: Efe Karasakal <hi@efe.dev>
PR-URL: https://github.com/nodejs/node/pull/64876
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Tim Perry <pimterry@gmail.com>
diff --git a/lib/_http_client.js b/lib/_http_client.js
index a6d0bc2eaaa..21cf9e8d407 100644
--- a/lib/_http_client.js
+++ b/lib/_http_client.js
@@ -28,6 +28,7 @@ const {
NumberIsFinite,
ObjectAssign,
ObjectDefineProperty,
+ ObjectHasOwn,
ObjectKeys,
ObjectSetPrototypeOf,
ReflectApply,
@@ -336,12 +337,15 @@ function ClientRequest(input, options, cb) {
OutgoingMessage.call(this);
+ let pathIsFromURL = false;
if (typeof input === 'string') {
const urlStr = input;
input = urlToHttpOptions(new URL(urlStr));
+ pathIsFromURL = true;
} else if (isURL(input)) {
// url.URL instance
input = urlToHttpOptions(input);
+ pathIsFromURL = true;
} else {
cb = options;
options = input;
@@ -352,6 +356,13 @@ function ClientRequest(input, options, cb) {
cb = options;
options = input || kEmptyObject;
} else {
+ const hasPathOverride = pathIsFromURL &&
+ options != null &&
+ ObjectHasOwn(options, 'path');
+ if (hasPathOverride) {
+ pathIsFromURL = false;
+ }
+
options = ObjectAssign({ __proto__: null }, input, options);
}
@@ -471,7 +482,13 @@ function ClientRequest(input, options, cb) {
this.joinDuplicateHeaders = options.joinDuplicateHeaders;
- this[kPath] = options.path || '/';
+ let path = options.path || '/';
+ // Strip the leading slash added when the CONNECT target comes from a URL.
+ if (method === 'CONNECT' && pathIsFromURL && path[0] === '/') {
+ path = path.slice(1) || '/';
+ }
+
+ this[kPath] = path;
if (cb) {
this.once('response', cb);
}
diff --git a/test/parallel/test-http-request-connect-path.js b/test/parallel/test-http-request-connect-path.js
new file mode 100644
index 00000000000..91b3cc73943
--- /dev/null
+++ b/test/parallel/test-http-request-connect-path.js
@@ -0,0 +1,56 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const http = require('http');
+
+{
+ const server = http.createServer(common.mustNotCall());
+
+ server.on('connect', common.mustCall((req, socket) => {
+ assert.strictEqual(req.url, 'example.com');
+ socket.end('HTTP/1.1 501 Not Implemented\r\n\r\n');
+ }));
+
+ server.listen(0, common.mustCall(() => {
+ const port = server.address().port;
+ const req = http.request(
+ new URL(`http://localhost:${port}/example.com`),
+ { method: 'CONNECT' },
+ );
+
+ req.on('connect', common.mustCall((res, socket) => {
+ assert.strictEqual(res.statusCode, 501);
+ socket.destroy();
+ server.close();
+ }));
+
+ req.end();
+ }));
+}
+
+{
+ const server = http.createServer(common.mustNotCall());
+
+ server.on('connect', common.mustCall((req, socket) => {
+ assert.strictEqual(req.url, '/example.com');
+ socket.end('HTTP/1.1 501 Not Implemented\r\n\r\n');
+ }));
+
+ server.listen(0, common.mustCall(() => {
+ const req = http.request({
+ host: 'localhost',
+ port: server.address().port,
+ method: 'CONNECT',
+ path: '/example.com',
+ });
+
+ req.on('connect', common.mustCall((res, socket) => {
+ assert.strictEqual(res.statusCode, 501);
+ socket.destroy();
+ server.close();
+ }));
+
+ req.end();
+ }));
+}