mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-06 09:15:57 +02:00
Only base64 encode payload when needed
This commit is contained in:
@@ -13,17 +13,14 @@ There is one central escape code used for this protocol, which is of the form::
|
||||
|
||||
Here, ``OSC`` is the bytes ``ESC ] (0x1b 0x5b)`` and ST is ``ESC \\ (0x1b 0x5c)``.
|
||||
The ``metadata`` is a colon separated list of ``key=value`` pairs.
|
||||
The final part of the escape code is the :rfc:`base64 <4648>` encoded payload data,
|
||||
whose meaning depends on the metadata.
|
||||
The final part of the escape code is the payload data, whose meaning depends on the metadata.
|
||||
|
||||
The payload must be no more than 4096 bytes encoded bytes. 4096 is the limit to
|
||||
be applied after encoding. When the payload is larger than 4096 base64 encoded
|
||||
The payload must be no more than 4096 bytes. When the payload is larger than 4096
|
||||
bytes, it is chunked up using the ``m`` key. An escape code that has a too long
|
||||
payload is transmitted in chunks. All but the last chunk must have ``m=1`` in
|
||||
their metadata. Each chunk must have a payload of no more than 4096 base64
|
||||
encoded bytes without trailing padding, except the last chunk which may
|
||||
optionally have trailing padding. Only the first chunk is guaranteed to have
|
||||
metadata other than the ``m`` key. Subsequent chunks may optionally omit all
|
||||
their metadata. Each chunk must have a payload of no more than 4096 bytes.
|
||||
Only the first chunk is guaranteed to have metadata other than the ``m`` key.
|
||||
Subsequent chunks may optionally omit all
|
||||
metadata except the ``m`` and ``i`` keys. While a chunked transfer is in
|
||||
progress it is a protocol error to for the sending side to
|
||||
send any protocol related escape codes other than chunked ones.
|
||||
@@ -31,6 +28,11 @@ send any protocol related escape codes other than chunked ones.
|
||||
All integer values used in this escape code must be 32-bit signed or unsigned
|
||||
integers encoded in decimal representation.
|
||||
|
||||
When transferring binary data the payload is :rfc:`base64 <4648>` encoded. The
|
||||
4096 bytes limit applies to *encoded bytes*, that is, it is applied after
|
||||
encoding. base64 padding bytes are optional and may or may not be present at
|
||||
the end of the last chunk.
|
||||
|
||||
Accepting drops
|
||||
-----------------
|
||||
|
||||
@@ -105,9 +107,9 @@ Requesting data is done by sending an escape code of the form::
|
||||
This will request data for the specifid MIME type. The terminal must respond
|
||||
with a series of escape codes of the form::
|
||||
|
||||
OSC _dnd_code ; t=r ; data ST
|
||||
OSC _dnd_code ; t=r ; base64 encoded data ST
|
||||
|
||||
End of data is indicated by an empty payload. If some error occures while
|
||||
End of data is indicated by an empty payload. If some error occurs while
|
||||
getting the data, the terminal must send an escape code of the form::
|
||||
|
||||
OSC _dnd_code ; t=R ; POSIX error name ST
|
||||
|
||||
@@ -342,7 +342,7 @@ def parsers() -> None:
|
||||
}
|
||||
text = generate(
|
||||
'parse_dnd_code', 'screen_handle_dnd_command', 'dnd_command', keymap, 'DnDCommand',
|
||||
payload_is_base64=True, start_parsing_at=0, field_sep=':')
|
||||
payload_is_base64=False, start_parsing_at=0, field_sep=':')
|
||||
write_header(text, 'kitty/parse-dnd-command.h')
|
||||
|
||||
|
||||
|
||||
40
kitty/dnd.c
40
kitty/dnd.c
@@ -99,7 +99,7 @@ string_arrays_cmp(const char **a, size_t an, const char **b, size_t bn) {
|
||||
}
|
||||
|
||||
static size_t
|
||||
send_payload_to_child(id_type id, const char *header, size_t header_sz, const char *data, const size_t data_sz) {
|
||||
send_payload_to_child(id_type id, const char *header, size_t header_sz, const char *data, const size_t data_sz, bool as_base64) {
|
||||
size_t offset = 0;
|
||||
char buf[4096 + 1024];
|
||||
memcpy(buf, header, header_sz);
|
||||
@@ -111,14 +111,21 @@ send_payload_to_child(id_type id, const char *header, size_t header_sz, const ch
|
||||
if (too_much_data) return 0;
|
||||
return 1;
|
||||
}
|
||||
const size_t limit = as_base64 ? 3072 : 4096;
|
||||
while (offset < data_sz) {
|
||||
size_t chunk = data_sz - offset;
|
||||
if (chunk > limit) chunk = limit;
|
||||
size_t p = header_sz;
|
||||
buf[p++] = offset + 3072 >= data_sz ? '0' : '1';
|
||||
buf[p++] = ';';
|
||||
size_t b64_len = sizeof(buf) - p;
|
||||
base64_encode8((const uint8_t*)data + offset, chunk, (uint8_t*)buf + p, &b64_len, false);
|
||||
p += b64_len;
|
||||
const bool is_last = offset + chunk >= data_sz;
|
||||
buf[p++] = is_last ? '0' : '1'; buf[p++] = ';';
|
||||
if (as_base64) {
|
||||
size_t b64_len = sizeof(buf) - p;
|
||||
base64_encode8((const uint8_t*)data + offset, chunk, (uint8_t*)buf + p, &b64_len, false);
|
||||
p += b64_len;
|
||||
} else {
|
||||
memcpy(buf + p, data + offset, chunk);
|
||||
p += chunk;
|
||||
}
|
||||
buf[p++] = 0x1b; buf[p++] = '\\';
|
||||
bool found, too_much_data;
|
||||
schedule_write_to_child_if_possible(id, buf, p, &found, &too_much_data);
|
||||
@@ -133,7 +140,7 @@ static bool
|
||||
flush_pending(id_type id, PendingData *pending) {
|
||||
while (pending->count) {
|
||||
PendingEntry *e = pending->items;
|
||||
size_t written = send_payload_to_child(id, e->buf, e->header_sz, e->buf + e->header_sz, e->data_sz);
|
||||
size_t written = send_payload_to_child(id, e->buf, e->header_sz, e->buf + e->header_sz, e->data_sz, e->as_base64);
|
||||
if (written < e->data_sz) {
|
||||
if (written) {
|
||||
e->data_sz -= written;
|
||||
@@ -162,16 +169,16 @@ flush_pending_payloads(id_type timer_id UNUSED, void *x) {
|
||||
}
|
||||
|
||||
static void
|
||||
queue_payload_to_child(id_type id, PendingData *pending, const char *header, size_t header_sz, const char *data, size_t data_sz) {
|
||||
queue_payload_to_child(id_type id, PendingData *pending, const char *header, size_t header_sz, const char *data, size_t data_sz, bool as_base64) {
|
||||
size_t offset = 0;
|
||||
if (flush_pending(id, pending)) offset = send_payload_to_child(id, header, header_sz, data, data_sz);
|
||||
if (flush_pending(id, pending)) offset = send_payload_to_child(id, header, header_sz, data, data_sz, as_base64);
|
||||
if (offset < data_sz || (!offset && !data_sz)) {
|
||||
ensure_space_for(pending, items, PendingEntry, pending->count + 1, capacity, 32, true);
|
||||
char *buf = malloc(header_sz + data_sz - offset);
|
||||
if (!buf) fatal("Out of memory");
|
||||
memcpy(buf, header, header_sz); memcpy(buf + header_sz, data, data_sz - offset);
|
||||
PendingEntry *e = &pending->items[pending->count++];
|
||||
e->buf = buf; e->header_sz = header_sz; e->data_sz = data_sz - offset;
|
||||
e->buf = buf; e->header_sz = header_sz; e->data_sz = data_sz - offset; e->as_base64 = as_base64;
|
||||
}
|
||||
if (pending->count) check_for_pending_writes();
|
||||
}
|
||||
@@ -215,11 +222,11 @@ drop_move_on_child(Window *w, const char** mimes, size_t num_mimes, bool is_drop
|
||||
if (n < 0) break;
|
||||
pos += n;
|
||||
}
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, mbuf, pos);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, mbuf, pos, false);
|
||||
}
|
||||
} else {
|
||||
buf[header_size++] = 0x1b; buf[header_size++] = '\\';
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, NULL, 0);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, NULL, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,12 +304,9 @@ get_errno_name(int err) {
|
||||
static void
|
||||
drop_send_error(Window *w, int error_code) {
|
||||
char buf[128];
|
||||
uint8_t ebuf[32];
|
||||
size_t b64_len = sizeof(ebuf);
|
||||
const char *e = get_errno_name(error_code);
|
||||
base64_encode8((const uint8_t*)e, strlen(e), ebuf, &b64_len, false);
|
||||
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=R", DND_CODE, w->drop.client_id);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, (char*)ebuf, b64_len);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, e, strlen(e), false);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -328,7 +332,7 @@ drop_dispatch_data(Window *w, const char *data, ssize_t sz) {
|
||||
else {
|
||||
char buf[128];
|
||||
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=r", DND_CODE, w->drop.client_id);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, sz ? data : NULL, sz);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, sz ? data : NULL, sz, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +344,6 @@ drop_left_child(Window *w) {
|
||||
if (w->drop.wanted) {
|
||||
char buf[128];
|
||||
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=m:x=-1:y=-1", DND_CODE, w->drop.client_id);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, NULL, 0);
|
||||
queue_payload_to_child(w->id, &w->drop.pending, buf, header_size, NULL, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
||||
const size_t parser_buf_pos) {
|
||||
unsigned int pos = 0;
|
||||
|
||||
size_t payload_start = 0;
|
||||
enum PARSER_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE, PAYLOAD };
|
||||
enum PARSER_STATES state = KEY, value_state = FLAG;
|
||||
DnDCommand g = {0};
|
||||
@@ -180,15 +180,8 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
||||
|
||||
case PAYLOAD: {
|
||||
sz = parser_buf_pos - pos;
|
||||
g.payload_sz = MAX(BUF_EXTRA, sz);
|
||||
if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {
|
||||
g.payload_sz = MAX(BUF_EXTRA, sz);
|
||||
REPORT_ERROR("Failed to parse DnDCommand command payload with error: "
|
||||
" invalid base64 data in chunk of size: %zu with output "
|
||||
"buffer size: %zu",
|
||||
sz, g.payload_sz);
|
||||
return;
|
||||
}
|
||||
payload_start = pos;
|
||||
g.payload_sz = sz;
|
||||
pos = parser_buf_pos;
|
||||
} break;
|
||||
|
||||
@@ -222,7 +215,7 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
||||
"cell_x", (int)g.cell_x, "cell_y", (int)g.cell_y, "pixel_x",
|
||||
(int)g.pixel_x, "pixel_y", (int)g.pixel_y,
|
||||
|
||||
"", (char *)parser_buf, g.payload_sz);
|
||||
"", (char *)parser_buf + payload_start, g.payload_sz);
|
||||
|
||||
screen_handle_dnd_command(self->screen, &g, parser_buf);
|
||||
screen_handle_dnd_command(self->screen, &g, parser_buf + payload_start);
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@ typedef struct WindowBarData {
|
||||
typedef struct PendingEntry {
|
||||
char *buf; size_t header_sz;
|
||||
size_t data_sz;
|
||||
bool as_base64;
|
||||
} PendingEntry;
|
||||
|
||||
typedef struct PendingData {
|
||||
|
||||
Reference in New Issue
Block a user