mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 12:18:51 +00:00
69 lines
2.7 KiB
Python
69 lines
2.7 KiB
Python
"""Pure path/manifest guards for public frontend chunks (#337)."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
_PATH = Path(__file__).parents[1] / "custom_components" / "houseplan" / "frontend_asset_manifest.py"
|
|
_SPEC = importlib.util.spec_from_file_location("hp_frontend_asset_manifest", _PATH)
|
|
assert _SPEC and _SPEC.loader
|
|
_MODULE = importlib.util.module_from_spec(_SPEC)
|
|
_SPEC.loader.exec_module(_MODULE)
|
|
resolve_frontend_asset = _MODULE.resolve_frontend_asset
|
|
|
|
|
|
def _root(tmp_path: Path) -> Path:
|
|
root = tmp_path / "frontend"
|
|
assets = root / "houseplan-assets"
|
|
assets.mkdir(parents=True)
|
|
(assets / "editor-abc.js").write_text("export {};", encoding="utf-8")
|
|
(assets / "stale.js").write_text("export {};", encoding="utf-8")
|
|
(root / "houseplan-assets.json").write_text(json.dumps({
|
|
"schema": 1,
|
|
"files": [{"path": "houseplan-assets/editor-abc.js"}],
|
|
}), encoding="utf-8")
|
|
return root
|
|
|
|
|
|
def test_frontend_asset_requires_current_manifest_entry(tmp_path: Path) -> None:
|
|
root = _root(tmp_path)
|
|
assert resolve_frontend_asset(root, "editor-abc.js") == (
|
|
root / "houseplan-assets" / "editor-abc.js"
|
|
).resolve()
|
|
assert resolve_frontend_asset(root, "stale.js") is None
|
|
assert resolve_frontend_asset(root, "missing.js") is None
|
|
assert resolve_frontend_asset(root, "houseplan-assets.json") is None
|
|
|
|
|
|
def test_frontend_asset_refuses_traversal_and_malformed_manifest(tmp_path: Path) -> None:
|
|
root = _root(tmp_path)
|
|
for value in (
|
|
"../editor-abc.js", "..\\editor-abc.js", "nested/editor-abc.js",
|
|
"..%2feditor-abc.js", "editor-abc.css", "..", "",
|
|
):
|
|
assert resolve_frontend_asset(root, value) is None
|
|
(root / "houseplan-assets.json").write_text("not json", encoding="utf-8")
|
|
assert resolve_frontend_asset(root, "editor-abc.js") is None
|
|
|
|
|
|
def test_frontend_asset_refuses_wrong_schema_and_symlink_escape(tmp_path: Path) -> None:
|
|
root = _root(tmp_path)
|
|
manifest = root / "houseplan-assets.json"
|
|
manifest.write_text(json.dumps({
|
|
"schema": 2, "files": [{"path": "houseplan-assets/editor-abc.js"}],
|
|
}), encoding="utf-8")
|
|
assert resolve_frontend_asset(root, "editor-abc.js") is None
|
|
|
|
outside = tmp_path / "outside.js"
|
|
outside.write_text("outside", encoding="utf-8")
|
|
link = root / "houseplan-assets" / "linked.js"
|
|
try:
|
|
link.symlink_to(outside)
|
|
except OSError:
|
|
return # Windows without Developer Mode; containment is covered elsewhere.
|
|
manifest.write_text(json.dumps({
|
|
"schema": 1, "files": [{"path": "houseplan-assets/linked.js"}],
|
|
}), encoding="utf-8")
|
|
assert resolve_frontend_asset(root, "linked.js") is None
|