use alloc::{string::String, vec::Vec};
#[cfg(feature = "std")]
use alloc::vec;
#[cfg(feature = "std")]
use std::{env, fs::File, io::Read};
#[cfg(all(feature = "async", not(feature = "tokio-support")))]
use blocking::{unblock, Unblock};
#[cfg(feature = "async")]
use futures_lite::{AsyncRead, AsyncReadExt};
#[cfg(feature = "tokio-support")]
use tokio_util::compat::TokioAsyncReadCompatExt as _;
#[derive(Default, Debug)]
pub struct AuthInfo {
pub name: String,
pub data: Vec<u8>,
pub family: u16,
pub address: Vec<u8>,
pub number: Vec<u8>,
}
#[cfg(feature = "std")]
#[inline]
fn counted_string(bytes: &mut &[u8]) -> Option<Vec<u8>> {
if bytes.len() < 2 {
log::error!("Auth did not contain length bytes");
return None;
}
let length: [u8; 2] = [bytes[0], bytes[1]];
let length = u16::from_be_bytes(length) as usize;
if bytes.len() < 2 + length {
log::error!("Auth did not contain string bytes");
return None;
}
let res = (&bytes[2..length + 2]).to_vec();
*bytes = &bytes[length + 2..];
Some(res)
}
#[cfg(feature = "std")]
impl AuthInfo {
#[inline]
fn from_buffer(s: &mut &[u8]) -> Option<Self> {
if s.len() < 2 {
log::error!("Auth did not contain family bytes");
return None;
}
let family: [u8; 2] = [s[0], s[1]];
let family = u16::from_be_bytes(family);
let mut cursor = &s[2..];
let address = counted_string(&mut cursor)?;
let number = counted_string(&mut cursor)?;
let name = match String::from_utf8(counted_string(&mut cursor)?) {
Ok(name) => name,
Err(e) => {
log::warn!("Name was not valid UTF-8, doing substitution.");
let mut name = e.into_bytes();
name.retain(|b| *b < 128);
match String::from_utf8(name) {
Ok(name) => name,
Err(_) => return None,
}
}
};
let data = counted_string(&mut cursor)?;
*s = &s[10 + address.len() + number.len() + name.len() + data.len()..];
Some(AuthInfo {
name,
data,
family,
address,
number,
})
}
#[inline]
fn many_from_buffer(mut s: &[u8]) -> Option<Vec<Self>> {
let mut res = vec![];
while !s.is_empty() {
res.push(Self::from_buffer(&mut s)?);
}
Some(res)
}
#[inline]
#[must_use]
pub fn from_stream<R: Read>(reader: &mut R) -> Option<Vec<Self>> {
let mut buffer = Vec::with_capacity(128);
let _ = reader.read_to_end(&mut buffer).ok()?;
Self::many_from_buffer(&buffer)
}
#[cfg(feature = "async")]
#[inline]
#[must_use]
pub async fn from_stream_async<R: AsyncRead + Unpin>(reader: &mut R) -> Option<Vec<Self>> {
let mut buffer = Vec::with_capacity(128);
let _ = reader.read_to_end(&mut buffer).await.ok()?;
Self::many_from_buffer(&buffer)
}
#[inline]
#[must_use]
pub fn from_xauthority() -> Option<Vec<Self>> {
let fname = env::var_os("XAUTHORITY")?;
let mut file = File::open(&fname).ok()?;
Self::from_stream(&mut file)
}
#[cfg(feature = "async")]
#[inline]
#[must_use]
pub async fn from_xauthority_async() -> Option<Vec<Self>> {
let fname = env::var_os("XAUTHORITY")?;
cfg_if::cfg_if! {
if #[cfg(feature = "tokio-support")] {
let mut file = tokio::fs::File::open(&fname).await.ok()?.compat();
Self::from_stream_async(&mut file).await
} else {
let file = unblock(move || File::open(&fname)).await.ok()?;
let mut file = Unblock::new(file);
Self::from_stream_async(&mut file).await
}
}
}
#[inline]
pub(crate) fn get() -> Self {
if cfg!(test) {
return Default::default();
}
if let Some(mut v) = Self::from_xauthority() {
if v.is_empty() {
Default::default()
} else {
v.remove(0)
}
} else {
log::error!("Failed to get AuthInfo from XAUTHORITY, using empty auth info");
Default::default()
}
}
#[cfg(feature = "async")]
#[inline]
pub(crate) async fn get_async() -> Self {
if cfg!(test) {
return Default::default();
}
match Self::from_xauthority_async().await {
Some(mut v) => {
if v.is_empty() {
Default::default()
} else {
v.remove(0)
}
}
None => Default::default(),
}
}
}
#[cfg(not(feature = "std"))]
impl AuthInfo {
#[inline]
pub(crate) fn get() -> Self {
Default::default()
}
}