1use libc::{c_char, c_int, size_t};
2use std::cmp::Ordering;
3use std::ffi::{CString, OsStr, OsString};
4use std::path::{Component, Path, PathBuf};
5
6use crate::{raw, Error};
7
8#[doc(hidden)]
9pub trait IsNull {
10 fn is_ptr_null(&self) -> bool;
11}
12impl<T> IsNull for *const T {
13 fn is_ptr_null(&self) -> bool {
14 self.is_null()
15 }
16}
17impl<T> IsNull for *mut T {
18 fn is_ptr_null(&self) -> bool {
19 self.is_null()
20 }
21}
22
23#[doc(hidden)]
24pub trait Binding: Sized {
25 type Raw;
26
27 unsafe fn from_raw(raw: Self::Raw) -> Self;
28 fn raw(&self) -> Self::Raw;
29
30 unsafe fn from_raw_opt<T>(raw: T) -> Option<Self>
31 where
32 T: Copy + IsNull,
33 Self: Binding<Raw = T>,
34 {
35 if raw.is_ptr_null() {
36 None
37 } else {
38 Some(Binding::from_raw(raw))
39 }
40 }
41}
42
43pub fn iter2cstrs_paths<T, I>(
49 iter: I,
50) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error>
51where
52 T: IntoCString,
53 I: IntoIterator<Item = T>,
54{
55 let cstrs = iter
56 .into_iter()
57 .map(|i| fixup_windows_path(i.into_c_string()?))
58 .collect::<Result<Vec<CString>, _>>()?;
59 iter2cstrs(cstrs)
60}
61
62pub fn iter2cstrs<T, I>(
67 iter: I,
68) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error>
69where
70 T: IntoCString,
71 I: IntoIterator<Item = T>,
72{
73 let cstrs = iter
74 .into_iter()
75 .map(|i| i.into_c_string())
76 .collect::<Result<Vec<CString>, _>>()?;
77 let ptrs = cstrs.iter().map(|i| i.as_ptr()).collect::<Vec<_>>();
78 let raw = raw::git_strarray {
79 strings: ptrs.as_ptr() as *mut _,
80 count: ptrs.len() as size_t,
81 };
82 Ok((cstrs, ptrs, raw))
83}
84
85#[cfg(unix)]
86pub fn bytes2path(b: &[u8]) -> &Path {
87 use std::os::unix::prelude::*;
88 Path::new(OsStr::from_bytes(b))
89}
90#[cfg(windows)]
91pub fn bytes2path(b: &[u8]) -> &Path {
92 use std::str;
93 Path::new(str::from_utf8(b).unwrap())
94}
95
96pub trait IntoCString {
101 fn into_c_string(self) -> Result<CString, Error>;
103}
104
105impl<'a, T: IntoCString + Clone> IntoCString for &'a T {
106 fn into_c_string(self) -> Result<CString, Error> {
107 self.clone().into_c_string()
108 }
109}
110
111impl<'a> IntoCString for &'a str {
112 fn into_c_string(self) -> Result<CString, Error> {
113 Ok(CString::new(self)?)
114 }
115}
116
117impl IntoCString for String {
118 fn into_c_string(self) -> Result<CString, Error> {
119 Ok(CString::new(self.into_bytes())?)
120 }
121}
122
123impl IntoCString for CString {
124 fn into_c_string(self) -> Result<CString, Error> {
125 Ok(self)
126 }
127}
128
129impl<'a> IntoCString for &'a Path {
130 fn into_c_string(self) -> Result<CString, Error> {
131 let s: &OsStr = self.as_ref();
132 s.into_c_string()
133 }
134}
135
136impl IntoCString for PathBuf {
137 fn into_c_string(self) -> Result<CString, Error> {
138 let s: OsString = self.into();
139 s.into_c_string()
140 }
141}
142
143impl<'a> IntoCString for &'a OsStr {
144 fn into_c_string(self) -> Result<CString, Error> {
145 self.to_os_string().into_c_string()
146 }
147}
148
149impl IntoCString for OsString {
150 #[cfg(unix)]
151 fn into_c_string(self) -> Result<CString, Error> {
152 use std::os::unix::prelude::*;
153 let s: &OsStr = self.as_ref();
154 Ok(CString::new(s.as_bytes())?)
155 }
156 #[cfg(windows)]
157 fn into_c_string(self) -> Result<CString, Error> {
158 match self.to_str() {
159 Some(s) => s.into_c_string(),
160 None => Err(Error::from_str(
161 "only valid unicode paths are accepted on windows",
162 )),
163 }
164 }
165}
166
167impl<'a> IntoCString for &'a [u8] {
168 fn into_c_string(self) -> Result<CString, Error> {
169 Ok(CString::new(self)?)
170 }
171}
172
173impl IntoCString for Vec<u8> {
174 fn into_c_string(self) -> Result<CString, Error> {
175 Ok(CString::new(self)?)
176 }
177}
178
179pub fn into_opt_c_string<S>(opt_s: Option<S>) -> Result<Option<CString>, Error>
180where
181 S: IntoCString,
182{
183 match opt_s {
184 None => Ok(None),
185 Some(s) => Ok(Some(s.into_c_string()?)),
186 }
187}
188
189pub fn c_cmp_to_ordering(cmp: c_int) -> Ordering {
190 match cmp {
191 0 => Ordering::Equal,
192 n if n < 0 => Ordering::Less,
193 _ => Ordering::Greater,
194 }
195}
196
197pub fn path_to_repo_path(path: &Path) -> Result<CString, Error> {
204 macro_rules! err {
205 ($msg:literal, $path:expr) => {
206 return Err(Error::from_str(&format!($msg, $path.display())))
207 };
208 }
209 match path.components().next() {
210 None => return Err(Error::from_str("repo path should not be empty")),
211 Some(Component::Prefix(_)) => err!(
212 "repo path `{}` should be relative, not a windows prefix",
213 path
214 ),
215 Some(Component::RootDir) => err!("repo path `{}` should be relative", path),
216 Some(Component::CurDir) => err!("repo path `{}` should not start with `.`", path),
217 Some(Component::ParentDir) => err!("repo path `{}` should not start with `..`", path),
218 Some(Component::Normal(_)) => {}
219 }
220 #[cfg(windows)]
221 {
222 match path.to_str() {
223 None => {
224 return Err(Error::from_str(
225 "only valid unicode paths are accepted on windows",
226 ))
227 }
228 Some(s) => return fixup_windows_path(s),
229 }
230 }
231 #[cfg(not(windows))]
232 {
233 path.into_c_string()
234 }
235}
236
237pub fn cstring_to_repo_path<T: IntoCString>(path: T) -> Result<CString, Error> {
238 fixup_windows_path(path.into_c_string()?)
239}
240
241#[cfg(windows)]
242fn fixup_windows_path<P: Into<Vec<u8>>>(path: P) -> Result<CString, Error> {
243 let mut bytes: Vec<u8> = path.into();
244 for i in 0..bytes.len() {
245 if bytes[i] == b'\\' {
246 bytes[i] = b'/';
247 }
248 }
249 Ok(CString::new(bytes)?)
250}
251
252#[cfg(not(windows))]
253fn fixup_windows_path(path: CString) -> Result<CString, Error> {
254 Ok(path)
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 macro_rules! assert_err {
262 ($path:expr, $msg:expr) => {
263 match path_to_repo_path(Path::new($path)) {
264 Ok(_) => panic!("expected `{}` to err", $path),
265 Err(e) => assert_eq!(e.message(), $msg),
266 }
267 };
268 }
269
270 macro_rules! assert_repo_path_ok {
271 ($path:expr) => {
272 assert_repo_path_ok!($path, $path)
273 };
274 ($path:expr, $expect:expr) => {
275 assert_eq!(
276 path_to_repo_path(Path::new($path)),
277 Ok(CString::new($expect).unwrap())
278 );
279 };
280 }
281
282 #[test]
283 #[cfg(windows)]
284 fn path_to_repo_path_translate() {
285 assert_repo_path_ok!("foo");
286 assert_repo_path_ok!("foo/bar");
287 assert_repo_path_ok!(r"foo\bar", "foo/bar");
288 assert_repo_path_ok!(r"foo\bar\", "foo/bar/");
289 }
290
291 #[test]
292 fn path_to_repo_path_no_weird() {
293 assert_err!("", "repo path should not be empty");
294 assert_err!("./foo", "repo path `./foo` should not start with `.`");
295 assert_err!("../foo", "repo path `../foo` should not start with `..`");
296 }
297
298 #[test]
299 #[cfg(not(windows))]
300 fn path_to_repo_path_no_absolute() {
301 assert_err!("/", "repo path `/` should be relative");
302 assert_repo_path_ok!("foo/bar");
303 }
304
305 #[test]
306 #[cfg(windows)]
307 fn path_to_repo_path_no_absolute() {
308 assert_err!(
309 r"c:",
310 r"repo path `c:` should be relative, not a windows prefix"
311 );
312 assert_err!(
313 r"c:\",
314 r"repo path `c:\` should be relative, not a windows prefix"
315 );
316 assert_err!(
317 r"c:temp",
318 r"repo path `c:temp` should be relative, not a windows prefix"
319 );
320 assert_err!(
321 r"\\?\UNC\a\b\c",
322 r"repo path `\\?\UNC\a\b\c` should be relative, not a windows prefix"
323 );
324 assert_err!(
325 r"\\?\c:\foo",
326 r"repo path `\\?\c:\foo` should be relative, not a windows prefix"
327 );
328 assert_err!(
329 r"\\.\COM42",
330 r"repo path `\\.\COM42` should be relative, not a windows prefix"
331 );
332 assert_err!(
333 r"\\a\b",
334 r"repo path `\\a\b` should be relative, not a windows prefix"
335 );
336 assert_err!(r"\", r"repo path `\` should be relative");
337 assert_err!(r"/", r"repo path `/` should be relative");
338 assert_err!(r"\foo", r"repo path `\foo` should be relative");
339 assert_err!(r"/foo", r"repo path `/foo` should be relative");
340 }
341}