base1: Break input into small blocks

The WebSocket protocol only allows messages up to 128K. Therefore
data larger than that size, such as file write data, or input into
processes' stdin need to be broken into smaller chunks.

Lets not rely on the caller of various cockpit.xxxx() functions
to do that. We know the places where it's necessary.

Issue #12338
Closes #13149
This commit is contained in:
Stef Walter 2019-11-18 11:55:12 +01:00 committed by Martin Pitt
parent 3c8d134f89
commit d1ea17988c
2 changed files with 110 additions and 29 deletions

View File

@ -95,6 +95,32 @@ function invoke_functions(functions, self, args) {
}
}
function iterate_data(data, callback, batch) {
var binary = false;
var i, n;
var len = 0;
if (!batch)
batch = 64 * 1024;
if (data) {
if (data.byteLength) {
len = data.byteLength;
binary = true;
} else if (data.length) {
len = data.length;
}
}
for (i = 0; i < len; i += batch) {
n = Math.min(len - i, batch);
if (binary)
callback(new window.Uint8Array(data.buffer, i, n));
else
callback(data.substr(i, n));
}
}
/* -------------------------------------------------------------------------
* Channels
*
@ -2818,7 +2844,9 @@ function factory() {
ret.input = function(message, stream) {
if (message !== null && message !== undefined) {
spawn_debug("process input:", message);
channel.send(message);
iterate_data(message, function(data) {
channel.send(data);
});
}
if (!stream)
channel.control({ command: "done" });
@ -3740,26 +3768,9 @@ function factory() {
}
});
var len = 0;
var binary = false;
if (file_content) {
if (file_content.byteLength) {
len = file_content.byteLength;
binary = true;
} else if (file_content.length) {
len = file_content.length;
}
}
var i, n;
var batch = 16 * 1024;
for (i = 0; i < len; i += batch) {
n = Math.min(len - i, batch);
if (binary)
replace_channel.send(new window.Uint8Array(file_content.buffer, i, n));
else
replace_channel.send(file_content.substr(i, n));
}
iterate_data(file_content, function(data) {
replace_channel.send(data);
});
replace_channel.control({ command: "done" });
return dfd.promise;
@ -4150,7 +4161,9 @@ function factory() {
if (input !== undefined) {
if (input !== "") {
http_debug("http input:", input);
channel.send(input);
iterate_data(input, function(data) {
channel.send(data);
});
}
http_debug("http done");
channel.control({ command: "done" });
@ -4227,7 +4240,9 @@ function factory() {
ret.input = function(message, stream) {
if (message !== null && message !== undefined) {
http_debug("http input:", message);
channel.send(message);
iterate_data(message, function(data) {
channel.send(data);
});
}
if (!stream) {
http_debug("http done");

View File

@ -27,12 +27,8 @@ function MockPeer() {
/* nada */
};
/* get event: triggered when we receive a get request */
this.onget = function(event, channel, request) {
if (event.isDefaultPrevented())
return false;
if (request.path == "/")
this.reply(channel, request, { key: "value" });
this.oncontrol = function(event, channel, options) {
/* nada */
};
/* send a message from peer back to channel */
@ -81,6 +77,13 @@ function MockPeer() {
window.setTimeout(function() { $(peer).trigger("recv", [channel, payload]) }, 5);
};
this.control = function(options) {
console.assert(typeof command === 'string');
console.assert(options !== null && typeof options === 'object');
console.assert(arguments.length == 1);
window.setTimeout(function() { $(peer).trigger("control", [channel, options]) }, 5);
};
this.close = function(options) {
console.assert(arguments.length <= 1);
this.valid = false;
@ -157,6 +160,69 @@ QUnit.test("simple request", function (assert) {
});
});
QUnit.test("input large", function (assert) {
const done = assert.async();
assert.expect(25);
var str = new Array(128 * 1024).join('abcdef12345');
var output = "";
var count = 0;
var peer = new MockPeer();
$(peer).on("recv", function(event, channel, payload) {
assert.ok(typeof (payload) == "string", "got payload");
output += payload;
count += 1;
});
$(peer).on("control", function(event, channel, options) {
if (options.command == "done")
this.close(channel);
});
cockpit.spawn(["/path/to/command"])
.input(str)
.always(function() {
assert.equal(this.state(), "resolved", "didn't fail");
assert.equal(str, output, "right output");
assert.ok(count > 1, "broken into multiple blocks");
done();
});
});
QUnit.test("binary large", function (assert) {
const done = assert.async();
assert.expect(10);
var data = new Uint8Array(249 * 1023);
var i;
var len = data.byteLength;
for (i = 0; i < len; i++)
data[i] = i % 233;
var count = 0;
var peer = new MockPeer();
$(peer).on("recv", function(event, channel, payload) {
console.log(typeof (payload), payload.constructor);
assert.equal(typeof (payload), "object", "got payload");
assert.equal(payload.constructor, Uint8Array, "right binary array");
count += 1;
});
$(peer).on("control", function(event, channel, options) {
console.log("control", options);
if (options.command == "done")
this.close(channel);
});
cockpit.spawn(["/ptah/to/command"])
.input(data)
.always(function() {
assert.equal(this.state(), "resolved", "didn't fail");
assert.ok(count > 1, "broken into multiple blocks");
done();
});
});
QUnit.test("string command", function (assert) {
const done = assert.async();
assert.expect(2);