csp/extensions/__init__.py (24 lines of code) (raw):
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Callable
from jinja2 import nodes
from jinja2.ext import Extension
from csp.utils import SCRIPT_ATTRS, build_script_tag
if TYPE_CHECKING:
from jinja2.parser import Parser
class NoncedScript(Extension):
# a set of names that trigger the extension.
tags = {"script"}
def parse(self, parser: Parser) -> nodes.Node:
# the first token is the token that started the tag. In our case
# we only listen to ``'script'`` so this will be a name token with
# `script` as value. We get the line number so that we can give
# that line number to the nodes we create by hand.
lineno = next(parser.stream).lineno
# Get the current context and pass along
kwargs = [nodes.Keyword("ctx", nodes.ContextReference())]
# Parse until we are done with optional script tag attributes
while parser.stream.current.value in SCRIPT_ATTRS:
attr_name = parser.stream.current.value
parser.stream.skip(2)
kwargs.append(nodes.Keyword(attr_name, parser.parse_expression()))
# now we parse the body of the script block up to `endscript` and
# drop the needle (which would always be `endscript` in that case)
body = parser.parse_statements(("name:endscript",), drop_needle=True)
# now return a `CallBlock` node that calls our _render_script
# helper method on this extension.
return nodes.CallBlock(self.call_method("_render_script", kwargs=kwargs), [], [], body).set_lineno(lineno)
def _render_script(self, caller: Callable[[], str], **kwargs: Any) -> str:
ctx = kwargs.pop("ctx")
request = ctx.get("request")
kwargs["nonce"] = str(request.csp_nonce)
kwargs["content"] = caller().strip()
return build_script_tag(**kwargs)