Skip to main content

script/dom/security/
cspviolationreporttask.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    /// <https://www.w3.org/TR/CSP/#deprecated-serialize-violation>
70    fn serialize_violation(&self) -> Option<RequestBody> {
71        let report_body = CSPReportUriViolationReport {
72            // Steps 1-3.
73            csp_report: self.violation_report.clone().into(),
74        };
75        // Step 4. Return the result of serialize an infra value to JSON bytes given «[ "csp-report" → body ]».
76        Some(create_request_body_with_content(
77            serde_json::to_string(&report_body).unwrap_or_default(),
78        ))
79    }
80
81    /// Step 3.4 of <https://www.w3.org/TR/CSP/#report-violation>
82    fn post_csp_violation_to_report_uri(&self, report_uri_directive: &csp::Directive) {
83        let global = self.global.root();
84        // Step 3.4.1. If violation’s policy’s directive set contains a directive named
85        // "report-to", skip the remaining substeps.
86        if self
87            .violation_policy
88            .contains_a_directive_whose_name_is("report-to")
89        {
90            return;
91        }
92        // Step 3.4.2. For each token of directive’s value:
93        for token in &report_uri_directive.value {
94            // Step 3.4.2.1. Let endpoint be the result of executing the URL parser with token as the input,
95            // and violation’s url as the base URL.
96            //
97            // TODO: Figure out if this should be the URL of the containing document or not in case
98            // the url points to a blob
99            let Ok(endpoint) = ServoUrl::parse_with_base(Some(&global.get_url()), token) else {
100                // Step 3.4.2.2. If endpoint is not a valid URL, skip the remaining substeps.
101                continue;
102            };
103            // Step 3.4.2.3. Let request be a new request, initialized as follows:
104            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            // Step 3.4.2.4. Fetch request. The result will be ignored.
123            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
135/// Corresponds to the operation in 5.5 Report Violation
136/// <https://w3c.github.io/webappsec-csp/#report-violation>
137/// > Queue a task to run the following steps:
138impl TaskOnce for CSPViolationReportTask {
139    fn run_once(self, cx: &mut JSContext) {
140        // > If target implements EventTarget, fire an event named securitypolicyviolation
141        // > that uses the SecurityPolicyViolationEvent interface
142        // > at target with its attributes initialized as follows:
143        self.fire_violation_event(cx);
144        // Step 3.4. If violation’s policy’s directive set contains a directive named "report-uri" directive:
145        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        // Step 3.5. If violation’s policy’s directive set contains a directive named "report-to" directive:
154        if let Some(report_to_directive) = self
155            .violation_policy
156            .directive_set
157            .iter()
158            .find(|directive| directive.name == "report-to")
159        {
160            // Step 3.5.1. Let body be a new CSPViolationReportBody, initialized as follows:
161            let body = self.violation_report.clone().convert();
162            // Step 3.5.2. Let settings object be violation’s global object’s relevant settings object.
163            // Step 3.5.3. Generate and queue a report with the following arguments:
164            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 URL of this request.
176    endpoint: ServoUrl,
177    /// The global object fetching the report uri violation
178    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}