From 107ceede57ce4faa4df5699abb3eaca14efcb5c5 Mon Sep 17 00:00:00 2001 From: bahmcloud Date: Wed, 14 Jan 2026 18:35:34 +0000 Subject: [PATCH] custom_components/bahmcloud_store/store.py aktualisiert --- custom_components/bahmcloud_store/store.py | 37 +++++++++++++++++----- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/custom_components/bahmcloud_store/store.py b/custom_components/bahmcloud_store/store.py index 3d671be..7be0ead 100644 --- a/custom_components/bahmcloud_store/store.py +++ b/custom_components/bahmcloud_store/store.py @@ -23,7 +23,7 @@ DOMAIN = "bahmcloud_store" class StoreError(Exception): - pass + """Store error.""" @dataclass @@ -114,6 +114,7 @@ class BahmcloudStore: async def refresh(self) -> None: session = async_get_clientsession(self.hass) + try: async with session.get(self.config.store_url, timeout=20) as resp: if resp.status != 200: @@ -146,7 +147,7 @@ class BahmcloudStore: pkg.zip_url = self._zip_url(pkg.repo, pkg.branch) parsed[pkg.id] = pkg - # update latest versions (sequential, safe) + # compute latest versions for pkg in parsed.values(): latest, rel_url = await self._fetch_latest_version(pkg) pkg.latest_version = latest or "unknown" @@ -176,6 +177,7 @@ class BahmcloudStore: raise StoreError("zip_url not set") session = async_get_clientsession(self.hass) + with tempfile.TemporaryDirectory() as td: zip_path = Path(td) / "repo.zip" extract_dir = Path(td) / "extract" @@ -224,26 +226,45 @@ class BahmcloudStore: return None async def register_http_views(self) -> None: + """Register HTTP views for static panel assets and JSON API.""" self.hass.http.register_view(_StaticView()) self.hass.http.register_view(_APIListView(self)) class _StaticView(HomeAssistantView): + """ + Serves panel assets from: custom_components/bahmcloud_store/panel/ + + URLs: + /api/bahmcloud_store_static/index.html + /api/bahmcloud_store_static/panel.js + /api/bahmcloud_store_static/app.js + /api/bahmcloud_store_static/styles.css + """ requires_auth = True name = "bahmcloud_store_static" url = "/api/bahmcloud_store_static/{path:.*}" async def get(self, request, path): base = Path(__file__).resolve().parent / "panel" - f = (base / path).resolve() - if not str(f).startswith(str(base)) or not f.exists(): - return web.Response(status=404) + if not path: + path = "index.html" - if f.suffix == ".js": + f = (base / path).resolve() + + # Prevent path traversal + if not str(f).startswith(str(base)) or not f.exists() or not f.is_file(): + return web.Response(status=404, text="Not found") + + suffix = f.suffix.lower() + if suffix == ".js": return web.Response(body=f.read_bytes(), content_type="application/javascript") - if f.suffix == ".css": + if suffix == ".css": return web.Response(body=f.read_bytes(), content_type="text/css") - return web.Response(body=f.read_bytes(), content_type="text/html") + if suffix in (".html", ".htm"): + return web.Response(body=f.read_bytes(), content_type="text/html") + + return web.Response(body=f.read_bytes(), content_type="application/octet-stream") class _APIListView(HomeAssistantView):