use std::{
ffi::{OsStr, OsString},
fmt::{self, Display, Formatter},
};
use anyhow::Result;
use clap::{builder::TypedValueParser, Arg, Command};
use nom::{
branch::alt,
bytes::complete::{tag, take_while1},
character::{complete::char, is_alphanumeric},
sequence::separated_pair,
IResult,
};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum XattrErr {
#[error("Attempted to initialize Xattr with unapproved namespace {0}")]
IllegalNamespace(String),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Namespace {
Security,
System,
Trusted,
User,
}
impl Namespace {
pub fn len(&self) -> usize {
match self {
Self::Security => 8,
Self::System => 6,
Self::Trusted => 7,
Self::User => 4,
}
}
}
impl Into<String> for Namespace {
fn into(self) -> String {
let s: &str = self.into();
String::from(s)
}
}
impl Into<&'static str> for Namespace {
fn into(self) -> &'static str {
match self {
Self::Security => "security",
Self::System => "system",
Self::Trusted => "trusted",
Self::User => "user",
}
}
}
#[derive(Error, Debug)]
pub enum NamespaceErr {
#[error("Illegal xattr namespace {0}")]
IllegalNamespace(String),
}
impl TryFrom<String> for Namespace {
type Error = anyhow::Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::try_from(s.as_str())
}
}
impl TryFrom<&str> for Namespace {
type Error = anyhow::Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"security" => Ok(Self::Security),
"system" => Ok(Self::System),
"trusted" => Ok(Self::Trusted),
"user" => Ok(Self::User),
x => Err(NamespaceErr::IllegalNamespace(x.to_string()).into()),
}
}
}
impl Display for Namespace {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let s: String = self.clone().into();
write!(f, "{}", s)
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Xattr {
pub namespace: Namespace,
pub attr: String,
}
impl Xattr {
pub fn new<S: std::string::ToString>(namespace: Namespace, attr: S) -> Self {
Self {
namespace,
attr: attr.to_string(),
}
}
pub fn to_osstring(&self) -> OsString {
OsString::from(self.to_string())
}
pub fn to_string(&self) -> String {
format!("{}.{}", self.namespace, self.attr)
}
pub fn len(&self) -> usize {
self.namespace.len() + self.attr.len() + 1
}
pub fn to_minimal_string(&self) -> String {
if self.namespace == Namespace::User {
self.attr.clone()
} else {
self.to_string()
}
}
pub fn minimal_len(&self) -> usize {
if self.namespace == Namespace::User {
self.attr.len()
} else {
self.len()
}
}
}
impl From<&str> for Xattr {
fn from(s: &str) -> Self {
parse_xattr(s.as_bytes()).unwrap().1
}
}
impl From<&[u8]> for Xattr {
fn from(b: &[u8]) -> Self {
parse_xattr(b).unwrap().1
}
}
impl Display for Xattr {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}.{}", self.namespace, self.attr)
}
}
pub struct StringVisitor;
impl<'de> Visitor<'de> for StringVisitor {
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
Ok(v)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
Ok(v.to_string())
}
}
impl Serialize for Xattr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_minimal_string().as_str())
}
}
impl<'a> Deserialize<'a> for Xattr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
let s = deserializer.deserialize_string(StringVisitor).unwrap();
let parts: Vec<&str> = s.split(".").collect();
if parts.len() == 1 {
Ok(Self::new(Namespace::User, parts[0]))
} else {
match Namespace::try_from(parts[0]) {
Ok(ns) => Ok(Self::new(ns, parts[1])),
Err(_) => Ok(Self::new(Namespace::User, s)),
}
}
}
}
fn is_alphanumeric_or_dot(c: u8) -> bool {
is_alphanumeric(c) || c == '.' as u8
}
fn parse_xattr_complete(i: &[u8]) -> IResult<&[u8], Xattr> {
separated_pair(
alt((tag("trusted"), tag("security"), tag("system"), tag("user"))),
char('.'),
take_while1(is_alphanumeric_or_dot),
)(i)
.map(|(i, (namespace, attr))| {
(
i,
Xattr::new(
Namespace::try_from(
String::from_utf8(namespace.iter().copied().collect())
.unwrap_or_else(|e| panic!("Namespace was not UTF-8: {}", e)),
)
.unwrap(),
String::from_utf8(attr.iter().copied().collect())
.unwrap_or_else(|e| panic!("Attribute was not UTF-8: {}", e)),
),
)
})
}
fn parse_xattr_bare(i: &[u8]) -> IResult<&[u8], Xattr> {
take_while1(is_alphanumeric_or_dot)(i).map(|(i, attr)| {
(
i,
Xattr::new(Namespace::User, String::from_utf8(attr.to_vec()).unwrap()),
)
})
}
pub fn parse_xattr(i: &[u8]) -> IResult<&[u8], Xattr> {
alt((parse_xattr_complete, parse_xattr_bare))(i)
}
#[test]
fn test_parse_xattr() {
assert!(parse_xattr(b"").is_err());
assert_eq!(
parse_xattr(b"abc"),
Ok((b"".as_slice(), Xattr::new(Namespace::User, "abc")))
);
assert_eq!(
parse_xattr(b"system.abc"),
Ok((b"".as_slice(), Xattr::new(Namespace::System, "abc")))
);
assert_eq!(
parse_xattr(b"blah.blah.blah"),
Ok((
b"".as_slice(),
Xattr::new(Namespace::User, "blah.blah.blah")
))
);
assert_eq!(
parse_xattr(b"user.ghee.test.init"),
Ok((
b"".as_slice(),
Xattr::new(Namespace::User, "ghee.test.init")
))
);
assert_eq!(
parse_xattr(b"test2"),
Ok((b"".as_slice(), Xattr::new(Namespace::User, "test2")))
);
}
#[derive(Clone)]
pub struct XattrParser;
impl TypedValueParser for XattrParser {
type Value = Xattr;
fn parse_ref(
&self,
_cmd: &Command,
_arg: Option<&Arg>,
value: &OsStr,
) -> Result<Self::Value, clap::error::Error<clap::error::RichFormatter>> {
parse_xattr(value.to_string_lossy().as_bytes())
.map(|(_remainder, predicate)| predicate)
.map_err(|e| {
clap::error::Error::raw(
clap::error::ErrorKind::InvalidValue,
format!("Malformed xattr: {}\n", e),
)
})
}
}