diff --git a/esphome/dashboard/settings.py b/esphome/dashboard/settings.py index 76633e1bf2..505131cba0 100644 --- a/esphome/dashboard/settings.py +++ b/esphome/dashboard/settings.py @@ -3,7 +3,7 @@ from __future__ import annotations import hmac import os from pathlib import Path - +from typing import Any from esphome.core import CORE from esphome.helpers import get_bool_env @@ -69,7 +69,8 @@ class DashboardSettings: # Compare password in constant running time (to prevent timing attacks) return hmac.compare_digest(self.password_hash, password_hash(password)) - def rel_path(self, *args): + def rel_path(self, *args: Any) -> str: + """Return a path relative to the ESPHome config folder.""" joined_path = os.path.join(self.config_dir, *args) # Raises ValueError if not relative to ESPHome config folder Path(joined_path).resolve().relative_to(self.absolute_config_dir) diff --git a/esphome/dashboard/web_server.py b/esphome/dashboard/web_server.py index 93d836d76d..8901da095f 100644 --- a/esphome/dashboard/web_server.py +++ b/esphome/dashboard/web_server.py @@ -525,9 +525,19 @@ class DownloadListRequestHandler(BaseHandler): class DownloadBinaryRequestHandler(BaseHandler): + def _load_file(self, path: str, compressed: bool) -> bytes: + """Load a file from disk and compress it if requested.""" + with open(path, "rb") as f: + data = f.read() + if compressed: + return gzip.compress(data, 9) + return data + @authenticated @bind_config - async def get(self, configuration=None): + async def get(self, configuration: str | None = None): + """Download a binary file.""" + loop = asyncio.get_running_loop() compressed = self.get_argument("compressed", "0") == "1" storage_path = ext_storage_path(configuration) @@ -584,11 +594,8 @@ class DownloadBinaryRequestHandler(BaseHandler): self.send_error(404) return - with open(path, "rb") as f: - data = f.read() - if compressed: - data = gzip.compress(data, 9) - self.write(data) + data = await loop.run_in_executor(None, self._load_file, path, compressed) + self.write(data) self.finish() @@ -748,13 +755,10 @@ class EditRequestHandler(BaseHandler): @authenticated @bind_config async def get(self, configuration: str | None = None): + """Get the content of a file.""" loop = asyncio.get_running_loop() filename = settings.rel_path(configuration) - try: - content = await loop.run_in_executor(None, self._read_file, filename) - except OSError: - self.send_error(404) - return + content = await loop.run_in_executor(None, self._read_file, filename) self.write(content) def _read_file(self, filename: str) -> bytes: @@ -769,6 +773,7 @@ class EditRequestHandler(BaseHandler): @authenticated @bind_config async def post(self, configuration: str | None = None): + """Write the content of a file.""" loop = asyncio.get_running_loop() config_file = settings.rel_path(configuration) await loop.run_in_executor(