1#[cfg(feature = "rust173-polyfill")]
14use crate::rust173::IntIsMultipleOf as _;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Xattr {
19 pub name: String,
22 pub value: Vec<u8>,
25}
26
27impl Xattr {
28 pub fn value_display(&self) -> String {
32 if let Ok(s) = std::str::from_utf8(&self.value) {
33 if !s.is_empty() && s.chars().all(|c| !c.is_control() || c == '\n' || c == '\t') {
34 return s.to_string();
35 }
36 }
37 format!("0x{}", hex_encode(&self.value))
38 }
39}
40
41pub fn hex_encode(bytes: &[u8]) -> String {
43 let mut s = String::with_capacity(bytes.len() * 2);
44 for b in bytes {
45 s.push(char::from_digit((b >> 4) as u32, 16).unwrap());
46 s.push(char::from_digit((b & 0xf) as u32, 16).unwrap());
47 }
48 s
49}
50
51pub fn parse_value(input: &str) -> Result<Vec<u8>, String> {
55 let trimmed = input.trim();
56 if let Some(hex) = trimmed
57 .strip_prefix("0x")
58 .or_else(|| trimmed.strip_prefix("0X"))
59 {
60 let hex: String = hex.chars().filter(|c| !c.is_whitespace()).collect();
61 if !hex.len().is_multiple_of(2) {
62 return Err("hex value must have an even number of digits".into());
63 }
64 let mut out = Vec::with_capacity(hex.len() / 2);
65 let bytes = hex.as_bytes();
66 let mut i = 0;
67 while i < bytes.len() {
68 let hi = (bytes[i] as char)
69 .to_digit(16)
70 .ok_or_else(|| format!("invalid hex digit {:?}", bytes[i] as char))?;
71 let lo = (bytes[i + 1] as char)
72 .to_digit(16)
73 .ok_or_else(|| format!("invalid hex digit {:?}", bytes[i + 1] as char))?;
74 out.push(((hi << 4) | lo) as u8);
75 i += 2;
76 }
77 Ok(out)
78 } else {
79 Ok(input.as_bytes().to_vec())
80 }
81}
82
83pub const NAMESPACES: &[&str] = &["user.", "trusted.", "security.", "system."];
86
87pub fn has_valid_namespace(name: &str) -> bool {
89 NAMESPACES.iter().any(|p| name.starts_with(p))
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn text_values_display_as_text_binary_as_hex() {
98 let t = Xattr {
99 name: "user.mime_type".into(),
100 value: b"text/plain".to_vec(),
101 };
102 assert_eq!(t.value_display(), "text/plain");
103 let b = Xattr {
104 name: "security.capability".into(),
105 value: vec![1, 0, 0, 2, 0xff],
106 };
107 assert_eq!(b.value_display(), "0x01000002ff");
108 }
109
110 #[test]
111 fn parse_round_trips_hex_and_text() {
112 assert_eq!(parse_value("0x0100ff").unwrap(), vec![1, 0, 0xff]);
113 assert_eq!(parse_value("0x01 00 ff").unwrap(), vec![1, 0, 0xff]);
114 assert_eq!(parse_value("text/plain").unwrap(), b"text/plain".to_vec());
115 assert!(parse_value("0x123").is_err(), "odd hex length rejected");
116 assert!(parse_value("0xzz").is_err(), "bad hex digit rejected");
117 }
118
119 #[test]
120 fn namespace_validation() {
121 assert!(has_valid_namespace("security.capability"));
122 assert!(has_valid_namespace("user.foo"));
123 assert!(!has_valid_namespace("bogus.foo"));
124 assert!(!has_valid_namespace("capability"));
125 }
126}