Compare commits

...

4 Commits

Author SHA1 Message Date
Damien Arrachequesne
439a8f669c chore(release): engine.io@6.6.7
Diff: https://github.com/socketio/socket.io/compare/engine.io@6.6.6...engine.io@6.6.7
2026-04-27 11:20:14 +02:00
Damien Arrachequesne
fc11285e14 fix(eio): close HTTP requests with invalid content type
Before this commit, after the initial handshake, an HTTP request with a "application/octet-stream"
content type would trigger a "invalid content" error at the Engine.IO level, but would not be
properly closed, which could possibly lead to resource exhaustion.

This behavior was introduced in [1] (engine.io@4.1.0, January 2021).

[1]: 663d326d18
2026-04-27 11:08:50 +02:00
Damien Arrachequesne
b059af6b12 refactor(eio): use plain IncomingMessage in the public API
The EngineRequest type was introduced in [1] (engine.io@6.6.0).

[1]: c310b7b6b6

Related: https://github.com/socketio/socket.io/issues/5468
2026-04-17 09:55:51 +02:00
Abhijeet Abhi
4e85378a46 docs: fix various typos in test and documentation (#5481) 2026-04-17 07:48:08 +02:00
9 changed files with 114 additions and 33 deletions

View File

@@ -296,7 +296,7 @@ A payload is a series of encoded packets tied together. The payload encoding for
<length1>:<packet1>[<length2>:<packet2>[...]]
```
* length: length of the packet in __characters__
* packet: actual packets as descriped above
* packet: actual packets as described above
When XHR2 is not supported, the same encoding principle is used also when
binary data is sent, but it is sent as base64 encoded strings. For the purposes of decoding, an identifier `b` is

View File

@@ -48,6 +48,23 @@
| [3.4.2](#342-2020-06-04) | June 2020 | `"` |
| [3.4.1](#341-2020-04-17) | April 2020 | `^7.1.2` |
## [6.6.7](https://github.com/socketio/socket.io/compare/engine.io@6.6.6...engine.io@6.6.7) (2026-04-27)
### Bug Fixes
* close HTTP requests with invalid content type ([fc11285](https://github.com/socketio/socket.io/commit/fc11285e14964c2132d122164bf130c355f60671))
* handle invalid packets when upgrading to WebTransport ([1fa1f46](https://github.com/socketio/socket.io/commit/1fa1f46cd420ac5b57bb4c04c959b58f3c79158c))
* prevent WebTransport connections when a middleware is registered ([d1f5aa9](https://github.com/socketio/socket.io/commit/d1f5aa93722a7f1ed729b96f771daf92a3dfdaf7))
### Dependencies
- [`ws@~8.18.3`](https://github.com/websockets/ws/releases/tag/8.18.3) (no change)
## [6.6.6](https://github.com/socketio/socket.io/compare/engine.io@6.6.5...engine.io@6.6.6) (2026-03-10)

View File

@@ -764,18 +764,20 @@ export class Server extends BaseServer {
/**
* Handles an Engine.IO HTTP request.
*
* @param {EngineRequest} req
* @param {IncomingMessage} req
* @param {ServerResponse} res
*/
public handleRequest(req: EngineRequest, res: ServerResponse) {
public handleRequest(req: IncomingMessage, res: ServerResponse) {
debug('handling "%s" http request "%s"', req.method, req.url);
this.prepare(req);
req.res = res;
const engineRequest = req as EngineRequest;
this.prepare(engineRequest);
engineRequest.res = res;
const callback: ErrorCallback = (errorCode, errorContext) => {
if (errorCode !== undefined) {
this.emit("connection_error", {
req,
req: engineRequest,
code: errorCode,
message: Server.errorMessages[errorCode],
context: errorContext,
@@ -784,25 +786,27 @@ export class Server extends BaseServer {
return;
}
if (req._query.sid) {
if (engineRequest._query.sid) {
debug("setting new request for existing client");
this.clients[req._query.sid].transport.onRequest(req);
this.clients[engineRequest._query.sid].transport.onRequest(
engineRequest,
);
} else {
const closeConnection = (errorCode, errorContext) =>
abortRequest(res, errorCode, errorContext);
this.handshake(
req._query.transport as TransportName,
req,
engineRequest._query.transport as TransportName,
engineRequest,
closeConnection,
);
}
};
this._applyMiddlewares(req, res, (err) => {
this._applyMiddlewares(engineRequest, res, (err) => {
if (err) {
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
} else {
this.verify(req, false, callback);
this.verify(engineRequest, false, callback);
}
});
}
@@ -811,17 +815,19 @@ export class Server extends BaseServer {
* Handles an Engine.IO HTTP Upgrade.
*/
public handleUpgrade(
req: EngineRequest,
req: IncomingMessage,
socket: Duplex,
upgradeHead: Buffer,
) {
this.prepare(req);
const engineRequest = req as EngineRequest;
const res = new WebSocketResponse(req, socket);
this.prepare(engineRequest);
const res = new WebSocketResponse(engineRequest, socket);
const callback: ErrorCallback = (errorCode, errorContext) => {
if (errorCode !== undefined) {
this.emit("connection_error", {
req,
req: engineRequest,
code: errorCode,
message: Server.errorMessages[errorCode],
context: errorContext,
@@ -838,18 +844,22 @@ export class Server extends BaseServer {
res.writeHead();
// delegate to ws
this.ws.handleUpgrade(req, socket, head, (websocket) => {
this.onWebSocket(req, socket, websocket);
this.ws.handleUpgrade(engineRequest, socket, head, (websocket) => {
this.onWebSocket(engineRequest, socket, websocket);
});
};
this._applyMiddlewares(req, res as unknown as ServerResponse, (err) => {
if (err) {
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
} else {
this.verify(req, true, callback);
}
});
this._applyMiddlewares(
engineRequest,
res as unknown as ServerResponse,
(err) => {
if (err) {
callback(Server.errors.BAD_REQUEST, { name: "MIDDLEWARE_FAILURE" });
} else {
this.verify(engineRequest, true, callback);
}
},
);
}
/**
@@ -947,7 +957,7 @@ export class Server extends BaseServer {
server.on("request", (req, res) => {
if (check(req)) {
debug('intercepting request for path "%s"', path);
this.handleRequest(req as EngineRequest, res);
this.handleRequest(req, res);
} else {
let i = 0;
const l = listeners.length;
@@ -960,7 +970,7 @@ export class Server extends BaseServer {
if (~this.opts.transports.indexOf("websocket")) {
server.on("upgrade", (req, socket, head) => {
if (check(req)) {
this.handleUpgrade(req as EngineRequest, socket, head);
this.handleUpgrade(req, socket, head);
} else if (false !== options.destroyUpgrade) {
// default node behavior is to disconnect when no handlers
// but by adding a handler, we prevent that

View File

@@ -136,7 +136,8 @@ export class Polling extends Transport {
const isBinary = "application/octet-stream" === req.headers["content-type"];
if (isBinary && this.protocol === 4) {
return this.onError("invalid content");
this.onError("invalid content");
return res.writeStatus("400 Bad Request").end();
}
this.dataReq = req;

View File

@@ -122,7 +122,8 @@ export class Polling extends Transport {
const isBinary = "application/octet-stream" === req.headers["content-type"];
if (isBinary && this.protocol === 4) {
return this.onError("invalid content");
this.onError("invalid content");
return res.writeHead(400).end();
}
this.dataReq = req;

View File

@@ -1,6 +1,6 @@
{
"name": "engine.io",
"version": "6.6.6",
"version": "6.6.7",
"description": "The realtime engine behind Socket.IO. Provides the foundation of a bidirectional connection between client and server",
"type": "commonjs",
"main": "./build/engine.io.js",

View File

@@ -60,6 +60,32 @@ exports.listen = (opts, fn) => {
return e;
};
exports.listenAsync = function listenAsync(opts = {}) {
return new Promise((resolve) => {
const engine = exports.listen(opts, (port) => {
resolve({
port,
close: () => {
engine.close();
if (engine.httpServer) {
engine.httpServer.close();
}
},
});
});
});
};
exports.runHandshake = async function runHandshake(port) {
const res = await fetch(
`http://localhost:${port}/engine.io/?EIO=4&transport=polling`,
);
const data = await res.text();
return {
sid: JSON.parse(data.substring(1)).sid,
};
};
exports.ClientSocket = Socket;
exports.createPartialDone = (done, count) => {

View File

@@ -7,7 +7,13 @@ const path = require("path");
const exec = require("child_process").exec;
const zlib = require("zlib");
const { Server, Socket, attach } = require("..");
const { ClientSocket, listen, createPartialDone } = require("./common");
const {
ClientSocket,
listen,
listenAsync,
runHandshake,
createPartialDone,
} = require("./common");
const expect = require("expect.js");
const request = require("superagent");
const cookieMod = require("cookie");
@@ -581,7 +587,7 @@ describe("server", () => {
});
});
it("should not suggest upgrades when none are availble", (done) => {
it("should not suggest upgrades when none are available", (done) => {
listen({ transports: ["polling"] }, (port) => {
const socket = new ClientSocket(`ws://localhost:${port}`, {});
socket.on("handshake", (obj) => {
@@ -1458,6 +1464,26 @@ describe("server", () => {
},
);
it("should abort the polling data request if the content type is invalid", async () => {
const { port, close } = await listenAsync();
const { sid } = await runHandshake(port);
const res = await fetch(
`http://localhost:${port}/engine.io/?EIO=4&transport=polling&sid=${sid}`,
{
method: "POST",
headers: {
"content-type": "application/octet-stream",
},
body: Buffer.of(1, 2, 3),
},
);
expect(res.status).to.eql(400);
close();
});
// tests https://github.com/LearnBoost/engine.io-client/issues/207
// websocket test, transport error
it("should trigger transport close before open for ws", (done) => {

View File

@@ -444,7 +444,7 @@ describe("server", () => {
nio.emit<"noArgs">,
);
expectType<ToEmit<ServerToClientEventsNoAck, "helloFromServer">>(
// These errors will dissapear once the TS version is updated from 4.7.4
// These errors will disappear once the TS version is updated from 4.7.4
// the TSD instance is using a newer version of TS than the workspace version
// to enable the ability to compare against `any`
sio.emit<"helloFromServer">,