Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1001,14 +1001,26 @@ def _refs_get_content(self, ref_id: str) -> None:
self._json({"error": str(e) or "blocked"}, 403)
return
etag = forge_refs._compute_etag(data)
# Non-ASCII (CJK) header values crash http.server on send; percent-
# encode label + source_agent so the response actually reaches the
# client. Without this, python's BaseHTTPRequestHandler raises
# UnicodeEncodeError writing headers and drops the connection
# (curl exit 52 / "Empty reply from server").
def _ascii(v: str) -> str:
from urllib.parse import quote
try:
v.encode("latin-1")
return v
except UnicodeEncodeError:
return quote(v, safe="")
self.send_response(200)
self.send_header("Content-Type", mime)
self.send_header("Content-Length", str(len(data)))
self.send_header("Cache-Control", "no-store")
self.send_header("ETag", etag)
self.send_header("X-Ref-Etag", etag)
self.send_header("X-Ref-Label", ref.get("label", ""))
self.send_header("X-Ref-Source-Agent", ref.get("source_agent", ""))
self.send_header("X-Ref-Label", _ascii(ref.get("label", "")))
self.send_header("X-Ref-Source-Agent", _ascii(ref.get("source_agent", "")))
self.send_header("X-Ref-Id", ref.get("id", ""))
self.end_headers()
self.wfile.write(data)
Expand Down
19 changes: 19 additions & 0 deletions tests/test_refs.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,25 @@ def test_http_content_round_trip(server, tmp_path):
assert headers.get("X-Ref-Source-Agent") == "milk"


def test_http_content_cjk_label_does_not_crash(server, tmp_path):
# Regression: labels with CJK / non-latin-1 chars used to blow up
# BaseHTTPRequestHandler when writing X-Ref-Label, killing the
# connection (curl exit 52 / "Empty reply from server").
from urllib.parse import quote
base = server
note = tmp_path / "SWAT-\u4f7f\u7528\u6307\u5357.md"
note.write_text("# 你好\n", encoding="utf-8")
_, body, _ = _call("POST", f"{base}/api/refs", {
"label": note.name, "abs_path": str(note), "source_agent": "milk",
})
rid = body["id"]
status, body, headers = _call("GET", f"{base}/api/refs/{rid}/content")
assert status == 200
assert "你好" in (body if isinstance(body, str) else body.decode("utf-8"))
# Header value must be latin-1-safe; ours is percent-encoded.
assert headers.get("X-Ref-Label") == quote(note.name, safe="")


def test_http_put_content_non_md_400(server, tmp_path):
# v1: non-md rejected at validation layer -> 400.
base = server
Expand Down
Loading