Implement initial version of ReportingObserver (#37905)

The specification moved around lately with how it defines its reports
and report bodies. They became dictionaries, but are currently missing
some fields [1].

Most tests won't be passing yet, since the `Reporting-Endpoints` header
isn't used yet. In fact, the specification leaves it up to the browser
to figure out when to run this task [2]. I am not sure if there some
background scheduling we can do here.

Confirmed with content-security-policy/reporting-api/
report-to-directive-allowed-in-meta.https.sub.html that the callback is
invoked. The test doesn't pass, since
the `describe_scripted_caller` is empty for HTML elements. Thus the
`source_file` is empty, whereas it should be equivalent to the current
document URL.

Part of #37328

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>

[1]: https://github.com/w3c/reporting/issues/286
[2]: https://w3c.github.io/reporting/#report-delivery
This commit is contained in:
Tim van der Lippe 2025-07-07 12:43:30 +02:00 committed by GitHub
parent 3d4868592a
commit fcb2a4cd95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 551 additions and 101 deletions

View file

@ -7,11 +7,14 @@ use serde::Serialize;
use servo_url::ServoUrl;
use crate::conversions::Convert;
use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::ReportBody;
use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding::{
SecurityPolicyViolationEventDisposition, SecurityPolicyViolationEventInit,
};
use crate::dom::globalscope::GlobalScope;
use crate::dom::reportingobserver::ReportingObserver;
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
@ -77,6 +80,28 @@ impl Convert<SecurityPolicyViolationEventInit> for SecurityPolicyViolationReport
}
}
impl Convert<CSPViolationReportBody> for SecurityPolicyViolationReport {
fn convert(self) -> CSPViolationReportBody {
CSPViolationReportBody {
sample: self.sample.map(|s| s.into()),
blockedURL: Some(self.blocked_url.into()),
// TODO(37328): Why does /content-security-policy/reporting-api/
// report-to-directive-allowed-in-meta.https.sub.html expect this to be
// empty, yet the spec expects us to copy referrer from SecurityPolicyViolationReport
referrer: Some("".to_owned().into()),
statusCode: self.status_code,
documentURL: self.document_url.into(),
sourceFile: Some(self.source_file.into()),
effectiveDirective: self.effective_directive.into(),
lineNumber: Some(self.line_number),
columnNumber: Some(self.column_number),
originalPolicy: self.original_policy.into(),
disposition: self.disposition,
parent: ReportBody::empty(),
}
}
}
/// <https://www.w3.org/TR/CSP/#deprecated-serialize-violation>
impl From<SecurityPolicyViolationReport> for CSPReportUriViolationReportBody {
fn from(value: SecurityPolicyViolationReport) -> Self {
@ -101,7 +126,7 @@ impl From<SecurityPolicyViolationReport> for CSPReportUriViolationReportBody {
// Step 2.1. Set body["source-file'] to the result of
// executing §5.4 Strip URL for use in reports on violations source file.
converted.source_file = ServoUrl::parse(&value.source_file)
.map(strip_url_for_reports)
.map(ReportingObserver::strip_url_for_reports)
.ok();
// Step 2.2. Set body["line-number"] to violations line number.
converted.line_number = Some(value.line_number);
@ -187,15 +212,15 @@ impl CSPViolationReportBuilder {
SecurityPolicyViolationReport {
violated_directive: self.effective_directive.clone(),
effective_directive: self.effective_directive.clone(),
document_url: strip_url_for_reports(global.get_url()),
document_url: ReportingObserver::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),
Referrer::Client(url) => ReportingObserver::strip_url_for_reports(url),
Referrer::ReferrerUrl(url) => ReportingObserver::strip_url_for_reports(url),
_ => "".to_owned(),
},
sample: self.sample,
@ -218,20 +243,3 @@ fn serialize_disposition<S: serde::Serializer>(
SecurityPolicyViolationEventDisposition::Enforce => serializer.serialize_str("enforce"),
}
}
/// <https://w3c.github.io/webappsec-csp/#strip-url-for-use-in-reports>
fn strip_url_for_reports(mut url: ServoUrl) -> String {
let scheme = url.scheme();
// > Step 1: If urls scheme is not an HTTP(S) scheme, then return urls scheme.
if scheme != "https" && scheme != "http" {
return scheme.to_owned();
}
// > Step 2: Set urls fragment to the empty string.
url.set_fragment(None);
// > Step 3: Set urls username to the empty string.
let _ = url.set_username("");
// > Step 4: Set urls 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()
}