feat(registry): add privilege check and improve ProgId handling
- Add WFHasPrivilege function to check user privileges - Refactor ProgId structure to use standard format with optional version - Improve registry operations with safer key/value handling - Update dependencies to include Win32_System_Registry
This commit is contained in:
@ -8,7 +8,12 @@ license = "SPDX:MIT"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
windows-sys = { version = "0.60.2", features = ["Win32_Security", "Win32_System_SystemServices", "Win32_UI_Shell"] }
|
windows-sys = { version = "0.60.2", features = [
|
||||||
|
"Win32_Security",
|
||||||
|
"Win32_System_SystemServices",
|
||||||
|
"Win32_UI_Shell",
|
||||||
|
"Win32_System_Registry",
|
||||||
|
] }
|
||||||
winreg = { version = "0.55.0", features = ["transactions"] }
|
winreg = { version = "0.55.0", features = ["transactions"] }
|
||||||
indexmap = "2.11.4"
|
indexmap = "2.11.4"
|
||||||
regex = "1.11.3"
|
regex = "1.11.3"
|
||||||
|
|||||||
@ -79,32 +79,27 @@ impl FromStr for Ext {
|
|||||||
/// - https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids
|
/// - https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids
|
||||||
/// - https://learn.microsoft.com/en-us/windows/win32/com/-progid--key
|
/// - https://learn.microsoft.com/en-us/windows/win32/com/-progid--key
|
||||||
pub enum ProgId {
|
pub enum ProgId {
|
||||||
Plain(String),
|
Other(String),
|
||||||
Loose(LosseProgId),
|
Std(StdProgId),
|
||||||
Strict(StrictProgId),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for ProgId {
|
impl From<&str> for ProgId {
|
||||||
fn from(s: &str) -> Self {
|
fn from(s: &str) -> Self {
|
||||||
// match it for strict ProgId first
|
// match it for standard ProgId first
|
||||||
if let Ok(v) = StrictProgId::from_str(s) {
|
if let Ok(v) = StdProgId::from_str(s) {
|
||||||
return Self::Strict(v);
|
Self::Std(v)
|
||||||
|
} else {
|
||||||
|
// fallback with other
|
||||||
|
Self::Other(s.to_string())
|
||||||
}
|
}
|
||||||
// then match for loose ProgId
|
|
||||||
if let Ok(v) = LosseProgId::from_str(s) {
|
|
||||||
return Self::Loose(v);
|
|
||||||
}
|
|
||||||
// fallback with plain
|
|
||||||
Self::Plain(s.to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ProgId {
|
impl Display for ProgId {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ProgId::Plain(v) => v.fmt(f),
|
ProgId::Other(v) => v.fmt(f),
|
||||||
ProgId::Loose(v) => v.fmt(f),
|
ProgId::Std(v) => v.fmt(f),
|
||||||
ProgId::Strict(v) => v.fmt(f),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,61 +119,18 @@ impl ParseProgIdError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The ProgId similar with strict ProgId, but no version part.
|
/// The ProgId exactly follows Microsoft suggested
|
||||||
pub struct LosseProgId {
|
/// `[Vendor or Application].[Component].[Version]` format.
|
||||||
|
/// And `[Version]` part is optional.
|
||||||
|
pub struct StdProgId {
|
||||||
vendor: String,
|
vendor: String,
|
||||||
component: String,
|
component: String,
|
||||||
|
version: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LosseProgId {
|
impl StdProgId {
|
||||||
pub fn new(vendor: &str, component: &str) -> Self {
|
/// Create a new standard ProgId.
|
||||||
Self {
|
pub fn new(vendor: &str, component: &str, version: Option<u32>) -> Self {
|
||||||
vendor: vendor.to_string(),
|
|
||||||
component: component.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_vendor(&self) -> &str {
|
|
||||||
&self.vendor
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_component(&self) -> &str {
|
|
||||||
&self.component
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for LosseProgId {
|
|
||||||
type Err = ParseProgIdError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
static RE: LazyLock<Regex> =
|
|
||||||
LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)$").unwrap());
|
|
||||||
let caps = RE.captures(s);
|
|
||||||
if let Some(caps) = caps {
|
|
||||||
let vendor = &caps[1];
|
|
||||||
let component = &caps[2];
|
|
||||||
Ok(Self::new(vendor, component))
|
|
||||||
} else {
|
|
||||||
Err(ParseProgIdError::new(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for LosseProgId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}.{}", self.vendor, self.component)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The ProgId exactly follows `[Vendor or Application].[Component].[Version]` format.
|
|
||||||
pub struct StrictProgId {
|
|
||||||
vendor: String,
|
|
||||||
component: String,
|
|
||||||
version: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StrictProgId {
|
|
||||||
pub fn new(vendor: &str, component: &str, version: u32) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
vendor: vendor.to_string(),
|
vendor: vendor.to_string(),
|
||||||
component: component.to_string(),
|
component: component.to_string(),
|
||||||
@ -186,32 +138,40 @@ impl StrictProgId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the vendor part of standard ProgId.
|
||||||
pub fn get_vendor(&self) -> &str {
|
pub fn get_vendor(&self) -> &str {
|
||||||
&self.vendor
|
&self.vendor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the component part of standard ProgId.
|
||||||
pub fn get_component(&self) -> &str {
|
pub fn get_component(&self) -> &str {
|
||||||
&self.component
|
&self.component
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_version(&self) -> u32 {
|
/// Get the version part of standard ProgId.
|
||||||
|
pub fn get_version(&self) -> Option<u32> {
|
||||||
self.version
|
self.version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for StrictProgId {
|
impl FromStr for StdProgId {
|
||||||
type Err = ParseProgIdError;
|
type Err = ParseProgIdError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
static RE: LazyLock<Regex> =
|
static RE: LazyLock<Regex> =
|
||||||
LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)\.([0-9]+)$").unwrap());
|
LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)(\.([0-9]+))?$").unwrap());
|
||||||
let caps = RE.captures(s);
|
let caps = RE.captures(s);
|
||||||
if let Some(caps) = caps {
|
if let Some(caps) = caps {
|
||||||
let vendor = &caps[1];
|
let vendor = &caps[1];
|
||||||
let component = &caps[2];
|
let component = &caps[2];
|
||||||
let version = caps[3]
|
let version = match caps.get(4) {
|
||||||
.parse::<u32>()
|
Some(sv) => Some(
|
||||||
.map_err(|_| ParseProgIdError::new(s))?;
|
sv.as_str()
|
||||||
|
.parse::<u32>()
|
||||||
|
.map_err(|_| ParseProgIdError::new(s))?,
|
||||||
|
),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
Ok(Self::new(vendor, component, version))
|
Ok(Self::new(vendor, component, version))
|
||||||
} else {
|
} else {
|
||||||
Err(ParseProgIdError::new(s))
|
Err(ParseProgIdError::new(s))
|
||||||
@ -219,9 +179,12 @@ impl FromStr for StrictProgId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for StrictProgId {
|
impl Display for StdProgId {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}.{}.{}", self.vendor, self.component, self.version)
|
match &self.version {
|
||||||
|
Some(version) => write!(f, "{}.{}.{}", self.vendor, self.component, version),
|
||||||
|
None => write!(f, "{}.{}", self.vendor, self.component),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ use winreg::RegKey;
|
|||||||
use winreg::enums::{
|
use winreg::enums::{
|
||||||
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE,
|
HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WRITE,
|
||||||
};
|
};
|
||||||
|
use assoc::{Ext, ProgId};
|
||||||
|
|
||||||
// region: Error Types
|
// region: Error Types
|
||||||
|
|
||||||
@ -138,7 +139,7 @@ pub struct Program {
|
|||||||
manners: IndexSet<String>,
|
manners: IndexSet<String>,
|
||||||
/// The collection holding all file extensions supported by this program.
|
/// The collection holding all file extensions supported by this program.
|
||||||
/// The key is file estension and value is its associated manner for opening it.
|
/// The key is file estension and value is its associated manner for opening it.
|
||||||
exts: IndexMap<assoc::Ext, Token>,
|
exts: IndexMap<Ext, Token>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
@ -200,7 +201,7 @@ impl Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create extension from string
|
// Create extension from string
|
||||||
let ext = assoc::Ext::new(ext)?;
|
let ext = Ext::new(ext)?;
|
||||||
// Backup a stringfied extension for error output.
|
// Backup a stringfied extension for error output.
|
||||||
let ext_str = ext.to_string();
|
let ext_str = ext.to_string();
|
||||||
// Insert file extension
|
// Insert file extension
|
||||||
@ -382,9 +383,17 @@ impl Program {
|
|||||||
|
|
||||||
// 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.
|
||||||
let subkey = classes.open_subkey_with_flags(ext.to_string(), KEY_WRITE)?;
|
if let Some(subkey) =
|
||||||
// Delete the default key.
|
winreg_extra::try_open_subkey_with_flags(&classes, ext.to_string(), KEY_WRITE)?
|
||||||
subkey.delete_value("")?;
|
{
|
||||||
|
// Only delete the default key if it is equal to our ProgId
|
||||||
|
if let Some(value) = winreg_extra::try_get_value::<String, _>(&subkey, "")? {
|
||||||
|
if value == prog_id.to_string() {
|
||||||
|
// Delete the default key.
|
||||||
|
subkey.delete_value("")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Okey
|
// Okey
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -416,10 +425,12 @@ impl Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build ProgId from identifier and given file extension.
|
/// Build ProgId from identifier and given file extension.
|
||||||
fn build_prog_id(&self, ext: &assoc::Ext) -> assoc::ProgId {
|
fn build_prog_id(&self, ext: &Ext) -> ProgId {
|
||||||
let vendor = utilities::capitalize_first_ascii(&self.identifier);
|
ProgId::Std(assoc::StdProgId::new(
|
||||||
let component = utilities::capitalize_first_ascii(ext.inner());
|
&self.identifier,
|
||||||
assoc::ProgId::Loose(assoc::LosseProgId::new(&vendor, &component))
|
&utilities::capitalize_first_ascii(ext.inner()),
|
||||||
|
None
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,69 @@
|
|||||||
//! This module expand `winreg` crate to make it more suit for this crate.
|
//! This module expand `winreg` crate to make it more suit for this crate.
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
|
||||||
|
use windows_sys::Win32::System::Registry::REG_SAM_FLAGS;
|
||||||
|
use winreg::RegKey;
|
||||||
|
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>>(
|
||||||
|
regkey: &RegKey,
|
||||||
|
path: P,
|
||||||
|
perms: REG_SAM_FLAGS,
|
||||||
|
) -> std::io::Result<Option<RegKey>> {
|
||||||
|
match regkey.open_subkey_with_flags(path, perms) {
|
||||||
|
Ok(v) => Ok(Some(v)),
|
||||||
|
Err(e) => match e.raw_os_error() {
|
||||||
|
Some(errno) => match errno as u32 {
|
||||||
|
ERROR_FILE_NOT_FOUND => Ok(None),
|
||||||
|
_ => 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>>(
|
||||||
|
regkey: &RegKey,
|
||||||
|
name: N,
|
||||||
|
) -> std::io::Result<Option<T>> {
|
||||||
|
match regkey.get_value::<T, N>(name) {
|
||||||
|
Ok(v) => Ok(Some(v)),
|
||||||
|
Err(e) => match e.raw_os_error() {
|
||||||
|
Some(errno) => match errno as u32 {
|
||||||
|
ERROR_FILE_NOT_FOUND => Ok(None),
|
||||||
|
_ => Err(e)
|
||||||
|
}
|
||||||
|
_ => Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region: Expand String
|
// region: Expand String
|
||||||
|
|
||||||
/// The struct basically is the alias of String, but make a slight difference with it,
|
/// The struct basically is the alias of String, but make a slight difference with it,
|
||||||
/// to make they are different when use it with String as generic argument.
|
/// to make they are different when use it with String as generic argument.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ExpandString(String);
|
pub struct ExpandString(String);
|
||||||
@ -12,22 +73,22 @@ impl ExpandString {
|
|||||||
pub fn new(s: String) -> Self {
|
pub fn new(s: String) -> Self {
|
||||||
Self(s)
|
Self(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create from &str
|
/// Create from &str
|
||||||
pub fn from_str(s: &str) -> Self {
|
pub fn from_str(s: &str) -> Self {
|
||||||
Self(s.to_string())
|
Self(s.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get reference to internal String.
|
/// Get reference to internal String.
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get mutable reference to internal String.
|
/// Get mutable reference to internal String.
|
||||||
pub fn as_mut_str(&mut self) -> &mut String {
|
pub fn as_mut_str(&mut self) -> &mut String {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Comsule self, return internal String.
|
/// Comsule self, return internal String.
|
||||||
pub fn into_inner(self) -> String {
|
pub fn into_inner(self) -> String {
|
||||||
self.0
|
self.0
|
||||||
@ -35,17 +96,15 @@ impl ExpandString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implement Deref trait to make it can be used like &str
|
// Implement Deref trait to make it can be used like &str
|
||||||
use std::ops::Deref;
|
|
||||||
impl Deref for ExpandString {
|
impl Deref for ExpandString {
|
||||||
type Target = str;
|
type Target = str;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement DerefMut trait
|
// Implement DerefMut trait
|
||||||
use std::ops::DerefMut;
|
|
||||||
impl DerefMut for ExpandString {
|
impl DerefMut for ExpandString {
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
|
|||||||
@ -90,6 +90,11 @@ pub extern "C" fn WFGetLastError() -> *const c_char {
|
|||||||
get_last_error()
|
get_last_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFHasPrivilege() -> bool {
|
||||||
|
wfassoc::utilities::has_privilege()
|
||||||
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFAdd(left: u32, right: u32, rv: *mut u32) -> bool {
|
pub extern "C" fn WFAdd(left: u32, right: u32, rv: *mut u32) -> bool {
|
||||||
unsafe { *rv = left + right; }
|
unsafe { *rv = left + right; }
|
||||||
|
|||||||
Reference in New Issue
Block a user