fix(l4d2-web): normalize CRLF to LF in script overlay POST
HTML <textarea> form submission encodes line breaks as CRLF per spec.
Storing those CRLFs unchanged means every line of the script reaches
bash with a trailing \r, which bash treats as part of the argument —
turning "ls /" into "ls /\r" and failing. Normalize CRLF/CR → LF in the
/overlays/{id}/script handler so storage and the sandbox tmpfile are
LF-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
908bca3687
commit
a62f26ba4a
2 changed files with 22 additions and 1 deletions
|
|
@ -145,7 +145,11 @@ def _load_script_overlay(db, overlay_id: int, user) -> tuple[Overlay | None, Res
|
||||||
def update_script(overlay_id: int) -> Response:
|
def update_script(overlay_id: int) -> Response:
|
||||||
user = current_user()
|
user = current_user()
|
||||||
assert user is not None
|
assert user is not None
|
||||||
script_text = request.form.get("script", "")
|
# HTML form submission of <textarea> uses CRLF line endings per spec; bash
|
||||||
|
# treats the trailing \r as part of each argument and breaks every command.
|
||||||
|
# Normalize to LF before storage so the script is well-formed when written
|
||||||
|
# to the sandbox tmpfile.
|
||||||
|
script_text = request.form.get("script", "").replace("\r\n", "\n").replace("\r", "\n")
|
||||||
with session_scope() as db:
|
with session_scope() as db:
|
||||||
overlay, err = _load_script_overlay(db, overlay_id, user)
|
overlay, err = _load_script_overlay(db, overlay_id, user)
|
||||||
if err is not None:
|
if err is not None:
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,23 @@ def test_update_script_body_enqueues_build(app, alice_id) -> None:
|
||||||
assert len(jobs) == 1
|
assert len(jobs) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_script_normalizes_crlf_to_lf(app, alice_id) -> None:
|
||||||
|
"""HTML <textarea> submits CRLF line endings; bash chokes on trailing \\r
|
||||||
|
in every command. Storage must be LF-only so the sandbox tmpfile is
|
||||||
|
well-formed."""
|
||||||
|
overlay_id = _create_script_overlay(app, alice_id)
|
||||||
|
client = _client_for(app, alice_id)
|
||||||
|
client.post(
|
||||||
|
f"/overlays/{overlay_id}/script",
|
||||||
|
data={"script": "ls /\r\necho hello\r\n"},
|
||||||
|
headers={"X-CSRF-Token": "test-token"},
|
||||||
|
)
|
||||||
|
with session_scope() as s:
|
||||||
|
overlay = s.query(Overlay).filter_by(id=overlay_id).one()
|
||||||
|
assert overlay.script == "ls /\necho hello\n"
|
||||||
|
assert "\r" not in overlay.script
|
||||||
|
|
||||||
|
|
||||||
def test_manual_rebuild(app, alice_id) -> None:
|
def test_manual_rebuild(app, alice_id) -> None:
|
||||||
overlay_id = _create_script_overlay(app, alice_id)
|
overlay_id = _create_script_overlay(app, alice_id)
|
||||||
client = _client_for(app, alice_id)
|
client = _client_for(app, alice_id)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue