Skip to main content

prosa_utils/
config.rs

1//! Module for ProSA configuration object
2//!
3//! <svg width="40" height="40">
4#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/settings.svg"))]
5//! </svg>
6
7use std::{path::PathBuf, process::Command};
8
9use base64::{Engine as _, engine::general_purpose::STANDARD};
10use thiserror::Error;
11use url::Url;
12
13// Feature openssl or rusttls,...
14pub mod ssl;
15
16// Feature opentelemetry
17#[cfg(feature = "config-observability")]
18pub mod observability;
19
20// Feature tracing
21#[cfg(feature = "config-observability")]
22pub mod tracing;
23
24/// Error define for configuration object
25#[derive(Debug, Error)]
26pub enum ConfigError {
27    /// Error that indicate a wrong path format in filesystem
28    #[error("The config parameter {0} have an incorrect value `{1}`")]
29    WrongValue(String, String),
30    /// Error that indicate a wrong path format pattern in filesystem
31    #[error("The path `{0}` provided don't match the pattern `{1}`")]
32    WrongPathPattern(String, glob::PatternError),
33    /// Error that indicate a wrong path format in filesystem
34    #[error("The path `{0}` provided is not correct")]
35    WrongPath(PathBuf),
36    /// Error on a file read
37    #[error("The file `{0}` can't be read `{1}`")]
38    IoFile(String, std::io::Error),
39    #[cfg(feature = "config-openssl")]
40    /// SSL error
41    #[error("Openssl error `{0}`")]
42    OpenSsl(#[from] openssl::error::ErrorStack),
43}
44
45/// Method to try get the country name from the OS
46pub 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
62/// Method to try get the hostname from the OS
63pub 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
96/// Method to get authentication value out of URL username/password
97///
98/// - If user password is provided, it return *Basic* authentication with base64 encoded username:password
99/// - If only password is provided, it return *Bearer* authentication with the password as token
100///
101/// ```
102/// use url::Url;
103/// use prosa_utils::config::url_authentication;
104///
105/// let basic_auth_target = Url::parse("http://user:pass@localhost:8080").unwrap();
106/// assert_eq!(Some(String::from("Basic dXNlcjpwYXNz")), url_authentication(&basic_auth_target));
107///
108/// let bearer_auth_target = Url::parse("http://:token@localhost:8080").unwrap();
109/// assert_eq!(Some(String::from("Bearer token")), url_authentication(&bearer_auth_target));
110/// ```
111pub 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}