gix_credentials/program/
mod.rs

1use std::process::{Command, Stdio};
2
3use bstr::{BString, ByteSlice, ByteVec};
4
5use crate::{helper, Program};
6
7/// The kind of helper program to use.
8#[derive(Debug, Clone, Eq, PartialEq)]
9pub enum Kind {
10    /// The built-in `git credential` helper program, part of any `git` distribution.
11    Builtin,
12    /// A custom credentials helper, as identified just by the name with optional arguments
13    ExternalName {
14        /// The name like `foo` along with optional args, like `foo --arg --bar="a b"`, with arguments using `sh` shell quoting rules.
15        /// The program executed will be `git-credential-foo [args]` if `name_and_args` starts with `foo [args]`.
16        /// Note that a shell is only used if it's needed.
17        name_and_args: BString,
18    },
19    /// A custom credentials helper, as identified just by the absolute path to the program and optional arguments. The program is executed through a shell.
20    ExternalPath {
21        /// The absolute path to the executable, like `/path/to/exe` along with optional args, like `/path/to/exe --arg --bar="a b"`, with arguments using `sh`
22        /// shell quoting rules.
23        path_and_args: BString,
24    },
25    /// A script to execute with `sh`.
26    ExternalShellScript(BString),
27}
28
29/// Initialization
30impl Program {
31    /// Create a new program of the given `kind`.
32    pub fn from_kind(kind: Kind) -> Self {
33        Program {
34            kind,
35            child: None,
36            stderr: true,
37        }
38    }
39
40    /// Parse the given input as per the custom helper definition, supporting `!<script>`, `name` and `/absolute/name`, the latter two
41    /// also support arguments which are ignored here.
42    pub fn from_custom_definition(input: impl Into<BString>) -> Self {
43        fn from_custom_definition_inner(mut input: BString) -> Program {
44            let kind = if input.starts_with(b"!") {
45                input.remove(0);
46                Kind::ExternalShellScript(input)
47            } else {
48                let path = gix_path::from_bstr(
49                    input
50                        .find_byte(b' ')
51                        .map_or(input.as_slice(), |pos| &input[..pos])
52                        .as_bstr(),
53                );
54                if gix_path::is_absolute(path) {
55                    Kind::ExternalPath { path_and_args: input }
56                } else {
57                    Kind::ExternalName { name_and_args: input }
58                }
59            };
60            Program {
61                kind,
62                child: None,
63                stderr: true,
64            }
65        }
66        from_custom_definition_inner(input.into())
67    }
68
69    /// Convert the program into the respective command, suitable to invoke `action`.
70    pub fn to_command(&self, action: &helper::Action) -> std::process::Command {
71        let git_program = gix_path::env::exe_invocation();
72        let mut cmd = match &self.kind {
73            Kind::Builtin => {
74                let mut cmd = Command::from(gix_command::prepare(git_program));
75                cmd.arg("credential").arg(action.as_arg(false));
76                cmd
77            }
78            Kind::ExternalName { name_and_args } => {
79                let mut args = name_and_args.clone();
80                args.insert_str(0, "credential-");
81                args.insert_str(0, " ");
82                args.insert_str(0, git_program.to_string_lossy().as_ref());
83                gix_command::prepare(gix_path::from_bstr(args.as_bstr()).into_owned())
84                    .arg(action.as_arg(true))
85                    .command_may_be_shell_script_allow_manual_argument_splitting()
86                    .into()
87            }
88            Kind::ExternalShellScript(for_shell)
89            | Kind::ExternalPath {
90                path_and_args: for_shell,
91            } => gix_command::prepare(gix_path::from_bstr(for_shell.as_bstr()).as_ref())
92                .command_may_be_shell_script()
93                .arg(action.as_arg(true))
94                .into(),
95        };
96        cmd.stdin(Stdio::piped())
97            .stdout(if action.expects_output() {
98                Stdio::piped()
99            } else {
100                Stdio::null()
101            })
102            .stderr(if self.stderr { Stdio::inherit() } else { Stdio::null() });
103        cmd
104    }
105}
106
107/// Builder
108impl Program {
109    /// By default `stderr` of programs is inherited and typically displayed in the terminal.
110    pub fn suppress_stderr(mut self) -> Self {
111        self.stderr = false;
112        self
113    }
114}
115
116impl Program {
117    pub(crate) fn start(
118        &mut self,
119        action: &helper::Action,
120    ) -> std::io::Result<(std::process::ChildStdin, Option<std::process::ChildStdout>)> {
121        assert!(self.child.is_none(), "BUG: must not call `start()` twice");
122        let mut cmd = self.to_command(action);
123        gix_trace::debug!(cmd = ?cmd, "launching credential helper");
124        let mut child = cmd.spawn()?;
125        let stdin = child.stdin.take().expect("stdin to be configured");
126        let stdout = child.stdout.take();
127
128        self.child = child.into();
129        Ok((stdin, stdout))
130    }
131
132    pub(crate) fn finish(&mut self) -> std::io::Result<()> {
133        let mut child = self.child.take().expect("Call `start()` before calling finish()");
134        let status = child.wait()?;
135        if status.success() {
136            Ok(())
137        } else {
138            Err(std::io::Error::new(
139                std::io::ErrorKind::Other,
140                format!("Credentials helper program failed with status code {:?}", status.code()),
141            ))
142        }
143    }
144}
145
146///
147pub mod main;
148pub use main::function::main;