mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-27 00:36:39 +00:00
fix(export): use portable path in graph.html document title
The <title> previously embedded str(output_path), so Windows absolute host paths leaked into a tracked artifact (regression of #433, #2598). Prefer a cwd-relative label, else keep from the graphify-out segment onward, else the filename only.
This commit is contained in:
@@ -322,6 +322,44 @@ LEGEND.forEach(c => {{
|
||||
}});
|
||||
</script>"""
|
||||
|
||||
|
||||
def _html_document_title(output_path: str) -> str:
|
||||
"""Return a portable label for the graph.html <title>.
|
||||
|
||||
Tracked artifacts must not embed the generator host absolute path
|
||||
(regression of #433; reported again as #2598 on Windows). Prefer a
|
||||
path relative to the process cwd; otherwise keep from the configured
|
||||
output-dir bare name (``graphify-out`` / ``GRAPHIFY_OUT`` basename)
|
||||
onward; finally fall back to the filename only.
|
||||
"""
|
||||
from graphify.paths import GRAPHIFY_OUT_NAME
|
||||
|
||||
raw = str(output_path).replace("\\", "/")
|
||||
# Drop Windows drive prefix so Path parts are comparable on any OS.
|
||||
if len(raw) >= 3 and raw[1] == ":" and raw[0].isalpha() and raw[2] == "/":
|
||||
raw = raw[2:] # "/Users/..." style after drive strip
|
||||
p = Path(raw)
|
||||
|
||||
try:
|
||||
resolved = p if p.is_absolute() else (Path.cwd() / p)
|
||||
rel = resolved.resolve().relative_to(Path.cwd().resolve())
|
||||
label = rel.as_posix()
|
||||
if label and label != ".":
|
||||
return label
|
||||
except (ValueError, OSError, RuntimeError):
|
||||
pass
|
||||
|
||||
parts = list(Path(raw).parts)
|
||||
# Path("C:/Users/..") on POSIX may keep "C:" as first part — strip it.
|
||||
if parts and len(parts[0]) == 2 and parts[0][1] == ":" and parts[0][0].isalpha():
|
||||
parts = parts[1:]
|
||||
marker = GRAPHIFY_OUT_NAME
|
||||
for i, part in enumerate(parts):
|
||||
if part == marker or part.startswith("graphify-out"):
|
||||
return "/".join(parts[i:])
|
||||
name = p.name
|
||||
return name if name else "graph.html"
|
||||
|
||||
def to_html(
|
||||
G: nx.Graph,
|
||||
communities: dict[int, list[str]],
|
||||
@@ -519,7 +557,7 @@ def to_html(
|
||||
edges_json = _js_safe(vis_edges)
|
||||
legend_json = _js_safe(legend_data)
|
||||
hyperedges_json = _js_safe(getattr(G, "graph", {}).get("hyperedges", []))
|
||||
title = _html.escape(sanitize_label(str(output_path)))
|
||||
title = _html.escape(sanitize_label(_html_document_title(output_path)))
|
||||
stats = f"{G.number_of_nodes()} nodes · {G.number_of_edges()} edges · {len(communities)} communities"
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
|
||||
@@ -191,6 +191,37 @@ def test_to_html_contains_visjs():
|
||||
assert "vis-network" in content
|
||||
|
||||
|
||||
|
||||
def test_to_html_title_uses_portable_path_not_host_absolute():
|
||||
"""#2598 / #433: <title> must not embed the generator host absolute path."""
|
||||
import re
|
||||
|
||||
G = make_graph()
|
||||
communities = cluster(G)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
userish = Path(tmp) / "Users" / "mike" / "proj" / "graphify-out" / "graph.html"
|
||||
userish.parent.mkdir(parents=True)
|
||||
to_html(G, communities, str(userish))
|
||||
html = userish.read_text(encoding="utf-8")
|
||||
m = re.search(r"<title>(.*?)</title>", html)
|
||||
assert m, "expected a <title> tag"
|
||||
title = m.group(1)
|
||||
assert title.startswith("graphify - ")
|
||||
label = title[len("graphify - "):]
|
||||
assert "mike" not in label
|
||||
assert "Users" not in label
|
||||
assert not label.startswith("/")
|
||||
assert "graphify-out/graph.html" in label or label == "graph.html"
|
||||
|
||||
|
||||
def test_html_document_title_helper_windows_and_relative():
|
||||
from graphify.exporters.html import _html_document_title
|
||||
|
||||
assert _html_document_title(r"C:\Users\mike\proj\graphify-out\graph.html") == "graphify-out/graph.html"
|
||||
assert _html_document_title("/home/u/proj/graphify-out/graph.html") == "graphify-out/graph.html"
|
||||
assert _html_document_title("graphify-out/graph.html") == "graphify-out/graph.html"
|
||||
assert _html_document_title("/tmp/only/graph.html") == "graph.html"
|
||||
|
||||
def test_to_html_neighbor_links_have_no_inline_onclick_xss():
|
||||
"""#1838: neighbor links dropped an unescaped JSON.stringify(nid) into a
|
||||
quoted inline onclick — which broke every link (the value's own quotes
|
||||
|
||||
Reference in New Issue
Block a user