summaryrefslogtreecommitdiffstats
path: root/vendor/gix-credentials/src/program
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-credentials/src/program')
-rw-r--r--vendor/gix-credentials/src/program/main.rs110
-rw-r--r--vendor/gix-credentials/src/program/mod.rs131
2 files changed, 241 insertions, 0 deletions
diff --git a/vendor/gix-credentials/src/program/main.rs b/vendor/gix-credentials/src/program/main.rs
new file mode 100644
index 000000000..062bcfc99
--- /dev/null
+++ b/vendor/gix-credentials/src/program/main.rs
@@ -0,0 +1,110 @@
+use std::{convert::TryFrom, ffi::OsString};
+
+use bstr::BString;
+
+/// The action passed to the credential helper implementation in [`main()`][crate::program::main()].
+#[derive(Debug, Copy, Clone)]
+pub enum Action {
+ /// Get credentials for a url.
+ Get,
+ /// Store credentials provided in the given context.
+ Store,
+ /// Erase credentials identified by the given context.
+ Erase,
+}
+
+impl TryFrom<OsString> for Action {
+ type Error = Error;
+
+ fn try_from(value: OsString) -> Result<Self, Self::Error> {
+ Ok(match value.to_str() {
+ Some("fill") | Some("get") => Action::Get,
+ Some("approve") | Some("store") => Action::Store,
+ Some("reject") | Some("erase") => Action::Erase,
+ _ => return Err(Error::ActionInvalid { name: value }),
+ })
+ }
+}
+
+impl Action {
+ /// Return ourselves as string representation, similar to what would be passed as argument to a credential helper.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Action::Get => "get",
+ Action::Store => "store",
+ Action::Erase => "erase",
+ }
+ }
+}
+
+/// The error of [`main()`][crate::program::main()].
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error("Action named {name:?} is invalid, need 'get', 'store', 'erase' or 'fill', 'approve', 'reject'")]
+ ActionInvalid { name: OsString },
+ #[error("The first argument must be the action to perform")]
+ ActionMissing,
+ #[error(transparent)]
+ Helper {
+ source: Box<dyn std::error::Error + Send + Sync + 'static>,
+ },
+ #[error(transparent)]
+ Io(#[from] std::io::Error),
+ #[error(transparent)]
+ Context(#[from] crate::protocol::context::decode::Error),
+ #[error("Credentials for {url:?} could not be obtained")]
+ CredentialsMissing { url: BString },
+ #[error("The url wasn't provided in input - the git credentials protocol mandates this")]
+ UrlMissing,
+}
+
+pub(crate) mod function {
+ use std::{convert::TryInto, ffi::OsString};
+
+ use crate::{
+ program::main::{Action, Error},
+ protocol::Context,
+ };
+
+ /// Invoke a custom credentials helper which receives program `args`, with the first argument being the
+ /// action to perform (as opposed to the program name).
+ /// Then read context information from `stdin` and if the action is `Action::Get`, then write the result to `stdout`.
+ /// `credentials` is the API version of such call, where`Ok(Some(context))` returns credentials, and `Ok(None)` indicates
+ /// no credentials could be found for `url`, which is always set when called.
+ ///
+ /// Call this function from a programs `main`, passing `std::env::args_os()`, `stdin()` and `stdout` accordingly, along with
+ /// your own helper implementation.
+ pub fn main<CredentialsFn, E>(
+ args: impl IntoIterator<Item = OsString>,
+ mut stdin: impl std::io::Read,
+ stdout: impl std::io::Write,
+ credentials: CredentialsFn,
+ ) -> Result<(), Error>
+ where
+ CredentialsFn: FnOnce(Action, Context) -> Result<Option<Context>, E>,
+ E: std::error::Error + Send + Sync + 'static,
+ {
+ let action: Action = args.into_iter().next().ok_or(Error::ActionMissing)?.try_into()?;
+ let mut buf = Vec::<u8>::with_capacity(512);
+ stdin.read_to_end(&mut buf)?;
+ let ctx = Context::from_bytes(&buf)?;
+ if ctx.url.is_none() {
+ return Err(Error::UrlMissing);
+ }
+ let res = credentials(action, ctx).map_err(|err| Error::Helper { source: Box::new(err) })?;
+ match (action, res) {
+ (Action::Get, None) => {
+ return Err(Error::CredentialsMissing {
+ url: Context::from_bytes(&buf)?.url.expect("present and checked above"),
+ })
+ }
+ (Action::Get, Some(ctx)) => ctx.write_to(stdout)?,
+ (Action::Erase | Action::Store, None) => {}
+ (Action::Erase | Action::Store, Some(_)) => {
+ panic!("BUG: credentials helper must not return context for erase or store actions")
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/vendor/gix-credentials/src/program/mod.rs b/vendor/gix-credentials/src/program/mod.rs
new file mode 100644
index 000000000..e13e0a5ec
--- /dev/null
+++ b/vendor/gix-credentials/src/program/mod.rs
@@ -0,0 +1,131 @@
+use std::process::{Command, Stdio};
+
+use bstr::{BString, ByteSlice, ByteVec};
+
+use crate::{helper, Program};
+
+/// The kind of helper program to use.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Kind {
+ /// The built-in `git credential` helper program, part of any `git` distribution.
+ Builtin,
+ /// A custom credentials helper, as identified just by the name with optional arguments
+ ExternalName {
+ /// The name like `foo` along with optional args, like `foo --arg --bar="a b"`, with arguments using `sh` shell quoting rules.
+ /// The program executed will be `git-credential-foo` if `name_and_args` starts with `foo`.
+ name_and_args: BString,
+ },
+ /// A custom credentials helper, as identified just by the absolute path to the program and optional arguments. The program is executed through a shell.
+ ExternalPath {
+ /// 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`
+ /// shell quoting rules.
+ path_and_args: BString,
+ },
+ /// A script to execute with `sh`.
+ ExternalShellScript(BString),
+}
+
+/// Initialization
+impl Program {
+ /// Create a new program of the given `kind`.
+ pub fn from_kind(kind: Kind) -> Self {
+ Program {
+ kind,
+ child: None,
+ stderr: true,
+ }
+ }
+
+ /// Parse the given input as per the custom helper definition, supporting `!<script>`, `name` and `/absolute/name`, the latter two
+ /// also support arguments which are ignored here.
+ pub fn from_custom_definition(input: impl Into<BString>) -> Self {
+ let mut input = input.into();
+ let kind = if input.starts_with(b"!") {
+ input.remove(0);
+ Kind::ExternalShellScript(input)
+ } else {
+ let path = gix_path::from_bstr(
+ input
+ .find_byte(b' ')
+ .map_or(input.as_slice(), |pos| &input[..pos])
+ .as_bstr(),
+ );
+ if gix_path::is_absolute(path) {
+ Kind::ExternalPath { path_and_args: input }
+ } else {
+ input.insert_str(0, "git credential-");
+ Kind::ExternalName { name_and_args: input }
+ }
+ };
+ Program {
+ kind,
+ child: None,
+ stderr: true,
+ }
+ }
+}
+
+/// Builder
+impl Program {
+ /// By default `stderr` of programs is inherited and typically displayed in the terminal.
+ pub fn suppress_stderr(mut self) -> Self {
+ self.stderr = false;
+ self
+ }
+}
+
+impl Program {
+ pub(crate) fn start(
+ &mut self,
+ action: &helper::Action,
+ ) -> std::io::Result<(std::process::ChildStdin, Option<std::process::ChildStdout>)> {
+ assert!(self.child.is_none(), "BUG: must not call `start()` twice");
+ let mut cmd = match &self.kind {
+ Kind::Builtin => {
+ let mut cmd = Command::new(cfg!(windows).then(|| "git.exe").unwrap_or("git"));
+ cmd.arg("credential").arg(action.as_arg(false));
+ cmd
+ }
+ Kind::ExternalShellScript(for_shell)
+ | Kind::ExternalName {
+ name_and_args: for_shell,
+ }
+ | Kind::ExternalPath {
+ path_and_args: for_shell,
+ } => gix_command::prepare(gix_path::from_bstr(for_shell.as_bstr()).as_ref())
+ .with_shell()
+ .arg(action.as_arg(true))
+ .into(),
+ };
+ cmd.stdin(Stdio::piped())
+ .stdout(if action.expects_output() {
+ Stdio::piped()
+ } else {
+ Stdio::null()
+ })
+ .stderr(if self.stderr { Stdio::inherit() } else { Stdio::null() });
+ let mut child = cmd.spawn()?;
+ let stdin = child.stdin.take().expect("stdin to be configured");
+ let stdout = child.stdout.take();
+
+ self.child = child.into();
+ Ok((stdin, stdout))
+ }
+
+ pub(crate) fn finish(&mut self) -> std::io::Result<()> {
+ let mut child = self.child.take().expect("Call `start()` before calling finish()");
+ let status = child.wait()?;
+ if status.success() {
+ Ok(())
+ } else {
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ format!("Credentials helper program failed with status code {:?}", status.code()),
+ ))
+ }
+ }
+}
+
+///
+pub mod main;
+pub use main::function::main;