-
Notifications
You must be signed in to change notification settings - Fork 7.3k
Conversation
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. |
Adding to the 0.12.1 milestone as it seems like a significant regression to me. |
@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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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))
There was a problem hiding this comment.
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
Couple comments, otherwise LGTM. |
|
||
connect({ | ||
host: 'localhost', | ||
port: 0 |
There was a problem hiding this comment.
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?
@cjihrig Thanks Colin! I added my comments inline. |
@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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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);
}
There was a problem hiding this comment.
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.
@misterdjules updated to use |
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); |
There was a problem hiding this comment.
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.
@misterdjules updated. |
@cjihrig Pasting what I said in #libuv to make sure it's not lost: Another concern I had is that actually 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 What do you think? |
The best way for number coercion is |
Updated to a slightly new approach. Added more tests, and incorporated @trevnorris |
LGTM |
@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. |
More precisely, tests for UNICEs here: http://jenkins.nodejs.org/job/node-test-commit-unix/23/ and Windows here: http://jenkins.nodejs.org/job/node-test-commit-windows/22/. |
One test failing on SmartOS: http://jenkins.nodejs.org/job/node-test-commit-unix/23/DESTCPU=ia32,label=smartos/tapTestReport/simple.tap-437/, |
The added validation allows non-negative numbers and numeric strings. All other values result in a thrown exception.
@misterdjules updated |
@cjihrig All tests passed. |
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 <[email protected]> Reviewed-By: Trevor Norris <[email protected]> Reviewed-By: James M Snell <[email protected]>
Thanks! Landed in 1a2a4da |
This allows port 0 to be passed to
Socket.prototype.connect()
andnet.createConnection()
. However, by allowing port 0, we are eliminating all type checking sincevar port = options.port | 0;
converts everything to a number. More thorough checking could be added by verifying thatoptions.port
isundefined
, a number, or a numeric string (which must be supported for compatibility withurl.parse()
results), but at what point does it become overkill.Closes #9194. cc: @trevnorris