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::reportingobserver::ReportingObserver;
18
19#[derive(Clone, Debug, Serialize)]
20#[serde(rename_all = "camelCase")]
21pub(crate) struct SecurityPolicyViolationReport {
22 sample: Option<String>,
23 #[serde(rename = "blockedURL")]
24 blocked_url: String,
25 referrer: String,
26 status_code: u16,
27 #[serde(rename = "documentURL")]
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 #[serde(serialize_with = "serialize_disposition")]
36 disposition: SecurityPolicyViolationEventDisposition,
37}
38
39#[derive(Serialize)]
40#[serde(rename_all = "kebab-case")]
41pub(crate) struct CSPReportUriViolationReportBody {
42 document_uri: String,
43 referrer: String,
44 blocked_uri: String,
45 effective_directive: String,
46 violated_directive: String,
47 original_policy: String,
48 #[serde(serialize_with = "serialize_disposition")]
49 disposition: SecurityPolicyViolationEventDisposition,
50 status_code: u16,
51 script_sample: Option<String>,
52 source_file: Option<String>,
53 line_number: Option<u32>,
54 column_number: Option<u32>,
55}
56
57#[derive(Serialize)]
58#[serde(rename_all = "kebab-case")]
59pub(crate) struct CSPReportUriViolationReport {
60 pub(crate) csp_report: CSPReportUriViolationReportBody,
61}
62
63impl Convert<SecurityPolicyViolationEventInit> for SecurityPolicyViolationReport {
64 fn convert(self) -> SecurityPolicyViolationEventInit {
65 SecurityPolicyViolationEventInit {
66 sample: self.sample.unwrap_or_default().into(),
67 blockedURI: self.blocked_url.into(),
68 referrer: self.referrer.into(),
69 statusCode: self.status_code,
70 documentURI: self.document_url.into(),
71 sourceFile: self.source_file.into(),
72 violatedDirective: self.violated_directive.into(),
73 effectiveDirective: self.effective_directive.into(),
74 lineNumber: self.line_number,
75 columnNumber: self.column_number,
76 originalPolicy: self.original_policy.into(),
77 disposition: self.disposition,
78 parent: EventInit::empty(),
79 }
80 }
81}
82
83impl Convert<CSPViolationReportBody> for SecurityPolicyViolationReport {
84 fn convert(self) -> CSPViolationReportBody {
85 CSPViolationReportBody {
86 sample: self.sample.map(|s| s.into()),
87 blockedURL: Some(self.blocked_url.into()),
88 referrer: Some("".to_owned().into()),
92 statusCode: self.status_code,
93 documentURL: self.document_url.into(),
94 sourceFile: Some(self.source_file.into()),
95 effectiveDirective: self.effective_directive.into(),
96 lineNumber: Some(self.line_number),
97 columnNumber: Some(self.column_number),
98 originalPolicy: self.original_policy.into(),
99 disposition: self.disposition,
100 parent: ReportBody::empty(),
101 }
102 }
103}
104
105impl From<SecurityPolicyViolationReport> for CSPReportUriViolationReportBody {
107 fn from(value: SecurityPolicyViolationReport) -> Self {
108 let mut converted = Self {
110 document_uri: value.document_url,
111 referrer: value.referrer,
112 blocked_uri: value.blocked_url,
113 effective_directive: value.effective_directive,
114 violated_directive: value.violated_directive,
115 original_policy: value.original_policy,
116 disposition: value.disposition,
117 status_code: value.status_code,
118 script_sample: None,
119 source_file: None,
120 line_number: None,
121 column_number: None,
122 };
123
124 if !value.source_file.is_empty() {
126 converted.source_file = ServoUrl::parse(&value.source_file)
129 .map(ReportingObserver::strip_url_for_reports)
130 .ok();
131 converted.line_number = Some(value.line_number);
133 converted.column_number = Some(value.column_number);
135 }
136
137 debug_assert!(converted.blocked_uri == "inline" || converted.script_sample.is_none());
139
140 converted
141 }
142}
143
144#[derive(Default)]
145pub(crate) struct CSPViolationReportBuilder {
146 pub report_only: bool,
147 pub sample: Option<String>,
149 pub resource: String,
151 pub line_number: u32,
153 pub column_number: u32,
155 pub source_file: String,
157 pub effective_directive: String,
159 pub original_policy: String,
161}
162
163impl CSPViolationReportBuilder {
164 pub fn report_only(mut self, report_only: bool) -> CSPViolationReportBuilder {
165 self.report_only = report_only;
166 self
167 }
168
169 pub fn sample(mut self, sample: Option<String>) -> CSPViolationReportBuilder {
171 self.sample = sample;
172 self
173 }
174
175 pub fn resource(mut self, resource: String) -> CSPViolationReportBuilder {
177 self.resource = resource;
178 self
179 }
180
181 pub fn line_number(mut self, line_number: u32) -> CSPViolationReportBuilder {
183 self.line_number = line_number;
184 self
185 }
186
187 pub fn column_number(mut self, column_number: u32) -> CSPViolationReportBuilder {
189 self.column_number = column_number;
190 self
191 }
192
193 pub fn source_file(mut self, source_file: String) -> CSPViolationReportBuilder {
195 self.source_file = source_file;
196 self
197 }
198
199 pub fn effective_directive(mut self, effective_directive: String) -> CSPViolationReportBuilder {
201 self.effective_directive = effective_directive;
202 self
203 }
204
205 pub fn original_policy(mut self, original_policy: String) -> CSPViolationReportBuilder {
207 self.original_policy = original_policy;
208 self
209 }
210
211 pub fn build(self, global: &GlobalScope) -> SecurityPolicyViolationReport {
212 SecurityPolicyViolationReport {
213 violated_directive: self.effective_directive.clone(),
214 effective_directive: self.effective_directive.clone(),
215 document_url: ReportingObserver::strip_url_for_reports(global.get_url()),
216 disposition: match self.report_only {
217 true => SecurityPolicyViolationEventDisposition::Report,
218 false => SecurityPolicyViolationEventDisposition::Enforce,
219 },
220 referrer: match global.get_referrer() {
222 Referrer::Client(url) => ReportingObserver::strip_url_for_reports(url),
223 Referrer::ReferrerUrl(url) => ReportingObserver::strip_url_for_reports(url),
224 _ => "".to_owned(),
225 },
226 sample: self.sample,
227 blocked_url: self.resource,
228 source_file: self.source_file,
229 original_policy: self.original_policy,
230 line_number: self.line_number,
231 column_number: self.column_number,
232 status_code: global.status_code().unwrap_or(0),
233 }
234 }
235}
236
237pub(crate) fn serialize_disposition<S: serde::Serializer>(
238 val: &SecurityPolicyViolationEventDisposition,
239 serializer: S,
240) -> Result<S::Ok, S::Error> {
241 match val {
242 SecurityPolicyViolationEventDisposition::Report => serializer.serialize_str("report"),
243 SecurityPolicyViolationEventDisposition::Enforce => serializer.serialize_str("enforce"),
244 }
245}