From 52f44b368d242272ddb93b8e797a345e94531972 Mon Sep 17 00:00:00 2001 From: Bxio Date: Sat, 10 Oct 2026 21:06:18 +0100 Subject: [PATCH] Send WebQuery action parameters as JSON instead of form data --- studio.mjs | 8 ++++++-- transport.test.mjs | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/studio.mjs b/studio.mjs index 80464c6..985a2b6 100644 --- a/studio.mjs +++ b/studio.mjs @@ -994,6 +994,7 @@ export function query(connection, command, params = {}, { signal } = {}) { command, ); if (readOnly) address.search = form.toString(); + const payload = readOnly ? null : Buffer.from(JSON.stringify(params), "utf8"); return new Promise((resolve, reject) => { const req = (address.protocol === "https:" ? https : http).request( address, @@ -1003,7 +1004,10 @@ export function query(connection, command, params = {}, { signal } = {}) { headers: { "x-api-key": connection.apiKey, ...(!readOnly - ? { "Content-Type": "application/x-www-form-urlencoded" } + ? { + "Content-Type": "application/json", + "Content-Length": payload.length, + } : {}), }, timeout: 10000, @@ -1058,7 +1062,7 @@ export function query(connection, command, params = {}, { signal } = {}) { ), ), ); - if (!readOnly) req.write(form.toString()); + if (payload) req.write(payload); req.end(); }); } diff --git a/transport.test.mjs b/transport.test.mjs index 5fd2e96..ec44335 100644 --- a/transport.test.mjs +++ b/transport.test.mjs @@ -4,7 +4,7 @@ import http from "node:http"; import { query } from "./studio.mjs"; import { httpRequest } from "./flow-steps.mjs"; -test("WebQuery uses authenticated GET and form POST, handles fragmented UTF-8 and server errors", async () => { +test("WebQuery uses authenticated GET and JSON POST, handles fragmented UTF-8 and server errors", async () => { const requests = []; const server = http.createServer(async (req, res) => { const chunks = []; @@ -13,10 +13,29 @@ test("WebQuery uses authenticated GET and form POST, handles fragmented UTF-8 an method: req.method, path: req.url, key: req.headers["x-api-key"], + contentType: req.headers["content-type"], + contentLength: req.headers["content-length"], + transferEncoding: req.headers["transfer-encoding"], body: Buffer.concat(chunks).toString(), }); res.setHeader("Content-Type", "application/json"); if (req.url.includes("channeledit")) { + let params; + try { + params = JSON.parse(Buffer.concat(chunks).toString()); + } catch {} + if ( + req.headers["content-type"] !== "application/json" || + !params?.cid || + typeof params.channel_description !== "string" + ) { + res.end( + JSON.stringify({ + status: { code: 1538, message: "invalid parameter" }, + }), + ); + return; + } res.end(JSON.stringify({ status: { code: 0 }, body: [] })); return; } @@ -48,7 +67,8 @@ test("WebQuery uses authenticated GET and form POST, handles fragmented UTF-8 an try { const result = await query(connection, "serverinfo"); assert.equal(result[0].virtualserver_name, "Olá | Portugal"); - const text = "Olá | linha\nmais = texto"; + const text = + "[b]Informações do servidor[/b]\nOlá | linha\nmais = texto & + / \\ 🎵"; await query(connection, "channeledit", { cid: 21, channel_description: text, @@ -56,10 +76,17 @@ test("WebQuery uses authenticated GET and form POST, handles fragmented UTF-8 an assert.equal(requests[0].method, "GET"); assert.equal(requests[0].key, "fixture-key"); assert.equal(requests[1].method, "POST"); + assert.equal(requests[1].key, "fixture-key"); + assert.equal(requests[1].contentType, "application/json"); assert.equal( - new URLSearchParams(requests[1].body).get("channel_description"), - text, + Number(requests[1].contentLength), + Buffer.byteLength(requests[1].body), ); + assert.equal(requests[1].transferEncoding, undefined); + assert.deepEqual(JSON.parse(requests[1].body), { + cid: 21, + channel_description: text, + }); await assert.rejects( () => query(connection, "clientkick", { clid: 1, reasonid: 5 }), /2568.*permissions/,