Giter Club home page Giter Club logo

winreg-rs's Introduction

winreg Github Actions Winreg on crates.io Winreg on docs.rs

Rust bindings to MS Windows Registry API. Work in progress.

Current features:

  • Basic registry operations:
    • open/create/delete/rename keys
    • load application hive from a file
    • read and write values
    • seamless conversion between REG_* types and rust primitives
      • String and OsString <= REG_SZ, REG_EXPAND_SZ or REG_MULTI_SZ
      • String, &str, OsString, &OsStr => REG_SZ
      • Vec<String>, Vec<OsString> <= REG_MULTI_SZ
      • Vec<String>, Vec<&str>, Vec<OsString>, Vec<&OsStr> => REG_MULTI_SZ
      • u32 <=> REG_DWORD
      • u64 <=> REG_QWORD
  • Iteration through key names and through values
  • Transactions
  • Transacted serialization of rust types into/from registry (only primitives, structures and maps for now)

Usage

Basic usage

# Cargo.toml
[dependencies]
winreg = "0.52"
use std::io;
use std::path::Path;
use winreg::enums::*;
use winreg::RegKey;

fn main() -> io::Result<()> {
    println!("Reading some system info...");
    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
    let cur_ver = hklm.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion")?;
    let pf: String = cur_ver.get_value("ProgramFilesDir")?;
    let dp: String = cur_ver.get_value("DevicePath")?;
    println!("ProgramFiles = {}\nDevicePath = {}", pf, dp);
    let info = cur_ver.query_info()?;
    println!("info = {:?}", info);
    let mt = info.get_last_write_time_system();
    println!(
        "last_write_time as windows_sys::Win32::Foundation::SYSTEMTIME = {}-{:02}-{:02} {:02}:{:02}:{:02}",
        mt.wYear, mt.wMonth, mt.wDay, mt.wHour, mt.wMinute, mt.wSecond
    );

    // enable `chrono` feature on `winreg` to make this work
    // println!(
    //     "last_write_time as chrono::NaiveDateTime = {}",
    //     info.get_last_write_time_chrono()
    // );

    println!("And now lets write something...");
    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let path = Path::new("Software").join("WinregRsExample1");
    let (key, disp) = hkcu.create_subkey(&path)?;

    match disp {
        REG_CREATED_NEW_KEY => println!("A new key has been created"),
        REG_OPENED_EXISTING_KEY => println!("An existing key has been opened"),
    }

    key.set_value("TestSZ", &"written by Rust")?;
    let sz_val: String = key.get_value("TestSZ")?;
    key.delete_value("TestSZ")?;
    println!("TestSZ = {}", sz_val);

    key.set_value("TestMultiSZ", &vec!["written", "by", "Rust"])?;
    let multi_sz_val: Vec<String> = key.get_value("TestMultiSZ")?;
    key.delete_value("TestMultiSZ")?;
    println!("TestMultiSZ = {:?}", multi_sz_val);

    key.set_value("TestDWORD", &1234567890u32)?;
    let dword_val: u32 = key.get_value("TestDWORD")?;
    println!("TestDWORD = {}", dword_val);

    key.set_value("TestQWORD", &1234567891011121314u64)?;
    let qword_val: u64 = key.get_value("TestQWORD")?;
    println!("TestQWORD = {}", qword_val);

    key.create_subkey("sub\\key")?;
    hkcu.delete_subkey_all(&path)?;

    println!("Trying to open nonexistent key...");
    hkcu.open_subkey(&path).unwrap_or_else(|e| match e.kind() {
        io::ErrorKind::NotFound => panic!("Key doesn't exist"),
        io::ErrorKind::PermissionDenied => panic!("Access denied"),
        _ => panic!("{:?}", e),
    });
    Ok(())
}

Iterators

use std::io;
use winreg::RegKey;
use winreg::enums::*;

fn main() -> io::Result<()> {
    println!("File extensions, registered in system:");
    for i in RegKey::predef(HKEY_CLASSES_ROOT)
        .enum_keys().map(|x| x.unwrap())
        .filter(|x| x.starts_with("."))
    {
        println!("{}", i);
    }

    let system = RegKey::predef(HKEY_LOCAL_MACHINE)
        .open_subkey("HARDWARE\\DESCRIPTION\\System")?;
    for (name, value) in system.enum_values().map(|x| x.unwrap()) {
        println!("{} = {:?}", name, value);
    }

    Ok(())
}

Transactions

