mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Resolve and caches paths for CachingStaticResource in the executor (#74474)
This commit is contained in:
parent
113ccfe6af
commit
332cf3cd2d
@ -9,11 +9,31 @@ from aiohttp import hdrs
|
||||
from aiohttp.web import FileResponse, Request, StreamResponse
|
||||
from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound
|
||||
from aiohttp.web_urldispatcher import StaticResource
|
||||
from lru import LRU # pylint: disable=no-name-in-module
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import KEY_HASS
|
||||
|
||||
CACHE_TIME: Final = 31 * 86400 # = 1 month
|
||||
CACHE_HEADERS: Final[Mapping[str, str]] = {
|
||||
hdrs.CACHE_CONTROL: f"public, max-age={CACHE_TIME}"
|
||||
}
|
||||
PATH_CACHE = LRU(512)
|
||||
|
||||
|
||||
def _get_file_path(
|
||||
filename: str, directory: Path, follow_symlinks: bool
|
||||
) -> Path | None:
|
||||
filepath = directory.joinpath(filename).resolve()
|
||||
if not follow_symlinks:
|
||||
filepath.relative_to(directory)
|
||||
# on opening a dir, load its contents if allowed
|
||||
if filepath.is_dir():
|
||||
return None
|
||||
if filepath.is_file():
|
||||
return filepath
|
||||
raise HTTPNotFound
|
||||
|
||||
|
||||
class CachingStaticResource(StaticResource):
|
||||
@ -21,16 +41,19 @@ class CachingStaticResource(StaticResource):
|
||||
|
||||
async def _handle(self, request: Request) -> StreamResponse:
|
||||
rel_url = request.match_info["filename"]
|
||||
hass: HomeAssistant = request.app[KEY_HASS]
|
||||
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()
|
||||
try:
|
||||
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 = self._directory.joinpath(filename).resolve()
|
||||
if not self._follow_symlinks:
|
||||
filepath.relative_to(self._directory)
|
||||
key = (filename, self._directory, self._follow_symlinks)
|
||||
if (filepath := PATH_CACHE.get(key)) is None:
|
||||
filepath = PATH_CACHE[key] = await hass.async_add_executor_job(
|
||||
_get_file_path, filename, self._directory, self._follow_symlinks
|
||||
)
|
||||
except (ValueError, FileNotFoundError) as error:
|
||||
# relatively safe
|
||||
raise HTTPNotFound() from error
|
||||
@ -39,13 +62,10 @@ class CachingStaticResource(StaticResource):
|
||||
request.app.logger.exception(error)
|
||||
raise HTTPNotFound() from error
|
||||
|
||||
# on opening a dir, load its contents if allowed
|
||||
if filepath.is_dir():
|
||||
return await super()._handle(request)
|
||||
if filepath.is_file():
|
||||
if filepath:
|
||||
return FileResponse(
|
||||
filepath,
|
||||
chunk_size=self._chunk_size,
|
||||
headers=CACHE_HEADERS,
|
||||
)
|
||||
raise HTTPNotFound
|
||||
return await super()._handle(request)
|
||||
|
@ -2,7 +2,7 @@
|
||||
"domain": "recorder",
|
||||
"name": "Recorder",
|
||||
"documentation": "https://www.home-assistant.io/integrations/recorder",
|
||||
"requirements": ["sqlalchemy==1.4.38", "fnvhash==0.1.0", "lru-dict==1.1.7"],
|
||||
"requirements": ["sqlalchemy==1.4.38", "fnvhash==0.1.0"],
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"quality_scale": "internal",
|
||||
"iot_class": "local_push"
|
||||
|
@ -38,6 +38,7 @@ dependencies = [
|
||||
"httpx==0.23.0",
|
||||
"ifaddr==0.1.7",
|
||||
"jinja2==3.1.2",
|
||||
"lru-dict==1.1.7",
|
||||
"PyJWT==2.4.0",
|
||||
# PyJWT has loose dependency. We want the latest one.
|
||||
"cryptography==36.0.2",
|
||||
|
@ -13,6 +13,7 @@ ciso8601==2.2.0
|
||||
httpx==0.23.0
|
||||
ifaddr==0.1.7
|
||||
jinja2==3.1.2
|
||||
lru-dict==1.1.7
|
||||
PyJWT==2.4.0
|
||||
cryptography==36.0.2
|
||||
orjson==3.7.5
|
||||
|
@ -974,9 +974,6 @@ logi_circle==0.2.3
|
||||
# homeassistant.components.london_underground
|
||||
london-tube-status==0.5
|
||||
|
||||
# homeassistant.components.recorder
|
||||
lru-dict==1.1.7
|
||||
|
||||
# homeassistant.components.luftdaten
|
||||
luftdaten==0.7.2
|
||||
|
||||
|
@ -681,9 +681,6 @@ life360==4.1.1
|
||||
# homeassistant.components.logi_circle
|
||||
logi_circle==0.2.3
|
||||
|
||||
# homeassistant.components.recorder
|
||||
lru-dict==1.1.7
|
||||
|
||||
# homeassistant.components.luftdaten
|
||||
luftdaten==0.7.2
|
||||
|
||||
|
@ -578,3 +578,35 @@ async def test_manifest_json(hass, frontend_themes, mock_http_client):
|
||||
|
||||
json = await resp.json()
|
||||
assert json["theme_color"] != DEFAULT_THEME_COLOR
|
||||
|
||||
|
||||
async def test_static_path_cache(hass, mock_http_client):
|
||||
"""Test static paths cache."""
|
||||
resp = await mock_http_client.get("/lovelace/default_view", allow_redirects=False)
|
||||
assert resp.status == 404
|
||||
|
||||
resp = await mock_http_client.get("/frontend_latest/", allow_redirects=False)
|
||||
assert resp.status == 403
|
||||
|
||||
resp = await mock_http_client.get(
|
||||
"/static/icons/favicon.ico", allow_redirects=False
|
||||
)
|
||||
assert resp.status == 200
|
||||
|
||||
# and again to make sure the cache works
|
||||
resp = await mock_http_client.get(
|
||||
"/static/icons/favicon.ico", allow_redirects=False
|
||||
)
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await mock_http_client.get(
|
||||
"/static/fonts/roboto/Roboto-Bold.woff2", allow_redirects=False
|
||||
)
|
||||
assert resp.status == 200
|
||||
|
||||
resp = await mock_http_client.get("/static/does-not-exist", allow_redirects=False)
|
||||
assert resp.status == 404
|
||||
|
||||
# and again to make sure the cache works
|
||||
resp = await mock_http_client.get("/static/does-not-exist", allow_redirects=False)
|
||||
assert resp.status == 404
|
||||
|
Loading…
x
Reference in New Issue
Block a user