diff --git a/src/packages/core/src/server.ts b/src/packages/core/src/server.ts index 67542c11b4..9c2a7139c3 100644 --- a/src/packages/core/src/server.ts +++ b/src/packages/core/src/server.ts @@ -185,6 +185,23 @@ export class Server< host = null; } const callbackIsFunction = typeof callback === "function"; + + // Method signature specifies port: number, but we parse a string if provided + // inspiration taken from nodejs internal port validator + // https://github.com/nodejs/node/blob/8c4b8b201ada6b76d5306c9c7f352e45087fb4a9/lib/internal/validators.js#L208-L219 + if ((typeof port !== 'number' && typeof port !== 'string') || + (typeof port === 'string' && (port).trim().length === 0) || + +port !== (+port >>> 0) || + port > 0xFFFF || + port === 0) { + const err = new Error(`Port should be >= 0 and < 65536. Received ${port}.`); + + return callbackIsFunction + ? process.nextTick(callback!, err) + : Promise.reject(err); + } + const portNumber = +port; + const status = this.#status; if (status === ServerStatus.closing) { // if closing @@ -195,7 +212,7 @@ export class Server< } else if ((status & ServerStatus.openingOrOpen) !== 0) { // if opening or open const err = new Error( - `Server is already open, or is opening, on port: ${port}.` + `Server is already open, or is opening, on port: ${portNumber}.` ); return callbackIsFunction ? process.nextTick(callback!, err) @@ -214,11 +231,11 @@ export class Server< host ? (this.#app as any).listen( host, - port, + portNumber, LIBUS_LISTEN_EXCLUSIVE_PORT, resolve ) - : this.#app.listen(port, LIBUS_LISTEN_EXCLUSIVE_PORT, resolve); + : this.#app.listen(portNumber, LIBUS_LISTEN_EXCLUSIVE_PORT, resolve); } ).then(listenSocket => { if (listenSocket) { @@ -228,7 +245,7 @@ export class Server< this.#status = ServerStatus.closed; const err = new Error( `listen EADDRINUSE: address already in use ${host || DEFAULT_HOST - }:${port}.` + }:${portNumber}.` ); throw err; } diff --git a/src/packages/core/tests/server.test.ts b/src/packages/core/tests/server.test.ts index 86c3f97713..a75923b2c5 100644 --- a/src/packages/core/tests/server.test.ts +++ b/src/packages/core/tests/server.test.ts @@ -70,10 +70,10 @@ describe("server", () => { (process.env.GITHUB_ACTION ? describe : describe.skip)("listen", function () { /** * Sends a post request to the server and returns the response. - * @param address - * @param port - * @param json - * @returns + * @param address + * @param port + * @param json + * @returns */ function post(host: string, port: number, json: any) { const data = JSON.stringify(json); @@ -299,6 +299,47 @@ describe("server", () => { }); }); + it("accepts port as number type or binary, octal, decimal or hexadecimal string", async () => { + const validPorts = [ + port, `0b${port.toString(2)}`, + `0o${port.toString(8)}`, port.toString(10), + `0x${port.toString(16)}` + ]; + + for (const specificPort of validPorts) { + s = Ganache.server(defaultOptions); + await s.listen(specificPort); + + try { + const req = request.post(`http://localhost:${+specificPort}`); + await req.send(jsonRpcJson); + } finally { + await teardown(); + } + } + }); + + it("fails with invalid ports", async () => { + const invalidPorts = [ + -1, 'a', {}, [], false, true, + 0xFFFF + 1, Infinity, -Infinity, NaN, + undefined, null, '', ' ', 1.1, '0x', + '-0x1', '-0o1', '-0b1', '0o', '0b', 0 + ]; + + for (const specificPort of invalidPorts) { + s = Ganache.server(defaultOptions); + + try { + await assert.rejects(s.listen(specificPort), { + message: `Port should be >= 0 and < 65536. Received ${specificPort}.` + }); + } finally { + await teardown(); + } + } + }); + it("fails to `.listen()` twice, Promise", async () => { await setup(); try {