ashpd/
file_path.rs

1use std::{
2    ffi::{CString, OsStr},
3    os::unix::ffi::OsStrExt,
4    path::Path,
5};
6
7use serde::{Deserialize, Serialize};
8use zbus::zvariant::Type;
9
10/// A file name represented as a nul-terminated byte array.
11#[derive(Type, Debug, Default, PartialEq)]
12#[zvariant(signature = "ay")]
13pub struct FilePath(CString);
14
15impl AsRef<Path> for FilePath {
16    fn as_ref(&self) -> &Path {
17        OsStr::from_bytes(self.0.as_bytes()).as_ref()
18    }
19}
20
21impl FilePath {
22    pub(crate) fn new<T: AsRef<Path>>(s: T) -> Result<Self, crate::Error> {
23        let c_string = CString::new(s.as_ref().as_os_str().as_bytes())
24            .map_err(|err| crate::Error::NulTerminated(err.nul_position()))?;
25
26        Ok(Self(c_string))
27    }
28}
29
30impl Serialize for FilePath {
31    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32    where
33        S: serde::Serializer,
34    {
35        serializer.serialize_bytes(self.0.as_bytes_with_nul())
36    }
37}
38
39impl<'de> Deserialize<'de> for FilePath {
40    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
41    where
42        D: serde::Deserializer<'de>,
43    {
44        let bytes = <Vec<u8>>::deserialize(deserializer)?;
45        let c_string = CString::from_vec_with_nul(bytes)
46            .map_err(|_| serde::de::Error::custom("Bytes are not nul-terminated"))?;
47
48        Ok(Self(c_string))
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use zbus::zvariant::{
55        serialized::{Context, Data},
56        to_bytes, Endian,
57    };
58
59    use super::*;
60
61    #[test]
62    fn test_serialize_is_nul_terminated() {
63        let bytes = vec![97, 98, 99, 0]; // b"abc\0"
64
65        assert_eq!(b"abc\0", bytes.as_slice());
66
67        let c_string = CString::from_vec_with_nul(bytes.clone()).unwrap();
68
69        assert_eq!(c_string.as_bytes_with_nul(), &bytes);
70
71        let ctxt = Context::new_dbus(Endian::Little, 0);
72        let file_path = FilePath(c_string);
73
74        let file_path_2 = FilePath::new("abc").unwrap();
75
76        let encoded_filename = to_bytes(ctxt, &file_path).unwrap().to_vec();
77        let encoded_filename_2 = to_bytes(ctxt, &file_path_2).unwrap().to_vec();
78        let encoded_bytes = to_bytes(ctxt, &bytes).unwrap().to_vec();
79
80        // It does not matter whether we use new("abc") or deserialize from b"abc\0".
81        assert_eq!(encoded_filename, encoded_bytes);
82        assert_eq!(encoded_filename_2, encoded_bytes);
83
84        let decoded: FilePath = Data::new(encoded_bytes, ctxt).deserialize().unwrap().0;
85        assert_eq!(decoded, file_path);
86        assert_eq!(decoded, file_path_2);
87    }
88}