use ssh_key::PublicKey; use tokio::process::Command; use serde::Deserialize; use async_trait::async_trait; #[async_trait] pub trait Fetch: std::fmt::Debug { async fn fetch(&self) -> Vec; } #[derive(Debug, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Source { Raw(Raw), Hosts(Hosts), Forge(Forge), Github(String), Sourcehut(String), Gitlab(String), Codeberg(String), } #[async_trait] impl Fetch for Source { async fn fetch(&self) -> Vec { match *self { Source::Raw(ref raw) => raw.fetch().await, Source::Hosts(ref raw) => raw.fetch().await, Source::Forge(ref raw) => raw.fetch().await, Source::Github(ref user) => Forge { user: user.clone(), host: "github.com".into() }.fetch().await, Source::Sourcehut(ref user) => Forge { user: user.clone(), host: "meta.sr.ht".into() }.fetch().await, Source::Gitlab(ref user) => Forge { user: user.clone(), host: "gitlab.com".into() }.fetch().await, Source::Codeberg(ref user) => Forge { user: user.clone(), host: "codeberg.org".into() }.fetch().await, } } } #[derive(Debug, Deserialize)] pub struct Raw(Vec); #[async_trait::async_trait] impl Fetch for Raw { async fn fetch(&self) -> Vec { self.0.clone() } } #[derive(Debug, Deserialize)] pub struct Hosts(pub Vec); #[async_trait] impl Fetch for Hosts { async fn fetch(&self) -> Vec { // TODO: Check if we can do it in-process instead of shelling out to `ssh-keyscan` let result = Command::new("ssh-keyscan") .args(&self.0) .output() .await .unwrap(); std::str::from_utf8(&result.stdout) .unwrap() .trim() .split('\n') .map(|line| { // Ignore first column as it contain hostname which is not // needed there line.split(' ') .skip(1) .collect::>() .join(" ") .to_owned() }) .map(|k| PublicKey::from_openssh(&k).unwrap()) .collect() } } #[derive(Debug, Deserialize)] pub struct Forge { pub user: String, pub host: String, } #[async_trait] impl Fetch for Forge { async fn fetch(&self) -> Vec { let url = format!("https://{}/{}.keys", self.host, self.user); reqwest::get(url) .await .unwrap() .text() .await .unwrap() .trim() .split('\n') .map(|s| PublicKey::from_openssh(s).unwrap()) .collect() } }