use std::fs::File;
use std::io::{self, Write};
use std::path::Path;
use crate::error::{Error, Result};
use crate::reg_export::{RegEntry, RegParser};
use crate::writer::{HiveBuilder, KeyTreeNode, KeyTreeValue};
#[derive(Debug, Clone)]
pub struct RegImportOptions {
pub root_name: String,
pub strip_prefix: Option<String>,
pub minor_version: u32,
}
impl Default for RegImportOptions {
fn default() -> Self {
Self {
root_name: String::new(),
strip_prefix: None,
minor_version: 6,
}
}
}
pub struct RegImporter {
entries: Vec<RegEntry>,
options: RegImportOptions,
}
impl RegImporter {
pub fn new(entries: Vec<RegEntry>, options: RegImportOptions) -> Self {
Self { entries, options }
}
pub fn from_string(content: &str) -> Self {
let parser = RegParser::new(content.to_string());
Self::new(parser.parse(), RegImportOptions::default())
}
pub fn from_string_with_options(content: &str, options: RegImportOptions) -> Self {
let parser = RegParser::new(content.to_string());
Self::new(parser.parse(), options)
}
pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let parser = RegParser::from_file(path)?;
Ok(Self::new(parser.parse(), RegImportOptions::default()))
}
pub fn from_file_with_options<P: AsRef<Path>>(
path: P,
options: RegImportOptions,
) -> io::Result<Self> {
let parser = RegParser::from_file(path)?;
Ok(Self::new(parser.parse(), options))
}
fn detect_common_prefix(&self) -> Option<String> {
if self.entries.is_empty() {
return None;
}
let first_path = &self.entries[0].key_path;
let components: Vec<&str> = first_path.split('\\').collect();
if components.is_empty() {
return None;
}
let mut common_len = components.len();
for entry in &self.entries[1..] {
let entry_components: Vec<&str> = entry.key_path.split('\\').collect();
let matching = components
.iter()
.zip(entry_components.iter())
.take_while(|(a, b)| a == b)
.count();
common_len = common_len.min(matching);
}
if common_len == 0 {
None
} else {
Some(components[..common_len].join("\\"))
}
}
fn strip_path_prefix<'a>(&self, path: &'a str, prefix: &str) -> &'a str {
if path == prefix {
""
} else if path.starts_with(prefix) {
let stripped = &path[prefix.len()..];
stripped.strip_prefix('\\').unwrap_or(stripped)
} else {
path
}
}
pub fn build_hive(&self) -> Result<Vec<u8>> {
if self.entries.is_empty() {
return Err(Error::InvalidPath("No registry entries to import".into()));
}
let prefix = self
.options
.strip_prefix
.clone()
.or_else(|| self.detect_common_prefix())
.unwrap_or_default();
let root_name = if !self.options.root_name.is_empty() {
self.options.root_name.clone()
} else if !prefix.is_empty() {
prefix
.rsplit('\\')
.next()
.unwrap_or("Root")
.to_string()
} else {
"Root".to_string()
};
let mut root = KeyTreeNode::new(&root_name);
for entry in &self.entries {
let stripped = self.strip_path_prefix(&entry.key_path, &prefix);
let key_node = root.get_or_create_path(stripped);
for value in &entry.values {
key_node.values.push(KeyTreeValue {
name: value.name.clone(),
data_type: value.data_type,
data: value.data.clone(),
});
}
}
let mut builder = HiveBuilder::from_tree_with_version(root, 1, self.options.minor_version);
builder.build()
}
pub fn build_hive_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let hive_bytes = self.build_hive()?;
let mut file = File::create(path)?;
file.write_all(&hive_bytes)?;
Ok(())
}
pub fn entries(&self) -> &[RegEntry] {
&self.entries
}
pub fn entry_count(&self) -> usize {
self.entries.len()
}
}
pub fn reg_to_hive<P: AsRef<Path>>(reg_path: P) -> Result<Vec<u8>> {
let importer = RegImporter::from_file(reg_path).map_err(Error::Io)?;
importer.build_hive()
}
pub fn reg_file_to_hive_file<P: AsRef<Path>, Q: AsRef<Path>>(
reg_path: P,
hive_path: Q,
) -> Result<()> {
let importer = RegImporter::from_file(reg_path).map_err(Error::Io)?;
importer.build_hive_to_file(hive_path)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hive::RegistryHive;
#[test]
fn test_simple_import() {
let content = r#"Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Test]
"StringValue"="Hello World"
"DwordValue"=dword:0000002a
"#;
let importer = RegImporter::from_string(content);
let hive_bytes = importer.build_hive().unwrap();
let hive = RegistryHive::from_bytes(hive_bytes).unwrap();
let root = hive.root_key().unwrap();
assert_eq!(root.name(), "Test");
let values = root.values().unwrap();
assert_eq!(values.len(), 2);
}
#[test]
fn test_nested_keys_import() {
let content = r#"Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Company]
[HKEY_LOCAL_MACHINE\SOFTWARE\Company\Product]
"Name"="TestProduct"
"Version"=dword:00000001
[HKEY_LOCAL_MACHINE\SOFTWARE\Company\Product\Settings]
"Debug"=dword:00000000
"#;
let importer = RegImporter::from_string(content);
let hive_bytes = importer.build_hive().unwrap();
let hive = RegistryHive::from_bytes(hive_bytes).unwrap();
let root = hive.root_key().unwrap();
let subkeys = root.subkeys().unwrap();
assert!(!subkeys.is_empty());
}
#[test]
fn test_custom_root_name() {
let content = r#"Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Test]
"Value"="Test"
"#;
let options = RegImportOptions {
root_name: "CustomRoot".to_string(),
..Default::default()
};
let importer = RegImporter::from_string_with_options(content, options);
let hive_bytes = importer.build_hive().unwrap();
let hive = RegistryHive::from_bytes(hive_bytes).unwrap();
let root = hive.root_key().unwrap();
assert_eq!(root.name(), "CustomRoot");
}
#[test]
fn test_binary_value_import() {
let content = r#"Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Test]
"BinaryData"=hex:00,01,02,03,04,05
"#;
let importer = RegImporter::from_string(content);
let hive_bytes = importer.build_hive().unwrap();
let hive = RegistryHive::from_bytes(hive_bytes).unwrap();
let root = hive.root_key().unwrap();
let values = root.values().unwrap();
assert_eq!(values.len(), 1);
assert_eq!(values[0].name(), "BinaryData");
}
#[test]
fn test_strip_prefix() {
let content = r#"Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]
"ProgramFilesDir"="C:\\Program Files"
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
"App"="C:\\App\\app.exe"
"#;
let options = RegImportOptions {
strip_prefix: Some("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows".to_string()),
root_name: "Windows".to_string(),
..Default::default()
};
let importer = RegImporter::from_string_with_options(content, options);
let hive_bytes = importer.build_hive().unwrap();
let hive = RegistryHive::from_bytes(hive_bytes).unwrap();
let root = hive.root_key().unwrap();
assert_eq!(root.name(), "Windows");
let subkeys = root.subkeys().unwrap();
assert_eq!(subkeys.len(), 1);
assert_eq!(subkeys[0].name(), "CurrentVersion");
}
}