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:
parent
748a102919
commit
ff690b0ab4
3 changed files with 58 additions and 36 deletions
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
console.clear();
|
|
||||||
console.log("Are you sure you want to continue?");
|
console.log("Are you sure you want to continue?");
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue