leyline/core/
fetch_site.rs1use 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}