| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
| |
| use std::sync::{Arc, Mutex}; |
| |
| use content_security_policy as csp; |
| use headers::{ContentType, HeaderMap, HeaderMapExt}; |
| use net_traits::request::{ |
| CredentialsMode, Destination, RequestBody, RequestId, create_request_body_with_content, |
| }; |
| use net_traits::{ |
| FetchMetadata, FetchResponseListener, NetworkError, ResourceFetchTiming, ResourceTimingType, |
| }; |
| use servo_url::ServoUrl; |
| use stylo_atoms::Atom; |
| |
| use crate::conversions::Convert; |
| use crate::dom::bindings::inheritance::Castable; |
| use crate::dom::bindings::refcounted::Trusted; |
| use crate::dom::bindings::root::DomRoot; |
| use crate::dom::csp::Violation; |
| use crate::dom::csppolicyviolationreport::{ |
| CSPReportUriViolationReport, SecurityPolicyViolationReport, |
| }; |
| use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed}; |
| use crate::dom::eventtarget::EventTarget; |
| use crate::dom::performanceresourcetiming::InitiatorType; |
| use crate::dom::reportingobserver::ReportingObserver; |
| use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent; |
| use crate::dom::types::GlobalScope; |
| use crate::fetch::create_a_potential_cors_request; |
| use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing}; |
| use crate::script_runtime::CanGc; |
| use crate::task::TaskOnce; |
| |
| pub(crate) struct CSPViolationReportTask { |
| global: Trusted<GlobalScope>, |
| event_target: Trusted<EventTarget>, |
| violation_report: SecurityPolicyViolationReport, |
| violation_policy: csp::Policy, |
| } |
| |
| impl CSPViolationReportTask { |
| pub fn new( |
| global: Trusted<GlobalScope>, |
| event_target: Trusted<EventTarget>, |
| violation_report: SecurityPolicyViolationReport, |
| violation_policy: csp::Policy, |
| ) -> CSPViolationReportTask { |
| CSPViolationReportTask { |
| global, |
| event_target, |
| violation_report, |
| violation_policy, |
| } |
| } |
| |
| fn fire_violation_event(&self, can_gc: CanGc) { |
| let event = SecurityPolicyViolationEvent::new( |
| &self.global.root(), |
| Atom::from("securitypolicyviolation"), |
| EventBubbles::Bubbles, |
| EventCancelable::NotCancelable, |
| EventComposed::Composed, |
| &self.violation_report.clone().convert(), |
| can_gc, |
| ); |
| |
| event |
| .upcast::<Event>() |
| .fire(&self.event_target.root(), can_gc); |
| } |
| |
| /// <https://www.w3.org/TR/CSP/#deprecated-serialize-violation> |
| fn serialize_violation(&self) -> Option<RequestBody> { |
| let report_body = CSPReportUriViolationReport { |
| // Steps 1-3. |
| csp_report: self.violation_report.clone().into(), |
| }; |
| // Step 4. Return the result of serialize an infra value to JSON bytes given «[ "csp-report" → body ]». |
| Some(create_request_body_with_content( |
| &serde_json::to_string(&report_body).unwrap_or("".to_owned()), |
| )) |
| } |
| |
| /// Step 3.4 of <https://www.w3.org/TR/CSP/#report-violation> |
| fn post_csp_violation_to_report_uri(&self, report_uri_directive: &csp::Directive) { |
| let global = self.global.root(); |
| // Step 3.4.1. If violation’s policy’s directive set contains a directive named |
| // "report-to", skip the remaining substeps. |
| if self |
| .violation_policy |
| .contains_a_directive_whose_name_is("report-to") |
| { |
| return; |
| } |
| // Step 3.4.2. For each token of directive’s value: |
| for token in &report_uri_directive.value { |
| // Step 3.4.2.1. Let endpoint be the result of executing the URL parser with token as the input, |
| // and violation’s url as the base URL. |
| // |
| // TODO: Figure out if this should be the URL of the containing document or not in case |
| // the url points to a blob |
| let Ok(endpoint) = ServoUrl::parse_with_base(Some(&global.get_url()), token) else { |
| // Step 3.4.2.2. If endpoint is not a valid URL, skip the remaining substeps. |
| continue; |
| }; |
| // Step 3.4.2.3. Let request be a new request, initialized as follows: |
| let mut headers = HeaderMap::with_capacity(1); |
| headers.typed_insert(ContentType::from( |
| "application/csp-report".parse::<mime::Mime>().unwrap(), |
| )); |
| let request_body = self.serialize_violation(); |
| let request = create_a_potential_cors_request( |
| None, |
| endpoint.clone(), |
| Destination::Report, |
| None, |
| None, |
| global.get_referrer(), |
| global.insecure_requests_policy(), |
| global.has_trustworthy_ancestor_or_current_origin(), |
| global.policy_container(), |
| ) |
| .method(http::Method::POST) |
| .body(request_body) |
| .origin(global.origin().immutable().clone()) |
| .credentials_mode(CredentialsMode::CredentialsSameOrigin) |
| .headers(headers); |
| // Step 3.4.2.4. Fetch request. The result will be ignored. |
| global.fetch( |
| request, |
| Arc::new(Mutex::new(CSPReportUriFetchListener { |
| endpoint, |
| global: Trusted::new(&global), |
| resource_timing: ResourceFetchTiming::new(ResourceTimingType::None), |
| })), |
| global.task_manager().networking_task_source().into(), |
| ); |
| } |
| } |
| } |
| |
| /// Corresponds to the operation in 5.5 Report Violation |
| /// <https://w3c.github.io/webappsec-csp/#report-violation> |
| /// > Queue a task to run the following steps: |
| impl TaskOnce for CSPViolationReportTask { |
| fn run_once(self) { |
| // > If target implements EventTarget, fire an event named securitypolicyviolation |
| // > that uses the SecurityPolicyViolationEvent interface |
| // > at target with its attributes initialized as follows: |
| self.fire_violation_event(CanGc::note()); |
| // Step 3.4. If violation’s policy’s directive set contains a directive named "report-uri" directive: |
| if let Some(report_uri_directive) = self |
| .violation_policy |
| .directive_set |
| .iter() |
| .find(|directive| directive.name == "report-uri") |
| { |
| self.post_csp_violation_to_report_uri(report_uri_directive); |
| } |
| // Step 3.5. If violation’s policy’s directive set contains a directive named "report-to" directive: |
| if let Some(report_to_directive) = self |
| .violation_policy |
| .directive_set |
| .iter() |
| .find(|directive| directive.name == "report-to") |
| { |
| // Step 3.5.1. Let body be a new CSPViolationReportBody, initialized as follows: |
| let body = self.violation_report.clone().convert(); |
| // Step 3.5.2. Let settings object be violation’s global object’s relevant settings object. |
| // Step 3.5.3. Generate and queue a report with the following arguments: |
| ReportingObserver::generate_and_queue_a_report( |
| &self.global.root(), |
| "csp-violation".into(), |
| Some(body), |
| report_to_directive.value.join(" ").into(), |
| ) |
| } |
| } |
| } |
| |
| struct CSPReportUriFetchListener { |
| /// Endpoint URL of this request. |
| endpoint: ServoUrl, |
| /// Timing data for this resource. |
| resource_timing: ResourceFetchTiming, |
| /// The global object fetching the report uri violation |
| global: Trusted<GlobalScope>, |
| } |
| |
| impl FetchResponseListener for CSPReportUriFetchListener { |
| fn process_request_body(&mut self, _: RequestId) {} |
| |
| fn process_request_eof(&mut self, _: RequestId) {} |
| |
| fn process_response( |
| &mut self, |
| _: RequestId, |
| fetch_metadata: Result<FetchMetadata, NetworkError>, |
| ) { |
| _ = fetch_metadata; |
| } |
| |
| fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) { |
| _ = chunk; |
| } |
| |
| fn process_response_eof( |
| &mut self, |
| _: RequestId, |
| response: Result<ResourceFetchTiming, NetworkError>, |
| ) { |
| _ = response; |
| } |
| |
| fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming { |
| &mut self.resource_timing |
| } |
| |
| fn resource_timing(&self) -> &ResourceFetchTiming { |
| &self.resource_timing |
| } |
| |
| fn submit_resource_timing(&mut self) { |
| submit_timing(self, CanGc::note()) |
| } |
| |
| fn process_csp_violations(&mut self, _request_id: RequestId, _violations: Vec<Violation>) {} |
| } |
| |
| impl ResourceTimingListener for CSPReportUriFetchListener { |
| fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) { |
| (InitiatorType::Other, self.endpoint.clone()) |
| } |
| |
| fn resource_timing_global(&self) -> DomRoot<GlobalScope> { |
| self.global.root() |
| } |
| } |
| |
| impl PreInvoke for CSPReportUriFetchListener { |
| fn should_invoke(&self) -> bool { |
| true |
| } |
| } |