script/
security_manager.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 std::sync::{Arc, Mutex};
6
7use content_security_policy as csp;
8use headers::{ContentType, HeaderMap, HeaderMapExt};
9use net_traits::request::{
10    CredentialsMode, Destination, RequestBody, RequestId, create_request_body_with_content,
11};
12use net_traits::{
13    FetchMetadata, FetchResponseListener, NetworkError, ResourceFetchTiming, ResourceTimingType,
14};
15use servo_url::ServoUrl;
16use stylo_atoms::Atom;
17
18use crate::conversions::Convert;
19use crate::dom::bindings::inheritance::Castable;
20use crate::dom::bindings::refcounted::Trusted;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::csp::Violation;
23use crate::dom::csppolicyviolationreport::{
24    CSPReportUriViolationReport, SecurityPolicyViolationReport,
25};
26use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
27use crate::dom::eventtarget::EventTarget;
28use crate::dom::performanceresourcetiming::InitiatorType;
29use crate::dom::reportingobserver::ReportingObserver;
30use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent;
31use crate::dom::types::GlobalScope;
32use crate::fetch::create_a_potential_cors_request;
33use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing};
34use crate::script_runtime::CanGc;
35use crate::task::TaskOnce;
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, can_gc: CanGc) {
60        let event = SecurityPolicyViolationEvent::new(
61            &self.global.root(),
62            Atom::from("securitypolicyviolation"),
63            EventBubbles::Bubbles,
64            EventCancelable::NotCancelable,
65            EventComposed::Composed,
66            &self.violation_report.clone().convert(),
67            can_gc,
68        );
69
70        event
71            .upcast::<Event>()
72            .fire(&self.event_target.root(), can_gc);
73    }
74
75    /// <https://www.w3.org/TR/CSP/#deprecated-serialize-violation>
76    fn serialize_violation(&self) -> Option<RequestBody> {
77        let report_body = CSPReportUriViolationReport {
78            // Steps 1-3.
79            csp_report: self.violation_report.clone().into(),
80        };
81        // Step 4. Return the result of serialize an infra value to JSON bytes given «[ "csp-report" → body ]».
82        Some(create_request_body_with_content(
83            &serde_json::to_string(&report_body).unwrap_or("".to_owned()),
84        ))
85    }
86
87    /// Step 3.4 of <https://www.w3.org/TR/CSP/#report-violation>
88    fn post_csp_violation_to_report_uri(&self, report_uri_directive: &csp::Directive) {
89        let global = self.global.root();
90        // Step 3.4.1. If violation’s policy’s directive set contains a directive named
91        // "report-to", skip the remaining substeps.
92        if self
93            .violation_policy
94            .contains_a_directive_whose_name_is("report-to")
95        {
96            return;
97        }
98        // Step 3.4.2. For each token of directive’s value:
99        for token in &report_uri_directive.value {
100            // Step 3.4.2.1. Let endpoint be the result of executing the URL parser with token as the input,
101            // and violation’s url as the base URL.
102            //
103            // TODO: Figure out if this should be the URL of the containing document or not in case
104            // the url points to a blob
105            let Ok(endpoint) = ServoUrl::parse_with_base(Some(&global.get_url()), token) else {
106                // Step 3.4.2.2. If endpoint is not a valid URL, skip the remaining substeps.
107                continue;
108            };
109            // Step 3.4.2.3. Let request be a new request, initialized as follows:
110            let mut headers = HeaderMap::with_capacity(1);
111            headers.typed_insert(ContentType::from(
112                "application/csp-report".parse::<mime::Mime>().unwrap(),
113            ));
114            let request_body = self.serialize_violation();
115            let request = create_a_potential_cors_request(
116                None,
117                endpoint.clone(),
118                Destination::Report,
119                None,
120                None,
121                global.get_referrer(),
122                global.insecure_requests_policy(),
123                global.has_trustworthy_ancestor_or_current_origin(),
124                global.policy_container(),
125            )
126            .method(http::Method::POST)
127            .body(request_body)
128            .origin(global.origin().immutable().clone())
129            .credentials_mode(CredentialsMode::CredentialsSameOrigin)
130            .headers(headers);
131            // Step 3.4.2.4. Fetch request. The result will be ignored.
132            global.fetch(
133                request,
134                Arc::new(Mutex::new(CSPReportUriFetchListener {
135                    endpoint,
136                    global: Trusted::new(&global),
137                    resource_timing: ResourceFetchTiming::new(ResourceTimingType::None),
138                })),
139                global.task_manager().networking_task_source().into(),
140            );
141        }
142    }
143}
144
145/// Corresponds to the operation in 5.5 Report Violation
146/// <https://w3c.github.io/webappsec-csp/#report-violation>
147/// > Queue a task to run the following steps:
148impl TaskOnce for CSPViolationReportTask {
149    fn run_once(self) {
150        // > If target implements EventTarget, fire an event named securitypolicyviolation
151        // > that uses the SecurityPolicyViolationEvent interface
152        // > at target with its attributes initialized as follows:
153        self.fire_violation_event(CanGc::note());
154        // Step 3.4. If violation’s policy’s directive set contains a directive named "report-uri" directive:
155        if let Some(report_uri_directive) = self
156            .violation_policy
157            .directive_set
158            .iter()
159            .find(|directive| directive.name == "report-uri")
160        {
161            self.post_csp_violation_to_report_uri(report_uri_directive);
162        }
163        // Step 3.5. If violation’s policy’s directive set contains a directive named "report-to" directive:
164        if let Some(report_to_directive) = self
165            .violation_policy
166            .directive_set
167            .iter()
168            .find(|directive| directive.name == "report-to")
169        {
170            // Step 3.5.1. Let body be a new CSPViolationReportBody, initialized as follows:
171            let body = self.violation_report.clone().convert();
172            // Step 3.5.2. Let settings object be violation’s global object’s relevant settings object.
173            // Step 3.5.3. Generate and queue a report with the following arguments:
174            ReportingObserver::generate_and_queue_a_report(
175                &self.global.root(),
176                "csp-violation".into(),
177                Some(body),
178                report_to_directive.value.join(" ").into(),
179            )
180        }
181    }
182}
183
184struct CSPReportUriFetchListener {
185    /// Endpoint URL of this request.
186    endpoint: ServoUrl,
187    /// Timing data for this resource.
188    resource_timing: ResourceFetchTiming,
189    /// The global object fetching the report uri violation
190    global: Trusted<GlobalScope>,
191}
192
193impl FetchResponseListener for CSPReportUriFetchListener {
194    fn process_request_body(&mut self, _: RequestId) {}
195
196    fn process_request_eof(&mut self, _: RequestId) {}
197
198    fn process_response(
199        &mut self,
200        _: RequestId,
201        fetch_metadata: Result<FetchMetadata, NetworkError>,
202    ) {
203        _ = fetch_metadata;
204    }
205
206    fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
207        _ = chunk;
208    }
209
210    fn process_response_eof(
211        &mut self,
212        _: RequestId,
213        response: Result<ResourceFetchTiming, NetworkError>,
214    ) {
215        _ = response;
216    }
217
218    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
219        &mut self.resource_timing
220    }
221
222    fn resource_timing(&self) -> &ResourceFetchTiming {
223        &self.resource_timing
224    }
225
226    fn submit_resource_timing(&mut self) {
227        submit_timing(self, CanGc::note())
228    }
229
230    fn process_csp_violations(&mut self, _request_id: RequestId, _violations: Vec<Violation>) {}
231}
232
233impl ResourceTimingListener for CSPReportUriFetchListener {
234    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
235        (InitiatorType::Other, self.endpoint.clone())
236    }
237
238    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
239        self.global.root()
240    }
241}
242
243impl PreInvoke for CSPReportUriFetchListener {
244    fn should_invoke(&self) -> bool {
245        true
246    }
247}