use std::{ ffi::{OsStr, OsString}, fmt::{Display, Formatter}, os::unix::prelude::OsStringExt, }; use clap::{builder::TypedValueParser, Arg, Command}; use nom::{ branch::alt, bytes::complete::{tag, take_while1}, character::complete::char, character::is_alphabetic, sequence::separated_pair, IResult, }; use serde::Serialize; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)] pub struct Xattr { pub namespace: Vec, pub attr: Vec, all: Vec, } impl Xattr { pub fn new(namespace: Vec, attr: Vec) -> Self { let all: Vec = namespace .iter() .copied() .chain(std::iter::once(b'.')) .chain(attr.iter().copied()) .collect(); Self { namespace, attr, all, } } pub fn to_osstring(&self) -> OsString { OsString::from_vec(self.all.clone()) } } // impl AsRef for Xattr { // fn as_ref(&self) -> &OsStr { // OsString::from_vec(self.all.clone()).as_os_str() // } // } 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, "{}.{}", String::from_utf8_lossy(self.namespace.as_slice()), String::from_utf8_lossy(self.attr.as_slice()) ) } } /// Parse the `namespace.attr` (or just bare `attr`) xattr names, filling in the `user` namespace /// when none is provided. // pub fn parse_xattr<'a, E: ParseError<&'a [u8]>>(i: &'a [u8]) -> IResult<&'a [u8], Xattr, E> { // pair( // alt((tag(""), terminated(take_while(is_alphabetic), tag(".")))), // take_while(is_alphabetic), // )(i) // .map(|(i, (namespace, attr))| { // ( // i, // Xattr { // namespace: if namespace.is_empty() { // b"user.".into_iter().copied().collect() // } else { // namespace.iter().copied().collect() // }, // attr: attr.iter().copied().collect(), // }, // ) // }) // } fn is_alphabetic_or_dot(c: u8) -> bool { is_alphabetic(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_alphabetic_or_dot), )(i) .map(|(i, (namespace, attr))| { ( i, Xattr::new( namespace.iter().copied().collect(), attr.iter().copied().collect(), ), ) }) } fn parse_xattr_bare(i: &[u8]) -> IResult<&[u8], Xattr> { take_while1(is_alphabetic_or_dot)(i).map(|(i, attr)| { ( i, Xattr::new(b"user".to_vec(), attr.iter().copied().collect()), ) }) } // pub fn parse_xattr(i: &[u8]) -> IResult<&[u8], Xattr> { // pair( // alt((tag(""), terminated(take_while(is_alphabetic), tag(".")))), // take_while1(is_alphabetic), // )(i) // .map(|(i, (namespace, attr))| { // ( // i, // Xattr { // namespace: if namespace.is_empty() { // b"user".into_iter().copied().collect() // } else { // namespace.iter().copied().collect() // }, // attr: attr.iter().copied().collect(), // }, // ) // }) // } 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(b"user".to_vec(), b"abc".to_vec()) )) ); assert_eq!( parse_xattr(b"system.abc"), Ok(( b"".as_slice(), Xattr::new(b"system".to_vec(), b"abc".to_vec()) )) ); assert_eq!( parse_xattr(b"blah.blah.blah"), Ok(( b"".as_slice(), Xattr::new(b"user".to_vec(), b"blah.blah.blah".to_vec()) )) ); } #[derive(Clone)] pub struct XattrParser; impl TypedValueParser for XattrParser { type Value = Xattr; fn parse_ref( &self, _cmd: &Command, _arg: Option<&Arg>, value: &OsStr, ) -> Result> { 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), ) }) } }