# Cargo.toml
[dependencies]
winreg = { version = "0.52", features = ["transactions"] }
use std::io;
use winreg::RegKey;
use winreg::enums::*;
use winreg::transaction::Transaction;

fn main() -> io::Result<()> {
    let t = Transaction::new()?;
    let hkcu = RegKey::predef(HKEY_CURRENT_USER);
    let (key, _disp) = hkcu.create_subkey_transacted("Software\\RustTransaction", &t)?;
    key.set_value("TestQWORD", &1234567891011121314u64)?;
    key.set_value("TestDWORD", &1234567890u32)?;

    println!("Commit transaction? [y/N]:");
    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    input = input.trim_right().to_owned();
    if input == "y" || input == "Y" {
        t.commit()?;
        println!("Transaction committed.");
    }
    else {
        // this is optional, if transaction wasn't committed,
        // it will be rolled back on disposal
        t.rollback()?;

        println!("Transaction wasn't committed, it will be rolled back.");
    }

    Ok(())
}

Serialization

# Cargo.toml
[dependencies]
winreg = { version = "0.52", features = ["serialization-serde"] }
serde = "1"
serde_derive = "1"
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error::Error;
use winreg::enums::*;

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Coords {
    x: u32,
    y: u32,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Size {
    w: u32,
    h: u32,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Rectangle {
    coords: Coords,
    size: Size,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Test {
    t_bool: bool,
    t_u8: u8,
    t_u16: u16,
    t_u32: u32,
    t_u64: u64,
    t_usize: usize,
    t_struct: Rectangle,
    t_map: HashMap<String, u32>,
    t_string: String,
    #[serde(rename = "")] // empty name becomes the (Default) value in the registry
    t_char: char,
    t_i8: i8,
    t_i16: i16,
    t_i32: i32,
    t_i64: i64,
    t_isize: isize,
    t_f64: f64,
    t_f32: f32,
}

fn main() -> Result<(), Box<dyn Error>> {
    let hkcu = winreg::RegKey::predef(HKEY_CURRENT_USER);
    let (key, _disp) = hkcu.create_subkey("Software\\RustEncode")?;

    let mut map = HashMap::new();
    map.insert("".to_owned(), 0); // empty name becomes the (Default) value in the registry
    map.insert("v1".to_owned(), 1);
    map.insert("v2".to_owned(), 2);
    map.insert("v3".to_owned(), 3);

    let v1 = Test {
        t_bool: false,
        t_u8: 127,
        t_u16: 32768,
        t_u32: 123_456_789,
        t_u64: 123_456_789_101_112,
        t_usize: 1_234_567_891,
        t_struct: Rectangle {
            coords: Coords { x: 55, y: 77 },
            size: Size { w: 500, h: 300 },
        },
        t_map: map,
        t_string: "test 123!".to_owned(),
        t_char: 'a',
        t_i8: -123,
        t_i16: -2049,
        t_i32: 20100,
        t_i64: -12_345_678_910,
        t_isize: -1_234_567_890,
        t_f64: -0.01,
        t_f32: 3.15,
    };

    key.encode(&v1)?;

    let v2: Test = key.decode()?;
    println!("Decoded {:?}", v2);

    println!("Equal to encoded: {:?}", v1 == v2);
    Ok(())
}

winreg-rs's People

Contributors

aaasuoliweng avatar anderender avatar coolreader18 avatar dholbert avatar dnlmlr avatar gentoo90 avatar jyn514 avatar laegluin avatar neivv avatar philosobyte avatar poiru avatar rahimovir avatar sn99 avatar wlbf avatar zoumi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

winreg-rs's Issues

RegValue.to_string() unexpectedly escapes forward slashes

I ran into an issue that I think is unexpected (or at least undocumented) behavior. I needed to convert a RegValue to a String when enumerating values, and the following happened:

        Ok(regkey
            .enum_values()
            .map(|r| {
                let (key, val) = r.unwrap();
                (key, val.to_string())
            })
            .collect::<HashMap<_, _>>())

val ends up containing values like: "C:\foo\bar" rather than C:\foo\bar which is what the registry had.

If I instead use the trait FromRegValue then I get my expected C:\foo\bar:

        Ok(regkey
            .enum_values()
            .map(|r| {
                let (key, val) = r.unwrap();

                (
                    key,
                    String::from_reg_value(&val)
                        .expect("failed to convert regkey OsStr to String"),
                )
            })
            .collect::<HashMap<_, _>>())

Also, is there a way to convert from RegValue to String using Into<String> rather than the custom trait? That would be nice too. I could also be misunderstanding how to use this crate as well.

Serde

Add serde support

Unable to refer to or use `EncodeError`

EncodeError is a public type, however it is defined in the encoder module which is not public. It appear to be impossible for me to write code that uses this type, because there is no way to reference it.

Don't set the internet Settings correctly

Hello,

I want to set windows proxy as "socks=127.0.0.1" and port 1080, but it will be "http://socks=127.0.0.1".
How to fix it?

    let (key, _) = RegKey::predef(HKEY_CURRENT_USER).create_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?;
    if isOpen{
        key.set_value("ProxyEnable", &1u32)?;
        key.set_value("ProxyServer", &format!("{}:{}", "socks=127.0.0.1", 1080))?;
        key.set_value("ProxyOverride", &"<local>")?;
    }else{
        key.set_value("ProxyEnable", &0u32)?;
    }

RegKey::encode doesn't work with existing transaction

Problem Description

The encode function fails when the subkey was created using a transaction.
Example (self implements Serialize):

let t = Transaction::new().unwrap();
let key = RegKey::predef(HKEY_LOCAL_MACHINE)
    .open_subkey_transacted(Self::REGKEY_BASE, &t)
    .unwrap();
let (entry, _disp) = key
    .create_subkey_transacted(Self::REGKEY, &t)
    .unwrap();
entry.encode(self).unwrap();
t.commit().unwrap();

Error on the entry.encode line:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: IoError(Os { code: 6700, kind: Uncategorized, message: "[(translated) The transaction handle associated with this action is invalid.]" })'

Possible Solution

When looking at the code, it seems like the encode function simply creates a new Encoder using the Encoder::from_key function and commits the transaction at the end.
A possible solution (as far as I can tell) might be to simply add a RegKey::encode_transacted(value: &T, t: &Transaction) function that uses Encoder::new and doesn't call commit at the end.

If I'm not missing anything and you think this is a sensible solution, let me know and I can implement it and create a PR

Edit: I just saw that the Encoder::new function is private. This solution would therefore require either creating a new Encoder::from_key_transacted function or setting Encoder::new to pub(crate)

REG_LINK keys

How do I tell if a reg key is a REG_LINK?

I apologize if Iโ€™m missing something simple.

Thanks

Get reg type when reading one value

I hope I'm missing something obvious. How do I get the registry value type when I'm just reading one value? Thanks

e.g.

    let v: String = match key.get_value(value) {
        Ok(v) => v,
        Err(_e) => return Ok(()),
    };

pub use HKEY

Since all the HKEY_* enums are re-exported, would it be a good idea to re-export HKEY from winapi as well? My use case it that Iโ€™m defining a list of keys to look for, and since that is used in a lot of places Iโ€™m doing it globally like this:

const KEY_PATHS: &'static [(HKEY, &str); 3] = &[
    (HKEY_CURRENT_USER, "Software\\Foo"),
    (HKEY_LOCAL_MACHINE, "Software\\Foo"),
    (HKEY_LOCAL_MACHINE, "Software\\Wow6432Node\\Foo"),
];

But that requires me to declare winapi as a dependency and extern crate. It works fine, but is not the most convenient. It would save a lot of hassle if winreg can just re-export HKEY.

Unable to open key created with Sysinternals reghide.exe

Registry malicious trickery and Reghide.exe: https://www.tripwire.com/state-of-security/mitre-framework/evade-detection-hiding-registry/

Key is created here on x64 Windows: HKCU\Software\Classes\VirtualStore\MACHINE\SOFTWARE\WOW6432Node\Systems Internals\Canโ€™t touch me!

I can use error catching with a match statement to find this evil but would love to be able to open that and keys like it and read all values.

If I can help in any way with this crate I would love to. Just have never worked with crates and Rust Win API access directly either.

Thanks

[Question] Access 32bits section of registry?

I'm trying to access a key which is not under WOW6432Node from a 64 bits application.

When doing

let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
hklm.open_subkey("SOFTWARE\\MyKey")?;

It's failing with "The system cannot find the file specified.".

I've workaround this before in another toy project by compiling for 32 bits, but this time, we don't want to do this.

Is there a way to access the 32 bits section?

Short Help with Strings

Hello,

i have following Iterator:

 for (name, value) in subkey.enum_values().map(|x| x.unwrap())
{
    	println!("{} = {:?}", name, value);
}

And i want value as an String.
Thx

Serde decoding fails with subkeys

When decoding via the serialization-serde feature, it fails with DecodeNotImplemented("deserialize_any for keys") even though I'm only interested in certain values (eg. AutoColorization only in this case).

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct DesktopReg {
  AutoColorization: u32,
}

pub fn decode_example() -> Result<DesktopReg> {
  let hkcu = RegKey::predef(HKEY_CURRENT_USER);
  let desktop_key = hkcu.open_subkey_with_flags("Control Panel\\Desktop", KEY_READ)?;
  
  let decoded: DesktopReg = desktop_key.decode()?;
  Ok(decoded)
}

example for mock key to test gets?

is possible to add example on a way for mock a key to test functions? ex i make a key to replicate a key with vals. i want to test function return expected data. not sure how to test other than live which may need admin depend on key lookup not sure how to point a function to made up key vs real

OsString values are incorrectly NULL-terminated

According to https://doc.rust-lang.org/std/ffi/struct.OsString.html, OsString objects should not be NULL terminated:

OsString and OsStr bridge this gap by simultaneously representing Rust and platform-native string values, and in particular allowing a Rust string to be converted into an โ€œOSโ€ string with no cost if possible. A consequence of this is that OsString instances are not NUL terminated; in order to pass to e.g., Unix system call, you should create a CStr.

However, winreg produces strings that have a NULL terminator:

use std::io;
use std::ffi::OsString;
use winreg::enums::*;
use winreg::RegKey;

fn main() -> io::Result<()> {
    println!("Reading some system info...");
    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
    let cur_ver = hklm.open_subkey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion")?;
    let pf: OsString = cur_ver.get_value("ProgramFilesDir")?;
    println!("Program files dir");
    use std::os::windows::ffi::OsStrExt;
    for c in pf.encode_wide() {
        print!(" {:02x}", c);
    }
    println!();

    Ok(())
}

Running this produces:

โฏ cargo run
   Compiling winreg-test v0.1.0 (C:\Users\smcro\Code\Ion\winreg-test)
    Finished dev [unoptimized + debuginfo] target(s) in 0.45s
     Running `target\debug\winreg-test.exe`
Reading some system info...
Program files dir
 43 3a 5c 50 72 6f 67 72 61 6d 20 46 69 6c 65 73 00
โฏ

REG_NONE type causes deserialization abort

I'm doing something somewhat simple as enumerating the list of installed apps

let uninstall_key = root
    .open_subkey_with_flags(path, KEY_READ | KEY_WOW64_32KEY)
    .expect("key is missing");

// let apps: HashMap<String, InstalledApp> = uninstall_key.decode().unwrap_or_default();
let apps_result = uninstall_key.decode();

It turns out on my PC, one of the installed apps in the 32-bit view must have a key declared as REG_NONE incorrectly.

winreg does check the type in serialization_serde.rs, but the problem is, returning the DecodeResult with a error causes the entire deserialization to fail, even if the rest of the keys I'm attempting to extract are valid.

I modified line 39 of serialization_serde.rs to
_ => no_impl!(format!("value type deserialization not implemented {:?}", v.vtype)),

so that I could confirm the error:

Problem opening the file: DecodeNotImplemented("value type deserialization not implemented REG_NONE")

`RegValue` should contain `Cow<[u8]>`, not `Vec<u8>`

In my abstracting write-function, I prevent having to unnecessarily clone the byte array this way:

pub fn write_reg_bin_value(
    reg_value_path: &RegValuePath,
    bytes: &Vec<u8>,
) -> Result<(), io::Error> {
    let key = RegKey::predef(reg_value_path.hkey)
        .open_subkey_with_flags(reg_value_path.subkey_path, KEY_SET_VALUE)?;

    let unsafe_reg_value = ManuallyDrop::new(RegValue {
        vtype: RegType::REG_BINARY,
        // Unsafely double-owned `Vec`.
        bytes: unsafe { Vec::from_raw_parts(bytes.as_ptr() as _, bytes.len(), bytes.capacity()) },
    });

    // A panic would leak the reg value, but at least not cause a double-drop.
    let result = key.set_raw_value(reg_value_path.value_name, &unsafe_reg_value);

    // Drop only parts in fact owned. Use `ManuallyDrop` like `Vec::into_raw_parts()`, which is available in nightly Rust (as of Nov. 2023).
    let RegValue { bytes, .. } = ManuallyDrop::into_inner(unsafe_reg_value);
    let _ = ManuallyDrop::new(bytes);

    result?;
    Ok(())
}

This is quite hacky, of course.

When winreg generates a RegValue by reading it from the registry, it would use Cow::Owned. But when writing one with RegKey::set_raw_value(), which borrows it immutably anyways, Cow::Borrowed would suffice, so you don't have to unnecessarily clone the value, just for an immutalbe borrow!

Enable public conversion of RegValue to Rust types (make from_reg_value public for example)

When enumerating over the key, RegValue is returned. The returned RegValue has already read data in bytes field. Unfortunately, now if I want a smooth conversion to Rust type (String for example), I either need to call key.get_value(reg_value.name) (which will re-read the registry value) or implement exactly the same code as in https://github.com/gentoo90/winreg-rs/blob/master/src/types.rs (which is private for the module).

As I am new to Rust, I could be missing something.

The usage example would be:

extern crate winreg;
use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ};

fn print_key_values(key: &winreg::RegKey) {
	for (name, value) in key.enum_values().map(|x| x.unwrap()) {
                // I want to get String or OsString from value which is of type winreg::RegValue and has a field pub bytes: Vec<u8>
                // Now I need either to write covertion function as already existing in the crate, but they are not public
               // or use key.get_value(name) - wich will access registry again, although I already have the needed data
		println!("Description: {}\nCommand line: {:?}", name, value);
    }
}

fn main() {
	let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE);
	let cur_ver_run =
		hklm.open_subkey_with_flags(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"#,
		KEY_READ)
        .expect("Failed to open subkey Run");
	let cur_ver_run_once =
		hklm.open_subkey_with_flags(r#"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"#,
		KEY_READ)
        .expect("Failed to open subkey RunOnce");

	print_key_values(&cur_ver_run_once);
	print_key_values(&cur_ver_run);
}

