Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure IPv6 hostname is enclosed within square brackets in Host header #5314

Closed
wants to merge 1 commit into from

Conversation

mpotra
Copy link
Contributor

@mpotra mpotra commented Feb 18, 2016

Fixes #5308

As per https://tools.ietf.org/html/rfc7230#section-5.4 and https://tools.ietf.org/html/rfc3986#section-3.2.2 IPv6 addresses in Host header (URI), must be enclosed within square brackets, in order to properly separate the host address from any port reference.

@mscdex mscdex added the http Issues or PRs related to the http subsystem. label Feb 19, 2016
@mscdex
Copy link
Contributor

mscdex commented Feb 19, 2016

It looks like the file permissions for _http_client.js were changed?

@mpotra
Copy link
Contributor Author

mpotra commented Feb 19, 2016

Thank you for noticing, and I apologize for the mistake. Not sure how that happened, but I'll update the PR with the permissions change reverted.

// For the Host header, ensure that IPv6 addresses are enclosed
// in square brackets, as defined by URI formatting
// https://tools.ietf.org/html/rfc3986#section-3.2.2
if ('[' !== hostHeader[0] && -1 !== hostHeader.indexOf(':')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use net.isIPv6(hostHeader) here instead?

// in square brackets, as defined by URI formatting
// https://tools.ietf.org/html/rfc3986#section-3.2.2
if (net.isIPv6(hostHeader)) {
hostHeader = '[' + hostHeader + ']';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very minor nit... these can use template strings now.. [${hostHeader}]... not critical tho :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't string concatenation faster than template strings?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to say that some benchmarks have shown that template strings do not introduce any performance issues, maybe even improve performance. Will have to look and see if I can find them though.

@jasnell
Copy link
Member

jasnell commented Feb 19, 2016

LGTM

req.end();
}

server = http.createServer(function(req, res) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you wrap this in a common.mustCall(function(req, res) { }), then the hadRequest can be be totally removed. It doesn't look like hadError is used at all, so that can be removed. With those two gone, we can also remove the process.on('exit') listener.

@evanlucas
Copy link
Contributor

If you can address the nits that both myself and @jasnell have mentioned, LGTM. Once that is done, we can run the CI and make sure everything is passing. Thanks for the contribution!

// For the Host header, ensure that IPv6 addresses are enclosed
// in square brackets, as defined by URI formatting
// https://tools.ietf.org/html/rfc3986#section-3.2.2
if (net.isIPv6(hostHeader)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this too heavyweight?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my initial PR, IPv6 checking was done by hostHeader.indexOf(':'), just as you also suggested in #5308
However, now I agree that net.isIPv6() is a much more reliable way of testing for IPv6.

I don't necessarily think it's too heavyweight, since it relies on uv_inet_pton. I guess it's slightly slower than indexOf, but since it also checks for valid characters inside the string, I believe it's worth the extra cycles.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were no check for correctness here before, so I assume that there are two possibilities:

  1. Host is check before and is valid at this point, so if there is : then it can only possibly be IPv6 address.
  2. Host isn't checked before, so it might be invalid. If it has : then it is either IPv6 or invalid. If we wrap invalid host in brackets it would still be invalid, so it doesn't really matter.

isIPv6 involves calling C++ code and also checking if argument is IPv4 before checking if it's IPv6.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Host value doesn't pass through proper validation. It actually relies on either url.parse() to produce a host value, or simply takes options.hostname || options.host. At this stage, especially if options object is passed to the request(), hostHeader could even be www.domain.tld:3000, before reaching my code; (which is not wrong, only unsupported atm). In this case, a simple : check would yield an invalid Host header [www.domain.tld:3000].

Secondly, the RFC says that if (IP is IPv6) then [IP], which in my view makes it mandatory to make absolutely sure that whatever I enclose within square brackets, is an IPv6 address and not anything else (even if invalid).

I've been considering all of the above right from my initial PR, including the fact that indexOf is faster than isIPv6. However, since all points of view express advantages and disadvantages, I also opted for net.isIPv6 in the end, because that allows my fix to avoid type assumptions and ultimately make the code terse, and enforces the RFC statement.

While the above explains my preference for net.isIPv6, if there's consensus on reverting it back to : matching, I'll happily do it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Secondly, the RFC says that if (IP is IPv6) then [IP], which in my view makes it mandatory to make absolutely sure that whatever I enclose within square bracket

It doesn't say that wrapping invalid host with brackets makes it more invalid, that's all I'm saying.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing a quick scan for two :'s may be a simple workaround here. I agree that dropping down to the native code to check is a bit overweight for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I agree. Just ran some benchmarks and net.isIPv6 runs about 3-6 times slower than an indexOf check here.

@mpotra
Copy link
Contributor Author

mpotra commented Feb 22, 2016

I've just pushed two commits with the following:

  • drop net.isIPv6 for indexOf(); checking for the presence of two colons, to avoid false positives when port is present. Reintroduced skipping when the hostname is already an IPv6 within square brackets.
  • Replaced string concatenation hostHeader = '[' + hostHeader +']' with string literal hostHeader = [${hostHeader}]``

@evanlucas
Copy link
Contributor

@jasnell
Copy link
Member

jasnell commented Mar 2, 2016

Still LGTM. @nodejs/http @nodejs/ctc

@jasnell
Copy link
Member

jasnell commented Mar 21, 2016

@evanlucas ... does this LGTY?

@evanlucas
Copy link
Contributor

LGTM

@jasnell
Copy link
Member

jasnell commented Mar 22, 2016

req.end();
}

server = http.createServer(common.mustCall(function(req, res) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would just make this const server

@jasnell
Copy link
Member

jasnell commented Mar 22, 2016

CI is green, added a small nit.

@mpotra
Copy link
Contributor Author

mpotra commented Mar 22, 2016

Modified to use const server

@jasnell
Copy link
Member

jasnell commented Mar 22, 2016

New CI after the rebase: https://ci.nodejs.org/job/node-test-pull-request/2021/

@mpotra
Copy link
Contributor Author

mpotra commented Mar 24, 2016

Apparently CI failed on "node-test-binary-arm » 3,pi1-raspbian-wheezy" https://ci.nodejs.org/job/node-test-binary-arm/1448//console
Could it be from my const server commit?

@jasnell
Copy link
Member

jasnell commented Mar 24, 2016

No, we've been seeing an increase in random flakiness with tests on the pi devices. I don't think the failure is related. /cc @nodejs/testing

@jasnell
Copy link
Member

jasnell commented Mar 24, 2016

Can I ask you to please squash the commits down into one commit now? Thank you!

@mpotra
Copy link
Contributor Author

mpotra commented Mar 24, 2016

Sure, done.

@evanlucas
Copy link
Contributor

@mpotra to clarify, the tests that live in test/internet are not run with our CI. Please put the test in test/parallel and then we will run the CI one last time and get this thing landed!!

@mpotra
Copy link
Contributor Author

mpotra commented Mar 25, 2016

Thanks, and sorry for having it under internet in the first place. It's adjusted now.

@evanlucas
Copy link
Contributor

No problem at all. Sorry for just noticing :]

I've started another CI run: https://ci.nodejs.org/job/node-test-pull-request/2063/

IPv6 addresses in Host header (URI), must be enclosed within
square brackets, in order to properly separate the host address
from any port reference.

test: add test for IPv6 hostname conformance in Host header
http: Ensure IPv6 is enclosed within square brackets in Host header
http: use net.isIPv6 in ClientRequest
test: update test with const instead of var
test: use common.mustCall and cleanup
http: ClientRequest, drop isIPv6() in favor of indexOf() checks
http: ClientRequest, replace concatenation with string literal
test: use const
test: clean-up
test: move test to parallel
test: skip if no IPv6 support
@mpotra
Copy link
Contributor Author

mpotra commented Mar 25, 2016

Two failures: 1 is parallel/test-tls-sni-option on smartos, but the arm (p1/p2-raspbian) fails on my test:

not ok 61 test-http-host-header-ipv6-fail.js
# events.js:154
# throw er; // Unhandled 'error' event
# ^
# 
# Error: listen EAFNOSUPPORT ::1:12346
# at Object.exports._errnoException (util.js:893:11)
# at exports._exceptionWithHostPort (util.js:916:20)
# at Server._listen2 (net.js:1225:19)
# at listen (net.js:1274:10)
# at net.js:1384:9
# at _combinedTickCallback (internal/process/next_tick.js:77:11)
# at process._tickCallback (internal/process/next_tick.js:98:9)
# at Function.Module.runMain (module.js:453:11)
# at startup (node.js:155:18)
# at node.js:433:3

Apparently it cannot bind ::1 IPv6. Any suggestions?
I guess I should I check for IPv6 support within interfaces, first..

@mpotra
Copy link
Contributor Author

mpotra commented Mar 25, 2016

Updated with IPv6 check, as per nodejs/node-v0.x-archive#7983

@evanlucas
Copy link
Contributor

@mpotra
Copy link
Contributor Author

mpotra commented Mar 30, 2016

Everything looks green, except a test-dns.js timeout, which is not related to this PR, on p2-raspbian`.

Let me know if there's anything else I can do.

@jasnell
Copy link
Member

jasnell commented Apr 1, 2016

LGTM

jasnell pushed a commit that referenced this pull request Apr 1, 2016
IPv6 addresses in Host header (URI), must be enclosed within
square brackets, in order to properly separate the host address
from any port reference.

PR-URL: #5314
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Evan Lucas <[email protected]>
@jasnell
Copy link
Member

jasnell commented Apr 1, 2016

Landed in dabe1d5

@jasnell jasnell closed this Apr 1, 2016
MylesBorins pushed a commit that referenced this pull request Apr 5, 2016
IPv6 addresses in Host header (URI), must be enclosed within
square brackets, in order to properly separate the host address
from any port reference.

PR-URL: #5314
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Evan Lucas <[email protected]>
MylesBorins pushed a commit that referenced this pull request Apr 5, 2016
Notable changes:

http:
  * Enclose IPv6 Host header in square brackets. This will enable
  proper seperation of the host adress from any port reference
  (Mihai Potra) #5314
path:
  * Make win32.isAbsolute more consistent (Brian White)
  #6028
This was referenced Apr 5, 2016
MylesBorins pushed a commit that referenced this pull request Apr 5, 2016
Notable changes:

http:
  * Enclose IPv6 Host header in square brackets. This will enable
  proper seperation of the host adress from any port reference
  (Mihai Potra) #5314

path:
  * Make win32.isAbsolute more consistent (Brian White)
  #6028

PR-URL: #6060
Reviewed-By: Jeremiah Senkpiel <[email protected]>
MylesBorins pushed a commit that referenced this pull request Apr 5, 2016
Notable changes:

http:
  * Enclose IPv6 Host header in square brackets. This will enable
  proper seperation of the host adress from any port reference
  (Mihai Potra) #5314

path:
  * Make win32.isAbsolute more consistent (Brian White)
  #6028

PR-URL: #6060
Reviewed-By: Jeremiah Senkpiel <[email protected]>
MylesBorins pushed a commit that referenced this pull request Apr 16, 2016
IPv6 addresses in Host header (URI), must be enclosed within
square brackets, in order to properly separate the host address
from any port reference.

PR-URL: #5314
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Evan Lucas <[email protected]>
MylesBorins pushed a commit that referenced this pull request Apr 22, 2016
IPv6 addresses in Host header (URI), must be enclosed within
square brackets, in order to properly separate the host address
from any port reference.

PR-URL: #5314
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Evan Lucas <[email protected]>
MylesBorins pushed a commit that referenced this pull request May 6, 2016
IPv6 addresses in Host header (URI), must be enclosed within
square brackets, in order to properly separate the host address
from any port reference.

PR-URL: #5314
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Evan Lucas <[email protected]>
MylesBorins pushed a commit that referenced this pull request May 18, 2016
IPv6 addresses in Host header (URI), must be enclosed within
square brackets, in order to properly separate the host address
from any port reference.

PR-URL: #5314
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Evan Lucas <[email protected]>
@MylesBorins MylesBorins mentioned this pull request May 18, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
http Issues or PRs related to the http subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

http.request sends non-standard Host header when IP6 address + port is used
6 participants