mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
The origin of a `file` URL is unspecified. Engines act like they're opaque except in a few special cases - one of which is the "is potentially trustworthy" algorithm. This change allows consumers of `servo-url` to distinguish between regular opaque origins and file origins. Then we use that to mark file origins as "potentially trustworthy" which is what the spec wants. For now we can get away without changes to the `url` crate (the one used in the wider ecosystem, not just servo), but I'm unsure if that will be the case in the future. Testing: This change adds a test Fixes: https://github.com/servo/servo/issues/42540 --------- Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
329 lines
10 KiB
Rust
329 lines
10 KiB
Rust
/* 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::cell::RefCell;
|
||
use std::net::IpAddr;
|
||
use std::rc::Rc;
|
||
|
||
use malloc_size_of::malloc_size_of_is_0;
|
||
use malloc_size_of_derive::MallocSizeOf;
|
||
use serde::{Deserialize, Serialize};
|
||
use url::{Host, Origin, Url};
|
||
use uuid::Uuid;
|
||
|
||
/// The origin of an URL
|
||
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
|
||
pub enum ImmutableOrigin {
|
||
/// A globally unique identifier
|
||
Opaque(OpaqueOrigin),
|
||
|
||
/// Consists of the URL's scheme, host and port
|
||
Tuple(String, Host, u16),
|
||
}
|
||
|
||
pub trait DomainComparable {
|
||
fn has_domain(&self) -> bool;
|
||
fn immutable(&self) -> &ImmutableOrigin;
|
||
}
|
||
|
||
impl DomainComparable for OriginSnapshot {
|
||
fn has_domain(&self) -> bool {
|
||
self.1.is_some()
|
||
}
|
||
fn immutable(&self) -> &ImmutableOrigin {
|
||
&self.0
|
||
}
|
||
}
|
||
|
||
impl DomainComparable for MutableOrigin {
|
||
fn has_domain(&self) -> bool {
|
||
(self.0).1.borrow().is_some()
|
||
}
|
||
fn immutable(&self) -> &ImmutableOrigin {
|
||
&(self.0).0
|
||
}
|
||
}
|
||
|
||
impl ImmutableOrigin {
|
||
pub fn new(url: &Url) -> ImmutableOrigin {
|
||
if url.scheme() == "file" {
|
||
return Self::new_opaque_for_file();
|
||
}
|
||
|
||
match url.origin() {
|
||
Origin::Opaque(_) => ImmutableOrigin::new_opaque(),
|
||
Origin::Tuple(scheme, host, port) => ImmutableOrigin::Tuple(scheme, host, port),
|
||
}
|
||
}
|
||
|
||
pub fn same_origin(&self, other: &impl DomainComparable) -> bool {
|
||
self == other.immutable()
|
||
}
|
||
|
||
pub fn same_origin_domain(&self, other: &impl DomainComparable) -> bool {
|
||
!other.has_domain() && self == other.immutable()
|
||
}
|
||
|
||
/// Creates a new opaque origin that is only equal to itself.
|
||
pub fn new_opaque() -> ImmutableOrigin {
|
||
ImmutableOrigin::Opaque(OpaqueOrigin {
|
||
id: Uuid::new_v4(),
|
||
is_for_data_worker_from_secure_context: false,
|
||
is_file_origin: false,
|
||
})
|
||
}
|
||
|
||
/// For use in mixed security context tests because data: URL workers inherit contexts
|
||
pub fn new_opaque_data_url_worker() -> ImmutableOrigin {
|
||
ImmutableOrigin::Opaque(OpaqueOrigin {
|
||
id: Uuid::new_v4(),
|
||
is_for_data_worker_from_secure_context: true,
|
||
is_file_origin: false,
|
||
})
|
||
}
|
||
|
||
pub fn new_opaque_for_file() -> ImmutableOrigin {
|
||
ImmutableOrigin::Opaque(OpaqueOrigin {
|
||
id: Uuid::new_v4(),
|
||
is_for_data_worker_from_secure_context: false,
|
||
is_file_origin: true,
|
||
})
|
||
}
|
||
|
||
pub fn scheme(&self) -> Option<&str> {
|
||
match *self {
|
||
ImmutableOrigin::Opaque(_) => None,
|
||
ImmutableOrigin::Tuple(ref scheme, _, _) => Some(&**scheme),
|
||
}
|
||
}
|
||
|
||
pub fn host(&self) -> Option<&Host> {
|
||
match *self {
|
||
ImmutableOrigin::Opaque(_) => None,
|
||
ImmutableOrigin::Tuple(_, ref host, _) => Some(host),
|
||
}
|
||
}
|
||
|
||
pub fn port(&self) -> Option<u16> {
|
||
match *self {
|
||
ImmutableOrigin::Opaque(_) => None,
|
||
ImmutableOrigin::Tuple(_, _, port) => Some(port),
|
||
}
|
||
}
|
||
|
||
pub fn into_url_origin(self) -> Origin {
|
||
match self {
|
||
ImmutableOrigin::Opaque(_) => Origin::new_opaque(),
|
||
ImmutableOrigin::Tuple(scheme, host, port) => Origin::Tuple(scheme, host, port),
|
||
}
|
||
}
|
||
|
||
/// Return whether this origin is a (scheme, host, port) tuple
|
||
/// (as opposed to an opaque origin).
|
||
pub fn is_tuple(&self) -> bool {
|
||
matches!(self, ImmutableOrigin::Tuple(..))
|
||
}
|
||
|
||
pub fn is_file_origin(&self) -> bool {
|
||
matches!(
|
||
self,
|
||
ImmutableOrigin::Opaque(OpaqueOrigin {
|
||
is_file_origin: true,
|
||
..
|
||
})
|
||
)
|
||
}
|
||
|
||
pub fn is_for_data_worker_from_secure_context(&self) -> bool {
|
||
matches!(
|
||
self,
|
||
ImmutableOrigin::Opaque(OpaqueOrigin {
|
||
is_for_data_worker_from_secure_context: true,
|
||
..
|
||
})
|
||
)
|
||
}
|
||
|
||
/// <https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy>
|
||
pub fn is_potentially_trustworthy(&self) -> bool {
|
||
// 1. If origin is an opaque origin return "Not Trustworthy"
|
||
if let ImmutableOrigin::Opaque(opaque_origin) = self {
|
||
// The webappsec spec assumes that file:// urls have a tuple origin,
|
||
// which is implementation defined.
|
||
// See <https://github.com/w3c/webappsec-secure-contexts/issues/66>.
|
||
//
|
||
// They're not tuple origins in our implementation (which is the more correct choice),
|
||
// so we have to return here instead of Step 6.
|
||
if opaque_origin.is_file_origin {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
if let ImmutableOrigin::Tuple(scheme, host, _) = self {
|
||
// 3. If origin’s scheme is either "https" or "wss", return "Potentially Trustworthy"
|
||
if scheme == "https" || scheme == "wss" {
|
||
return true;
|
||
}
|
||
|
||
// 6. If origin’s scheme is "file", return "Potentially Trustworthy".
|
||
// NOTE: The comment at Step 1 explains why this is unreachable here.
|
||
debug_assert_ne!(scheme, "file", "File URLs don't have a tuple origin");
|
||
|
||
// 4. If origin’s host matches one of the CIDR notations 127.0.0.0/8 or ::1/128,
|
||
// return "Potentially Trustworthy".
|
||
if let Ok(ip_addr) = host.to_string().parse::<IpAddr>() {
|
||
return ip_addr.is_loopback();
|
||
}
|
||
// 5. If the user agent conforms to the name resolution rules in
|
||
// [let-localhost-be-localhost] and one of the following is true:
|
||
// * origin’s host is "localhost" or "localhost."
|
||
// * origin’s host ends with ".localhost" or ".localhost."
|
||
// then return "Potentially Trustworthy".
|
||
if let Host::Domain(domain) = host {
|
||
if domain == "localhost" || domain.ends_with(".localhost") {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 9. Return "Not Trustworthy".
|
||
false
|
||
}
|
||
|
||
/// <https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin>
|
||
pub fn ascii_serialization(&self) -> String {
|
||
self.clone().into_url_origin().ascii_serialization()
|
||
}
|
||
}
|
||
|
||
/// Opaque identifier for URLs that have file or other schemes
|
||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||
pub struct OpaqueOrigin {
|
||
id: Uuid,
|
||
/// Workers created from `data:` urls will have opaque origins but need to be treated
|
||
/// as inheriting the secure context they were created in. This tracks that the origin
|
||
/// was created in such a context
|
||
is_for_data_worker_from_secure_context: bool,
|
||
/// `file://` URLs are *usually* treated as opaque, but not always. This flag serves
|
||
/// as an indicator that they need special handling in certain cases.
|
||
///
|
||
/// See <https://github.com/whatwg/html/issues/3099>.
|
||
is_file_origin: bool,
|
||
}
|
||
|
||
malloc_size_of_is_0!(OpaqueOrigin);
|
||
|
||
/// A snapshot of a MutableOrigin at a moment in time.
|
||
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
|
||
pub struct OriginSnapshot(ImmutableOrigin, Option<Host>);
|
||
|
||
impl OriginSnapshot {
|
||
pub fn immutable(&self) -> &ImmutableOrigin {
|
||
&self.0
|
||
}
|
||
|
||
pub fn has_domain(&self) -> bool {
|
||
self.1.is_some()
|
||
}
|
||
|
||
pub fn same_origin(&self, other: &impl DomainComparable) -> bool {
|
||
self.immutable() == other.immutable()
|
||
}
|
||
|
||
pub fn same_origin_domain(&self, other: &OriginSnapshot) -> bool {
|
||
if let Some(ref self_domain) = self.1 {
|
||
if let Some(ref other_domain) = other.1 {
|
||
self_domain == other_domain && self.0.scheme() == other.0.scheme()
|
||
} else {
|
||
false
|
||
}
|
||
} else {
|
||
self.0.same_origin_domain(other)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A representation of an [origin](https://html.spec.whatwg.org/multipage/#origin-2).
|
||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||
pub struct MutableOrigin(Rc<(ImmutableOrigin, RefCell<Option<Host>>)>);
|
||
|
||
malloc_size_of_is_0!(MutableOrigin);
|
||
|
||
impl MutableOrigin {
|
||
pub fn from_snapshot(snapshot: OriginSnapshot) -> MutableOrigin {
|
||
MutableOrigin(Rc::new((snapshot.0, RefCell::new(snapshot.1))))
|
||
}
|
||
|
||
pub fn snapshot(&self) -> OriginSnapshot {
|
||
OriginSnapshot(self.0.0.clone(), self.0.1.borrow().clone())
|
||
}
|
||
|
||
pub fn new(origin: ImmutableOrigin) -> MutableOrigin {
|
||
MutableOrigin(Rc::new((origin, RefCell::new(None))))
|
||
}
|
||
|
||
pub fn immutable(&self) -> &ImmutableOrigin {
|
||
&(self.0).0
|
||
}
|
||
|
||
pub fn is_tuple(&self) -> bool {
|
||
self.immutable().is_tuple()
|
||
}
|
||
|
||
pub fn scheme(&self) -> Option<&str> {
|
||
self.immutable().scheme()
|
||
}
|
||
|
||
pub fn host(&self) -> Option<&Host> {
|
||
self.immutable().host()
|
||
}
|
||
|
||
pub fn port(&self) -> Option<u16> {
|
||
self.immutable().port()
|
||
}
|
||
|
||
pub fn same_origin(&self, other: &MutableOrigin) -> bool {
|
||
self.immutable() == other.immutable()
|
||
}
|
||
|
||
pub fn same_origin_domain(&self, other: &MutableOrigin) -> bool {
|
||
if let Some(ref self_domain) = *(self.0).1.borrow() {
|
||
if let Some(ref other_domain) = *(other.0).1.borrow() {
|
||
self_domain == other_domain &&
|
||
self.immutable().scheme() == other.immutable().scheme()
|
||
} else {
|
||
false
|
||
}
|
||
} else {
|
||
self.immutable().same_origin_domain(other)
|
||
}
|
||
}
|
||
|
||
pub fn domain(&self) -> Option<Host> {
|
||
(self.0).1.borrow().clone()
|
||
}
|
||
|
||
pub fn set_domain(&self, domain: Host) {
|
||
*(self.0).1.borrow_mut() = Some(domain);
|
||
}
|
||
|
||
pub fn has_domain(&self) -> bool {
|
||
(self.0).1.borrow().is_some()
|
||
}
|
||
|
||
/// <https://html.spec.whatwg.org/multipage/#concept-origin-effective-domain>
|
||
pub fn effective_domain(&self) -> Option<Host> {
|
||
// Step 1. If origin is an opaque origin, then return null.
|
||
if !self.is_tuple() {
|
||
return None;
|
||
}
|
||
self.immutable()
|
||
.host()
|
||
// Step 2. If origin's domain is non-null, then return origin's domain.
|
||
// Step 3. Return origin's host.
|
||
.map(|host| self.domain().unwrap_or_else(|| host.clone()))
|
||
}
|
||
}
|