diff --git a/kitty/boss.py b/kitty/boss.py index d72daf53d..829832432 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -3716,28 +3716,3 @@ class Boss: def copy_or_noop(self) -> None: if w := self.active_window: w.copy_or_noop() - - def nth_decoded_file_url(self, n: int, text_uri_list: bytes) -> tuple[bool, str]: - ' Return the abspath of the nth url in text_uri_list if it is a valid file:// URL. Appropriate error code returned otherwise ' - from urllib.parse import unquote, urlparse - for line in text_uri_list.decode().splitlines(): - if line.startswith('#'): - continue - if n <= 0: - break - n -= 1 - else: - return False, 'ENOENT' - try: - purl = urlparse(line.strip()) - if purl.scheme != 'file': - return False, 'EUNKNOWN' - if not purl.path: - return False, 'EINVAL' - path = unquote(purl.path) - if not os.path.isabs(path): - return False, 'EINVAL' - path = os.path.abspath(os.path.realpath(path)) - return True, path - except Exception: - return False, 'EINVAL' diff --git a/kitty/dnd.c b/kitty/dnd.c index fcace2c41..6df7852fd 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -8,6 +8,7 @@ #include "dnd.h" #include "base64.h" #include "control-codes.h" +#include "safe-wrappers.h" #include "iqsort.h" #include #include @@ -516,7 +517,7 @@ drop_send_file_data(Window *w, const char *path) { } if (!S_ISREG(st.st_mode)) { drop_send_error(w, EINVAL); return; } - int fd = open(path, O_RDONLY | O_CLOEXEC); + int fd = safe_open(path, O_RDONLY | O_CLOEXEC, 0); if (fd < 0) { switch (errno) { case ENOENT: case ENOTDIR: drop_send_error(w, ENOENT); break; @@ -530,20 +531,20 @@ drop_send_file_data(Window *w, const char *path) { char *data = NULL; if (data_sz) { data = malloc(data_sz); - if (!data) { close(fd); drop_send_error(w, EIO); return; } + if (!data) { safe_close(fd, __FILE__, __LINE__); drop_send_error(w, EIO); return; } size_t done = 0; while (done < data_sz) { ssize_t n = read(fd, data + done, data_sz - done); if (n < 0) { if (errno == EINTR) continue; - free(data); close(fd); drop_send_error(w, EIO); return; + free(data); safe_close(fd, __FILE__, __LINE__); drop_send_error(w, EIO); return; } if (n == 0) break; done += (size_t)n; } data_sz = done; } - close(fd); + safe_close(fd, __FILE__, __LINE__); char hdr[128]; int hdr_sz = snprintf(hdr, sizeof(hdr), "\x1b]%d;t=r", DND_CODE); diff --git a/kitty_tests/dnd.py b/kitty_tests/dnd.py index 3c11ff6f6..a2792ba4c 100644 --- a/kitty_tests/dnd.py +++ b/kitty_tests/dnd.py @@ -514,7 +514,8 @@ class TestDnDProtocol(BaseTest): def test_uri_file_transfer_basic(self) -> None: """t=s request sends the content of a regular file as t=r chunks.""" - import os, tempfile + import os + import tempfile content = b'Hello, remote DnD world!\n' * 100 with tempfile.NamedTemporaryFile(delete=False) as f: f.write(content) @@ -537,7 +538,8 @@ class TestDnDProtocol(BaseTest): def test_uri_file_transfer_integrity(self) -> None: """File content is transferred byte-for-byte (binary integrity).""" - import os, tempfile + import os + import tempfile # Use binary content with all byte values to check integrity content = bytes(range(256)) * 512 # 128 KiB with tempfile.NamedTemporaryFile(delete=False) as f: @@ -571,7 +573,8 @@ class TestDnDProtocol(BaseTest): def test_uri_file_transfer_out_of_bounds(self) -> None: """t=s with an index beyond the URI list returns ENOENT.""" - import os, tempfile + import os + import tempfile with tempfile.NamedTemporaryFile(delete=False) as f: fpath = f.name try: @@ -588,7 +591,8 @@ class TestDnDProtocol(BaseTest): def test_uri_request_without_uri_list_returns_einval(self) -> None: """t=s without prior text/uri-list request returns EINVAL.""" - import os, tempfile + import os + import tempfile with tempfile.NamedTemporaryFile(delete=False) as f: fpath = f.name try: @@ -619,7 +623,9 @@ class TestDnDProtocol(BaseTest): def test_uri_directory_transfer_tree(self) -> None: """Full directory tree transfer: listing, sub-dirs, file integrity.""" - import os, tempfile, hashlib + import hashlib + import os + import tempfile # Build a tree: root/ a.txt b/ b/c.txt b/d/ b/d/e.txt with tempfile.TemporaryDirectory() as root: @@ -739,7 +745,8 @@ class TestDnDProtocol(BaseTest): def test_dir_handle_close_and_reuse(self) -> None: """Closing a directory handle invalidates it; subsequent requests return EINVAL.""" - import os, tempfile + import os + import tempfile with tempfile.TemporaryDirectory() as root: open(os.path.join(root, 'f.txt'), 'w').close() uri_list = f'file://{root}\r\n'.encode() @@ -765,7 +772,8 @@ class TestDnDProtocol(BaseTest): def test_dir_entry_out_of_bounds_returns_enoent(self) -> None: """Reading a directory entry with an out-of-range index returns ENOENT.""" - import os, tempfile + import os + import tempfile with tempfile.TemporaryDirectory() as root: open(os.path.join(root, 'only.txt'), 'w').close() uri_list = f'file://{root}\r\n'.encode() @@ -786,7 +794,8 @@ class TestDnDProtocol(BaseTest): def test_dir_unique_identifier_prevents_loops(self) -> None: """Each directory listing starts with a unique id (dev:inode format).""" - import os, tempfile + import os + import tempfile with tempfile.TemporaryDirectory() as root: sub = os.path.join(root, 'sub') os.mkdir(sub) @@ -822,7 +831,8 @@ class TestDnDProtocol(BaseTest): def test_window_close_during_transfer_no_leak(self) -> None: """Closing the window while dir handles are open frees all resources (no crash).""" - import os, tempfile + import os + import tempfile with tempfile.TemporaryDirectory() as root: open(os.path.join(root, 'f.txt'), 'w').close() uri_list = f'file://{root}\r\n'.encode()