Skip to content

Commit 4360686

Browse files
fix: properly close the websocket connection upon handshake error
This should fix the following error: > TypeError: Cannot read property 'writeHead' of undefined This bug was introduced by [1], because the `if (res !== undefined) { ... }` check was removed. But `res` is indeed undefined when the client connects with WebSocket directly, in that case we need to manually write the response content (in the abortUpgrade method). Please note that the previous behavior was invalid too, since the WebSocket connection was left open when an error occurred during the handshake. [1]: 7096e98
1 parent 29bd4fe commit 4360686

File tree

2 files changed

+58
-29
lines changed

2 files changed

+58
-29
lines changed

lib/server.js

+30-29
Original file line numberDiff line numberDiff line change
@@ -236,15 +236,17 @@ class Server extends EventEmitter {
236236
message: Server.errorMessages[errorCode],
237237
context: errorContext
238238
});
239-
sendErrorMessage(req, res, errorCode, errorContext);
239+
abortRequest(res, errorCode, errorContext);
240240
return;
241241
}
242242

243243
if (req._query.sid) {
244244
debug("setting new request for existing client");
245245
this.clients[req._query.sid].transport.onRequest(req);
246246
} else {
247-
this.handshake(req._query.transport, req);
247+
const closeConnection = (errorCode, errorContext) =>
248+
abortRequest(res, errorCode, errorContext);
249+
this.handshake(req._query.transport, req, closeConnection);
248250
}
249251
};
250252

@@ -273,9 +275,11 @@ class Server extends EventEmitter {
273275
*
274276
* @param {String} transport name
275277
* @param {Object} request object
278+
* @param {Function} closeConnection
279+
*
276280
* @api private
277281
*/
278-
async handshake(transportName, req) {
282+
async handshake(transportName, req, closeConnection) {
279283
const protocol = req._query.EIO === "4" ? 4 : 3; // 3rd revision by default
280284
if (protocol === 3 && !this.opts.allowEIO3) {
281285
debug("unsupported protocol version");
@@ -288,11 +292,7 @@ class Server extends EventEmitter {
288292
protocol
289293
}
290294
});
291-
sendErrorMessage(
292-
req,
293-
req.res,
294-
Server.errors.UNSUPPORTED_PROTOCOL_VERSION
295-
);
295+
closeConnection(Server.errors.UNSUPPORTED_PROTOCOL_VERSION);
296296
return;
297297
}
298298

@@ -310,7 +310,7 @@ class Server extends EventEmitter {
310310
error: e
311311
}
312312
});
313-
sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST);
313+
closeConnection(Server.errors.BAD_REQUEST);
314314
return;
315315
}
316316

@@ -341,7 +341,7 @@ class Server extends EventEmitter {
341341
error: e
342342
}
343343
});
344-
sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST);
344+
closeConnection(Server.errors.BAD_REQUEST);
345345
return;
346346
}
347347
const socket = new Socket(id, this, transport, req, protocol);
@@ -389,16 +389,16 @@ class Server extends EventEmitter {
389389
message: Server.errorMessages[errorCode],
390390
context: errorContext
391391
});
392-
abortConnection(socket, errorCode, errorContext);
392+
abortUpgrade(socket, errorCode, errorContext);
393393
return;
394394
}
395395

396396
const head = Buffer.from(upgradeHead); // eslint-disable-line node/no-deprecated-api
397397
upgradeHead = null;
398398

