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]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
windows-sys = { version = "0.60.2", features = [
|
windows-sys = { version = "0.60.2", features = [
|
||||||
"Win32_Security",
|
|
||||||
"Win32_System_SystemServices",
|
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
"Win32_Security",
|
||||||
|
"Win32_System_Environment",
|
||||||
"Win32_System_Registry",
|
"Win32_System_Registry",
|
||||||
|
"Win32_System_SystemServices",
|
||||||
] }
|
] }
|
||||||
winreg = { version = "0.55.0", features = ["transactions"] }
|
winreg = { version = "0.55.0", features = ["transactions"] }
|
||||||
widestring = "1.2.1"
|
widestring = "1.2.1"
|
||||||
|
|||||||
@ -2,20 +2,139 @@
|
|||||||
//! These features are not implemented in any crates (as I known scope)
|
//! These features are not implemented in any crates (as I known scope)
|
||||||
//! and should be manually implemented for our file association use.
|
//! and should be manually implemented for our file association use.
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use thiserror::Error as TeError;
|
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::Shell::ExtractIconExW;
|
||||||
use windows_sys::Win32::UI::WindowsAndMessaging::{DestroyIcon, HICON};
|
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
|
// region: Icon
|
||||||
|
|
||||||
/// Error occurs when loading icon.
|
/// Error occurs when loading icon.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("error occurs when loading icon")]
|
#[error("error occurs when loading icon")]
|
||||||
pub enum LoadIconError {
|
pub enum LoadIconError {
|
||||||
EmbeddedNul(#[from] widestring::error::ContainsNul<widestring::WideChar>),
|
/// Given path has embedded NUL.
|
||||||
Other,
|
EmbeddedNul(#[from] widestring::error::ContainsNul<WideChar>),
|
||||||
|
/// Error occurs when executing Win32 extract function.
|
||||||
|
ExtractIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The size kind of loaded icon
|
/// The size kind of loaded icon
|
||||||
@ -50,13 +169,13 @@ impl Icon {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if rv != 1 || icon.is_null() {
|
if rv != 1 || icon.is_null() {
|
||||||
Err(LoadIconError::Other)
|
Err(LoadIconError::ExtractIcon)
|
||||||
} else {
|
} else {
|
||||||
Ok(Self { icon })
|
Ok(Self { icon })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_raw(hicon: HICON) -> Self {
|
pub unsafe fn from_raw(hicon: HICON) -> Self {
|
||||||
Self { icon: hicon }
|
Self { icon: hicon }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user