mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-26 10:12:17 +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)``.
|
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 ``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,
|
The final part of the escape code is the payload data, whose meaning depends on the metadata.
|
||||||
whose meaning depends on the metadata.
|
|
||||||
|
|
||||||
The payload must be no more than 4096 bytes encoded bytes. 4096 is the limit to
|
The payload must be no more than 4096 bytes. When the payload is larger than 4096
|
||||||
be applied after encoding. When the payload is larger than 4096 base64 encoded
|
|
||||||
bytes, it is chunked up using the ``m`` key. An escape code that has a too long
|
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
|
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
|
their metadata. Each chunk must have a payload of no more than 4096 bytes.
|
||||||
encoded bytes without trailing padding, except the last chunk which may
|
Only the first chunk is guaranteed to have metadata other than the ``m`` key.
|
||||||
optionally have trailing padding. Only the first chunk is guaranteed to have
|
Subsequent chunks may optionally omit all
|
||||||
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
|
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
|
progress it is a protocol error to for the sending side to
|
||||||
send any protocol related escape codes other than chunked ones.
|
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
|
All integer values used in this escape code must be 32-bit signed or unsigned
|
||||||
integers encoded in decimal representation.
|
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
|
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
|
This will request data for the specifid MIME type. The terminal must respond
|
||||||
with a series of escape codes of the form::
|
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::
|
getting the data, the terminal must send an escape code of the form::
|
||||||
|
|
||||||
OSC _dnd_code ; t=R ; POSIX error name ST
|
OSC _dnd_code ; t=R ; POSIX error name ST
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ def parsers() -> None:
|
|||||||
}
|
}
|
||||||
text = generate(
|
text = generate(
|
||||||
'parse_dnd_code', 'screen_handle_dnd_command', 'dnd_command', keymap, 'DnDCommand',
|
'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')
|
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
|
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;
|
size_t offset = 0;
|
||||||
char buf[4096 + 1024];
|
char buf[4096 + 1024];
|
||||||
memcpy(buf, header, header_sz);
|
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;
|
if (too_much_data) return 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
const size_t limit = as_base64 ? 3072 : 4096;
|
||||||
while (offset < data_sz) {
|
while (offset < data_sz) {
|
||||||
size_t chunk = data_sz - offset;
|
size_t chunk = data_sz - offset;
|
||||||
|
if (chunk > limit) chunk = limit;
|
||||||
size_t p = header_sz;
|
size_t p = header_sz;
|
||||||
buf[p++] = offset + 3072 >= data_sz ? '0' : '1';
|
const bool is_last = offset + chunk >= data_sz;
|
||||||
buf[p++] = ';';
|
buf[p++] = is_last ? '0' : '1'; buf[p++] = ';';
|
||||||
size_t b64_len = sizeof(buf) - p;
|
if (as_base64) {
|
||||||
base64_encode8((const uint8_t*)data + offset, chunk, (uint8_t*)buf + p, &b64_len, false);
|
size_t b64_len = sizeof(buf) - p;
|
||||||
p += b64_len;
|
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++] = '\\';
|
buf[p++] = 0x1b; buf[p++] = '\\';
|
||||||
bool found, too_much_data;
|
bool found, too_much_data;
|
||||||
schedule_write_to_child_if_possible(id, buf, p, &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) {
|
flush_pending(id_type id, PendingData *pending) {
|
||||||
while (pending->count) {
|
while (pending->count) {
|
||||||
PendingEntry *e = pending->items;
|
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) {
|
||||||
if (written) {
|
if (written) {
|
||||||
e->data_sz -= written;
|
e->data_sz -= written;
|
||||||
@@ -162,16 +169,16 @@ flush_pending_payloads(id_type timer_id UNUSED, void *x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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;
|
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)) {
|
if (offset < data_sz || (!offset && !data_sz)) {
|
||||||
ensure_space_for(pending, items, PendingEntry, pending->count + 1, capacity, 32, true);
|
ensure_space_for(pending, items, PendingEntry, pending->count + 1, capacity, 32, true);
|
||||||
char *buf = malloc(header_sz + data_sz - offset);
|
char *buf = malloc(header_sz + data_sz - offset);
|
||||||
if (!buf) fatal("Out of memory");
|
if (!buf) fatal("Out of memory");
|
||||||
memcpy(buf, header, header_sz); memcpy(buf + header_sz, data, data_sz - offset);
|
memcpy(buf, header, header_sz); memcpy(buf + header_sz, data, data_sz - offset);
|
||||||
PendingEntry *e = &pending->items[pending->count++];
|
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();
|
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;
|
if (n < 0) break;
|
||||||
pos += n;
|
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 {
|
} else {
|
||||||
buf[header_size++] = 0x1b; buf[header_size++] = '\\';
|
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
|
static void
|
||||||
drop_send_error(Window *w, int error_code) {
|
drop_send_error(Window *w, int error_code) {
|
||||||
char buf[128];
|
char buf[128];
|
||||||
uint8_t ebuf[32];
|
|
||||||
size_t b64_len = sizeof(ebuf);
|
|
||||||
const char *e = get_errno_name(error_code);
|
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);
|
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
|
void
|
||||||
@@ -328,7 +332,7 @@ drop_dispatch_data(Window *w, const char *data, ssize_t sz) {
|
|||||||
else {
|
else {
|
||||||
char buf[128];
|
char buf[128];
|
||||||
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;i=%u:t=r", DND_CODE, w->drop.client_id);
|
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) {
|
if (w->drop.wanted) {
|
||||||
char buf[128];
|
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);
|
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,
|
static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
||||||
const size_t parser_buf_pos) {
|
const size_t parser_buf_pos) {
|
||||||
unsigned int pos = 0;
|
unsigned int pos = 0;
|
||||||
|
size_t payload_start = 0;
|
||||||
enum PARSER_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE, PAYLOAD };
|
enum PARSER_STATES { KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE, PAYLOAD };
|
||||||
enum PARSER_STATES state = KEY, value_state = FLAG;
|
enum PARSER_STATES state = KEY, value_state = FLAG;
|
||||||
DnDCommand g = {0};
|
DnDCommand g = {0};
|
||||||
@@ -180,15 +180,8 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
|
|||||||
|
|
||||||
case PAYLOAD: {
|
case PAYLOAD: {
|
||||||
sz = parser_buf_pos - pos;
|
sz = parser_buf_pos - pos;
|
||||||
g.payload_sz = MAX(BUF_EXTRA, sz);
|
payload_start = pos;
|
||||||
if (!base64_decode8(parser_buf + pos, sz, parser_buf, &g.payload_sz)) {
|
g.payload_sz = 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;
|
|
||||||
}
|
|
||||||
pos = parser_buf_pos;
|
pos = parser_buf_pos;
|
||||||
} break;
|
} 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",
|
"cell_x", (int)g.cell_x, "cell_y", (int)g.cell_y, "pixel_x",
|
||||||
(int)g.pixel_x, "pixel_y", (int)g.pixel_y,
|
(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 {
|
typedef struct PendingEntry {
|
||||||
char *buf; size_t header_sz;
|
char *buf; size_t header_sz;
|
||||||
size_t data_sz;
|
size_t data_sz;
|
||||||
|
bool as_base64;
|
||||||
} PendingEntry;
|
} PendingEntry;
|
||||||
|
|
||||||
typedef struct PendingData {
|
typedef struct PendingData {
|
||||||
|
|||||||
Reference in New Issue
Block a user