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