mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-25 05:52:34 +00:00
fix(clipboard): reject non-png clipboard images when png normalization fails
This commit is contained in:
parent
c872f07c47
commit
8db544b4d0
2 changed files with 62 additions and 5 deletions
|
|
@ -22,6 +22,7 @@ from pathlib import Path
|
||||||
from hermes_constants import is_wsl as _is_wsl
|
from hermes_constants import is_wsl as _is_wsl
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
_PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
|
||||||
|
|
||||||
|
|
||||||
def save_clipboard_image(dest: Path) -> bool:
|
def save_clipboard_image(dest: Path) -> bool:
|
||||||
|
|
@ -378,10 +379,13 @@ def _wayland_save(dest: Path) -> bool:
|
||||||
dest.unlink(missing_ok=True)
|
dest.unlink(missing_ok=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# BMP needs conversion to PNG (common in WSLg where only BMP
|
# save_clipboard_image() promises a PNG output path. Wayland can offer
|
||||||
# is bridged from Windows clipboard via RDP).
|
# JPEG/GIF/WebP/BMP payloads, so normalize every non-PNG result before
|
||||||
if mime == "image/bmp":
|
# returning success.
|
||||||
return _convert_to_png(dest)
|
if mime != "image/png":
|
||||||
|
if not _convert_to_png(dest) or not _is_png_file(dest):
|
||||||
|
dest.unlink(missing_ok=True)
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -433,6 +437,14 @@ def _convert_to_png(path: Path) -> bool:
|
||||||
return path.exists() and path.stat().st_size > 0
|
return path.exists() and path.stat().st_size > 0
|
||||||
|
|
||||||
|
|
||||||
|
def _is_png_file(path: Path) -> bool:
|
||||||
|
"""Return True when *path* starts with the PNG file signature."""
|
||||||
|
try:
|
||||||
|
return path.read_bytes().startswith(_PNG_SIGNATURE)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# ── X11 (xclip) ─────────────────────────────────────────────────────────
|
# ── X11 (xclip) ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _xclip_has_image() -> bool:
|
def _xclip_has_image() -> bool:
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ from cli import _should_auto_attach_clipboard_image_on_paste
|
||||||
|
|
||||||
FAKE_PNG = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100
|
FAKE_PNG = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100
|
||||||
FAKE_BMP = b"BM" + b"\x00" * 100
|
FAKE_BMP = b"BM" + b"\x00" * 100
|
||||||
|
FAKE_JPEG = b"\xff\xd8\xff\xe0" + b"\x00" * 100
|
||||||
|
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
|
|
@ -393,9 +394,53 @@ class TestWaylandSave:
|
||||||
if "stdout" in kw and hasattr(kw["stdout"], "write"):
|
if "stdout" in kw and hasattr(kw["stdout"], "write"):
|
||||||
kw["stdout"].write(FAKE_BMP)
|
kw["stdout"].write(FAKE_BMP)
|
||||||
return MagicMock(returncode=0)
|
return MagicMock(returncode=0)
|
||||||
|
|
||||||
|
def fake_convert(path):
|
||||||
|
assert path == dest
|
||||||
|
path.write_bytes(FAKE_PNG)
|
||||||
|
return True
|
||||||
|
|
||||||
|
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
|
||||||
|
with patch("hermes_cli.clipboard._convert_to_png", side_effect=fake_convert):
|
||||||
|
assert _wayland_save(dest) is True
|
||||||
|
|
||||||
|
def test_jpeg_extraction_converts_to_real_png(self, tmp_path):
|
||||||
|
dest = tmp_path / "out.png"
|
||||||
|
|
||||||
|
def fake_run(cmd, **kw):
|
||||||
|
if "--list-types" in cmd:
|
||||||
|
return MagicMock(stdout="image/jpeg\ntext/plain\n", returncode=0)
|
||||||
|
if "stdout" in kw and hasattr(kw["stdout"], "write"):
|
||||||
|
kw["stdout"].write(FAKE_JPEG)
|
||||||
|
return MagicMock(returncode=0)
|
||||||
|
|
||||||
|
def fake_convert(path):
|
||||||
|
assert path == dest
|
||||||
|
path.write_bytes(FAKE_PNG)
|
||||||
|
return True
|
||||||
|
|
||||||
|
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
|
||||||
|
with patch("hermes_cli.clipboard._convert_to_png", side_effect=fake_convert) as mock_convert:
|
||||||
|
assert _wayland_save(dest) is True
|
||||||
|
|
||||||
|
mock_convert.assert_called_once_with(dest)
|
||||||
|
assert dest.read_bytes() == FAKE_PNG
|
||||||
|
|
||||||
|
def test_non_png_conversion_failure_cleans_up(self, tmp_path):
|
||||||
|
dest = tmp_path / "out.png"
|
||||||
|
|
||||||
|
def fake_run(cmd, **kw):
|
||||||
|
if "--list-types" in cmd:
|
||||||
|
return MagicMock(stdout="image/jpeg\n", returncode=0)
|
||||||
|
if "stdout" in kw and hasattr(kw["stdout"], "write"):
|
||||||
|
kw["stdout"].write(FAKE_JPEG)
|
||||||
|
return MagicMock(returncode=0)
|
||||||
|
|
||||||
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
|
with patch("hermes_cli.clipboard.subprocess.run", side_effect=fake_run):
|
||||||
with patch("hermes_cli.clipboard._convert_to_png", return_value=True):
|
with patch("hermes_cli.clipboard._convert_to_png", return_value=True):
|
||||||
assert _wayland_save(dest) is True
|
assert _wayland_save(dest) is False
|
||||||
|
|
||||||
|
assert not dest.exists()
|
||||||
|
|
||||||
def test_no_image_types(self, tmp_path):
|
def test_no_image_types(self, tmp_path):
|
||||||
dest = tmp_path / "out.png"
|
dest = tmp_path / "out.png"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue