#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/settings.svg"))]
use std::{io, path::PathBuf, process::Command};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use thiserror::Error;
use url::Url;
use uuid::Uuid;
pub mod ssl;
#[cfg(feature = "config-observability")]
pub mod observability;
#[cfg(feature = "config-observability")]
pub mod tracing;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("The config parameter {0} have an incorrect value `{1}`")]
WrongValue(String, String),
#[error("The path `{0}` provided don't match the pattern `{1}`")]
WrongPathPattern(String, glob::PatternError),
#[error("The path `{0}` provided is not correct")]
WrongPath(PathBuf),
#[error("The file `{0}` can't be read `{1}`")]
IoFile(String, std::io::Error),
#[cfg(feature = "config-openssl")]
#[error("Openssl error `{0}`")]
OpenSsl(#[from] openssl::error::ErrorStack),
}
impl From<ConfigError> for io::Error {
fn from(err: ConfigError) -> Self {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("ProSA Config error: {}", err),
)
}
}
pub fn os_country() -> Option<String> {
if let Some(lang) = option_env!("LANG") {
let language = if let Some(pos) = lang.find('.') {
&lang[..pos]
} else {
lang
};
if let Some(pos) = language.find('_') {
return Some(String::from(&language[pos + 1..]));
}
}
None
}
pub fn hostname() -> Option<String> {
#[cfg(target_family = "unix")]
if let Ok(host) = std::env::var("HOSTNAME").map(|h| h.trim().to_string())
&& !host.is_empty()
&& !host.contains('\n')
{
return Some(host);
}
#[cfg(target_family = "unix")]
return Command::new("hostname")
.arg("-s")
.output()
.ok()
.and_then(|h| {
str::from_utf8(h.stdout.trim_ascii())
.ok()
.filter(|h| !h.is_empty() && !h.contains('\n'))
.map(|h| h.to_string())
});
#[cfg(target_family = "windows")]
return Command::new("hostname").output().ok().and_then(|h| {
str::from_utf8(h.stdout.trim_ascii())
.ok()
.filter(|h| !h.is_empty() && !h.contains('\n'))
.map(|h| h.to_string())
});
#[cfg(all(not(target_family = "unix"), not(target_family = "windows")))]
return None;
}
pub fn hostid() -> String {
#[cfg(target_os = "linux")]
if let Ok(machine_id) = std::fs::read_to_string("/etc/machine-id")
&& let Ok(machine_uuid) = Uuid::parse_str(machine_id.trim())
{
return machine_uuid.to_string();
}
#[cfg(target_os = "macos")]
if let Ok(output) = Command::new("ioreg")
.args(["-rd1", "-c", "IOPlatformExpertDevice"])
.output()
&& output.status.success()
&& let Ok(output_str) = String::from_utf8(output.stdout)
{
for line in output_str.lines() {
if line.contains("IOPlatformUUID")
&& let Some(value) = line.split('"').nth(3)
&& let Ok(machine_uuid) = Uuid::parse_str(value.trim())
{
return machine_uuid.to_string();
}
}
}
if let Some(hostname) = hostname() {
let mut node_id = [0u8; 6];
let len = hostname.len().min(6);
node_id[..len].copy_from_slice(&hostname.as_bytes()[hostname.len() - len..]);
Uuid::now_v1(&node_id).to_string()
} else {
Uuid::new_v4().to_string()
}
}
pub fn url_authentication(url: &Url) -> Option<String> {
if let Some(password) = url.password().map(|p| {
p.replace("%24", "$")
.replace("%26", "&")
.replace("%3D", "=")
}) {
if url.username().is_empty() {
Some(format!("Bearer {password}"))
} else {
Some(format!(
"Basic {}",
STANDARD.encode(format!("{}:{}", url.username(), password))
))
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_os_country() {
let country = os_country();
if let Some(cn) = country {
assert_eq!(2, cn.len());
}
}
#[test]
fn test_hostname() {
let host = hostname();
if let Some(hn) = host {
assert!(!hn.is_empty());
}
}
#[test]
fn test_hostid() {
let host_id = hostid();
assert_eq!(host_id.len(), 36);
}
#[test]
fn test_url_authentication_basic() {
let basic_auth_target = Url::parse("http://user:pass@localhost:8080")
.expect("Basic auth target URL should be valid");
assert_eq!(
Some(String::from("Basic dXNlcjpwYXNz")),
url_authentication(&basic_auth_target)
);
}
#[test]
fn test_url_safe_authentication_basic() {
let basic_auth_target = Url::parse("http://user:$ab&cd=@localhost:8080")
.expect("Basic auth target URL should be valid");
assert_eq!(
Some(String::from("Basic dXNlcjokYWImY2Q9")),
url_authentication(&basic_auth_target)
);
}
#[test]
fn test_url_authentication_bearer() {
let bearer_auth_target = Url::parse("http://:token@localhost:8080")
.expect("Basic auth target URL should be valid");
assert_eq!(
Some(String::from("Bearer token")),
url_authentication(&bearer_auth_target)
);
}
}