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