servo_url/
origin.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::RefCell;
6use std::net::IpAddr;
7use std::rc::Rc;
8
9use malloc_size_of::malloc_size_of_is_0;
10use malloc_size_of_derive::MallocSizeOf;
11use serde::{Deserialize, Serialize};
12use url::{Host, Origin, Url};
13use uuid::Uuid;
14
15/// The origin of an URL
16#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
17pub enum ImmutableOrigin {
18    /// A globally unique identifier
19    Opaque(OpaqueOrigin),
20
21    /// Consists of the URL's scheme, host and port
22    Tuple(String, Host, u16),
23}
24
25pub trait DomainComparable {
26    fn has_domain(&self) -> bool;
27    fn immutable(&self) -> &ImmutableOrigin;
28}
29
30impl DomainComparable for OriginSnapshot {
31    fn has_domain(&self) -> bool {
32        self.1.is_some()
33    }
34    fn immutable(&self) -> &ImmutableOrigin {
35        &self.0
36    }
37}
38
39impl DomainComparable for MutableOrigin {
40    fn has_domain(&self) -> bool {
41        (self.0).1.borrow().is_some()
42    }
43    fn immutable(&self) -> &ImmutableOrigin {
44        &(self.0).0
45    }
46}
47
48impl ImmutableOrigin {
49    pub fn new(url: &Url) -> ImmutableOrigin {
50        if url.scheme() == "file" {
51            return Self::new_opaque_for_file();
52        }
53
54        match url.origin() {
55            Origin::Opaque(_) => ImmutableOrigin::new_opaque(),
56            Origin::Tuple(scheme, host, port) => ImmutableOrigin::Tuple(scheme, host, port),
57        }
58    }
59
60    pub fn same_origin(&self, other: &impl DomainComparable) -> bool {
61        self == other.immutable()
62    }
63
64    pub fn same_origin_domain(&self, other: &impl DomainComparable) -> bool {
65        !other.has_domain() && self == other.immutable()
66    }
67
68    /// Creates a new opaque origin that is only equal to itself.
69    pub fn new_opaque() -> ImmutableOrigin {
70        ImmutableOrigin::Opaque(OpaqueOrigin {
71            id: Uuid::new_v4(),
72            is_for_data_worker_from_secure_context: false,
73            is_file_origin: false,
74        })
75    }
76
77    /// For use in mixed security context tests because data: URL workers inherit contexts
78    pub fn new_opaque_data_url_worker() -> ImmutableOrigin {
79        ImmutableOrigin::Opaque(OpaqueOrigin {
80            id: Uuid::new_v4(),
81            is_for_data_worker_from_secure_context: true,
82            is_file_origin: false,
83        })
84    }
85
86    pub fn new_opaque_for_file() -> ImmutableOrigin {
87        ImmutableOrigin::Opaque(OpaqueOrigin {
88            id: Uuid::new_v4(),
89            is_for_data_worker_from_secure_context: false,
90            is_file_origin: true,
91        })
92    }
93
94    pub fn scheme(&self) -> Option<&str> {
95        match *self {
96            ImmutableOrigin::Opaque(_) => None,
97            ImmutableOrigin::Tuple(ref scheme, _, _) => Some(&**scheme),
98        }
99    }
100
101    pub fn host(&self) -> Option<&Host> {
102        match *self {
103            ImmutableOrigin::Opaque(_) => None,
104            ImmutableOrigin::Tuple(_, ref host, _) => Some(host),
105        }
106    }
107
108    pub fn port(&self) -> Option<u16> {
109        match *self {
110            ImmutableOrigin::Opaque(_) => None,
111            ImmutableOrigin::Tuple(_, _, port) => Some(port),
112        }
113    }
114
115    pub fn into_url_origin(self) -> Origin {
116        match self {
117            ImmutableOrigin::Opaque(_) => Origin::new_opaque(),
118            ImmutableOrigin::Tuple(scheme, host, port) => Origin::Tuple(scheme, host, port),
119        }
120    }
121
122    /// Return whether this origin is a (scheme, host, port) tuple
123    /// (as opposed to an opaque origin).
124    pub fn is_tuple(&self) -> bool {
125        matches!(self, ImmutableOrigin::Tuple(..))
126    }
127
128    pub fn is_file_origin(&self) -> bool {
129        matches!(
130            self,
131            ImmutableOrigin::Opaque(OpaqueOrigin {
132                is_file_origin: true,
133                ..
134            })
135        )
136    }
137
138    pub fn is_for_data_worker_from_secure_context(&self) -> bool {
139        matches!(
140            self,
141            ImmutableOrigin::Opaque(OpaqueOrigin {
142                is_for_data_worker_from_secure_context: true,
143                ..
144            })
145        )
146    }
147
148    /// <https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy>
149    pub fn is_potentially_trustworthy(&self) -> bool {
150        // 1. If origin is an opaque origin return "Not Trustworthy"
151        if let ImmutableOrigin::Opaque(opaque_origin) = self {
152            // The webappsec spec assumes that file:// urls have a tuple origin,
153            // which is implementation defined.
154            // See <https://github.com/w3c/webappsec-secure-contexts/issues/66>.
155            //
156            // They're not tuple origins in our implementation (which is the more correct choice),
157            // so we have to return here instead of Step 6.
158            if opaque_origin.is_file_origin {
159                return true;
160            }
161            return false;
162        }
163
164        if let ImmutableOrigin::Tuple(scheme, host, _) = self {
165            // 3. If origin’s scheme is either "https" or "wss", return "Potentially Trustworthy"
166            if scheme == "https" || scheme == "wss" {
167                return true;
168            }
169
170            // 6. If origin’s scheme is "file", return "Potentially Trustworthy".
171            // NOTE: The comment at Step 1 explains why this is unreachable here.
172            debug_assert_ne!(scheme, "file", "File URLs don't have a tuple origin");
173
174            // 4. If origin’s host matches one of the CIDR notations 127.0.0.0/8 or ::1/128,
175            // return "Potentially Trustworthy".
176            if let Ok(ip_addr) = host.to_string().parse::<IpAddr>() {
177                return ip_addr.is_loopback();
178            }
179            // 5. If the user agent conforms to the name resolution rules in
180            // [let-localhost-be-localhost] and one of the following is true:
181            // * origin’s host is "localhost" or "localhost."
182            // * origin’s host ends with ".localhost" or ".localhost."
183            // then return "Potentially Trustworthy".
184            if let Host::Domain(domain) = host {
185                if domain == "localhost" || domain.ends_with(".localhost") {
186                    return true;
187                }
188            }
189        }
190
191        // 9. Return "Not Trustworthy".
192        false
193    }
194
195    /// <https://html.spec.whatwg.org/multipage/#ascii-serialisation-of-an-origin>
196    pub fn ascii_serialization(&self) -> String {
197        self.clone().into_url_origin().ascii_serialization()
198    }
199}
200
201/// Opaque identifier for URLs that have file or other schemes
202#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
203pub struct OpaqueOrigin {
204    id: Uuid,
205    /// Workers created from `data:` urls will have opaque origins but need to be treated
206    /// as inheriting the secure context they were created in. This tracks that the origin
207    /// was created in such a context
208    is_for_data_worker_from_secure_context: bool,
209    /// `file://` URLs are *usually* treated as opaque, but not always. This flag serves
210    /// as an indicator that they need special handling in certain cases.
211    ///
212    /// See <https://github.com/whatwg/html/issues/3099>.
213    is_file_origin: bool,
214}
215
216malloc_size_of_is_0!(OpaqueOrigin);
217
218/// A snapshot of a MutableOrigin at a moment in time.
219#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
220pub struct OriginSnapshot(ImmutableOrigin, Option<Host>);
221
222impl OriginSnapshot {
223    pub fn immutable(&self) -> &ImmutableOrigin {
224        &self.0
225    }
226
227    pub fn has_domain(&self) -> bool {
228        self.1.is_some()
229    }
230
231    pub fn same_origin(&self, other: &impl DomainComparable) -> bool {
232        self.immutable() == other.immutable()
233    }
234
235    pub fn same_origin_domain(&self, other: &OriginSnapshot) -> bool {
236        if let Some(ref self_domain) = self.1 {
237            if let Some(ref other_domain) = other.1 {
238                self_domain == other_domain && self.0.scheme() == other.0.scheme()
239            } else {
240                false
241            }
242        } else {
243            self.0.same_origin_domain(other)
244        }
245    }
246}
247
248/// A representation of an [origin](https://html.spec.whatwg.org/multipage/#origin-2).
249#[derive(Clone, Debug, Deserialize, Serialize)]
250pub struct MutableOrigin(Rc<(ImmutableOrigin, RefCell<Option<Host>>)>);
251
252malloc_size_of_is_0!(MutableOrigin);
253
254impl MutableOrigin {
255    pub fn from_snapshot(snapshot: OriginSnapshot) -> MutableOrigin {
256        MutableOrigin(Rc::new((snapshot.0, RefCell::new(snapshot.1))))
257    }
258
259    pub fn snapshot(&self) -> OriginSnapshot {
260        OriginSnapshot(self.0.0.clone(), self.0.1.borrow().clone())
261    }
262
263    pub fn new(origin: ImmutableOrigin) -> MutableOrigin {
264        MutableOrigin(Rc::new((origin, RefCell::new(None))))
265    }
266
267    pub fn immutable(&self) -> &ImmutableOrigin {
268        &(self.0).0
269    }
270
271    pub fn is_tuple(&self) -> bool {
272        self.immutable().is_tuple()
273    }
274
275    pub fn scheme(&self) -> Option<&str> {
276        self.immutable().scheme()
277    }
278
279    pub fn host(&self) -> Option<&Host> {
280        self.immutable().host()
281    }
282
283    pub fn port(&self) -> Option<u16> {
284        self.immutable().port()
285    }
286
287    pub fn same_origin(&self, other: &MutableOrigin) -> bool {
288        self.immutable() == other.immutable()
289    }
290
291    pub fn same_origin_domain(&self, other: &MutableOrigin) -> bool {
292        if let Some(ref self_domain) = *(self.0).1.borrow() {
293            if let Some(ref other_domain) = *(other.0).1.borrow() {
294                self_domain == other_domain &&
295                    self.immutable().scheme() == other.immutable().scheme()
296            } else {
297                false
298            }
299        } else {
300            self.immutable().same_origin_domain(other)
301        }
302    }
303
304    pub fn domain(&self) -> Option<Host> {
305        (self.0).1.borrow().clone()
306    }
307
308    pub fn set_domain(&self, domain: Host) {
309        *(self.0).1.borrow_mut() = Some(domain);
310    }
311
312    pub fn has_domain(&self) -> bool {
313        (self.0).1.borrow().is_some()
314    }
315
316    /// <https://html.spec.whatwg.org/multipage/#concept-origin-effective-domain>
317    pub fn effective_domain(&self) -> Option<Host> {
318        // Step 1. If origin is an opaque origin, then return null.
319        if !self.is_tuple() {
320            return None;
321        }
322        self.immutable()
323            .host()
324            // Step 2. If origin's domain is non-null, then return origin's domain.
325            // Step 3. Return origin's host.
326            .map(|host| self.domain().unwrap_or_else(|| host.clone()))
327    }
328}