diff options
author | SoniEx2 <endermoneymod@gmail.com> | 2022-04-30 22:23:31 -0300 |
---|---|---|
committer | SoniEx2 <endermoneymod@gmail.com> | 2022-04-30 23:38:50 -0300 |
commit | 23ba75117815bc384bd4d678b5c5c2091edffcbb (patch) | |
tree | bb11fa24f86f5ffa3264e5ff66997360e86fe8a5 | |
parent | f56ae877c32418630485d71a58b76edb64e96200 (diff) |
Add pluginpref impl
-rw-r--r-- | src/lib.rs | 339 |
1 files changed, 331 insertions, 8 deletions
diff --git a/src/lib.rs b/src/lib.rs index 8f72571..5f80c1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,12 @@ //! //! This caveat does not apply to static assets (`static FOO: &'static str`, for example), but it //! does apply to thread-local storage. +//! +//! # Panics +//! +//! Unless otherwise stated, all functions in this crate taking strings (be +//! that `&str`, `String`, etc) panic when the string contains embedded `NUL` +//! characters (`\0`). //! //! # Examples //! @@ -167,12 +173,12 @@ * -[x] hexchat_find_context * -[x] hexchat_get_context * -[x] hexchat_set_context - * -[ ] hexchat_pluginpref_set_str - * -[ ] hexchat_pluginpref_get_str - * -[ ] hexchat_pluginpref_set_int - * -[ ] hexchat_pluginpref_get_int - * -[ ] hexchat_pluginpref_delete - * -[ ] hexchat_pluginpref_list + * -[x] hexchat_pluginpref_set_str + * -[x] hexchat_pluginpref_get_str + * -[x] ~~hexchat_pluginpref_set_int~~ not available - use `format!` + * -[x] ~~hexchat_pluginpref_get_int~~ not available - use `str::parse` + * -[x] hexchat_pluginpref_delete + * -[x] hexchat_pluginpref_list * -[x] hexchat_plugingui_add * -[x] ~~hexchat_plugingui_remove~~ not available - use Drop impls. * -[x] Wrap contexts within something we keep track of. Mark them invalid when contexts are @@ -233,6 +239,7 @@ use std::fmt; use std::marker::PhantomData; use std::mem; use std::mem::ManuallyDrop; +use std::mem::MaybeUninit; use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe, catch_unwind}; use std::pin::Pin; use std::ptr; @@ -435,12 +442,81 @@ pub struct Context<'ph> { // #[derive(Debug)] // doesn't work pub struct InvalidContextError<F>(F); +/// An iterable list of plugin pref names. +pub struct PluginPrefList { + inner: Vec<u8>, +} + +/// An iterator over `PluginPrefList`. +pub struct PluginPrefListIter<'a> { + list: Option<&'a [u8]>, +} + // Enums +/// Errors returned by `pluginpref_*` functions. +#[derive(Debug)] +pub enum PluginPrefError { + /// The var contains a forbidden character: `[ ,=\n]` (space, comma, + /// equals, newline), is too long (max 126 bytes), is empty, or contains + /// trailing ASCII whitespace. + /// + /// Returned by anything that interacts with vars. `pluginpref_list` only + /// returns these during iteration. + InvalidVar, + /// The value starts with space. + /// + /// Returned by `pluginpref_set`. + InvalidValue, + /// The input (var + value) was too long after encoding. + /// + /// Returned by `pluginpref_set`. + TooLong, + /// The returned value was not valid UTF-8. + /// + /// Returned by `pluginpref_get`. + Utf8Error(::std::ffi::IntoStringError), + /// The returned var was not valid UTF-8. + /// + /// Returned while iterating a `pluginpref_list`. + VarUtf8Error(::std::str::Utf8Error), + /// The operation failed. + /// + /// Returned by anything that interacts with pluginprefs. Iterating a + /// `pluginpref_list` never returns this. + Failed, +} + // ***** // // impls // // ***** // +impl<'a> Iterator for PluginPrefListIter<'a> { + type Item = Result<&'a str, PluginPrefError>; + + fn next(&mut self) -> Option<Self::Item> { + let mut splitter = self.list?.splitn(2, |x| *x == 0); + let ret = splitter.next().unwrap(); + let ret = test_pluginpref_var(ret).and_then(|_| { + std::str::from_utf8(ret).map_err(|e| PluginPrefError::VarUtf8Error(e)) + }); + self.list = splitter.next(); + Some(ret) + } +} + +impl<'a> IntoIterator for &'a PluginPrefList { + type Item = Result<&'a str, PluginPrefError>; + + type IntoIter = PluginPrefListIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + PluginPrefListIter { + list: self.inner.split_last().map(|(_, x)| x), + } + } +} + impl<F> InvalidContextError<F> { /// Returns the closure wrapped within this error. pub fn get_closure(self) -> F { @@ -1278,8 +1354,203 @@ impl<'ph> PluginHandle<'ph> { String::from_utf8(out).unwrap() } - // TODO pluginprefs but see: https://developer.gimp.org/api/2.0/glib/glib-String-Utility-Functions.html#g-strescape - // var.len() + g_strescape(value).len() + 3 < 512 + /// Sets a pluginpref. + /// + /// Note: If two pluginprefs exist with the same name, but different ASCII + /// case, only one will be available through `pluginpref_get`. + /// + /// # Panics + /// + /// Panics if the plugin is not registered. + /// + /// # Examples + /// + /// ```no_run + /// use hexchat_unsafe_plugin::PluginHandle; + /// + /// fn set_str(ph: &PluginHandle<'_>, val: &str) { + /// ph.pluginpref_set("string", val); + /// } + /// + /// fn set_int(ph: &PluginHandle<'_>, val: i32) { + /// ph.pluginpref_set("int", &format!("{}", val)); + /// } + /// ``` + pub fn pluginpref_set( + &self, + var: &str, + value: &str, + ) -> Result<(), PluginPrefError> { + assert!(!self.info.name.is_null(), "must register plugin first"); + if value.starts_with(' ') { + return Err(PluginPrefError::InvalidValue) + } + let var_len = var.len(); + let var = check_pluginpref_var(var)?; + let mut val_len = value.len(); + // octal is \000 - \377, adds 3 bytes + val_len += 3 * value.bytes().filter(|&x| x < 32 || x > 127).count(); + // special chars are \t \n etc, or octal - 2 bytes + val_len -= 2 * value.bytes().filter(|&x| { + matches!( + x, + | b'\n' + | b'\r' + | b'\t' + | b'\x0C' // \f + | b'\x08' // \b + ) + }).count(); + // additionally \ and " also get an extra byte + val_len += value.bytes().filter(|&x| x == b'"' || x == b'\\').count(); + // 3 bytes from " = ", < 512 because 511 + NUL + if var_len + val_len + 3 < 512 { + let value = CString::new(value).unwrap(); + let success = unsafe { + ph_call!( + hexchat_pluginpref_set_str( + self, + var.as_ptr(), + value.as_ptr() + ) + ) + } != 0; + if !success { + Err(PluginPrefError::Failed) + } else { + Ok(()) + } + } else { + Err(PluginPrefError::TooLong) + } + } + + /// Retrieves a pluginpref. + /// + /// # Panics + /// + /// Panics if the plugin is not registered. + /// + /// # Examples + /// + /// ```no_run + /// use hexchat_unsafe_plugin::PluginHandle; + /// + /// fn get_str(ph: &PluginHandle<'_>) -> String { + /// ph.pluginpref_get("string").unwrap_or(String::new()) + /// } + /// + /// fn get_int(ph: &PluginHandle<'_>) -> i32 { + /// ph.pluginpref_get("int").unwrap_or(String::new()).parse().unwrap_or(-1) + /// } + /// ``` + pub fn pluginpref_get( + &self, + var: &str, + ) -> Result<String, PluginPrefError> { + assert!(!self.info.name.is_null(), "must register plugin first"); + let var = check_pluginpref_var(var)?; + let mut buffer: [MaybeUninit<c_char>; 512] = unsafe { + MaybeUninit::uninit().assume_init() + }; + let success = unsafe { + ph_call!( + hexchat_pluginpref_get_str( + self, + var.as_ptr(), + buffer.as_mut_ptr() as *mut _ + ) + ) + } != 0; + if !success { + Err(PluginPrefError::Failed) + } else { + match unsafe { + CStr::from_ptr(buffer.as_ptr() as *const _) + }.to_owned().into_string() { + Ok(s) => Ok(s), + Err(e) => Err(PluginPrefError::Utf8Error(e)), + } + } + } + + /// Removes a pluginpref. + /// + /// # Panics + /// + /// Panics if the plugin is not registered. + /// + /// # Examples + /// + /// ```no_run + /// use hexchat_unsafe_plugin::PluginHandle; + /// + /// fn del_str(ph: &PluginHandle<'_>) { + /// let _ = ph.pluginpref_delete("string"); + /// } + /// + /// fn del_int(ph: &PluginHandle<'_>) { + /// let _ = ph.pluginpref_delete("int"); + /// } + /// ``` + pub fn pluginpref_delete(&self, var: &str) -> Result<(), PluginPrefError> { + assert!(!self.info.name.is_null(), "must register plugin first"); + let var = check_pluginpref_var(var)?; + let success = unsafe { + ph_call!(hexchat_pluginpref_delete(self, var.as_ptr())) + } != 0; + if !success { + Err(PluginPrefError::Failed) + } else { + Ok(()) + } + } + + /// Lists pluginprefs. + /// + /// # Panics + /// + /// Panics if the plugin is not registered. + /// + /// # Examples + /// + /// ```no_run + /// use hexchat_unsafe_plugin::PluginHandle; + /// + /// fn list_prefs(ph: &PluginHandle<'_>) { + /// match ph.pluginpref_list() { + /// Ok(it) => for pref in &it { + /// match pref { + /// Ok(pref) => write!(ph, "{}", pref), + /// _ => (), + /// } + /// }, + /// _ => (), + /// } + /// } + /// ``` + pub fn pluginpref_list(&self) -> Result<PluginPrefList, PluginPrefError> { + assert!(!self.info.name.is_null(), "must register plugin first"); + let mut buffer: [MaybeUninit<c_char>; 4096] = unsafe { + MaybeUninit::uninit().assume_init() + }; + let success = unsafe { + ph_call!( + hexchat_pluginpref_list(self, buffer.as_mut_ptr() as *mut _) + ) + } != 0; + if !success { + Err(PluginPrefError::Failed) + } else { + let mut list = PluginPrefList { + inner: unsafe { + CStr::from_ptr(buffer.as_ptr() as *const _) + }.to_owned().into_bytes(), + }; + list.inner.iter_mut().for_each(|x| if *x == b',' { *x = 0; }); + Ok(list) + } + } // ******* // // PRIVATE // @@ -1689,6 +1960,37 @@ impl<'a, 'ph: 'a> ValidContext<'a, 'ph> { pub fn strip(&self, s: &str, strip: Strip) -> String { self.ph.strip(s, strip) } + /// Sets a pluginpref. + /// + /// See [`PluginHandle::pluginpref_set`]. + pub fn pluginpref_set( + &self, + var: &str, + value: &str, + ) -> Result<(), PluginPrefError> { + self.ph.pluginpref_set(var, value) + } + /// Retrieves a pluginpref. + /// + /// See [`PluginHandle::pluginpref_get`]. + pub fn pluginpref_get( + &self, + var: &str, + ) -> Result<String, PluginPrefError> { + self.ph.pluginpref_get(var) + } + /// Removes a pluginpref. + /// + /// See [`PluginHandle::pluginpref_delete`]. + pub fn pluginpref_delete(&self, var: &str) -> Result<(), PluginPrefError> { + self.ph.pluginpref_delete(var) + } + /// Lists pluginprefs. + /// + /// See [`PluginHandle::pluginpref_list`]. + pub fn pluginpref_list(&self) -> Result<PluginPrefList, PluginPrefError> { + self.ph.pluginpref_list() + } } // ******* // @@ -1850,6 +2152,27 @@ unsafe fn pop_userdata<'ph>(ph: LtPhPtr<'ph>) -> Box<PhUserdata<'ph>> { Box::from_raw(mem::replace(&mut (*ph.ph).userdata, ptr::null_mut()) as *mut PhUserdata<'ph>) } +fn test_pluginpref_var(var: &[u8]) -> Result<(), PluginPrefError> { + if var.len() >= 127 + || var.len() < 1 + // rust uses the same definition of ascii whitespace + || var.last().unwrap().is_ascii_whitespace() + || var.contains(&b' ') + || var.contains(&b'=') + || var.contains(&b'\n') + || var.contains(&b',') { + Err(PluginPrefError::InvalidVar) + } else { + Ok(()) + } +} + +fn check_pluginpref_var(var: impl Into<Vec<u8>>) -> Result<CString, PluginPrefError> { + let var = var.into(); + test_pluginpref_var(&var)?; + Ok(CString::new(var).unwrap()) +} + // *********************** // // PUBLIC OUT OF NECESSITY // // *********************** // |