feat(windows): add blank path guard for registry operations
- Add BlankStringError to handle empty registry paths - Implement blank_path_guard function to prevent dangerous registry operations - Apply blank path guard to extension and ProgId key operations - Update error handling to include BlankPath error variant
This commit is contained in:
@ -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::<String, _>(&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<Option<ProgIdKey>> {
|
||||
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::<String, _>(&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(())
|
||||
}
|
||||
|
||||
@ -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<P: AsRef<OsStr>>(
|
||||
@ -28,19 +29,19 @@ pub fn try_open_subkey_with_flags<P: AsRef<OsStr>>(
|
||||
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<T: FromRegValue, N: AsRef<OsStr>>(
|
||||
@ -52,21 +53,40 @@ pub fn try_get_value<T: FromRegValue, N: AsRef<OsStr>>(
|
||||
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<P: AsRef<OsStr>>(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<P: AsRef<OsStr>>(path: P) -> std::result::Result<P, BlankStringError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user