From 124f7ebf705366b2e1844dff721928d270f87895 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Tue, 9 Aug 2022 06:29:11 -0300 Subject: [PATCH] Merge pull request from GHSA-8qr4-xgw6-wmr3 --- index.js | 7 ++- lib/core/util.js | 17 +++++- test/request.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 test/request.js diff --git a/index.js b/index.js index b2144c844ba..8099f5a692f 100644 --- a/index.js +++ b/index.js @@ -53,7 +53,12 @@ function makeDispatcher (fn) { throw new InvalidArgumentError('invalid opts.path') } - url = new URL(opts.path, util.parseOrigin(url)) + let path = opts.path + if (!opts.path.startsWith('/')) { + path = `/${path}` + } + + url = new URL(util.parseOrigin(url).origin + path) } else { if (!opts) { opts = typeof url === 'object' ? url : {} diff --git a/lib/core/util.js b/lib/core/util.js index 635ef2e15f2..5b0c5d1ef39 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -108,14 +108,25 @@ function parseURL (url) { const port = url.port != null ? url.port : (url.protocol === 'https:' ? 443 : 80) - const origin = url.origin != null + let origin = url.origin != null ? url.origin : `${url.protocol}//${url.hostname}:${port}` - const path = url.path != null + let path = url.path != null ? url.path : `${url.pathname || ''}${url.search || ''}` - url = new URL(path, origin) + if (origin.endsWith('/')) { + origin = origin.substring(0, origin.length - 1) + } + + if (path && !path.startsWith('/')) { + path = `/${path}` + } + // new URL(path, origin) is unsafe when `path` contains an absolute URL + // From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL: + // If first parameter is a relative URL, second param is required, and will be used as the base URL. + // If first parameter is an absolute URL, a given second param will be ignored. + url = new URL(origin + path) } return url diff --git a/test/request.js b/test/request.js new file mode 100644 index 00000000000..52e3ecff2fc --- /dev/null +++ b/test/request.js @@ -0,0 +1,151 @@ +'use strict' + +const { createServer } = require('http') +const { test } = require('tap') +const { request } = require('..') + +test('no-slash/one-slash pathname should be included in req.path', async (t) => { + const pathServer = createServer((req, res) => { + t.fail('it shouldn\'t be called') + res.statusCode = 200 + res.end('hello') + }) + + const requestedServer = createServer((req, res) => { + t.equal(`/localhost:${pathServer.address().port}`, req.url) + t.equal('GET', req.method) + t.equal(`localhost:${requestedServer.address().port}`, req.headers.host) + res.statusCode = 200 + res.end('hello') + }) + + t.teardown(requestedServer.close.bind(requestedServer)) + t.teardown(pathServer.close.bind(pathServer)) + + await Promise.all([ + requestedServer.listen(0), + pathServer.listen(0) + ]) + + const noSlashPathname = await request({ + method: 'GET', + origin: `http://localhost:${requestedServer.address().port}`, + pathname: `localhost:${pathServer.address().port}` + }) + t.equal(noSlashPathname.statusCode, 200) + const noSlashPath = await request({ + method: 'GET', + origin: `http://localhost:${requestedServer.address().port}`, + path: `localhost:${pathServer.address().port}` + }) + t.equal(noSlashPath.statusCode, 200) + const noSlashPath2Arg = await request( + `http://localhost:${requestedServer.address().port}`, + { path: `localhost:${pathServer.address().port}` } + ) + t.equal(noSlashPath2Arg.statusCode, 200) + const oneSlashPathname = await request({ + method: 'GET', + origin: `http://localhost:${requestedServer.address().port}`, + pathname: `/localhost:${pathServer.address().port}` + }) + t.equal(oneSlashPathname.statusCode, 200) + const oneSlashPath = await request({ + method: 'GET', + origin: `http://localhost:${requestedServer.address().port}`, + path: `/localhost:${pathServer.address().port}` + }) + t.equal(oneSlashPath.statusCode, 200) + const oneSlashPath2Arg = await request( + `http://localhost:${requestedServer.address().port}`, + { path: `/localhost:${pathServer.address().port}` } + ) + t.equal(oneSlashPath2Arg.statusCode, 200) + t.end() +}) + +test('protocol-relative URL as pathname should be included in req.path', async (t) => { + const pathServer = createServer((req, res) => { + t.fail('it shouldn\'t be called') + res.statusCode = 200 + res.end('hello') + }) + + const requestedServer = createServer((req, res) => { + t.equal(`//localhost:${pathServer.address().port}`, req.url) + t.equal('GET', req.method) + t.equal(`localhost:${requestedServer.address().port}`, req.headers.host) + res.statusCode = 200 + res.end('hello') + }) + + t.teardown(requestedServer.close.bind(requestedServer)) + t.teardown(pathServer.close.bind(pathServer)) + + await Promise.all([ + requestedServer.listen(0), + pathServer.listen(0) + ]) + + const noSlashPathname = await request({ + method: 'GET', + origin: `http://localhost:${requestedServer.address().port}`, + pathname: `//localhost:${pathServer.address().port}` + }) + t.equal(noSlashPathname.statusCode, 200) + const noSlashPath = await request({ + method: 'GET', + origin: `http://localhost:${requestedServer.address().port}`, + path: `//localhost:${pathServer.address().port}` + }) + t.equal(noSlashPath.statusCode, 200) + const noSlashPath2Arg = await request( + `http://localhost:${requestedServer.address().port}`, + { path: `//localhost:${pathServer.address().port}` } + ) + t.equal(noSlashPath2Arg.statusCode, 200) + t.end() +}) + +test('Absolute URL as pathname should be included in req.path', async (t) => { + const pathServer = createServer((req, res) => { + t.fail('it shouldn\'t be called') + res.statusCode = 200 + res.end('hello') + }) + + const requestedServer = createServer((req, res) => { + t.equal(`/http://localhost:${pathServer.address().port}`, req.url) + t.equal('GET', req.method) + t.equal(`localhost:${requestedServer.address().port}`, req.headers.host) + res.statusCode = 200 + res.end('hello') + }) + + t.teardown(requestedServer.close.bind(requestedServer)) + t.teardown(pathServer.close.bind(pathServer)) + + await Promise.all([ + requestedServer.listen(0), + pathServer.listen(0) + ]) + + const noSlashPathname = await request({ + method: 'GET', + origin: `http://localhost:${requestedServer.address().port}`, + pathname: `http://localhost:${pathServer.address().port}` + }) + t.equal(noSlashPathname.statusCode, 200) + const noSlashPath = await request({ + method: 'GET', + origin: `http://localhost:${requestedServer.address().port}`, + path: `http://localhost:${pathServer.address().port}` + }) + t.equal(noSlashPath.statusCode, 200) + const noSlashPath2Arg = await request( + `http://localhost:${requestedServer.address().port}`, + { path: `http://localhost:${pathServer.address().port}` } + ) + t.equal(noSlashPath2Arg.statusCode, 200) + t.end() +})