1use std::{
2 ffi::{OsStr, OsString},
3 fmt::{self, Display, Formatter},
4 string::ToString,
5};
6
7use anyhow::Result;
8use clap::{builder::TypedValueParser, Arg, Command};
9use nom::{
10 branch::alt,
11 bytes::complete::{tag, take_while1},
12 character::{complete::char, is_alphanumeric},
13 sequence::separated_pair,
14 IResult,
15};
16use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
17use thiserror::Error;
18
19#[derive(Error, Debug)]
20pub enum XattrErr {
21 #[error("Attempted to initialize Xattr with unapproved namespace {0}")]
22 IllegalNamespace(String),
23}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
26pub enum Namespace {
27 Security,
28 System,
29 Trusted,
30 User,
31}
32
33impl Namespace {
34 pub fn len(&self) -> usize {
35 match self {
36 Self::Security => 8,
37 Self::System => 6,
38 Self::Trusted => 7,
39 Self::User => 4,
40 }
41 }
42}
43
44impl Into<String> for Namespace {
45 fn into(self) -> String {
46 let s: &str = self.into();
47 String::from(s)
48 }
49}
50impl Into<&'static str> for Namespace {
51 fn into(self) -> &'static str {
52 match self {
53 Self::Security => "security",
54 Self::System => "system",
55 Self::Trusted => "trusted",
56 Self::User => "user",
57 }
58 }
59}
60
61#[derive(Error, Debug)]
62pub enum NamespaceErr {
63 #[error("Illegal xattr namespace {0}")]
64 IllegalNamespace(String),
65}
66
67impl TryFrom<String> for Namespace {
68 type Error = anyhow::Error;
69 fn try_from(s: String) -> Result<Self, Self::Error> {
70 Self::try_from(s.as_str())
71 }
72}
73
74impl TryFrom<&str> for Namespace {
75 type Error = anyhow::Error;
76 fn try_from(s: &str) -> Result<Self, Self::Error> {
77 match s {
78 "security" => Ok(Self::Security),
79 "system" => Ok(Self::System),
80 "trusted" => Ok(Self::Trusted),
81 "user" => Ok(Self::User),
82 x => Err(NamespaceErr::IllegalNamespace(x.to_string()).into()),
83 }
84 }
85}
86
87impl Display for Namespace {
88 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
89 let s: String = self.clone().into();
90 write!(f, "{}", s)
91 }
92}
93
94#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
95pub struct Xattr {
96 pub namespace: Namespace,
97 pub attr: String,
98}
99
100impl Xattr {
101 pub fn new<S: ToString>(namespace: Namespace, attr: S) -> Self {
102 Self {
103 namespace,
104 attr: attr.to_string(),
105 }
106 }
107
108 pub fn to_osstring(&self) -> OsString {
109 OsString::from(self.to_string())
110 }
111
112 pub fn to_string(&self) -> String {
113 format!("{}.{}", self.namespace, self.attr)
114 }
115
116 pub fn len(&self) -> usize {
117 self.namespace.len() + self.attr.len() + 1
118 }
119
120 pub fn to_minimal_string(&self) -> String {
121 if self.namespace == Namespace::User {
122 self.attr.clone()
123 } else {
124 self.to_string()
125 }
126 }
127
128 pub fn minimal_len(&self) -> usize {
129 if self.namespace == Namespace::User {
130 self.attr.len()
131 } else {
132 self.len()
133 }
134 }
135
136 pub fn sub<S: ToString>(&self, sub_xattr: S) -> Xattr {
141 let sub = sub_xattr.to_string();
142
143 let attr = format!("{}.{}", self.attr, sub);
144
145 Self::new(self.namespace, attr)
146 }
147}
148
149impl From<&str> for Xattr {
150 fn from(s: &str) -> Self {
151 parse_xattr(s.as_bytes()).unwrap().1
152 }
153}
154
155impl From<&[u8]> for Xattr {
156 fn from(b: &[u8]) -> Self {
157 parse_xattr(b).unwrap().1
158 }
159}
160
161impl Display for Xattr {
162 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
163 write!(f, "{}.{}", self.namespace, self.attr)
164 }
165}
166
167pub struct StringVisitor;
168impl<'de> Visitor<'de> for StringVisitor {
169 type Value = String;
170 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
171 formatter.write_str("a string")
172 }
173
174 fn visit_string<E>(self, v: String) -> Result<Self::Value, E> {
175 Ok(v)
176 }
177
178 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> {
179 Ok(v.to_string())
180 }
181}
182
183impl Serialize for Xattr {
184 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
185 where
186 S: Serializer,
187 {
188 serializer.serialize_str(self.to_minimal_string().as_str())
189 }
190}
191
192impl<'a> Deserialize<'a> for Xattr {
193 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194 where
195 D: Deserializer<'a>,
196 {
197 let s = deserializer.deserialize_string(StringVisitor).unwrap();
198 let parts: Vec<&str> = s.split(".").collect();
199 if parts.len() == 1 {
200 Ok(Self::new(Namespace::User, parts[0]))
201 } else {
202 match Namespace::try_from(parts[0]) {
203 Ok(ns) => Ok(Self::new(ns, parts[1])),
204 Err(_) => Ok(Self::new(Namespace::User, s)),
205 }
206 }
207 }
208}
209
210fn is_alphanumeric_or_dot_or_dash(c: u8) -> bool {
211 is_alphanumeric(c) || c == '.' as u8 || c == '-' as u8
212}
213
214fn parse_xattr_complete(i: &[u8]) -> IResult<&[u8], Xattr> {
215 separated_pair(
216 alt((tag("trusted"), tag("security"), tag("system"), tag("user"))),
217 char('.'),
218 take_while1(is_alphanumeric_or_dot_or_dash),
219 )(i)
220 .map(|(i, (namespace, attr))| {
221 (
222 i,
223 Xattr::new(
224 Namespace::try_from(
225 String::from_utf8(namespace.iter().copied().collect())
226 .unwrap_or_else(|e| panic!("Namespace was not UTF-8: {}", e)),
227 )
228 .unwrap(),
229 String::from_utf8(attr.iter().copied().collect())
230 .unwrap_or_else(|e| panic!("Attribute was not UTF-8: {}", e)),
231 ),
232 )
233 })
234}
235
236fn parse_xattr_bare(i: &[u8]) -> IResult<&[u8], Xattr> {
237 take_while1(is_alphanumeric_or_dot_or_dash)(i).map(|(i, attr)| {
238 (
239 i,
240 Xattr::new(Namespace::User, String::from_utf8(attr.to_vec()).unwrap()),
241 )
242 })
243}
244
245pub fn parse_xattr(i: &[u8]) -> IResult<&[u8], Xattr> {
246 alt((parse_xattr_complete, parse_xattr_bare))(i)
247}
248
249#[test]
250fn test_parse_xattr() {
251 assert!(parse_xattr(b"").is_err());
252 assert_eq!(
253 parse_xattr(b"abc"),
254 Ok((b"".as_slice(), Xattr::new(Namespace::User, "abc")))
255 );
256
257 assert_eq!(
258 parse_xattr(b"system.abc"),
259 Ok((b"".as_slice(), Xattr::new(Namespace::System, "abc")))
260 );
261 assert_eq!(
262 parse_xattr(b"blah.blah.blah"),
263 Ok((
264 b"".as_slice(),
265 Xattr::new(Namespace::User, "blah.blah.blah")
266 ))
267 );
268 assert_eq!(
269 parse_xattr(b"user.ghee.test.init"),
270 Ok((
271 b"".as_slice(),
272 Xattr::new(Namespace::User, "ghee.test.init")
273 ))
274 );
275 assert_eq!(
276 parse_xattr(b"test2"),
277 Ok((b"".as_slice(), Xattr::new(Namespace::User, "test2")))
278 );
279 assert_eq!(
280 parse_xattr(b"most-recent-snapshot"),
281 Ok((
282 b"".as_slice(),
283 Xattr::new(Namespace::User, "most-recent-snapshot")
284 ))
285 );
286}
287#[derive(Clone)]
288pub struct XattrParser;
289impl TypedValueParser for XattrParser {
290 type Value = Xattr;
291
292 fn parse_ref(
293 &self,
294 _cmd: &Command,
295 _arg: Option<&Arg>,
296 value: &OsStr,
297 ) -> Result<Self::Value, clap::error::Error<clap::error::RichFormatter>> {
298 parse_xattr(value.to_string_lossy().as_bytes())
299 .map(|(_remainder, predicate)| predicate)
300 .map_err(|e| {
301 clap::error::Error::raw(
302 clap::error::ErrorKind::InvalidValue,
303 format!("Malformed xattr: {}\n", e),
304 )
305 })
306 }
307}