use std::{
collections::HashSet,
env,
io::Read,
path::{Path, PathBuf},
process::Command,
str,
};
use anyhow::{Result, bail};
use bc_components::XID;
use bc_envelope::prelude::*;
use bc_xid::XIDDocument;
pub fn read_password(
prompt: &str,
password_override: Option<&str>,
use_askpass: bool,
) -> anyhow::Result<String> {
if let Some(p) = password_override {
return Ok(p.to_owned());
}
let password = if use_askpass {
if let Some(cmd) = resolve_askpass() {
let out = Command::new(cmd).arg(prompt).output()?;
if out.status.success() {
let pass = str::from_utf8(&out.stdout)
.map_err(|e| {
anyhow::anyhow!("askpass produced invalid UTF‑8: {e}")
})?
.trim_end_matches(&['\n', '\r'][..])
.to_owned();
Some(pass)
} else {
eprintln!("askpass exited with {}", out.status);
None
}
} else {
None
}
} else {
None
}
.unwrap_or_else(|| {
rpassword::prompt_password(prompt).unwrap_or_default()
});
if password.is_empty() {
bail!("Password cannot be empty");
}
Ok(password)
}
fn resolve_askpass() -> Option<PathBuf> {
if let Ok(path) = env::var("SSH_ASKPASS").or_else(|_| env::var("ASKPASS")) {
return Some(PathBuf::from(path));
}
const CANDIDATES: &[&str] = &[
"/usr/libexec/ssh-askpass",
"/usr/lib/ssh/ssh-askpass",
"/usr/local/bin/ssh-askpass",
"/opt/homebrew/bin/ssh-askpass",
];
for cand in CANDIDATES {
let p = Path::new(cand);
if p.exists() && p.is_file() {
return Some(p.to_path_buf());
}
}
which::which("askpass").ok()
}
pub fn read_argument(argument: Option<&str>) -> Result<String> {
let string = if let Some(arg) = argument {
arg.to_string()
} else {
let mut s = String::new();
std::io::stdin().read_to_string(&mut s)?;
s
};
if string.is_empty() {
bail!("No argument provided");
}
Ok(string)
}
pub fn envelope_from_ur(ur: &UR) -> Result<Envelope> {
if let Ok(envelope) = Envelope::from_ur(ur) {
return Ok(envelope);
}
if let Ok(envelope) = Envelope::from_tagged_cbor(ur.cbor()) {
return Ok(envelope);
}
if ur.ur_type_str() == "xid" {
let xid = XID::from_untagged_cbor(ur.cbor())?;
let doc = XIDDocument::from(xid);
return Ok(doc.into_envelope());
}
bail!("Invalid envelope");
}
pub fn read_envelope(envelope: Option<&str>) -> Result<Envelope> {
let ur_string = if let Some(env) = envelope {
env.to_string()
} else {
let mut s = String::new();
std::io::stdin().read_line(&mut s)?;
s
};
let ur_string = ur_string.trim();
if ur_string.is_empty() {
bail!("No envelope provided");
}
Envelope::from_ur_string(ur_string)
.or_else(|_| envelope_from_ur(&UR::from_ur_string(ur_string)?))
}
pub fn parse_digest(target: &str) -> Result<Digest> {
let ur = UR::from_ur_string(target)?;
let digest = match ur.ur_type_str() {
"digest" => Digest::from_ur(&ur)?,
"envelope" => Envelope::from_ur(&ur)?.digest(),
_ => {
bail!("Invalid digest type: {}", ur.ur_type_str());
}
};
Ok(digest)
}
pub fn parse_digests(target: &str) -> Result<HashSet<Digest>> {
let target = target.trim();
if target.is_empty() {
Ok(HashSet::new())
} else {
target
.split(' ')
.map(parse_digest)
.collect::<Result<HashSet<Digest>>>()
}
}