r402_extensions/siwx/
origin.rs1use compact_str::CompactString;
7use url::Url;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
11#[non_exhaustive]
12pub enum SiwxOriginError {
13 #[error("siwx origin is required")]
15 Missing,
16 #[error("siwx origin must be an absolute http(s) URL")]
20 NotAbsolute,
21 #[error("siwx origin authority is invalid")]
23 InvalidAuthority,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct SiwxOrigin {
35 origin: CompactString,
36 domain: CompactString,
37}
38
39impl SiwxOrigin {
40 pub fn parse(raw: &str) -> Result<Self, SiwxOriginError> {
49 let trimmed = raw.trim();
50 if trimmed.is_empty() {
51 return Err(SiwxOriginError::Missing);
52 }
53 let parsed = Url::parse(trimmed).map_err(|_| SiwxOriginError::NotAbsolute)?;
54 if parsed.scheme() != "http" && parsed.scheme() != "https" {
55 return Err(SiwxOriginError::NotAbsolute);
56 }
57 if !parsed.username().is_empty() || parsed.password().is_some() {
58 return Err(SiwxOriginError::InvalidAuthority);
59 }
60 if parsed.host_str().is_none_or(str::is_empty) {
61 return Err(SiwxOriginError::InvalidAuthority);
62 }
63 let serialized = parsed.origin().ascii_serialization();
64 let domain = serialized
65 .strip_prefix("https://")
66 .or_else(|| serialized.strip_prefix("http://"))
67 .ok_or(SiwxOriginError::NotAbsolute)?;
68 Ok(Self {
69 origin: CompactString::from(serialized.as_str()),
70 domain: CompactString::from(domain),
71 })
72 }
73
74 #[must_use]
76 pub fn as_str(&self) -> &str {
77 &self.origin
78 }
79
80 #[must_use]
82 pub fn domain(&self) -> &str {
83 &self.domain
84 }
85
86 #[must_use]
88 pub fn uri(&self, path: &str) -> CompactString {
89 join_origin_path(&self.origin, path)
90 }
91
92 #[must_use]
94 pub fn store_key(&self, path: &str) -> CompactString {
95 join_origin_path(&self.origin, path)
96 }
97}
98
99fn join_origin_path(origin: &str, path: &str) -> CompactString {
100 let path = if path.is_empty() { "/" } else { path };
101 let mut out = CompactString::from(origin);
102 if !path.starts_with('/') {
103 out.push('/');
104 }
105 out.push_str(path);
106 out
107}