feat(windows): add ExpandString for environment variable expansion
Implement ExpandString struct to handle Windows environment variable expansion with proper error handling. Also reorganize windows-sys dependencies and improve icon loading error messages.
This commit is contained in:
@ -9,11 +9,12 @@ license = "SPDX:MIT"
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
windows-sys = { version = "0.60.2", features = [
|
||||
"Win32_Security",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_Security",
|
||||
"Win32_System_Environment",
|
||||
"Win32_System_Registry",
|
||||
"Win32_System_SystemServices",
|
||||
] }
|
||||
winreg = { version = "0.55.0", features = ["transactions"] }
|
||||
widestring = "1.2.1"
|
||||
|
||||
@ -2,20 +2,139 @@
|
||||
//! These features are not implemented in any crates (as I known scope)
|
||||
//! and should be manually implemented for our file association use.
|
||||
|
||||
use regex::Regex;
|
||||
use std::fmt::Display;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
use thiserror::Error as TeError;
|
||||
use widestring::WideCString;
|
||||
use widestring::{WideCStr, WideCString, WideChar};
|
||||
use windows_sys::Win32::UI::Shell::ExtractIconExW;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{DestroyIcon, HICON};
|
||||
|
||||
// region: Expand String
|
||||
|
||||
/// Error occurs when creating Expand String.
|
||||
#[derive(Debug, TeError)]
|
||||
#[error("given string is not an expand string")]
|
||||
pub struct BadExpandStrError {}
|
||||
|
||||
impl BadExpandStrError {
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error occurs when expand Expand String
|
||||
#[derive(Debug, TeError)]
|
||||
#[error("error occurs when expanding expand string")]
|
||||
pub enum ExpandEnvVarError {
|
||||
/// Given string has embedded NUL.
|
||||
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
|
||||
/// Error occurs when executing Win32 expand function.
|
||||
ExpandFunction,
|
||||
/// Error occurs when int type casting.
|
||||
BadIntCast(#[from] std::num::TryFromIntError),
|
||||
/// Integeral arithmatic downflow.
|
||||
Underflow,
|
||||
/// The C-format string is invalid.
|
||||
BadString(#[from] widestring::error::NulError<WideChar>),
|
||||
/// The encoding of string is invalid.
|
||||
BadEncoding(#[from] widestring::error::Utf16Error),
|
||||
/// Some environment vairable are not expanded.
|
||||
NoEnvVar,
|
||||
}
|
||||
|
||||
/// The struct representing an Expand String,
|
||||
/// which contain environment variable in string,
|
||||
/// like `%LOCALAPPDATA%\SomeApp.exe`.
|
||||
pub struct ExpandString {
|
||||
inner: String,
|
||||
}
|
||||
|
||||
impl ExpandString {
|
||||
const VAR_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"%[a-zA-Z0-9_]+%").unwrap());
|
||||
}
|
||||
|
||||
impl ExpandString {
|
||||
/// Create a new expand string
|
||||
pub fn new(s: &str) -> Result<Self, BadExpandStrError> {
|
||||
Self::from_str(s)
|
||||
}
|
||||
|
||||
/// Expand the variables located in this string
|
||||
/// and produce the final usable string.
|
||||
pub fn expand_string(&self) -> Result<String, ExpandEnvVarError> {
|
||||
use windows_sys::Win32::System::Environment::ExpandEnvironmentStringsW;
|
||||
|
||||
// Fetch the size of expand result
|
||||
let source = WideCString::from_str(self.inner.as_str())?;
|
||||
let size = unsafe {
|
||||
ExpandEnvironmentStringsW(source.as_ptr(), Default::default(), 0)
|
||||
};
|
||||
if size == 0 {
|
||||
return Err(ExpandEnvVarError::ExpandFunction)
|
||||
}
|
||||
let size_no_nul = size.checked_sub(1).ok_or(ExpandEnvVarError::Underflow)?;
|
||||
|
||||
// Allocate buffer for it.
|
||||
let len: usize = size.try_into()?;
|
||||
let len_no_nul = len.checked_sub(1).ok_or(ExpandEnvVarError::Underflow)?;
|
||||
let mut buffer= vec![0; len];
|
||||
// Receive result
|
||||
let size = unsafe {
|
||||
ExpandEnvironmentStringsW(source.as_ptr(), buffer.as_mut_ptr(), size_no_nul)
|
||||
};
|
||||
if size == 0 {
|
||||
return Err(ExpandEnvVarError::ExpandFunction)
|
||||
}
|
||||
|
||||
// Cast result as Rust string
|
||||
let wstr = unsafe { WideCStr::from_ptr(buffer.as_ptr(), len_no_nul)? };
|
||||
let rv = wstr.to_string()?;
|
||||
|
||||
// If the final string still has environment variable,
|
||||
// we think we fail to expand it.
|
||||
if Self::VAR_RE.is_match(rv.as_str()) {
|
||||
Err(ExpandEnvVarError::NoEnvVar)
|
||||
} else {
|
||||
Ok(rv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ExpandString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ExpandString {
|
||||
type Err = BadExpandStrError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if Self::VAR_RE.is_match(s) {
|
||||
Ok(Self {
|
||||
inner: s.to_string(),
|
||||
})
|
||||
} else {
|
||||
Err(BadExpandStrError::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Icon
|
||||
|
||||
/// Error occurs when loading icon.
|
||||
#[derive(Debug, TeError)]
|
||||
#[error("error occurs when loading icon")]
|
||||
pub enum LoadIconError {
|
||||
EmbeddedNul(#[from] widestring::error::ContainsNul<widestring::WideChar>),
|
||||
Other,
|
||||
/// Given path has embedded NUL.
|
||||
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
|
||||
/// Error occurs when executing Win32 extract function.
|
||||
ExtractIcon,
|
||||
}
|
||||
|
||||
/// The size kind of loaded icon
|
||||
@ -50,13 +169,13 @@ impl Icon {
|
||||
};
|
||||
|
||||
if rv != 1 || icon.is_null() {
|
||||
Err(LoadIconError::Other)
|
||||
Err(LoadIconError::ExtractIcon)
|
||||
} else {
|
||||
Ok(Self { icon })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_raw(hicon: HICON) -> Self {
|
||||
pub unsafe fn from_raw(hicon: HICON) -> Self {
|
||||
Self { icon: hicon }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user