Pass a Buffer to a patched stdio write from an encoded data listener

A byte write to a replaced process.stdout.write or process.stderr.write
was boxed with the 'data' chunk helper, which answers a string while a
listener of a socket or HTTP response with setEncoding("utf8") runs. A
`process.stdout.write(buffer)` from such a listener reached the
replacement as a string. Node passes the Buffer it was given.

The stdio hook now boxes a byte write as a Buffer whatever listener is
running.
This commit is contained in:
Chris Tate
2026-10-29 02:58:50 -05:00
parent 8c79a0f6b8
commit b7bcbc68d8
5 changed files with 52 additions and 1 deletions
@@ -0,0 +1,7 @@
{
"order": [
"<repo>/tests/corpus/stdout-patch-buffer-from-encoded-data/patch.mjs",
"<repo>/tests/corpus/stdout-patch-buffer-from-encoded-data/main.ts"
],
"diags": []
}
+4 -1
View File
@@ -1534,9 +1534,12 @@ static bool scr_stdio_intercept(int fd, const void *data, size_t length, int fla
}
ScrDyn *chunk;
if (flags & SCR_STDIO_HOOK_BYTES) {
/* Always a Buffer: a write from a 'data' listener of a stream with an
* encoding set is still the bytes it was given. */
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)length);
memcpy(b->data, data, length);
chunk = scr_dyn_new_chunk(b);
b->is_buffer = true;
chunk = scr_dyn_new_bytes(b);
scr_bytes_release(b);
} else {
ScrStr *text = scr_str_new(data, length);
@@ -0,0 +1,32 @@
// A Buffer written to a replaced process.stdout.write from a 'data' listener
// of a socket or an HTTP response with setEncoding("utf8") arrives as a
// Buffer; the listener's own chunks are strings. patch.mjs installs the
// replacement and prints what it received at exit.
import * as http from "node:http";
import * as net from "node:net";
import "./patch.mjs";
const tcp = net.createServer((socket) => socket.end("tcp-data"));
tcp.listen(0, "127.0.0.1", () => {
const port = (tcp.address() as net.AddressInfo).port;
const client = net.connect(port, "127.0.0.1");
client.setEncoding("utf8");
client.on("data", (text: string) => {
process.stdout.write(Buffer.from(`socket ${typeof text} ${text}`));
process.stdout.write(`socket text ${text}`);
});
client.on("end", () => {
tcp.close();
const server = http.createServer((req, res) => res.end("http-data"));
server.listen(0, "127.0.0.1", () => {
const httpPort = (server.address() as net.AddressInfo).port;
http.get(`http://127.0.0.1:${httpPort}/`, (res) => {
res.setEncoding("utf8");
res.on("data", (text: string) => {
process.stdout.write(Buffer.from(`response ${typeof text} ${text}`));
});
res.on("end", () => server.close());
});
});
});
});
@@ -0,0 +1,8 @@
// Replaces process.stdout.write with a recorder; an exit handler prints what it saw.
const seen = [];
process.stdout.write = function (chunk) {
const kind = typeof chunk === "string" ? "string" : Buffer.isBuffer(chunk) ? "Buffer" : typeof chunk;
seen.push(`${kind}:${String(chunk)}`);
return true;
};
process.on("exit", () => process.stderr.write(`${seen.join("\n")}\n`));
@@ -0,0 +1 @@
{ "plain": 5044, "san": 13845 }