Best regards,
Yan

REG_MULTI_SZ parsing shouldn't replace nulls with newlines; use a Vec instead!

I don't understand the reason for one of the implementation details noted in the FromRegValue trait:

NOTE: When converting to String, trailing NULL characters are trimmed and line separating NULL characters in REG_MULTI_SZ are replaced by \n. When converting to OsString, all NULL characters are left as is.

Windows docs state that REG_MULTI_SZ represents a "sequence of null-terminated strings, terminated by an empty string", which communicates to me that a REG_MULTI_SZ is better modeled by a Vec<String>; in other words, the intermediary nulls represent the end of an individual string element, instead of the line separator of a single that the docs seem to indicate. Furthermore, using a newline character seems like a potentially problematic behavior if a stored REG_MULTI_SZ already contains newlines:

extern crate encoding;

use encoding::all::UTF_16LE;

fn parse_reg_multi_sz(s: String) -> Vec<String> {
    s.into_iter().split('\n').collect() // Presumably correct splitting behavior according to docs
}

fn main() {
    // The UTF-16 bytes of the problematic string gets stored as:
    // "just\0some\0strings\nyo\0\0"
    let registry_data: String  = { // Coerced to a single `String`, per documented behavior in `FromRegValue`.
        let hive = RegKey::predef(/* ... */);
        let key = hive.open_subkey(/* ... */);
        key.get_value(&"something").unwrap()
    };

    assert_eq!(parse_reg_multi_sz(registry_data), vec!["just", "some", "strings\nyo"); // panics because this was parsed as ["just", "some", "strings", "yo"]
}

I'm more than happy to submit a PR with some design and implementation work to fix this issue, if it is accepted as a defect. :)

Convert String to HKEY

Hello,

can anybody say me how i can a String convert to an HKEY. I want to deliver a String trough an function but don't want to import the HKEY Modul in the caller function.

registry::get_value ("HKEY_LOCAL_MACHINE".to_string(),"".to_string(),"".to_string());

thx

Make `commit` and `rollback` consume the transaction

As far as I can tell, commit and rollback should consume the transaction.
Is there any reason this hasn't been done that I'm missing?
I guess it COULD be problematic if the function fails, but what are you going to do if CommitTransaction fails except log it, anyways?

[ Feature ] RegKeyRename

Can you extend the lib to offer rename of RegKey objects?

maybe https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regrenamekey

Access is denied. (os error 5)

Hi,

I'm having this error when doing a set_value on a subkey, open_subkey seems to work fine:

    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
    let key = hklm.open_subkey(SUB_KEY)?;
    key.set_value(USBEPP2_VALUE, &"FIXED")?;

I've tried to run my app as admin with no success.

What am I missing?

`enum_values` and `enum_keys` creates infinite iterators if you don't open the key with read permissions

The following code will never terminate (until it runs out of memory):

fn main() {
    if let Ok(key) = winreg::RegKey::predef(HKEY_CURRENT_USER)
        .open_subkey_with_flags(r"SOFTWARE\Classes\tel", winreg::enums::KEY_SET_VALUE)
    {
        let _: Vec<_> = key.enum_values().collect();
    }
}

While the problem is obvious (you need to include winreg::enums::KEY_READ), the failure case is confusing. It might be more intuitive if enum_values (and presumably enum_keys) returned a single Err for the ERROR_ACCESS_DENIED, returned an empty iterator, or had a return value of io::Result<EnumValues> instead. :)

