1use net_traits::request::Referrer;
6use serde::Serialize;
7use servo_url::ServoUrl;
8
9use crate::conversions::Convert;
10use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody;
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::globalscope::GlobalScope;
17use crate::dom::reporting::reportingobserver::ReportingObserver;
18
19#[derive(Clone)]
20pub(crate) struct SecurityPolicyViolationReport {
21 sample: Option<String>,
22 blocked_url: String,
23 referrer: String,
24 status_code: u16,
25 document_url: String,
26 source_file: String,
27 violated_directive: String,
28 effective_directive: String,
29 line_number: u32,
30 column_number: u32,
31 original_policy: String,
32 disposition: SecurityPolicyViolationEventDisposition,
33}
34
35#[derive(Serialize)]
36#[serde(rename_all = "kebab-case")]
37pub(crate) struct CSPReportUriViolationReportBody {
38 document_uri: String,
39 referrer: String,
40 blocked_uri: String,
41 effective_directive: String,
42 violated_directive: String,
43 original_policy: String,
44 #[serde(serialize_with = "serialize_disposition")]
45 disposition: SecurityPolicyViolationEventDisposition,
46 status_code: u16,
47 script_sample: Option<String>,
48 source_file: Option<String>,
49 line_number: Option<u32>,
50 column_number: Option<u32>,
51}
52
53#[derive(Serialize)]
54#[serde(rename_all = "kebab-case")]
55pub(crate) struct CSPReportUriViolationReport {
56 pub(crate) csp_report: CSPReportUriViolationReportBody,
57}
58
59impl Convert<SecurityPolicyViolationEventInit> for SecurityPolicyViolationReport {
60 fn convert(self) -> SecurityPolicyViolationEventInit {
61 SecurityPolicyViolationEventInit {
62 sample: self.sample.unwrap_or_default().into(),
63 blockedURI: self.blocked_url.into(),
64 referrer: self.referrer.into(),
65 statusCode: self.status_code,
66 documentURI: self.document_url.into(),
67 sourceFile: self.source_file.into(),
68 violatedDirective: self.violated_directive.into(),
69 effectiveDirective: self.effective_directive.into(),
70 lineNumber: self.line_number,
71 columnNumber: self.column_number,
72 originalPolicy: self.original_policy.into(),
73 disposition: self.disposition,
74 parent: EventInit::empty(),
75 }
76 }
77}
78
79impl Convert<CSPViolationReportBody> for SecurityPolicyViolationReport {
80 fn convert(self) -> CSPViolationReportBody {
81 let (source_file, line_number, column_number) = if !self.source_file.is_empty() {
82 (
83 Some(self.source_file.into()),
84 Some(self.line_number),
85 Some(self.column_number),
86 )
87 } else {
88 (None, None, None)
89 };
90 CSPViolationReportBody {
91 sample: self.sample.map(|s| s.into()),
92 blockedURL: Some(self.blocked_url.into()),
93 referrer: Some("".to_owned().into()),
97 statusCode: self.status_code,
98 documentURL: self.document_url.into(),
99 sourceFile: source_file,
100 effectiveDirective: self.effective_directive.into(),
101 lineNumber: line_number,
102 columnNumber: column_number,
103 originalPolicy: self.original_policy.into(),
104 disposition: self.disposition,
105 parent: ReportBody::empty(),
106 }
107 }
108}
109
110impl From<SecurityPolicyViolationReport> for CSPReportUriViolationReportBody {
112 fn from(value: SecurityPolicyViolationReport) -> Self {
113 let mut converted = Self {
115 document_uri: value.document_url,
116 referrer: value.referrer,
117 blocked_uri: value.blocked_url,
118 effective_directive: value.effective_directive,
119 violated_directive: value.violated_directive,
120 original_policy: value.original_policy,
121 disposition: value.disposition,
122 status_code: value.status_code,
123 script_sample: None,
124 source_file: None,
125 line_number: None,
126 column_number: None,
127 };
128
129 if !value.source_file.is_empty() {
131 converted.source_file = ServoUrl::parse(&value.source_file)
134 .map(ReportingObserver::strip_url_for_reports)
135 .ok();
136 converted.line_number = Some(value.line_number);
138 converted.column_number = Some(value.column_number);
140 }
141
142 debug_assert!(converted.blocked_uri == "inline" || converted.script_sample.is_none());
144
145 converted
146 }
147}
148
149#[derive(Default)]
150pub(crate) struct CSPViolationReportBuilder {
151 pub report_only: bool,
152 pub sample: Option<String>,
154 pub resource: String,
156 pub line_number: u32,
158 pub column_number: u32,
160 pub source_file: String,
162 pub effective_directive: String,
164 pub original_policy: String,
166}
167
168impl CSPViolationReportBuilder {
169 pub fn report_only(mut self, report_only: bool) -> CSPViolationReportBuilder {
170 self.report_only = report_only;
171 self
172 }
173
174 pub fn sample(mut self, sample: Option<String>) -> CSPViolationReportBuilder {
176 self.sample = sample;
177 self
178 }
179
180 pub fn resource(mut self, resource: String) -> CSPViolationReportBuilder {
182 self.resource = resource;
183 self
184 }
185
186 pub fn line_number(mut self, line_number: u32) -> CSPViolationReportBuilder {
188 self.line_number = line_number;
189 self
190 }
191
192 pub fn column_number(mut self, column_number: u32) -> CSPViolationReportBuilder {
194 self.column_number = column_number;
195 self
196 }
197
198 pub fn source_file(mut self, source_file: String) -> CSPViolationReportBuilder {
200 self.source_file = source_file;
201 self
202 }
203
204 pub fn effective_directive(mut self, effective_directive: String) -> CSPViolationReportBuilder {
206 self.effective_directive = effective_directive;
207 self
208 }
209
210 pub fn original_policy(mut self, original_policy: String) -> CSPViolationReportBuilder {
212 self.original_policy = original_policy;
213 self
214 }
215
216 pub fn build(self, global: &GlobalScope) -> SecurityPolicyViolationReport {
217 SecurityPolicyViolationReport {
218 violated_directive: self.effective_directive.clone(),
219 effective_directive: self.effective_directive.clone(),
220 document_url: ReportingObserver::strip_url_for_reports(global.get_url()),
221 disposition: match self.report_only {
222 true => SecurityPolicyViolationEventDisposition::Report,
223 false => SecurityPolicyViolationEventDisposition::Enforce,
224 },
225 referrer: match global.get_referrer() {
227 Referrer::Client(url) => ReportingObserver::strip_url_for_reports(url),
228 Referrer::ReferrerUrl(url) => ReportingObserver::strip_url_for_reports(url),
229 _ => "".to_owned(),
230 },
231 sample: self.sample,
232 blocked_url: self.resource,
233 source_file: self.source_file,
234 original_policy: self.original_policy,
235 line_number: self.line_number,
236 column_number: self.column_number,
237 status_code: global.status_code().unwrap_or(0),
238 }
239 }
240}
241
242pub(crate) fn serialize_disposition<S: serde::Serializer>(
243 val: &SecurityPolicyViolationEventDisposition,
244 serializer: S,
245) -> Result<S::Ok, S::Error> {
246 match val {
247 SecurityPolicyViolationEventDisposition::Report => serializer.serialize_str("report"),
248 SecurityPolicyViolationEventDisposition::Enforce => serializer.serialize_str("enforce"),
249 }
250}