1use serde::Serialize;
6use servo_url::ServoUrl;
7
8use crate::conversions::Convert;
9use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody;
10use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
11use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
12use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::ReportBody;
13use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding::{
14 SecurityPolicyViolationEventDisposition, SecurityPolicyViolationEventInit,
15};
16use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::globalscope::GlobalScope;
19use crate::dom::reporting::reportingobserver::ReportingObserver;
20use crate::dom::window::Window;
21
22#[derive(Clone)]
23pub(crate) struct SecurityPolicyViolationReport {
24 sample: Option<String>,
25 blocked_url: String,
26 referrer: String,
27 status_code: u16,
28 document_url: String,
29 source_file: String,
30 violated_directive: String,
31 effective_directive: String,
32 line_number: u32,
33 column_number: u32,
34 original_policy: String,
35 disposition: SecurityPolicyViolationEventDisposition,
36}
37
38#[derive(Serialize)]
39#[serde(rename_all = "kebab-case")]
40pub(crate) struct CSPReportUriViolationReportBody {
41 document_uri: String,
42 referrer: String,
43 blocked_uri: String,
44 effective_directive: String,
45 violated_directive: String,
46 original_policy: String,
47 #[serde(serialize_with = "serialize_disposition")]
48 disposition: SecurityPolicyViolationEventDisposition,
49 status_code: u16,
50 script_sample: Option<String>,
51 source_file: Option<String>,
52 line_number: Option<u32>,
53 column_number: Option<u32>,
54}
55
56#[derive(Serialize)]
57#[serde(rename_all = "kebab-case")]
58pub(crate) struct CSPReportUriViolationReport {
59 pub(crate) csp_report: CSPReportUriViolationReportBody,
60}
61
62impl Convert<SecurityPolicyViolationEventInit> for SecurityPolicyViolationReport {
63 fn convert(self) -> SecurityPolicyViolationEventInit {
64 SecurityPolicyViolationEventInit {
65 sample: self.sample.unwrap_or_default().into(),
66 blockedURI: self.blocked_url.into(),
67 referrer: self.referrer.into(),
68 statusCode: self.status_code,
69 documentURI: self.document_url.into(),
70 sourceFile: self.source_file.into(),
71 violatedDirective: self.violated_directive.into(),
72 effectiveDirective: self.effective_directive.into(),
73 lineNumber: self.line_number,
74 columnNumber: self.column_number,
75 originalPolicy: self.original_policy.into(),
76 disposition: self.disposition,
77 parent: EventInit::empty(),
78 }
79 }
80}
81
82impl Convert<CSPViolationReportBody> for SecurityPolicyViolationReport {
83 fn convert(self) -> CSPViolationReportBody {
84 let (source_file, line_number, column_number) = if !self.source_file.is_empty() {
85 (
86 Some(self.source_file.into()),
87 Some(self.line_number),
88 Some(self.column_number),
89 )
90 } else {
91 (None, None, None)
92 };
93 CSPViolationReportBody {
94 sample: self.sample.map(|s| s.into()),
95 blockedURL: Some(self.blocked_url.into()),
96 referrer: Some("".to_owned().into()),
100 statusCode: self.status_code,
101 documentURL: self.document_url.into(),
102 sourceFile: source_file,
103 effectiveDirective: self.effective_directive.into(),
104 lineNumber: line_number,
105 columnNumber: column_number,
106 originalPolicy: self.original_policy.into(),
107 disposition: self.disposition,
108 parent: ReportBody::empty(),
109 }
110 }
111}
112
113impl From<SecurityPolicyViolationReport> for CSPReportUriViolationReportBody {
115 fn from(value: SecurityPolicyViolationReport) -> Self {
116 let mut converted = Self {
118 document_uri: value.document_url,
119 referrer: value.referrer,
120 blocked_uri: value.blocked_url,
121 effective_directive: value.effective_directive,
122 violated_directive: value.violated_directive,
123 original_policy: value.original_policy,
124 disposition: value.disposition,
125 status_code: value.status_code,
126 script_sample: None,
127 source_file: None,
128 line_number: None,
129 column_number: None,
130 };
131
132 if !value.source_file.is_empty() {
134 converted.source_file = ServoUrl::parse(&value.source_file)
137 .map(ReportingObserver::strip_url_for_reports)
138 .ok();
139 converted.line_number = Some(value.line_number);
141 converted.column_number = Some(value.column_number);
143 }
144
145 debug_assert!(converted.blocked_uri == "inline" || converted.script_sample.is_none());
147
148 converted
149 }
150}
151
152#[derive(Default)]
153pub(crate) struct CSPViolationReportBuilder {
154 pub report_only: bool,
155 pub sample: Option<String>,
157 pub resource: String,
159 pub line_number: u32,
161 pub column_number: u32,
163 pub source_file: String,
165 pub effective_directive: String,
167 pub original_policy: String,
169}
170
171impl CSPViolationReportBuilder {
172 pub fn report_only(mut self, report_only: bool) -> CSPViolationReportBuilder {
173 self.report_only = report_only;
174 self
175 }
176
177 pub fn sample(mut self, sample: Option<String>) -> CSPViolationReportBuilder {
179 self.sample = sample;
180 self
181 }
182
183 pub fn resource(mut self, resource: String) -> CSPViolationReportBuilder {
185 self.resource = resource;
186 self
187 }
188
189 pub fn line_number(mut self, line_number: u32) -> CSPViolationReportBuilder {
191 self.line_number = line_number;
192 self
193 }
194
195 pub fn column_number(mut self, column_number: u32) -> CSPViolationReportBuilder {
197 self.column_number = column_number;
198 self
199 }
200
201 pub fn source_file(mut self, source_file: String) -> CSPViolationReportBuilder {
203 self.source_file = source_file;
204 self
205 }
206
207 pub fn effective_directive(mut self, effective_directive: String) -> CSPViolationReportBuilder {
209 self.effective_directive = effective_directive;
210 self
211 }
212
213 pub fn original_policy(mut self, original_policy: String) -> CSPViolationReportBuilder {
215 self.original_policy = original_policy;
216 self
217 }
218
219 pub fn build(self, global: &GlobalScope) -> SecurityPolicyViolationReport {
221 SecurityPolicyViolationReport {
230 violated_directive: self.effective_directive.clone(),
231 effective_directive: self.effective_directive.clone(),
232 document_url: ReportingObserver::strip_url_for_reports(global.get_url()),
233 disposition: match self.report_only {
234 true => SecurityPolicyViolationEventDisposition::Report,
235 false => SecurityPolicyViolationEventDisposition::Enforce,
236 },
237 referrer: global
239 .downcast::<Window>()
240 .map(|window| window.Document().Referrer().into())
241 .unwrap_or_default(),
242 sample: self.sample,
243 blocked_url: self.resource,
244 source_file: self.source_file,
245 original_policy: self.original_policy,
246 line_number: self.line_number,
247 column_number: self.column_number,
248 status_code: global.status_code().unwrap_or(0),
251 }
252 }
253}
254
255pub(crate) fn serialize_disposition<S: serde::Serializer>(
256 val: &SecurityPolicyViolationEventDisposition,
257 serializer: S,
258) -> Result<S::Ok, S::Error> {
259 match val {
260 SecurityPolicyViolationEventDisposition::Report => serializer.serialize_str("report"),
261 SecurityPolicyViolationEventDisposition::Enforce => serializer.serialize_str("enforce"),
262 }
263}