gix_credentials/program/
main.rs1use std::ffi::OsString;
2
3use bstr::BString;
4
5#[derive(Debug, Copy, Clone)]
7pub enum Action {
8 Get,
10 Store,
12 Erase,
14}
15
16impl TryFrom<OsString> for Action {
17 type Error = Error;
18
19 fn try_from(value: OsString) -> Result<Self, Self::Error> {
20 Ok(match value.to_str() {
21 Some("fill" | "get") => Action::Get,
22 Some("approve" | "store") => Action::Store,
23 Some("reject" | "erase") => Action::Erase,
24 _ => return Err(Error::ActionInvalid { name: value }),
25 })
26 }
27}
28
29impl Action {
30 pub fn as_str(&self) -> &'static str {
32 match self {
33 Action::Get => "get",
34 Action::Store => "store",
35 Action::Erase => "erase",
36 }
37 }
38}
39
40#[derive(Debug, thiserror::Error)]
42#[allow(missing_docs)]
43pub enum Error {
44 #[error("Action named {name:?} is invalid, need 'get', 'store', 'erase' or 'fill', 'approve', 'reject'")]
45 ActionInvalid { name: OsString },
46 #[error("The first argument must be the action to perform")]
47 ActionMissing,
48 #[error(transparent)]
49 Helper {
50 source: Box<dyn std::error::Error + Send + Sync + 'static>,
51 },
52 #[error(transparent)]
53 Io(#[from] std::io::Error),
54 #[error(transparent)]
55 Context(#[from] crate::protocol::context::decode::Error),
56 #[error("Credentials for {url:?} could not be obtained")]
57 CredentialsMissing { url: BString },
58 #[error("The url wasn't provided in input - the git credentials protocol mandates this")]
59 UrlMissing,
60}
61
62pub(crate) mod function {
63 use std::ffi::OsString;
64
65 use crate::{
66 program::main::{Action, Error},
67 protocol::Context,
68 };
69
70 pub fn main<CredentialsFn, E>(
79 args: impl IntoIterator<Item = OsString>,
80 mut stdin: impl std::io::Read,
81 stdout: impl std::io::Write,
82 credentials: CredentialsFn,
83 ) -> Result<(), Error>
84 where
85 CredentialsFn: FnOnce(Action, Context) -> Result<Option<Context>, E>,
86 E: std::error::Error + Send + Sync + 'static,
87 {
88 let action: Action = args.into_iter().next().ok_or(Error::ActionMissing)?.try_into()?;
89 let mut buf = Vec::<u8>::with_capacity(512);
90 stdin.read_to_end(&mut buf)?;
91 let ctx = Context::from_bytes(&buf)?;
92 if ctx.url.is_none() {
93 return Err(Error::UrlMissing);
94 }
95 let res = credentials(action, ctx).map_err(|err| Error::Helper { source: Box::new(err) })?;
96 match (action, res) {
97 (Action::Get, None) => {
98 return Err(Error::CredentialsMissing {
99 url: Context::from_bytes(&buf)?.url.expect("present and checked above"),
100 })
101 }
102 (Action::Get, Some(ctx)) => ctx.write_to(stdout)?,
103 (Action::Erase | Action::Store, None) => {}
104 (Action::Erase | Action::Store, Some(_)) => {
105 panic!("BUG: credentials helper must not return context for erase or store actions")
106 }
107 }
108 Ok(())
109 }
110}