use std::collections::BTreeMap;
use std::path::PathBuf;
use super::CertFingerprint;
use crate::error::{Result, UgosError};
fn store_path() -> Result<PathBuf> {
let dir = dirs::config_dir()
.ok_or_else(|| UgosError::Encryption("cannot determine config directory".into()))?
.join("ugos-cli");
Ok(dir.join("known_hosts.json"))
}
fn key(host: &str, port: u16) -> String {
format!("{host}:{port}")
}
fn read_all() -> BTreeMap<String, String> {
store_path()
.ok()
.and_then(|p| std::fs::read_to_string(p).ok())
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default()
}
pub fn get(host: &str, port: u16) -> Result<Option<CertFingerprint>> {
let Some(stored) = read_all().get(&key(host, port)).cloned() else {
return Ok(None);
};
let fp = CertFingerprint::from_hex(&stored).map_err(|e| {
UgosError::Encryption(format!(
"malformed fingerprint stored for {}: {e}",
key(host, port)
))
})?;
Ok(Some(fp))
}
pub fn put(host: &str, port: u16, fp: &CertFingerprint) -> Result<()> {
let path = store_path()?;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| UgosError::io(format!("creating {}", parent.display()), e))?;
}
let mut all = read_all();
let _previous = all.insert(key(host, port), fp.to_hex());
let json = serde_json::to_string_pretty(&all)?;
std::fs::write(&path, json)
.map_err(|e| UgosError::io(format!("writing {}", path.display()), e))?;
Ok(())
}
pub fn forget(host: &str, port: u16) -> Result<bool> {
let mut all = read_all();
if all.remove(&key(host, port)).is_none() {
return Ok(false);
}
let path = store_path()?;
let json = serde_json::to_string_pretty(&all)?;
std::fs::write(&path, json)
.map_err(|e| UgosError::io(format!("writing {}", path.display()), e))?;
Ok(true)
}
#[must_use]
pub fn list() -> Vec<(String, String)> {
read_all().into_iter().collect()
}