known_folders/win.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
// src/win.rs
//
// Copyright (c) 2023 Ryan Lopopolo <rjl@hyperbo.la>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE> or
// <http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT>
// or <http://opensource.org/licenses/MIT>, at your option. All files in the
// project carrying such notice may not be copied, modified, or distributed
// except according to those terms.
use core::mem::size_of;
use core::ptr;
use core::slice;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::path::PathBuf;
use windows_sys::Win32::{
Foundation::{E_FAIL, E_INVALIDARG, S_OK},
Globalization::lstrlenW,
UI::Shell::{SHGetKnownFolderPath, KF_FLAG_DEFAULT},
};
mod ffi;
mod known_folder;
pub use known_folder::KnownFolder;
/// Retrieve the full path of a known folder identified by the folder's
/// [`KNOWNFOLDERID`].
///
/// A safe wrapper around the [`SHGetKnownFolderPath`] Win32 API function on
/// Windows.
///
/// See [`KnownFolder`] for the types of known folders this function can
/// retrieve.
///
/// # Errors
///
/// If an error occurs when calling the underlying Windows APIs or the given
/// Known Folder ID is not present on the system (for example, if the ID was
/// introduced in a newer OS version), [`None`] is returned.
///
/// # Examples
///
/// ```
/// use known_folders::{get_known_folder_path, KnownFolder};
///
/// let profile_dir = get_known_folder_path(KnownFolder::Profile);
/// ```
///
/// [`KNOWNFOLDERID`]: KnownFolder
/// [`SHGetKnownFolderPath`]: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
#[must_use]
#[allow(clippy::match_same_arms)]
#[allow(clippy::cast_possible_wrap)]
pub fn get_known_folder_path(known_folder: KnownFolder) -> Option<PathBuf> {
// This guard ensures `CoTaskMemFree` is always called after invoking
// `SHGetKnownFolderPath`, which is required regardless of the return
// value.
//
// See `ppszPath` out parameter description:
//
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath#parameters
let mut guard = ffi::Guard::default();
// Upstream docs:
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
//
// `SHGetKnownFolderPath` replaces `SHGetFolderPathW` as of Windows Vista:
//
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetfolderpathw
//
// SAFETY: this invocation meets the preconditions defined in the API
// documentation:
//
// - `rfid` is a reference to a known folder ID, provided by `windows-sys`.
// - `dwFlags` can be `0` per the documentation, we have no special retrieval
// requirements, so use the default defined in `windows-sys`.
// The `KNOWN_FOLDER_FLAG` enum is documented here:
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/ne-shlobj_core-known_folder_flag
// - `hToken` is "an access token that represents a particular user. If this
// parameter is `NULL`, which is the most common usage, the function
// requests the known folder for the current user. We want the known folder
// for the current user, so use `ptr::null_mut()`.
// - `ppszPath` is an out parameter and should be a NULL pointer to a PWSTR.
//
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath#parameters
match unsafe {
SHGetKnownFolderPath(
known_folder.to_guid(),
KF_FLAG_DEFAULT as _,
ptr::null_mut(),
guard.as_out_ppszPath(),
)
} {
S_OK => {
let path_ptr = guard.as_pcwstr();
// SAFETY: on success, the out pointer is guaranteed to be a valid,
// NUL-terminated wide string.
//
// > When `SHGetKnownFolderPath` returns, contains the address of a
// > pointer to a null-terminated Unicode string that specifies the
// > path of the known folder
let len = unsafe {
let len = lstrlenW(path_ptr);
usize::try_from(len).ok()?
};
// SAFETY: `path_ptr` is valid for `len` "characters" in a single
// string allocation, per windows-sys APIs. "Characters" are `WCHAR`
// values. Additionally, `lstrlenW` returns `i32` on 64-bit
// platforms. The `match` below guarantees the size of the
// allocation is no larger than `isize::MAX`.
let path = unsafe {
match isize::try_from(len) {
Ok(len) if len < 0 => return None,
Ok(len) if len.checked_mul(size_of::<u16>() as isize).is_some() => {}
Ok(_) | Err(_) => return None,
};
// NOTE: this slice must go out of scope before `guard` above is
// dropped. This invariant holds since the guard is constructed
// outside the scope of this `match` block.
slice::from_raw_parts(path_ptr, len)
};
let os_str = OsString::from_wide(path);
Some(os_str.into())
}
// Expected return codes. See:
//
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath#return-value
E_FAIL | E_INVALIDARG => None,
// Unexpected return code.
_ => None,
}
}