Skip to content

Commit dfb5ab3

Browse files
fix(sio): prevent uWebSockets.js from serving missing client files
Use an explicit allowlist of shipped Socket.IO client bundles when serving static files with the uWebSockets.js-based engine, so requests for unsupported bundle names are ignored instead of attempting to serve missing files.
1 parent dcbd961 commit dfb5ab3

2 files changed

Lines changed: 145 additions & 23 deletions

File tree

packages/socket.io/lib/index.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ const debug = debugModule("socket.io:server");
5454
const clientVersion = require("../package.json").version;
5555
const dotMapRegex = /\.map/;
5656

57+
const CLIENT_FILES = new Set([
58+
"socket.io.esm.min.js",
59+
"socket.io.esm.min.js.map",
60+
"socket.io.js",
61+
"socket.io.js.map",
62+
"socket.io.min.js",
63+
"socket.io.min.js.map",
64+
"socket.io.msgpack.min.js",
65+
"socket.io.msgpack.min.js.map",
66+
]);
67+
5768
type ParentNspNameMatchFn = (
5869
name: string,
5970
auth: { [key: string]: any },
@@ -538,17 +549,13 @@ export class Server<
538549
if (this._serveClient) {
539550
// attach static file serving
540551
app.get(`${this._path}/*`, (res, req) => {
541-
if (!this.clientPathRegex.test(req.getUrl())) {
552+
const filename = this.extractFilename(req.getUrl());
553+
if (!CLIENT_FILES.has(filename)) {
542554
req.setYield(true);
543555
return;
544556
}
545557

546-
const filename = req
547-
.getUrl()
548-
.replace(this._path, "")
549-
.replace(/\?.*$/, "")
550-
.replace(/^\//, "");
551-
const isMap = dotMapRegex.test(filename);
558+
const isMap = filename.endsWith(".map");
552559
const type = isMap ? "map" : "source";
553560

554561
// Per the standard, ETags must be quoted:
@@ -583,6 +590,10 @@ export class Server<
583590
patchAdapter(app);
584591
}
585592

593+
private extractFilename(url: string) {
594+
return url.substring(this._path.length + 1).split("?")[0];
595+
}
596+
586597
/**
587598
* Initialize engine
588599
*

packages/socket.io/test/uws.ts

Lines changed: 127 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
} from "uWebSockets.js";
66
import { Server } from "..";
77
import { io as ioc, Socket as ClientSocket } from "socket.io-client";
8-
import request from "supertest";
98
import expect from "expect.js";
109
import { assert } from "./support/util";
1110

@@ -228,22 +227,134 @@ describe("socket.io with uWebSocket.js-based engine", () => {
228227
io.of("/").sockets.get(client.id)!.disconnect();
229228
});
230229

231-
it("should serve static files", (done) => {
230+
describe("static files", () => {
232231
const clientVersion = require("../package.json").version;
233232

234-
request(`http://localhost:${port}`)
235-
.get("/socket.io/socket.io.js")
236-
.buffer(true)
237-
.end((err, res) => {
238-
if (err) return done(err);
239-
expect(res.headers["content-type"]).to.be(
240-
"application/javascript; charset=utf-8",
241-
);
242-
expect(res.headers.etag).to.be('"' + clientVersion + '"');
243-
expect(res.headers["x-sourcemap"]).to.be(undefined);
244-
expect(res.text).to.match(/engine\.io/);
245-
expect(res.status).to.be(200);
246-
done();
247-
});
233+
it("should serve socket.io.js", async () => {
234+
const res = await fetch(
235+
`http://localhost:${port}/socket.io/socket.io.js`,
236+
);
237+
238+
expect(res.status).to.be(200);
239+
expect(res.headers.get("content-type")).to.be(
240+
"application/javascript; charset=utf-8",
241+
);
242+
expect(res.headers.get("etag")).to.be('"' + clientVersion + '"');
243+
});
244+
245+
it("should serve socket.io.js (with query params)", async () => {
246+
const res = await fetch(
247+
`http://localhost:${port}/socket.io/socket.io.js?foo=bar`,
248+
);
249+
250+
expect(res.status).to.be(200);
251+
expect(res.headers.get("content-type")).to.be(
252+
"application/javascript; charset=utf-8",
253+
);
254+
expect(res.headers.get("etag")).to.be('"' + clientVersion + '"');
255+
});
256+
257+
it("should serve socket.io.js.map", async () => {
258+
const res = await fetch(
259+
`http://localhost:${port}/socket.io/socket.io.js.map`,
260+
);
261+
262+
expect(res.status).to.be(200);
263+
expect(res.headers.get("content-type")).to.be(
264+
"application/json; charset=utf-8",
265+
);
266+
expect(res.headers.get("etag")).to.be('"' + clientVersion + '"');
267+
});
268+
269+
it("should serve socket.io.min.js", async () => {
270+
const res = await fetch(
271+
`http://localhost:${port}/socket.io/socket.io.min.js`,
272+
);
273+
274+
expect(res.status).to.be(200);
275+
expect(res.headers.get("content-type")).to.be(
276+
"application/javascript; charset=utf-8",
277+
);
278+
expect(res.headers.get("etag")).to.be('"' + clientVersion + '"');
279+
});
280+
281+
it("should serve socket.io.min.js.map", async () => {
282+
const res = await fetch(
283+
`http://localhost:${port}/socket.io/socket.io.min.js.map`,
284+
);
285+
286+
expect(res.status).to.be(200);
287+
expect(res.headers.get("content-type")).to.be(
288+
"application/json; charset=utf-8",
289+
);
290+
expect(res.headers.get("etag")).to.be('"' + clientVersion + '"');
291+
});
292+
293+
it("should serve socket.io.msgpack.min.js", async () => {
294+
const res = await fetch(
295+
`http://localhost:${port}/socket.io/socket.io.msgpack.min.js`,
296+
);
297+
298+
expect(res.status).to.be(200);
299+
expect(res.headers.get("content-type")).to.be(
300+
"application/javascript; charset=utf-8",
301+
);
302+
expect(res.headers.get("etag")).to.be('"' + clientVersion + '"');
303+
});
304+
305+
it("should serve socket.io.msgpack.min.js.map", async () => {
306+
const res = await fetch(
307+
`http://localhost:${port}/socket.io/socket.io.msgpack.min.js.map`,
308+
);
309+
310+
expect(res.status).to.be(200);
311+
expect(res.headers.get("content-type")).to.be(
312+
"application/json; charset=utf-8",
313+
);
314+
expect(res.headers.get("etag")).to.be('"' + clientVersion + '"');
315+
});
316+
317+
it("should not serve unknown files from the Socket.IO path", async () => {
318+
const res = await fetch(
319+
`http://localhost:${port}/socket.io/socket.io.esm.js`,
320+
);
321+
322+
expect(res.status).to.be(404);
323+
});
324+
325+
it("should return 404 for mismatching path", async () => {
326+
const res = await fetch(
327+
`http://localhost:${port}/abcdefghij/socket.io.js`,
328+
);
329+
330+
expect(res.status).to.be(404);
331+
});
332+
333+
it("should return 304 when If-None-Match matches ETag", async () => {
334+
const res = await fetch(
335+
`http://localhost:${port}/socket.io/socket.io.js`,
336+
{
337+
headers: {
338+
"If-None-Match": '"' + clientVersion + '"',
339+
},
340+
},
341+
);
342+
343+
expect(res.status).to.be(304);
344+
});
345+
346+
it("should return 200 when If-None-Match does not match ETag", async () => {
347+
const res = await fetch(
348+
`http://localhost:${port}/socket.io/socket.io.js`,
349+
{
350+
headers: {
351+
"If-None-Match": '"wrong-version"',
352+
},
353+
},
354+
);
355+
356+
expect(res.status).to.be(200);
357+
expect(res.headers.get("etag")).to.be('"' + clientVersion + '"');
358+
});
248359
});
249360
});

0 commit comments

Comments
 (0)