Enable autoescape in themes Jinja templates

This commit is contained in:
Oleh Prypin
2024-02-01 18:23:39 +01:00
parent 66a6d8c5a2
commit 5f5ae44fcd
11 changed files with 49 additions and 25 deletions

View File

@@ -9,6 +9,7 @@ from urllib.parse import urljoin, urlsplit
import jinja2
from jinja2.exceptions import TemplateNotFound
from markupsafe import Markup
import mkdocs
from mkdocs import utils
@@ -212,11 +213,15 @@ def _build_page(
# Run `page_context` plugin events.
context = config.plugins.on_page_context(context, page=page, config=config, nav=nav)
page.content = Markup(page.content or '')
if excluded:
page.content = (
'<div class="mkdocs-draft-marker" title="This page will not be included into the built site.">'
'DRAFT'
'</div>' + (page.content or '')
Markup(
'<div class="mkdocs-draft-marker" title="This page will not be included into the built site.">'
'DRAFT'
'</div>'
)
+ page.content
)
# Render the template.

View File

@@ -32,6 +32,7 @@ from urllib.parse import urlsplit, urlunsplit
import markdown
import pathspec
import pathspec.gitignore
from markupsafe import Markup
from mkdocs import plugins, theme, utils
from mkdocs.config.base import (
@@ -536,6 +537,15 @@ class URL(OptionallyRequired[str]):
raise ValidationError("The URL isn't valid, it should include the http:// (scheme)")
class HTMLString(BaseConfigOption[Markup]):
"""A string of HTML that should not be further escaped when pasting it into other HTML content."""
def run_validation(self, value: object) -> Markup:
if not isinstance(value, str):
raise ValidationError(f"Expected a string but received: {type(value)}")
return Markup(value)
class Optional(Generic[T], BaseConfigOption[Union[T, None]]):
"""
Wraps a field and makes a None value possible for it when no value is set.

View File

@@ -79,7 +79,7 @@ class MkDocsConfig(base.Config):
site_dir = c.SiteDir(default='site')
"""The directory where the site will be built to"""
copyright = c.Optional(c.Type(str))
copyright = c.Optional(c.HTMLString())
"""A copyright notice to add to the footer of documentation."""
google_analytics = c.Deprecated(

View File

@@ -4,6 +4,8 @@ import abc
from typing import TYPE_CHECKING, Iterable
if TYPE_CHECKING:
from markupsafe import Markup
from mkdocs.structure.nav import Section
@@ -21,7 +23,7 @@ class StructureItem(metaclass=abc.ABCMeta):
def is_top_level(self) -> bool:
return self.parent is None
title: str | None
title: Markup | None
is_section: bool = False
is_page: bool = False
is_link: bool = False

View File

@@ -4,6 +4,8 @@ import logging
from typing import TYPE_CHECKING, Iterator, TypeVar
from urllib.parse import urlsplit
from markupsafe import Markup
from mkdocs.exceptions import BuildError
from mkdocs.structure import StructureItem
from mkdocs.structure.files import file_sort_key
@@ -46,17 +48,17 @@ class Navigation:
class Section(StructureItem):
def __init__(self, title: str, children: list[StructureItem]) -> None:
self.title = title
def __init__(self, title: Markup, children: list[StructureItem]) -> None:
self.title = Markup(title)
self.children = children
self.active = False
def __repr__(self):
name = self.__class__.__name__
return f"{name}(title={self.title!r})"
return f"{name}(title={str(self.title)!r})"
title: str
title: Markup
"""The title of the section."""
children: list[StructureItem]
@@ -95,16 +97,16 @@ class Section(StructureItem):
class Link(StructureItem):
def __init__(self, title: str, url: str):
self.title = title
def __init__(self, title: Markup, url: str):
self.title = Markup(title)
self.url = url
def __repr__(self):
name = self.__class__.__name__
title = f"{self.title!r}" if self.title is not None else '[blank]'
title = f"{str(self.title)!r}" if self.title is not None else '[blank]'
return f"{name}(title={title}, url={self.url!r})"
title: str
title: Markup
"""The title of the link. This would generally be used as the label of the link."""
url: str

View File

@@ -15,6 +15,7 @@ import markdown.htmlparser # type: ignore
import markdown.postprocessors
import markdown.treeprocessors
from markdown.util import AMP_SUBSTITUTE
from markupsafe import Markup
from mkdocs import utils
from mkdocs.structure import StructureItem
@@ -33,11 +34,11 @@ log = logging.getLogger(__name__)
class Page(StructureItem):
def __init__(self, title: str | None, file: File, config: MkDocsConfig) -> None:
def __init__(self, title: Markup | None, file: File, config: MkDocsConfig) -> None:
file.page = self
self.file = file
if title is not None:
self.title = title
self.title = Markup(title)
# Navigation attributes
self.children = None
@@ -68,7 +69,7 @@ class Page(StructureItem):
def __repr__(self):
name = self.__class__.__name__
title = f"{self.title!r}" if self.title is not None else '[blank]'
title = f"{str(self.title)!r}" if self.title is not None else '[blank]'
url = self.abs_url or self.file.url
return f"{name}(title={title}, url={url!r})"
@@ -281,7 +282,7 @@ class Page(StructureItem):
extract_title_ext = _ExtractTitleTreeprocessor()
extract_title_ext._register(md)
self.content = md.convert(self.markdown)
self.content = Markup(md.convert(self.markdown))
self.toc = get_toc(getattr(md, 'toc_tokens', []))
self._title_from_render = extract_title_ext.title
self.present_anchor_ids = (

View File

@@ -5,15 +5,18 @@ For the sake of simplicity we use the Python-Markdown `toc` extension to
generate a list of dicts for each toc item, and then store it as AnchorLinks to
maintain compatibility with older versions of MkDocs.
"""
from __future__ import annotations
from typing import Iterable, Iterator, TypedDict
from markupsafe import Markup
class _TocToken(TypedDict):
level: int
id: str
name: str
name: Markup
children: list[_TocToken]
@@ -28,11 +31,11 @@ def get_toc(toc_tokens: list[_TocToken]) -> TableOfContents:
class AnchorLink:
"""A single entry in the table of contents."""
def __init__(self, title: str, id: str, level: int) -> None:
self.title, self.id, self.level = title, id, level
def __init__(self, title: Markup, id: str, level: int) -> None:
self.title, self.id, self.level = Markup(title), id, level
self.children = []
title: str
title: Markup
"""The text of the item, as HTML."""
@property

View File

@@ -7,6 +7,7 @@ import unittest
from unittest import mock
import markdown
from markupsafe import Markup
from mkdocs.config.defaults import MkDocsConfig
from mkdocs.structure.files import File, Files
@@ -749,7 +750,7 @@ class RelativePathExtensionTests(unittest.TestCase):
) -> str:
cfg = load_config(docs_dir=DOCS_DIR, **kwargs)
fs = [File(f, cfg.docs_dir, cfg.site_dir, cfg.use_directory_urls) for f in files]
pg = Page('Foo', fs[0], cfg)
pg = Page(Markup('Foo'), fs[0], cfg)
with mock.patch('mkdocs.structure.files.open', mock.mock_open(read_data=content)):
pg.read_source(cfg)

View File

@@ -156,7 +156,7 @@ class Theme(MutableMapping[str, Any]):
"""Return a Jinja environment for the theme."""
loader = jinja2.FileSystemLoader(self.dirs)
# No autoreload because editing a template in the middle of a build is not useful.
env = jinja2.Environment(loader=loader, auto_reload=False)
env = jinja2.Environment(loader=loader, auto_reload=False, autoescape=True)
env.filters['url'] = templates.url_filter
env.filters['script_tag'] = templates.script_tag_filter
localization.install_translations(env, self.locale, self.dirs)

View File

@@ -184,7 +184,7 @@
{%- if config.copyright %}
<p>{{ config.copyright }}</p>
{%- endif %}
<p>{% trans mkdocs_link='<a href="https://www.mkdocs.org/">MkDocs</a>' %}Documentation built with {{ mkdocs_link }}.{% endtrans %}</p>
<p>{% trans mkdocs_link='<a href="https://www.mkdocs.org/">MkDocs</a>'|safe %}Documentation built with {{ mkdocs_link }}.{% endtrans %}</p>
{%- endblock %}
</footer>

View File

@@ -22,5 +22,5 @@
{%- endif %}
</div>
{% trans mkdocs_link='<a href="https://www.mkdocs.org/">MkDocs</a>', sphinx_link='<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>', rtd_link='<a href="https://readthedocs.org">Read the Docs</a>' %}Built with %(mkdocs_link)s using a %(sphinx_link)s provided by %(rtd_link)s.{% endtrans %}
{% trans mkdocs_link='<a href="https://www.mkdocs.org/">MkDocs</a>'|safe, sphinx_link='<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>'|safe, rtd_link='<a href="https://readthedocs.org">Read the Docs</a>'|safe %}Built with %(mkdocs_link)s using a %(sphinx_link)s provided by %(rtd_link)s.{% endtrans %}
</footer>