xattr.rs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. //! Extended attribute support for `NetBSD`, `Darwin`, and `Linux` systems.
  2. #![allow(trivial_casts)] // for ARM
  3. use std::fmt::{Display, Formatter};
  4. use std::io;
  5. use std::path::Path;
  6. use std::str;
  7. pub const ENABLED: bool = cfg!(any(
  8. target_os = "macos",
  9. target_os = "linux",
  10. target_os = "netbsd",
  11. target_os = "freebsd"
  12. ));
  13. #[derive(Debug)]
  14. pub struct Attribute {
  15. pub name: String,
  16. pub value: Option<Vec<u8>>,
  17. }
  18. pub trait FileAttributes {
  19. fn attributes(&self) -> io::Result<Vec<Attribute>>;
  20. fn symlink_attributes(&self) -> io::Result<Vec<Attribute>>;
  21. }
  22. #[cfg(any(
  23. target_os = "macos",
  24. target_os = "linux",
  25. target_os = "netbsd",
  26. target_os = "freebsd"
  27. ))]
  28. impl FileAttributes for Path {
  29. fn attributes(&self) -> io::Result<Vec<Attribute>> {
  30. extended_attrs::attributes(self, true)
  31. }
  32. fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
  33. extended_attrs::attributes(self, false)
  34. }
  35. }
  36. #[cfg(not(any(
  37. target_os = "macos",
  38. target_os = "linux",
  39. target_os = "netbsd",
  40. target_os = "freebsd"
  41. )))]
  42. impl FileAttributes for Path {
  43. fn attributes(&self) -> io::Result<Vec<Attribute>> {
  44. Ok(Vec::new())
  45. }
  46. fn symlink_attributes(&self) -> io::Result<Vec<Attribute>> {
  47. Ok(Vec::new())
  48. }
  49. }
  50. #[cfg(any(
  51. target_os = "macos",
  52. target_os = "linux",
  53. target_os = "netbsd",
  54. target_os = "freebsd"
  55. ))]
  56. mod extended_attrs {
  57. use super::Attribute;
  58. use libc::{c_char, c_void, size_t, ssize_t, ERANGE};
  59. use std::ffi::{CStr, CString, OsStr, OsString};
  60. use std::io;
  61. use std::os::unix::ffi::OsStrExt;
  62. use std::path::Path;
  63. use std::ptr::null_mut;
  64. #[cfg(target_os = "macos")]
  65. mod os {
  66. use libc::{
  67. c_char, c_int, c_void, getxattr, listxattr, size_t, ssize_t, XATTR_NOFOLLOW,
  68. XATTR_SHOWCOMPRESSION,
  69. };
  70. // Options to use for MacOS versions of getxattr and listxattr
  71. fn get_options(follow_symlinks: bool) -> c_int {
  72. if follow_symlinks {
  73. XATTR_SHOWCOMPRESSION
  74. } else {
  75. XATTR_NOFOLLOW | XATTR_SHOWCOMPRESSION
  76. }
  77. }
  78. // Wrapper around listxattr that handles symbolic links
  79. pub(super) fn list_xattr(
  80. follow_symlinks: bool,
  81. path: *const c_char,
  82. namebuf: *mut c_char,
  83. size: size_t,
  84. ) -> ssize_t {
  85. // SAFETY: Calling C function
  86. unsafe { listxattr(path, namebuf, size, get_options(follow_symlinks)) }
  87. }
  88. // Wrapper around getxattr that handles symbolic links
  89. pub(super) fn get_xattr(
  90. follow_symlinks: bool,
  91. path: *const c_char,
  92. name: *const c_char,
  93. value: *mut c_void,
  94. size: size_t,
  95. ) -> ssize_t {
  96. // SAFETY: Calling C function
  97. unsafe { getxattr(path, name, value, size, 0, get_options(follow_symlinks)) }
  98. }
  99. }
  100. #[cfg(target_os = "linux")]
  101. mod os {
  102. use libc::{c_char, c_void, size_t, ssize_t};
  103. use libc::{getxattr, lgetxattr, listxattr, llistxattr};
  104. // Wrapper around listxattr and llistattr for handling symbolic links
  105. pub(super) fn list_xattr(
  106. follow_symlinks: bool,
  107. path: *const c_char,
  108. namebuf: *mut c_char,
  109. size: size_t,
  110. ) -> ssize_t {
  111. if follow_symlinks {
  112. // SAFETY: Calling C function
  113. unsafe { listxattr(path, namebuf, size) }
  114. } else {
  115. // SAFETY: Calling C function
  116. unsafe { llistxattr(path, namebuf, size) }
  117. }
  118. }
  119. // Wrapper around getxattr and lgetxattr for handling symbolic links
  120. pub(super) fn get_xattr(
  121. follow_symlinks: bool,
  122. path: *const c_char,
  123. name: *const c_char,
  124. value: *mut c_void,
  125. size: size_t,
  126. ) -> ssize_t {
  127. if follow_symlinks {
  128. // SAFETY: Calling C function
  129. unsafe { getxattr(path, name, value, size) }
  130. } else {
  131. // SAFETY: Calling C function
  132. unsafe { lgetxattr(path, name, value, size) }
  133. }
  134. }
  135. }
  136. #[cfg(any(target_os = "netbsd", target_os = "freebsd"))]
  137. mod os {
  138. use libc::{
  139. c_char, c_int, c_void, extattr_get_file, extattr_get_link, extattr_list_file,
  140. extattr_list_link, size_t, ssize_t, EXTATTR_NAMESPACE_SYSTEM, EXTATTR_NAMESPACE_USER,
  141. };
  142. // Wrapper around listxattr that handles symbolic links
  143. fn list_xattr(
  144. follow_symlinks: bool,
  145. path: *const c_char,
  146. namespace: c_int,
  147. value: *mut c_void,
  148. size: size_t,
  149. ) -> ssize_t {
  150. if follow_symlinks {
  151. // SAFETY: Calling C function
  152. unsafe { extattr_list_file(path, namespace, value, size) }
  153. } else {
  154. // SAFETY: Calling C function
  155. unsafe { extattr_list_link(path, namespace, value, size) }
  156. }
  157. }
  158. fn get_xattr(
  159. follow_symlinks: bool,
  160. path: *const c_char,
  161. namespace: c_int,
  162. name: *const c_char,
  163. value: *mut c_void,
  164. size: size_t,
  165. ) -> ssize_t {
  166. if follow_symlinks {
  167. // SAFETY: Calling C function
  168. unsafe { extattr_get_file(path, namespace, name, value, size) }
  169. } else {
  170. // SAFETY: Calling C function
  171. unsafe { extattr_get_link(path, namespace, name, value, size) }
  172. }
  173. }
  174. pub(super) fn list_system_xattr(
  175. follow_symlinks: bool,
  176. path: *const c_char,
  177. namebuf: *mut c_char,
  178. size: size_t,
  179. ) -> ssize_t {
  180. list_xattr(
  181. follow_symlinks,
  182. path,
  183. EXTATTR_NAMESPACE_SYSTEM,
  184. namebuf.cast(),
  185. size,
  186. )
  187. }
  188. pub(super) fn list_user_xattr(
  189. follow_symlinks: bool,
  190. path: *const c_char,
  191. namebuf: *mut c_char,
  192. size: size_t,
  193. ) -> ssize_t {
  194. list_xattr(
  195. follow_symlinks,
  196. path,
  197. EXTATTR_NAMESPACE_USER,
  198. namebuf.cast(),
  199. size,
  200. )
  201. }
  202. pub(super) fn get_system_xattr(
  203. follow_symlinks: bool,
  204. path: *const c_char,
  205. name: *const c_char,
  206. value: *mut c_void,
  207. size: size_t,
  208. ) -> ssize_t {
  209. get_xattr(
  210. follow_symlinks,
  211. path,
  212. EXTATTR_NAMESPACE_SYSTEM,
  213. name,
  214. value.cast(),
  215. size,
  216. )
  217. }
  218. pub(super) fn get_user_xattr(
  219. follow_symlinks: bool,
  220. path: *const c_char,
  221. name: *const c_char,
  222. value: *mut c_void,
  223. size: size_t,
  224. ) -> ssize_t {
  225. get_xattr(
  226. follow_symlinks,
  227. path,
  228. EXTATTR_NAMESPACE_USER,
  229. name,
  230. value.cast(),
  231. size,
  232. )
  233. }
  234. }
  235. // Split attribute name list. Each attribute name is null terminated in the
  236. // list.
  237. #[cfg(any(target_os = "macos", target_os = "linux"))]
  238. fn split_attribute_list(buffer: &[u8]) -> Vec<OsString> {
  239. buffer[..buffer.len() - 1] // Skip trailing null
  240. .split(|&c| c == 0)
  241. .filter(|&s| !s.is_empty())
  242. .map(OsStr::from_bytes)
  243. .map(std::borrow::ToOwned::to_owned)
  244. .collect()
  245. }
  246. // Split attribute name list. Each attribute is a one byte name length
  247. // followed by the name.
  248. #[cfg(any(target_os = "netbsd", target_os = "freebsd"))]
  249. fn split_attribute_list(buffer: &[u8]) -> Vec<OsString> {
  250. let mut result = Vec::new();
  251. let mut index = 0;
  252. let length = buffer.len();
  253. while index < length {
  254. let item_length = buffer[index] as usize;
  255. let start = index + 1;
  256. let end = start + item_length;
  257. if end <= length {
  258. result.push(OsStr::from_bytes(&buffer[start..end]).to_owned());
  259. }
  260. index = end;
  261. }
  262. result
  263. }
  264. // Calling getxattr and listxattr is a two part process. The first call
  265. // a null ptr for buffer and a zero buffer size is passed and the function
  266. // returns the needed buffer size. The second call the buffer ptr and the
  267. // buffer size is passed and the buffer is filled. Care must be taken if
  268. // the buffer size changes between the first and second call.
  269. fn get_loop<F: Fn(*mut u8, usize) -> ssize_t>(f: F) -> io::Result<Option<Vec<u8>>> {
  270. let mut buffer: Vec<u8> = Vec::new();
  271. loop {
  272. let buffer_size = match f(null_mut(), 0) {
  273. -1 => return Err(io::Error::last_os_error()),
  274. 0 => return Ok(None),
  275. size => size as size_t,
  276. };
  277. buffer.resize(buffer_size, 0);
  278. return match f(buffer.as_mut_ptr(), buffer_size) {
  279. -1 => {
  280. let last_os_error = io::Error::last_os_error();
  281. if last_os_error.raw_os_error() == Some(ERANGE) {
  282. // Passed buffer was to small so retry again.
  283. continue;
  284. }
  285. Err(last_os_error)
  286. }
  287. 0 => Ok(None),
  288. len => {
  289. // Just in case the size shrunk
  290. buffer.truncate(len as usize);
  291. Ok(Some(buffer))
  292. }
  293. };
  294. }
  295. }
  296. // Get a list of all attribute names on `path`
  297. fn list_attributes(
  298. path: &CStr,
  299. follow_symlinks: bool,
  300. lister: fn(
  301. follow_symlinks: bool,
  302. path: *const c_char,
  303. namebuf: *mut c_char,
  304. size: size_t,
  305. ) -> ssize_t,
  306. ) -> io::Result<Vec<OsString>> {
  307. Ok(
  308. get_loop(|buf, size| lister(follow_symlinks, path.as_ptr(), buf.cast(), size))?
  309. .map_or_else(Vec::new, |buffer| split_attribute_list(&buffer)),
  310. )
  311. }
  312. // Get the attribute value `name` on `path`
  313. #[cfg(any(target_os = "macos", target_os = "linux"))]
  314. fn get_attribute(
  315. path: &CStr,
  316. name: &CStr,
  317. follow_symlinks: bool,
  318. getter: fn(
  319. follow_symlinks: bool,
  320. path: *const c_char,
  321. name: *const c_char,
  322. value: *mut c_void,
  323. size: size_t,
  324. ) -> ssize_t,
  325. ) -> io::Result<Option<Vec<u8>>> {
  326. use libc::ENODATA;
  327. get_loop(|buf, size| {
  328. getter(
  329. follow_symlinks,
  330. path.as_ptr(),
  331. name.as_ptr(),
  332. buf.cast(),
  333. size,
  334. )
  335. })
  336. .or_else(|err| {
  337. if err.raw_os_error() == Some(ENODATA) {
  338. // This handles the case when the named attribute is not on the
  339. // path. This is for mainly handling the special case for the
  340. // security.selinux attribute mentioned below. This can
  341. // also happen when an attribute is deleted between listing
  342. // the attributes and getting its value.
  343. Ok(None)
  344. } else {
  345. Err(err)
  346. }
  347. })
  348. }
  349. #[cfg(any(target_os = "netbsd", target_os = "freebsd"))]
  350. fn get_attribute(
  351. path: &CStr,
  352. name: &CStr,
  353. follow_symlinks: bool,
  354. getter: fn(
  355. follow_symlinks: bool,
  356. path: *const c_char,
  357. name: *const c_char,
  358. value: *mut c_void,
  359. size: size_t,
  360. ) -> ssize_t,
  361. ) -> io::Result<Option<Vec<u8>>> {
  362. get_loop(|buf, size| {
  363. getter(
  364. follow_symlinks,
  365. path.as_ptr(),
  366. name.as_ptr(),
  367. buf.cast(),
  368. size,
  369. )
  370. })
  371. }
  372. // Specially handle security.linux for filesystem that do not list attributes.
  373. #[cfg(target_os = "linux")]
  374. fn get_selinux_attribute(path: &CStr, follow_symlinks: bool) -> io::Result<Vec<Attribute>> {
  375. const SELINUX_XATTR_NAME: &str = "security.selinux";
  376. let name = CString::new(SELINUX_XATTR_NAME).unwrap();
  377. get_attribute(path, &name, follow_symlinks, os::get_xattr).map(|value| {
  378. if value.is_some() {
  379. vec![Attribute {
  380. name: String::from(SELINUX_XATTR_NAME),
  381. value,
  382. }]
  383. } else {
  384. Vec::new()
  385. }
  386. })
  387. }
  388. // Get a vector of all attribute names and values on `path`
  389. #[cfg(any(target_os = "macos", target_os = "linux"))]
  390. pub fn attributes(path: &Path, follow_symlinks: bool) -> io::Result<Vec<Attribute>> {
  391. let path = CString::new(path.as_os_str().as_bytes())
  392. .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
  393. let attr_names = list_attributes(&path, follow_symlinks, os::list_xattr)?;
  394. #[cfg(target_os = "linux")]
  395. if attr_names.is_empty() {
  396. // Some filesystems, like sysfs, return nothing on listxattr, even though the security
  397. // attribute is set.
  398. return get_selinux_attribute(&path, follow_symlinks);
  399. }
  400. let mut attrs = Vec::with_capacity(attr_names.len());
  401. for attr_name in attr_names {
  402. if let Some(name) = attr_name.to_str() {
  403. let attr_name =
  404. CString::new(name).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
  405. let value = get_attribute(&path, &attr_name, follow_symlinks, os::get_xattr)?;
  406. attrs.push(Attribute {
  407. name: name.to_string(),
  408. value,
  409. });
  410. }
  411. }
  412. Ok(attrs)
  413. }
  414. #[cfg(any(target_os = "netbsd", target_os = "freebsd"))]
  415. fn get_namespace_attributes(
  416. path: &CStr,
  417. follow_symlinks: bool,
  418. attr_names: Vec<OsString>,
  419. namespace: &str,
  420. getter: fn(
  421. follow_symlinks: bool,
  422. path: *const c_char,
  423. name: *const c_char,
  424. value: *mut c_void,
  425. size: size_t,
  426. ) -> ssize_t,
  427. attrs: &mut Vec<Attribute>,
  428. ) -> io::Result<()> {
  429. for attr_name in attr_names {
  430. if let Some(name) = attr_name.to_str() {
  431. let attr_name =
  432. CString::new(name).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
  433. let value = get_attribute(path, &attr_name, follow_symlinks, getter)?;
  434. attrs.push(Attribute {
  435. name: format!("{namespace}::{name}"),
  436. value,
  437. });
  438. }
  439. }
  440. Ok(())
  441. }
  442. #[cfg(any(target_os = "netbsd", target_os = "freebsd"))]
  443. pub fn attributes(path: &Path, follow_symlinks: bool) -> io::Result<Vec<Attribute>> {
  444. use libc::EPERM;
  445. let path = CString::new(path.as_os_str().as_bytes())
  446. .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
  447. let attr_names_system = list_attributes(&path, follow_symlinks, os::list_system_xattr)
  448. .or_else(|err| {
  449. // Reading of attributes in the system namespace is only supported for root
  450. if err.raw_os_error() == Some(EPERM) {
  451. Ok(Vec::new())
  452. } else {
  453. Err(err)
  454. }
  455. })?;
  456. let attr_names_user = list_attributes(&path, follow_symlinks, os::list_user_xattr)?;
  457. let mut attrs = Vec::with_capacity(attr_names_system.len() + attr_names_user.len());
  458. get_namespace_attributes(
  459. &path,
  460. follow_symlinks,
  461. attr_names_system,
  462. "system",
  463. os::get_system_xattr,
  464. &mut attrs,
  465. )?;
  466. get_namespace_attributes(
  467. &path,
  468. follow_symlinks,
  469. attr_names_user,
  470. "user",
  471. os::get_user_xattr,
  472. &mut attrs,
  473. )?;
  474. Ok(attrs)
  475. }
  476. }
  477. const ATTRIBUTE_VALUE_MAX_HEX_LENGTH: usize = 16;
  478. // Display for an attribute. Attribute values that have a custom display are
  479. // enclosed in curley brackets.
  480. impl Display for Attribute {
  481. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  482. f.write_fmt(format_args!("{}: ", self.name))?;
  483. if let Some(value) = custom_attr_display(self) {
  484. f.write_fmt(format_args!("<{value}>"))
  485. } else {
  486. match &self.value {
  487. None => f.write_str("<empty>"),
  488. Some(value) => {
  489. if let Some(val) = custom_value_display(value) {
  490. f.write_fmt(format_args!("<{val}>"))
  491. } else if let Ok(v) = str::from_utf8(value) {
  492. f.write_fmt(format_args!("{:?}", v.trim_end_matches(char::from(0))))
  493. } else if value.len() <= ATTRIBUTE_VALUE_MAX_HEX_LENGTH {
  494. f.write_fmt(format_args!("{value:02x?}"))
  495. } else {
  496. f.write_fmt(format_args!("<length {}>", value.len()))
  497. }
  498. }
  499. }
  500. }
  501. }
  502. }
  503. struct AttributeDisplay {
  504. pub attribute: &'static str,
  505. pub display: fn(&Attribute) -> Option<String>,
  506. }
  507. // Check for a custom display by attribute name and call the display function
  508. fn custom_attr_display(attribute: &Attribute) -> Option<String> {
  509. let name = attribute.name.as_str();
  510. // Strip off MacOS Metadata Persistence Flags
  511. // See https://eclecticlight.co/2020/11/02/controlling-metadata-tricks-with-persistence/
  512. #[cfg(target_os = "macos")]
  513. let name = name.rsplit_once('#').map_or(name, |n| n.0);
  514. ATTRIBUTE_DISPLAYS
  515. .iter()
  516. .find(|c| c.attribute == name)
  517. .and_then(|c| (c.display)(attribute))
  518. }
  519. #[cfg(target_os = "macos")]
  520. const ATTRIBUTE_DISPLAYS: &[AttributeDisplay] = &[
  521. AttributeDisplay {
  522. attribute: "com.apple.lastuseddate",
  523. display: display_lastuseddate,
  524. },
  525. AttributeDisplay {
  526. attribute: "com.apple.macl",
  527. display: display_macl,
  528. },
  529. ];
  530. #[cfg(not(target_os = "macos"))]
  531. const ATTRIBUTE_DISPLAYS: &[AttributeDisplay] = &[];
  532. // com.apple.lastuseddate is two 64-bit values representing the seconds and nano seconds
  533. // from January 1, 1970
  534. #[cfg(target_os = "macos")]
  535. fn display_lastuseddate(attribute: &Attribute) -> Option<String> {
  536. use chrono::{Local, SecondsFormat, TimeZone};
  537. attribute
  538. .value
  539. .as_ref()
  540. .filter(|value| value.len() == 16)
  541. .and_then(|value| {
  542. let sec = i64::from_le_bytes(value[0..8].try_into().unwrap());
  543. let n_sec = i64::from_le_bytes(value[8..].try_into().unwrap());
  544. Local
  545. .timestamp_opt(sec, n_sec as u32)
  546. .map(|dt| dt.to_rfc3339_opts(SecondsFormat::Nanos, true))
  547. .single()
  548. })
  549. }
  550. // com.apple.macl is a two byte flag followed by a uuid for the application
  551. #[cfg(target_os = "macos")]
  552. fn format_macl(value: &[u8]) -> String {
  553. const HEX: [u8; 16] = [
  554. b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e',
  555. b'f',
  556. ];
  557. const GROUPS: [(usize, usize, u8); 6] = [
  558. (0, 4, b';'),
  559. (5, 13, b'-'),
  560. (14, 18, b'-'),
  561. (19, 23, b'-'),
  562. (24, 28, b'-'),
  563. (29, 41, 0),
  564. ];
  565. let mut dst = [0; 41];
  566. let mut i = 0;
  567. for (start, end, sep) in GROUPS {
  568. for j in (start..end).step_by(2) {
  569. let x = value[i];
  570. i += 1;
  571. dst[j] = HEX[(x >> 4) as usize];
  572. dst[j + 1] = HEX[(x & 0x0f) as usize];
  573. }
  574. if sep != 0 {
  575. dst[end] = sep;
  576. }
  577. }
  578. // SAFETY: Vector generated above with only ASCII characters.
  579. unsafe { String::from_utf8_unchecked(dst.to_vec()) }
  580. }
  581. // See https://book.hacktricks.xyz/macos-hardening/macos-security-and-privilege-escalation/macos-security-protections/macos-tcc
  582. #[cfg(target_os = "macos")]
  583. fn display_macl(attribute: &Attribute) -> Option<String> {
  584. attribute
  585. .value
  586. .as_ref()
  587. .filter(|v| v.len() % 18 == 0)
  588. .map(|v| {
  589. let macls = v
  590. .as_slice()
  591. .chunks(18)
  592. .filter(|c| c[0] != 0 || c[1] != 0)
  593. .map(format_macl)
  594. .collect::<Vec<String>>()
  595. .join(", ");
  596. format!("[{macls}]")
  597. })
  598. }
  599. // plist::XmlWriter takes the writer instead of borrowing it. This is a
  600. // wrapper around a borrowed vector that just forwards the Write trait
  601. // calls to the borrowed vector.
  602. struct BorrowedWriter<'a> {
  603. pub buffer: &'a mut Vec<u8>,
  604. }
  605. impl<'a> io::Write for BorrowedWriter<'a> {
  606. fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
  607. self.buffer.write(buf)
  608. }
  609. fn flush(&mut self) -> io::Result<()> {
  610. self.buffer.flush()
  611. }
  612. fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
  613. self.buffer.write_all(buf)
  614. }
  615. }
  616. fn custom_value_display(value: &[u8]) -> Option<String> {
  617. if value.starts_with(b"bplist") {
  618. plist_value_display(value)
  619. } else {
  620. None
  621. }
  622. }
  623. // Convert a binary plist to a XML plist.
  624. fn plist_value_display(value: &[u8]) -> Option<String> {
  625. let reader = io::Cursor::new(value);
  626. plist::Value::from_reader(reader).ok().and_then(|v| {
  627. let mut buffer = Vec::new();
  628. v.to_writer_xml_with_options(
  629. BorrowedWriter {
  630. buffer: &mut buffer,
  631. },
  632. &plist::XmlWriteOptions::default()
  633. .indent(b' ', 0)
  634. .root_element(false),
  635. )
  636. .ok()
  637. .and_then(|()| str::from_utf8(&buffer).ok())
  638. .map(|s| format!("<plist version=\"1.0\">{}</plist>", s.replace('\n', "")))
  639. })
  640. }