Implement `Send` for `Transaction`

I am not absolutely sure about this, but as far as I can tell a transaction is just a handle. Can a transaction be made Send just like RegKey?

How to check disposition

How to check "disposition" after create key with create_subkey?
Maybe return enum Disposition { Created(RegKey), Opened(RegKey) }?

Can't read machine GUID

Example with reading Program Files dir works well, however this one does not:

    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
    let win_crypt = hklm.open_subkey_with_flags("SOFTWARE\\Microsoft\\Cryptography",
        KEY_READ).unwrap();
    let guid: String = win_crypt.get_value("MachineGuid").unwrap();
    println!("guid: {}",guid);

thread '

' panicked at 'called Result::unwrap() on an Err value: Error { repr: Os { code: 2, message: "The system cannot find the file specified." } }', ../src/libcore\result.rs:746
note: Run with RUST_BACKTRACE=1 for a backtrace.
Process didn't exit successfully: target\debug\request-generator.exe (exit code: 101)

FromRegValue for String does not validate null-termination

Per MSDN:

If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been stored with the proper terminating null characters. Therefore, even if the function returns ERROR_SUCCESS, the application should ensure that the string is properly terminated before using it;

Relicense under dual MIT/Apache-2.0

This issue was automatically generated. Feel free to close without ceremony if
you do not agree with re-licensing or if it is not possible for other reasons.
Respond to @cmr with any questions or concerns, or pop over to
#rust-offtopic on IRC to discuss.

