1from __future__ import annotations
2
3import collections.abc as cabc
4import typing as t
5
6from .structures import CallbackDict
7
8if t.TYPE_CHECKING:
9 import typing_extensions as te
10
11
12def csp_property(key: str, deprecated: str | None = None) -> t.Any:
13 """Create a property for a CSP directive."""
14 return property(
15 lambda x: x._get_value(key, deprecated=deprecated),
16 lambda x, v: x._set_value(key, v, deprecated=deprecated),
17 lambda x: x._del_value(key, deprecated, deprecated=deprecated),
18 f"The ``{key}`` directive.",
19 )
20
21
22class ContentSecurityPolicy(CallbackDict[str, str]):
23 """A dict that stores values for a ``Content-Security-Policy`` header.
24 Properties are available to access the CSP directives. The properties have
25 the same name as the directives, with dashes replaced with underscore.
26
27 To add a directive that does not have a property implemented, set the dict
28 key directly, like ``csp["new-directive"] = "value"``.
29
30 .. versionchanged:: 3.2
31 Added the ``required_trusted_types_for``, ``trusted_types``, and
32 ``upgrade_insecure_requests`` properties.
33
34 .. versionchanged:: 3.2
35 The ``prefetch_src``, ``navigate_to``, and ``plugin_types`` properties
36 are deprecated and will be removed in Werkzeug 3.3.
37
38 .. versionchanged:: 3.2
39 The ``on_update`` parameter was removed.
40
41 .. versionadded:: 1.0
42 """
43
44 # sections from MDN docs
45 # fetch directives
46 child_src: str | None = csp_property("child-src")
47 connect_src: str | None = csp_property("connect-src")
48 default_src: str | None = csp_property("default-src")
49 font_src: str | None = csp_property("font-src")
50 frame_src: str | None = csp_property("frame-src")
51 img_src: str | None = csp_property("img-src")
52 manifest_src: str | None = csp_property("manifest-src")
53 media_src: str | None = csp_property("media-src")
54 object_src: str | None = csp_property("object-src")
55 script_src: str | None = csp_property("script-src")
56 script_src_attr: str | None = csp_property("script-src-attr")
57 script_src_elem: str | None = csp_property("script-src-elem")
58 style_src: str | None = csp_property("style-src")
59 style_src_attr: str | None = csp_property("style-src-attr")
60 style_src_elem: str | None = csp_property("style-src-elem")
61 worker_src: str | None = csp_property("worker-src")
62 # document directives
63 base_uri: str | None = csp_property("base-uri")
64 sandbox: str | None = csp_property("sandbox")
65 # navigation directives
66 form_action: str | None = csp_property("form-action")
67 frame_ancestors: str | None = csp_property("frame-ancestors")
68 # reporting directives
69 report_to: str | None = csp_property("report-to")
70 # other directives
71 require_trusted_types_for: str | None = csp_property("require-trusted-types-for")
72 trusted_types: str | None = csp_property("trusted-types")
73 upgrade_insecure_requests: str | None = csp_property("upgrade-insecure-requests")
74 # deprecated directives
75 report_uri: str | None = csp_property("report-uri") # still widely supported
76 prefetch_src: str | None = csp_property("prefetch-src", deprecated="3.3")
77 # removed directives
78 navigate_to: str | None = csp_property("navigate-to", deprecated="3.3")
79 plugin_types: str | None = csp_property("plugin-types", deprecated="3.3")
80
81 def __init__(
82 self,
83 values: cabc.Mapping[str, str] | cabc.Iterable[tuple[str, str]] | None = None,
84 ) -> None:
85 super().__init__(values)
86 self.provided = values is not None
87
88 def _get_value(self, key: str, deprecated: str | None = None) -> str | None:
89 """Used internally by the accessor properties."""
90 if deprecated is not None:
91 import warnings
92
93 warnings.warn(
94 f"The CSP '{key}' directive is deprecated and will be removed"
95 f" in Werkzeug {deprecated}.",
96 DeprecationWarning,
97 stacklevel=3,
98 )
99
100 return self.get(key)
101
102 def _set_value(
103 self, key: str, value: str | None, deprecated: str | None = None
104 ) -> None:
105 """Used internally by the accessor properties."""
106 if deprecated is not None:
107 import warnings
108
109 warnings.warn(
110 f"The CSP '{key}' directive is deprecated and will be removed"
111 f" in Werkzeug {deprecated}.",
112 DeprecationWarning,
113 stacklevel=3,
114 )
115
116 if value is None:
117 self.pop(key, None)
118 else:
119 self[key] = value
120
121 def _del_value(self, key: str, deprecated: str | None = None) -> None:
122 """Used internally by the accessor properties."""
123 if deprecated is not None:
124 import warnings
125
126 warnings.warn(
127 f"The CSP '{key}' directive is deprecated and will be removed"
128 f" in Werkzeug {deprecated}.",
129 DeprecationWarning,
130 stacklevel=3,
131 )
132
133 if key in self:
134 del self[key]
135
136 @classmethod
137 def from_header(cls, value: str | None) -> te.Self:
138 """Parse a ``Content-Security-Policy`` header value and create an
139 instance of this class.
140
141 .. versionadded:: 3.2
142 """
143 if not value:
144 return cls()
145
146 items = []
147
148 for policy in value.split(";"):
149 policy = policy.strip()
150
151 # Ignore badly formatted policies (no space)
152 if " " in policy:
153 directive, value = policy.strip().split(" ", 1)
154 items.append((directive.strip(), value.strip()))
155
156 return cls(items)
157
158 def to_header(self) -> str:
159 """Convert to a ``Content-Security-Policy`` header value."""
160 return "; ".join(f"{key} {value}" for key, value in self.items())
161
162 def __str__(self) -> str:
163 return self.to_header()
164
165 def __repr__(self) -> str:
166 kv_str = " ".join(f"{k}={v!r}" for k, v in sorted(self.items()))
167 return f"<{type(self).__name__} {kv_str}>"