1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-22 15:06:54 -05:00

chore: fix flaky stdio_streams_are_locked_in_permission_prompt (#19443)

This commit is contained in:
David Sherret 2023-06-09 13:24:39 -04:00 committed by GitHub
parent 748a102919
commit ff690b0ab4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 36 deletions

View file

@ -4370,6 +4370,7 @@ fn stdio_streams_are_locked_in_permission_prompt() {
std::thread::sleep(Duration::from_millis(50)); // give the other thread some time to output std::thread::sleep(Duration::from_millis(50)); // give the other thread some time to output
console.write_line_raw("invalid"); console.write_line_raw("invalid");
console.expect("Unrecognized option."); console.expect("Unrecognized option.");
console.expect("Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions)");
console.write_line_raw("y"); console.write_line_raw("y");
console.expect("Granted write access to"); console.expect("Granted write access to");

View file

@ -1,2 +1 @@
console.clear();
console.log("Are you sure you want to continue?"); console.log("Are you sure you want to continue?");

View file

@ -5,6 +5,10 @@ use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex; use deno_core::parking_lot::Mutex;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::fmt::Write; use std::fmt::Write;
use std::io::BufRead;
use std::io::StderrLock;
use std::io::StdinLock;
use std::io::Write as IoWrite;
/// Helper function to strip ansi codes and ASCII control characters. /// Helper function to strip ansi codes and ASCII control characters.
fn strip_ansi_codes_and_ascii_control(s: &str) -> std::borrow::Cow<str> { fn strip_ansi_codes_and_ascii_control(s: &str) -> std::borrow::Cow<str> {
@ -85,7 +89,10 @@ impl PermissionPrompter for TtyPrompter {
}; };
#[cfg(unix)] #[cfg(unix)]
fn clear_stdin() -> Result<(), AnyError> { fn clear_stdin(
_stdin_lock: &mut StdinLock,
_stderr_lock: &mut StderrLock,
) -> Result<(), AnyError> {
// TODO(bartlomieju): // TODO(bartlomieju):
#[allow(clippy::undocumented_unsafe_blocks)] #[allow(clippy::undocumented_unsafe_blocks)]
let r = unsafe { libc::tcflush(0, libc::TCIFLUSH) }; let r = unsafe { libc::tcflush(0, libc::TCIFLUSH) };
@ -94,7 +101,10 @@ impl PermissionPrompter for TtyPrompter {
} }
#[cfg(not(unix))] #[cfg(not(unix))]
fn clear_stdin() -> Result<(), AnyError> { fn clear_stdin(
stdin_lock: &mut StdinLock,
stderr_lock: &mut StderrLock,
) -> Result<(), AnyError> {
use deno_core::anyhow::bail; use deno_core::anyhow::bail;
use winapi::shared::minwindef::TRUE; use winapi::shared::minwindef::TRUE;
use winapi::shared::minwindef::UINT; use winapi::shared::minwindef::UINT;
@ -118,11 +128,11 @@ impl PermissionPrompter for TtyPrompter {
// emulate an enter key press to clear any line buffered console characters // emulate an enter key press to clear any line buffered console characters
emulate_enter_key_press(stdin)?; emulate_enter_key_press(stdin)?;
// read the buffered line or enter key press // read the buffered line or enter key press
read_stdin_line()?; read_stdin_line(stdin_lock)?;
// check if our emulated key press was executed // check if our emulated key press was executed
if is_input_buffer_empty(stdin)? { if is_input_buffer_empty(stdin)? {
// if so, move the cursor up to prevent a blank line // if so, move the cursor up to prevent a blank line
move_cursor_up()?; move_cursor_up(stderr_lock)?;
} else { } else {
// the emulated key press is still pending, so a buffered line was read // the emulated key press is still pending, so a buffered line was read
// and we can flush the emulated key press // and we can flush the emulated key press
@ -181,36 +191,35 @@ impl PermissionPrompter for TtyPrompter {
Ok(events_read == 0) Ok(events_read == 0)
} }
fn move_cursor_up() -> Result<(), AnyError> { fn move_cursor_up(stderr_lock: &mut StderrLock) -> Result<(), AnyError> {
use std::io::Write; write!(stderr_lock, "\x1B[1A")?;
write!(std::io::stderr(), "\x1B[1A")?;
Ok(()) Ok(())
} }
fn read_stdin_line() -> Result<(), AnyError> { fn read_stdin_line(stdin_lock: &mut StdinLock) -> Result<(), AnyError> {
let mut input = String::new(); let mut input = String::new();
let stdin = std::io::stdin(); stdin_lock.read_line(&mut input)?;
stdin.read_line(&mut input)?;
Ok(()) Ok(())
} }
} }
// Clear n-lines in terminal and move cursor to the beginning of the line. // Clear n-lines in terminal and move cursor to the beginning of the line.
fn clear_n_lines(n: usize) { fn clear_n_lines(stderr_lock: &mut StderrLock, n: usize) {
eprint!("\x1B[{n}A\x1B[0J"); write!(stderr_lock, "\x1B[{n}A\x1B[0J").unwrap();
}
// For security reasons we must consume everything in stdin so that previously
// buffered data cannot effect the prompt.
if let Err(err) = clear_stdin() {
eprintln!("Error clearing stdin for permission prompt. {err:#}");
return PromptResponse::Deny; // don't grant permission if this fails
} }
// Lock stdio streams, so no other output is written while the prompt is // Lock stdio streams, so no other output is written while the prompt is
// displayed. // displayed.
let _stdout_guard = std::io::stdout().lock(); let stdout_lock = std::io::stdout().lock();
let _stderr_guard = std::io::stderr().lock(); let mut stderr_lock = std::io::stderr().lock();
let mut stdin_lock = std::io::stdin().lock();
// For security reasons we must consume everything in stdin so that previously
// buffered data cannot affect the prompt.
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 message = strip_ansi_codes_and_ascii_control(message); let message = strip_ansi_codes_and_ascii_control(message);
let name = strip_ansi_codes_and_ascii_control(name); let name = strip_ansi_codes_and_ascii_control(name);
@ -238,13 +247,12 @@ impl PermissionPrompter for TtyPrompter {
write!(&mut output, "└ {}", colors::bold("Allow?")).unwrap(); write!(&mut output, "└ {}", colors::bold("Allow?")).unwrap();
write!(&mut output, " {opts} > ").unwrap(); write!(&mut output, " {opts} > ").unwrap();
eprint!("{}", output); stderr_lock.write_all(output.as_bytes()).unwrap();
} }
let value = loop { let value = loop {
let mut input = String::new(); let mut input = String::new();
let stdin = std::io::stdin(); let result = stdin_lock.read_line(&mut input);
let result = stdin.read_line(&mut input);
if result.is_err() { if result.is_err() {
break PromptResponse::Deny; break PromptResponse::Deny;
}; };
@ -254,34 +262,48 @@ impl PermissionPrompter for TtyPrompter {
}; };
match ch { match ch {
'y' | 'Y' => { 'y' | 'Y' => {
clear_n_lines(if api_name.is_some() { 4 } else { 3 }); clear_n_lines(
&mut stderr_lock,
if api_name.is_some() { 4 } else { 3 },
);
let msg = format!("Granted {message}."); let msg = format!("Granted {message}.");
eprintln!("{}", colors::bold(&msg)); writeln!(stderr_lock, "{}", colors::bold(&msg)).unwrap();
break PromptResponse::Allow; break PromptResponse::Allow;
} }
'n' | 'N' => { 'n' | 'N' => {
clear_n_lines(if api_name.is_some() { 4 } else { 3 }); clear_n_lines(
&mut stderr_lock,
if api_name.is_some() { 4 } else { 3 },
);
let msg = format!("Denied {message}."); let msg = format!("Denied {message}.");
eprintln!("{}", colors::bold(&msg)); writeln!(stderr_lock, "{}", colors::bold(&msg)).unwrap();
break PromptResponse::Deny; break PromptResponse::Deny;
} }
'A' if is_unary => { 'A' if is_unary => {
clear_n_lines(if api_name.is_some() { 4 } else { 3 }); clear_n_lines(
&mut stderr_lock,
if api_name.is_some() { 4 } else { 3 },
);
let msg = format!("Granted all {name} access."); let msg = format!("Granted all {name} access.");
eprintln!("{}", colors::bold(&msg)); writeln!(stderr_lock, "{}", colors::bold(&msg)).unwrap();
break PromptResponse::AllowAll; break PromptResponse::AllowAll;
} }
_ => { _ => {
// If we don't get a recognized option try again. // If we don't get a recognized option try again.
clear_n_lines(1); clear_n_lines(&mut stderr_lock, 1);
eprint!("{}", colors::bold("Unrecognized option. Allow?")); write!(
eprint!(" {opts} > "); stderr_lock,
"└ {} {opts} > ",
colors::bold("Unrecognized option. Allow?")
)
.unwrap();
} }
}; };
}; };
drop(_stdout_guard); drop(stdout_lock);
drop(_stderr_guard); drop(stderr_lock);
drop(stdin_lock);
value value
} }