Skip to main content

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