blob: ac40068a744306dbc3fb2105ab0b1d22490ab7cd [file] [log] [blame] [edit]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::sync::{Arc, Mutex};
use content_security_policy as csp;
use headers::{ContentType, HeaderMap, HeaderMapExt};
use net_traits::request::{
CredentialsMode, Destination, RequestBody, RequestId, create_request_body_with_content,
};
use net_traits::{
FetchMetadata, FetchResponseListener, NetworkError, ResourceFetchTiming, ResourceTimingType,
};
use servo_url::ServoUrl;
use stylo_atoms::Atom;
use crate::conversions::Convert;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::root::DomRoot;
use crate::dom::csp::Violation;
use crate::dom::csppolicyviolationreport::{
CSPReportUriViolationReport, SecurityPolicyViolationReport,
};
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
use crate::dom::eventtarget::EventTarget;
use crate::dom::performanceresourcetiming::InitiatorType;
use crate::dom::reportingobserver::ReportingObserver;
use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent;
use crate::dom::types::GlobalScope;
use crate::fetch::create_a_potential_cors_request;
use crate::network_listener::{PreInvoke, ResourceTimingListener, submit_timing};
use crate::script_runtime::CanGc;
use crate::task::TaskOnce;
pub(crate) struct CSPViolationReportTask {
global: Trusted<GlobalScope>,
event_target: Trusted<EventTarget>,
violation_report: SecurityPolicyViolationReport,
violation_policy: csp::Policy,
}
impl CSPViolationReportTask {
pub fn new(
global: Trusted<GlobalScope>,
event_target: Trusted<EventTarget>,
violation_report: SecurityPolicyViolationReport,
violation_policy: csp::Policy,
) -> CSPViolationReportTask {
CSPViolationReportTask {
global,
event_target,
violation_report,
violation_policy,
}
}
fn fire_violation_event(&self, can_gc: CanGc) {
let event = SecurityPolicyViolationEvent::new(
&self.global.root(),
Atom::from("securitypolicyviolation"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
EventComposed::Composed,
&self.violation_report.clone().convert(),
can_gc,
);
event
.upcast::<Event>()
.fire(&self.event_target.root(), can_gc);
}
/// <https://www.w3.org/TR/CSP/#deprecated-serialize-violation>
fn serialize_violation(&self) -> Option<RequestBody> {
let report_body = CSPReportUriViolationReport {
// Steps 1-3.
csp_report: self.violation_report.clone().into(),
};
// Step 4. Return the result of serialize an infra value to JSON bytes given «[ "csp-report" → body ]».
Some(create_request_body_with_content(
&serde_json::to_string(&report_body).unwrap_or("".to_owned()),
))
}
/// Step 3.4 of <https://www.w3.org/TR/CSP/#report-violation>
fn post_csp_violation_to_report_uri(&self, report_uri_directive: &csp::Directive) {
let global = self.global.root();
// Step 3.4.1. If violation’s policy’s directive set contains a directive named
// "report-to", skip the remaining substeps.
if self
.violation_policy
.contains_a_directive_whose_name_is("report-to")
{
return;
}
// Step 3.4.2. For each token of directive’s value:
for token in &report_uri_directive.value {
// Step 3.4.2.1. Let endpoint be the result of executing the URL parser with token as the input,
// and violation’s url as the base URL.
//
// TODO: Figure out if this should be the URL of the containing document or not in case
// the url points to a blob
let Ok(endpoint) = ServoUrl::parse_with_base(Some(&global.get_url()), token) else {
// Step 3.4.2.2. If endpoint is not a valid URL, skip the remaining substeps.
continue;
};
// Step 3.4.2.3. Let request be a new request, initialized as follows:
let mut headers = HeaderMap::with_capacity(1);
headers.typed_insert(ContentType::from(
"application/csp-report".parse::<mime::Mime>().unwrap(),
));
let request_body = self.serialize_violation();
let request = create_a_potential_cors_request(
None,
endpoint.clone(),
Destination::Report,
None,
None,
global.get_referrer(),
global.insecure_requests_policy(),
global.has_trustworthy_ancestor_or_current_origin(),
global.policy_container(),
)
.method(http::Method::POST)
.body(request_body)
.origin(global.origin().immutable().clone())
.credentials_mode(CredentialsMode::CredentialsSameOrigin)
.headers(headers);
// Step 3.4.2.4. Fetch request. The result will be ignored.
global.fetch(
request,
Arc::new(Mutex::new(CSPReportUriFetchListener {
endpoint,
global: Trusted::new(&global),
resource_timing: ResourceFetchTiming::new(ResourceTimingType::None),
})),
global.task_manager().networking_task_source().into(),
);
}
}
}
/// Corresponds to the operation in 5.5 Report Violation
/// <https://w3c.github.io/webappsec-csp/#report-violation>
/// > Queue a task to run the following steps:
impl TaskOnce for CSPViolationReportTask {
fn run_once(self) {
// > If target implements EventTarget, fire an event named securitypolicyviolation
// > that uses the SecurityPolicyViolationEvent interface
// > at target with its attributes initialized as follows:
self.fire_violation_event(CanGc::note());
// Step 3.4. If violation’s policy’s directive set contains a directive named "report-uri" directive:
if let Some(report_uri_directive) = self
.violation_policy
.directive_set
.iter()
.find(|directive| directive.name == "report-uri")
{
self.post_csp_violation_to_report_uri(report_uri_directive);
}
// Step 3.5. If violation’s policy’s directive set contains a directive named "report-to" directive:
if let Some(report_to_directive) = self
.violation_policy
.directive_set
.iter()
.find(|directive| directive.name == "report-to")
{
// Step 3.5.1. Let body be a new CSPViolationReportBody, initialized as follows:
let body = self.violation_report.clone().convert();
// Step 3.5.2. Let settings object be violation’s global object’s relevant settings object.
// Step 3.5.3. Generate and queue a report with the following arguments:
ReportingObserver::generate_and_queue_a_report(
&self.global.root(),
"csp-violation".into(),
Some(body),
report_to_directive.value.join(" ").into(),
)
}
}
}
struct CSPReportUriFetchListener {
/// Endpoint URL of this request.
endpoint: ServoUrl,
/// Timing data for this resource.
resource_timing: ResourceFetchTiming,
/// The global object fetching the report uri violation
global: Trusted<GlobalScope>,
}
impl FetchResponseListener for CSPReportUriFetchListener {
fn process_request_body(&mut self, _: RequestId) {}
fn process_request_eof(&mut self, _: RequestId) {}
fn process_response(
&mut self,
_: RequestId,
fetch_metadata: Result<FetchMetadata, NetworkError>,
) {
_ = fetch_metadata;
}
fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
_ = chunk;
}
fn process_response_eof(
&mut self,
_: RequestId,
response: Result<ResourceFetchTiming, NetworkError>,
) {
_ = response;
}
fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
&mut self.resource_timing
}
fn resource_timing(&self) -> &ResourceFetchTiming {
&self.resource_timing
}
fn submit_resource_timing(&mut self) {
submit_timing(self, CanGc::note())
}
fn process_csp_violations(&mut self, _request_id: RequestId, _violations: Vec<Violation>) {}
}
impl ResourceTimingListener for CSPReportUriFetchListener {
fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
(InitiatorType::Other, self.endpoint.clone())
}
fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
self.global.root()
}
}
impl PreInvoke for CSPReportUriFetchListener {
fn should_invoke(&self) -> bool {
true
}
}