1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/settings.svg"))]
5use std::{path::PathBuf, process::Command};
8
9use base64::{Engine as _, engine::general_purpose::STANDARD};
10use thiserror::Error;
11use url::Url;
12
13pub mod ssl;
15
16#[cfg(feature = "config-observability")]
18pub mod observability;
19
20#[cfg(feature = "config-observability")]
22pub mod tracing;
23
24#[derive(Debug, Error)]
26pub enum ConfigError {
27 #[error("The config parameter {0} have an incorrect value `{1}`")]
29 WrongValue(String, String),
30 #[error("The path `{0}` provided don't match the pattern `{1}`")]
32 WrongPathPattern(String, glob::PatternError),
33 #[error("The path `{0}` provided is not correct")]
35 WrongPath(PathBuf),
36 #[error("The file `{0}` can't be read `{1}`")]
38 IoFile(String, std::io::Error),
39 #[cfg(feature = "config-openssl")]
40 #[error("Openssl error `{0}`")]
42 OpenSsl(#[from] openssl::error::ErrorStack),
43}
44
45pub fn os_country() -> Option<String> {
47 if let Some(lang) = option_env!("LANG") {
48 let language = if let Some(pos) = lang.find('.') {
49 &lang[..pos]
50 } else {
51 lang
52 };
53
54 if let Some(pos) = language.find('_') {
55 return Some(String::from(&language[pos + 1..]));
56 }
57 }
58
59 None
60}
61
62pub fn hostname() -> Option<String> {
64 #[cfg(target_family = "unix")]
65 if let Ok(host) = std::env::var("HOSTNAME").map(|h| h.trim().to_string())
66 && !host.is_empty()
67 && !host.contains('\n')
68 {
69 return Some(host);
70 }
71
72 #[cfg(target_family = "unix")]
73 return Command::new("hostname")
74 .arg("-s")
75 .output()
76 .ok()
77 .and_then(|h| {
78 str::from_utf8(h.stdout.trim_ascii())
79 .ok()
80 .filter(|h| !h.is_empty() && !h.contains('\n'))
81 .map(|h| h.to_string())
82 });
83
84 #[cfg(target_family = "windows")]
85 return Command::new("hostname").output().ok().and_then(|h| {
86 str::from_utf8(h.stdout.trim_ascii())
87 .ok()
88 .filter(|h| !h.is_empty() && !h.contains('\n'))
89 .map(|h| h.to_string())
90 });
91
92 #[cfg(all(not(target_family = "unix"), not(target_family = "windows")))]
93 return None;
94}
95
96pub fn url_authentication(url: &Url) -> Option<String> {
112 if let Some(password) = url.password().map(|p| {
113 p.replace("%24", "$")
114 .replace("%26", "&")
115 .replace("%3D", "=")
116 }) {
117 if url.username().is_empty() {
118 Some(format!("Bearer {password}"))
119 } else {
120 Some(format!(
121 "Basic {}",
122 STANDARD.encode(format!("{}:{}", url.username(), password))
123 ))
124 }
125 } else {
126 None
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_os_country() {
136 let country = os_country();
137 if let Some(cn) = country {
138 assert_eq!(2, cn.len());
139 }
140 }
141
142 #[test]
143 fn test_hostname() {
144 let host = hostname();
145 if let Some(hn) = host {
146 assert!(!hn.is_empty());
147 }
148 }
149
150 #[test]
151 fn test_url_authentication_basic() {
152 let basic_auth_target = Url::parse("http://user:pass@localhost:8080").unwrap();
153 assert_eq!(
154 Some(String::from("Basic dXNlcjpwYXNz")),
155 url_authentication(&basic_auth_target)
156 );
157 }
158
159 #[test]
160 fn test_url_safe_authentication_basic() {
161 let basic_auth_target = Url::parse("http://user:$ab&cd=@localhost:8080").unwrap();
162 assert_eq!(
163 Some(String::from("Basic dXNlcjokYWImY2Q9")),
164 url_authentication(&basic_auth_target)
165 );
166 }
167
168 #[test]
169 fn test_url_authentication_bearer() {
170 let bearer_auth_target = Url::parse("http://:token@localhost:8080").unwrap();
171 assert_eq!(
172 Some(String::from("Bearer token")),
173 url_authentication(&bearer_auth_target)
174 );
175 }
176}