From 346d0c3d5510d2468e43014ca84df6ed89181e24 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Wed, 5 Jun 2024 08:29:20 +0200 Subject: [PATCH] Use binary http channel for podman socket Cockpit's (default) text channels ignore frames with split UTF-8 multi-byte characters. This causes broken JSON. This is ultimately an issue with cockpit's channel API design, and Cockpit should hide that by buffering and partially decoding text channel frames, see https://github.com/cockpit-project/cockpit/issues/19235 Until that happens, convert the REST API to use a binary channel. Fixes #1733 --- src/rest.js | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/rest.js b/src/rest.js index 30a5dfa7b..730f32c2e 100644 --- a/src/rest.js +++ b/src/rest.js @@ -17,28 +17,39 @@ function manage_error(reject, error, content) { // calls are async, so keep track of a call counter to associate a result with a call let call_id = 0; +const NL = '\n'.charCodeAt(0); // always 10, but avoid magic constant + function connect(address, system) { /* This doesn't create a channel until a request */ - const http = cockpit.http(address, { superuser: system ? "require" : null }); + /* HACK: use binary channel to work around https://github.com/cockpit-project/cockpit/issues/19235 */ + const http = cockpit.http(address, { superuser: system ? "require" : null, binary: true }); const connection = {}; + const decoder = new TextDecoder(); connection.monitor = function(options, callback, system, return_raw) { return new Promise((resolve, reject) => { - let buffer = ""; + let buffer = new Uint8Array(); http.request(options) .stream(data => { if (return_raw) callback(data); else { - buffer += data; - const chunks = buffer.split("\n"); - buffer = chunks.pop(); + buffer = new Uint8Array([...buffer, ...data]); + + // split the buffer into lines on NL (this is safe with UTF-8) + for (;;) { + const idx = buffer.indexOf(NL); + if (idx < 0) + break; - chunks.forEach(chunk => { - debug(system, "monitor", chunk); - callback(JSON.parse(chunk)); - }); + const line = buffer.slice(0, idx); + buffer = buffer.slice(idx + 1); + + const line_str = decoder.decode(line); + debug(system, "monitor", line_str); + callback(JSON.parse(line_str)); + } } }) .catch((error, content) => { @@ -55,12 +66,14 @@ function connect(address, system) { options = options || {}; http.request(options) .then(result => { - debug(system, `call ${id} result:`, JSON.stringify(result)); - resolve(result); + const text = decoder.decode(result); + debug(system, `call ${id} result:`, text); + resolve(text); }) .catch((error, content) => { - debug(system, `call ${id} error:`, JSON.stringify(error), "content", JSON.stringify(content)); - manage_error(reject, error, content); + const text = decoder.decode(content); + debug(system, `call ${id} error:`, JSON.stringify(error), "content", text); + manage_error(reject, error, text); }); }); };