Skip to content

Passing username from URL object to http.clientRequest without decoding #31439

Closed
@izonder

Description

@izonder
  • Version: v8.x.x+
  • Platform: any
  • Subsystem: any

Description

Passing username with "unsafe" symbols (e.g. @) to URL object causes wrongly computed Basic-Authorization header string.

Pre-requisites

The next code looks good enough (Node.js CLI):

const {URL} = require('url');
const url = new URL('http://localhost');
url.username = 'test@test';
url.password = '123456';
console.log(url);

This should result in:

URL {
  href: 'http://test%40test:123456@localhost/',
  origin: 'http://localhost',
  protocol: 'http:',
  username: 'test%40test',
  password: '123456',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}

The field username turned to percent-encoded as mentioned in the documentation (https://nodejs.org/api/url.html#url_url_username). According to the composed URI in the field href it's working as expected.

Expected behavior

Reference calls via cURL will look like:

curl -u 'test@test:123456' -v 'http://localhost/'
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
* Server auth using Basic with user 'test@test'
> GET / HTTP/1.1
> Host: localhost
> Authorization: Basic dGVzdEB0ZXN0OjEyMzQ1Ng==
> User-Agent: curl/7.58.0
> Accept: */*

curl -v 'http://test%40test:123456@localhost/'
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
* Server auth using Basic with user 'test@test'
> GET / HTTP/1.1
> Host: localhost
> Authorization: Basic dGVzdEB0ZXN0OjEyMzQ1Ng==
> User-Agent: curl/7.58.0
> Accept: */*

Decoding the header Authorization: Basic dGVzdEB0ZXN0OjEyMzQ1Ng== results to test@test:123456 as expected.

Actual behavior

Again try to make the same call from Node.js CLI:

const {URL} = require('url');
const url = new URL('http://localhost');
url.username = 'test@test';
url.password = '123456';

const http = require('http');
console.log(http.get(url, () => {}).outputData);

That will output something like:

[
  {
    data: 'GET /profile HTTP/1.1\r\n' +
      'Host: localhost\r\n' +
      'Authorization: Basic dGVzdCU0MHRlc3Q6MTIzNDU2\r\n' +
      'Connection: close\r\n' +
      '\r\n',
    encoding: 'latin1',
    callback: [Function: bound onFinish]
  }
]

Decoding Authorization header results to test%40test:123456, which is wrong.

Expectation

When http.request(<URL>) grabs a value from href or username fields, it should sanitize and decode values before composing Authorization header.

-or-

WHATWG-URL should keep raw username and provide it like:

URL {
  href: 'http://test%40test:123456@localhost/', # here encoded values
  origin: 'http://localhost',
  protocol: 'http:',
  username: 'test@test', # here should be raw value
  password: '123456',
  host: 'localhost',
  hostname: 'localhost',
  port: '',
  pathname: '/',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    httpIssues or PRs related to the http subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions