1use std::borrow::Cow;
6
7use constellation_traits::{LoadData, LoadOrigin};
8pub use content_security_policy::InlineCheckType;
10pub use content_security_policy::Violation;
12use content_security_policy::{
13 CheckResult, CspList, Destination, Element as CspElement, Initiator, NavigationCheckType,
14 Origin, ParserMetadata, PolicyDisposition, PolicySource, Request, ViolationResource,
15};
16use http::header::{HeaderMap, HeaderValue, ValueIter};
17use hyper_serde::Serde;
18use js::rust::describe_scripted_caller;
19use log::warn;
20
21use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
22use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString;
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::refcounted::Trusted;
25use crate::dom::csppolicyviolationreport::CSPViolationReportBuilder;
26use crate::dom::element::Element;
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::node::{Node, NodeTraits};
29use crate::dom::trustedscript::TrustedScript;
30use crate::dom::window::Window;
31use crate::script_runtime::CanGc;
32use crate::security_manager::CSPViolationReportTask;
33
34pub(crate) trait CspReporting {
35 fn is_js_evaluation_allowed(&self, global: &GlobalScope, source: &str) -> bool;
36 fn is_wasm_evaluation_allowed(&self, global: &GlobalScope) -> bool;
37 fn should_navigation_request_be_blocked(
38 &self,
39 global: &GlobalScope,
40 load_data: &mut LoadData,
41 element: Option<&Element>,
42 can_gc: CanGc,
43 ) -> bool;
44 fn should_elements_inline_type_behavior_be_blocked(
45 &self,
46 global: &GlobalScope,
47 el: &Element,
48 type_: InlineCheckType,
49 source: &str,
50 ) -> bool;
51 fn is_trusted_type_policy_creation_allowed(
52 &self,
53 global: &GlobalScope,
54 policy_name: &str,
55 created_policy_names: &[&str],
56 ) -> bool;
57 fn does_sink_type_require_trusted_types(
58 &self,
59 sink_group: &str,
60 include_report_only_policies: bool,
61 ) -> bool;
62 fn should_sink_type_mismatch_violation_be_blocked_by_csp(
63 &self,
64 global: &GlobalScope,
65 sink: &str,
66 sink_group: &str,
67 source: &str,
68 ) -> bool;
69 fn concatenate(self, new_csp_list: Option<CspList>) -> Option<CspList>;
70}
71
72impl CspReporting for Option<CspList> {
73 fn is_js_evaluation_allowed(&self, global: &GlobalScope, source: &str) -> bool {
75 let Some(csp_list) = self else {
76 return true;
77 };
78
79 let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source);
80
81 global.report_csp_violations(violations, None, None);
82
83 is_js_evaluation_allowed == CheckResult::Allowed
84 }
85
86 fn is_wasm_evaluation_allowed(&self, global: &GlobalScope) -> bool {
88 let Some(csp_list) = self else {
89 return true;
90 };
91
92 let (is_wasm_evaluation_allowed, violations) = csp_list.is_wasm_evaluation_allowed();
93
94 global.report_csp_violations(violations, None, None);
95
96 is_wasm_evaluation_allowed == CheckResult::Allowed
97 }
98
99 fn should_navigation_request_be_blocked(
101 &self,
102 global: &GlobalScope,
103 load_data: &mut LoadData,
104 element: Option<&Element>,
105 can_gc: CanGc,
106 ) -> bool {
107 let Some(csp_list) = self else {
108 return false;
109 };
110 let mut request = Request {
111 url: load_data.url.clone().into_url(),
112 origin: match &load_data.load_origin {
113 LoadOrigin::Script(immutable_origin) => immutable_origin.clone().into_url_origin(),
114 _ => Origin::new_opaque(),
115 },
116 redirect_count: 0,
118 destination: Destination::None,
119 initiator: Initiator::None,
120 nonce: "".to_owned(),
121 integrity_metadata: "".to_owned(),
122 parser_metadata: ParserMetadata::None,
123 };
124 let (result, violations) = csp_list.should_navigation_request_be_blocked(
126 &mut request,
127 NavigationCheckType::Other,
128 |script_source| {
129 TrustedScript::get_trusted_script_compliant_string(
132 global,
133 TrustedScriptOrString::String(script_source.into()),
134 "Location href",
135 can_gc,
136 )
137 .ok()
138 .map(|s| s.into())
139 },
140 );
141
142 load_data.url = request.url.into();
144
145 global.report_csp_violations(violations, element, None);
146
147 result == CheckResult::Blocked
148 }
149
150 fn should_elements_inline_type_behavior_be_blocked(
152 &self,
153 global: &GlobalScope,
154 el: &Element,
155 type_: InlineCheckType,
156 source: &str,
157 ) -> bool {
158 let Some(csp_list) = self else {
159 return false;
160 };
161 let element = CspElement {
162 nonce: el.nonce_value_if_nonceable().map(Cow::Owned),
163 };
164 let (result, violations) =
165 csp_list.should_elements_inline_type_behavior_be_blocked(&element, type_, source);
166
167 global.report_csp_violations(violations, Some(el), None);
168
169 result == CheckResult::Blocked
170 }
171
172 fn is_trusted_type_policy_creation_allowed(
174 &self,
175 global: &GlobalScope,
176 policy_name: &str,
177 created_policy_names: &[&str],
178 ) -> bool {
179 let Some(csp_list) = self else {
180 return true;
181 };
182
183 let (allowed_by_csp, violations) =
184 csp_list.is_trusted_type_policy_creation_allowed(policy_name, created_policy_names);
185
186 global.report_csp_violations(violations, None, None);
187
188 allowed_by_csp == CheckResult::Allowed
189 }
190
191 fn does_sink_type_require_trusted_types(
193 &self,
194 sink_group: &str,
195 include_report_only_policies: bool,
196 ) -> bool {
197 let Some(csp_list) = self else {
198 return false;
199 };
200
201 csp_list.does_sink_type_require_trusted_types(sink_group, include_report_only_policies)
202 }
203
204 fn should_sink_type_mismatch_violation_be_blocked_by_csp(
206 &self,
207 global: &GlobalScope,
208 sink: &str,
209 sink_group: &str,
210 source: &str,
211 ) -> bool {
212 let Some(csp_list) = self else {
213 return false;
214 };
215
216 let (allowed_by_csp, violations) = csp_list
217 .should_sink_type_mismatch_violation_be_blocked_by_csp(sink, sink_group, source);
218
219 global.report_csp_violations(violations, None, None);
220
221 allowed_by_csp == CheckResult::Blocked
222 }
223
224 fn concatenate(self, new_csp_list: Option<CspList>) -> Option<CspList> {
225 let Some(new_csp_list) = new_csp_list else {
226 return self;
227 };
228
229 match self {
230 None => Some(new_csp_list),
231 Some(mut old_csp_list) => {
232 old_csp_list.append(new_csp_list);
233 Some(old_csp_list)
234 },
235 }
236 }
237}
238
239pub(crate) struct SourcePosition {
240 pub(crate) source_file: String,
241 pub(crate) line_number: u32,
242 pub(crate) column_number: u32,
243}
244
245pub(crate) trait GlobalCspReporting {
246 fn report_csp_violations(
247 &self,
248 violations: Vec<Violation>,
249 element: Option<&Element>,
250 source_position: Option<SourcePosition>,
251 );
252}
253
254#[allow(unsafe_code)]
255fn compute_scripted_caller_source_position() -> SourcePosition {
256 let scripted_caller =
257 unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default();
258
259 SourcePosition {
260 source_file: scripted_caller.filename,
261 line_number: scripted_caller.line,
262 column_number: scripted_caller.col + 1,
263 }
264}
265
266impl GlobalCspReporting for GlobalScope {
267 fn report_csp_violations(
269 &self,
270 violations: Vec<Violation>,
271 element: Option<&Element>,
272 source_position: Option<SourcePosition>,
273 ) {
274 if violations.is_empty() {
275 return;
276 }
277 warn!("Reporting CSP violations: {:?}", violations);
278 let source_position =
279 source_position.unwrap_or_else(compute_scripted_caller_source_position);
280 for violation in violations {
281 let (sample, resource) = match violation.resource {
282 ViolationResource::Inline { sample } => (sample, "inline".to_owned()),
283 ViolationResource::Url(url) => (Some(String::new()), url.into()),
284 ViolationResource::TrustedTypePolicy { sample } => {
285 (Some(sample), "trusted-types-policy".to_owned())
286 },
287 ViolationResource::TrustedTypeSink { sample } => {
288 (Some(sample), "trusted-types-sink".to_owned())
289 },
290 ViolationResource::Eval { sample } => (sample, "eval".to_owned()),
291 ViolationResource::WasmEval => (None, "wasm-eval".to_owned()),
292 };
293 let report = CSPViolationReportBuilder::default()
294 .resource(resource)
295 .sample(sample)
296 .effective_directive(violation.directive.name)
297 .original_policy(violation.policy.to_string())
298 .report_only(violation.policy.disposition == PolicyDisposition::Report)
299 .source_file(source_position.source_file.clone())
300 .line_number(source_position.line_number)
301 .column_number(source_position.column_number)
302 .build(self);
303 let target = element.and_then(|event_target| {
307 if let Some(window) = self.downcast::<Window>() {
310 if event_target.upcast::<Node>().owner_document() != window.Document() {
314 return None;
315 }
316 }
317 Some(event_target)
318 });
319 let target = match target {
320 None => {
322 if let Some(window) = self.downcast::<Window>() {
324 Trusted::new(window.Document().upcast())
325 } else {
326 Trusted::new(self.upcast())
328 }
329 },
330 Some(event_target) => Trusted::new(event_target.upcast()),
331 };
332 let task =
334 CSPViolationReportTask::new(Trusted::new(self), target, report, violation.policy);
335 self.task_manager()
336 .dom_manipulation_task_source()
337 .queue(task);
338 }
339 }
340}
341
342fn parse_and_potentially_append_to_csp_list(
343 old_csp_list: Option<CspList>,
344 csp_header_iter: ValueIter<HeaderValue>,
345 disposition: PolicyDisposition,
346) -> Option<CspList> {
347 let mut csp_list = old_csp_list;
348 for header in csp_header_iter {
349 let new_csp_list = header
352 .to_str()
353 .ok()
354 .map(|value| CspList::parse(value, PolicySource::Header, disposition));
355 csp_list = csp_list.concatenate(new_csp_list);
356 }
357 csp_list
358}
359
360pub(crate) fn parse_csp_list_from_metadata(headers: &Option<Serde<HeaderMap>>) -> Option<CspList> {
362 let headers = headers.as_ref()?;
363 let csp_enforce_list = parse_and_potentially_append_to_csp_list(
364 None,
365 headers.get_all("content-security-policy").iter(),
366 PolicyDisposition::Enforce,
367 );
368
369 parse_and_potentially_append_to_csp_list(
370 csp_enforce_list,
371 headers
372 .get_all("content-security-policy-report-only")
373 .iter(),
374 PolicyDisposition::Report,
375 )
376}