You're receiving this because someone (perhaps the project maintainer)
published a crates.io package with the license as "MIT" xor "Apache-2.0" and
the repository field pointing here.

TL;DR the Rust ecosystem is largely Apache-2.0. Being available under that
license is good for interoperation. The MIT license as an add-on can be nice
for GPLv2 projects to use your code.

Why?

The MIT license requires reproducing countless copies of the same copyright
header with different names in the copyright field, for every MIT library in
use. The Apache license does not have this drawback. However, this is not the
primary motivation for me creating these issues. The Apache license also has
protections from patent trolls and an explicit contribution licensing clause.
However, the Apache license is incompatible with GPLv2. This is why Rust is
dual-licensed as MIT/Apache (the "primary" license being Apache, MIT only for
GPLv2 compat), and doing so would be wise for this project. This also makes
this crate suitable for inclusion and unrestricted sharing in the Rust
standard distribution and other projects using dual MIT/Apache, such as my
personal ulterior motive, the Robigalia project.

Some ask, "Does this really apply to binary redistributions? Does MIT really
require reproducing the whole thing?" I'm not a lawyer, and I can't give legal
advice, but some Google Android apps include open source attributions using
this interpretation. Others also agree with
it
.
But, again, the copyright notice redistribution is not the primary motivation
for the dual-licensing. It's stronger protections to licensees and better
interoperation with the wider Rust ecosystem.

