Skip to main content

leyline/core/
fetch_site.rs

1use url::Url;
2
3use crate::cookie::parse::registrable_domain;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6#[non_exhaustive]
7pub enum FetchSite {
8    SameOrigin,
9    SameSite,
10    CrossSite,
11}
12
13impl FetchSite {
14    #[must_use]
15    pub fn of(context: &Url, request: &Url) -> Self {
16        if context.origin() == request.origin() {
17            return Self::SameOrigin;
18        }
19        let same_site = context.scheme() == request.scheme()
20            && match (context.host_str(), request.host_str()) {
21                (Some(a), Some(b)) => match (registrable_domain(a), registrable_domain(b)) {
22                    (Some(da), Some(db)) => da == db,
23                    _ => a == b,
24                },
25                _ => false,
26            };
27        if same_site {
28            Self::SameSite
29        } else {
30            Self::CrossSite
31        }
32    }
33
34    pub(crate) fn across<'a>(context: &Url, chain: impl IntoIterator<Item = &'a Url>) -> Self {
35        chain
36            .into_iter()
37            .map(|url| Self::of(context, url))
38            .max_by_key(|site| match site {
39                Self::SameOrigin => 0,
40                Self::SameSite => 1,
41                Self::CrossSite => 2,
42            })
43            .unwrap_or(Self::SameOrigin)
44    }
45
46    #[must_use]
47    pub fn as_str(self) -> &'static str {
48        match self {
49            Self::SameOrigin => "same-origin",
50            Self::SameSite => "same-site",
51            Self::CrossSite => "cross-site",
52        }
53    }
54}
55
56impl std::fmt::Display for FetchSite {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        f.write_str(self.as_str())
59    }
60}