Skip to main content

orinium_browser/engine/
origin.rs

1//! Origin model for rendered documents.
2//!
3//! See <https://html.spec.whatwg.org/multipage/origin.html>. The browser layer
4//! uses origins to:
5//! - expose a consistent `origin` through the `window` / `location` /
6//!   `document` JavaScript APIs,
7//! - gate access to internal schemes (`resource:`, ...) from web pages,
8//! - compute `Origin` / `Referer` request headers without leaking bundled
9//!   resource URLs to external servers,
10//! - enforce same-origin / cross-origin visibility for `fetch()` and
11//!   `XMLHttpRequest`.
12
13use url::{Origin as UrlOrigin, Url};
14
15/// The origin of a document or a fetch initiator.
16///
17/// Network schemes (`http`, `https`, `ftp`, `ws`, `wss`) become tuple origins
18/// whose equality ignores default ports. Everything else (`resource:`, `data:`,
19/// `about:`, `file:`, custom schemes) is opaque: it serializes as `"null"` in
20/// JavaScript and never compares equal to another origin.
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub struct Origin(UrlOrigin);
23
24impl Origin {
25    /// Computes the origin of a parsed URL.
26    pub fn from_url(url: &Url) -> Self {
27        Self(url.origin())
28    }
29
30    /// Computes the origin of a URL string, falling back to a fresh opaque
31    /// origin when the string does not parse.
32    pub fn from_url_string(url: &str) -> Self {
33        match Url::parse(url) {
34            Ok(url) => Self::from_url(&url),
35            Err(_) => Self::opaque(),
36        }
37    }
38
39    /// A fresh opaque origin (internal schemes, parse failures, ...).
40    pub fn opaque() -> Self {
41        Self(UrlOrigin::new_opaque())
42    }
43
44    /// Serialization exposed to JavaScript: `scheme://host[:port]` or `"null"`.
45    pub fn ascii_serialization(&self) -> String {
46        self.0.ascii_serialization()
47    }
48
49    /// Whether this is an `http(s)://` tuple origin that proxies a real web page.
50    pub fn is_network(&self) -> bool {
51        self.0.is_tuple()
52    }
53
54    /// Whether this is an opaque origin backed by an internal scheme.
55    pub fn is_opaque(&self) -> bool {
56        !self.0.is_tuple()
57    }
58
59    /// Same-origin check. Opaque origins are never equal to any other origin.
60    pub fn same_origin(&self, other: &Self) -> bool {
61        self.0 == other.0
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    fn url(s: &str) -> Url {
70        Url::parse(s).unwrap()
71    }
72
73    #[test]
74    fn network_origins_equal_with_default_port_normalization() {
75        let a = Origin::from_url(&url("https://example.test/path"));
76        let b = Origin::from_url(&url("https://example.test:443/other"));
77        assert_eq!(a, b);
78        assert!(a.same_origin(&b));
79        assert_eq!(a.ascii_serialization(), "https://example.test");
80    }
81
82    #[test]
83    fn non_default_ports_make_distinct_origins() {
84        let a = Origin::from_url(&url("https://example.test:443/"));
85        let b = Origin::from_url(&url("https://example.test:8443/"));
86        assert!(a.is_network());
87        assert!(b.is_network());
88        assert!(!a.same_origin(&b));
89        assert_eq!(b.ascii_serialization(), "https://example.test:8443");
90    }
91
92    #[test]
93    fn scheme_and_host_differences_break_origin_equality() {
94        let https = Origin::from_url(&url("https://example.test/"));
95        let http = Origin::from_url(&url("http://example.test/"));
96        let other_host = Origin::from_url(&url("https://other.test/"));
97        assert!(!https.same_origin(&http));
98        assert!(!https.same_origin(&other_host));
99    }
100
101    #[test]
102    fn internal_schemes_are_opaque_and_serialize_as_null() {
103        for raw in [
104            "resource:///devtools/index.html",
105            "data:text/plain,hello",
106            "about:blank",
107        ] {
108            let origin = Origin::from_url(&url(raw));
109            assert!(origin.is_opaque(), "{} should be opaque", raw);
110            assert_eq!(origin.ascii_serialization(), "null");
111        }
112    }
113
114    #[test]
115    fn opaque_origins_never_compare_equal() {
116        let a = Origin::from_url_string("resource:///devtools/index.html");
117        let b = Origin::from_url_string("resource:///devtools/index.html");
118        assert!(!a.same_origin(&b));
119        assert_ne!(a, b);
120    }
121
122    #[test]
123    fn unparsable_url_strings_fall_back_to_opaque() {
124        let origin = Origin::from_url_string("::not a url::");
125        assert!(origin.is_opaque());
126        assert_eq!(origin.ascii_serialization(), "null");
127    }
128}