fix(task_execute): lotto/seriale in |tojson invece di letterale JS (#32)
lotNumber/serialNumber erano '{{ lot_number or '' }}': autoescape HTML dentro
uno <script>, non escaping JS/JSON. '&' diventava '&' e finiva salvato nel
DB a ogni misura e a production/start; un lotto con backslash rompeva la
sintassi JS della pagina. |tojson produce un letterale JSON valido sia come JS
che come HTML, come già fa refuseWhileBlocked() più sotto nello stesso file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"""gitea:Adriano/TieMeasureFlow#32.
|
||||
|
||||
lotNumber/serialNumber used to be interpolated into task_execute.html's
|
||||
<script> block as a quoted JS string literal (``'{{ lot_number or '' }}'``),
|
||||
which relies on Jinja's *HTML* autoescaping - the wrong escaping for a value
|
||||
that has to be valid *inside a <script> block*. A lot number containing ``&``
|
||||
came back HTML-entity-escaped (``A&B-12``) instead of the literal
|
||||
character, silently corrupting the value sent to the server on every
|
||||
measurement and on production/start. A lot ending in a backslash produced
|
||||
invalid JavaScript outright.
|
||||
|
||||
``|tojson`` is the fix: it produces a JSON literal, safe both as JS source
|
||||
and as HTML, matching how every other user-supplied value on this page is
|
||||
already embedded (see ``refuseWhileBlocked()``'s comment on the same file).
|
||||
"""
|
||||
from tests.conftest import api_get_router
|
||||
|
||||
TASK = {
|
||||
"id": 11,
|
||||
"recipe_id": 3,
|
||||
"version_id": 5,
|
||||
"task_type": "measure",
|
||||
"title": "Quota",
|
||||
"directive": None,
|
||||
"description": None,
|
||||
"file_path": None,
|
||||
"file_type": None,
|
||||
"annotations_json": None,
|
||||
"subtasks": [{
|
||||
"id": 21, "marker_number": 1, "description": "Altezza", "order_index": 0,
|
||||
"nominal": 10.0, "utl": 10.5, "uwl": 10.2, "lwl": 9.8, "ltl": 9.5,
|
||||
"unit": "mm",
|
||||
}],
|
||||
}
|
||||
|
||||
RECIPE = {
|
||||
"id": 3, "code": "REC-1", "name": "Ricetta",
|
||||
"measurement_interval_minutes": 30, "allow_manual_input": True,
|
||||
"current_version": {"id": 5, "version_number": 1},
|
||||
}
|
||||
|
||||
|
||||
def _execute_page(client, mock_api_client, monkeypatch, lot_number, serial_number):
|
||||
with client.session_transaction() as sess:
|
||||
sess["language"] = "it"
|
||||
sess["lot_number"] = lot_number
|
||||
sess["serial_number"] = serial_number
|
||||
# task_execute() only renders a recipe assigned to this station (#24);
|
||||
# not what #32 is about, but the route 403s without it.
|
||||
monkeypatch.setattr("blueprints.measure.Config.STATION_CODE", "ST-TEST")
|
||||
mock_api_client.get_station_recipes.return_value = {"items": [{"id": 3}]}
|
||||
mock_api_client.get.side_effect = api_get_router({
|
||||
"/api/tasks/11": TASK,
|
||||
"/api/recipes/3/tasks": [{"id": 11, "order_index": 0}],
|
||||
"/api/recipes/3": RECIPE,
|
||||
})
|
||||
resp = client.get("/measure/execute/11")
|
||||
assert resp.status_code == 200
|
||||
return resp.get_data(as_text=True)
|
||||
|
||||
|
||||
def test_ampersand_in_lot_number_is_not_html_escaped(logged_in_client, mock_api_client, monkeypatch):
|
||||
html = _execute_page(logged_in_client, mock_api_client, monkeypatch, "A&B-12", "S1")
|
||||
|
||||
# The old bug, scoped to the <script> data block: the JS literal for
|
||||
# lotNumber must not carry HTML-entity escaping (that is correct for the
|
||||
# plain-text display of the same value elsewhere on the page, but breaks
|
||||
# the value actually sent to the server on every measurement).
|
||||
assert 'lotNumber: "A&B-12"' not in html
|
||||
# tojson's escaping for "&" is the \uXXXX form.
|
||||
assert 'lotNumber: "A\\u0026B-12"' in html
|
||||
|
||||
|
||||
def test_backslash_in_serial_number_stays_valid_json(logged_in_client, mock_api_client, monkeypatch):
|
||||
# The old literal '{{ serial_number or '' }}' would end with a bare
|
||||
# backslash escaping the closing quote - a SyntaxError, not merely a
|
||||
# wrong value. tojson escapes it instead.
|
||||
html = _execute_page(logged_in_client, mock_api_client, monkeypatch, "L1", "LOT\\")
|
||||
|
||||
assert 'serialNumber: "LOT\\\\"' in html
|
||||
|
||||
|
||||
def test_apostrophe_in_lot_number_stays_a_single_json_string(logged_in_client, mock_api_client, monkeypatch):
|
||||
# The old literal '{{ lot_number or '' }}' would have closed its own
|
||||
# quoted string early on the apostrophe. tojson escapes it to ',
|
||||
# which the JS engine reads back as the same original character.
|
||||
html = _execute_page(logged_in_client, mock_api_client, monkeypatch, "O'Brien-7", "S2")
|
||||
|
||||
assert 'lotNumber: "O\\u0027Brien-7"' in html
|
||||
Reference in New Issue
Block a user