How?

To do this, get explicit approval from each contributor of copyrightable work
(as not all contributions qualify for copyright, due to not being a "creative
work", e.g. a typo fix) and then add the following to your README:

## License

Licensed under either of

 * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
 * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.

and in your license headers, if you have them, use the following boilerplate
(based on that used in Rust):

// Copyright 2016 winreg-rs developers
//
// 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. This file may not be copied, modified, or distributed
// except according to those terms.

It's commonly asked whether license headers are required. I'm not comfortable
making an official recommendation either way, but the Apache license
recommends it in their appendix on how to use the license.

Be sure to add the relevant LICENSE-{MIT,APACHE} files. You can copy these
from the Rust repo for a plain-text
version.

And don't forget to update the license metadata in your Cargo.toml to:

license = "MIT/Apache-2.0"

I'll be going through projects which agree to be relicensed and have approval
by the necessary contributors and doing this changes, so feel free to leave
the heavy lifting to me!

Contributor checkoff

To agree to relicensing, comment with :

I license past and future contributions under the dual MIT/Apache-2.0 license, allowing licensees to chose either at their option.

Or, if you're a contributor, you can check the box in this repo next to your
name. My scripts will pick this exact phrase up and check your checkbox, but
I'll come through and manually review this issue later as well.

Check if value exists

Looking through the crate's documentation I don't see any way to check if a key in a subkey exists, eg:

        let key = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER)
            .open_subkey(FOO_REGISTRY_PATH)?;

        // this returns an error...
        key.get_value("a key that does not exist");

        // so I'd like to test first
        if key.contains_value("a key that does not exist") {
           // ....
        }

