/* 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 net_traits::request::Referrer; use serde::Serialize; use servo_url::ServoUrl; use stylo_atoms::Atom; use crate::conversions::Convert; use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit; use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding::{ SecurityPolicyViolationEventDisposition, SecurityPolicyViolationEventInit, }; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed}; use crate::dom::eventtarget::EventTarget; use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent; use crate::dom::types::GlobalScope; use crate::script_runtime::CanGc; use crate::task::TaskOnce; pub(crate) struct CSPViolationReportTask { global: Trusted, event_target: Trusted, violation_report: SecurityPolicyViolationReport, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct SecurityPolicyViolationReport { sample: Option, #[serde(rename = "blockedURL")] blocked_url: String, referrer: String, status_code: u16, #[serde(rename = "documentURL")] document_url: String, source_file: String, violated_directive: String, effective_directive: String, line_number: u32, column_number: u32, original_policy: String, #[serde(serialize_with = "serialize_disposition")] disposition: SecurityPolicyViolationEventDisposition, } #[derive(Default)] pub(crate) struct CSPViolationReportBuilder { pub report_only: bool, /// pub sample: Option, /// pub resource: String, /// pub line_number: u32, /// pub column_number: u32, /// pub source_file: String, /// pub effective_directive: String, /// pub original_policy: String, } impl CSPViolationReportBuilder { pub fn report_only(mut self, report_only: bool) -> CSPViolationReportBuilder { self.report_only = report_only; self } /// pub fn sample(mut self, sample: Option) -> CSPViolationReportBuilder { self.sample = sample; self } /// pub fn resource(mut self, resource: String) -> CSPViolationReportBuilder { self.resource = resource; self } /// pub fn line_number(mut self, line_number: u32) -> CSPViolationReportBuilder { self.line_number = line_number; self } /// pub fn column_number(mut self, column_number: u32) -> CSPViolationReportBuilder { self.column_number = column_number; self } /// pub fn source_file(mut self, source_file: String) -> CSPViolationReportBuilder { self.source_file = source_file; self } /// pub fn effective_directive(mut self, effective_directive: String) -> CSPViolationReportBuilder { self.effective_directive = effective_directive; self } /// pub fn original_policy(mut self, original_policy: String) -> CSPViolationReportBuilder { self.original_policy = original_policy; self } /// fn strip_url_for_reports(&self, mut url: ServoUrl) -> String { let scheme = url.scheme(); // > Step 1: If url’s scheme is not an HTTP(S) scheme, then return url’s scheme. if scheme != "https" && scheme != "http" { return scheme.to_owned(); } // > Step 2: Set url’s fragment to the empty string. url.set_fragment(None); // > Step 3: Set url’s username to the empty string. let _ = url.set_username(""); // > Step 4: Set url’s password to the empty string. let _ = url.set_password(None); // > Step 5: Return the result of executing the URL serializer on url. url.into_string() } pub fn build(self, global: &GlobalScope) -> SecurityPolicyViolationReport { SecurityPolicyViolationReport { violated_directive: self.effective_directive.clone(), effective_directive: self.effective_directive.clone(), document_url: self.strip_url_for_reports(global.get_url()), disposition: match self.report_only { true => SecurityPolicyViolationEventDisposition::Report, false => SecurityPolicyViolationEventDisposition::Enforce, }, // https://w3c.github.io/webappsec-csp/#violation-referrer referrer: match global.get_referrer() { Referrer::Client(url) => self.strip_url_for_reports(url), Referrer::ReferrerUrl(url) => self.strip_url_for_reports(url), _ => "".to_owned(), }, sample: self.sample, blocked_url: self.resource, source_file: self.source_file, original_policy: self.original_policy, line_number: self.line_number, column_number: self.column_number, status_code: global.status_code().unwrap_or(0), } } } impl CSPViolationReportTask { pub fn new( global: Trusted, event_target: Trusted, violation_report: SecurityPolicyViolationReport, ) -> CSPViolationReportTask { CSPViolationReportTask { global, event_target, violation_report, } } fn fire_violation_event(self, can_gc: CanGc) { let event = SecurityPolicyViolationEvent::new( &self.global.root(), Atom::from("securitypolicyviolation"), EventBubbles::Bubbles, EventCancelable::Cancelable, EventComposed::Composed, &self.violation_report.convert(), can_gc, ); event .upcast::() .fire(&self.event_target.root(), can_gc); } } /// Corresponds to the operation in 5.5 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()); // TODO: Support `report-to` directive that corresponds to 5.5.3.5. } } impl Convert for SecurityPolicyViolationReport { fn convert(self) -> SecurityPolicyViolationEventInit { SecurityPolicyViolationEventInit { sample: self.sample.unwrap_or_default().into(), blockedURI: self.blocked_url.into(), referrer: self.referrer.into(), statusCode: self.status_code, documentURI: self.document_url.into(), sourceFile: self.source_file.into(), violatedDirective: self.violated_directive.into(), effectiveDirective: self.effective_directive.into(), lineNumber: self.line_number, columnNumber: self.column_number, originalPolicy: self.original_policy.into(), disposition: self.disposition, parent: EventInit::empty(), } } } fn serialize_disposition( val: &SecurityPolicyViolationEventDisposition, serializer: S, ) -> Result { match val { SecurityPolicyViolationEventDisposition::Report => serializer.serialize_str("report"), SecurityPolicyViolationEventDisposition::Enforce => serializer.serialize_str("enforce"), } }