diff --git a/Cargo.lock b/Cargo.lock index 9564c4e..23485fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,6 +343,7 @@ dependencies = [ "regex", "thiserror", "uuid", + "windows-sys 0.60.2", "winreg", ] diff --git a/wfassoc/Cargo.toml b/wfassoc/Cargo.toml index 975775f..7e0124f 100644 --- a/wfassoc/Cargo.toml +++ b/wfassoc/Cargo.toml @@ -8,6 +8,7 @@ license = "SPDX:MIT" [dependencies] thiserror = { workspace = true } +windows-sys = { version = "0.60.2", features = ["Win32_Security", "Win32_System_SystemServices"]} winreg = "0.55.0" regex = "1.11.3" uuid = "1.18.1" diff --git a/wfassoc/src/components.rs b/wfassoc/src/components.rs deleted file mode 100644 index 9c5a98b..0000000 --- a/wfassoc/src/components.rs +++ /dev/null @@ -1,129 +0,0 @@ -use regex::Regex; -use std::fmt::Display; -use std::path::PathBuf; -use std::str::FromStr; -use std::sync::LazyLock; -use thiserror::Error as TeError; - -/// The register kind when registering program. -pub enum RegisterKind { - /// Register for current user. - User, - /// Register for all users under this computer. - System, -} - -// region: File Extension - -/// The struct representing an file extension which must start with dot (`.`) -/// and followed by at least one arbitrary characters. -pub struct FileExt { - /// The body of file extension (excluding dot). - inner: String, -} - -impl FileExt { - pub fn new(file_ext: &str) -> Result { - static RE: LazyLock = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap()); - match RE.captures(file_ext) { - Some(v) => Ok(Self { - inner: v[1].to_string(), - }), - None => Err(ParseFileExtError::new()), - } - } -} - -/// The error occurs when try parsing string into FileExt. -#[derive(Debug, TeError)] -#[error("given file extension name is illegal")] -pub struct ParseFileExtError {} - -impl ParseFileExtError { - fn new() -> Self { - Self {} - } -} - -impl Display for FileExt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, ".{}", self.inner) - } -} - -impl FromStr for FileExt { - type Err = ParseFileExtError; - - fn from_str(s: &str) -> Result { - Self::new(s) - } -} - -// endregion - -/// The struct representing an Windows acceptable Prgram ID, -/// which looks like `Program.Document.2` -pub struct ProgId { - inner: String, -} - -impl ProgId { - pub fn new(prog_id: &str) -> Self { - Self { - inner: prog_id.to_string(), - } - } -} - -// region: Executable Resource - -/// The struct representing an Windows executable resources path like -/// `path_to_file.exe,1`. -pub struct ExecRc { - /// The path to binary for finding resources. - binary: PathBuf, - /// The inner index of resources. - index: u32, -} - -impl ExecRc { - pub fn new(res_str: &str) -> Result { - static RE: LazyLock = LazyLock::new(|| Regex::new(r"^([^,]+),([0-9]+)$").unwrap()); - let caps = RE.captures(res_str); - if let Some(caps) = caps { - let binary = PathBuf::from_str(&caps[1])?; - let index = caps[2].parse::()?; - Ok(Self { binary, index }) - } else { - Err(ParseExecRcError::NoCapture) - } - } -} - -/// The error occurs when try parsing string into ExecRc. -#[derive(Debug, TeError)] -#[error("given string is not a valid executable resource string")] -pub enum ParseExecRcError { - /// Given string is not matched with format. - NoCapture, - /// Fail to convert executable part into path. - BadBinaryPath(#[from] std::convert::Infallible), - /// Fail to convert index part into valid number. - BadIndex(#[from] std::num::ParseIntError), -} - -impl FromStr for ExecRc { - type Err = ParseExecRcError; - - fn from_str(s: &str) -> Result { - ExecRc::new(s) - } -} - -impl Display for ExecRc { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{},{}", self.binary.to_str().unwrap(), self.index) - } -} - -// endregion diff --git a/wfassoc/src/error.rs b/wfassoc/src/error.rs deleted file mode 100644 index 504d7ea..0000000 --- a/wfassoc/src/error.rs +++ /dev/null @@ -1,15 +0,0 @@ -use thiserror::Error as TeError; - -/// All possible error occurs in this crate. -#[derive(Debug, TeError)] -pub enum Error { - #[error("can not register because lack essential privilege. please consider running with Administrator role")] - NoPrivilege, - #[error("{0}")] - BadFileExt(#[from] super::components::ParseFileExtError), - #[error("{0}")] - BadExecRc(#[from] super::components::ParseExecRcError), -} - -/// The result type used in this crate. -pub type Result = std::result::Result; diff --git a/wfassoc/src/lib.rs b/wfassoc/src/lib.rs index c7d8b96..4c1cf91 100644 --- a/wfassoc/src/lib.rs +++ b/wfassoc/src/lib.rs @@ -1,13 +1,247 @@ //! This crate provide utilities fetching and manilupating Windows file association. //! All code under crate are following Microsoft document: https://learn.microsoft.com/en-us/windows/win32/shell/customizing-file-types-bumper +use regex::Regex; +use std::fmt::Display; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::LazyLock; +use thiserror::Error as TeError; + #[cfg(not(target_os = "windows"))] compile_error!("Crate wfassoc is only supported on Windows."); -pub(crate) mod error; -pub(crate) mod components; -pub(crate) mod program; +// region: Error Types -pub use error::Error as WfError; -pub use program::Program; -pub use components::RegisterKind; +/// All possible error occurs in this crate. +#[derive(Debug, TeError)] +pub enum Error { + #[error( + "can not register because lack essential privilege. please consider running with Administrator role" + )] + NoPrivilege, + #[error("{0}")] + BadFileExt(#[from] ParseFileExtError), + #[error("{0}")] + BadExecRc(#[from] ParseExecRcError), +} + +// endregion + +/// The scope where wfassoc will operate. +pub enum Scope { + /// Scope for current user. + User, + /// Scope for all users under this computer. + System, +} + +/// Check whether current process has administrative privilege. +/// +/// It usually means that checking whether current process is running as Administrator. +/// Return true if it is, otherwise false. +/// +/// Reference: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership +pub fn has_privilege() -> bool { + use windows_sys::core::BOOL; + use windows_sys::Win32::Foundation::HANDLE; + use windows_sys::Win32::Security::{ + SECURITY_NT_AUTHORITY, PSID, AllocateAndInitializeSid, CheckTokenMembership, FreeSid + }; + use windows_sys::Win32::System::SystemServices:: { + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS + }; + + let nt_authority = SECURITY_NT_AUTHORITY.clone(); + let mut administrators_group: PSID = PSID::default(); + let success: BOOL = unsafe { + AllocateAndInitializeSid( + &nt_authority, + 2, + SECURITY_BUILTIN_DOMAIN_RID as u32, + DOMAIN_ALIAS_RID_ADMINS as u32, + 0, + 0, + 0, + 0, + 0, + 0, + &mut administrators_group, + ) + }; + + if success == 0 { + panic!("Win32 AllocateAndInitializeSid() failed"); + } + + let mut is_member: BOOL = BOOL::default(); + let success: BOOL = unsafe { + CheckTokenMembership( + HANDLE::default(), + administrators_group, + &mut is_member, + ) + }; + + unsafe { + FreeSid(administrators_group); + } + + if success == 0 { + panic!("Win32 CheckTokenMembership() failed"); + } + + is_member != 0 +} + +// region: File Extension + +/// The struct representing an file extension which must start with dot (`.`) +/// and followed by at least one arbitrary characters. +pub struct FileExt { + /// The body of file extension (excluding dot). + inner: String, +} + +impl FileExt { + pub fn new(file_ext: &str) -> Result { + static RE: LazyLock = LazyLock::new(|| Regex::new(r"^\.([^\.]+)$").unwrap()); + match RE.captures(file_ext) { + Some(v) => Ok(Self { + inner: v[1].to_string(), + }), + None => Err(ParseFileExtError::new()), + } + } +} + +/// The error occurs when try parsing string into FileExt. +#[derive(Debug, TeError)] +#[error("given file extension name is illegal")] +pub struct ParseFileExtError {} + +impl ParseFileExtError { + fn new() -> Self { + Self {} + } +} + +impl Display for FileExt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, ".{}", self.inner) + } +} + +impl FromStr for FileExt { + type Err = ParseFileExtError; + + fn from_str(s: &str) -> Result { + Self::new(s) + } +} + +// endregion + +// region: Executable Resource + +/// The struct representing an Windows executable resources path like +/// `path_to_file.exe,1`. +pub struct ExecRc { + /// The path to binary for finding resources. + binary: PathBuf, + /// The inner index of resources. + index: u32, +} + +impl ExecRc { + pub fn new(res_str: &str) -> Result { + static RE: LazyLock = LazyLock::new(|| Regex::new(r"^([^,]+),([0-9]+)$").unwrap()); + let caps = RE.captures(res_str); + if let Some(caps) = caps { + let binary = PathBuf::from_str(&caps[1])?; + let index = caps[2].parse::()?; + Ok(Self { binary, index }) + } else { + Err(ParseExecRcError::NoCapture) + } + } +} + +/// The error occurs when try parsing string into ExecRc. +#[derive(Debug, TeError)] +#[error("given string is not a valid executable resource string")] +pub enum ParseExecRcError { + /// Given string is not matched with format. + NoCapture, + /// Fail to convert executable part into path. + BadBinaryPath(#[from] std::convert::Infallible), + /// Fail to convert index part into valid number. + BadIndex(#[from] std::num::ParseIntError), +} + +impl FromStr for ExecRc { + type Err = ParseExecRcError; + + fn from_str(s: &str) -> Result { + ExecRc::new(s) + } +} + +impl Display for ExecRc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{},{}", self.binary.to_str().unwrap(), self.index) + } +} + +// endregion + +// /// The struct representing an Windows acceptable Prgram ID, +// /// which looks like `Program.Document.2` +// pub struct ProgId { +// inner: String, +// } + +// impl ProgId { +// pub fn new(prog_id: &str) -> Self { +// Self { +// inner: prog_id.to_string(), +// } +// } +// } + +// region: Program + +// /// The struct representing a complete Win32 program. +// pub struct Program { +// file_exts: Vec, +// } + +// impl Program { +// /// Create a program descriptor. +// pub fn new() -> Self { +// Self { +// file_exts: Vec::new(), +// } +// } +// } + +// impl Program { +// /// Register program in this computer +// pub fn register(&self, kind: RegisterKind) -> Result<(), Error> { +// todo!("pretend to register >_<...") +// } + +// /// Unregister program from this computer. +// pub fn unregister(&self) -> Result<(), Error> { +// todo!("pretend to unregister >_<...") +// } +// } + +// impl Program { +// /// Query file extension infos which this program want to associate with. +// pub fn query(&self) -> Result<(), Error> { +// todo!("pretend to query >_<...") +// } +// } + +// endregion diff --git a/wfassoc/src/program.rs b/wfassoc/src/program.rs index 8166235..9253c27 100644 --- a/wfassoc/src/program.rs +++ b/wfassoc/src/program.rs @@ -1,35 +1,2 @@ use super::error::{Error, Result}; use super::components::*; - -/// The struct representing a complete Win32 program. -pub struct Program { - file_exts: Vec, -} - -impl Program { - /// Create a program descriptor. - pub fn new() -> Self { - Self { - file_exts: Vec::new(), - } - } -} - -impl Program { - /// Register program in this computer - pub fn register(&self, kind: RegisterKind) -> Result<()> { - todo!("pretend to register >_<...") - } - - /// Unregister program from this computer. - pub fn unregister(&self) -> Result<()> { - todo!("pretend to unregister >_<...") - } -} - -impl Program { - /// Query file extension infos which this program want to associate with. - pub fn query(&self) -> Result<()> { - todo!("pretend to query >_<...") - } -} diff --git a/wfassoc_exec/src/main.rs b/wfassoc_exec/src/main.rs index fb65262..8a566d3 100644 --- a/wfassoc_exec/src/main.rs +++ b/wfassoc_exec/src/main.rs @@ -1,8 +1,22 @@ use clap::{Parser, Subcommand}; use std::process; -use wfassoc::{Program, RegisterKind, WfError}; +use wfassoc::{Error as WfError}; +use thiserror::Error as TeError; -type Result = std::result::Result; +// region: Basic Types + +/// All errors occurs in this executable. +#[derive(TeError, Debug)] +enum Error { + /// Error from wfassoc core. + #[error("{0}")] + Core(#[from] WfError) +} + +/// Result type used in this executable. +type Result = std::result::Result; + +// endregion // region: Command Line Parser @@ -35,14 +49,14 @@ enum ForTarget { System, } -impl From for RegisterKind { - fn from(target: ForTarget) -> Self { - match target { - ForTarget::User => RegisterKind::User, - ForTarget::System => RegisterKind::System, - } - } -} +// impl From for RegisterKind { +// fn from(target: ForTarget) -> Self { +// match target { +// ForTarget::User => RegisterKind::User, +// ForTarget::System => RegisterKind::System, +// } +// } +// } #[derive(Subcommand)] enum Commands { @@ -62,19 +76,23 @@ enum Commands { // region: Correponding Runner fn run_register(cli: Cli) -> Result<()> { - let program = Program::new(); - let kind: RegisterKind = cli.for_which.into(); - program.register(kind) + // let program = Program::new(); + // let kind: RegisterKind = cli.for_which.into(); + // program.register(kind)?; + Ok(()) } fn run_unregister(cli: Cli) -> Result<()> { - let program = Program::new(); - program.unregister() + // let program = Program::new(); + // program.unregister()?; + Ok(()) } fn run_query(cli: Cli) -> Result<()> { - let program = Program::new(); - program.query() + // let program = Program::new(); + // program.query()?; + println!("Has privilege: {}", wfassoc::has_privilege()); + Ok(()) } // endregion