Skip to content

Commit 1c7a34e

Browse files
committed
Get UserProgramFiles with KF_FLAG_DONT_VERIFY for test
In the `gix-path` tests, `ProgramFilesPaths::obtain_envlessly()` gets the expected user program files directory path `pf_user` by retrieving the path of the `UserProgramFiles` known folder. This is usually the best way to get that path. But like the other program files paths, we only use the known folders system to get this path in the test suite. In the code under test, we obtain this and other program files paths from environment variables, so that production code remains reasonably simple, without excessive or long-building dependencies. However, unlike the other program files paths, there is no environment variable specifically for the user program files path. Instead, there is an environment variable for the user's local application data directory, `LocalAppData`. It is expected that the user's program files path, if any, is the `Programs` subdirectory of the directory given by the `LocalAppData` environment variable. For the global (systemwide) program files directories, it is not safe to guess their locations if they are absent. This is because they are expected to be subdirectories of the root directory of the system drive, and limited user accounts are capable of creating new directories in the root directory of the system drive on most Windows systems. (See CVE-2024-43785 on how this can go wrong.) However, for the user's program files directory, it is safe to guess its location, so long as one guesses it inside a directory the user is expected to have full control of and that other users without administrative powers are expected not to have the ability to create directories inside. That is the case when guessing that it is the `Programs` subdirectory of the user's local application data directory. Therefore, to support Git for Windows installations in the user's program files directory even when `git.exe` cannot be found in a `PATH` search, we shall search in the default locations where such an installation would be. We shall do this without first checking if the per-user program files directory they are inside exists yet, since that extra step is not necessary for safety. (Also, unlike nonexistent global program files directories, which are never likely to come into existence short of installing a different version of the operating system, a nonexistent user program files directory is simply one that has not yet been created due to no need for it. It is created the first time an application tries to install there.) However, this raises the question of how we test that this path (to where the user program files directory either alredy exists or would be created) is found correctly by comparing the environment-variables-based result in the code under test to the known-folders-based findings in the test. Two approaches come to mind: 1. We could use the known folders system to find a directory higher up, and then append the further component(s). But this would effectively repeat logic between the code and their tests. It would also make the test suite unable to identify failures if, in some future version of the operating system, the user program files directory is practically able to exist an other places. 2. We could retrieve the `UserProgramFiles` known folder entry. This requires passing the `KF_FLAG_DONT_VERIFY` known folders flag, without which we get an error if the directory doesn't exist yet. However, we have been accessing known folders information through the `known-folders` library, which doesn't allow known folders flags other than `KF_FLAG_DEFAULT` to be passed to `SHGetKnownFolderPath` Windows API function. Here, we go with way (2). The `windows` crate is already a test dependency of `gix-path`. This uses it to call that function with an explicit flag argument, so we can pass `KF_FLAG_DONT_VERIFY`.
1 parent c9ff0ac commit 1c7a34e

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

gix-path/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,9 @@ serial_test = { version = "3.1.0", default-features = false }
3030

3131
[target.'cfg(windows)'.dev-dependencies]
3232
known-folders = "1.3.1"
33-
windows = { version = "0.61.3", features = ["Win32_System_Threading"] }
33+
windows = { version = "0.61.3", features = [
34+
"Win32_System_Com",
35+
"Win32_System_Threading",
36+
"Win32_UI_Shell",
37+
] }
3438
winreg = "0.55.0"

gix-path/src/env/git/tests.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@ use std::path::Path;
33
#[cfg(windows)]
44
mod locations {
55
use std::{
6-
ffi::{OsStr, OsString},
6+
ffi::{c_void, OsStr, OsString},
77
io::ErrorKind,
8+
os::windows::ffi::OsStringExt,
89
path::{Path, PathBuf},
910
};
1011

1112
use known_folders::{get_known_folder_path, KnownFolder};
1213
use windows::{
13-
core::{Result as WindowsResult, BOOL},
14-
Win32::System::Threading::{GetCurrentProcess, IsWow64Process},
14+
core::{Result as WindowsResult, BOOL, GUID, PWSTR},
15+
Win32::{
16+
System::{
17+
Com::CoTaskMemFree,
18+
Threading::{GetCurrentProcess, IsWow64Process},
19+
},
20+
UI::Shell::{FOLDERID_UserProgramFiles, SHGetKnownFolderPath, KF_FLAG_DONT_VERIFY, KNOWN_FOLDER_FLAG},
21+
},
1522
};
1623
use winreg::{
1724
enums::{HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE},
@@ -361,6 +368,32 @@ mod locations {
361368
);
362369
}
363370

371+
/// Owner of a `PWSTR` that must be freed with `CoTaskMemFree`.
372+
struct CoStr {
373+
pwstr: PWSTR,
374+
}
375+
376+
impl CoStr {
377+
fn new(pwstr: PWSTR) -> Self {
378+
Self { pwstr }
379+
}
380+
}
381+
382+
impl Drop for CoStr {
383+
fn drop(&mut self) {
384+
unsafe { CoTaskMemFree(Some(self.pwstr.as_ptr().cast::<c_void>())) };
385+
}
386+
}
387+
388+
fn get_known_folder_path_with_flag(id: GUID, flag: KNOWN_FOLDER_FLAG) -> WindowsResult<PathBuf> {
389+
unsafe {
390+
SHGetKnownFolderPath(&id, flag, None)
391+
.map(CoStr::new)
392+
.map(|costr| OsString::from_wide(costr.pwstr.as_wide()))
393+
.map(PathBuf::from)
394+
}
395+
}
396+
364397
#[derive(Clone, Copy, Debug)]
365398
enum PlatformBitness {
366399
Is32on32,
@@ -461,8 +494,7 @@ mod locations {
461494
})
462495
.ok();
463496

464-
// FIXME: This lacks KF_FLAG_DONT_VERIFY, so it errors out if the folder hasn't been created.
465-
let pf_user = get_known_folder_path(KnownFolder::UserProgramFiles)
497+
let pf_user = get_known_folder_path_with_flag(FOLDERID_UserProgramFiles, KF_FLAG_DONT_VERIFY)
466498
.expect("The path where the user's local Programs folder is, or would be created, is known");
467499

468500
Self {

0 commit comments

Comments
 (0)