1use std::{
4 collections::HashMap,
5 fmt::{Display, Formatter, Result as FmtResult},
6 net::{IpAddr, Ipv6Addr},
7 path::PathBuf,
8 sync::Arc,
9};
10
11use parking_lot::RwLock;
12use rand::{distributions::Alphanumeric, Rng};
13use tracing::{debug, instrument, warn};
14
15use super::{CertificateSource, DefaultCertificateSource, ListenAddress, LogLevel};
16use crate::{
17 config::partial::Partial, server::Protocol, stats::StatisticCategories, store::BackendType,
18 util::A_YEAR,
19};
20
21#[derive(Debug)]
25pub struct Config {
26 inner: RwLock<ConfigInner>,
27 file: Option<PathBuf>,
28}
29
30impl Config {
31 #[must_use]
42 pub fn new(file: Option<PathBuf>) -> Self {
43 let config = ConfigInner::default();
44
45 let config = Self {
46 inner: RwLock::new(config),
47 file,
48 };
49 config.update();
50 config
51 }
52
53 #[must_use]
69 pub fn new_static(file: Option<PathBuf>) -> &'static Self {
70 Box::leak(Box::new(Self::new(file)))
71 }
72
73 #[instrument(level = "info", fields(%self))]
83 pub fn update(&self) {
84 let mut config = ConfigInner::default();
85
86 config.update_from_partial(&Partial::from_env_vars());
87
88 if let Some(ref file) = *self.file() {
89 match Partial::from_file(file) {
90 Ok(partial) => config.update_from_partial(&partial),
91 Err(err) => warn!("Could not read configuration from file: {err}"),
92 }
93 }
94
95 config.update_from_partial(&Partial::from_args());
96
97 debug!(new_config = ?config, "Configuration reloaded");
98
99 *self.inner.write() = config;
100 }
101
102 #[must_use]
105 pub fn redirector(&self) -> Redirector {
106 Redirector {
107 hsts: self.hsts(),
108 send_alt_svc: self.send_alt_svc(),
109 send_server: self.send_server(),
110 send_csp: self.send_csp(),
111 statistics: self.statistics(),
112 }
113 }
114
115 #[must_use]
117 pub fn log_level(&self) -> LogLevel {
118 self.inner.read().log_level
119 }
120
121 #[must_use]
123 pub fn token(&self) -> Arc<str> {
124 Arc::clone(&self.inner.read().token)
125 }
126
127 #[must_use]
129 pub fn listeners(&self) -> Vec<ListenAddress> {
130 self.inner.read().listeners.clone()
131 }
132
133 #[must_use]
135 pub fn statistics(&self) -> StatisticCategories {
136 self.inner.read().statistics
137 }
138
139 #[must_use]
141 pub fn default_certificate(&self) -> DefaultCertificateSource {
142 self.inner.read().default_certificate.clone()
143 }
144
145 #[must_use]
147 pub fn certificates(&self) -> Vec<CertificateSource> {
148 self.inner.read().certificates.clone()
149 }
150
151 #[must_use]
153 pub fn hsts(&self) -> Hsts {
154 self.inner.read().hsts
155 }
156
157 #[must_use]
159 pub fn https_redirect(&self) -> bool {
160 self.inner.read().https_redirect
161 }
162
163 #[must_use]
165 pub fn send_alt_svc(&self) -> bool {
166 self.inner.read().send_alt_svc
167 }
168
169 #[must_use]
171 pub fn send_server(&self) -> bool {
172 self.inner.read().send_server
173 }
174
175 #[must_use]
177 pub fn send_csp(&self) -> bool {
178 self.inner.read().send_csp
179 }
180
181 #[must_use]
183 pub fn store(&self) -> BackendType {
184 self.inner.read().store
185 }
186
187 #[must_use]
189 pub fn store_config(&self) -> HashMap<String, String> {
190 self.inner.read().store_config.clone()
191 }
192
193 #[must_use]
195 pub const fn file(&self) -> &Option<PathBuf> {
196 &self.file
197 }
198}
199
200impl Display for Config {
201 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
202 fmt.debug_struct("Config")
203 .field("log_level", &(self.log_level()).to_string())
204 .field(
205 "token",
206 &(self.token())
207 .chars()
208 .take(3)
209 .chain("...".chars())
210 .collect::<String>(),
211 )
212 .field("listeners", &serde_json::to_string(&self.listeners()))
213 .field("statistics", &serde_json::to_string(&self.statistics()))
214 .field("default_certificate", &self.default_certificate())
215 .field("certificates", &self.certificates())
216 .field("hsts", &self.hsts())
217 .field("https_redirect", &self.https_redirect())
218 .field("send_alt_svc", &self.send_alt_svc())
219 .field("send_server", &self.send_server())
220 .field("send_csp", &self.send_csp())
221 .field("store", &self.store())
222 .field("store_config", &self.store_config())
223 .field("file", &self.file())
224 .finish()
225 }
226}
227
228#[derive(Debug, PartialEq)]
230#[expect(clippy::struct_excessive_bools)]
231struct ConfigInner {
232 pub log_level: LogLevel,
236 pub token: Arc<str>,
238 pub listeners: Vec<ListenAddress>,
240 pub statistics: StatisticCategories,
242 pub default_certificate: DefaultCertificateSource,
244 pub certificates: Vec<CertificateSource>,
246 pub hsts: Hsts,
248 pub https_redirect: bool,
251 pub send_alt_svc: bool,
254 pub send_server: bool,
256 pub send_csp: bool,
258 pub store: BackendType,
260 pub store_config: HashMap<String, String>,
262}
263
264impl ConfigInner {
265 fn update_from_partial(&mut self, partial: &Partial) {
269 if let Some(log_level) = partial.log_level {
270 self.log_level = log_level;
271 }
272
273 if let Some(ref token) = partial.token {
274 self.token = Arc::from(token.as_str());
275 }
276
277 if let Some(ref listeners) = partial.listeners {
278 self.listeners.clone_from(listeners);
279 }
280
281 if let Some(statistics) = partial.statistics {
282 self.statistics = statistics;
283 }
284
285 if let Some(ref default_certificate) = partial.default_certificate {
286 self.default_certificate = default_certificate.clone();
287 }
288
289 if let Some(ref certificates) = partial.certificates {
290 self.certificates.clone_from(certificates);
291 }
292
293 if let Some(hsts) = partial.hsts() {
294 self.hsts = hsts;
295 }
296
297 if let Some(https_redirect) = partial.https_redirect {
298 self.https_redirect = https_redirect;
299 }
300
301 if let Some(send_alt_svc) = partial.send_alt_svc {
302 self.send_alt_svc = send_alt_svc;
303 }
304
305 if let Some(send_server) = partial.send_server {
306 self.send_server = send_server;
307 }
308
309 if let Some(send_csp) = partial.send_csp {
310 self.send_csp = send_csp;
311 }
312
313 if let Some(store) = partial.store {
314 self.store = store;
315 }
316
317 if let Some(ref store_config) = partial.store_config {
318 self.store_config
319 .extend(store_config.iter().map(|(k, v)| (k.clone(), v.clone())));
320 }
321 }
322}
323
324impl Default for ConfigInner {
325 fn default() -> Self {
326 Self {
327 log_level: LogLevel::default(),
328 token: rand::thread_rng()
329 .sample_iter(&Alphanumeric)
330 .take(32)
331 .map(char::from)
332 .collect::<String>()
333 .into(),
334 listeners: vec![
335 ListenAddress {
336 protocol: Protocol::Http,
337 address: None,
338 port: None,
339 },
340 ListenAddress {
341 protocol: Protocol::Https,
342 address: None,
343 port: None,
344 },
345 ListenAddress {
346 protocol: Protocol::Grpc,
347 address: Some(IpAddr::V6(Ipv6Addr::LOCALHOST)),
348 port: None,
349 },
350 ListenAddress {
351 protocol: Protocol::Grpcs,
352 address: None,
353 port: None,
354 },
355 ],
356 statistics: StatisticCategories::default(),
357 https_redirect: false,
358 default_certificate: DefaultCertificateSource::None,
359 certificates: Vec::default(),
360 hsts: Hsts::default(),
361 send_alt_svc: false,
362 send_server: true,
363 send_csp: true,
364 store: BackendType::default(),
365 store_config: HashMap::with_capacity(0),
366 }
367 }
368}
369
370#[derive(Copy, Clone, Debug, PartialEq, Eq)]
374pub struct Redirector {
375 pub hsts: Hsts,
377 pub send_alt_svc: bool,
380 pub send_server: bool,
382 pub send_csp: bool,
384 pub statistics: StatisticCategories,
386}
387
388#[derive(Copy, Clone, Debug, PartialEq, Eq)]
415pub enum Hsts {
416 Disable,
418 Enable(u32),
421 IncludeSubDomains(u32),
432 Preload(u32),
445}
446
447impl Default for Hsts {
448 fn default() -> Self {
449 Self::Enable(2 * A_YEAR)
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use crate::stats::StatisticType;
457
458 #[test]
459 fn config_inner_update_from_partial_all() {
460 let mut inner = ConfigInner::default();
461 let empty_partial = Partial::default();
462 let full_partial = Partial::from_toml(include_str!("../../example-config.toml")).unwrap();
463
464 inner.update_from_partial(&empty_partial);
465
466 assert_eq!(inner, ConfigInner {
467 token: Arc::clone(&inner.token),
469 ..Default::default()
470 });
471
472 inner.update_from_partial(&full_partial);
473
474 assert_ne!(inner, ConfigInner {
475 token: Arc::clone(&inner.token),
477 ..Default::default()
478 });
479 }
480
481 #[test]
482 fn config_inner_update_from_partial_overwrite_listeners() {
483 let mut inner = ConfigInner::default();
484 let first = Partial {
485 listeners: Some(vec![ListenAddress {
486 protocol: Protocol::Http,
487 address: Some("::1".parse().unwrap()),
488 port: None,
489 }]),
490 ..Default::default()
491 };
492 let second = Partial {
493 listeners: Some(vec![]),
494 ..Default::default()
495 };
496
497 inner.update_from_partial(&first);
498
499 assert!(!inner.listeners.is_empty());
500
501 inner.update_from_partial(&second);
502
503 assert!(inner.listeners.is_empty());
504 }
505
506 #[test]
507 fn config_inner_update_from_partial_overwrite_statistics() {
508 let mut inner = ConfigInner::default();
509 let first = Partial {
510 statistics: Some(StatisticCategories::ALL),
511 ..Default::default()
512 };
513 let second = Partial {
514 statistics: Some(StatisticCategories::NONE),
515 ..Default::default()
516 };
517
518 inner.update_from_partial(&first);
519
520 assert!(inner.statistics.specifies(StatisticType::Request));
521
522 inner.update_from_partial(&second);
523
524 assert!(!inner.statistics.specifies(StatisticType::Request));
525 }
526}