polish: env_for hardening per code review (M1-M3)
- env_for validates theme against THEMES whitelist (defense in depth) - degraded single-path envs are no longer cached - tests: unknown-theme rejection, real macro shadowing semantics, degraded branch path + no-cache Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -23,13 +23,17 @@ def env_for(theme):
|
|||||||
|
|
||||||
Lets a theme override shared macros (_professional_components.html) while
|
Lets a theme override shared macros (_professional_components.html) while
|
||||||
inheriting everything else. Cached: one env per theme across large builds.
|
inheriting everything else. Cached: one env per theme across large builds.
|
||||||
|
Only built-in theme names are accepted; callers are expected to enforce the
|
||||||
|
THEMES whitelist too (render does), this is defense in depth.
|
||||||
"""
|
"""
|
||||||
|
if theme not in THEMES:raise ValueError('未知主题')
|
||||||
env=_ENVS.get(theme)
|
env=_ENVS.get(theme)
|
||||||
if env is None:
|
if env is None:
|
||||||
theme_dir=ROOT/'themes'/theme
|
theme_dir=ROOT/'themes'/theme
|
||||||
search=[str(theme_dir),str(ROOT/'themes')] if theme_dir.is_dir() else [str(ROOT/'themes')]
|
canonical=theme_dir.is_dir()
|
||||||
|
search=[str(theme_dir),str(ROOT/'themes')] if canonical else [str(ROOT/'themes')]
|
||||||
env=Environment(loader=FileSystemLoader(search),autoescape=select_autoescape(['html','xml']))
|
env=Environment(loader=FileSystemLoader(search),autoescape=select_autoescape(['html','xml']))
|
||||||
_ENVS[theme]=env
|
if canonical:_ENVS[theme]=env # degraded envs are not cached
|
||||||
return env
|
return env
|
||||||
|
|
||||||
TITLES={'product':'产品中心','vehicle':'车型中心','post':'新闻资讯','case':'项目案例','brand':'品牌中心','service':'服务与解决方案'}
|
TITLES={'product':'产品中心','vehicle':'车型中心','post':'新闻资讯','case':'项目案例','brand':'品牌中心','service':'服务与解决方案'}
|
||||||
|
|||||||
@@ -54,3 +54,40 @@ def test_env_for_is_cached_per_theme():
|
|||||||
def test_env_for_pro_template_loads_unprefixed():
|
def test_env_for_pro_template_loads_unprefixed():
|
||||||
from app import theme_pro
|
from app import theme_pro
|
||||||
assert theme_pro.env_for('industrial').get_template('pro.html') is not None
|
assert theme_pro.env_for('industrial').get_template('pro.html') is not None
|
||||||
|
|
||||||
|
def test_env_for_rejects_unknown_theme():
|
||||||
|
import pytest
|
||||||
|
from app import theme_pro
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
theme_pro.env_for('../../etc')
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
theme_pro.env_for('no-such-theme')
|
||||||
|
|
||||||
|
def test_env_for_theme_macro_shadows_shared_root(tmp_path, monkeypatch):
|
||||||
|
# The core mechanism: a theme-local _professional_components.html wins over
|
||||||
|
# the shared one at themes/ root.
|
||||||
|
from app import theme_pro
|
||||||
|
themes = tmp_path / 'themes'
|
||||||
|
for sub, marker in (('industrial', 'theme-local'), ('_root_macros', None)):
|
||||||
|
d = themes / sub
|
||||||
|
d.mkdir(parents=True)
|
||||||
|
(themes / '_professional_components.html').write_text('SHARED', encoding='utf-8')
|
||||||
|
(themes / 'industrial' / '_professional_components.html').write_text('THEME-LOCAL', encoding='utf-8')
|
||||||
|
monkeypatch.setattr(theme_pro, 'ROOT', tmp_path)
|
||||||
|
monkeypatch.setattr(theme_pro, '_ENVS', {})
|
||||||
|
tpl = theme_pro.env_for('industrial').get_template('_professional_components.html')
|
||||||
|
assert tpl.render().strip() == 'THEME-LOCAL'
|
||||||
|
|
||||||
|
def test_env_for_degraded_branch_not_cached_and_single_path(tmp_path, monkeypatch):
|
||||||
|
from app import theme_pro
|
||||||
|
themes = tmp_path / 'themes'
|
||||||
|
themes.mkdir(parents=True)
|
||||||
|
monkeypatch.setattr(theme_pro, 'ROOT', tmp_path)
|
||||||
|
monkeypatch.setattr(theme_pro, '_ENVS', {})
|
||||||
|
core.THEMES = ('industrial',) # whitelist a theme whose dir does not exist
|
||||||
|
try:
|
||||||
|
env = theme_pro.env_for('industrial')
|
||||||
|
assert [str(p) for p in env.loader.searchpath] == [str(themes)]
|
||||||
|
assert 'industrial' not in theme_pro._ENVS
|
||||||
|
finally:
|
||||||
|
core.THEMES = core.discover_themes()
|
||||||
|
|||||||
Reference in New Issue
Block a user