/* 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 crate::conversions::Convert; use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit; use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding::{ SecurityPolicyViolationEventDisposition, SecurityPolicyViolationEventInit, }; use crate::dom::globalscope::GlobalScope; #[derive(Clone, 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(Serialize)] #[serde(rename_all = "kebab-case")] pub(crate) struct CSPReportUriViolationReportBody { document_uri: String, referrer: String, blocked_uri: String, effective_directive: String, violated_directive: String, original_policy: String, #[serde(serialize_with = "serialize_disposition")] disposition: SecurityPolicyViolationEventDisposition, status_code: u16, script_sample: Option, source_file: Option, line_number: Option, column_number: Option, } #[derive(Serialize)] #[serde(rename_all = "kebab-case")] pub(crate) struct CSPReportUriViolationReport { pub(crate) csp_report: CSPReportUriViolationReportBody, } 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 From for CSPReportUriViolationReportBody { fn from(value: SecurityPolicyViolationReport) -> Self { // Step 1. Let body be a map with its keys initialized as follows: let mut converted = Self { document_uri: value.document_url, referrer: value.referrer, blocked_uri: value.blocked_url, effective_directive: value.effective_directive, violated_directive: value.violated_directive, original_policy: value.original_policy, disposition: value.disposition, status_code: value.status_code, script_sample: None, source_file: None, line_number: None, column_number: None, }; // Step 2. If violation’s source file is not null: if !value.source_file.is_empty() { // Step 2.1. Set body["source-file'] to the result of // executing § 5.4 Strip URL for use in reports on violation’s source file. converted.source_file = ServoUrl::parse(&value.source_file) .map(strip_url_for_reports) .ok(); // Step 2.2. Set body["line-number"] to violation’s line number. converted.line_number = Some(value.line_number); // Step 2.3. Set body["column-number"] to violation’s column number. converted.column_number = Some(value.column_number); } // Step 3. Assert: If body["blocked-uri"] is not "inline", then body["sample"] is the empty string. debug_assert!(converted.blocked_uri == "inline" || converted.script_sample.is_none()); converted } } #[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 } pub fn build(self, global: &GlobalScope) -> SecurityPolicyViolationReport { SecurityPolicyViolationReport { violated_directive: self.effective_directive.clone(), effective_directive: self.effective_directive.clone(), document_url: 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) => strip_url_for_reports(url), Referrer::ReferrerUrl(url) => 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), } } } fn serialize_disposition( val: &SecurityPolicyViolationEventDisposition, serializer: S, ) -> Result { match val { SecurityPolicyViolationEventDisposition::Report => serializer.serialize_str("report"), SecurityPolicyViolationEventDisposition::Enforce => serializer.serialize_str("enforce"), } } /// fn strip_url_for_reports(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() }