diff --git a/scripts/measurement_records.py b/scripts/measurement_records.py index f61185331..f82bb9b84 100644 --- a/scripts/measurement_records.py +++ b/scripts/measurement_records.py @@ -89,6 +89,8 @@ def records_path(emulator: str) -> str: FINDINGS = "findings" CAMPAIGN_RUNS = "campaign-runs" PACK_PROBES = "pack-probes" +# The subject of every core_probe record a pack probe writes. +PACK_SUBJECT_PREFIX = "pack-" CAMPAIGN_FILES = { FINDINGS: ("found_at", "campaign-finding.schema.json"), CAMPAIGN_RUNS: ("started_at", "campaign-run.schema.json"), diff --git a/scripts/pack_probe.py b/scripts/pack_probe.py index 59cef3c2e..cfac94b34 100644 --- a/scripts/pack_probe.py +++ b/scripts/pack_probe.py @@ -514,7 +514,8 @@ def probe_argv(core: dict, choice: CoreChoice, content: campaign.Content, load: def subject_for(platform: str, install: str, system: str, so: str, content: str) -> str: - return mr.slugify(f"pack {platform} {install} {system} {so} {content}", 90) + subject = mr.slugify(f"{platform} {install} {system} {so} {content}", 85) + return f"{mr.PACK_SUBJECT_PREFIX}{subject}" def probe_one(layout: Layout, platform: str, config: dict, profile: dict, choice: CoreChoice, diff --git a/scripts/sitemarkup.py b/scripts/sitemarkup.py index 2660f04b9..cbd995606 100644 --- a/scripts/sitemarkup.py +++ b/scripts/sitemarkup.py @@ -75,11 +75,25 @@ def inline_html(text: str) -> str: continue part = html.escape(part, quote=False) part = re.sub(r"\*\*([^*]+)\*\*", r"\1", part) - part = re.sub(r"\[([^\]]+)\]\(([^)\s]+)\)", r'\1', part) + part = re.sub(r"\[([^\]]+)\]\(([^)\s]+)\)", _link, part) out.append(part) return "".join(out) +# A scheme before any slash: javascript:, data:, vbscript: and the like. +_SCHEME = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:") + + +def _link(match: re.Match) -> str: + """A Markdown link as an anchor when its target is http(s) or relative, + else left as the text it was. The target is already escaped for text, + quotes are escaped for the attribute.""" + label, href = match.group(1), match.group(2) + if _SCHEME.match(href) and not re.match(r"^https?://", href, re.IGNORECASE): + return match.group(0) + return f'{label}' + + def _indented(lines: list[str], pad: str) -> list[str]: return [f"{pad}{line}" if line.strip() else "" for line in lines] diff --git a/scripts/sitemeasurements.py b/scripts/sitemeasurements.py index c4cb244e7..af3cd6fef 100644 --- a/scripts/sitemeasurements.py +++ b/scripts/sitemeasurements.py @@ -280,12 +280,22 @@ def packs_section(items: list[dict]) -> list[str]: ] +def _inert(value: object) -> str: + """Run output (a core's log line, a file name it opened) placed in raw + HTML as text: every character HTML, MDX or Markdown reads as markup, + quotes included, written as a character reference.""" + return _plain(value).replace('"', """).replace("'", "'") + + def _pack_details(item: dict) -> list[str]: + def line(label: str, value: object) -> str: + return _sentence_html(label, _inert(value), markup_ready=True) + lines = [ - _sentence_html("Result", item["detail"]), - _sentence_html("Core", f"{item['core']} ({item['emulator']})"), - _sentence_html("Files the core opened", "; ".join(item["files"]) or "none"), - _sentence_html("Findings", "; ".join(item["finding_list"]) or "none"), + line("Result", item["detail"]), + line("Core", f"{item['core']} ({item['emulator']})"), + line("Files the core opened", "; ".join(item["files"]) or "none"), + line("Findings", "; ".join(item["finding_list"]) or "none"), f'

Full record.

