script/dom/security/
cspviolationreporttask.rs1use content_security_policy as csp;
6use headers::{ContentType, HeaderMap, HeaderMapExt};
7use js::context::JSContext;
8use net_traits::request::{
9 CredentialsMode, Destination, RequestBody, RequestId, create_request_body_with_content,
10};
11use net_traits::{FetchMetadata, NetworkError, ResourceFetchTiming};
12use servo_url::ServoUrl;
13use stylo_atoms::Atom;
14
15use crate::conversions::Convert;
16use crate::dom::bindings::inheritance::Castable;
17use crate::dom::bindings::refcounted::Trusted;
18use crate::dom::bindings::root::DomRoot;
19use crate::dom::csp::Violation;
20use crate::dom::csppolicyviolationreport::{
21 CSPReportUriViolationReport, SecurityPolicyViolationReport,
22};
23use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
24use crate::dom::eventtarget::EventTarget;
25use crate::dom::performance::performanceresourcetiming::InitiatorType;
26use crate::dom::reporting::reportingobserver::ReportingObserver;
27use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent;
28use crate::dom::types::GlobalScope;
29use crate::fetch::{RequestWithGlobalScope, create_a_potential_cors_request};
30use crate::network_listener::{FetchResponseListener, ResourceTimingListener, submit_timing};
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, cx: &mut JSContext) {
56 let event = SecurityPolicyViolationEvent::new(
57 cx,
58 &self.global.root(),
59 Atom::from("securitypolicyviolation"),
60 EventBubbles::Bubbles,
61 EventCancelable::NotCancelable,
62 EventComposed::Composed,
63 &self.violation_report.clone().convert(),
64 );
65
66 event.upcast::<Event>().fire(cx, &self.event_target.root());
67 }
68
69 fn serialize_violation(&self) -> Option<RequestBody> {
71 let report_body = CSPReportUriViolationReport {
72 csp_report: self.violation_report.clone().into(),
74 };
75 Some(create_request_body_with_content(
77 serde_json::to_string(&report_body).unwrap_or_default(),
78 ))
79 }
80
81 fn post_csp_violation_to_report_uri(&self, report_uri_directive: &csp::Directive) {
83 let global = self.global.root();
84 if self
87 .violation_policy
88 .contains_a_directive_whose_name_is("report-to")
89 {
90 return;
91 }
92 for token in &report_uri_directive.value {
94 let Ok(endpoint) = ServoUrl::parse_with_base(Some(&global.get_url()), token) else {
100 continue;
102 };
103 let mut headers = HeaderMap::with_capacity(1);
105 headers.typed_insert(ContentType::from(
106 "application/csp-report".parse::<mime::Mime>().unwrap(),
107 ));
108 let request_body = self.serialize_violation();
109 let request = create_a_potential_cors_request(
110 None,
111 endpoint.clone(),
112 Destination::Report,
113 None,
114 None,
115 global.get_referrer(),
116 )
117 .with_global_scope(&global)
118 .method(http::Method::POST)
119 .body(request_body)
120 .credentials_mode(CredentialsMode::CredentialsSameOrigin)
121 .headers(headers);
122 global.fetch(
124 request,
125 CSPReportUriFetchListener {
126 endpoint,
127 global: Trusted::new(&global),
128 },
129 global.task_manager().networking_task_source().into(),
130 );
131 }
132 }
133}
134
135impl TaskOnce for CSPViolationReportTask {
139 fn run_once(self, cx: &mut JSContext) {
140 self.fire_violation_event(cx);
144 if let Some(report_uri_directive) = self
146 .violation_policy
147 .directive_set
148 .iter()
149 .find(|directive| directive.name == "report-uri")
150 {
151 self.post_csp_violation_to_report_uri(report_uri_directive);
152 }
153 if let Some(report_to_directive) = self
155 .violation_policy
156 .directive_set
157 .iter()
158 .find(|directive| directive.name == "report-to")
159 {
160 let body = self.violation_report.clone().convert();
162 ReportingObserver::generate_and_queue_a_report(
165 &self.global.root(),
166 "csp-violation".into(),
167 Some(body),
168 report_to_directive.value.join(" ").into(),
169 )
170 }
171 }
172}
173
174struct CSPReportUriFetchListener {
175 endpoint: ServoUrl,
177 global: Trusted<GlobalScope>,
179}
180
181impl FetchResponseListener for CSPReportUriFetchListener {
182 fn process_request_body(&mut self, _: RequestId) {}
183
184 fn process_response(
185 &mut self,
186 _: &mut JSContext,
187 _: RequestId,
188 fetch_metadata: Result<FetchMetadata, NetworkError>,
189 ) {
190 _ = fetch_metadata;
191 }
192
193 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
194 _ = chunk;
195 }
196
197 fn process_response_eof(
198 self,
199 cx: &mut JSContext,
200 _: RequestId,
201 response: Result<(), NetworkError>,
202 timing: ResourceFetchTiming,
203 ) {
204 submit_timing(cx, &self, &response, &timing)
205 }
206
207 fn process_csp_violations(&mut self, _request_id: RequestId, _violations: Vec<Violation>) {}
208}
209
210impl ResourceTimingListener for CSPReportUriFetchListener {
211 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
212 (InitiatorType::Other, self.endpoint.clone())
213 }
214
215 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
216 self.global.root()
217 }
218}