1use std::io::Read;
2use std::process::{Child, Command};
3
4use anyhow::{anyhow, Result};
5use log::debug;
6
7use super::PGPKey;
8use crate::pgp::PGPClient;
9
10pub(crate) fn get_pgp_key_info<S: AsRef<str>, T: AsRef<str>>(
11 executable: S,
12 identifier: T,
13) -> Result<(String, String, String)> {
14 let output = Command::new(executable.as_ref())
15 .args(["--list-keys", "--with-colons", identifier.as_ref()])
16 .output()?;
17 if !output.status.success() {
18 return Err(anyhow!("Failed to get PGP key"));
19 }
20
21 let info = String::from_utf8(output.stdout)?;
22 debug!("fingerprint output: {}", info);
23
24 let mut fpr = String::new();
25
26 for line in info.lines() {
27 if line.starts_with("fpr") {
28 if let Some(fingerprint) = line.split(':').nth(9) {
29 fpr = fingerprint.to_string();
30 } else {
31 return Err(anyhow!("Failed to parse fingerprint"));
32 }
33 } else if line.starts_with("uid") {
34 let username = {
35 if let Some(before_at) = line.split_once(" <") {
36 let name_part = before_at.0;
37
38 if let Some(name) = name_part.rsplit(':').next() {
39 Ok(name.to_string())
40 } else {
41 Err(anyhow!("Failed to parse username"))
42 }
43 } else {
44 Err(anyhow!("Failed to parse username"))
45 }
46 }?;
47
48 let email = line
49 .split('<')
50 .nth(1)
51 .and_then(|part| part.split('>').next())
52 .ok_or(anyhow!("Failed to parse email"))?;
53 return Ok((fpr, username, email.to_string()));
54 }
55 }
56 Err(anyhow!(format!("No userinfo found for {}", identifier.as_ref())))
57}
58
59pub(super) fn wait_child_process(cmd: &mut Child) -> Result<()> {
60 let status = cmd.wait()?;
61 if status.success() {
62 Ok(())
63 } else {
64 let err_msg = match cmd.stderr.take() {
65 Some(mut stderr) => {
66 let mut buf = String::new();
67 stderr.read_to_string(&mut buf)?;
68 buf
69 }
70 None => return Err(anyhow!("Failed to read stderr")),
71 };
72 Err(anyhow!(format!("Failed to edit PGP key, code: {:?}\nError: {}", status, err_msg)))
73 }
74}
75
76macro_rules! get_keys_field {
77 ($self:ident, $field:ident) => {{
78 let mut res = Vec::with_capacity($self.keys.len());
79 for key in &$self.keys {
80 res.push(key.$field.as_str());
81 }
82 res
83 }};
84}
85
86impl PGPClient {
87 pub fn new<S: AsRef<str>>(executable: S, infos: &[impl AsRef<str>]) -> Result<Self> {
88 let mut gpg_client =
89 PGPClient { executable: executable.as_ref().to_string(), keys: Vec::new() };
90 gpg_client.update_info(infos)?;
91 Ok(gpg_client)
92 }
93
94 pub fn get_executable(&self) -> &str {
95 &self.executable
96 }
97
98 pub fn get_keys_fpr(&self) -> Vec<&str> {
99 get_keys_field!(self, key_fpr)
100 }
101
102 pub fn get_usernames(&self) -> Vec<&str> {
103 get_keys_field!(self, username)
104 }
105
106 pub fn get_email(&self) -> Vec<&str> {
107 get_keys_field!(self, email)
108 }
109
110 fn update_info<S: AsRef<str>>(&mut self, infos: &[S]) -> Result<()> {
111 self.keys = Vec::with_capacity(infos.len());
112 for info in infos {
113 let (fpr, username, email) = get_pgp_key_info(&self.executable, info)?;
114 self.keys.push(PGPKey { key_fpr: fpr, username, email });
115 debug!("Add key: {:?}", self.keys.last().unwrap());
116 }
117 Ok(())
118 }
119}