pubhubs/servers/config/
host_aliases.rs1use std::net::IpAddr;
3use url::Url;
4
5use crate::misc::net_ext;
6
7use indexmap::{IndexMap, IndexSet};
8
9#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
11#[serde(rename_all = "snake_case")]
12pub enum HostAlias {
13 Ip(IpAddr),
15
16 SourceIpFor(UrlPwa),
21}
22
23impl HostAlias {
24 fn as_ip(&self) -> Option<&IpAddr> {
26 match self {
27 HostAlias::Ip(ip) => Some(ip),
28 _ => None,
29 }
30 }
31}
32
33#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default)]
34#[serde(transparent)]
35pub struct HostAliases {
36 inner: IndexMap<String, HostAlias>,
37}
38
39#[derive(Debug, Clone)]
41pub enum UrlPwa {
42 PerhapsWithAlias(Url),
44
45 WithoutAlias(Url),
47}
48
49impl HostAliases {
50 pub fn resolve_all(&mut self) -> anyhow::Result<()> {
52 Resolver::new(&mut self.inner).resolve_all()?;
53 Ok(())
54 }
55
56 pub fn dealias(&self, url_pwa: &mut UrlPwa) {
60 let mut url: Url = match url_pwa {
61 UrlPwa::WithoutAlias(_) => {
62 return; }
64 UrlPwa::PerhapsWithAlias(url) => url.clone(),
65 };
66
67 'dealias: {
68 if let Some(host) = url.host() {
69 let ip: IpAddr = match host {
71 url::Host::Ipv4(ip) => ip.into(),
72 url::Host::Ipv6(ip) => ip.into(),
73 url::Host::Domain(hostname) => {
74 if let Some(ha) = self.inner.get(hostname) {
75 *ha.as_ip().unwrap()
76 } else {
77 break 'dealias; }
79 }
80 };
81
82 url.set_ip_host(ip)
83 .expect("unexpectedly could not set host of an url that had a host already");
84 }
85 }
86
87 *url_pwa = UrlPwa::WithoutAlias(url);
88 }
89}
90
91struct Resolver<'a> {
93 aliases: &'a mut IndexMap<String, HostAlias>,
94
95 todo: std::collections::HashSet<usize>,
98
99 deps_stack: IndexSet<usize>,
101}
102
103impl<'a> Resolver<'a> {
104 fn new(aliases: &'a mut IndexMap<String, HostAlias>) -> Self {
105 let n = aliases.len();
106 Self {
107 aliases,
108 todo: (0..n).collect(),
109 deps_stack: Default::default(),
110 }
111 }
112
113 fn resolve_all(mut self) -> anyhow::Result<()> {
114 while !self.todo.is_empty() {
115 let hai: usize = *self.todo.iter().next().unwrap();
116 self.resolve_new_one(hai)?;
117 }
118 Ok(())
119 }
120
121 fn resolve_new_one(&mut self, hai: usize) -> anyhow::Result<()> {
122 assert_eq!(self.deps_stack.len(), 0);
123
124 self.deps_stack = indexmap::indexset![hai];
125
126 while !self.deps_stack.is_empty() {
127 self.deps_stack_step()?;
128 }
129
130 Ok(())
131 }
132
133 fn deps_stack_step(&mut self) -> anyhow::Result<()> {
134 let latest_ha: usize = *self.deps_stack.last().unwrap();
135
136 if let Err(depi) = self.try_resolve_ha(latest_ha)? {
137 let already_a_dep: bool = !self.deps_stack.insert(depi);
138 anyhow::ensure!(
139 !already_a_dep,
140 "cyclic dependency involving host alias {}",
141 self.aliases.get_index_entry(depi).unwrap().key(),
142 );
143 return Ok(());
144 }
145
146 assert_eq!(self.deps_stack.pop().unwrap(), latest_ha);
147 self.todo.remove(&latest_ha);
148
149 Ok(())
150 }
151
152 fn try_resolve_ha(&mut self, hai: usize) -> anyhow::Result<Result<(), usize>> {
155 let ha: &HostAlias = self.aliases.get_index(hai).unwrap().1;
156
157 let ip: IpAddr = match ha {
158 HostAlias::Ip(_) => return Ok(Ok(())), HostAlias::SourceIpFor(url_pwa) => {
160 let url_pwa = match self.try_dealias_url_pwa(url_pwa.clone()) {
161 Ok(url_pwa) => url_pwa,
162 Err(dep) => return Ok(Err(dep)),
163 };
164 net_ext::source_ip_for(url_pwa.as_ref())?
165 }
166 };
167
168 let ha: &mut HostAlias = self.aliases.get_index_mut(hai).unwrap().1;
169 *ha = HostAlias::Ip(ip);
170
171 Ok(Ok(()))
172 }
173
174 fn try_dealias_url_pwa(&mut self, url: UrlPwa) -> Result<UrlPwa, usize> {
175 let mut url: Url = match url {
176 UrlPwa::WithoutAlias(url) => {
177 return Ok(UrlPwa::WithoutAlias(url));
178 }
179 UrlPwa::PerhapsWithAlias(url) => url,
180 };
181
182 if let Some(host) = url.host() {
183 let ip: IpAddr = match host {
184 url::Host::Domain(hostname) => {
185 let (idx, ha): (usize, &HostAlias) =
187 if let Some((idx, _, ha)) = self.aliases.get_full(hostname) {
188 (idx, ha)
189 } else {
190 return Ok(UrlPwa::WithoutAlias(url));
191 };
192
193 if self.todo.contains(&idx) {
194 return Err(idx);
197 }
198
199 *ha.as_ip().unwrap()
200 }
201 url::Host::Ipv4(ip) => ip.into(),
202 url::Host::Ipv6(ip) => ip.into(),
203 };
204
205 url.set_ip_host(ip)
206 .expect("unexpectedly could not set host of an url that had a host already");
207 }
208
209 Ok(UrlPwa::WithoutAlias(url))
210 }
211}
212
213impl UrlPwa {
214 fn url_perhaps_with_alias(&self) -> &Url {
216 match self {
217 UrlPwa::PerhapsWithAlias(u) | UrlPwa::WithoutAlias(u) => u,
218 }
219 }
220}
221
222impl From<Url> for UrlPwa {
223 fn from(url: Url) -> Self {
224 UrlPwa::WithoutAlias(url)
225 }
226}
227
228impl std::fmt::Display for UrlPwa {
229 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
230 self.url_perhaps_with_alias().fmt(f)
231 }
232}
233
234impl serde::Serialize for UrlPwa {
235 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
236 self.url_perhaps_with_alias().serialize(s)
237 }
238}
239
240impl<'de> serde::Deserialize<'de> for UrlPwa {
241 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
242 Ok(UrlPwa::PerhapsWithAlias(Url::deserialize(d)?))
243 }
244}
245
246impl AsRef<Url> for UrlPwa {
247 fn as_ref(&self) -> &url::Url {
248 if let UrlPwa::WithoutAlias(url) = self {
249 return url;
250 }
251 panic!(
252 "internal error: url {self} is used but might still contain a host alias. it should have been dealiased during configuration processing."
253 );
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn host_aliases() {
263 let mut has: HostAliases = toml::from_str(
264 r#"
265 [localhost2]
266 source_ip_for = "udp://localhost:1234"
267
268 [localhost]
269 ip = "127.0.0.1"
270
271 [localhost3]
272 source_ip_for = "udp://localhost2:1234"
273
274 [localhost4]
275 source_ip_for = "udp://[::1]:3"
276
277 "#,
278 )
279 .unwrap();
280
281 has.resolve_all().unwrap();
282
283 let mut url =
284 UrlPwa::PerhapsWithAlias(Url::parse("https://localhost3:1234/dsa?asd#pwa").unwrap());
285
286 has.dealias(&mut url);
287
288 assert_eq!(
289 url.as_ref().to_string(),
290 "https://127.0.0.1:1234/dsa?asd#pwa"
291 );
292
293 let mut url =
294 UrlPwa::PerhapsWithAlias(Url::parse("https://localhost4:1234/dsa?asd#pwa").unwrap());
295
296 has.dealias(&mut url);
297
298 assert_eq!(url.as_ref().to_string(), "https://[::1]:1234/dsa?asd#pwa");
299 }
300}