Modify drop protocol to allow transmission of broken symlinks in the URI list

This commit is contained in:
Kovid Goyal
2026-04-23 12:36:22 +05:30
parent 3cf037d263
commit 4c6f7ff6b5
4 changed files with 36 additions and 25 deletions

View File

@@ -169,10 +169,14 @@ If the client requests an entry that is not a supported URI type the
terminal must reply with ``EUNKNOWN``. terminal must reply with ``EUNKNOWN``.
Terminals must ONLY send data for regular files or directories. Symbolic links must be Terminals must ONLY send data for regular files or directories. Symbolic links must be
resolved and the corresponding file or directory read. If the terminal does not have resolved and the corresponding file or directory read. Only if the symbolic
permission to read the file it must reply with ``EPERM``. Terminals link cannot be resolved must it be transmitted as a symbolic link (in which
must respond with ``EINVAL`` if the file is not a regular file after case ``X=1`` and the payload is the base64 encoded target of the symlink. See
resolving symlinks and ``ENOENT`` if the file does not exist. If an below for more details about sending symlinks.
If the terminal does not have permission to read the file it must reply with
``EPERM``. Terminals must respond with ``EINVAL`` if the file is not a regular
file after resolving symlinks and ``ENOENT`` if the file does not exist. If an
I/O error occurs the terminal must send ``EIO``. I/O error occurs the terminal must send ``EIO``.
For security reasons, terminals must reply with ``EPERM`` if the drag For security reasons, terminals must reply with ``EPERM`` if the drag

View File

@@ -396,7 +396,10 @@ func (dnd *dnd) on_remote_drop_data(cmd DC) (err error) {
} }
if e.dest == nil { // this entry is finished if e.dest == nil { // this entry is finished
drop_status.open_remote_dir.num_children_finished++ drop_status.open_remote_dir.num_children_finished++
if len(e.children) > 0 {
if e.item_type != 0 && e.item_type != 1 { if e.item_type != 0 && e.item_type != 1 {
dnd.lp.QueueDnDData(DC{Type: 'r', Yp: e.item_type}) // close directory in terminal
}
// TODO: request the children // TODO: request the children
} }
} }

View File

@@ -690,18 +690,7 @@ get_nth_file_url(const char *uri_list, size_t uri_list_sz, int n, char **path_ou
*query_or_fragment_start = 0; *query_or_fragment_start = 0;
url_decode_inplace(path); url_decode_inplace(path);
if (path[0] != '/') { *error_out = "EINVAL"; return false; } if (path[0] != '/') { *error_out = "EINVAL"; return false; }
*path_out = strdup(path);
char resolved[PATH_MAX];
if (!realpath(path, resolved)) {
switch (errno) {
case ENOENT: case ENOTDIR: case ELOOP: *error_out = "ENOENT"; break;
case EACCES: case EPERM: *error_out = "EPERM"; break;
default: *error_out = "EINVAL"; break;
}
return false;
}
*path_out = strdup(resolved);
if (!*path_out) { *error_out = "ENOMEM"; return false; } if (!*path_out) { *error_out = "ENOMEM"; return false; }
return true; return true;
} }
@@ -949,6 +938,16 @@ drop_send_dir_listing(Window *w, const char *path) {
queue_payload_to_child(w->id, w->drop.client_id, &w->drop.pending, hdr, hdr_sz, NULL, 0, true); queue_payload_to_child(w->id, w->drop.client_id, &w->drop.pending, hdr, hdr_sz, NULL, 0, true);
} }
static void
drop_send_symlink(Window *w, const char *target, size_t sz) {
char hdr[128];
int hdr_sz = snprintf(hdr, sizeof(hdr), "\x1b]%d;t=r", DND_CODE);
hdr_sz += drop_append_request_keys(w, hdr + hdr_sz, sizeof(hdr) - hdr_sz);
hdr_sz += snprintf(hdr + hdr_sz, sizeof(hdr) - hdr_sz, ":X=1");
queue_payload_to_child(w->id, w->drop.client_id, &w->drop.pending, hdr, hdr_sz, target, sz, true);
queue_payload_to_child(w->id, w->drop.client_id, &w->drop.pending, hdr, hdr_sz, NULL, 0, true);
}
/* Send the file/directory at URI-list index idx. /* Send the file/directory at URI-list index idx.
* Returns true if completed synchronously, false if async file I/O started. */ * Returns true if completed synchronously, false if async file I/O started. */
static bool static bool
@@ -970,7 +969,7 @@ do_drop_request_uri_data(Window *w, int32_t mime_idx, int32_t file_idx) {
if (file_idx < 1) { drop_send_error(w, EINVAL); return true; } if (file_idx < 1) { drop_send_error(w, EINVAL); return true; }
int file_n = file_idx - 1; int file_n = file_idx - 1;
char *path = NULL; RAII_ALLOC(char, path, NULL);
const char *err = NULL; const char *err = NULL;
if (!get_nth_file_url(w->drop.uri_list, w->drop.uri_list_sz, file_n, &path, &err)) { if (!get_nth_file_url(w->drop.uri_list, w->drop.uri_list_sz, file_n, &path, &err)) {
drop_send_error_str(w, err); drop_send_error_str(w, err);
@@ -979,7 +978,7 @@ do_drop_request_uri_data(Window *w, int32_t mime_idx, int32_t file_idx) {
struct stat st; struct stat st;
if (stat(path, &st) < 0) { if (stat(path, &st) < 0) {
free(path); if (lstat(path, &st) < 0) {
switch (errno) { switch (errno) {
case ENOENT: case ENOTDIR: drop_send_error(w, ENOENT); break; case ENOENT: case ENOTDIR: drop_send_error(w, ENOENT); break;
case EACCES: case EPERM: drop_send_error(w, EPERM); break; case EACCES: case EPERM: drop_send_error(w, EPERM); break;
@@ -987,6 +986,12 @@ do_drop_request_uri_data(Window *w, int32_t mime_idx, int32_t file_idx) {
} }
return true; return true;
} }
// We have a broken symlink
char target[PATH_MAX]; ssize_t tgtsz;
if ((tgtsz = readlink(path, target, sizeof(target)-1)) < 0) drop_send_error(w, ENOENT);
drop_send_symlink(w, target, tgtsz);
return true;
}
bool sync; bool sync;
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
@@ -998,7 +1003,6 @@ do_drop_request_uri_data(Window *w, int32_t mime_idx, int32_t file_idx) {
drop_send_error(w, EINVAL); drop_send_error(w, EINVAL);
sync = true; sync = true;
} }
free(path);
return sync; return sync;
} }

View File

@@ -42,7 +42,7 @@ def create_fs(base):
f.write(b'x' * sz) f.write(b'x' * sz)
os.makedirs(join('d1', 'sd', 'ssd')) os.makedirs(join('d1', 'sd', 'ssd'))
os.mkdir(join('d2')) os.mkdir(join('d2'))
# os.symlink('/does-not-exist', join('s1')) os.symlink('/does-not-exist', join('s1'))
os.symlink('d1', join('sd')) os.symlink('d1', join('sd'))
os.symlink('/', join('sr')) os.symlink('/', join('sr'))
os.symlink('../d1', join('d1', 'sr')) os.symlink('../d1', join('d1', 'sr'))