mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-05-13 08:26:56 +00:00
Add descriptions to error codes for drag sources
Some checks are pending
CI / Linux (python=3.13 cc=clang sanitize=1) (push) Waiting to run
CI / Linux (python=3.11 cc=gcc sanitize=0) (push) Waiting to run
CI / Linux (python=3.12 cc=gcc sanitize=1) (push) Waiting to run
CI / Linux package (push) Waiting to run
CI / Bundle test (macos-latest) (push) Waiting to run
CI / Bundle test (ubuntu-latest) (push) Waiting to run
CI / macOS Brew (push) Waiting to run
CI / Test ./dev.sh and benchmark (push) Waiting to run
CodeQL / CodeQL-Build (actions, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, macos-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (go, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (python, ubuntu-latest) (push) Waiting to run
Depscan / Scan dependencies for vulnerabilities (push) Waiting to run
Some checks are pending
CI / Linux (python=3.13 cc=clang sanitize=1) (push) Waiting to run
CI / Linux (python=3.11 cc=gcc sanitize=0) (push) Waiting to run
CI / Linux (python=3.12 cc=gcc sanitize=1) (push) Waiting to run
CI / Linux package (push) Waiting to run
CI / Bundle test (macos-latest) (push) Waiting to run
CI / Bundle test (ubuntu-latest) (push) Waiting to run
CI / macOS Brew (push) Waiting to run
CI / Test ./dev.sh and benchmark (push) Waiting to run
CodeQL / CodeQL-Build (actions, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, macos-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (go, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (python, ubuntu-latest) (push) Waiting to run
Depscan / Scan dependencies for vulnerabilities (push) Waiting to run
This commit is contained in:
parent
10efa4be76
commit
21d8b2bcc0
3 changed files with 104 additions and 97 deletions
|
|
@ -117,11 +117,12 @@ The terminal must respond with a series of escape codes of the form::
|
|||
End of data is indicated by an empty payload and ``m=0``. If some error occurs while
|
||||
getting the data, the terminal must send an escape code of the form::
|
||||
|
||||
OSC _dnd_code ; t=R:x=idx ; POSIX error name ST
|
||||
OSC _dnd_code ; t=R:x=idx ; POSIX error name:optional description ST
|
||||
|
||||
Here ``POSIX error name`` is a POSIX symbolic error name such as ``ENOENT`` or
|
||||
``EIO`` or the value ``EUNKNOWN`` for an unknown error. Unless otherwise noted,
|
||||
any error response means the drop is terminated.
|
||||
any error response means the drop is terminated. The description is optional
|
||||
and must consist only of :ref:`safe_utf8`.
|
||||
|
||||
Once the client program finishes reading all the dropped data it needs, it must
|
||||
send an escape code of the form::
|
||||
|
|
@ -164,7 +165,7 @@ transmit the data as for a normal MIME data request, except it will have
|
|||
Similarly, error responses are as above, except for the addition of
|
||||
``y=subidx``, for example::
|
||||
|
||||
OSC _dnd_code ; t=R:x=idx:y=subidx ; POSIX error name ST
|
||||
OSC _dnd_code ; t=R:x=idx:y=subidx ; POSIX error name:optional desc ST
|
||||
|
||||
Terminals must reply with ``ENOENT`` if the index is out of bounds.
|
||||
If the client does not first request the ``text/uri-list`` MIME type or that
|
||||
|
|
@ -224,7 +225,7 @@ to the client. The terminal will respond with an escape code of the forms ::
|
|||
|
||||
In case of any errors, the terminal will respond with::
|
||||
|
||||
OSC _dnd_code ; t=R:Y=handle:x=num ; POSIX error name ST
|
||||
OSC _dnd_code ; t=R:Y=handle:x=num ; POSIX error name:optional desc ST
|
||||
|
||||
In the above, the ``Y=handle`` and ``x=num`` keys allow the client to know
|
||||
which directory entry the response concerns. The ``handle`` points to the
|
||||
|
|
@ -333,7 +334,7 @@ operation, it indicates the drag should be started by sending ``t=P:x=-1``. At
|
|||
this time if the user has already cancelled the drag or the terminal determines
|
||||
the drag operation is not allowed, it must respond with ``t=E ; EPERM``. If any
|
||||
other error occurs starting the drag operation, it must respond with the appropriate
|
||||
POSIX error name. If it determines that the image data after conversion to
|
||||
POSIX error name and optional error description. If it determines that the image data after conversion to
|
||||
display format is too large, it must respond with ``t=E ; EFBIG``. If the drag
|
||||
operation is successfully started, it must respond with ``t=E ; OK``.
|
||||
|
||||
|
|
@ -366,10 +367,11 @@ index into the list of MIME types. The data should be chunked using the
|
|||
``m`` key. End of data is denoted by ``m=0`` and an empty payload. If an error
|
||||
occurs the client should send::
|
||||
|
||||
OSC _dnd_code ; t=E:y=idx ; POSIX error name ST
|
||||
OSC _dnd_code ; t=E:y=idx ; POSIX error name:optional description ST
|
||||
|
||||
Where ``POSIX error name`` is a POSIX symbolic error name such as ``ENOENT``
|
||||
if the MIME type is not found or ``EIO`` if an IO error occurred and so on.
|
||||
if the MIME type is not found or ``EIO`` if an IO error occurred and so on. The
|
||||
description is optional and must contain only :ref:`safe_utf8`.
|
||||
|
||||
If the client wants to cancel the full drag at any time, it should send:
|
||||
|
||||
|
|
@ -425,7 +427,7 @@ to the drop destination.
|
|||
If any error occurs in the client while reading the data, it can inform
|
||||
the terminal using::
|
||||
|
||||
OSC _dnd_code ; t=E ; POSIX error name ST
|
||||
OSC _dnd_code ; t=E ; POSIX error name:optional description ST
|
||||
|
||||
The terminal must then abort the drag.
|
||||
|
||||
|
|
|
|||
164
kitty/dnd.c
164
kitty/dnd.c
|
|
@ -1289,22 +1289,24 @@ drag_free_offer(Window *w) {
|
|||
}
|
||||
|
||||
static void
|
||||
drag_send_error(Window *w, int error_code) {
|
||||
char buf[128];
|
||||
const char *e = get_errno_name(error_code);
|
||||
drag_send_error(Window *w, int error_code, const char *details) {
|
||||
char buf[128], details_buf[1024];
|
||||
int n;
|
||||
if (details && details[0]) n = snprintf(details_buf, sizeof(details_buf), "%s:%s", get_errno_name(error_code), details);
|
||||
else n = snprintf(details_buf, sizeof(details_buf), "%s", get_errno_name(error_code));
|
||||
int header_size = snprintf(buf, sizeof(buf), "\x1b]%d;t=E", DND_CODE);
|
||||
queue_payload_to_child(
|
||||
w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_size, e, strlen(e), false);
|
||||
w->id, w->drag_source.client_id, &w->drag_source.pending, buf, header_size, details_buf, n, false);
|
||||
}
|
||||
|
||||
static void
|
||||
cancel_drag(Window *w, int error_code) {
|
||||
if (error_code) drag_send_error(w, error_code);
|
||||
cancel_drag(Window *w, int error_code, const char *details) {
|
||||
if (error_code) drag_send_error(w, error_code, details);
|
||||
if (global_state.drag_source.is_active && global_state.drag_source.from_window == w->id) cancel_current_drag_source();
|
||||
drag_free_offer(w);
|
||||
}
|
||||
|
||||
#define abrt(code) { cancel_drag(w, code); return; }
|
||||
#define abrt(code, details) { cancel_drag(w, code, details); return; }
|
||||
|
||||
void
|
||||
drag_start_offerring(Window *w, const char *client_machine_id, size_t sz) {
|
||||
|
|
@ -1320,15 +1322,15 @@ drag_stop_offerring(Window *w) {
|
|||
|
||||
void
|
||||
drag_add_mimes(Window *w, int allowed_operations, uint32_t client_id, const char *data, size_t sz, bool has_more) {
|
||||
if (!ds.can_offer) abrt(EINVAL);
|
||||
if (!ds.can_offer) abrt(EINVAL, "cannot add drag source mimes as not offerring drag");
|
||||
if (allowed_operations && !ds.allowed_operations) ds.allowed_operations = allowed_operations;
|
||||
if (!ds.allowed_operations || ds.state > DRAG_SOURCE_BEING_BUILT) abrt(EINVAL);
|
||||
if (!ds.allowed_operations || ds.state > DRAG_SOURCE_BEING_BUILT) abrt(EINVAL, !ds.allowed_operations ? "cannot add drag source mimes as allowed operations are not set" : "cannot add drag source mimes as drag source is not being built");
|
||||
ds.state = DRAG_SOURCE_BEING_BUILT;
|
||||
ds.client_id = client_id;
|
||||
size_t new_sz = ds.bufsz + sz;
|
||||
if (new_sz > MIME_LIST_SIZE_CAP) abrt(EFBIG);
|
||||
if (new_sz > MIME_LIST_SIZE_CAP) abrt(EFBIG, "drag source mimes size too large");
|
||||
char *tmp = realloc(ds.mimes_buf, ds.bufsz + sz + 1);
|
||||
if (!tmp) abrt(ENOMEM);
|
||||
if (!tmp) abrt(ENOMEM, "out of mmeory adding drag source mimes");
|
||||
ds.mimes_buf = tmp;
|
||||
memcpy(ds.mimes_buf + ds.bufsz, data, sz);
|
||||
ds.bufsz = new_sz;
|
||||
|
|
@ -1341,7 +1343,7 @@ drag_add_mimes(Window *w, int allowed_operations, uint32_t client_id, const char
|
|||
rough_count++;
|
||||
}
|
||||
ds.items = calloc(rough_count + 2, sizeof(ds.items[0]));
|
||||
if (!ds.items) abrt(ENOMEM);
|
||||
if (!ds.items) abrt(ENOMEM, "out of mmeory adding drag source mimes");
|
||||
char *p = ds.mimes_buf, *end = ds.mimes_buf + ds.bufsz;
|
||||
ds.num_mimes = 0;
|
||||
while (p < end) {
|
||||
|
|
@ -1358,8 +1360,9 @@ drag_add_mimes(Window *w, int allowed_operations, uint32_t client_id, const char
|
|||
|
||||
void
|
||||
drag_add_pre_sent_data(Window *w, unsigned idx, const uint8_t *payload, size_t sz) {
|
||||
if (ds.state != DRAG_SOURCE_BEING_BUILT || idx >= ds.num_mimes) abrt(EINVAL);
|
||||
if (sz + ds.pre_sent_total_sz > PRESENT_DATA_CAP) abrt(EFBIG);
|
||||
if (ds.state != DRAG_SOURCE_BEING_BUILT || idx >= ds.num_mimes) abrt(EINVAL, idx >= ds.num_mimes ?
|
||||
"pre-sent data item idx too large" : "drag source not being currently built, cannot add pre-sent data");
|
||||
if (sz + ds.pre_sent_total_sz > PRESENT_DATA_CAP) abrt(EFBIG, "too much pre-sent data");
|
||||
ds.pre_sent_total_sz += sz;
|
||||
#define item ds.items[idx]
|
||||
if (!item.data_decode_initialized) {
|
||||
|
|
@ -1369,12 +1372,12 @@ drag_add_pre_sent_data(Window *w, unsigned idx, const uint8_t *payload, size_t s
|
|||
if (item.data_capacity < sz + item.data_size) {
|
||||
size_t newcap = MAX(item.data_capacity * 2, sz + item.data_size);
|
||||
uint8_t *tmp = realloc(item.optional_data, newcap);
|
||||
if (!tmp) abrt(ENOMEM);
|
||||
if (!tmp) abrt(ENOMEM, "");
|
||||
item.optional_data = tmp;
|
||||
item.data_capacity = newcap;
|
||||
}
|
||||
size_t outlen = item.data_capacity - item.data_size;
|
||||
if (!base64_decode_stream(&item.base64_state, payload, sz, item.optional_data + item.data_size, &outlen)) abrt(EINVAL);
|
||||
if (!base64_decode_stream(&item.base64_state, payload, sz, item.optional_data + item.data_size, &outlen)) abrt(EINVAL, "error while decoding base64 pre-sent data");
|
||||
item.data_size += outlen;
|
||||
#undef item
|
||||
}
|
||||
|
|
@ -1383,13 +1386,13 @@ drag_add_pre_sent_data(Window *w, unsigned idx, const uint8_t *payload, size_t s
|
|||
|
||||
void
|
||||
drag_add_image(Window *w, unsigned idx, int fmt, int width, int height, int opacity, const uint8_t *payload, size_t sz) {
|
||||
if (ds.state != DRAG_SOURCE_BEING_BUILT) abrt(EINVAL);
|
||||
if (idx + 1 >= arraysz(ds.images)) abrt(EFBIG);
|
||||
if (ds.images_sent_total_sz + sz > PRESENT_DATA_CAP) abrt(EFBIG);
|
||||
if (ds.state != DRAG_SOURCE_BEING_BUILT) abrt(EINVAL, "cannot add drag thumbnail as drag source not currently being built");
|
||||
if (idx + 1 >= arraysz(ds.images)) abrt(EFBIG, "too many drag thumbnails");
|
||||
if (ds.images_sent_total_sz + sz > PRESENT_DATA_CAP) abrt(EFBIG, "drag thumbnails too large");
|
||||
ds.images_sent_total_sz += sz;
|
||||
if (!img.started) {
|
||||
if (fmt != 0 && fmt != 24 && fmt != 32 && fmt != 100) abrt(EINVAL);
|
||||
if (fmt != 0 && (width < 1 || height < 1)) abrt(EINVAL);
|
||||
if (fmt != 0 && fmt != 24 && fmt != 32 && fmt != 100) abrt(EINVAL, "unknown drag thumbnail format");
|
||||
if (fmt != 0 && (width < 1 || height < 1)) abrt(EINVAL, "invalid drag thumbnail image dimensions");
|
||||
img.started = true;
|
||||
img.width = width; img.height = height;
|
||||
img.fmt = fmt;
|
||||
|
|
@ -1399,12 +1402,12 @@ drag_add_image(Window *w, unsigned idx, int fmt, int width, int height, int opac
|
|||
if (img.capacity < MAX(32u, sz + img.sz)) {
|
||||
size_t newcap = MAX(img.capacity * 2, MAX(32u, sz + img.sz));
|
||||
uint8_t *tmp = realloc(img.data, newcap);
|
||||
if (!tmp) abrt(ENOMEM);
|
||||
if (!tmp) abrt(ENOMEM, "out of memory processing drag thumbnails");
|
||||
img.data = tmp;
|
||||
img.capacity = newcap;
|
||||
}
|
||||
size_t outlen = img.capacity - img.sz;
|
||||
if (!base64_decode_stream(&img.base64_state, payload, sz, img.data + img.sz, &outlen)) abrt(EINVAL);
|
||||
if (!base64_decode_stream(&img.base64_state, payload, sz, img.data + img.sz, &outlen)) abrt(EINVAL, "could not base64 decode drag thumbnail data");
|
||||
img.sz += outlen;
|
||||
}
|
||||
|
||||
|
|
@ -1416,11 +1419,11 @@ drag_change_image(Window *w, unsigned idx) {
|
|||
|
||||
static bool
|
||||
expand_rgb_data(Window *w, size_t idx) {
|
||||
#define fail(code) { cancel_drag(w, code); return false; }
|
||||
if (img.sz != (size_t)img.width * (size_t)img.height * 3) fail(EINVAL);
|
||||
#define fail(code, details) { cancel_drag(w, code, details); return false; }
|
||||
if (img.sz != (size_t)img.width * (size_t)img.height * 3) fail(EINVAL, "drag thumbnail RGB data not correct size");
|
||||
const size_t sz = (size_t)img.width * (size_t)img.height * 4u;
|
||||
RAII_ALLOC(uint8_t, expanded, malloc(sz));
|
||||
if (!expanded) fail(ENOMEM);
|
||||
if (!expanded) fail(ENOMEM, "out of memory processing drag thumbnail RGB data");
|
||||
memset(expanded, 0xff, sz);
|
||||
for (int r = 0; r < img.height; r++) {
|
||||
uint8_t *src_row = img.data + r * img.width * 3, *dest_row = expanded + r * img.width * 4;
|
||||
|
|
@ -1449,7 +1452,7 @@ static size_t last_total_image_size = 0;
|
|||
|
||||
void
|
||||
drag_start(Window *w) {
|
||||
if (ds.state != DRAG_SOURCE_BEING_BUILT) abrt(EINVAL);
|
||||
if (ds.state != DRAG_SOURCE_BEING_BUILT) abrt(EINVAL, "cannot start drag as drag source is not being built");
|
||||
size_t total_size = 0;
|
||||
for (size_t idx = 0; idx < arraysz(ds.images); idx++) {
|
||||
if (img.sz) {
|
||||
|
|
@ -1482,11 +1485,11 @@ drag_start(Window *w) {
|
|||
cp, cp->overridden.default_bg, cp->configured.default_bg).rgb | (((uint32_t)bg_alpha) << 24);
|
||||
// Add a null terminator for draw_window_title
|
||||
uint8_t *txt = realloc(img.data, img.sz + 1);
|
||||
if (!txt) { abrt(ENOMEM); return; }
|
||||
if (!txt) { abrt(ENOMEM, "out of memory processing text based drag thumbnail"); return; }
|
||||
img.data = txt; txt[img.sz] = '\0';
|
||||
const size_t max_width = (size_t)screen->cell_size.width * screen->columns;
|
||||
uint8_t *render_buf = malloc(max_width * render_height * 4);
|
||||
if (!render_buf) { abrt(ENOMEM); return; }
|
||||
if (!render_buf) { abrt(ENOMEM, "out of memory processing text based drag thumbnail"); return; }
|
||||
size_t actual_width = max_width;
|
||||
bool ok = draw_window_title(adjusted_font_sz, ydpi, (const char*)img.data,
|
||||
fg_color, bg_color, render_buf,
|
||||
|
|
@ -1512,14 +1515,15 @@ drag_start(Window *w) {
|
|||
} break;
|
||||
}
|
||||
total_size += img.sz;
|
||||
if (total_size > 2 * PRESENT_DATA_CAP) abrt(EFBIG);
|
||||
if (img.sz != (size_t)img.width * (size_t)img.height * 4u) abrt(EINVAL);
|
||||
if (total_size > 2 * PRESENT_DATA_CAP) abrt(EFBIG, "too large a drag thumbnail");
|
||||
if (img.sz != (size_t)img.width * (size_t)img.height * 4u) abrt(EINVAL, "drag thumbnail size incorrect");
|
||||
}
|
||||
}
|
||||
last_total_image_size = total_size;
|
||||
int err = start_window_drag(w, dnd_is_test_mode());
|
||||
if (err != 0) {
|
||||
abrt(err);
|
||||
if (err == EPERM) abrt(err, "permission to start drag denied, this can happen if the user has already released the drag or if the mouse has moved out of the window");
|
||||
abrt(err, "failed to start drag in OS");
|
||||
} else {
|
||||
// Free images and optional_data but keep the items array for later
|
||||
// data requests from the drop target
|
||||
|
|
@ -1535,7 +1539,7 @@ drag_start(Window *w) {
|
|||
zero_at_ptr(ds.images + i);
|
||||
}
|
||||
ds.state = DRAG_SOURCE_STARTED;
|
||||
drag_send_error(w, 0); // send OK
|
||||
drag_send_error(w, 0, ""); // send OK
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1673,7 +1677,7 @@ open_item_tmpfile(void) {
|
|||
void
|
||||
drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *payload, size_t payload_sz) {
|
||||
if ((ds.state < DRAG_SOURCE_STARTED) || idx >= ds.num_mimes || !ds.items) {
|
||||
abrt(EINVAL);
|
||||
abrt(EINVAL, ds.state < DRAG_SOURCE_STARTED ? "cannot process drag source item data as drag has not been started" : "cannot process drag source item data as item index is out of bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1687,7 +1691,7 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
|||
ds.items[idx].data_decode_initialized = false;
|
||||
if (!dnd_is_test_mode()) {
|
||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||
if (ret) cancel_drag(w, ret);
|
||||
if (ret) cancel_drag(w, ret, "could not notify OS that drag source item data is available");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -1699,7 +1703,7 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
|||
if (!ds.items[idx].requested_remote_files) {
|
||||
if (!dnd_is_test_mode()) {
|
||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||
if (ret) cancel_drag(w, ret);
|
||||
if (ret) cancel_drag(w, ret, "could not notify OS that drag source item data is available");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1709,7 +1713,7 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
|||
// Open temp file if not yet open
|
||||
if (!ds.items[idx].fd_plus_one) {
|
||||
int fd = open_item_tmpfile();
|
||||
if (fd < 0) { cancel_drag(w, EIO); return; }
|
||||
if (fd < 0) { cancel_drag(w, EIO, "failed to open temporary file to store drag source item data"); return; }
|
||||
ds.items[idx].fd_plus_one = fd + 1;
|
||||
ds.items[idx].data_decode_initialized = true;
|
||||
ds.items[idx].data_size = 0; // read position for pread
|
||||
|
|
@ -1720,17 +1724,17 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
|||
// Decode and write payload data
|
||||
if (payload_sz > 0) {
|
||||
RAII_ALLOC(uint8_t, decoded, malloc(payload_sz));
|
||||
if (!decoded) { cancel_drag(w, ENOMEM); return; }
|
||||
if (!decoded) { cancel_drag(w, ENOMEM, "out of memory processing drag source item data"); return; }
|
||||
size_t outlen = payload_sz;
|
||||
if (!base64_decode_stream(&ds.items[idx].base64_state, payload, payload_sz, decoded, &outlen)) {
|
||||
cancel_drag(w, EINVAL);
|
||||
cancel_drag(w, EINVAL, "failed to base64 decode drag source item data");
|
||||
return;
|
||||
}
|
||||
size_t written = 0;
|
||||
while (written < outlen) {
|
||||
ssize_t n = safe_write(ds.items[idx].fd_plus_one - 1, decoded + written, outlen - written);
|
||||
if (n < 0) {
|
||||
cancel_drag(w, EIO);
|
||||
cancel_drag(w, EIO, "failed to write drag source item data to temp file");
|
||||
return;
|
||||
}
|
||||
written += (size_t)n;
|
||||
|
|
@ -1740,7 +1744,7 @@ drag_process_item_data(Window *w, size_t idx, int has_more, const uint8_t *paylo
|
|||
if (!ds.items[idx].requested_remote_files) {
|
||||
if (!dnd_is_test_mode()) {
|
||||
int ret = notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[idx].mime_type);
|
||||
if (ret) cancel_drag(w, ret);
|
||||
if (ret) cancel_drag(w, ret, "could not notify OS that drag source item data is available");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1751,16 +1755,16 @@ parse_uri_list(Window *w, int fd, size_t *num_uris_out) {
|
|||
*num_uris_out = 0;
|
||||
// Determine file size and read all data
|
||||
off_t file_size = lseek(fd, 0, SEEK_END);
|
||||
if (file_size < 0) { cancel_drag(w, EIO); return NULL; }
|
||||
if (lseek(fd, 0, SEEK_SET) < 0) { cancel_drag(w, EIO); return NULL; }
|
||||
if (file_size < 0) { cancel_drag(w, EIO, "failed to read cached uri-list data"); return NULL; }
|
||||
if (lseek(fd, 0, SEEK_SET) < 0) { cancel_drag(w, EIO, "failed to read cached uri-list data"); return NULL; }
|
||||
RAII_ALLOC(char, buf, malloc((size_t)file_size + 1));
|
||||
if (!buf) { cancel_drag(w, ENOMEM); return NULL; }
|
||||
if (!buf) { cancel_drag(w, ENOMEM, "out of memory processing uri list data"); return NULL; }
|
||||
size_t total = 0;
|
||||
while (total < (size_t)file_size) {
|
||||
ssize_t n = read(fd, buf + total, (size_t)file_size - total);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
cancel_drag(w, EIO); return NULL;
|
||||
cancel_drag(w, EIO, "failed to read cached uri-list data"); return NULL;
|
||||
}
|
||||
if (n == 0) break;
|
||||
total += (size_t)n;
|
||||
|
|
@ -1785,7 +1789,7 @@ parse_uri_list(Window *w, int fd, size_t *num_uris_out) {
|
|||
}
|
||||
|
||||
char **result = calloc((count + 1), sizeof(const char*));
|
||||
if (!result) { cancel_drag(w, ENOMEM); return NULL; }
|
||||
if (!result) { cancel_drag(w, ENOMEM, "out of memory parsing uri list"); return NULL; }
|
||||
|
||||
// Second pass: fill in decoded URI strings
|
||||
size_t idx = 0;
|
||||
|
|
@ -1800,7 +1804,7 @@ parse_uri_list(Window *w, int fd, size_t *num_uris_out) {
|
|||
char *decoded = strdup(p);
|
||||
if (!decoded) {
|
||||
for (size_t k = 0; k < idx; k++) free((char*)result[k]);
|
||||
free(result); cancel_drag(w, ENOMEM); return NULL;
|
||||
free(result); cancel_drag(w, ENOMEM, "out of memory parsing uri list"); return NULL;
|
||||
}
|
||||
result[idx++] = decoded;
|
||||
}
|
||||
|
|
@ -1828,15 +1832,15 @@ static void
|
|||
finish_remote_data(Window *w, size_t item_idx) {
|
||||
const int fd = ds.items[item_idx].fd_plus_one - 1;
|
||||
ds.items[item_idx].requested_remote_files = false;
|
||||
if (safe_ftruncate(fd, 0) != 0) abrt(errno);
|
||||
if (lseek(fd, 0, SEEK_SET) == -1) abrt(errno);
|
||||
if (safe_ftruncate(fd, 0) != 0) abrt(errno, "error updating uri list after all remote data received");
|
||||
if (lseek(fd, 0, SEEK_SET) == -1) abrt(errno, "error updating uri list after all remote data received");
|
||||
size_t new_size = 0;
|
||||
for (size_t i = 0; i < ds.items[item_idx].num_uris; i++) {
|
||||
int ret = write_all(fd, ds.items[item_idx].uri_list[i], strlen(ds.items[item_idx].uri_list[i]));
|
||||
new_size += strlen(ds.items[item_idx].uri_list[i]);
|
||||
free((char*)ds.items[item_idx].uri_list[i]); ds.items[item_idx].uri_list[i] = NULL;
|
||||
if (ret) abrt(ret);
|
||||
if ((ret = write_all(fd, "\r\n", 2))) abrt(ret);
|
||||
if (ret) abrt(ret, "error updating uri list after all remote data received");
|
||||
if ((ret = write_all(fd, "\r\n", 2))) abrt(ret, "error updating uri list after all remote data received");
|
||||
new_size += 2;
|
||||
}
|
||||
free(ds.items[item_idx].uri_list); ds.items[item_idx].uri_list = NULL; ds.items[item_idx].num_uris = 0;
|
||||
|
|
@ -1845,7 +1849,7 @@ finish_remote_data(Window *w, size_t item_idx) {
|
|||
ds.items[item_idx].data_capacity = new_size;
|
||||
ds.items[item_idx].data_size = 0;
|
||||
int ret = dnd_is_test_mode() ? 0 : notify_drag_data_ready(global_state.drag_source.from_os_window, ds.items[item_idx].mime_type);
|
||||
if (ret) abrt(ret);
|
||||
if (ret) abrt(ret, "could not notify OS that drag source item data is available");
|
||||
}
|
||||
|
||||
#define mi ds.items[mime_item_idx]
|
||||
|
|
@ -1906,7 +1910,7 @@ static void
|
|||
populate_dir_entries(Window *w, DragRemoteItem *ri) {
|
||||
size_t num = count_occurrences((char*)ri->data, ri->data_sz, 0) + 1;
|
||||
ri->children = calloc(num + 1, sizeof(ri->children[0]));
|
||||
if (!ri->children) abrt(ENOMEM);
|
||||
if (!ri->children) abrt(ENOMEM, "out of memory processing drag source item directory entries");
|
||||
ri->children_sz = 0;
|
||||
const char *ptr = (char*)ri->data;
|
||||
const char *end = (char*)ri->data + ri->data_sz;
|
||||
|
|
@ -1915,7 +1919,7 @@ populate_dir_entries(Window *w, DragRemoteItem *ri) {
|
|||
size_t len = p ? (size_t)(p - ptr) : (size_t)(end - ptr);
|
||||
if (len > 0) {
|
||||
char *name = strndup(ptr, len);
|
||||
if (!name) abrt(ENOMEM);
|
||||
if (!name) abrt(ENOMEM, "out of memory processing drag source item directory entries");
|
||||
ri->children[ri->children_sz++].dir_entry_name = name;
|
||||
}
|
||||
ptr = p ? p + 1 : end;
|
||||
|
|
@ -1927,37 +1931,37 @@ populate_dir_entries(Window *w, DragRemoteItem *ri) {
|
|||
static void
|
||||
add_payload(Window *w, DragRemoteItem *ri, bool has_more, const uint8_t *payload, size_t payload_sz, int dirfd) {
|
||||
if (payload_sz && payload) {
|
||||
if (payload_sz > 4096) abrt(EINVAL);
|
||||
if (payload_sz > 4096) abrt(EINVAL, "drag source item data chunk too large");
|
||||
switch (ri->type) {
|
||||
case 0: {
|
||||
if (!ri->fd_plus_one) {
|
||||
int fd = safe_openat(dirfd, ri->dir_entry_name, O_CREAT | O_WRONLY, file_permissions);
|
||||
if (fd < 0) abrt(errno);
|
||||
if (fd < 0) abrt(errno, "could not open drag source item data file");
|
||||
ri->fd_plus_one = fd + 1;
|
||||
}
|
||||
uint8_t buf[4096];
|
||||
size_t outlen = sizeof(buf);
|
||||
if (!base64_decode_stream(&ri->base64_state, payload, payload_sz, buf, &outlen)) abrt(EINVAL);
|
||||
if (!base64_decode_stream(&ri->base64_state, payload, payload_sz, buf, &outlen)) abrt(EINVAL, "could not base64 decode drag source item data");
|
||||
ds.total_remote_data_size += outlen;
|
||||
if (outlen && write_all(ri->fd_plus_one-1, buf, outlen) < 0) abrt(errno);
|
||||
if (outlen && write_all(ri->fd_plus_one-1, buf, outlen) < 0) abrt(errno, "could not write drag source item data to file");
|
||||
} break;
|
||||
default: {
|
||||
if (ri->data_sz + payload_sz > ri->data_capacity) {
|
||||
size_t cap = MAX(ri->data_capacity * 2, ri->data_sz + payload_sz + 4096);
|
||||
if (cap > PRESENT_DATA_CAP) abrt(EMFILE);
|
||||
if (cap > PRESENT_DATA_CAP) abrt(EMFILE, "too much drag source item data");
|
||||
uint8_t *tmp = realloc(ri->data, cap);
|
||||
if (!tmp) abrt(ENOMEM);
|
||||
if (!tmp) abrt(ENOMEM, "out of memory processing drag source item data");
|
||||
ri->data = tmp;
|
||||
ri->data_capacity = cap;
|
||||
}
|
||||
size_t outlen = ri->data_capacity - ri->data_sz;
|
||||
if (!base64_decode_stream(&ri->base64_state, payload, payload_sz, ri->data + ri->data_sz, &outlen)) abrt(EINVAL);
|
||||
if (!base64_decode_stream(&ri->base64_state, payload, payload_sz, ri->data + ri->data_sz, &outlen)) abrt(EINVAL, "could not base64 decode drag source item data");
|
||||
ds.total_remote_data_size += outlen;
|
||||
ri->data_sz += outlen;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
if (ds.total_remote_data_size > REMOTE_DRAG_LIMIT) abrt(EMFILE);
|
||||
if (ds.total_remote_data_size > REMOTE_DRAG_LIMIT) abrt(EMFILE, "too much drag source item data");
|
||||
if (!has_more && !payload_sz) { // all data received
|
||||
switch (ri->type) {
|
||||
case 0:
|
||||
|
|
@ -1968,15 +1972,15 @@ add_payload(Window *w, DragRemoteItem *ri, bool has_more, const uint8_t *payload
|
|||
// Ensure room for the null terminator needed by symlinkat
|
||||
if (ri->data_sz >= ri->data_capacity) {
|
||||
uint8_t *tmp = realloc(ri->data, ri->data_sz + 1);
|
||||
if (!tmp) abrt(ENOMEM);
|
||||
if (!tmp) abrt(ENOMEM, "out of memory processingdrag source symlink item");
|
||||
ri->data = tmp;
|
||||
ri->data_capacity = ri->data_sz + 1;
|
||||
}
|
||||
ri->data[ri->data_sz] = 0;
|
||||
if (symlinkat((char*)ri->data, dirfd, ri->dir_entry_name) != 0) abrt(errno);
|
||||
if (symlinkat((char*)ri->data, dirfd, ri->dir_entry_name) != 0) abrt(errno, "failed to create symlink for drag source item");
|
||||
break;
|
||||
default:
|
||||
if (mkdirat(dirfd, ri->dir_entry_name, dir_permissions) != 0 && errno != EEXIST) abrt(errno);
|
||||
if (mkdirat(dirfd, ri->dir_entry_name, dir_permissions) != 0 && errno != EEXIST) abrt(errno, "failed to create directory for drag source item");
|
||||
populate_dir_entries(w, ri);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1992,32 +1996,32 @@ toplevel_data_for_drag(
|
|||
) {
|
||||
if (!mi.remote_items) {
|
||||
mi.remote_items = calloc(mi.num_uris, sizeof(mi.remote_items[0]));
|
||||
if (!mi.remote_items) abrt(ENOMEM);
|
||||
if (!mi.remote_items) abrt(ENOMEM, "out of memory processing drag source item");
|
||||
mi.num_remote_items = mi.num_uris;
|
||||
}
|
||||
if (!mi.base_dir_for_remote_items) {
|
||||
int fd;
|
||||
mi.base_dir_for_remote_items = mktempdir_in_cache("dnd-drag-", &fd);
|
||||
if (!mi.base_dir_for_remote_items) abrt(errno);
|
||||
if (!mi.base_dir_for_remote_items) abrt(errno, "failed to create temporary directory for drag source items");
|
||||
mi.base_dir_fd_plus_one = fd + 1;
|
||||
detect_tempdir_case_sensitivity(mi.base_dir_for_remote_items);
|
||||
}
|
||||
if (uri_item_idx >= mi.num_remote_items) abrt(EINVAL);
|
||||
if (uri_item_idx >= mi.num_remote_items) abrt(EINVAL, "out of bounds uri list item index for drag source");
|
||||
DragRemoteItem *ri = mi.remote_items + uri_item_idx;
|
||||
if (!ri->started) {
|
||||
ri->started = true;
|
||||
ri->type = item_type;
|
||||
base64_init_stream_decoder(&ri->base64_state);
|
||||
if (uri_item_idx >= mi.num_uris) abrt(EINVAL);
|
||||
if (uri_item_idx >= mi.num_uris) abrt(EINVAL, "out of bounds uri list item index for drag source");
|
||||
const char *uri = mi.uri_list[uri_item_idx];
|
||||
char *fname = sanitized_filename_from_url(uri);
|
||||
if (!fname) abrt(EINVAL);
|
||||
if (!fname) abrt(EINVAL, "could not sanitize filename for URI in drag source uri-list");
|
||||
ri->dir_entry_name = fname;
|
||||
char path[32];
|
||||
snprintf(path, sizeof(path), "%u", uri_item_idx);
|
||||
if (mkdirat(mi.base_dir_fd_plus_one - 1, path, dir_permissions) != 0 && errno != EEXIST) abrt(errno);
|
||||
if (mkdirat(mi.base_dir_fd_plus_one - 1, path, dir_permissions) != 0 && errno != EEXIST) abrt(errno, "failed to create directory for drag source item");
|
||||
int fd = safe_openat(mi.base_dir_fd_plus_one - 1, path, O_RDONLY | O_DIRECTORY, 0);
|
||||
if (fd < 0) abrt(errno);
|
||||
if (fd < 0) abrt(errno, "failed to create directory for drag source item");
|
||||
ri->top_level_parent_dir_fd_plus_one = fd + 1;
|
||||
free(mi.uri_list[uri_item_idx]);
|
||||
mi.uri_list[uri_item_idx] = as_file_url(mi.base_dir_for_remote_items, path, ri->dir_entry_name);
|
||||
|
|
@ -2046,7 +2050,7 @@ subdir_data_for_drag(
|
|||
Window *w, unsigned mime_item_idx, unsigned uri_item_idx, int handle, unsigned entry_num, unsigned item_type,
|
||||
bool has_more, const uint8_t *payload, size_t payload_sz
|
||||
) {
|
||||
if (!mi.remote_items || uri_item_idx >= mi.num_remote_items) abrt(EINVAL);
|
||||
if (!mi.remote_items || uri_item_idx >= mi.num_remote_items) abrt(EINVAL, "drag source sub directory item uri list index out of range");
|
||||
DragRemoteItem *parent = NULL;
|
||||
if (mi.currently_open_subdir) {
|
||||
if (mi.currently_open_subdir->type == handle) parent = mi.currently_open_subdir;
|
||||
|
|
@ -2061,19 +2065,19 @@ subdir_data_for_drag(
|
|||
if (parent == NULL || !parent->fd_plus_one) {
|
||||
char path[PATH_MAX+1]; path[PATH_MAX] = 0;
|
||||
DragRemoteItem *root = mi.remote_items + uri_item_idx;
|
||||
if (!root->dir_entry_name) abrt(EINVAL);
|
||||
if (!root->dir_entry_name) abrt(EINVAL, "drag source sub directory parent dir does not exist");
|
||||
size_t pos = snprintf(path, PATH_MAX, "%s/%u/%s",
|
||||
mi.base_dir_for_remote_items, uri_item_idx, root->dir_entry_name);
|
||||
parent = find_by_handle(root, handle, path, &pos);
|
||||
if (!parent) abrt(EINVAL);
|
||||
if (!parent) abrt(EINVAL, "drag source sub directory parent dir handle does not exist");
|
||||
mi.currently_open_subdir = parent;
|
||||
if (!parent->fd_plus_one) {
|
||||
int fd = safe_open(path, O_DIRECTORY | O_RDONLY, 0);
|
||||
if (fd < 0) abrt(errno);
|
||||
if (fd < 0) abrt(errno, "drag source failed to create sub directory");
|
||||
parent->fd_plus_one = fd + 1;
|
||||
}
|
||||
}
|
||||
if (entry_num >= parent->children_sz) abrt(EINVAL);
|
||||
if (entry_num >= parent->children_sz) abrt(EINVAL, "drag source sub diretory index out of bounds");
|
||||
DragRemoteItem *ri = parent->children + entry_num;
|
||||
if (!ri->started) {
|
||||
ri->started = true;
|
||||
|
|
@ -2103,12 +2107,12 @@ drag_remote_file_data(
|
|||
item_idx = i; break;
|
||||
}
|
||||
}
|
||||
if (item_idx == ds.num_mimes || ds.items[item_idx].fd_plus_one == 0) abrt(EINVAL);
|
||||
if (item_idx == ds.num_mimes || ds.items[item_idx].fd_plus_one == 0) abrt(EINVAL, "drag source remote file item index out of bounds");
|
||||
if (ds.items[item_idx].uri_list == NULL) {
|
||||
ds.items[item_idx].uri_list = parse_uri_list(w, ds.items[item_idx].fd_plus_one-1, &ds.items[item_idx].num_uris);
|
||||
if (!ds.items[item_idx].uri_list) return;
|
||||
}
|
||||
if (X < 0) abrt(EINVAL);
|
||||
if (X < 0) abrt(EINVAL, "drag source remote item X cannot be negative");
|
||||
if (!x && !y && !Y) { finish_remote_data(w, item_idx); return; }
|
||||
if (!Y) toplevel_data_for_drag(w, item_idx, x - 1, X, has_more, payload, payload_sz);
|
||||
else subdir_data_for_drag(w, item_idx, x - 1, Y, y - 1, X, has_more, payload, payload_sz);
|
||||
|
|
@ -2328,7 +2332,7 @@ dnd_test_drag_finish(PyObject *self UNUSED, PyObject *args) {
|
|||
if (!w) { PyErr_SetString(PyExc_ValueError, "Window not found"); return NULL; }
|
||||
global_state.drag_source.was_canceled = canceled_by_user;
|
||||
if (!errcode) drag_notify(w, DRAG_NOTIFY_FINISHED);
|
||||
cancel_drag(w, errcode);
|
||||
cancel_drag(w, errcode, "");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1517,7 +1517,7 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), b'EINVAL')
|
||||
self.ae(events[0]['payload'].strip().partition(b':')[0], b'EINVAL')
|
||||
|
||||
def test_drag_pre_send_data_moderate_chunk(self) -> None:
|
||||
"""Pre-sending a moderate chunk of data succeeds without triggering size cap."""
|
||||
|
|
@ -1539,7 +1539,7 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), b'EINVAL')
|
||||
self.ae(events[0]['payload'].strip().partition(b':')[0], b'EINVAL')
|
||||
|
||||
def test_drag_add_image_rgba_valid(self) -> None:
|
||||
"""Adding a valid RGBA image succeeds without error."""
|
||||
|
|
@ -1572,7 +1572,7 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), b'EINVAL')
|
||||
self.ae(events[0]['payload'].strip().partition(b':')[0], b'EINVAL')
|
||||
|
||||
def test_drag_add_image_invalid_dimensions_returns_einval(self) -> None:
|
||||
"""Adding an image with zero or negative dimensions returns EINVAL."""
|
||||
|
|
@ -1585,7 +1585,7 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), b'EINVAL')
|
||||
self.ae(events[0]['payload'].strip().partition(b':')[0], b'EINVAL')
|
||||
|
||||
def test_drag_add_image_without_offer_returns_einval(self) -> None:
|
||||
"""Adding an image without a prior drag offer returns EINVAL."""
|
||||
|
|
@ -1596,7 +1596,7 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), b'EINVAL')
|
||||
self.ae(events[0]['payload'].strip().partition(b':')[0], b'EINVAL')
|
||||
|
||||
def test_drag_add_too_many_images_returns_error(self) -> None:
|
||||
"""Adding more than the maximum number of images returns an error."""
|
||||
|
|
@ -1625,7 +1625,7 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), b'EINVAL')
|
||||
self.ae(events[0]['payload'].strip().partition(b':')[0], b'EINVAL')
|
||||
|
||||
def test_drag_free_offer_cleans_up(self) -> None:
|
||||
"""Sending t=O cleans up a partially built drag offer."""
|
||||
|
|
@ -1645,7 +1645,7 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), b'EINVAL')
|
||||
self.ae(events[0]['payload'].strip().partition(b':')[0], b'EINVAL')
|
||||
|
||||
def test_drag_cancel_from_client(self) -> None:
|
||||
"""Client can cancel a drag via t=E:y=-1."""
|
||||
|
|
@ -1660,7 +1660,7 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), b'EINVAL')
|
||||
self.ae(events[0]['payload'].strip().partition(b':')[0], b'EINVAL')
|
||||
|
||||
def test_drag_second_offer_replaces_first(self) -> None:
|
||||
"""A second offer with operations replaces the first one."""
|
||||
|
|
@ -1831,7 +1831,8 @@ class TestDnDProtocol(BaseTest):
|
|||
events = self._get_events(cap)
|
||||
self.assertEqual(len(events), 1, events)
|
||||
self.ae(events[0]['type'], 'E')
|
||||
self.ae(events[0]['payload'].strip(), code.encode())
|
||||
payload = events[0]['payload'].strip()
|
||||
self.ae(payload.partition(b':')[0], code.encode())
|
||||
|
||||
def test_drag_pre_send_multiple_mimes(self) -> None:
|
||||
"""Pre-sent data can be provided for multiple different MIME types."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue