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 string = if let Some(arg) = argument {
109 arg.to_string()
110 } else {
111 let mut s = String::new();
112 std::io::stdin().read_to_string(&mut s)?;
113 s
114 };
115 if string.is_empty() {
116 bail!("No argument provided");
117 }
118 Ok(string)
119}
120
121pub fn read_envelope(envelope: Option<&str>) -> Result<Envelope> {
122 let ur_string = if let Some(env) = envelope {
123 env.to_string()
124 } else {
125 let mut s = String::new();
126 std::io::stdin().read_line(&mut s)?;
127 s
128 };
129 if ur_string.is_empty() {
130 bail!("No envelope provided");
131 }
132 if let Ok(envelope) = Envelope::from_ur_string(ur_string.trim()) {
134 Ok(envelope)
135 } else if let Ok(ur) = UR::from_ur_string(ur_string.trim()) {
137 let cbor = ur.cbor();
138 if let Ok(envelope) = Envelope::from_tagged_cbor(cbor) {
140 Ok(envelope)
141 } else if ur.ur_type_str() == "xid" {
142 let xid = XID::from_untagged_cbor(ur.cbor())?;
143 let doc = XIDDocument::from(xid);
144 Ok(doc.into_envelope())
145 } else {
146 todo!();
149 }
150 } else {
151 bail!("Invalid envelope");
152 }
153}
154
155pub fn parse_digest(target: &str) -> Result<Digest> {
156 let ur = UR::from_ur_string(target)?;
157 let digest = match ur.ur_type_str() {
158 "digest" => Digest::from_ur(&ur)?,
159 "envelope" => Envelope::from_ur(&ur)?.digest().into_owned(),
160 _ => {
161 bail!("Invalid digest type: {}", ur.ur_type_str());
162 }
163 };
164 Ok(digest)
165}
166
167pub fn parse_digests(target: &str) -> Result<HashSet<Digest>> {
168 let target = target.trim();
169 if target.is_empty() {
170 Ok(HashSet::new())
171 } else {
172 target
173 .split(' ')
174 .map(parse_digest)
175 .collect::<Result<HashSet<Digest>>>()
176 }
177}