/* 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 js::jsapi::RuntimeCode; use net_traits::request::Referrer; use serde::Serialize; use servo_atoms::Atom; use servo_url::ServoUrl; 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::bindings::reflector::DomObject; use crate::dom::event::{Event, EventBubbles, EventCancelable}; 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 struct CSPViolationReporter { sample: Option, filename: String, report_only: bool, runtime_code: RuntimeCode, line_number: u32, column_number: u32, target: Trusted, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub 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, disposition: SecurityPolicyViolationEventDisposition, } impl CSPViolationReporter { pub fn new( global: &GlobalScope, sample: Option, report_only: bool, runtime_code: RuntimeCode, filename: String, line_number: u32, column_number: u32, ) -> CSPViolationReporter { CSPViolationReporter { sample, filename, report_only, runtime_code, line_number, column_number, target: Trusted::new(global.upcast::()), } } fn get_report(&self, global: &GlobalScope) -> SecurityPolicyViolationReport { SecurityPolicyViolationReport { sample: self.sample.clone(), disposition: match self.report_only { true => SecurityPolicyViolationEventDisposition::Report, false => SecurityPolicyViolationEventDisposition::Enforce, }, // https://w3c.github.io/webappsec-csp/#violation-resource blocked_url: match self.runtime_code { RuntimeCode::JS => "eval".to_owned(), RuntimeCode::WASM => "wasm-eval".to_owned(), }, // 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(), }, status_code: global.status_code().unwrap_or(200), document_url: self.strip_url_for_reports(global.get_url()), source_file: self.filename.clone(), violated_directive: "script-src".to_owned(), effective_directive: "script-src".to_owned(), line_number: self.line_number, column_number: self.column_number, original_policy: String::default(), } } fn fire_violation_event(&self, can_gc: CanGc) { let target = self.target.root(); let global = &target.global(); let report = self.get_report(global); let event = SecurityPolicyViolationEvent::new( global, Atom::from("securitypolicyviolation"), EventBubbles::Bubbles, EventCancelable::Cancelable, &report.convert(), can_gc, ); event.upcast::().fire(&target, can_gc); } /// 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() } } /// Corresponds to the operation in 5.5 Report Violation /// /// > Queue a task to run the following steps: impl TaskOnce for CSPViolationReporter { 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(), } } } impl Serialize for SecurityPolicyViolationEventDisposition { fn serialize(&self, serializer: S) -> Result { match self { Self::Report => serializer.serialize_str("report"), Self::Enforce => serializer.serialize_str("enforce"), } } }