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