diff --git a/Cargo.toml b/Cargo.toml index 3af94700f..b89ebbfc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" chrono = "0.4.6" failure = "0.1.5" +failure_derive = "0.1.5" # TODO: make optional rustyline = "4.1.0" lazy_static = "1.3.0" diff --git a/ci_scripts/ci_upload.sh b/ci_scripts/ci_upload.sh index 3cd457a92..f79299573 100755 --- a/ci_scripts/ci_upload.sh +++ b/ci_scripts/ci_upload.sh @@ -2,7 +2,7 @@ if [ -z "$DEVPI_LOGIN" ] ; then echo "required: password for 'dc' user on https://m.devpi/net/dc index" - exit 1 + exit 0 fi set -xe diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 7ed8a0f3d..73779a606 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -1455,6 +1455,106 @@ pub unsafe fn dc_make_rel_and_copy(context: &Context, path: *mut *mut libc::c_ch success } +/// Error type for the [OsStrExt] trait +#[derive(Debug, Fail, PartialEq)] +pub enum CStringError { + /// The string contains an interior null byte + #[fail(display = "String contains an interior null byte")] + InteriorNullByte, + /// The string is not valid Unicode + #[fail(display = "String is not valid unicode")] + NotUnicode, +} + +/// Extra convenience methods on [std::ffi::OsStr] to work with `*libc::c_char`. +/// +/// The primary function of this trait is to more easily convert +/// [OsStr], [OsString] or [Path] into pointers to C strings. This always +/// allocates a new string since it is very common for the source +/// string not to have the required terminal null byte. +/// +/// It is implemented for `AsRef>` trait, which +/// allows any type which implements this trait to transparently use +/// this. This is how the conversion for [Path] works. +/// +/// [OsStr]: std::ffi::OsStr +/// [OsString]: std::ffi::OsString +/// [Path]: std::path::Path +/// +/// # Example +/// +/// ``` +/// use deltachat::dc_tools::{dc_strdup, OsStrExt}; +/// let path = std::path::Path::new("/some/path"); +/// let path_c = path.to_c_string().unwrap(); +/// unsafe { +/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr()); +/// } +/// ``` +pub trait OsStrExt { + /// Convert a [std::ffi::OsStr] to an [std::ffi::CString] + /// + /// This is useful to convert e.g. a [std::path::Path] to + /// [*libc::c_char] by using + /// [Path::as_os_str()](std::path::Path::as_os_str) and + /// [CStr::as_ptr()](std::ffi::CStr::as_ptr). + /// + /// This returns [CString] and not [&CStr] because not all [OsStr] + /// slices end with a null byte, particularly those coming from + /// [Path] do not have a null byte and having to handle this as + /// the caller would defeat the point of this function. + /// + /// On Windows this requires that the [OsStr] contains valid + /// unicode, which should normally be the case for a [Path]. + /// + /// [CString]: std::ffi::CString + /// [CStr]: std::ffi::CStr + /// [OsStr]: std::ffi::OsStr + /// [Path]: std::path::Path + /// + /// # Errors + /// + /// Since a C `*char` is terminated by a NULL byte this conversion + /// will fail, when the [OsStr] has an interior null byte. The + /// function will return + /// `[Err]([CStringError::InteriorNullByte])`. When converting + /// from a [Path] it should be safe to + /// [`.unwrap()`](std::result::Result::unwrap) this anyway since a + /// [Path] should not contain interior null bytes. + /// + /// On windows when the string contains invalid Unicode + /// `[Err]([CStringError::NotUnicode])` is returned. + fn to_c_string(&self) -> Result; +} + +impl> OsStrExt for T { + #[cfg(not(target_os = "windows"))] + fn to_c_string(&self) -> Result { + use std::os::unix::ffi::OsStrExt; + std::ffi::CString::new(self.as_ref().as_bytes()).map_err(|err| match err { + std::ffi::NulError { .. } => CStringError::InteriorNullByte, + }) + } + + #[cfg(target_os = "windows")] + fn to_c_string(&self) -> Result { + os_str_to_c_string_unicode(&self) + } +} + +// Implementation for os_str_to_c_string on windows. +#[allow(dead_code)] +fn os_str_to_c_string_unicode( + os_str: &AsRef, +) -> Result { + match os_str.as_ref().to_str() { + Some(val) => std::ffi::CString::new(val.as_bytes()).map_err(|err| match err { + std::ffi::NulError { .. } => CStringError::InteriorNullByte, + }), + None => Err(CStringError::NotUnicode), + } +} + pub fn to_cstring>(s: S) -> std::ffi::CString { std::ffi::CString::new(s.as_ref()).unwrap() } @@ -1873,4 +1973,73 @@ mod tests { ); } } + + #[test] + fn test_os_str_to_c_string_cwd() { + let some_dir = std::env::current_dir().unwrap(); + some_dir.as_os_str().to_c_string().unwrap(); + } + + #[test] + fn test_os_str_to_c_string_unicode() { + let some_str = String::from("/some/valid/utf8"); + let some_dir = std::path::Path::new(&some_str); + assert_eq!( + some_dir.as_os_str().to_c_string().unwrap(), + std::ffi::CString::new("/some/valid/utf8").unwrap() + ); + } + + #[test] + fn test_os_str_to_c_string_nul() { + let some_str = std::ffi::OsString::from("foo\x00bar"); + assert_eq!( + some_str.to_c_string().err().unwrap(), + CStringError::InteriorNullByte + ) + } + + #[test] + fn test_path_to_c_string_cwd() { + let some_dir = std::env::current_dir().unwrap(); + some_dir.to_c_string().unwrap(); + } + + #[test] + fn test_path_to_c_string_unicode() { + let some_str = String::from("/some/valid/utf8"); + let some_dir = std::path::Path::new(&some_str); + assert_eq!( + some_dir.as_os_str().to_c_string().unwrap(), + std::ffi::CString::new("/some/valid/utf8").unwrap() + ); + } + + #[test] + fn test_os_str_to_c_string_unicode_fn() { + let some_str = std::ffi::OsString::from("foo"); + assert_eq!( + os_str_to_c_string_unicode(&some_str).unwrap(), + std::ffi::CString::new("foo").unwrap() + ); + } + + #[test] + fn test_path_to_c_string_unicode_fn() { + let some_str = String::from("/some/path"); + let some_path = std::path::Path::new(&some_str); + assert_eq!( + os_str_to_c_string_unicode(&some_path).unwrap(), + std::ffi::CString::new("/some/path").unwrap() + ); + } + + #[test] + fn test_os_str_to_c_string_unicode_fn_nul() { + let some_str = std::ffi::OsString::from("fooz\x00bar"); + assert_eq!( + os_str_to_c_string_unicode(&some_str).err().unwrap(), + CStringError::InteriorNullByte + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 419e5e6a9..06e2bd3d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ )] #![feature(c_variadic, ptr_wrapping_offset_from)] +#[macro_use] +extern crate failure_derive; #[macro_use] extern crate num_derive; #[macro_use]