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::bindings::reflector::DomGlobal;
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(crate) struct CSPViolationReportTask {
event_target: Trusted<EventTarget>,
violation_report: SecurityPolicyViolationReport,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct SecurityPolicyViolationReport {
sample: Option<String>,
#[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<String>,
pub resource: String,
pub line_number: u32,
pub column_number: u32,
pub source_file: String,
pub effective_directive: 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<String>) -> 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
}
fn strip_url_for_reports(&self, mut url: ServoUrl) -> String {
let scheme = url.scheme();
if scheme != "https" && scheme != "http" {
return scheme.to_owned();
}
url.set_fragment(None);
let _ = url.set_username("");
let _ = url.set_password(None);
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,
},
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: "".to_owned(),
line_number: self.line_number,
column_number: self.column_number,
status_code: global.status_code().unwrap_or(0),
}
}
}
impl CSPViolationReportTask {
pub fn new(
global: &GlobalScope,
report: SecurityPolicyViolationReport,
) -> CSPViolationReportTask {
CSPViolationReportTask {
violation_report: report,
event_target: Trusted::new(global.upcast::<EventTarget>()),
}
}
fn fire_violation_event(self, can_gc: CanGc) {
let target = self.event_target.root();
let global = &target.global();
let event = SecurityPolicyViolationEvent::new(
global,
Atom::from("securitypolicyviolation"),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
&self.violation_report.convert(),
can_gc,
);
event.upcast::<Event>().fire(&target, can_gc);
}
}
impl TaskOnce for CSPViolationReportTask {
fn run_once(self) {
self.fire_violation_event(CanGc::note());
}
}
impl Convert<SecurityPolicyViolationEventInit> 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<S: serde::Serializer>(
val: &SecurityPolicyViolationEventDisposition,
serializer: S,
) -> Result<S::Ok, S::Error> {
match val {
SecurityPolicyViolationEventDisposition::Report => serializer.serialize_str("report"),
SecurityPolicyViolationEventDisposition::Enforce => serializer.serialize_str("enforce"),
}
}