tuwunel_core/utils/
url.rs1use std::fmt::{self, Display};
7
8use http::Uri;
9
10#[must_use]
16pub fn hostname_matches_domain(hostname: &str, domain: &str) -> bool {
17 if domain == "." {
18 return hostname.ends_with('.');
19 }
20
21 let domain = domain.strip_prefix('.').unwrap_or(domain);
22
23 if domain.is_empty() {
24 return false;
25 }
26
27 if hostname.eq_ignore_ascii_case(domain) {
28 return true;
29 }
30
31 let Some(separator) = hostname
32 .len()
33 .checked_sub(domain.len())
34 .and_then(|index| index.checked_sub(1))
35 else {
36 return false;
37 };
38
39 let Some(suffix_start) = separator.checked_add(1) else {
40 return false;
41 };
42
43 hostname.as_bytes().get(separator) == Some(&b'.')
44 && hostname
45 .get(suffix_start..)
46 .is_some_and(|suffix| suffix.eq_ignore_ascii_case(domain))
47}
48
49pub struct SanitizedUri<'a> {
54 uri: &'a Uri,
55 path: Option<&'a str>,
56}
57
58impl<'a> SanitizedUri<'a> {
59 #[must_use]
63 pub const fn new(uri: &'a Uri) -> Self { Self { uri, path: None } }
64
65 #[must_use]
69 pub const fn with_path(uri: &'a Uri, path: &'a str) -> Self { Self { uri, path: Some(path) } }
70}
71
72impl Display for SanitizedUri<'_> {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 let path = self.path.unwrap_or_else(|| self.uri.path());
75
76 match self.uri.query() {
77 | Some(_) => write!(f, "{path}?<redacted>"),
78 | None => f.write_str(path),
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn redacts_query() {
89 let uri: Uri = "/_matrix/client/v3/sync?access_token=syt_realtoken"
90 .parse()
91 .expect("valid uri");
92
93 let out = SanitizedUri::new(&uri).to_string();
94
95 assert_eq!(out, "/_matrix/client/v3/sync?<redacted>");
96 assert!(!out.contains("access_token"));
97 assert!(!out.contains("syt_realtoken"));
98 }
99
100 #[test]
101 fn preserves_path_without_query() {
102 let uri: Uri = "/_matrix/client/v3/sync"
103 .parse()
104 .expect("valid uri");
105
106 assert_eq!(SanitizedUri::new(&uri).to_string(), "/_matrix/client/v3/sync");
107 }
108
109 #[test]
110 fn replaces_concrete_path_parameters() {
111 let uri = Uri::builder()
112 .path_and_query(
113 "/_matrix/client/unstable/org.matrix.msc4140/delayed_events/secret?action=send",
114 )
115 .build()
116 .expect("valid uri");
117
118 let out = SanitizedUri::with_path(
119 &uri,
120 "/_matrix/client/unstable/org.matrix.msc4140/delayed_events/{delay_id}",
121 )
122 .to_string();
123
124 assert_eq!(
125 out,
126 "/_matrix/client/unstable/org.matrix.msc4140/delayed_events/{delay_id}?<redacted>"
127 );
128 assert!(!out.contains("secret"));
129 }
130}