diff --git a/wfassoc/src/assoc.rs b/wfassoc/src/assoc.rs index f457380..24d27fb 100644 --- a/wfassoc/src/assoc.rs +++ b/wfassoc/src/assoc.rs @@ -24,6 +24,8 @@ pub enum Error { ParseExt(#[from] crate::extra::windows::ParseExtError), #[error("{0}")] ParseProgId(#[from] crate::extra::windows::ParseProgIdError), + #[error("{0}")] + BlankPath(#[from] crate::extra::winreg::BlankStringError), } /// The result type used in this crate. @@ -232,11 +234,14 @@ pub struct ExtKey { impl ExtKey { /// Set the default "Open With" of this file extension to given ProgId. pub fn link(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> { + use winreg_extra::blank_path_guard; + // Open Classes key let classes = ClassesVisitor::open_with_scope(scope)?; // Open or create this extension key - let (subkey, _) = classes.create_subkey_with_flags(self.ext.to_string(), KEY_WRITE)?; + let (subkey, _) = + classes.create_subkey_with_flags(blank_path_guard(self.ext.to_string())?, KEY_WRITE)?; // Set the default way to open this file extension subkey.set_value("", &prog_id.to_string())?; @@ -249,15 +254,18 @@ impl ExtKey { /// If the default "Open With" of this file extension is not given ProgId, /// or there is no such file extension, this function do nothing. pub fn unlink(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> { - use winreg_extra::{try_get_value, try_open_subkey_with_flags}; + use winreg_extra::{blank_path_guard, try_get_value, try_open_subkey_with_flags}; // Open Classes key let classes = ClassesVisitor::open_with_scope(scope)?; // Open key for this extension. // If there is no such key, return directly. - if let Some(subkey) = try_open_subkey_with_flags(&classes, self.ext.to_string(), KEY_WRITE)? - { + if let Some(subkey) = try_open_subkey_with_flags( + &classes, + blank_path_guard(self.ext.to_string())?, + KEY_WRITE, + )? { // Only delete the default key if it is equal to our ProgId if let Some(value) = try_get_value::(&subkey, "")? { if value == prog_id.to_string() { @@ -275,13 +283,17 @@ impl ExtKey { /// /// This function will return its associated ProgId if "Open With" was set. pub fn query(&self, view: View) -> Result> { - use winreg_extra::{try_get_value, try_open_subkey_with_flags}; + use winreg_extra::{blank_path_guard, try_get_value, try_open_subkey_with_flags}; // Open Classes key let classes = ClassesVisitor::open_with_view(view)?; // Open key for this extension if possible - let rv = match try_open_subkey_with_flags(&classes, self.ext.to_string(), KEY_READ)? { + let rv = match try_open_subkey_with_flags( + &classes, + blank_path_guard(self.ext.to_string())?, + KEY_READ, + )? { Some(subkey) => { // Try get associated ProgId if possible match try_get_value::(&subkey, "")? { @@ -327,8 +339,11 @@ pub struct ProgIdKey { impl ProgIdKey { /// Create ProgId into Registry in given scope with given parameters pub fn create(&self, scope: Scope, command: &str) -> Result<()> { + use winreg_extra::blank_path_guard; + let classes = ClassesVisitor::open_with_scope(scope)?; - let (subkey, _) = classes.create_subkey_with_flags(self.prog_id.to_string(), KEY_WRITE)?; + let (subkey, _) = classes + .create_subkey_with_flags(blank_path_guard(self.prog_id.to_string())?, KEY_WRITE)?; // Create verb let (subkey_verb, _) = subkey.create_subkey_with_flags("open", KEY_READ)?; @@ -340,10 +355,10 @@ impl ProgIdKey { /// Delete this ProgId from registry in given scope. pub fn delete(&self, scope: Scope) -> Result<()> { - use winreg_extra::safe_delete_subkey_all; + use winreg_extra::blank_path_guard; let classes = ClassesVisitor::open_with_scope(scope)?; - safe_delete_subkey_all(&classes, self.prog_id.to_string())?; + classes.delete_subkey_all(blank_path_guard(self.prog_id.to_string())?)?; Ok(()) } diff --git a/wfassoc/src/extra/winreg.rs b/wfassoc/src/extra/winreg.rs index 3f96d43..014cb84 100644 --- a/wfassoc/src/extra/winreg.rs +++ b/wfassoc/src/extra/winreg.rs @@ -3,6 +3,7 @@ use std::ffi::OsStr; use std::ops::Deref; use std::ops::DerefMut; +use thiserror::Error as TeError; use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND; use windows_sys::Win32::System::Registry::REG_SAM_FLAGS; use winreg::RegKey; @@ -11,11 +12,11 @@ use winreg::types::FromRegValue; // region: Extra Operations /// Get the subkey with given name. -/// +/// /// If error occurs when fetching given subkey, it return `Err(...)`, /// otherwise, it will return `Ok(Some(...))` if aubkey is existing, /// or `Ok(None)` if there is no suchsub key. -/// +/// /// Comparing with the function provided by winreg, /// it differ "no such subkey" error and other access error. pub fn try_open_subkey_with_flags>( @@ -28,19 +29,19 @@ pub fn try_open_subkey_with_flags>( Err(e) => match e.raw_os_error() { Some(errno) => match errno as u32 { ERROR_FILE_NOT_FOUND => Ok(None), - _ => Err(e) - } + _ => Err(e), + }, _ => Err(e), }, } } /// Get the value by given key. -/// +/// /// If error occurs when fetching given key, it return `Err(...)`, /// otherwise, it will return `Ok(Some(...))` if key is existing, /// or `Ok(None)` if there is no such key. -/// +/// /// Comparing with the function provided by winreg, /// it differ "no such key" error and other access error. pub fn try_get_value>( @@ -52,21 +53,40 @@ pub fn try_get_value>( Err(e) => match e.raw_os_error() { Some(errno) => match errno as u32 { ERROR_FILE_NOT_FOUND => Ok(None), - _ => Err(e) - } + _ => Err(e), + }, _ => Err(e), }, } } -/// Passing empty string to `delete_subkey_all` may cause -/// that the whole parent tree are deleted, not the subkey. -/// So we create this "safe" function to prevent this horrible scenarios. -pub fn safe_delete_subkey_all>(regkey: &RegKey, path: P) -> std::io::Result<()> { +// endregion + +// region: Blank String Guard + +/// The error occurs when given string is empty. +#[derive(Debug, TeError)] +#[error("unexpected empty string")] +pub struct BlankStringError {} + +impl BlankStringError { + fn new() -> Self { + Self {} + } +} + +/// Check whether given registry path is empty. +/// If it is, return error, otherwise the given path. +/// +/// Passing empty path for some registry functions is dangerous. +/// Because it will cause unexpected behavior that returning key self, rather than subkey. +/// This is VERY dangerous especially for those registry delete functions. +/// So I create this function to prevent any harmful blank path was passed into registry function. +pub fn blank_path_guard>(path: P) -> std::result::Result { if path.as_ref().is_empty() { - Err(std::io::Error::other("dangerous Registry delete_subkey_all")) + Err(BlankStringError::new()) } else { - regkey.delete_subkey_all(path) + Ok(path) } }