From 6dd4ce57a96c79557efae968768e23b828bac5ef Mon Sep 17 00:00:00 2001 From: Mikachu2333 Date: Sun, 30 Aug 2026 15:13:16 +0800 Subject: [PATCH] fix: stop env expansion from auto-creating `%USERPROFILE%` folders REG_EXPAND_SZ values referencing variables from a later registry hive (e.g. TEMP referencing USERPROFILE stored in Volatile Environment) kept literal %VAR% text, so shells auto-created a %USERPROFILE% folder in the working directory. Fall back to the process environment when the referenced variable is not loaded yet, and add a regression test using the real USERPROFILE so it passes on any machine. --- .../src/tty/windows/environment.rs | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/nebula_terminal/src/tty/windows/environment.rs b/nebula_terminal/src/tty/windows/environment.rs index ffef195..0b0f19a 100644 --- a/nebula_terminal/src/tty/windows/environment.rs +++ b/nebula_terminal/src/tty/windows/environment.rs @@ -131,7 +131,7 @@ impl EnvironmentState { environment.remove_normalized(name); } for hive in snapshot.hives { - apply_registry_hive(&mut environment, &hive); + apply_registry_hive(&mut environment, &self.inherited, &hive); } for entry in overrides.entries.into_values() { environment.insert(entry.name, entry.value); @@ -186,8 +186,16 @@ struct RegistryValue { kind: RegistryValueKind, } -fn apply_registry_hive(environment: &mut CaseInsensitiveEnv, values: &[RegistryValue]) { - // REG_SZ 先落表,随后 REG_EXPAND_SZ 才能引用同一 hive 中已经载入的变量。 +/// `inherited` 是删除注册表键之前的进程原始环境,用作展开兜底:`USERPROFILE` +/// 等登录期变量只存于 Volatile Environment,在该 hive 落表之前展开 +/// `%USERPROFILE%` 引用必须回退到它,否则子进程拿到字面量相对路径。 +fn apply_registry_hive( + environment: &mut CaseInsensitiveEnv, + inherited: &CaseInsensitiveEnv, + values: &[RegistryValue], +) { + // REG_SZ 先落表,随后 REG_EXPAND_SZ 才能引用同一 hive 中已经载入的变量, + // 引用尚未落表的 hive 间变量时回退 `inherited`。 for expand_pass in [false, true] { for registry_value in values { let is_path = is_path_variable(®istry_value.name); @@ -199,7 +207,7 @@ fn apply_registry_hive(environment: &mut CaseInsensitiveEnv, values: &[RegistryV } let value = if is_expand { - expand_environment_variables(®istry_value.value, environment) + expand_environment_variables(®istry_value.value, environment, inherited) } else { registry_value.value.clone() }; @@ -215,14 +223,18 @@ fn apply_registry_hive(environment: &mut CaseInsensitiveEnv, values: &[RegistryV } } -fn expand_environment_variables(input: &str, environment: &CaseInsensitiveEnv) -> String { +fn expand_environment_variables( + input: &str, + environment: &CaseInsensitiveEnv, + inherited: &CaseInsensitiveEnv, +) -> String { let mut output = String::with_capacity(input.len()); let mut variable = None::; for character in input.chars() { if character == '%' { if let Some(name) = variable.take() { - if let Some(value) = environment.get(&name) { + if let Some(value) = environment.get(&name).or_else(|| inherited.get(&name)) { output.push_str(value); } else { output.push('%'); @@ -441,6 +453,34 @@ mod tests { assert_eq!(merged.get("SDK"), Some(r"C:\tools\sdk")); } + #[test] + fn expandable_values_fall_back_to_the_process_environment() { + // Volatile Environment 排在 HKCU\Environment 之后:TEMP 引用的 + // USERPROFILE 展开时还没落表,且已在 merge 开头从继承环境中删除, + // 必须回退进程启动环境,否则 pane 的 TEMP/TMP 变成字面量相对路径, + // shell 一写临时文件就在启动目录下建出 %USERPROFILE% 文件夹。 + // 用户目录随电脑而不同,直接从当前进程取真实 USERPROFILE,断言值 + // 也由它推导,避免把某台机器的用户名写进测试导致其他电脑失败。 + let profile = std::env::var_os("USERPROFILE") + .expect("USERPROFILE must be set in the test process") + .to_string_lossy() + .into_owned(); + let temp = format!(r"%USERPROFILE%\AppData\Local\Temp"); + let mut state = EnvironmentState::new(environment(&[("USERPROFILE", profile.as_str())])); + let merged = state.merge( + snapshot(vec![ + Vec::new(), + vec![expanded("TEMP", temp.as_str())], + vec![plain("USERPROFILE", profile.as_str())], + ]), + CaseInsensitiveEnv::default(), + ); + + let expected_temp = format!(r"{profile}\AppData\Local\Temp"); + assert_eq!(merged.get("TEMP"), Some(expected_temp.as_str())); + assert_eq!(merged.get("USERPROFILE"), Some(profile.as_str())); + } + #[test] fn path_stored_as_reg_sz_is_still_expanded() { let mut state = EnvironmentState::new(CaseInsensitiveEnv::default());