Skip to content

Commit

Permalink
Generalize support for access rights in windows-registry (#3482)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennykerr authored Feb 12, 2025
1 parent b8e3197 commit 7269206
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 35 deletions.
6 changes: 3 additions & 3 deletions crates/libs/registry/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ fn main() -> Result<()> {
let key = CURRENT_USER
.options()
.read(true)
.write(true)
.create(true)
.read()
.write()
.create()
.transaction(&tx)
.open("software\\windows-rs")?;
Expand Down
8 changes: 2 additions & 6 deletions crates/libs/registry/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@ pub struct Key(pub(crate) HKEY);
impl Key {
/// Creates a registry key. If the key already exists, the function opens it.
pub fn create<T: AsRef<str>>(&self, path: T) -> Result<Self> {
self.options()
.read(true)
.write(true)
.create(true)
.open(path)
self.options().read().write().create().open(path)
}

/// Opens a registry key.
pub fn open<T: AsRef<str>>(&self, path: T) -> Result<Self> {
self.options().read(true).open(path)
self.options().read().open(path)
}

/// Creates an `OpenOptions` object for the registry key.
Expand Down
49 changes: 25 additions & 24 deletions crates/libs/registry/src/open_options.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use super::*;

/// Options and flags used to configure how a registry key is opened.
#[derive(Debug)]
pub struct OpenOptions<'a> {
parent: &'a Key,
read: bool,
write: bool,
access: u32,
create: bool,
transaction: Option<&'a Transaction>,
}
Expand All @@ -13,28 +13,33 @@ impl<'a> OpenOptions<'a> {
pub(crate) fn new(parent: &'a Key) -> Self {
Self {
parent,
read: false,
write: false,
access: 0,
create: false,
transaction: None,
}
}

/// Sets the option for read access.
pub fn read(&mut self, read: bool) -> &mut Self {
self.read = read;
pub fn read(&mut self) -> &mut Self {
self.access |= KEY_READ;
self
}

/// Sets the option for write access.
pub fn write(&mut self, write: bool) -> &mut Self {
self.write = write;
pub fn write(&mut self) -> &mut Self {
self.access |= KEY_WRITE;
self
}

/// Sets additional access rights.
pub fn access(&mut self, access: u32) -> &mut Self {
self.access |= access;
self
}

/// Sets the option to create a new registry key, or open it if it already exists.
pub fn create(&mut self, create: bool) -> &mut Self {
self.create = create;
pub fn create(&mut self) -> &mut Self {
self.create = true;
self
}

Expand All @@ -46,16 +51,6 @@ impl<'a> OpenOptions<'a> {

/// Opens a registry key with the options provided by `self`.
pub fn open<T: AsRef<str>>(&self, path: T) -> Result<Key> {
let mut flags = 0;

if self.read {
flags |= KEY_READ;
}

if self.write {
flags |= KEY_WRITE;
}

let mut handle = null_mut();

let result = unsafe {
Expand All @@ -67,7 +62,7 @@ impl<'a> OpenOptions<'a> {
0,
null(),
REG_OPTION_NON_VOLATILE,
flags,
self.access,
null(),
&mut handle,
null_mut(),
Expand All @@ -79,7 +74,7 @@ impl<'a> OpenOptions<'a> {
self.parent.0,
pcwstr(path).as_ptr(),
0,
flags,
self.access,
&mut handle,
transaction.0,
null(),
Expand All @@ -92,13 +87,19 @@ impl<'a> OpenOptions<'a> {
0,
null(),
REG_OPTION_NON_VOLATILE,
flags,
self.access,
null(),
&mut handle,
null_mut(),
)
} else {
RegOpenKeyExW(self.parent.0, pcwstr(path).as_ptr(), 0, flags, &mut handle)
RegOpenKeyExW(
self.parent.0,
pcwstr(path).as_ptr(),
0,
self.access,
&mut handle,
)
}
};

Expand Down
3 changes: 3 additions & 0 deletions crates/tests/misc/registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ features = ["Win32_System_Registry"]

[dependencies.windows-strings]
workspace = true

[dependencies.regex]
version = "1.7"
82 changes: 82 additions & 0 deletions crates/tests/misc/registry/tests/access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use windows_registry::*;
use windows_result::*;
use windows_sys::Win32::{Foundation::*, System::Registry::*};

#[test]
fn access() {
let test_key = "software\\windows-rs\\tests\\access";
_ = CURRENT_USER.remove_tree(test_key);

let key = CURRENT_USER
.options()
.create()
.access(KEY_WRITE)
.open(test_key)
.unwrap();

key.set_u64("u64", 123u64).unwrap();

assert_eq!(
key.get_u64("u64").unwrap_err().code(),
HRESULT::from_win32(ERROR_ACCESS_DENIED)
);

let key = CURRENT_USER
.options()
.access(KEY_READ)
.open(test_key)
.unwrap();

assert_eq!(key.get_u64("u64").unwrap(), 123u64);

assert_eq!(
key.set_u64("u64", 123u64).unwrap_err().code(),
HRESULT::from_win32(ERROR_ACCESS_DENIED)
);
}

#[test]
fn flags() {
// `OpenOptions` defaults to no access
let mut options = CURRENT_USER.options();
assert_eq!(get_access(&options), 0);

// `read` and `write` equate to `KEY_READ` and `KEY_WRITE`
options.read().write();
assert_eq!(get_access(&options), KEY_READ | KEY_WRITE);

// Combine additional access rights
options.access(KEY_WOW64_32KEY);
assert_eq!(get_access(&options), KEY_WOW64_32KEY | KEY_READ | KEY_WRITE);

// Start with specific access rights
let mut options = CURRENT_USER.options();
options.access(KEY_WOW64_32KEY | KEY_QUERY_VALUE);
assert_eq!(get_access(&options), KEY_WOW64_32KEY | KEY_QUERY_VALUE);

// `read` is additive
options.read();
assert_eq!(
get_access(&options),
KEY_WOW64_32KEY | KEY_QUERY_VALUE | KEY_READ
);

// `write` is additive
options.write();
assert_eq!(
get_access(&options),
KEY_WOW64_32KEY | KEY_QUERY_VALUE | KEY_READ | KEY_WRITE
);
}

fn get_access(options: &OpenOptions) -> u32 {
regex::Regex::new(r#"access:\s*(\d+)"#)
.unwrap()
.captures(&format!("{options:?}"))
.unwrap()
.get(1)
.unwrap()
.as_str()
.parse()
.unwrap()
}
32 changes: 32 additions & 0 deletions crates/tests/misc/registry/tests/read_write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use windows_registry::*;
use windows_result::*;
use windows_sys::Win32::Foundation::*;

#[test]
fn read_write() {
let test_key = "software\\windows-rs\\tests\\read_write";
_ = CURRENT_USER.remove_tree(test_key);

let key = CURRENT_USER
.options()
.create()
.write()
.open(test_key)
.unwrap();

key.set_u64("u64", 123u64).unwrap();

assert_eq!(
key.get_u64("u64").unwrap_err().code(),
HRESULT::from_win32(ERROR_ACCESS_DENIED)
);

let key = CURRENT_USER.options().read().open(test_key).unwrap();

assert_eq!(key.get_u64("u64").unwrap(), 123u64);

assert_eq!(
key.set_u64("u64", 123u64).unwrap_err().code(),
HRESULT::from_win32(ERROR_ACCESS_DENIED)
);
}
4 changes: 2 additions & 2 deletions crates/tests/misc/registry/tests/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ fn create_with_transaction() {
let tx_key = CURRENT_USER
.options()
.transaction(&tx)
.read(true)
.write(true)
.read()
.write()
.open(test_key)
.unwrap();

Expand Down

0 comments on commit 7269206

Please sign in to comment.