diff --git a/.changeset/validate-listen-port.md b/.changeset/validate-listen-port.md new file mode 100644 index 00000000..d7b6270b --- /dev/null +++ b/.changeset/validate-listen-port.md @@ -0,0 +1,5 @@ +--- +'serve': patch +--- + +Validate `--listen` port numbers instead of crashing with a RangeError diff --git a/source/utilities/cli.ts b/source/utilities/cli.ts index 726c5268..828ac015 100644 --- a/source/utilities/cli.ts +++ b/source/utilities/cli.ts @@ -102,8 +102,14 @@ export const getHelpText = (): string => helpText; * @returns A list of parsed endpoints. */ export const parseEndpoint = (uriOrPort: string): ParsedEndpoint => { - // If the endpoint is a port number, return it as is. - if (!isNaN(Number(uriOrPort))) return { port: Number(uriOrPort) }; + // If the endpoint is a port number, return it as is. Reject numbers the + // socket layer cannot bind to, instead of crashing later with a RangeError. + if (!isNaN(Number(uriOrPort))) { + const port = Number(uriOrPort); + if (!Number.isInteger(port) || port < 0 || port > 65535) + throw new Error(`Invalid port number: ${uriOrPort}`); + return { port }; + } // Cast it as a string, since we know for sure it is not a number. const endpoint = uriOrPort; @@ -125,14 +131,19 @@ export const parseEndpoint = (uriOrPort: string): ParsedEndpoint => { throw new Error(`Invalid UNIX domain socket endpoint: ${endpoint}`); return { host: url.pathname }; - case 'tcp:': + case 'tcp:': { url.port = url.port ?? '3000'; url.hostname = url.hostname ?? 'localhost'; + const port = Number(url.port); + if (!Number.isInteger(port) || port < 0 || port > 65535) + throw new Error(`Invalid port number: ${url.port}`); + return { - port: Number(url.port), + port, host: url.hostname, }; + } default: throw new Error( `Unknown --listen endpoint scheme (protocol): ${ diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 21ba189a..e666b990 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -35,6 +35,11 @@ const invalidEndpoints = [ ['protocol', 'ws://localhost', /unknown.*endpoint.*scheme.*/i], ['unix socket', 'unix://', /invalid.*unix.*socket.*/i], ['windows pipe', 'pipe:\\localhost', /invalid.*pipe.*/i], + ['negative port', '-1', /invalid.*port/i], + ['out of range port', '99999', /invalid.*port/i], + ['fractional port', '3000.5', /invalid.*port/i], + ['non-finite port', 'Infinity', /invalid.*port/i], + ['tcp out of range port', 'tcp://localhost:99999', /invalid.*port/i], ] as InvalidEndpointTestCase[]; describe('utilities/cli', () => {