1use std::{
2 collections::HashSet,
3 env,
4 io::Read,
5 path::{Path, PathBuf},
6 process::Command,
7 str,
8};
9
10use anyhow::{Result, bail};
11use bc_components::XID;
12use bc_envelope::prelude::*;
13use bc_xid::XIDDocument;
14
15pub fn read_password(
31 prompt: &str,
32 password_override: Option<&str>,
33 use_askpass: bool,
34) -> anyhow::Result<String> {
35 if let Some(p) = password_override {
37 return Ok(p.to_owned());
38 }
39
40 let password = if use_askpass {
42 if let Some(cmd) = resolve_askpass() {
43 let out = Command::new(cmd).arg(prompt).output()?;
44
45 if out.status.success() {
46 let pass = str::from_utf8(&out.stdout)
47 .map_err(|e| {
48 anyhow::anyhow!("askpass produced invalid UTF‑8: {e}")
49 })?
50 .trim_end_matches(&['\n', '\r'][..])
51 .to_owned();
52 Some(pass)
53 } else {
54 eprintln!("askpass exited with {}", out.status);
57 None
58 }
59 } else {
60 None
61 }
62 } else {
63 None
64 }
65 .unwrap_or_else(|| {
66 rpassword::prompt_password(prompt).unwrap_or_default()
68 });
69
70 if password.is_empty() {
72 bail!("Password cannot be empty");
73 }
74 Ok(password)
75}
76
77fn resolve_askpass() -> Option<PathBuf> {
84 if let Ok(path) = env::var("SSH_ASKPASS").or_else(|_| env::var("ASKPASS")) {
86 return Some(PathBuf::from(path));
87 }
88
89 const CANDIDATES: &[&str] = &[
91 "/usr/libexec/ssh-askpass",
92 "/usr/lib/ssh/ssh-askpass",
93 "/usr/local/bin/ssh-askpass",
94 "/opt/homebrew/bin/ssh-askpass",
95 ];
96 for cand in CANDIDATES {
97 let p = Path::new(cand);
98 if p.exists() && p.is_file() {
99 return Some(p.to_path_buf());
100 }
101 }
102
103 which::which("askpass").ok()
105}
106
107pub fn read_argument(argument: Option<&str>) -> Result<String> {
108 let mut string = String::new();
109 if argument.is_none() {
110 std::io::stdin().read_to_string(&mut string)?;
111 } else {
112 string = argument.as_ref().unwrap().to_string();
113 }
114 if string.is_empty() {
115 bail!("No argument provided");
116 }
117 Ok(string.to_string())
118}
119
120pub fn read_envelope(envelope: Option<&str>) -> Result<Envelope> {
121 let mut ur_string = String::new();
122 if envelope.is_none() {
123 std::io::stdin().read_line(&mut ur_string)?;
124 } else {
125 ur_string = envelope.as_ref().unwrap().to_string();
126 }
127 if ur_string.is_empty() {
128 bail!("No envelope provided");
129 }
130 if let Ok(envelope) = Envelope::from_ur_string(ur_string.trim()) {
132 Ok(envelope)
133 } else if let Ok(ur) = UR::from_ur_string(ur_string.trim()) {
135 let cbor = ur.cbor();
136 if let Ok(envelope) = Envelope::from_tagged_cbor(cbor) {
138 Ok(envelope)
139 } else if ur.ur_type_str() == "xid" {
140 let xid = XID::from_untagged_cbor(ur.cbor())?;
141 let doc = XIDDocument::from(xid);
142 Ok(doc.into_envelope())
143 } else {
144 todo!();
145 }
146 } else {
147 bail!("Invalid envelope");
148 }
149}
150
151pub fn parse_digest(target: &str) -> Result<Digest> {
152 let ur = UR::from_ur_string(target)?;
153 let digest = match ur.ur_type_str() {
154 "digest" => Digest::from_ur(&ur)?,
155 "envelope" => Envelope::from_ur(&ur)?.digest().into_owned(),
156 _ => {
157 bail!("Invalid digest type: {}", ur.ur_type_str());
158 }
159 };
160 Ok(digest)
161}
162
163pub fn parse_digests(target: &str) -> Result<HashSet<Digest>> {
164 let target = target.trim();
165 if target.is_empty() {
166 Ok(HashSet::new())
167 } else {
168 target
169 .split(' ')
170 .map(parse_digest)
171 .collect::<Result<HashSet<Digest>>>()
172 }
173}