diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:35 +0000 |
commit | 7e5d7eea9c580ef4b41a765bde624af431942b96 (patch) | |
tree | 2c0d9ca12878fc4525650aa4e54d77a81a07cc09 /vendor/gix-prompt/src | |
parent | Adding debian version 1.70.0+dfsg1-9. (diff) | |
download | rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.tar.xz rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.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.rs | 76 | ||||
-rw-r--r-- | vendor/gix-prompt/src/types.rs | 84 | ||||
-rw-r--r-- | vendor/gix-prompt/src/unix.rs | 93 |
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 }) + } +} |