diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 404dd18e..754a15c1 100755 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -57,6 +57,22 @@ const supportedProtocols = platform.protocols.map((protocol) => { return protocol + ':'; }); +// Node's WHATWG URL parser returns `username` and `password` percent-encoded. +// Decode before composing the `auth` option so credentials such as +// `my%40email.com:pass` are sent as `my@email.com:pass`. Falls back to the +// original value for malformed input so a bad encoding never throws. +const decodeURIComponentSafe = (value) => { + if (!utils.isString(value)) { + return value; + } + + try { + return decodeURIComponent(value); + } catch (error) { + return value; + } +}; + const flushOnFinish = (stream, [throttled, flush]) => { stream.on('end', flush).on('error', flush); @@ -650,8 +666,8 @@ export default isHttpAdapterSupported && } if (!auth && parsed.username) { - const urlUsername = parsed.username; - const urlPassword = parsed.password; + const urlUsername = decodeURIComponentSafe(parsed.username); + const urlPassword = decodeURIComponentSafe(parsed.password); auth = urlUsername + ':' + urlPassword; } diff --git a/tests/unit/adapters/http.test.js b/tests/unit/adapters/http.test.js index 70c23bf1..d93c551d 100644 --- a/tests/unit/adapters/http.test.js +++ b/tests/unit/adapters/http.test.js @@ -890,6 +890,44 @@ describe('supports http with nodejs', () => { } }); + it('should decode basic auth credentials from the request URL', async () => { + const server = await startHTTPServer( + (req, res) => { + res.end(req.headers.authorization); + }, + { port: SERVER_PORT } + ); + + try { + const response = await axios.get( + `http://my%40email.com:pa%24ss@localhost:${server.address().port}/` + ); + const base64 = Buffer.from('my@email.com:pa$ss', 'utf8').toString('base64'); + assert.strictEqual(response.data, `Basic ${base64}`); + } finally { + await stopHTTPServer(server); + } + }); + + it('keeps malformed URL credentials percent-encoding and does not throw', async () => { + const server = await startHTTPServer( + (req, res) => { + res.end(req.headers.authorization); + }, + { port: SERVER_PORT } + ); + + try { + const response = await axios.get( + `http://user%:foo%zz@localhost:${server.address().port}/` + ); + const base64 = Buffer.from('user%:foo%zz', 'utf8').toString('base64'); + assert.strictEqual(response.data, `Basic ${base64}`); + } finally { + await stopHTTPServer(server); + } + }); + it('should support basic auth with a header', async () => { const server = await startHTTPServer( (req, res) => {