html: Allow legacy referrer policies only for <meta> referrer (#39506)

Follow the HTML specification and allow to use legacy referrer policies
(never/default/always/origin-when-crossorigin) only with 'meta'
referrer.

See https://html.spec.whatwg.org/multipage/#meta-referrer (step 5)

While for another HTML elements with 'referrerpolicy' content attribute
(https://html.spec.whatwg.org/multipage/#referrer-policy-attribute)
and for 'Referrer-Policy' HTTP header

(https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-header-dfn)
the referrer policy should be determine from the standard policy tokens
(https://w3c.github.io/webappsec-referrer-policy/#referrer-policy).

So unknown policy values (legacy from meta-referrer) will be ignored
and determine as 'ReferrerPolicy::EmptyString'.

Testing: No changes

Fixes: #36833

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin 2025-09-30 19:29:24 +03:00 committed by GitHub
parent f724651e1a
commit 6a1a3aea08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 59 additions and 45 deletions

View file

@ -22,7 +22,6 @@ use constellation_traits::{NavigationHistoryBehavior, ScriptToConstellationMessa
use content_security_policy::sandboxing_directive::SandboxingFlagSet; use content_security_policy::sandboxing_directive::SandboxingFlagSet;
use content_security_policy::{CspList, PolicyDisposition}; use content_security_policy::{CspList, PolicyDisposition};
use cookie::Cookie; use cookie::Cookie;
use cssparser::match_ignore_ascii_case;
use data_url::mime::Mime; use data_url::mime::Mime;
use devtools_traits::ScriptToDevtoolsControlMsg; use devtools_traits::ScriptToDevtoolsControlMsg;
use dom_struct::dom_struct; use dom_struct::dom_struct;
@ -6003,21 +6002,6 @@ fn update_with_current_instant(marker: &Cell<Option<CrossProcessInstant>>) {
} }
} }
/// <https://w3c.github.io/webappsec-referrer-policy/#determine-policy-for-token>
pub(crate) fn determine_policy_for_token(token: &str) -> ReferrerPolicy {
match_ignore_ascii_case! { token,
"never" | "no-referrer" => ReferrerPolicy::NoReferrer,
"no-referrer-when-downgrade" => ReferrerPolicy::NoReferrerWhenDowngrade,
"origin" => ReferrerPolicy::Origin,
"same-origin" => ReferrerPolicy::SameOrigin,
"strict-origin" => ReferrerPolicy::StrictOrigin,
"default" | "strict-origin-when-cross-origin" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
"origin-when-cross-origin" => ReferrerPolicy::OriginWhenCrossOrigin,
"always" | "unsafe-url" => ReferrerPolicy::UnsafeUrl,
_ => ReferrerPolicy::EmptyString,
}
}
/// Specifies the type of focus event that is sent to a pipeline /// Specifies the type of focus event that is sent to a pipeline
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub(crate) enum FocusType { pub(crate) enum FocusType {

View file

@ -106,7 +106,7 @@ use crate::dom::customelementregistry::{
CallbackReaction, CustomElementDefinition, CustomElementReaction, CustomElementState, CallbackReaction, CustomElementDefinition, CustomElementReaction, CustomElementState,
is_valid_custom_element_name, is_valid_custom_element_name,
}; };
use crate::dom::document::{Document, LayoutDocumentHelpers, determine_policy_for_token}; use crate::dom::document::{Document, LayoutDocumentHelpers};
use crate::dom::documentfragment::DocumentFragment; use crate::dom::documentfragment::DocumentFragment;
use crate::dom::domrect::DOMRect; use crate::dom::domrect::DOMRect;
use crate::dom::domrectlist::DOMRectList; use crate::dom::domrectlist::DOMRectList;
@ -5481,7 +5481,7 @@ pub(crate) fn reflect_referrer_policy_attribute(element: &Element) -> DOMString
pub(crate) fn referrer_policy_for_element(element: &Element) -> ReferrerPolicy { pub(crate) fn referrer_policy_for_element(element: &Element) -> ReferrerPolicy {
element element
.get_attribute(&ns!(), &local_name!("referrerpolicy")) .get_attribute(&ns!(), &local_name!("referrerpolicy"))
.map(|attribute| determine_policy_for_token(&attribute.value())) .map(|attribute| ReferrerPolicy::from(&**attribute.value()))
.unwrap_or(element.owner_document().get_referrer_policy()) .unwrap_or(element.owner_document().get_referrer_policy())
} }

View file

@ -35,7 +35,7 @@ use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::{Document, determine_policy_for_token}; use crate::dom::document::Document;
use crate::dom::domtokenlist::DOMTokenList; use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::element::{ use crate::dom::element::{
AttributeMutation, Element, LayoutElementHelpers, reflect_referrer_policy_attribute, AttributeMutation, Element, LayoutElementHelpers, reflect_referrer_policy_attribute,
@ -336,7 +336,7 @@ impl HTMLIFrameElement {
// Note: despite not being explicitly stated in the spec steps, this falls back to // Note: despite not being explicitly stated in the spec steps, this falls back to
// document's referrer policy here because it satisfies the expectations that when unset, // document's referrer policy here because it satisfies the expectations that when unset,
// the iframe should inherit the referrer policy of its parent // the iframe should inherit the referrer policy of its parent
let referrer_policy = match determine_policy_for_token(referrer_policy_token.str()) { let referrer_policy = match ReferrerPolicy::from(referrer_policy_token.str()) {
ReferrerPolicy::EmptyString => document.get_referrer_policy(), ReferrerPolicy::EmptyString => document.get_referrer_policy(),
policy => policy, policy => policy,
}; };

View file

@ -59,7 +59,7 @@ use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::csp::{GlobalCspReporting, Violation}; use crate::dom::csp::{GlobalCspReporting, Violation};
use crate::dom::document::{Document, determine_policy_for_token}; use crate::dom::document::Document;
use crate::dom::element::{ use crate::dom::element::{
AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers, AttributeMutation, CustomElementCreationMode, Element, ElementCreator, LayoutElementHelpers,
cors_setting_for_element, referrer_policy_for_element, reflect_cross_origin_attribute, cors_setting_for_element, referrer_policy_for_element, reflect_cross_origin_attribute,
@ -1872,15 +1872,10 @@ impl VirtualMethods for HTMLImageElement {
// The element's referrerpolicy attribute's state is changed. // The element's referrerpolicy attribute's state is changed.
let referrer_policy_state_changed = match mutation { let referrer_policy_state_changed = match mutation {
AttributeMutation::Removed | AttributeMutation::Set(None) => { AttributeMutation::Removed | AttributeMutation::Set(None) => {
let referrer_policy = determine_policy_for_token(&attr.value()); ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::EmptyString
referrer_policy != ReferrerPolicy::EmptyString
}, },
AttributeMutation::Set(Some(old_value)) => { AttributeMutation::Set(Some(old_value)) => {
let new_referrer_policy = determine_policy_for_token(&attr.value()); ReferrerPolicy::from(&**attr.value()) != ReferrerPolicy::from(&**old_value)
let old_referrer_policy = determine_policy_for_token(old_value);
new_referrer_policy != old_referrer_policy
}, },
}; };

View file

@ -8,6 +8,7 @@ use compositing_traits::viewport_description::ViewportDescription;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns}; use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject; use js::rust::HandleObject;
use net_traits::ReferrerPolicy;
use servo_config::pref; use servo_config::pref;
use style::str::HTML_SPACE_CHARACTERS; use style::str::HTML_SPACE_CHARACTERS;
@ -17,7 +18,7 @@ use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString; use crate::dom::bindings::str::DOMString;
use crate::dom::document::{Document, determine_policy_for_token}; use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element}; use crate::dom::element::{AttributeMutation, Element};
use crate::dom::html::htmlelement::HTMLElement; use crate::dom::html::htmlelement::HTMLElement;
use crate::dom::html::htmlheadelement::HTMLHeadElement; use crate::dom::html::htmlheadelement::HTMLHeadElement;
@ -97,28 +98,32 @@ impl HTMLMetaElement {
// From spec: For historical reasons, unlike other standard metadata names, the processing model for referrer // From spec: For historical reasons, unlike other standard metadata names, the processing model for referrer
// is not responsive to element removals, and does not use tree order. Only the most-recently-inserted or // is not responsive to element removals, and does not use tree order. Only the most-recently-inserted or
// most-recently-modified meta element in this state has an effect. // most-recently-modified meta element in this state has an effect.
// 1. If element is not in a document tree, then return. // Step 1. If element is not in a document tree, then return.
let meta_node = self.upcast::<Node>(); let meta_node = self.upcast::<Node>();
if !meta_node.is_in_a_document_tree() { if !meta_node.is_in_a_document_tree() {
return; return;
} }
// 2. If element does not have a name attribute whose value is an ASCII case-insensitive match for "referrer", // Step 2. If element does not have a name attribute whose value is an ASCII
// then return. // case-insensitive match for "referrer", then return.
if self.upcast::<Element>().get_name() != Some(atom!("referrer")) { if self.upcast::<Element>().get_name() != Some(atom!("referrer")) {
return; return;
} }
// 3. If element does not have a content attribute, or that attribute's value is the empty string, then return. // Step 3. If element does not have a content attribute, or that attribute's value is the
let content = self // empty string, then return.
if let Some(content) = self
.upcast::<Element>() .upcast::<Element>()
.get_attribute(&ns!(), &local_name!("content")); .get_attribute(&ns!(), &local_name!("content"))
if let Some(attr) = content { .filter(|attr| !attr.value().is_empty())
let attr = attr.value(); {
let attr_val = attr.trim(); // Step 4. Let value be the value of element's content attribute, converted to ASCII
if !attr_val.is_empty() { // lowercase.
doc.set_referrer_policy(determine_policy_for_token(attr_val)); // Step 5. If value is one of the values given in the first column of the following
} // table, then set value to the value given in the second column:
// Step 6. If value is a referrer policy, then set element's node document's policy
// container's referrer policy to policy.
doc.set_referrer_policy(ReferrerPolicy::from_with_legacy(&content.value()));
} }
} }

View file

@ -30,7 +30,7 @@ use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
use crate::dom::csp::{GlobalCspReporting, Violation}; use crate::dom::csp::{GlobalCspReporting, Violation};
use crate::dom::document::{Document, determine_policy_for_token}; use crate::dom::document::Document;
use crate::dom::element::Element; use crate::dom::element::Element;
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::medialist::MediaList; use crate::dom::medialist::MediaList;
@ -100,7 +100,7 @@ impl LinkProcessingOptions {
// Step 4. If attribs["referrerpolicy"] exists and is an ASCII case-insensitive match for // Step 4. If attribs["referrerpolicy"] exists and is an ASCII case-insensitive match for
// some referrer policy, then set options's referrer policy to that referrer policy. // some referrer policy, then set options's referrer policy to that referrer policy.
if let Some(referrer_policy) = link_object.value_for_key_in_link_header("referrerpolicy") { if let Some(referrer_policy) = link_object.value_for_key_in_link_header("referrerpolicy") {
self.referrer_policy = determine_policy_for_token(referrer_policy); self.referrer_policy = ReferrerPolicy::from(referrer_policy);
} }
// Step 5. If attribs["nonce"] exists, then set options's nonce to attribs["nonce"]. // Step 5. If attribs["nonce"] exists, then set options's nonce to attribs["nonce"].
if let Some(nonce) = link_object.value_for_key_in_link_header("nonce") { if let Some(nonce) = link_object.value_for_key_in_link_header("nonce") {

View file

@ -24,7 +24,7 @@ use servo_url::{ImmutableOrigin, ServoUrl};
use crate::dom::bindings::reflector::DomGlobal; use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::trace::{CustomTraceable, JSTraceable}; use crate::dom::bindings::trace::{CustomTraceable, JSTraceable};
use crate::dom::document::{Document, determine_policy_for_token}; use crate::dom::document::Document;
use crate::dom::html::htmlscriptelement::script_fetch_request; use crate::dom::html::htmlscriptelement::script_fetch_request;
use crate::dom::processingoptions::determine_cors_settings_for_token; use crate::dom::processingoptions::determine_cors_settings_for_token;
use crate::fetch::create_a_potential_cors_request; use crate::fetch::create_a_potential_cors_request;
@ -263,7 +263,7 @@ impl PrefetchSink {
fn get_referrer_policy(&self, tag: &Tag, name: LocalName) -> ReferrerPolicy { fn get_referrer_policy(&self, tag: &Tag, name: LocalName) -> ReferrerPolicy {
self.get_attr(tag, name) self.get_attr(tag, name)
.map(|attr| determine_policy_for_token(&attr.value)) .map(|attr| ReferrerPolicy::from(&*attr.value))
.unwrap_or(self.referrer_policy) .unwrap_or(self.referrer_policy)
} }

View file

@ -133,6 +133,19 @@ pub enum ReferrerPolicy {
} }
impl ReferrerPolicy { impl ReferrerPolicy {
/// <https://html.spec.whatwg.org/multipage/#meta-referrer>
pub fn from_with_legacy(value: &str) -> Self {
// Step 5. If value is one of the values given in the first column of the following table,
// then set value to the value given in the second column:
match value.to_ascii_lowercase().as_str() {
"never" => ReferrerPolicy::NoReferrer,
"default" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
"always" => ReferrerPolicy::UnsafeUrl,
"origin-when-crossorigin" => ReferrerPolicy::OriginWhenCrossOrigin,
_ => ReferrerPolicy::from(value),
}
}
/// <https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header> /// <https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header>
pub fn parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self { pub fn parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self {
// Step 4. Return policy. // Step 4. Return policy.
@ -145,6 +158,23 @@ impl ReferrerPolicy {
} }
} }
impl From<&str> for ReferrerPolicy {
/// <https://html.spec.whatwg.org/multipage/#referrer-policy-attribute>
fn from(value: &str) -> Self {
match value.to_ascii_lowercase().as_str() {
"no-referrer" => ReferrerPolicy::NoReferrer,
"no-referrer-when-downgrade" => ReferrerPolicy::NoReferrerWhenDowngrade,
"origin" => ReferrerPolicy::Origin,
"same-origin" => ReferrerPolicy::SameOrigin,
"strict-origin" => ReferrerPolicy::StrictOrigin,
"strict-origin-when-cross-origin" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
"origin-when-cross-origin" => ReferrerPolicy::OriginWhenCrossOrigin,
"unsafe-url" => ReferrerPolicy::UnsafeUrl,
_ => ReferrerPolicy::EmptyString,
}
}
}
impl Display for ReferrerPolicy { impl Display for ReferrerPolicy {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let string = match self { let string = match self {