Stage 368: PR #2382 — fix: serve raw chat attachments from inbox by @Michaelyklam
This commit is contained in:
commit
57199f16ec
3 files changed
+59
-4
No files matched your search
+26
-2
@@ -6360,10 +6360,34 @@ def _handle_media(handler, parsed):
|
||||
or html_inline_ok
|
||||
)
|
||||
) else "attachment"
|
||||
# _serve_file_bytes sends Content-Security-Policy when csp is set.
|
||||
csp = "sandbox allow-scripts" if html_inline_ok else None
|
||||
return _serve_file_bytes(handler, target, mime, disposition, "private, max-age=3600", csp=csp)
|
||||
|
||||
|
||||
def _file_raw_target(session, sid: str, rel: str) -> Path | None:
|
||||
"""Resolve /api/file/raw paths from the workspace or this session's uploads."""
|
||||
try:
|
||||
target = safe_resolve(Path(session.workspace), rel)
|
||||
except ValueError:
|
||||
target = None
|
||||
if target and target.exists() and target.is_file():
|
||||
return target
|
||||
|
||||
# Chat uploads now live in a per-session attachment inbox outside the
|
||||
# workspace. Keep the public URL stable while scoping fallback lookup to
|
||||
# the requesting session's own attachment directory.
|
||||
try:
|
||||
from api.upload import _session_attachment_dir
|
||||
|
||||
attachment_target = safe_resolve(_session_attachment_dir(sid), rel)
|
||||
except Exception:
|
||||
return None
|
||||
if attachment_target.exists() and attachment_target.is_file():
|
||||
return attachment_target
|
||||
return None
|
||||
|
||||
|
||||
def _handle_file_raw(handler, parsed):
|
||||
qs = parse_qs(parsed.query)
|
||||
sid = qs.get("session_id", [""])[0]
|
||||
@@ -6375,8 +6399,8 @@ def _handle_file_raw(handler, parsed):
|
||||
return bad(handler, "Session not found", 404)
|
||||
rel = qs.get("path", [""])[0]
|
||||
force_download = qs.get("download", [""])[0] == "1"
|
||||
target = safe_resolve(Path(s.workspace), rel)
|
||||
if not target.exists() or not target.is_file():
|
||||
target = _file_raw_target(s, sid, rel)
|
||||
if target is None:
|
||||
return j(handler, {"error": "not found"}, status=404)
|
||||
ext = target.suffix.lower()
|
||||
mime = MIME_MAP.get(ext, "application/octet-stream")
|
||||
|
||||
@@ -67,7 +67,7 @@ def test_raw_endpoint_path_traversal_blocked(cleanup_test_sessions):
|
||||
get_raw(f"/api/file/raw?session_id={sid}&path=../../etc/passwd")
|
||||
assert False
|
||||
except urllib.error.HTTPError as e:
|
||||
assert e.code in (400, 500)
|
||||
assert e.code in (400, 404, 500)
|
||||
|
||||
def test_raw_endpoint_missing_file_returns_404(cleanup_test_sessions):
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
|
||||
+32
-1
@@ -1,5 +1,5 @@
|
||||
"""Sprint 6 tests: Escape from editor, Phase D validation, HTML extraction, cron create, session export."""
|
||||
import json, uuid, pathlib, urllib.request, urllib.error
|
||||
import json, uuid, pathlib, urllib.parse, urllib.request, urllib.error
|
||||
REPO_ROOT = pathlib.Path(__file__).parent.parent.resolve()
|
||||
|
||||
from tests._pytest_port import BASE
|
||||
@@ -74,6 +74,37 @@ def test_file_raw_unknown_session():
|
||||
except urllib.error.HTTPError as e:
|
||||
assert e.code == 404
|
||||
|
||||
def test_file_raw_serves_session_attachment_inbox(cleanup_test_sessions):
|
||||
from api.upload import _session_attachment_dir
|
||||
|
||||
sid, workspace = make_session_tracked(cleanup_test_sessions)
|
||||
filename = f"uploaded-chat-image-{uuid.uuid4().hex}.png"
|
||||
attachment_dir = _session_attachment_dir(sid)
|
||||
attachment_dir.mkdir(parents=True, exist_ok=True)
|
||||
payload = b"fake-png-bytes"
|
||||
(attachment_dir / filename).write_bytes(payload)
|
||||
|
||||
assert not (workspace / filename).exists(), "regression must exercise attachment fallback"
|
||||
raw, headers, status = get_raw(
|
||||
f"/api/file/raw?session_id={sid}&path={urllib.parse.quote(filename)}"
|
||||
)
|
||||
assert status == 200
|
||||
assert raw == payload
|
||||
assert "image/png" in headers.get("Content-Type", "")
|
||||
|
||||
def test_file_raw_attachment_fallback_rejects_traversal(cleanup_test_sessions):
|
||||
from api.upload import _session_attachment_dir
|
||||
|
||||
sid, _ = make_session_tracked(cleanup_test_sessions)
|
||||
attachment_dir = _session_attachment_dir(sid)
|
||||
attachment_dir.mkdir(parents=True, exist_ok=True)
|
||||
(attachment_dir / "safe.txt").write_text("safe", encoding="utf-8")
|
||||
try:
|
||||
get_raw(f"/api/file/raw?session_id={sid}&path={urllib.parse.quote('../../safe.txt')}")
|
||||
assert False, "Expected 404"
|
||||
except urllib.error.HTTPError as e:
|
||||
assert e.code == 404
|
||||
|
||||
# ── Cron create ──
|
||||
|
||||
def test_cron_create_requires_prompt():
|
||||
|
||||
Reference in new issue
Block a user