diff --git a/lib/_http_client.js b/lib/_http_client.js index 9e2ebca8ee5c..7450dd8e4805 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -49,14 +49,19 @@ const Agent = require('_http_agent'); const { Buffer } = require('buffer'); const { defaultTriggerAsyncIdScope } = require('internal/async_hooks'); const { URL, urlToOptions, searchParamsSymbol } = require('internal/url'); -const { kOutHeaders, kNeedDrain } = require('internal/http'); +const { + kOutHeaders, + kNeedDrain, + isValidConnectPath +} = require('internal/http'); const { connResetException, codes } = require('internal/errors'); const { ERR_HTTP_HEADERS_SENT, ERR_INVALID_ARG_TYPE, ERR_INVALID_HTTP_TOKEN, ERR_INVALID_PROTOCOL, - ERR_UNESCAPED_CHARACTERS + ERR_UNESCAPED_CHARACTERS, + ERR_INVALID_ARG_VALUE } = codes; const { validateInteger } = require('internal/validators'); const { getTimerDuration } = require('internal/timers'); @@ -197,7 +202,22 @@ function ClientRequest(input, options, cb) { } this.insecureHTTPParser = insecureHTTPParser; - this.path = options.path || '/'; + path = options.path; + if (path) { + if (method === 'CONNECT') { + if (path.startsWith('/')) { + // if path is '/' + path = path.slice(1) || '/'; + if (!isValidConnectPath(path)) { + throw new ERR_INVALID_ARG_VALUE('options.path', + path, + 'must be a valid host:port combo'); + } + } + } + } + this.path = path || '/'; + if (cb) { this.once('response', cb); } diff --git a/lib/internal/http.js b/lib/internal/http.js index aab4170a2f0e..6b7cfe9a19e6 100644 --- a/lib/internal/http.js +++ b/lib/internal/http.js @@ -6,6 +6,7 @@ const { const { setUnrefTimeout } = require('internal/timers'); const { PerformanceEntry, notify } = internalBinding('performance'); +const { URL } = require('internal/url'); let nowCache; let utcCache; @@ -32,6 +33,13 @@ function resetCache() { utcCache = undefined; } +function isValidConnectPath(path) { + const url = new URL(`http://${path}`); + if (url.hostname && url.port && `http://${url.host}/` === url.href) + return true; + return false; +} + class HttpRequestTiming extends PerformanceEntry { constructor(statistics) { super(); @@ -53,5 +61,6 @@ module.exports = { kNeedDrain: Symbol('kNeedDrain'), nowDate, utcDate, - emitStatistics + emitStatistics, + isValidConnectPath }; diff --git a/test/parallel/test-http-request-connect-method.js b/test/parallel/test-http-request-connect-method.js new file mode 100644 index 000000000000..f92fbf6415a5 --- /dev/null +++ b/test/parallel/test-http-request-connect-method.js @@ -0,0 +1,42 @@ +// Flags: --expose-internals + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); +const { isValidConnectPath } = require('internal/http'); + +const server = http.createServer(); + +server.on('connect', common.mustCall((req, stream) => { + assert.strictEqual(req.url, 'example.com:80'); + stream.end('HTTP/1.1 501 Not Implemented\r\n\r\n'); +})); + +server.listen(0); + +server.on('listening', common.mustCall(() => { + const url = new URL(`http://localhost:${server.address().port}/example.com:80`); + let req = http.request(url, { method: 'CONNECT' }).end(); + // invalid path + const invalidPathURL = new URL(`http://localhost:${server.address().port}/example.com`); + assert.throws( + () => { + req = http.request(invalidPathURL, { method: 'CONNECT' }).end(); + }, + { + code: 'ERR_INVALID_ARG_VALUE', + name: 'TypeError', + message: /^The argument 'options\.path' must be a valid host:port combo\. Received .+$/ + } + ); + req.once('connect', common.mustCall((res) => { + res.destroy(); + server.close(); + })); +})); + +['example.com', 'example.com:8080/example'].forEach((path) => { + assert.strictEqual(isValidConnectPath(path), false); +});