Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

net: allow port 0 in connect() #9268

Closed
wants to merge 1 commit into from
Closed

net: allow port 0 in connect() #9268

wants to merge 1 commit into from

Conversation

cjihrig
Copy link

@cjihrig cjihrig commented Feb 23, 2015

This allows port 0 to be passed to Socket.prototype.connect() and net.createConnection(). However, by allowing port 0, we are eliminating all type checking since var port = options.port | 0; converts everything to a number. More thorough checking could be added by verifying that options.port is undefined, a number, or a numeric string (which must be supported for compatibility with url.parse() results), but at what point does it become overkill.

Closes #9194. cc: @trevnorris

@misterdjules
Copy link

I think that adding additional checks to throw except for valid numerical strings and numbers is worthwhile, especially if the original code was doing that.

@misterdjules misterdjules added this to the 0.12.1 milestone Feb 25, 2015
@misterdjules
Copy link

Adding to the 0.12.1 milestone as it seems like a significant regression to me.

@cjihrig
Copy link
Author

cjihrig commented Feb 25, 2015

@misterdjules added type checking back. PTAL

@@ -906,8 +906,11 @@ Socket.prototype.connect = function(options, cb) {
if (localPort && !util.isNumber(localPort))
throw new TypeError('localPort should be a number: ' + localPort);

if (port <= 0 || port > 65535)
throw new RangeError('port should be > 0 and < 65536: ' + port);
if (!util.isUndefined(options.port) && options.port != port)

Choose a reason for hiding this comment

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

Do options.port >>> 0 === port && options.port >= 0. That's a uint check that excludes NaN and prevents wrap around.

Choose a reason for hiding this comment

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

This also means you can remove the additional below check. But you should do port >>>= options.port to make sure number strings (i.e. '0xfe') are properly coerced.

Choose a reason for hiding this comment

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

@trevnorris I'm not sure I understand what you mean in the previous two comments. Could you please elaborate?

Copy link
Author

Choose a reason for hiding this comment

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

@trevnorris I don't think that will work. I added more tests at @misterdjules request, and that validation failed on the empty string IIRC. What does seem to handle all cases is if (options.port !== undefined && parseInt(options.port, 10) !== port). I'm not big on using parseInt(), but it handles numeric strings well, which is the most annoying part of this check.

Choose a reason for hiding this comment

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

@cjihrig ah, my checks are to see if they are. so instead it would be:

if (!(options.port >>> 0 === options.port && options.port >= 0))

Copy link
Author

Choose a reason for hiding this comment

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

@trevnorris I'm still having some trouble with your checks, and variations on them:

if ((options.port >>> 0 === options.port && options.port >= 0)) throws on valid port 12346
if (!(options.port >>> 0 === options.port && options.port >= 0)) throws on valid port '12346'
if (!(options.port >>> 0 === port && options.port >= 0)) does not throw on invalid port ''
if ((options.port >>> 0 === port && options.port >= 0)) throws on valid port 12346

@trevnorris
Copy link

Couple comments, otherwise LGTM.


connect({
host: 'localhost',
port: 0

Choose a reason for hiding this comment

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

Instead of removing this test, can we put it back and assert it doesn't throw?

@misterdjules
Copy link

@cjihrig Thanks Colin! I added my comments inline.

@cjihrig
Copy link
Author

cjihrig commented Feb 27, 2015

@misterdjules I made the test changes your requested.

@@ -906,8 +906,11 @@ Socket.prototype.connect = function(options, cb) {
if (localPort && !util.isNumber(localPort))
throw new TypeError('localPort should be a number: ' + localPort);

if (port <= 0 || port > 65535)
throw new RangeError('port should be > 0 and < 65536: ' + port);
if (options.port !== undefined && parseInt(options.port, 10) !== port)

Choose a reason for hiding this comment

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

Using parseInt we would accept "42foo" as a valid port, which we wouldn't accept before this change. I don't think we should accept these values, what do you think?

Copy link
Author

Choose a reason for hiding this comment

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

If options.port was "42foo", then parseInt() would return 42, but port would be 0, and an exception would be thrown.

Choose a reason for hiding this comment

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

Right, I thought "42foo" | 0 would evaluate to 42, which it does not. Thanks for the clarification!

Using parseInt only with a radix of 10 makes strings representing hexadecimal numbers such as 0x12 throw. The current version of the code does, and it seems that it matches with the concept of "undefined, a number, or a string representing a number".

I'm not sure if we want to support it though. I added support for strings representing hexadecimal numbers in a gist, and the problem is that it would accept invalid values such as 0x123R, since 0x123R | 0 === 0 and parseInt(0x123R, 10) === 0 too.

Other than that, I think the above-mentioned gist improves the current changes by making the checks a bit clearer though, so feel free to reuse it without the hexadecimal string support.

Choose a reason for hiding this comment

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

I created another gist that I think makes the check even clearer: https://gist.github.com/misterdjules/741cb578638b18f139ef.

Copy link
Author

Choose a reason for hiding this comment

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

Unfortunately, I thought that parseInt('0xff', 10) would return 255, but that is not the case. I think this could be the answer:

if (options.port !== undefined && parseFloat(options.port) !== port)

EDIT: Still doesn't handle hex strings

Copy link
Author

Choose a reason for hiding this comment

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

@misterdjules OK, I found this, which appears to handle everything I've tried so far. It's less efficient than I'd like, but could also be added to util. Thoughts?

function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

Choose a reason for hiding this comment

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

As mentioned in #libuv, isNumeric would not handle negative hexadecimal number strings, and thus isNumeric would be inconsistent. But we could have:

/*
 * This function returns `true` if `value` is a string that represents a valid
 * decimal or hexadecimal non-negative number, false otherwise.
 */
function isNonNegativeNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n) && n >= 0;
}

and use it like in the following gist: https://gist.github.com/misterdjules/7e4f25997b3805792aac.

@cjihrig
Copy link
Author

cjihrig commented Feb 27, 2015

@misterdjules updated to use IsUint32()

if (port <= 0 || port > 65535)
throw new RangeError('port should be > 0 and < 65536: ' + port);
if (options.port !== undefined && parseInt(options.port, 10) !== port)
throw new TypeError('port should be a number: ' + options.port);

Choose a reason for hiding this comment

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

We might also want to change the error message to 'port should be undefined or should represent a valid number: ' + options.port', but I'm not sure how much of a breaking change that would be for users who consume error messages.

@cjihrig
Copy link
Author

cjihrig commented Mar 1, 2015

@misterdjules updated.

@misterdjules
Copy link

@cjihrig Pasting what I said in #libuv to make sure it's not lost:

Another concern I had is that actually parseFloat doesn’t handle hexadecimal strings at all, for instance parseFloat(‘0xff’) === 0. In the current state of the PR, it works out correctly, but because we’re lucky.

It seems to me that your original method (comparing coercion to int’s result with parseInt’s output) would be more robust and consistent. We could add some additional checks and comments like something closer to what I had in this gist: https://gist.github.com/misterdjules/741cb578638b18f139ef with the error message mentioning that numbers and numeric strings are accepted instead of 'port shoud be a number'.

What do you think?

@trevnorris
Copy link

The best way for number coercion is +number. Use that instead of parseFloat().

@cjihrig
Copy link
Author

cjihrig commented Mar 3, 2015

Updated to a slightly new approach. Added more tests, and incorporated @trevnorris +number suggestion.

@jasnell
Copy link
Member

jasnell commented Mar 3, 2015

LGTM

@misterdjules
Copy link

@cjihrig I like it, thank you very much for persisting!

LGTM and tests running here: http://jenkins.nodejs.org/job/node-test-commit-unix/ and here: http://jenkins.nodejs.org/job/node-test-commit-windows/. I don't expect any of them to fail since all tests pass locally, but better safe than sorry.

@misterdjules
Copy link

@misterdjules
Copy link

The added validation allows non-negative numbers and numeric
strings. All other values result in a thrown exception.
@cjihrig
Copy link
Author

cjihrig commented Mar 4, 2015

@misterdjules updated Connect6

@misterdjules
Copy link

@cjihrig Tests running on UNIX and Windows.

@misterdjules
Copy link

@cjihrig All tests passed.

cjihrig added a commit that referenced this pull request Mar 5, 2015
The added validation allows non-negative numbers and numeric
strings. All other values result in a thrown exception.

Fixes: #9194
PR-URL: #9268
Reviewed-By: Julien Gilli <julien.gilli@joyent.com>
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: James M Snell <jasnell@users.noreply.github.com>
@cjihrig
Copy link
Author

cjihrig commented Mar 5, 2015

Thanks! Landed in 1a2a4da

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants