summaryrefslogtreecommitdiffstats
path: root/vendor/gix-prompt/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-prompt/src
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-prompt/src')
-rw-r--r--vendor/gix-prompt/src/lib.rs76
-rw-r--r--vendor/gix-prompt/src/types.rs84
-rw-r--r--vendor/gix-prompt/src/unix.rs93
3 files changed, 253 insertions, 0 deletions
diff --git a/vendor/gix-prompt/src/lib.rs b/vendor/gix-prompt/src/lib.rs
new file mode 100644
index 000000000..75cc1ebd5
--- /dev/null
+++ b/vendor/gix-prompt/src/lib.rs
@@ -0,0 +1,76 @@
+//! Git style prompting with support for `GIT_ASKPASS` and `askpass` program configuration.
+//!
+//! ### Compatibility
+//!
+//! This is a unix-only crate which will return with an error when trying to obtain any prompt on other platforms.
+//! On those platforms it is common to have helpers which perform this task so it shouldn't be a problem.
+#![deny(rust_2018_idioms, missing_docs)]
+#![forbid(unsafe_code)]
+
+mod types;
+pub use types::{Error, Mode, Options};
+
+///
+pub mod unix;
+#[cfg(unix)]
+use unix::imp;
+
+#[cfg(not(unix))]
+mod imp {
+ use crate::{Error, Options};
+
+ pub(crate) fn ask(_prompt: &str, _opts: &Options<'_>) -> Result<String, Error> {
+ Err(Error::UnsupportedPlatform)
+ }
+}
+
+/// Ask the user given a `prompt`, returning the result.
+pub fn ask(prompt: &str, opts: &Options<'_>) -> Result<String, Error> {
+ if let Some(askpass) = opts.askpass.as_deref() {
+ match gix_command::prepare(askpass).arg(prompt).spawn() {
+ Ok(cmd) => {
+ if let Some(mut stdout) = cmd
+ .wait_with_output()
+ .ok()
+ .and_then(|out| String::from_utf8(out.stdout).ok())
+ {
+ if stdout.ends_with('\n') {
+ stdout.pop();
+ }
+ if stdout.ends_with('\r') {
+ stdout.pop();
+ }
+ return Ok(stdout);
+ }
+ }
+ Err(err) => eprintln!("Cannot run askpass program: {askpass:?} with error: {err}"),
+ }
+ }
+ imp::ask(prompt, opts)
+}
+
+/// Ask for information typed by the user into the terminal after showing the prompt`, like `"Username: `.
+///
+/// Use [`ask()`] for more control.
+pub fn openly(prompt: impl AsRef<str>) -> Result<String, Error> {
+ imp::ask(
+ prompt.as_ref(),
+ &Options {
+ mode: Mode::Visible,
+ askpass: None,
+ },
+ )
+}
+
+/// Ask for information _securely_ after showing the `prompt` (like `"password: "`) by not showing what's typed.
+///
+/// Use [`ask()`] for more control.
+pub fn securely(prompt: impl AsRef<str>) -> Result<String, Error> {
+ imp::ask(
+ prompt.as_ref(),
+ &Options {
+ mode: Mode::Hidden,
+ askpass: None,
+ },
+ )
+}
diff --git a/vendor/gix-prompt/src/types.rs b/vendor/gix-prompt/src/types.rs
new file mode 100644
index 000000000..cc938dd3a
--- /dev/null
+++ b/vendor/gix-prompt/src/types.rs
@@ -0,0 +1,84 @@
+use std::{borrow::Cow, convert::TryFrom, path::Path};
+
+/// The error returned by [ask()][crate::ask()].
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error("Terminal prompts are disabled")]
+ Disabled,
+ #[error("The current platform has no implementation for prompting in the terminal")]
+ UnsupportedPlatform,
+ #[error(
+ "Failed to open terminal at {:?} for writing prompt, or to write it",
+ crate::unix::TTY_PATH
+ )]
+ TtyIo(#[from] std::io::Error),
+ #[cfg(unix)]
+ #[error("Failed to obtain or set terminal configuration")]
+ TerminalConfiguration(#[from] nix::errno::Errno),
+}
+
+/// The way the user is prompted.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Mode {
+ /// Visibly show user input.
+ Visible,
+ /// Do not show user input, suitable for sensitive data.
+ Hidden,
+ /// Do not prompt the user at all but rather abort with an error. This is useful in conjunction with [Options::askpass].
+ Disable,
+}
+
+impl Default for Mode {
+ fn default() -> Self {
+ Mode::Hidden
+ }
+}
+
+/// The options used in `[ask()]`.
+#[derive(Default, Clone)]
+pub struct Options<'a> {
+ /// The path or name (for lookup in `PATH`) to the askpass program to call before prompting the user.
+ ///
+ /// It's called like this `askpass <prompt>`, but note that it won't know if the input should be hidden or not.
+ pub askpass: Option<Cow<'a, Path>>,
+ /// The way the user is prompted.
+ pub mode: Mode,
+}
+
+impl Options<'_> {
+ /// Change this instance to incorporate information from the environment.
+ ///
+ /// - if `use_git_askpass` is true, use `GIT_ASKPASS` to override any existing [`askpass`][Options::askpass] program
+ /// - otherwise fall back to the [`askpass`][Options::askpass] program already set
+ /// - or try to use the `SSH_ASKPASS` if `use_ssh_askpass` is true
+ ///
+ /// At the and of this process, the `askpass` program may be set depending on the rules above.
+ ///
+ /// Lastly, if `use_git_terminal_prompt` is set, use the `GIT_TERMINAL_PROMPT` environment variable and evaluate it as boolean,
+ /// and if false, set [`mode`][Options::mode] to `disable`.
+ pub fn apply_environment(
+ mut self,
+ use_git_askpass: bool,
+ use_ssh_askpass: bool,
+ use_git_terminal_prompt: bool,
+ ) -> Self {
+ if let Some(askpass) = use_git_askpass.then(|| std::env::var_os("GIT_ASKPASS")).flatten() {
+ self.askpass = Some(Cow::Owned(askpass.into()))
+ }
+ if self.askpass.is_none() {
+ if let Some(askpass) = use_ssh_askpass.then(|| std::env::var_os("SSH_ASKPASS")).flatten() {
+ self.askpass = Some(Cow::Owned(askpass.into()))
+ }
+ }
+ self.mode = use_git_terminal_prompt
+ .then(|| {
+ std::env::var_os("GIT_TERMINAL_PROMPT")
+ .and_then(|val| gix_config_value::Boolean::try_from(val).ok())
+ .and_then(|allow| (!allow.0).then_some(Mode::Disable))
+ })
+ .flatten()
+ .unwrap_or(self.mode);
+ self
+ }
+}
diff --git a/vendor/gix-prompt/src/unix.rs b/vendor/gix-prompt/src/unix.rs
new file mode 100644
index 000000000..a08d2d777
--- /dev/null
+++ b/vendor/gix-prompt/src/unix.rs
@@ -0,0 +1,93 @@
+/// The path to the default TTY on linux
+pub const TTY_PATH: &str = "/dev/tty";
+
+#[cfg(unix)]
+pub(crate) mod imp {
+ use std::{
+ io::{BufRead, Write},
+ os::unix::io::{AsRawFd, RawFd},
+ };
+
+ use nix::sys::{termios, termios::Termios};
+ use parking_lot::{const_mutex, lock_api::MutexGuard, Mutex, RawMutex};
+
+ use crate::{unix::TTY_PATH, Error, Mode, Options};
+
+ static TERM_STATE: Mutex<Option<Termios>> = const_mutex(None);
+
+ /// Ask the user given a `prompt`, returning the result.
+ pub(crate) fn ask(prompt: &str, Options { mode, .. }: &Options<'_>) -> Result<String, Error> {
+ match mode {
+ Mode::Disable => Err(Error::Disabled),
+ Mode::Hidden => {
+ let state = TERM_STATE.lock();
+ let mut in_out = std::fs::OpenOptions::new().write(true).read(true).open(TTY_PATH)?;
+ let restore = save_term_state_and_disable_echo(state, in_out.as_raw_fd())?;
+ in_out.write_all(prompt.as_bytes())?;
+
+ let mut buf_read = std::io::BufReader::with_capacity(64, in_out);
+ let mut out = String::with_capacity(64);
+ buf_read.read_line(&mut out)?;
+
+ out.pop();
+ if out.ends_with('\r') {
+ out.pop();
+ }
+ restore.now()?;
+ Ok(out)
+ }
+ Mode::Visible => {
+ let mut in_out = std::fs::OpenOptions::new().write(true).read(true).open(TTY_PATH)?;
+ in_out.write_all(prompt.as_bytes())?;
+
+ let mut buf_read = std::io::BufReader::with_capacity(64, in_out);
+ let mut out = String::with_capacity(64);
+ buf_read.read_line(&mut out)?;
+ Ok(out.trim_end().to_owned())
+ }
+ }
+ }
+
+ type TermiosGuard<'a> = MutexGuard<'a, RawMutex, Option<Termios>>;
+
+ struct RestoreTerminalStateOnDrop<'a> {
+ state: TermiosGuard<'a>,
+ fd: RawFd,
+ }
+
+ impl<'a> RestoreTerminalStateOnDrop<'a> {
+ fn now(mut self) -> Result<(), Error> {
+ let state = self.state.take().expect("BUG: we exist only if something is saved");
+ termios::tcsetattr(self.fd, termios::SetArg::TCSAFLUSH, &state)?;
+ Ok(())
+ }
+ }
+
+ impl<'a> Drop for RestoreTerminalStateOnDrop<'a> {
+ fn drop(&mut self) {
+ if let Some(state) = self.state.take() {
+ termios::tcsetattr(self.fd, termios::SetArg::TCSAFLUSH, &state).ok();
+ }
+ }
+ }
+
+ fn save_term_state_and_disable_echo(
+ mut state: TermiosGuard<'_>,
+ fd: RawFd,
+ ) -> Result<RestoreTerminalStateOnDrop<'_>, Error> {
+ assert!(
+ state.is_none(),
+ "BUG: recursive calls are not possible and we restore afterwards"
+ );
+
+ let prev = termios::tcgetattr(fd)?;
+ let mut new = prev.clone();
+ *state = prev.into();
+
+ new.local_flags &= !termios::LocalFlags::ECHO;
+ new.local_flags |= termios::LocalFlags::ECHONL;
+ termios::tcsetattr(fd, termios::SetArg::TCSAFLUSH, &new)?;
+
+ Ok(RestoreTerminalStateOnDrop { fd, state })
+ }
+}