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