399399
// delegate to ws
400-
this.ws.handleUpgrade(req, socket, head, conn => {
401-
this.onWebSocket(req, conn);
400+
this.ws.handleUpgrade(req, socket, head, websocket => {
401+
this.onWebSocket(req, socket, websocket);
402402
});
403403
});
404404
}
@@ -409,40 +409,40 @@ class Server extends EventEmitter {
409409
* @param {ws.Socket} websocket
410410
* @api private
411411
*/
412-
onWebSocket(req, socket) {
413-
socket.on("error", onUpgradeError);
412+
onWebSocket(req, socket, websocket) {
413+
websocket.on("error", onUpgradeError);
414414

415415
if (
416416
transports[req._query.transport] !== undefined &&
417417
!transports[req._query.transport].prototype.handlesUpgrades
418418
) {
419419
debug("transport doesnt handle upgraded requests");
420-
socket.close();
420+
websocket.close();
421421
return;
422422
}
423423

424424
// get client id
425425
const id = req._query.sid;
426426

427427
// keep a reference to the ws.Socket
428-
req.websocket = socket;
428+
req.websocket = websocket;
429429

430430
if (id) {
431431
const client = this.clients[id];
432432
if (!client) {
433433
debug("upgrade attempt for closed client");
434-
socket.close();
434+
websocket.close();
435435
} else if (client.upgrading) {
436436
debug("transport has already been trying to upgrade");
437-
socket.close();
437+
websocket.close();
438438
} else if (client.upgraded) {
439439
debug("transport had already been upgraded");
440-
socket.close();
440+
websocket.close();
441441
} else {
442442
debug("upgrading existing transport");
443443

444444
// transport error handling takes over
445-
socket.removeListener("error", onUpgradeError);
445+
websocket.removeListener("error", onUpgradeError);
446446

447447
const transport = new transports[req._query.transport](req);
448448
if (req._query && req._query.b64) {
@@ -455,14 +455,16 @@ class Server extends EventEmitter {
455455
}
456456
} else {
457457
// transport error handling takes over
458-
socket.removeListener("error", onUpgradeError);
458+
websocket.removeListener("error", onUpgradeError);
459459

460-
this.handshake(req._query.transport, req);
460+
const closeConnection = (errorCode, errorContext) =>
461+
abortUpgrade(socket, errorCode, errorContext);
462+
this.handshake(req._query.transport, req, closeConnection);
461463
}
462464

463465
function onUpgradeError() {
464466
debug("websocket error before upgrade");
465-
// socket.close() not needed
467+
// websocket.close() not needed
466468
}
467469
}
468470

@@ -548,17 +550,16 @@ Server.errorMessages = {
548550
};
549551

550552
/**
551-
* Sends an Engine.IO Error Message
553+
* Close the HTTP long-polling request
552554
*
553-
* @param req - the request object
554555
* @param res - the response object
555556
* @param errorCode - the error code
556557
* @param errorContext - additional error context
557558
*
558559
* @api private
559560
*/
560561

561-
function sendErrorMessage(req, res, errorCode, errorContext) {
562+
function abortRequest(res, errorCode, errorContext) {
562563
const statusCode = errorCode === Server.errors.FORBIDDEN ? 403 : 400;
563564
const message =
564565
errorContext && errorContext.message
@@ -575,7 +576,7 @@ function sendErrorMessage(req, res, errorCode, errorContext) {
575576
}
576577

577578
/**
578-
* Closes the connection
579+
* Close the WebSocket connection
579580
*
580581
* @param {net.Socket} socket
581582
* @param {string} errorCode - the error code
@@ -584,7 +585,7 @@ function sendErrorMessage(req, res, errorCode, errorContext) {
584585
* @api private
585586
*/
586587

587-
function abortConnection(socket, errorCode, errorContext) {
588+
function abortUpgrade(socket, errorCode, errorContext = {}) {
588589
socket.on("error", () => {
589590
debug("ignoring error from closed connection");
590591
});

test/server.js

+28
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,34 @@ describe("server", () => {
374374
});
375375
});
376376

377+
it("should disallow connection that are rejected by `generateId` (websocket only)", function(done) {
378+
if (process.env.EIO_WS_ENGINE === "eiows") {
379+
this.skip();
380+
}
381+
const partialDone = createPartialDone(done, 2);
382+
383+
engine = listen({ allowUpgrades: false }, port => {
384+
engine.generateId = () => {
385+
return Promise.reject(new Error("nope"));
386+
};
387+
388+
engine.on("connection_error", err => {
389+
expect(err.req).to.be.an(http.IncomingMessage);
390+
expect(err.code).to.be(3);
391+
expect(err.message).to.be("Bad request");
392+
expect(err.context.name).to.be("ID_GENERATION_ERROR");
393+
partialDone();
394+
});
395+
396+
const socket = new eioc.Socket("ws://localhost:%d".s(port), {
397+
transports: ["websocket"]
398+
});
399+
socket.on("error", () => {
400+
partialDone();
401+
});
402+
});
403+
});
404+
377405
it("should exchange handshake data", done => {
378406
listen({ allowUpgrades: false }, port => {
379407
const socket = new eioc.Socket("ws://localhost:%d".s(port));

0 commit comments

Comments
 (0)