diff options
Diffstat (limited to 'vendor/gix-credentials/src/program')
-rw-r--r-- | vendor/gix-credentials/src/program/main.rs | 110 | ||||
-rw-r--r-- | vendor/gix-credentials/src/program/mod.rs | 131 |
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; |