#[cfg(feature = "rust173-polyfill")]
use crate::rust173::IntIsMultipleOf as _;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Xattr {
pub name: String,
pub value: Vec<u8>,
}
impl Xattr {
pub fn value_display(&self) -> String {
if let Ok(s) = std::str::from_utf8(&self.value) {
if !s.is_empty() && s.chars().all(|c| !c.is_control() || c == '\n' || c == '\t') {
return s.to_string();
}
}
format!("0x{}", hex_encode(&self.value))
}
}
pub fn hex_encode(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push(char::from_digit((b >> 4) as u32, 16).unwrap());
s.push(char::from_digit((b & 0xf) as u32, 16).unwrap());
}
s
}
pub fn parse_value(input: &str) -> Result<Vec<u8>, String> {
let trimmed = input.trim();
if let Some(hex) = trimmed
.strip_prefix("0x")
.or_else(|| trimmed.strip_prefix("0X"))
{
let hex: String = hex.chars().filter(|c| !c.is_whitespace()).collect();
if !hex.len().is_multiple_of(2) {
return Err("hex value must have an even number of digits".into());
}
let mut out = Vec::with_capacity(hex.len() / 2);
let bytes = hex.as_bytes();
let mut i = 0;
while i < bytes.len() {
let hi = (bytes[i] as char)
.to_digit(16)
.ok_or_else(|| format!("invalid hex digit {:?}", bytes[i] as char))?;
let lo = (bytes[i + 1] as char)
.to_digit(16)
.ok_or_else(|| format!("invalid hex digit {:?}", bytes[i + 1] as char))?;
out.push(((hi << 4) | lo) as u8);
i += 2;
}
Ok(out)
} else {
Ok(input.as_bytes().to_vec())
}
}
pub const NAMESPACES: &[&str] = &["user.", "trusted.", "security.", "system."];
pub fn has_valid_namespace(name: &str) -> bool {
NAMESPACES.iter().any(|p| name.starts_with(p))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_values_display_as_text_binary_as_hex() {
let t = Xattr {
name: "user.mime_type".into(),
value: b"text/plain".to_vec(),
};
assert_eq!(t.value_display(), "text/plain");
let b = Xattr {
name: "security.capability".into(),
value: vec![1, 0, 0, 2, 0xff],
};
assert_eq!(b.value_display(), "0x01000002ff");
}
#[test]
fn parse_round_trips_hex_and_text() {
assert_eq!(parse_value("0x0100ff").unwrap(), vec![1, 0, 0xff]);
assert_eq!(parse_value("0x01 00 ff").unwrap(), vec![1, 0, 0xff]);
assert_eq!(parse_value("text/plain").unwrap(), b"text/plain".to_vec());
assert!(parse_value("0x123").is_err(), "odd hex length rejected");
assert!(parse_value("0xzz").is_err(), "bad hex digit rejected");
}
#[test]
fn namespace_validation() {
assert!(has_valid_namespace("security.capability"));
assert!(has_valid_namespace("user.foo"));
assert!(!has_valid_namespace("bogus.foo"));
assert!(!has_valid_namespace("capability"));
}
}