Other ways I could think to do this would make get_value return an Option although this would break back compat. Maybe try_get_value which returns Option? Or just stick with a new method that only checks for existence?

OsString::from_reg_value shouldn't preserve null terminator

Example code:

use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::path::PathBuf;

use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;

fn main() {
    let base = RegKey::predef(HKEY_CURRENT_USER);
    let desktop = base.open_subkey(r#"Control Panel\Desktop"#).unwrap();
    // read using OsString
    let wallpaper: OsString = desktop.get_value("WallPaper").unwrap();
    let path = PathBuf::from(wallpaper);
    println!("Wallpaper exists? {}, path is {:?}", path.exists(), path);
    // read using String
    let wallpaper: String = desktop.get_value("WallPaper").unwrap();
    let path = PathBuf::from(wallpaper);
    println!("Wallpaper exists? {}, path is {:?}", path.exists(), path);
    // read manually
    let wallpaper = desktop.get_raw_value("WallPaper").unwrap();
    let mut raw: Vec<u16> = wallpaper
        .bytes
        .chunks_exact(2)
        .into_iter()
        .map(|c| u16::from_le_bytes([c[0], c[1]]))
        .collect();
    while raw.ends_with(&[0]) {
        raw.pop();
    }
    let path = PathBuf::from(OsString::from_wide(&raw));
    println!("Wallpaper exists? {}, path is {:?}", path.exists(), path);
}

On my machine this prints:

Wallpaper exists? false, path is "C:\\Users\\X\\AppData\\Local\\Packages\\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\\LocalState\\Assets\\0659d89079d32d12eab382d73089f7997f00ee80d8d6c053dc879e956600804b\u{0}"
Wallpaper exists? true, path is "C:\\Users\\X\\AppData\\Local\\Packages\\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\\LocalState\\Assets\\0659d89079d32d12eab382d73089f7997f00ee80d8d6c053dc879e956600804b"
Wallpaper exists? true, path is "C:\\Users\\X\\AppData\\Local\\Packages\\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\\LocalState\\Assets\\0659d89079d32d12eab382d73089f7997f00ee80d8d6c053dc879e956600804b"

(these may all print false on your machine as internally it caches elsewhere; I just needed a simpler repro)

The null terminator on the OsString causes the PathBuf to have a path to a non-existent file. As a result, I have to choose between using a String (lossy if Unicode is ill-formed) or manually turning the registry's bytes into an OsString (lossless but complex, not sure my method is 100% correct).

From the OsString docs:

OsString and OsStr bridge this gap by simultaneously representing Rust and platform-native string values, and in particular allowing a Rust string to be converted into an "OS" string with no cost if possible. A consequence of this is that OsString instances are not NUL terminated; in order to pass to e.g., Unix system call, you should create a CStr.

Lossy utf8 conversion error prone

This can just be taken as feedback for what it's worth.

In rustup I recently submitted a patch to stop using get_value / set_value/ for strings of the HKCU\Environment\PATH value. rustup needs to read and write the value and it was important to detect and preserve the non-unicode.

Providing lossless OsString conversions may help, since then I could handle the encoding issues on my side, while still using the nice get_value / set_value fns.

Strongly-typed enums for registry data

Hey there! I have a design-related question which I hope may eventually result in a contribution to enhance this codebase. This issue can be closed when maintainers have a consensus on whether or not this would be useful. :)

Recently, I've been developing some code in Rust that can parse Registry.pol files, and found it immensely useful to have a enum that models the data actually associated with registry values. It would be something along the lines of this:

pub enum RegistryData {
RegNone,
RegSz(String),
RegExpandSz(String),
RegBinary(Vec),
RegDwordLittleEndian(u32),
RegDwordBigEndian(u32),
RegLink(String),
RegMultiSz(Vec),
RegResourceList,
RegFullResourceDescriptor,
RegResourceRequirementsList,
RegQword(u64),
}

The key benefit I see to this model is that it allows more specific and strong types for usage with the domain that this crate handles. This is especially useful for testing -- some of the code I might write to test a sample data file in my parser might like this, and I think it's pretty easy to follow:

