pars_core/pgp/
utils.rs

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}