2016-11-25 21:04:06 +00:00
|
|
|
"""Static file handling for HTTP component."""
|
2024-03-08 13:52:48 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2023-12-17 23:16:31 +00:00
|
|
|
from collections.abc import Mapping
|
2023-11-03 17:37:29 +00:00
|
|
|
import mimetypes
|
2019-02-15 17:31:54 +00:00
|
|
|
from pathlib import Path
|
2021-05-10 21:30:47 +00:00
|
|
|
from typing import Final
|
2019-02-15 17:31:54 +00:00
|
|
|
|
2016-11-25 21:04:06 +00:00
|
|
|
from aiohttp import hdrs
|
2021-05-10 21:30:47 +00:00
|
|
|
from aiohttp.web import FileResponse, Request, StreamResponse
|
2019-12-05 12:42:09 +00:00
|
|
|
from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound
|
2016-11-25 21:04:06 +00:00
|
|
|
from aiohttp.web_urldispatcher import StaticResource
|
2023-12-17 23:16:31 +00:00
|
|
|
from lru import LRU
|
2022-07-06 18:49:48 +00:00
|
|
|
|
|
|
|
from .const import KEY_HASS
|
2019-02-15 17:31:54 +00:00
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
CACHE_TIME: Final = 31 * 86400 # = 1 month
|
2023-11-03 17:37:29 +00:00
|
|
|
CACHE_HEADER = f"public, max-age={CACHE_TIME}"
|
|
|
|
CACHE_HEADERS: Mapping[str, str] = {hdrs.CACHE_CONTROL: CACHE_HEADER}
|
2024-01-28 20:07:12 +00:00
|
|
|
PATH_CACHE: LRU[tuple[str, Path], tuple[Path | None, str | None]] = LRU(512)
|
2022-07-06 18:49:48 +00:00
|
|
|
|
|
|
|
|
2024-01-28 20:07:12 +00:00
|
|
|
def _get_file_path(rel_url: str, directory: Path) -> Path | None:
|
2023-10-31 19:31:58 +00:00
|
|
|
"""Return the path to file on disk or None."""
|
|
|
|
filename = Path(rel_url)
|
|
|
|
if filename.anchor:
|
|
|
|
# rel_url is an absolute name like
|
|
|
|
# /static/\\machine_name\c$ or /static/D:\path
|
|
|
|
# where the static dir is totally different
|
|
|
|
raise HTTPForbidden
|
|
|
|
filepath: Path = directory.joinpath(filename).resolve()
|
2024-01-28 20:07:12 +00:00
|
|
|
filepath.relative_to(directory)
|
2022-07-06 18:49:48 +00:00
|
|
|
# on opening a dir, load its contents if allowed
|
|
|
|
if filepath.is_dir():
|
|
|
|
return None
|
|
|
|
if filepath.is_file():
|
|
|
|
return filepath
|
2022-07-28 14:43:32 +00:00
|
|
|
raise FileNotFoundError
|
2017-03-30 07:50:53 +00:00
|
|
|
|
2016-11-25 21:04:06 +00:00
|
|
|
|
2017-03-30 07:50:53 +00:00
|
|
|
class CachingStaticResource(StaticResource):
|
|
|
|
"""Static Resource handler that will add cache headers."""
|
|
|
|
|
2021-05-10 21:30:47 +00:00
|
|
|
async def _handle(self, request: Request) -> StreamResponse:
|
2023-10-31 19:31:58 +00:00
|
|
|
"""Return requested file from disk as a FileResponse."""
|
2019-07-31 19:25:30 +00:00
|
|
|
rel_url = request.match_info["filename"]
|
2024-01-28 20:07:12 +00:00
|
|
|
key = (rel_url, self._directory)
|
2023-11-03 17:37:29 +00:00
|
|
|
if (filepath_content_type := PATH_CACHE.get(key)) is None:
|
2024-03-07 12:37:48 +00:00
|
|
|
hass = request.app[KEY_HASS]
|
2023-10-31 19:31:58 +00:00
|
|
|
try:
|
|
|
|
filepath = await hass.async_add_executor_job(_get_file_path, *key)
|
|
|
|
except (ValueError, FileNotFoundError) as error:
|
|
|
|
# relatively safe
|
2024-03-17 23:40:38 +00:00
|
|
|
raise HTTPNotFound from error
|
2023-10-31 19:31:58 +00:00
|
|
|
except HTTPForbidden:
|
|
|
|
# forbidden
|
|
|
|
raise
|
|
|
|
except Exception as error:
|
|
|
|
# perm error or other kind!
|
2024-03-29 06:20:36 +00:00
|
|
|
request.app.logger.exception("Unexpected exception")
|
2024-03-17 23:40:38 +00:00
|
|
|
raise HTTPNotFound from error
|
2017-03-30 07:50:53 +00:00
|
|
|
|
2023-11-03 17:37:29 +00:00
|
|
|
content_type: str | None = None
|
|
|
|
if filepath is not None:
|
|
|
|
content_type = (mimetypes.guess_type(rel_url))[
|
|
|
|
0
|
|
|
|
] or "application/octet-stream"
|
|
|
|
PATH_CACHE[key] = (filepath, content_type)
|
|
|
|
else:
|
|
|
|
filepath, content_type = filepath_content_type
|
|
|
|
|
|
|
|
if filepath and content_type:
|
2019-09-27 19:57:59 +00:00
|
|
|
return FileResponse(
|
|
|
|
filepath,
|
|
|
|
chunk_size=self._chunk_size,
|
2023-11-03 17:37:29 +00:00
|
|
|
headers={
|
|
|
|
hdrs.CACHE_CONTROL: CACHE_HEADER,
|
|
|
|
hdrs.CONTENT_TYPE: content_type,
|
|
|
|
},
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2023-10-31 19:31:58 +00:00
|
|
|
|
2022-07-06 18:49:48 +00:00
|
|
|
return await super()._handle(request)
|