#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
use std::fs;
use std::io::{self, Read, Write};
use std::path::Path;
use std::process::{Command, Stdio};
pub fn DetectGpgBinary() -> Result<String, String> {
let gpgBinaryPriorityList: &[&str] = &[ "gpg2", "gpg", "/bin/gpg2", "/usr/bin/gpg2", "/usr/local/bin/gpg2", "/bin/gpg", "/usr/bin/gpg", "/usr/local/bin/gpg", ];
for binary in gpgBinaryPriorityList { let err = ValidateGpgBinary(binary); if err.is_ok() { return Ok(binary.to_string()); }
}
Err("Unable to detect the location of the gpg binary to use".to_string()) }
pub fn ValidateGpgBinary(gpgPath: &str) -> Result<(), String> {
let status = Command::new(gpgPath)
.arg("--version") .stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
match status {
Ok(s) if s.success() => Ok(()),
Ok(s) => Err(format!("gpg --version exited {s}")),
Err(e) => Err(format!("spawn {gpgPath}: {e}")),
}
}
pub fn GpgDecryptFile(filePath: &Path, gpgPath: &str) -> Result<String, String> {
let passwordFile = fs::File::open(filePath) .map_err(|e| format!("{e}"))?;
let gpgOptions: &[&str] = &["--decrypt", "--yes", "--quiet", "--batch", "-"];
let mut cmd = Command::new(gpgPath) .args(gpgOptions)
.stdin(Stdio::from(passwordFile)) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()
.map_err(|e| format!("spawn gpg: {e}"))?;
let mut stdout = String::new(); let mut stderr = String::new(); if let Some(mut s) = cmd.stdout.take() {
s.read_to_string(&mut stdout).map_err(|e| format!("read stdout: {e}"))?;
}
if let Some(mut s) = cmd.stderr.take() {
s.read_to_string(&mut stderr).map_err(|e| format!("read stderr: {e}"))?;
}
let status = cmd.wait().map_err(|e| format!("wait: {e}"))?;
if !status.success() { return Err(format!("Error: gpg exited {status}, Stderr: {stderr}")); }
Ok(stdout) }
pub fn GpgEncryptFile(
filePath: &Path,
contents: &str,
recipients: &[String],
gpgPath: &str,
) -> Result<(), String> {
if let Some(parent) = filePath.parent() { fs::create_dir_all(parent)
.map_err(|e| format!("Unable to create directory structure: {e}"))?; }
let mut gpgOptions: Vec<String> = vec![ "--encrypt".into(), "--yes".into(), "--quiet".into(), "--batch".into(), "--output".into(), filePath.to_string_lossy().into_owned(), ];
for recipient in recipients { gpgOptions.push("--recipient".into()); gpgOptions.push(recipient.clone()); }
let mut cmd = Command::new(gpgPath) .args(gpgOptions.iter().map(String::as_str))
.stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()
.map_err(|e| format!("spawn gpg: {e}"))?;
if let Some(mut s) = cmd.stdin.take() {
s.write_all(contents.as_bytes())
.map_err(|e| format!("write stdin: {e}"))?;
}
let mut stderr = String::new(); if let Some(mut s) = cmd.stderr.take() {
s.read_to_string(&mut stderr).ok();
}
let status = cmd.wait().map_err(|e| format!("wait: {e}"))?;
if !status.success() { return Err(format!("Error: gpg exited {status}, Stderr: {stderr}")); }
Ok(()) }
pub fn DetectGpgRecipients(filePath: &Path) -> Result<Vec<String>, String> {
let mut dir = filePath .parent()
.ok_or_else(|| "file has no parent".to_string())?
.to_path_buf();
loop { let file = fs::read_to_string(dir.join(".gpg-id")); match file { Ok(body) => {
let normalized = body .replace("\r\n", "\n");
return Ok(normalized
.trim()
.split('\n')
.filter(|l| !l.is_empty())
.map(|l| l.to_string())
.collect());
}
Err(e) if e.kind() == io::ErrorKind::NotFound => {} Err(e) => { return Err(format!("Unable to open `.gpg-id` file: {e}")); }
}
let parentDir = dir.parent().map(|p| p.to_path_buf()); match parentDir { Some(p) if p != dir => dir = p, _ => return Err("Unable to find '.gpg-id' file".to_string()), }
}
}
pub fn IsDirectoryEmpty(dirPath: &Path) -> io::Result<bool> {
let mut f = fs::read_dir(dirPath)?;
match f.next() { None => Ok(true), Some(Err(e)) => Err(e),
Some(Ok(_)) => Ok(false), }
}
#[allow(non_snake_case)]
const _: () = ();