mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(browser): reject invalid UTF-8 in HTTP CDP discovery (#4524)
This commit is contained in:
1 parent
d47efe1a54
commit
095a51cf38
2 files changed
+102
-4
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { request, type ClientRequest, type IncomingMessage } from "node:http";
|
||||
import { isUtf8 } from "node:buffer";
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -515,9 +516,17 @@ export const readCdpJson = async (
|
||||
});
|
||||
received.on("end", () => {
|
||||
if (settled) return;
|
||||
const parsed = safeParseJson(
|
||||
Buffer.concat(chunks, bytes).toString("utf8"),
|
||||
);
|
||||
const body = Buffer.concat(chunks, bytes);
|
||||
if (!isUtf8(body)) {
|
||||
finish(
|
||||
new BrowserObservationError(
|
||||
operation,
|
||||
"invalid_endpoint_response",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const parsed = safeParseJson(body.toString("utf8"));
|
||||
if (!parsed.ok) {
|
||||
finish(
|
||||
new BrowserObservationError(
|
||||
|
||||
@@ -3,7 +3,10 @@ import type { RequestListener, Server } from "node:http";
|
||||
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { readCdpJson } from "../../../src/browser/CdpEndpoint.js";
|
||||
import {
|
||||
discoverCdpEndpoint,
|
||||
readCdpJson,
|
||||
} from "../../../src/browser/CdpEndpoint.js";
|
||||
|
||||
const servers: Server[] = [];
|
||||
|
||||
@@ -21,6 +24,92 @@ afterEach(async () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe("CDP discovery response UTF-8", () => {
|
||||
it.each(["title", "url"] as const)(
|
||||
"rejects malformed UTF-8 in a discovered target %s and permits the next discovery",
|
||||
async (field) => {
|
||||
const target = {
|
||||
id: "selected",
|
||||
type: "page",
|
||||
title: "Selected target",
|
||||
url: "https://example.test/",
|
||||
[field]:
|
||||
field === "url" ? "https://example.test/target-slot" : "target-slot",
|
||||
};
|
||||
const valid = Buffer.from(JSON.stringify([target]));
|
||||
const malformed = Buffer.from(valid);
|
||||
malformed[malformed.indexOf("target-slot")] =
|
||||
field === "title" ? 0xff : 0xc3;
|
||||
let sendMalformed = true;
|
||||
let responseClosed: (() => void) | undefined;
|
||||
const closed = new Promise<void>((resolve) => {
|
||||
responseClosed = resolve;
|
||||
});
|
||||
const server = await startServer((request, response) => {
|
||||
if (request.url === "/json/version") {
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
Browser: "Fixture Chromium",
|
||||
"Protocol-Version": "1.3",
|
||||
"User-Agent": "Fixture",
|
||||
"V8-Version": "1",
|
||||
"WebKit-Version": "1",
|
||||
webSocketDebuggerUrl: `ws://${request.headers.host}/devtools/browser/fixture`,
|
||||
}),
|
||||
);
|
||||
} else if (request.url === "/json/list") {
|
||||
if (sendMalformed) response.once("close", () => responseClosed?.());
|
||||
response.end(sendMalformed ? malformed : valid);
|
||||
} else {
|
||||
response.writeHead(404).end();
|
||||
}
|
||||
});
|
||||
|
||||
await expect(
|
||||
discoverCdpEndpoint(server.url.href, "list_browser_targets"),
|
||||
).rejects.toMatchObject({
|
||||
_tag: "BrowserObservationError",
|
||||
operation: "list_browser_targets",
|
||||
reason: "invalid_endpoint_response",
|
||||
});
|
||||
await closed;
|
||||
sendMalformed = false;
|
||||
await expect(
|
||||
discoverCdpEndpoint(server.url.href, "list_browser_targets"),
|
||||
).resolves.toMatchObject({ targets: [target] });
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves valid replacement characters and multibyte text across HTTP chunks", async () => {
|
||||
const value = {
|
||||
title: "Literal \uFFFD and 🛰️",
|
||||
url: "https://example.test/空",
|
||||
};
|
||||
const body = Buffer.from(JSON.stringify(value));
|
||||
const split = body.indexOf(Buffer.from("🛰")) + 1;
|
||||
const server = await startServer((_request, response) => {
|
||||
response.write(body.subarray(0, split));
|
||||
setImmediate(() => response.end(body.subarray(split)));
|
||||
});
|
||||
|
||||
await expect(
|
||||
readCdpJson(server.url, "list_browser_targets"),
|
||||
).resolves.toEqual(value);
|
||||
});
|
||||
|
||||
it("keeps rejecting a leading UTF-8 BOM instead of normalizing it away", async () => {
|
||||
const server = await startServer((_request, response) => {
|
||||
response.end(
|
||||
Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), Buffer.from("{}")]),
|
||||
);
|
||||
});
|
||||
|
||||
await expect(
|
||||
readCdpJson(server.url, "list_browser_targets"),
|
||||
).rejects.toMatchObject({ reason: "invalid_endpoint_response" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("CDP discovery response bounds", () => {
|
||||
it("accepts a complete JSON response exactly at the byte limit", async () => {
|
||||
const body = JSON.stringify("x".repeat(14));
|
||||
|
||||
Reference in new issue
Block a user