let parsed_reg_pol_file = RegistryPolicyFile::from_buffer(File::open("Registry.pol")?)?;
assert_eq(parsed_reg_pol_file.configuration_settings. == vec![
	RegSz("Some value"),
    RegExpandSz("APPDATA"),
	RegDword(25u32),
    RegMultiSz(vec!["just", "some", "words"]),
    // etc.
]);

This enum is not intended to replace the unparsed types like the RegValue struct or the RegType enum -- but it would be convenient to users who don't need or intend to use the intermediate types. I already have conversions defined to this enum from sequences of bytes, which might be interesting to compare with the implementation that's in this project.

There are other details I would love to discuss, too -- but first, I want to know if this is even something interesting for the winreg crate's authors! I certainly thing it would be for users.

Implement serialize_some and possibly serialize_none for serialization_serde

Options are currently not supported for serialization in the serialization_serde module. That makes it unnecessarily hard to use a common pattern like this where you have optional values that don't necessarily need to be present and should be left out when they are None:

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
pub struct InstalledApp {
    pub display_name: Option<String>,
    pub display_version: Option<String>,
    pub uninstall_string: Option<String>,
}

This works fine for deserialization but runs into the unimplemented serialize_none and serialize_some functions for serialization. The None case can be easily avoided with #[serde(skip_serializing_if = "Option::is_none")], but in case of Some the value would have to be mapped.

It is totally possible that there is reason why the two functions are not implemented but right now I can't think of any reasons.
I would suggest to at least implement serialize_some to simply serialize the T value in Some(T) and I'd probably also implement serialize_none to not do anything.

Let me know what you think and if there are any reasons that made you not implement it. If you want I can also implement this and do a PR

`{u32, u64}::from_reg_value` is unsound

winreg-rs/src/types.rs

Lines 110 to 128 in fc6521e

impl FromRegValue for u32 {
fn from_reg_value(val: &RegValue) -> io::Result<u32> {
match val.vtype {
#[allow(clippy::cast_ptr_alignment)]
REG_DWORD => Ok(unsafe { *(val.bytes.as_ptr() as *const u32) }),
_ => werr!(Foundation::ERROR_BAD_FILE_TYPE),
}
}
}
impl FromRegValue for u64 {
fn from_reg_value(val: &RegValue) -> io::Result<u64> {
match val.vtype {
#[allow(clippy::cast_ptr_alignment)]
REG_QWORD => Ok(unsafe { *(val.bytes.as_ptr() as *const u64) }),
_ => werr!(Foundation::ERROR_BAD_FILE_TYPE),
}
}
}

RegValue.bytes is a Rust Vec<u8>. This implementation assumes that the vec pointer is sufficiently aligned for u32/u64. This will usually be true (e.g. the Windows System allocator I believe always sufficiently aligns), but this is not guaranteed; an alternative #[global_allocator] could provide allocations at odd addresses. x86 bytecode doesn't care about alignment, but Rust/LLVM does, and an unaligned access is UB even if it works fine on the target.

Significantly more problematic, as RegValue's fields are public, it is possible to provide an empty vector to this function, which will happily try to read from a fully invalid pointer.

let val = RegValue {
    bytes: vec![],
    vtype: REG_QWORD,
};
u64::from_reg_value(&val); // ๐Ÿ’ฃ๐Ÿ’ฅโ€ผ๏ธ

A correct and maximally performant implementation can be written in pure safe code fairly easily:

impl FromRegValue for u32 {
    fn from_reg_value(val: &RegValue) -> io::Result<u32> {
        let bytes = val.bytes.as_slice().try_into();
        match val.vtype {
            REG_DWORD => bytes
                .map(u64::from_ne_bytes)
                .map_err(|_| werr!(ERROR_INVALID_DATA)),
            REG_DWORD_BIG_ENDIAN => bytes
                .map(u64::from_be_bytes)
                .map_err(|_| werr!(ERROR_INVALID_DATA)),
            _ => werr!(Foundation::ERROR_BAD_FILE_TYPE),
        }
    }
}

impl FromRegValue for u64 {
    fn from_reg_value(val: &RegValue) -> io::Result<u64> {
        let bytes = val.bytes.as_slice().try_into();
        match val.vtype {
            REG_QWORD => bytes
                .map(u64::from_ne_bytes)
                .map_err(|_| werr!(ERROR_INVALID_DATA)),
            _ => werr!(Foundation::ERROR_BAD_FILE_TYPE),
        }
    }
}

Any cost imposed by the bounds check and unaligned access is trivially dominated by the cost of reading from the registry.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.