diff options
author | Matt Mastracci <matthew@mastracci.com> | 2024-04-02 15:55:06 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-02 15:55:06 -0600 |
commit | 3b9fd1af804e4fe534798ec2d7da440d97ba610c (patch) | |
tree | 0719c35c46a4d1210dcb48f8b69304744ca2fcc0 /runtime/permissions/prompter.rs | |
parent | cc4ede41a7d39e318d18d3bccf797d232a5b286a (diff) |
fix(cli): Enforce a human delay in prompt to fix paste problem (#23184)
The permission prompt doesn't wait for quiescent input, so someone
pasting a large text file into the console may end up losing the prompt.
We enforce a minimum human delay and wait for a 100ms quiescent period
before we write and accept prompt input to avoid this problem.
This does require adding a human delay in all prompt tests, but that's
pretty straightforward. I rewrote the locked stdout/stderr test while I
was in here.
Diffstat (limited to 'runtime/permissions/prompter.rs')
-rw-r--r-- | runtime/permissions/prompter.rs | 64 |
1 files changed, 57 insertions, 7 deletions
diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs index da5979f0a..f75fe466a 100644 --- a/runtime/permissions/prompter.rs +++ b/runtime/permissions/prompter.rs @@ -91,16 +91,58 @@ pub trait PermissionPrompter: Send + Sync { } pub struct TtyPrompter; - #[cfg(unix)] fn clear_stdin( _stdin_lock: &mut StdinLock, _stderr_lock: &mut StderrLock, ) -> Result<(), AnyError> { - // TODO(bartlomieju): - #[allow(clippy::undocumented_unsafe_blocks)] - let r = unsafe { libc::tcflush(0, libc::TCIFLUSH) }; - assert_eq!(r, 0); + use deno_core::anyhow::bail; + use std::mem::MaybeUninit; + + const STDIN_FD: i32 = 0; + + // SAFETY: use libc to flush stdin + unsafe { + // Create fd_set for select + let mut raw_fd_set = MaybeUninit::<libc::fd_set>::uninit(); + libc::FD_ZERO(raw_fd_set.as_mut_ptr()); + libc::FD_SET(STDIN_FD, raw_fd_set.as_mut_ptr()); + + loop { + let r = libc::tcflush(STDIN_FD, libc::TCIFLUSH); + if r != 0 { + bail!("clear_stdin failed (tcflush)"); + } + + // Initialize timeout for select to be 100ms + let mut timeout = libc::timeval { + tv_sec: 0, + tv_usec: 100_000, + }; + + // Call select with the stdin file descriptor set + let r = libc::select( + STDIN_FD + 1, // nfds should be set to the highest-numbered file descriptor in any of the three sets, plus 1. + raw_fd_set.as_mut_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut timeout, + ); + + // Check if select returned an error + if r < 0 { + bail!("clear_stdin failed (select)"); + } + + // Check if select returned due to timeout (stdin is quiescent) + if r == 0 { + break; // Break out of the loop as stdin is quiescent + } + + // If select returned due to data available on stdin, clear it by looping around to flush + } + } + Ok(()) } @@ -291,9 +333,17 @@ impl PermissionPrompter for TtyPrompter { } let value = loop { + // Clear stdin each time we loop around in case the user accidentally pasted + // multiple lines or otherwise did something silly to generate a torrent of + // input. + if let Err(err) = clear_stdin(&mut stdin_lock, &mut stderr_lock) { + eprintln!("Error clearing stdin for permission prompt. {err:#}"); + return PromptResponse::Deny; // don't grant permission if this fails + } + let mut input = String::new(); let result = stdin_lock.read_line(&mut input); - if result.is_err() { + if result.is_err() || input.len() > 2 { break PromptResponse::Deny; }; let ch = match input.chars().next() { @@ -310,7 +360,7 @@ impl PermissionPrompter for TtyPrompter { writeln!(stderr_lock, "✅ {}", colors::bold(&msg)).unwrap(); break PromptResponse::Allow; } - 'n' | 'N' => { + 'n' | 'N' | '\x1b' => { clear_n_lines( &mut stderr_lock, if api_name.is_some() { 4 } else { 3 }, |