1from __future__ import annotations
2
3import typing as t
4
5from .structures import CallbackDict
6
7
8def _csp_property(key: str, deprecated: str | None = None) -> t.Any:
9 """Create a property for a CSP directive."""
10 return property(
11 lambda x: x._get_value(key, deprecated=deprecated),
12 lambda x, v: x._set_value(key, v, deprecated=deprecated),
13 lambda x: x._del_value(key, deprecated, deprecated=deprecated),
14 f"The ``{key}`` directive.",
15 )
16
17
18class ContentSecurityPolicy(CallbackDict[str, t.Any]):
19 """A parsed ``Content-Security-Policy`` header.
20
21 Set :attr:`.Response.content_security_policy` or
22 :attr:`.Response.content_security_policy_report_only` to an instance to set
23 the header. Modifying the instance will update the header.
24
25 Typically, you'll use the various directive properties. It also allows
26 indexing ``csp[directive]`` to get, set, or delete unknown directives that
27 do not have corresponding properties.
28
29 .. versionchanged:: 3.2
30 Directives with only a name and no space or value are considered boolean
31 rather than discarded.
32
33 Added the ``required_trusted_types_for``, ``trusted_types``, and
34 ``upgrade_insecure_requests`` properties.
35
36 The ``prefetch_src``, ``navigate_to``, and ``plugin_types`` properties
37 are deprecated and will be removed in Werkzeug 4.0.
38
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: bool | 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="4.0")
77 # removed directives
78 navigate_to: str | None = _csp_property("navigate-to", deprecated="4.0")
79 plugin_types: str | None = _csp_property("plugin-types", deprecated="4.0")
80
81 def _get_value(self, key: str, deprecated: str | None = None) -> t.Any | None:
82 """Used internally by the accessor properties."""
83 if deprecated is not None:
84 import warnings
85
86 warnings.warn(
87 f"The CSP '{key}' directive is deprecated and will be removed"
88 f" in Werkzeug {deprecated}.",
89 DeprecationWarning,
90 stacklevel=3,
91 )
92
93 if key in self:
94 if (value := self[key]) is None:
95 return True
96
97 return value
98
99 return None
100
101 def _set_value(
102 self, key: str, value: t.Any | None, deprecated: str | None = None
103 ) -> None:
104 """Used internally by the accessor properties."""
105 if deprecated is not None:
106 import warnings
107
108 warnings.warn(
109 f"The CSP '{key}' directive is deprecated and will be removed"
110 f" in Werkzeug {deprecated}.",
111 DeprecationWarning,
112 stacklevel=3,
113 )
114
115 if not value:
116 self.pop(key, None)
117 else:
118 self[key] = value
119
120 def _del_value(self, key: str, deprecated: str | None = None) -> None:
121 """Used internally by the accessor properties."""
122 if deprecated is not None:
123 import warnings
124
125 warnings.warn(
126 f"The CSP '{key}' directive is deprecated and will be removed"
127 f" in Werkzeug {deprecated}.",
128 DeprecationWarning,
129 stacklevel=3,
130 )
131
132 if key in self:
133 del self[key]
134
135 @classmethod
136 def from_header(cls, value: str | None) -> t.Self:
137 """Parse a ``Content-Security-Policy`` header value and create an
138 instance of this class.
139
140 .. versionadded:: 3.2
141 """
142 if not value:
143 return cls()
144
145 items: list[tuple[str, str | None]] = []
146
147 for policy in value.split(";"):
148 policy = policy.strip(" \t")
149
150 # Ignore badly formatted policies (no space)
151 if " " in policy:
152 directive, _, value = policy.partition(" ")
153 items.append((directive.strip(" \t"), value.strip(" \t")))
154 else:
155 items.append((policy, None))
156
157 return cls(items)
158
159 def to_header(self) -> str:
160 """Convert to a ``Content-Security-Policy`` header value."""
161 out = []
162
163 for key, value in self.items():
164 if value is None:
165 out.append(key)
166 else:
167 out.append(f"{key} {value}")
168
169 return "; ".join(out)
170
171 def __str__(self) -> str:
172 return self.to_header()
173
174 def __repr__(self) -> str:
175 kv_str = " ".join(f"{k}={v!r}" for k, v in sorted(self.items()))
176 return f"<{type(self).__name__} {kv_str}>"