1use serde::{Deserialize, Serialize};
3use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
6pub enum PathError {
7 #[error("legacy path contains an ambiguous replacement character")]
8 AmbiguousLegacy,
9 #[error("unsupported path encoding version or platform")]
10 Encoding,
11 #[error("path is empty or contains a native NUL")]
12 Invalid,
13}
14
15#[derive(Serialize, Deserialize)]
16#[serde(tag = "encoding", deny_unknown_fields)]
17enum Native {
18 #[serde(rename = "unix-bytes")]
19 Unix { version: u32, bytes: Vec<u8> },
20 #[serde(rename = "windows-utf16")]
21 Windows { version: u32, units: Vec<u16> },
22}
23#[derive(Deserialize)]
24#[serde(untagged)]
25enum Wire {
26 Legacy(String),
27 Native(Native),
28}
29
30pub fn serialize<S: serde::Serializer>(path: &Path, serializer: S) -> Result<S::Ok, S::Error> {
31 #[cfg(unix)]
32 {
33 use std::os::unix::ffi::OsStrExt;
34 Native::Unix {
35 version: 1,
36 bytes: path.as_os_str().as_bytes().to_vec(),
37 }
38 .serialize(serializer)
39 }
40 #[cfg(windows)]
41 {
42 use std::os::windows::ffi::OsStrExt;
43 Native::Windows {
44 version: 1,
45 units: path.as_os_str().encode_wide().collect(),
46 }
47 .serialize(serializer)
48 }
49 #[cfg(not(any(unix, windows)))]
50 {
51 let _ = path;
52 Err(serde::ser::Error::custom(PathError::Encoding))
53 }
54}
55
56pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<PathBuf, D::Error> {
57 decode(Wire::deserialize(deserializer)?).map_err(serde::de::Error::custom)
58}
59
60fn decode(wire: Wire) -> Result<PathBuf, PathError> {
61 let path = match wire {
62 Wire::Legacy(value) => {
63 if value.contains('\u{fffd}') {
64 return Err(PathError::AmbiguousLegacy);
65 }
66 PathBuf::from(value)
67 }
68 Wire::Native(native) => match native {
69 #[cfg(unix)]
70 Native::Unix { version: 1, bytes } => {
71 use std::os::unix::ffi::OsStringExt;
72 PathBuf::from(std::ffi::OsString::from_vec(bytes))
73 }
74 #[cfg(windows)]
75 Native::Windows { version: 1, units } => {
76 use std::os::windows::ffi::OsStringExt;
77 PathBuf::from(std::ffi::OsString::from_wide(&units))
78 }
79 _ => return Err(PathError::Encoding),
80 },
81 };
82 validate(&path)?;
83 Ok(path)
84}
85
86pub fn validate(path: &Path) -> Result<(), PathError> {
87 #[cfg(unix)]
88 let nul = {
89 use std::os::unix::ffi::OsStrExt;
90 path.as_os_str().as_bytes().contains(&0)
91 };
92 #[cfg(windows)]
93 let nul = {
94 use std::os::windows::ffi::OsStrExt;
95 path.as_os_str().encode_wide().any(|unit| unit == 0)
96 };
97 #[cfg(not(any(unix, windows)))]
98 let nul = true;
99 if path.as_os_str().is_empty() || nul {
100 return Err(PathError::Invalid);
101 }
102 Ok(())
103}
104
105pub mod option {
106 use super::*;
107 struct PathRef<'a>(&'a Path);
108 impl Serialize for PathRef<'_> {
109 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
110 super::serialize(self.0, serializer)
111 }
112 }
113 pub fn serialize<S: serde::Serializer>(
114 path: &Option<PathBuf>,
115 serializer: S,
116 ) -> Result<S::Ok, S::Error> {
117 path.as_deref().map(PathRef).serialize(serializer)
118 }
119 pub fn deserialize<'de, D: serde::Deserializer<'de>>(
120 deserializer: D,
121 ) -> Result<Option<PathBuf>, D::Error> {
122 Option::<Wire>::deserialize(deserializer)?
123 .map(decode)
124 .transpose()
125 .map_err(serde::de::Error::custom)
126 }
127}