diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2017-01-25 23:10:45 +0100 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2017-01-31 13:47:23 +0100 |
| commit | c7cdc75ee6e4232967256973f0192b6105f2ffc8 (patch) | |
| tree | 4f216b60b0270550b01da1e3c0667ba178ef5fa2 /talpid_openvpn_plugin/src | |
| parent | db300b94dc777333345ad68dfc2b823d625f3a4b (diff) | |
| download | mullvadvpn-c7cdc75ee6e4232967256973f0192b6105f2ffc8.tar.xz mullvadvpn-c7cdc75ee6e4232967256973f0192b6105f2ffc8.zip | |
Add parse module with array and env functions
Diffstat (limited to 'talpid_openvpn_plugin/src')
| -rw-r--r-- | talpid_openvpn_plugin/src/ffi/mod.rs | 2 | ||||
| -rw-r--r-- | talpid_openvpn_plugin/src/ffi/parse.rs | 130 | ||||
| -rw-r--r-- | talpid_openvpn_plugin/src/lib.rs | 3 |
3 files changed, 135 insertions, 0 deletions
diff --git a/talpid_openvpn_plugin/src/ffi/mod.rs b/talpid_openvpn_plugin/src/ffi/mod.rs index 019decf157..cf3b7fb42c 100644 --- a/talpid_openvpn_plugin/src/ffi/mod.rs +++ b/talpid_openvpn_plugin/src/ffi/mod.rs @@ -9,6 +9,8 @@ use std::os::raw::{c_char, c_int, c_uint, c_void}; mod consts; use self::consts::*; +mod parse; + /// Struct sent to `openvpn_plugin_open_v3` containing input values. #[repr(C)] diff --git a/talpid_openvpn_plugin/src/ffi/parse.rs b/talpid_openvpn_plugin/src/ffi/parse.rs new file mode 100644 index 0000000000..72b46316f0 --- /dev/null +++ b/talpid_openvpn_plugin/src/ffi/parse.rs @@ -0,0 +1,130 @@ +use std::collections::HashMap; +use std::ffi::CStr; +use std::os::raw::c_char; + +error_chain!{ + errors { + Null { + description("Null pointer") + } + NoEqual { + description("No equal sign in string") + } + } + foreign_links { + InvalidContent(::std::str::Utf8Error); + } +} + + +/// Parses a null terminated C string array into a Vec<String> for safe usage. +/// +/// Returns an Err if given a null pointer or if a string is not valid utf-8. +/// +/// # Segfaults +/// +/// Can cause the program to crash if the pointer array starting at `ptr` is not correctly null +/// terminated. Likewise, if any string pointed to is not properly null terminated it may crash. +pub unsafe fn string_array(mut ptr: *const *const c_char) -> Result<Vec<String>> { + if ptr.is_null() { + Err(Error::from(ErrorKind::Null)) + } else { + let mut strings = Vec::new(); + while !(*ptr).is_null() { + let cstr = CStr::from_ptr(*ptr); + strings.push(cstr.to_str()?.to_owned()); + ptr = ptr.offset(1); + } + Ok(strings) + } +} + + +/// Parses a null terminated array of C strings with "=" delimiters into a key-value map. +/// +/// The input environment has to contain null terminated strings containing at least +/// one equal sign ("="). Every string is split at the first equal sign and added to the map with +/// the first part being the key and the second the value. +/// +/// If multiple entries have the same key, the last one will be in the result map. +/// +/// # Segfaults +/// +/// Uses `string_array` internally and will segfault for the same reasons as that function. +pub unsafe fn env(envptr: *const *const c_char) -> Result<HashMap<String, String>> { + let mut map = HashMap::new(); + for string in string_array(envptr)? { + let mut iter = string.splitn(2, "="); + let key = iter.next().unwrap(); + let value = iter.next().ok_or(Error::from(ErrorKind::NoEqual))?; + map.insert(key.to_owned(), value.to_owned()); + } + Ok(map) +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::os::raw::c_char; + use std::ptr; + + /// Assert macro that fails if the given pattern does not match the given expression + /// TODO(Linus): Very useful macro for asserting on patterns where the type does not implement + /// Eq. Thus it should probably be moved to a general place/crate when needed in more places. + macro_rules! assert_pat { + ($expected:pat, $actual:expr) => {{ + if let $expected = $actual {} else { + let msg = stringify!($expected); + panic!("Expected {}. Got {:?}", msg, $actual); + } + }} + } + + #[test] + fn string_array_null() { + let result = unsafe { string_array(ptr::null()) }; + assert_pat!(Err(Error(ErrorKind::Null, _)), result); + } + + #[test] + fn string_array_empty() { + let ptr_arr = [ptr::null()]; + let result = unsafe { string_array(&ptr_arr as *const *const c_char).unwrap() }; + assert!(result.is_empty()); + } + + #[test] + fn string_array_one_string() { + let test_str = "foobar\0"; + let ptr_arr = [test_str as *const _ as *const c_char, ptr::null()]; + let result = unsafe { string_array(&ptr_arr as *const *const c_char).unwrap() }; + assert_eq!(["foobar"], &result[..]); + } + + #[test] + fn env_one_value() { + let test_str = "var_a=value_b\0"; + let ptr_arr = [test_str as *const _ as *const c_char, ptr::null()]; + let result = unsafe { env(&ptr_arr as *const *const c_char).unwrap() }; + assert_eq!(1, result.len()); + assert_eq!(Some("value_b"), result.get("var_a").map(|s| &s[..])); + } + + #[test] + fn env_no_equal() { + let test_str = "foobar\0"; + let ptr_arr = [test_str as *const _ as *const c_char, ptr::null()]; + let result = unsafe { env(&ptr_arr as *const *const c_char) }; + assert_pat!(Err(Error(ErrorKind::NoEqual, _)), result); + } + + #[test] + fn env_double_equal_trailing_space() { + let test_str = "foo=bar=baz \0"; + let ptr_arr = [test_str as *const _ as *const c_char, ptr::null()]; + let env = unsafe { env(&ptr_arr as *const *const c_char).unwrap() }; + assert_eq!(1, env.len()); + assert_eq!(Some("bar=baz "), env.get("foo").map(|s| &s[..])); + } +} diff --git a/talpid_openvpn_plugin/src/lib.rs b/talpid_openvpn_plugin/src/lib.rs index a917ead7a5..91fcd34396 100644 --- a/talpid_openvpn_plugin/src/lib.rs +++ b/talpid_openvpn_plugin/src/lib.rs @@ -1,6 +1,9 @@ #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate error_chain; + mod ffi; /// Publicly export the functions making up the public interface of the plugin. These are the C FFI |
