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