-
Notifications
You must be signed in to change notification settings - Fork 1
fix: harden ontology site identifiers and output safety #382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d14dc49
aab1e60
91bf532
4be1bc3
aabc1e4
72380b9
9b09703
16c3c6c
bf5d7f2
8f74a95
1297591
851db25
b6eac1d
648e2b9
5eb707f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,11 @@ | |
| from collections.abc import Iterable | ||
| from pathlib import Path | ||
| from typing import Any | ||
| from urllib.parse import quote | ||
|
|
||
| try: | ||
| from scripts.ontology_site_contract import public_fragment | ||
| except ModuleNotFoundError: # direct execution with ``scripts`` as sys.path[0] | ||
| from ontology_site_contract import public_fragment | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
| from rdflib import Graph, Literal, URIRef | ||
| from rdflib.compare import to_canonical_graph | ||
|
|
@@ -113,7 +117,7 @@ def _write_serializations(graph: Graph, ontology_dir: Path) -> None: | |
| def _term_href(value: URIRef, ontology_subjects: set[URIRef]) -> str: | ||
| """Return a local fragment for local terms and an absolute IRI otherwise.""" | ||
| if value in ontology_subjects: | ||
| return f"#{quote(_fragment(value), safe='-._~')}" | ||
| return f"#{public_fragment(_fragment(value))}" | ||
| return str(value) | ||
|
|
||
|
|
||
|
|
@@ -146,11 +150,12 @@ def _render_relation_rows( | |
|
|
||
| def _render_term(graph: Graph, subject: URIRef, ontology_subjects: set[URIRef]) -> str: | ||
| """Render one fragment-addressable ontology term section.""" | ||
| fragment = _fragment(subject) | ||
| raw_fragment = _fragment(subject) | ||
| fragment = public_fragment(raw_fragment) | ||
| label = ( | ||
| _preferred_literal(graph, subject, RDFS.label) | ||
| or _preferred_literal(graph, subject, SKOS.prefLabel) | ||
| or fragment | ||
| or raw_fragment | ||
| ) | ||
| comment = _preferred_literal(graph, subject, RDFS.comment) | ||
| lookup_predicate = URIRef( | ||
|
|
@@ -171,7 +176,7 @@ def _render_term(graph: Graph, subject: URIRef, ontology_subjects: set[URIRef]) | |
| ) | ||
| return ( | ||
| f'<article class="term-card" id="{html.escape(fragment, quote=True)}">' | ||
| f'<h3><a class="fragment-link" href="#{quote(fragment, safe="-._~")}" ' | ||
| f'<h3><a class="fragment-link" href="#{html.escape(fragment, quote=True)}" ' | ||
|
Comment on lines
178
to
+179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Fragment id/href encoding now consistent The element id and its self-link href both derive from Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| f'aria-label="Link to {html.escape(label, quote=True)}">#</a> ' | ||
| f"{html.escape(label)}</h3>" | ||
| f'<p class="iri"><code>{html.escape(str(subject))}</code></p>' | ||
|
|
@@ -411,7 +416,10 @@ def build_site(repository_root: Path, output_dir: Path) -> None: | |
| raise FileNotFoundError(f"PROV-O support profile is missing: {prov_profile}") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Direct builder path skips fragment/link validation
(Refers to this code) Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| if output.exists(): | ||
| shutil.rmtree(output) | ||
| raise FileExistsError( | ||
| "refusing to replace an existing output directory; " | ||
| "use publish_ontology_site for marked replacement" | ||
| ) | ||
|
Comment on lines
418
to
+422
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Raw builder now errors on reused output dir
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| ontology_dir = output / "ontology" | ||
| ontology_dir.mkdir(parents=True) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| """Shared contracts for safe public ontology-site identifiers.""" | ||
|
|
||
| from urllib.parse import quote | ||
|
|
||
|
|
||
| def public_fragment(fragment: str) -> str: | ||
| """Encode one local fragment identically for HTML IDs and hrefs.""" | ||
| return quote(fragment, safe="-._~") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
|
|
||
| import argparse | ||
| import importlib.util | ||
| import shutil | ||
| from collections.abc import Iterable | ||
| from pathlib import Path | ||
| from types import ModuleType | ||
|
|
@@ -18,6 +19,11 @@ | |
| from rdflib import Graph, URIRef | ||
| from rdflib.namespace import OWL, RDF, RDFS, SKOS | ||
|
|
||
| try: | ||
| from scripts.ontology_site_contract import public_fragment | ||
| except ModuleNotFoundError: # direct execution with ``scripts`` as sys.path[0] | ||
| from ontology_site_contract import public_fragment | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
| OUTPUT_MARKER = ".lineageweave-ontology-site" | ||
| SOURCE_RELATIVE_PATH = Path("docs/ontology/lineageweave-kg.ttl") | ||
| PROV_PROFILE_RELATIVE_PATH = Path("docs/ontology/prov-o-support-profile.ttl") | ||
|
|
@@ -75,7 +81,7 @@ def validate_public_graph(graph: Graph) -> None: | |
| subjects = _public_subjects(graph) | ||
| fragment_owner: dict[str, URIRef] = {} | ||
| for subject in sorted(subjects, key=str): | ||
| fragment = _fragment(subject) | ||
| fragment = public_fragment(_fragment(subject)) | ||
| owner = fragment_owner.setdefault(fragment, subject) | ||
| if owner != subject: | ||
| raise ValueError( | ||
|
|
@@ -122,6 +128,8 @@ def publish_site(repository_root: Path, output_dir: Path) -> None: | |
| validate_public_graph(graph) | ||
|
|
||
| renderer = _load_renderer(root) | ||
| if output.exists(): | ||
| shutil.rmtree(output) | ||
|
Comment on lines
+131
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Output replacement moved into validated publisher path
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| renderer.build_site(root, output) | ||
| (output / OUTPUT_MARKER).write_text("", encoding="utf-8") | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.