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),
|
ParseExt(#[from] crate::extra::windows::ParseExtError),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
ParseProgId(#[from] crate::extra::windows::ParseProgIdError),
|
ParseProgId(#[from] crate::extra::windows::ParseProgIdError),
|
||||||
|
#[error("{0}")]
|
||||||
|
BlankPath(#[from] crate::extra::winreg::BlankStringError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result type used in this crate.
|
/// The result type used in this crate.
|
||||||
@ -232,11 +234,14 @@ pub struct ExtKey {
|
|||||||
impl ExtKey {
|
impl ExtKey {
|
||||||
/// Set the default "Open With" of this file extension to given ProgId.
|
/// Set the default "Open With" of this file extension to given ProgId.
|
||||||
pub fn link(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> {
|
pub fn link(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> {
|
||||||
|
use winreg_extra::blank_path_guard;
|
||||||
|
|
||||||
// Open Classes key
|
// Open Classes key
|
||||||
let classes = ClassesVisitor::open_with_scope(scope)?;
|
let classes = ClassesVisitor::open_with_scope(scope)?;
|
||||||
|
|
||||||
// Open or create this extension key
|
// 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
|
// Set the default way to open this file extension
|
||||||
subkey.set_value("", &prog_id.to_string())?;
|
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,
|
/// If the default "Open With" of this file extension is not given ProgId,
|
||||||
/// or there is no such file extension, this function do nothing.
|
/// or there is no such file extension, this function do nothing.
|
||||||
pub fn unlink(&self, prog_id: &ProgIdKey, scope: Scope) -> Result<()> {
|
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
|
// Open Classes key
|
||||||
let classes = ClassesVisitor::open_with_scope(scope)?;
|
let classes = ClassesVisitor::open_with_scope(scope)?;
|
||||||
|
|
||||||
// Open key for this extension.
|
// Open key for this extension.
|
||||||
// If there is no such key, return directly.
|
// 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
|
// Only delete the default key if it is equal to our ProgId
|
||||||
if let Some(value) = try_get_value::<String, _>(&subkey, "")? {
|
if let Some(value) = try_get_value::<String, _>(&subkey, "")? {
|
||||||
if value == prog_id.to_string() {
|
if value == prog_id.to_string() {
|
||||||
@ -275,13 +283,17 @@ impl ExtKey {
|
|||||||
///
|
///
|
||||||
/// This function will return its associated ProgId if "Open With" was set.
|
/// This function will return its associated ProgId if "Open With" was set.
|
||||||
pub fn query(&self, view: View) -> Result<Option<ProgIdKey>> {
|
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
|
// Open Classes key
|
||||||
let classes = ClassesVisitor::open_with_view(view)?;
|
let classes = ClassesVisitor::open_with_view(view)?;
|
||||||
|
|
||||||
// Open key for this extension if possible
|
// 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) => {
|
Some(subkey) => {
|
||||||
// Try get associated ProgId if possible
|
// Try get associated ProgId if possible
|
||||||
match try_get_value::<String, _>(&subkey, "")? {
|
match try_get_value::<String, _>(&subkey, "")? {
|
||||||
@ -327,8 +339,11 @@ pub struct ProgIdKey {
|
|||||||
impl ProgIdKey {
|
impl ProgIdKey {
|
||||||
/// Create ProgId into Registry in given scope with given parameters
|
/// Create ProgId into Registry in given scope with given parameters
|
||||||
pub fn create(&self, scope: Scope, command: &str) -> Result<()> {
|
pub fn create(&self, scope: Scope, command: &str) -> Result<()> {
|
||||||
|
use winreg_extra::blank_path_guard;
|
||||||
|
|
||||||
let classes = ClassesVisitor::open_with_scope(scope)?;
|
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
|
// Create verb
|
||||||
let (subkey_verb, _) = subkey.create_subkey_with_flags("open", KEY_READ)?;
|
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.
|
/// Delete this ProgId from registry in given scope.
|
||||||
pub fn delete(&self, scope: Scope) -> Result<()> {
|
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)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
use thiserror::Error as TeError;
|
||||||
use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
|
use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
|
||||||
use windows_sys::Win32::System::Registry::REG_SAM_FLAGS;
|
use windows_sys::Win32::System::Registry::REG_SAM_FLAGS;
|
||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
@ -11,11 +12,11 @@ use winreg::types::FromRegValue;
|
|||||||
// region: Extra Operations
|
// region: Extra Operations
|
||||||
|
|
||||||
/// Get the subkey with given name.
|
/// Get the subkey with given name.
|
||||||
///
|
///
|
||||||
/// If error occurs when fetching given subkey, it return `Err(...)`,
|
/// If error occurs when fetching given subkey, it return `Err(...)`,
|
||||||
/// otherwise, it will return `Ok(Some(...))` if aubkey is existing,
|
/// otherwise, it will return `Ok(Some(...))` if aubkey is existing,
|
||||||
/// or `Ok(None)` if there is no suchsub key.
|
/// or `Ok(None)` if there is no suchsub key.
|
||||||
///
|
///
|
||||||
/// Comparing with the function provided by winreg,
|
/// Comparing with the function provided by winreg,
|
||||||
/// it differ "no such subkey" error and other access error.
|
/// it differ "no such subkey" error and other access error.
|
||||||
pub fn try_open_subkey_with_flags<P: AsRef<OsStr>>(
|
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() {
|
Err(e) => match e.raw_os_error() {
|
||||||
Some(errno) => match errno as u32 {
|
Some(errno) => match errno as u32 {
|
||||||
ERROR_FILE_NOT_FOUND => Ok(None),
|
ERROR_FILE_NOT_FOUND => Ok(None),
|
||||||
_ => Err(e)
|
_ => Err(e),
|
||||||
}
|
},
|
||||||
_ => Err(e),
|
_ => Err(e),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value by given key.
|
/// Get the value by given key.
|
||||||
///
|
///
|
||||||
/// If error occurs when fetching given key, it return `Err(...)`,
|
/// If error occurs when fetching given key, it return `Err(...)`,
|
||||||
/// otherwise, it will return `Ok(Some(...))` if key is existing,
|
/// otherwise, it will return `Ok(Some(...))` if key is existing,
|
||||||
/// or `Ok(None)` if there is no such key.
|
/// or `Ok(None)` if there is no such key.
|
||||||
///
|
///
|
||||||
/// Comparing with the function provided by winreg,
|
/// Comparing with the function provided by winreg,
|
||||||
/// it differ "no such key" error and other access error.
|
/// it differ "no such key" error and other access error.
|
||||||
pub fn try_get_value<T: FromRegValue, N: AsRef<OsStr>>(
|
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() {
|
Err(e) => match e.raw_os_error() {
|
||||||
Some(errno) => match errno as u32 {
|
Some(errno) => match errno as u32 {
|
||||||
ERROR_FILE_NOT_FOUND => Ok(None),
|
ERROR_FILE_NOT_FOUND => Ok(None),
|
||||||
_ => Err(e)
|
_ => Err(e),
|
||||||
}
|
},
|
||||||
_ => Err(e),
|
_ => Err(e),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Passing empty string to `delete_subkey_all` may cause
|
// endregion
|
||||||
/// that the whole parent tree are deleted, not the subkey.
|
|
||||||
/// So we create this "safe" function to prevent this horrible scenarios.
|
// region: Blank String Guard
|
||||||
pub fn safe_delete_subkey_all<P: AsRef<OsStr>>(regkey: &RegKey, path: P) -> std::io::Result<()> {
|
|
||||||
|
/// 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() {
|
if path.as_ref().is_empty() {
|
||||||
Err(std::io::Error::other("dangerous Registry delete_subkey_all"))
|
Err(BlankStringError::new())
|
||||||
} else {
|
} else {
|
||||||
regkey.delete_subkey_all(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user