This is an automated email from the ASF dual-hosted git repository.
sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
The following commit(s) were added to refs/heads/sbp by this push:
new 5d3140b0 Fix the encoding of JSON data in the form to move files
5d3140b0 is described below
commit 5d3140b086f566f0b1555f3c18242396a29072fd
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Feb 20 16:24:41 2026 +0000
Fix the encoding of JSON data in the form to move files
---
atr/get/finish.py | 5 ++---
atr/util.py | 6 ++++++
tests/e2e/announce/conftest.py | 10 ++++++++++
tests/e2e/announce/test_get.py | 13 +++++++++++++
tests/unit/test_util.py | 13 +++++++++++++
5 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/atr/get/finish.py b/atr/get/finish.py
index 61fabc75..39d36907 100644
--- a/atr/get/finish.py
+++ b/atr/get/finish.py
@@ -17,7 +17,6 @@
import dataclasses
-import json
import pathlib
from collections.abc import Sequence
@@ -440,9 +439,9 @@ async def _render_page(
safe_source_files_rel = [util.validate_path(f).as_posix() for f in
sorted(source_files_rel)]
safe_target_dirs = [util.validate_path(d).as_posix() for d in
sorted(target_dirs)]
page.append(
- htpy.script(id="file-data",
type="application/json")[markupsafe.escape(json.dumps(safe_source_files_rel))]
+ htpy.script(id="file-data",
type="application/json")[util.json_for_script_element(safe_source_files_rel)]
)
- page.append(htpy.script(id="dir-data",
type="application/json")[markupsafe.escape(json.dumps(safe_target_dirs))])
+ page.append(htpy.script(id="dir-data",
type="application/json")[util.json_for_script_element(safe_target_dirs)])
page.append(
htpy.script(
id="main-script-data",
diff --git a/atr/util.py b/atr/util.py
index 3730c62b..1c30d07b 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -44,6 +44,7 @@ import asfquart.base as base
import asfquart.session as session
import gitignore_parser
import jinja2
+import markupsafe
import quart
# NOTE: The atr.db module imports this module
@@ -139,6 +140,11 @@ def as_url(func: Callable, **kwargs: Any) -> str:
return quart.url_for(annotations["endpoint"], **kwargs)
+def json_for_script_element(value: Any) -> markupsafe.Markup:
+ """Serialise JSON safely for use inside a script element."""
+ return jinja2.utils.htmlsafe_json_dumps(value, dumps=json.dumps,
ensure_ascii=False)
+
+
def asf_uid_from_email(email: str) -> str | None:
ldap_params = ldap.SearchParameters(email_query=email)
ldap.search(ldap_params)
diff --git a/tests/e2e/announce/conftest.py b/tests/e2e/announce/conftest.py
index 33f3f7cd..71a14105 100644
--- a/tests/e2e/announce/conftest.py
+++ b/tests/e2e/announce/conftest.py
@@ -35,6 +35,7 @@ VERSION_NAME: Final[str] = "0.1+e2e-announce"
FILE_NAME: Final[str] = "apache-test-0.2.tar.gz"
CURRENT_DIR: Final[pathlib.Path] = pathlib.Path(__file__).parent.resolve()
ANNOUNCE_URL: Final[str] = f"/announce/{PROJECT_NAME}/{VERSION_NAME}"
+FINISH_URL: Final[str] = f"/finish/{PROJECT_NAME}/{VERSION_NAME}"
@pytest.fixture(scope="module")
@@ -104,6 +105,15 @@ def page_announce(announce_context: BrowserContext) ->
Generator[Page]:
page.close()
[email protected]
+def page_finish(announce_context: BrowserContext) -> Generator[Page]:
+ """Navigate to the finish page with a fresh page for each test."""
+ page = announce_context.new_page()
+ helpers.visit(page, FINISH_URL)
+ yield page
+ page.close()
+
+
def _poll_for_vote_thread_link(page: Page, max_attempts: int = 30) -> None:
"""Poll for the vote task to be completed."""
thread_link_locator = page.locator('a:has-text("view thread")')
diff --git a/tests/e2e/announce/test_get.py b/tests/e2e/announce/test_get.py
index 56cf8000..3390fa5b 100644
--- a/tests/e2e/announce/test_get.py
+++ b/tests/e2e/announce/test_get.py
@@ -82,3 +82,16 @@ def
test_submit_button_disabled_until_confirm_typed(page_announce: Page) -> None
confirm_input.fill("CONFIRM")
expect(submit_button).to_be_enabled()
+
+
+def test_finish_move_form_populates_from_json(page_finish: Page) -> None:
+ """The finish move form should populate rows from script JSON data."""
+ file_option = page_finish.locator("#file-list-table-body
input[type='checkbox'][data-item-path]").first
+ dir_option = page_finish.locator("#dir-list-table-body
input[type='radio'][name='target-directory-radio']").first
+ expect(file_option).to_be_visible()
+ expect(dir_option).to_be_visible()
+
+ toggle_button = page_finish.locator("#select-files-toggle-button")
+ expect(toggle_button).to_have_text("Select these files")
+ toggle_button.click()
+ expect(toggle_button).to_have_text("Unselect all")
diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py
index 704f402d..454350c9 100644
--- a/tests/unit/test_util.py
+++ b/tests/unit/test_util.py
@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
+import json
import os
import pathlib
import stat
@@ -93,3 +94,15 @@ def test_chmod_files_sets_default_permissions(tmp_path:
pathlib.Path):
file_mode = stat.S_IMODE(test_file.stat().st_mode)
assert file_mode == 0o444
+
+
+def test_json_for_script_element_escapes_correctly():
+ payload = ["example.txt", "</script><script>alert(1)</script>",
"apple&banana"]
+
+ serialized = util.json_for_script_element(payload)
+
+ assert "</script>" not in serialized
+ assert "<script>" not in serialized
+ assert "apple&banana" not in serialized
+ assert "apple\\u0026banana" in serialized
+ assert json.loads(serialized) == payload
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]