diff --git a/Cargo.lock b/Cargo.lock index 3233b03c96..97d27d34cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,9 +525,9 @@ dependencies = [ [[package]] name = "console_static_text" -version = "0.3.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f166cdfb9db0607e2079b382ba64bc4164344006c733b95c1ecfa782a180a34a" +checksum = "953d2c3cf53213a4eccdbe8f2e0b49b5d0f77e87a2a9060117bbf9346f92b64e" dependencies = [ "unicode-width", "vte", @@ -1285,6 +1285,7 @@ name = "deno_runtime" version = "0.99.0" dependencies = [ "atty", + "console_static_text", "deno_ast", "deno_broadcast_channel", "deno_cache", diff --git a/Cargo.toml b/Cargo.toml index da026daf14..2e9d34b850 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ base64 = "=0.13.1" bencher = "0.1" bytes = "=1.2.1" cache_control = "=0.2.0" +console_static_text = "=0.7.1" data-url = "=0.2.0" dlopen = "0.1.8" encoding_rs = "=0.8.31" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 65e4359c2e..27409ec91a 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -62,7 +62,7 @@ chrono = { version = "=0.4.22", default-features = false, features = ["clock"] } clap = "=3.1.12" clap_complete = "=3.1.2" clap_complete_fig = "=3.1.5" -console_static_text = "=0.3.4" +console_static_text.workspace = true data-url.workspace = true dissimilar = "=1.0.4" dprint-plugin-json = "=0.17.0" diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 2393119755..c3a2c45a7e 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -4027,6 +4027,51 @@ fn stdio_streams_are_locked_in_permission_prompt() { }); } +#[test] +fn permission_prompt_strips_ansi_codes_and_control_chars() { + let _guard = util::http_server(); + util::with_pty(&["repl"], |mut console| { + console.write_line( + r#"Deno.permissions.request({ name: "env", variable: "\rDo you like ice cream? y/n" });"# + ); + console.write_line("close();"); + let output = console.read_all_output(); + + assert!(output.contains( + "┌ ⚠️ Deno requests env access to \"Do you like ice cream? y/n\"." + )); + }); + + util::with_pty(&["repl"], |mut console| { + console.write_line( + r#" +const boldANSI = "\u001b[1m" // bold +const unboldANSI = "\u001b[22m" // unbold + +const prompt = `┌ ⚠️ ${boldANSI}Deno requests run access to "echo"${unboldANSI} +├ Requested by \`Deno.Command().output()` + +const moveANSIUp = "\u001b[1A" // moves to the start of the line +const clearANSI = "\u001b[2K" // clears the line +const moveANSIStart = "\u001b[1000D" // moves to the start of the line + +Deno[Object.getOwnPropertySymbols(Deno)[0]].core.ops.op_spawn_child({ + cmd: "cat", + args: ["/etc/passwd"], + clearEnv: false, + env: [], + stdin: "null", + stdout: "inherit", + stderr: "piped" +}, moveANSIUp + clearANSI + moveANSIStart + prompt)"#, + ); + console.write_line("close();"); + let output = console.read_all_output(); + + assert!(output.contains(r#"┌ ⚠️ Deno requests run access to "cat""#)); + }); +} + itest!(node_builtin_modules_ts { args: "run --quiet --allow-read run/node_builtin_modules/mod.ts hello there", output: "run/node_builtin_modules/mod.ts.out", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 0ba8f8d3ad..360ef2aa07 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -86,6 +86,7 @@ deno_websocket.workspace = true deno_webstorage.workspace = true atty.workspace = true +console_static_text.workspace = true dlopen.workspace = true encoding_rs.workspace = true filetime = "0.2.16" diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs index d148b485e7..d94264b1a0 100644 --- a/runtime/permissions/prompter.rs +++ b/runtime/permissions/prompter.rs @@ -5,6 +5,14 @@ use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use once_cell::sync::Lazy; +/// Helper function to strip ansi codes and ASCII control characters. +fn strip_ansi_codes_and_ascii_control(s: &str) -> std::borrow::Cow { + console_static_text::strip_ansi_codes(s) + .chars() + .filter(|c| !c.is_ascii_control()) + .collect() +} + pub const PERMISSION_EMOJI: &str = "⚠️"; #[derive(Debug, Eq, PartialEq)] @@ -203,6 +211,10 @@ impl PermissionPrompter for TtyPrompter { let _stdout_guard = std::io::stdout().lock(); let _stderr_guard = std::io::stderr().lock(); + let message = strip_ansi_codes_and_ascii_control(message); + let name = strip_ansi_codes_and_ascii_control(name); + let api_name = api_name.map(strip_ansi_codes_and_ascii_control); + // print to stderr so that if stdout is piped this is still displayed. let opts: String = if is_unary { format!("[y/n/A] (y = yes, allow; n = no, deny; A = allow all {name} permissions)") @@ -211,9 +223,9 @@ impl PermissionPrompter for TtyPrompter { }; eprint!("┌ {PERMISSION_EMOJI} "); eprint!("{}", colors::bold("Deno requests ")); - eprint!("{}", colors::bold(message)); + eprint!("{}", colors::bold(message.clone())); eprintln!("{}", colors::bold(".")); - if let Some(api_name) = api_name { + if let Some(api_name) = api_name.clone() { eprintln!("├ Requested by `{api_name}` API"); } let msg = format!("Run again with --allow-{name} to bypass this prompt.");