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 servo_url::ServoUrl;
14use stylo_atoms::Atom;
15
16use crate::conversions::Convert;
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::bindings::refcounted::Trusted;
19use crate::dom::bindings::root::DomRoot;
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};
31use crate::fetch::network_listener::{
32 FetchResponseListener, ResourceTimingListener, submit_timing,
33};
34use crate::tasks::task::TaskOnce;
35
36pub(crate) struct CSPViolationReportTask {
37 global: Trusted<GlobalScope>,
38 event_target: Trusted<EventTarget>,
39 violation_report: SecurityPolicyViolationReport,
40 violation_policy: csp::Policy,
41}
42
43impl CSPViolationReportTask {
44 pub fn new(
45 global: Trusted<GlobalScope>,
46 event_target: Trusted<EventTarget>,
47 violation_report: SecurityPolicyViolationReport,
48 violation_policy: csp::Policy,
49 ) -> CSPViolationReportTask {
50 CSPViolationReportTask {
51 global,
52 event_target,
53 violation_report,
54 violation_policy,
55 }
56 }
57
58 fn fire_violation_event(&self, cx: &mut JSContext) {
59 let event = SecurityPolicyViolationEvent::new(
60 cx,
61 &self.global.root(),
62 Atom::from("securitypolicyviolation"),
63 EventBubbles::Bubbles,
64 EventCancelable::NotCancelable,
65 EventComposed::Composed,
66 &self.violation_report.clone().convert(),
67 );
68
69 event.upcast::<Event>().fire(cx, &self.event_target.root());
70 }
71
72 fn serialize_violation(&self) -> Option<RequestBody> {
74 let report_body = CSPReportUriViolationReport {
75 csp_report: self.violation_report.clone().into(),
77 };
78 Some(create_request_body_with_content(
80 serde_json::to_string(&report_body).unwrap_or_default(),
81 ))
82 }
83
84 fn post_csp_violation_to_report_uri(&self, report_uri_directive: &csp::Directive) {
86 let global = self.global.root();
87 if self
90 .violation_policy
91 .contains_a_directive_whose_name_is("report-to")
92 {
93 return;
94 }
95 for token in &report_uri_directive.value {
97 let Ok(endpoint) = ServoUrl::parse_with_base(Some(&global.get_url()), token) else {
103 continue;
105 };
106 let mut headers = HeaderMap::with_capacity(1);
108 headers.typed_insert(ContentType::from(
109 "application/csp-report".parse::<mime::Mime>().unwrap(),
110 ));
111 let request_body = self.serialize_violation();
112 let request = create_a_potential_cors_request(
113 None,
114 endpoint.clone(),
115 Destination::Report,
116 None,
117 None,
118 global.get_referrer(),
119 )
120 .with_global_scope(&global)
121 .method(http::Method::POST)
122 .body(request_body)
123 .credentials_mode(CredentialsMode::CredentialsSameOrigin)
124 .headers(headers);
125 global.fetch(
127 request,
128 CSPReportUriFetchListener {
129 endpoint,
130 global: Trusted::new(&global),
131 },
132 global.task_manager().networking_task_source().into(),
133 );
134 }
135 }
136}
137
138impl TaskOnce for CSPViolationReportTask {
142 fn run_once(self, cx: &mut JSContext) {
143 self.fire_violation_event(cx);
147 if let Some(report_uri_directive) = self
149 .violation_policy
150 .directive_set
151 .iter()
152 .find(|directive| directive.name == "report-uri")
153 {
154 self.post_csp_violation_to_report_uri(report_uri_directive);
155 }
156 if let Some(report_to_directive) = self
158 .violation_policy
159 .directive_set
160 .iter()
161 .find(|directive| directive.name == "report-to")
162 {
163 let body = self.violation_report.clone().convert();
165 ReportingObserver::generate_and_queue_a_report(
168 &self.global.root(),
169 "csp-violation".into(),
170 Some(body),
171 report_to_directive.value.join(" ").into(),
172 )
173 }
174 }
175}
176
177struct CSPReportUriFetchListener {
178 endpoint: ServoUrl,
180 global: Trusted<GlobalScope>,
182}
183
184impl FetchResponseListener for CSPReportUriFetchListener {
185 fn process_request_body(&mut self, _: RequestId) {}
186
187 fn process_response(
188 &mut self,
189 _: &mut JSContext,
190 _: RequestId,
191 fetch_metadata: Result<FetchMetadata, NetworkError>,
192 ) {
193 _ = fetch_metadata;
194 }
195
196 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Bytes) {
197 _ = chunk;
198 }
199
200 fn process_response_eof(
201 self,
202 cx: &mut JSContext,
203 _: RequestId,
204 response: Result<(), NetworkError>,
205 timing: ResourceFetchTiming,
206 ) {
207 submit_timing(cx, &self, &response, &timing)
208 }
209
210 fn process_csp_violations(
211 &mut self,
212 _cx: &mut JSContext,
213 _request_id: RequestId,
214 _violations: Vec<Violation>,
215 ) {
216 }
217}
218
219impl ResourceTimingListener for CSPReportUriFetchListener {
220 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
221 (InitiatorType::Other, self.endpoint.clone())
222 }
223
224 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
225 self.global.root()
226 }
227}