' if item["record_url"] else "", ] return [line for line in lines if line] @@ -308,8 +318,8 @@ def pack_tested_section(items: list[dict]) -> list[str]: '"] for item in sorted(items, key=lambda i: (i["system"], i["content"], i["core"], i["install"])): - title = (f"{_cell(item['system'])}, {_cell(short_content(item['content']))} " - f"{html.escape(item['core'])}") + title = (f"{_inert(item['system'])}, {_inert(short_content(item['content']))} " + f"{_inert(item['core'])}") lines += [ '
', ("" @@ -646,6 +656,9 @@ def by_emulator(index: dict) -> dict[str, list[dict]]: opened[line["emulator"]] = opened.get(line["emulator"], 0) + 1 out: dict[str, list[dict]] = {} for item in index.get("items", []): + # A pack probe's run is shown on its platform's page, beside the pack. + if item["subject"].startswith(mr.PACK_SUBJECT_PREFIX): + continue out.setdefault(item["emulator"], []).append( {**item, "open_findings": opened.get(item["emulator"], 0)}) return out diff --git a/tests/test_pack_probe.py b/tests/test_pack_probe.py index 6556e49c4..592e8933f 100644 --- a/tests/test_pack_probe.py +++ b/tests/test_pack_probe.py @@ -249,10 +249,44 @@ class SitePages(unittest.TestCase): self.assertIn('works', text) self.assertIn('pack', text) - self.assertIn("sega-saturn, Game mednafen_saturn", text) - self.assertIn("Files the core opened: sega_101.bin (sega_101.bin).", text) - self.assertIn("Findings: missing_declared x.bin.", text) + self.assertIn("sega-saturn, Game mednafen_saturn", text) + self.assertIn("Files the core opened: sega_101.bin (sega_101.bin).", text) + self.assertIn("Findings: missing_declared x.bin.", text) self.assertNotIn("| retroarch |", text) + + def test_pack_runs_stay_off_the_emulator_page(self) -> None: + import sitemeasurements as sm + + subject = pp.subject_for("retroarch", "pack", "sega-saturn", "mednafen_saturn", "Game (Japan)") + self.assertEqual(subject, "pack-retroarch-pack-sega-saturn-mednafen-saturn-game-japan") + index = {"items": [{"emulator": "beetle_saturn", "subject": subject}, + {"emulator": "beetle_saturn", "subject": "boot-sega-101-bin"}]} + self.assertEqual([i["subject"] for i in sm.by_emulator(index)["beetle_saturn"]], + ["boot-sega-101-bin"]) + + def test_run_output_is_inert(self) -> None: + """A file name or log line from a third-party core reaches the page + as text: no tag, link, code span or MDX expression comes out of it.""" + import sitemarkup + import sitemeasurements as sm + + hostile = "`x` {alert(1)} [x](javascript:alert(1)) \"q\" 'q' **b**" + item = {"platform": "retroarch", "platform_version": "1", "install": "pack", + "system": hostile, "emulator": hostile, "core": hostile, "content": hostile, + "verdict": "boots", "date": "2026-10-28T00:00:00+00:00", "opened": 1, "findings": 1, + "id": "2026-10-28-0123456789ab", "record_url": "https://example.org/r#L1", + "files": [hostile], "finding_list": [hostile], "detail": hostile} + text = "\n".join(sm.pack_tested_section([item])) + for raw in ("", "b", "**b", + "href=\"javascript", '"q"', "'q'"): + self.assertNotIn(raw, text) + self.assertIn("<script>", text) + self.assertIn("{alert(1)}", text) + # A Markdown link in other site text keeps only http(s) and relative targets. + self.assertEqual(sitemarkup.inline_html("[x](javascript:alert(1))"), "[x](javascript:alert(1))") + self.assertEqual(sitemarkup.inline_html('[x](https://a.org/"onmouseover=1)'), + 'x') + self.assertEqual(sitemarkup.inline_html("[x](../a.md)"), 'x') self.assertEqual(sm.pack_tested_section([]), [])