🐛 fix(core): reject invalid LSP framing
This commit is contained in:
@@ -18,6 +18,15 @@ def raw_frame(body: bytes) -> bytes:
|
||||
return f"Content-Length: {len(body)}\r\n\r\n".encode("ascii") + body
|
||||
|
||||
|
||||
def content_type_frame(message: dict) -> bytes:
|
||||
body = json.dumps(message, separators=(",", ":")).encode("utf-8")
|
||||
header = (
|
||||
f"Content-Length: {len(body)}\r\n"
|
||||
"Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n"
|
||||
).encode("ascii")
|
||||
return header + body
|
||||
|
||||
|
||||
def read_messages(data: bytes) -> list[dict]:
|
||||
messages = []
|
||||
offset = 0
|
||||
@@ -294,6 +303,43 @@ def assert_cancellation_and_failures(server: Path) -> None:
|
||||
client.kill()
|
||||
|
||||
|
||||
def assert_framing(server: Path) -> None:
|
||||
invalid_payloads = [
|
||||
b"Content-Length: 2junk\r\n\r\n{}",
|
||||
b"Content-Length: 2\r\nContent-Length: 2\r\n\r\n{}",
|
||||
b"Content-Length: 0\r\n\r\n",
|
||||
b"Content-Length: 16777217\r\n\r\n",
|
||||
b"Content-Length: 10\r\n\r\n{}",
|
||||
b"Content-Length: 2\n\n{}",
|
||||
b"X-Length: 2\r\n\r\n{}",
|
||||
b"Content-Length: 2\r\nContent-Type: application/json\r\n\r\n{}",
|
||||
]
|
||||
for index, payload in enumerate(invalid_payloads):
|
||||
result = run_raw(server, payload)
|
||||
if result.returncode != 1:
|
||||
raise RuntimeError(f"invalid framing case {index} should return code 1")
|
||||
if result.stdout:
|
||||
raise RuntimeError(f"invalid framing case {index} should not process its body")
|
||||
|
||||
valid_payload = b"".join([
|
||||
content_type_frame({
|
||||
"jsonrpc": "2.0",
|
||||
"id": 31,
|
||||
"method": "initialize",
|
||||
"params": {},
|
||||
}),
|
||||
frame({"jsonrpc": "2.0", "id": 32, "method": "shutdown"}),
|
||||
frame({"jsonrpc": "2.0", "method": "exit"}),
|
||||
])
|
||||
valid = run_raw(server, valid_payload)
|
||||
if valid.returncode != 0:
|
||||
raise RuntimeError("canonical Content-Type should be accepted")
|
||||
if response_by_id(valid.stdout, 31).get("error"):
|
||||
raise RuntimeError("initialize with canonical Content-Type should succeed")
|
||||
if response_by_id(valid.stdout, 32).get("result", "missing") is not None:
|
||||
raise RuntimeError("continuous framing should reach shutdown")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--server", type=Path, required=True)
|
||||
@@ -302,6 +348,7 @@ def main() -> int:
|
||||
assert_lifecycle(args.server)
|
||||
assert_json_rpc_errors(args.server)
|
||||
assert_cancellation_and_failures(args.server)
|
||||
assert_framing(args.server)
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user