use std::fmt;
use std::mem;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
pub struct LogPrefix {
inner: Arc<Mutex<Inner>>,
}
#[derive(Clone)]
struct Inner {
protocol: String,
user: Option<String>,
helo: Option<String>,
ua_name: Option<String>,
ua_version: Option<String>,
}
impl LogPrefix {
pub fn new(protocol: String) -> Self {
Self {
inner: Arc::new(Mutex::new(Inner {
protocol,
user: None,
helo: None,
ua_name: None,
ua_version: None,
})),
}
}
pub fn deep_clone(&self) -> Self {
let inner = self.inner.lock().unwrap();
Self {
inner: Arc::new(Mutex::new(Inner::clone(&inner))),
}
}
pub fn set_user(&self, user: String) {
self.inner.lock().unwrap().user = Some(sanitise(user));
}
pub fn set_helo(&self, helo: String) {
self.inner.lock().unwrap().helo = Some(sanitise(helo));
}
pub fn set_user_agent(
&self,
name: Option<String>,
version: Option<String>,
) {
let mut inner = self.inner.lock().unwrap();
inner.ua_name = name.map(sanitise);
inner.ua_version = version.map(sanitise);
}
}
impl fmt::Display for LogPrefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let inner = self.inner.lock().unwrap();
write!(f, "{}", inner.protocol)?;
if inner.user.is_some()
|| inner.helo.is_some()
|| inner.ua_name.is_some()
|| inner.ua_version.is_some()
{
write!(f, "[")?;
let mut first = true;
if let Some(ref user) = inner.user {
write!(f, "{user}")?;
first = false;
}
if let Some(ref helo) = inner.helo {
if !mem::take(&mut first) {
write!(f, " ")?;
}
write!(f, "helo={helo}")?;
}
if inner.ua_name.is_some() || inner.ua_version.is_some() {
if !mem::take(&mut first) {
write!(f, " ")?;
}
write!(
f,
"agent={}/{}",
inner.ua_name.as_deref().unwrap_or("unknown"),
inner.ua_version.as_deref().unwrap_or("unknown"),
)?;
}
write!(f, "]")?;
}
Ok(())
}
}
fn sanitise(mut s: String) -> String {
s.retain(|c| !c.is_control());
if let Some((truncate_len, _)) = s.char_indices().nth(64) {
s.truncate(truncate_len);
}
s
}