script/
security_manager.rs1use std::sync::{Arc, Mutex};
6
7use content_security_policy as csp;
8use headers::{ContentType, HeaderMap, HeaderMapExt};
9use net_traits::request::{
10 CredentialsMode, Destination, RequestBody, RequestId, create_request_body_with_content,
11};
12use net_traits::{
13 FetchMetadata, FetchResponseListener, NetworkError, ResourceFetchTiming, ResourceTimingType,
14};
15use servo_url::ServoUrl;
16use stylo_atoms::Atom;
17
18use crate::conversions::Convert;
19use crate::dom::bindings::inheritance::Castable;
20use crate::dom::bindings::refcounted::Trusted;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::csp::Violation;
23use crate::dom::csppolicyviolationreport::{
24 CSPReportUriViolationReport, SecurityPolicyViolationReport,
25};
26use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
27use crate::dom::eventtarget::EventTarget;
28use crate::dom::performanceresourcetiming::InitiatorType;
29use crate::dom::reportingobserver::ReportingObserver;
30use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent;
31use crate::dom::types::GlobalScope;
32use crate::fetch::create_a_potential_cors_request;
33use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing};
34use crate::script_runtime::CanGc;
35use crate::task::TaskOnce;
36
37pub(crate) struct CSPViolationReportTask {
38 global: Trusted<GlobalScope>,
39 event_target: Trusted<EventTarget>,
40 violation_report: SecurityPolicyViolationReport,
41 violation_policy: csp::Policy,
42}
43
44impl CSPViolationReportTask {
45 pub fn new(
46 global: Trusted<GlobalScope>,
47 event_target: Trusted<EventTarget>,
48 violation_report: SecurityPolicyViolationReport,
49 violation_policy: csp::Policy,
50 ) -> CSPViolationReportTask {
51 CSPViolationReportTask {
52 global,
53 event_target,
54 violation_report,
55 violation_policy,
56 }
57 }
58
59 fn fire_violation_event(&self, can_gc: CanGc) {
60 let event = SecurityPolicyViolationEvent::new(
61 &self.global.root(),
62 Atom::from("securitypolicyviolation"),
63 EventBubbles::Bubbles,
64 EventCancelable::NotCancelable,
65 EventComposed::Composed,
66 &self.violation_report.clone().convert(),
67 can_gc,
68 );
69
70 event
71 .upcast::<Event>()
72 .fire(&self.event_target.root(), can_gc);
73 }
74
75 fn serialize_violation(&self) -> Option<RequestBody> {
77 let report_body = CSPReportUriViolationReport {
78 csp_report: self.violation_report.clone().into(),
80 };
81 Some(create_request_body_with_content(
83 &serde_json::to_string(&report_body).unwrap_or("".to_owned()),
84 ))
85 }
86
87 fn post_csp_violation_to_report_uri(&self, report_uri_directive: &csp::Directive) {
89 let global = self.global.root();
90 if self
93 .violation_policy
94 .contains_a_directive_whose_name_is("report-to")
95 {
96 return;
97 }
98 for token in &report_uri_directive.value {
100 let Ok(endpoint) = ServoUrl::parse_with_base(Some(&global.get_url()), token) else {
106 continue;
108 };
109 let mut headers = HeaderMap::with_capacity(1);
111 headers.typed_insert(ContentType::from(
112 "application/csp-report".parse::<mime::Mime>().unwrap(),
113 ));
114 let request_body = self.serialize_violation();
115 let request = create_a_potential_cors_request(
116 None,
117 endpoint.clone(),
118 Destination::Report,
119 None,
120 None,
121 global.get_referrer(),
122 global.insecure_requests_policy(),
123 global.has_trustworthy_ancestor_or_current_origin(),
124 global.policy_container(),
125 )
126 .method(http::Method::POST)
127 .body(request_body)
128 .origin(global.origin().immutable().clone())
129 .credentials_mode(CredentialsMode::CredentialsSameOrigin)
130 .headers(headers);
131 global.fetch(
133 request,
134 Arc::new(Mutex::new(CSPReportUriFetchListener {
135 endpoint,
136 global: Trusted::new(&global),
137 resource_timing: ResourceFetchTiming::new(ResourceTimingType::None),
138 })),
139 global.task_manager().networking_task_source().into(),
140 );
141 }
142 }
143}
144
145impl TaskOnce for CSPViolationReportTask {
149 fn run_once(self) {
150 self.fire_violation_event(CanGc::note());
154 if let Some(report_uri_directive) = self
156 .violation_policy
157 .directive_set
158 .iter()
159 .find(|directive| directive.name == "report-uri")
160 {
161 self.post_csp_violation_to_report_uri(report_uri_directive);
162 }
163 if let Some(report_to_directive) = self
165 .violation_policy
166 .directive_set
167 .iter()
168 .find(|directive| directive.name == "report-to")
169 {
170 let body = self.violation_report.clone().convert();
172 ReportingObserver::generate_and_queue_a_report(
175 &self.global.root(),
176 "csp-violation".into(),
177 Some(body),
178 report_to_directive.value.join(" ").into(),
179 )
180 }
181 }
182}
183
184struct CSPReportUriFetchListener {
185 endpoint: ServoUrl,
187 resource_timing: ResourceFetchTiming,
189 global: Trusted<GlobalScope>,
191}
192
193impl FetchResponseListener for CSPReportUriFetchListener {
194 fn process_request_body(&mut self, _: RequestId) {}
195
196 fn process_request_eof(&mut self, _: RequestId) {}
197
198 fn process_response(
199 &mut self,
200 _: RequestId,
201 fetch_metadata: Result<FetchMetadata, NetworkError>,
202 ) {
203 _ = fetch_metadata;
204 }
205
206 fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
207 _ = chunk;
208 }
209
210 fn process_response_eof(
211 &mut self,
212 _: RequestId,
213 response: Result<ResourceFetchTiming, NetworkError>,
214 ) {
215 _ = response;
216 }
217
218 fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
219 &mut self.resource_timing
220 }
221
222 fn resource_timing(&self) -> &ResourceFetchTiming {
223 &self.resource_timing
224 }
225
226 fn submit_resource_timing(&mut self) {
227 submit_timing(self, CanGc::note())
228 }
229
230 fn process_csp_violations(&mut self, _request_id: RequestId, _violations: Vec<Violation>) {}
231}
232
233impl ResourceTimingListener for CSPReportUriFetchListener {
234 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
235 (InitiatorType::Other, self.endpoint.clone())
236 }
237
238 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
239 self.global.root()
240 }
241}
242
243impl PreInvoke for CSPReportUriFetchListener {
244 fn should_invoke(&self) -> bool {
245 true
246 }
247}