use std::path::Path;
use std::process::Command;
const KEYSERVER: &str = "keyserver.ubuntu.com";
fn extract_valid_pgp_keys(pkgbuild: &str) -> Vec<String> {
let Some(start) = pkgbuild.find("validpgpkeys=(") else {
return Vec::new();
};
let after = &pkgbuild[start + "validpgpkeys=(".len()..];
let Some(end) = after.find(')') else {
return Vec::new();
};
let mut keys = Vec::new();
let mut chars = after[..end].chars().peekable();
while let Some(c) = chars.next() {
if c == '\'' || c == '"' {
let quote = c;
let key: String = chars.by_ref().take_while(|&c| c != quote).collect();
if !key.is_empty() {
keys.push(key);
}
}
}
keys
}
pub fn import_build_keys(pkg_dir: &Path) {
let Ok(pkgbuild) = std::fs::read_to_string(pkg_dir.join("PKGBUILD")) else {
return;
};
let keys = extract_valid_pgp_keys(&pkgbuild);
if keys.is_empty() {
return;
}
let gnupg_home = pkg_dir.join(".gnupg");
if std::fs::create_dir_all(&gnupg_home).is_err() {
return;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&gnupg_home, std::fs::Permissions::from_mode(0o700));
}
let bundled_dir = pkg_dir.join("keys").join("pgp");
for key in &keys {
let bundled = bundled_dir.join(format!("{key}.asc"));
let status = if bundled.is_file() {
Command::new("gpg")
.args(["--homedir"])
.arg(&gnupg_home)
.args(["--batch", "--quiet", "--no-permission-warning", "--import"])
.arg(&bundled)
.status()
} else {
Command::new("gpg")
.args(["--homedir"])
.arg(&gnupg_home)
.args([
"--batch",
"--quiet",
"--no-permission-warning",
"--keyserver",
KEYSERVER,
"--recv-keys",
])
.arg(key)
.status()
};
let _ = status;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_single_key() {
let pkgbuild = "pkgname=spotify\nvalidpgpkeys=('E1096BCBFF6D418796DE78515384CE82BA52C83A') # comment\n";
assert_eq!(
extract_valid_pgp_keys(pkgbuild),
vec!["E1096BCBFF6D418796DE78515384CE82BA52C83A".to_string()]
);
}
#[test]
fn extracts_multiple_multiline_keys() {
let pkgbuild = "validpgpkeys=(\n 'AAAA1111'\n \"BBBB2222\"\n)\n";
assert_eq!(
extract_valid_pgp_keys(pkgbuild),
vec!["AAAA1111".to_string(), "BBBB2222".to_string()]
);
}
#[test]
fn missing_array_returns_empty() {
assert!(extract_valid_pgp_keys("pkgname=foo\n").is_empty());
}
#[test]
fn import_is_a_noop_without_a_pkgbuild() {
let dir = tempfile::tempdir().unwrap();
import_build_keys(dir.path());
assert!(!dir.path().join(".gnupg").exists());
}
}