From 7b41ed3455f33f52327db83bbe34dc7f740e345b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 7 Sep 2024 10:46:43 +0200 Subject: [PATCH 1/2] prototype working --- bar.ts | 1 + foo.ts | 3 + main.ts | 11 ++++ runtime/js/30_os.js | 3 +- runtime/ops/os/mod.rs | 13 +++-- runtime/permissions/lib.rs | 100 ++++++++++++++++++++++++++++++++ runtime/permissions/prompter.rs | 78 +++++++++++++++++++++++++ 7 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 bar.ts create mode 100644 foo.ts create mode 100644 main.ts diff --git a/bar.ts b/bar.ts new file mode 100644 index 00000000000000..98b11a50b03e66 --- /dev/null +++ b/bar.ts @@ -0,0 +1 @@ +export { checkHostname } from "./foo.ts"; diff --git a/foo.ts b/foo.ts new file mode 100644 index 00000000000000..f26f3db5c81581 --- /dev/null +++ b/foo.ts @@ -0,0 +1,3 @@ +export function checkHostname() { + return Deno.hostname(); +} diff --git a/main.ts b/main.ts new file mode 100644 index 00000000000000..a9d73c4f8c1855 --- /dev/null +++ b/main.ts @@ -0,0 +1,11 @@ +import { checkHostname } from "./bar.ts"; + +function hello() { + console.log("Hello there!", checkHostname()); +} + +function bar() { + return hello(); +} + +bar(); diff --git a/runtime/js/30_os.js b/runtime/js/30_os.js index f3dfda886d60b0..d048c0cec61ae3 100644 --- a/runtime/js/30_os.js +++ b/runtime/js/30_os.js @@ -40,7 +40,8 @@ function loadavg() { } function hostname() { - return op_hostname(); + const stack = (new Error()).stack; + return op_hostname(stack); } function osRelease() { diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index 544031dd7bff2d..fceaf80021a21a 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -187,10 +187,15 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { #[op2] #[string] -fn op_hostname(state: &mut OpState) -> Result { - state - .borrow_mut::() - .check_sys("hostname", "Deno.hostname()")?; +fn op_hostname( + state: &mut OpState, + #[string] stack: Option, +) -> Result { + state.borrow_mut::().check_sys2( + "hostname", + "Deno.hostname()", + stack.as_ref().map(|s| s.as_str()), + )?; Ok(sys_info::hostname()) } diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index c5cfbff7039291..614d022ff765f8 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -19,6 +19,7 @@ use deno_core::ModuleSpecifier; use deno_terminal::colors; use fqdn::FQDN; use once_cell::sync::Lazy; +use prompter::permission_prompt2; use std::borrow::Cow; use std::collections::HashSet; use std::ffi::OsStr; @@ -195,6 +196,44 @@ impl PermissionState { _ => (Err(Self::error(name, info)), false, false), } } + + #[inline] + fn check3( + self, + name: &str, + api_name: Option<&str>, + info: impl Fn() -> Option, + prompt: bool, + stack: Option<&str>, + ) -> (Result<(), AnyError>, bool, bool) { + match self { + PermissionState::Granted => { + Self::log_perm_access(name, info); + (Ok(()), false, false) + } + PermissionState::Prompt if prompt => { + let msg = format!( + "{} access{}", + name, + info() + .map(|info| { format!(" to {info}") }) + .unwrap_or_default(), + ); + match permission_prompt2(&msg, name, api_name, true, stack) { + PromptResponse::Allow => { + Self::log_perm_access(name, info); + (Ok(()), true, false) + } + PromptResponse::AllowAll => { + Self::log_perm_access(name, info); + (Ok(()), true, true) + } + PromptResponse::Deny => (Err(Self::error(name, info)), true, false), + } + } + _ => (Err(Self::error(name, info)), false, false), + } + } } impl fmt::Display for PermissionState { @@ -413,6 +452,41 @@ impl UnaryPermission { result } + fn check_desc2( + &mut self, + desc: Option<&T>, + assert_non_partial: bool, + api_name: Option<&str>, + get_display_name: impl Fn() -> Option, + stack: Option<&str>, + ) -> Result<(), AnyError> { + skip_check_if_is_permission_fully_granted!(self); + let (result, prompted, is_allow_all) = self + .query_desc(desc, AllowPartial::from(!assert_non_partial)) + .check3( + T::flag_name(), + api_name, + || match get_display_name() { + Some(display_name) => Some(display_name), + None => desc.map(|d| format!("\"{}\"", d.name())), + }, + self.prompt, + stack, + ); + if prompted { + if result.is_ok() { + if is_allow_all { + self.insert_granted(None); + } else { + self.insert_granted(desc.cloned()); + } + } else { + self.insert_prompt_denied(desc.cloned()); + } + } + result + } + fn query_desc( &self, desc: Option<&T>, @@ -1306,6 +1380,22 @@ impl UnaryPermission { ) } + pub fn check2( + &mut self, + kind: &str, + api_name: Option<&str>, + stack: Option<&str>, + ) -> Result<(), AnyError> { + skip_check_if_is_permission_fully_granted!(self); + self.check_desc2( + Some(&SysDescriptor(kind.to_string())), + false, + api_name, + || None, + stack, + ) + } + pub fn check_all(&mut self) -> Result<(), AnyError> { skip_check_if_is_permission_fully_granted!(self); self.check_desc(None, false, None, || None) @@ -1725,6 +1815,16 @@ impl PermissionsContainer { self.0.lock().sys.check(kind, Some(api_name)) } + #[inline(always)] + pub fn check_sys2( + &self, + kind: &str, + api_name: &str, + stack: Option<&str>, + ) -> Result<(), AnyError> { + self.0.lock().sys.check2(kind, Some(api_name), stack) + } + #[inline(always)] pub fn check_env(&mut self, var: &str) -> Result<(), AnyError> { self.0.lock().env.check(var, None) diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs index e48e0af10da6eb..86cff1cbdb7890 100644 --- a/runtime/permissions/prompter.rs +++ b/runtime/permissions/prompter.rs @@ -72,6 +72,25 @@ pub fn permission_prompt( r } +pub fn permission_prompt2( + message: &str, + flag: &str, + api_name: Option<&str>, + is_unary: bool, + stack: Option<&str>, +) -> PromptResponse { + if let Some(before_callback) = MAYBE_BEFORE_PROMPT_CALLBACK.lock().as_mut() { + before_callback(); + } + let r = PERMISSION_PROMPTER + .lock() + .prompt2(message, flag, api_name, is_unary, stack); + if let Some(after_callback) = MAYBE_AFTER_PROMPT_CALLBACK.lock().as_mut() { + after_callback(); + } + r +} + pub fn set_prompt_callbacks( before_callback: PromptCallback, after_callback: PromptCallback, @@ -90,6 +109,17 @@ pub trait PermissionPrompter: Send + Sync { api_name: Option<&str>, is_unary: bool, ) -> PromptResponse; + + fn prompt2( + &mut self, + _message: &str, + _name: &str, + _api_name: Option<&str>, + _is_unary: bool, + _stack: Option<&str>, + ) -> PromptResponse { + PromptResponse::AllowAll + } } pub struct TtyPrompter; @@ -277,6 +307,30 @@ impl PermissionPrompter for TtyPrompter { name: &str, api_name: Option<&str>, is_unary: bool, + ) -> PromptResponse { + self.prompt_inner(message, name, api_name, is_unary, None) + } + + fn prompt2( + &mut self, + message: &str, + name: &str, + api_name: Option<&str>, + is_unary: bool, + stack: Option<&str>, + ) -> PromptResponse { + self.prompt_inner(message, name, api_name, is_unary, stack) + } +} + +impl TtyPrompter { + fn prompt_inner( + &mut self, + message: &str, + name: &str, + api_name: Option<&str>, + is_unary: bool, + stack: Option<&str>, ) -> PromptResponse { if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() { return PromptResponse::Deny; @@ -333,6 +387,30 @@ impl PermissionPrompter for TtyPrompter { ) .unwrap(); } + if let Some(stack) = stack { + let lines: Vec<_> = stack.split('\n').skip(1).collect(); + let len = lines.len(); + for (idx, line) in lines.into_iter().enumerate() { + if idx != len - 1 { + writeln!( + &mut output, + "┃ {} {}", + colors::gray("├─"), + colors::gray(line.trim()) + ) + .unwrap(); + } else { + writeln!( + &mut output, + "┃ {} {}", + colors::gray("└─"), + colors::gray(line.trim()) + ) + .unwrap(); + } + } + } + let msg = format!( "Learn more at: {}", colors::cyan_with_underline(&format!( From 3114a91211e553c57ca49b0f998a968d41f6b67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 9 Sep 2024 23:41:09 +0200 Subject: [PATCH 2/2] capture using v8 API --- ext/net/ops.rs | 65 ++++++++++++++++++++++++++----------------- main.ts | 3 ++ runtime/ops/os/mod.rs | 9 +++++- 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/ext/net/ops.rs b/ext/net/ops.rs index b74dc8d755d78d..d36d78441e049b 100644 --- a/ext/net/ops.rs +++ b/ext/net/ops.rs @@ -77,34 +77,49 @@ pub(crate) fn accept_err(e: std::io::Error) -> AnyError { } } -#[op2(async)] +use deno_core::error::JsError; +use deno_core::futures::Future; +use deno_core::v8; + +#[op2(async, reentrant)] #[serde] -pub async fn op_net_accept_tcp( +pub fn op_net_accept_tcp( + scope: &mut v8::HandleScope, state: Rc>, #[smi] rid: ResourceId, -) -> Result<(ResourceId, IpAddr, IpAddr), AnyError> { - let resource = state - .borrow() - .resource_table - .get::>(rid) - .map_err(|_| bad_resource("Listener has been closed"))?; - let listener = RcRef::map(&resource, |r| &r.listener) - .try_borrow_mut() - .ok_or_else(|| custom_error("Busy", "Another accept task is ongoing"))?; - let cancel = RcRef::map(resource, |r| &r.cancel); - let (tcp_stream, _socket_addr) = listener - .accept() - .try_or_cancel(cancel) - .await - .map_err(accept_err)?; - let local_addr = tcp_stream.local_addr()?; - let remote_addr = tcp_stream.peer_addr()?; - - let mut state = state.borrow_mut(); - let rid = state - .resource_table - .add(TcpStreamResource::new(tcp_stream.into_split())); - Ok((rid, IpAddr::from(local_addr), IpAddr::from(remote_addr))) +) -> Result< + impl Future>, + AnyError, +> { + let msg = v8::String::new(scope, "asdf").unwrap(); + let error = v8::Exception::error(scope, msg.into()); + let js_error = JsError::from_v8_exception(scope, error); + eprintln!("js error {:#?}", js_error); + + Ok(async move { + let resource = state + .borrow() + .resource_table + .get::>(rid) + .map_err(|_| bad_resource("Listener has been closed"))?; + let listener = RcRef::map(&resource, |r| &r.listener) + .try_borrow_mut() + .ok_or_else(|| custom_error("Busy", "Another accept task is ongoing"))?; + let cancel = RcRef::map(resource, |r| &r.cancel); + let (tcp_stream, _socket_addr) = listener + .accept() + .try_or_cancel(cancel) + .await + .map_err(accept_err)?; + let local_addr = tcp_stream.local_addr()?; + let remote_addr = tcp_stream.peer_addr()?; + + let mut state = state.borrow_mut(); + let rid = state + .resource_table + .add(TcpStreamResource::new(tcp_stream.into_split())); + Ok((rid, IpAddr::from(local_addr), IpAddr::from(remote_addr))) + }) } #[op2(async)] diff --git a/main.ts b/main.ts index a9d73c4f8c1855..cd3acbb7a5146f 100644 --- a/main.ts +++ b/main.ts @@ -9,3 +9,6 @@ function bar() { } bar(); + +const listener = Deno.listen({ hostname: "0.0.0.0", port: 8080 }); +const conn = await listener.accept(); diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index fceaf80021a21a..398301dd7fe6f4 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -4,6 +4,7 @@ use super::utils::into_string; use crate::worker::ExitCode; use deno_core::error::type_error; use deno_core::error::AnyError; +use deno_core::error::JsError; use deno_core::normalize_path; use deno_core::op2; use deno_core::v8; @@ -185,12 +186,18 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { Ok(sys_info::loadavg()) } -#[op2] +#[op2(reentrant)] #[string] fn op_hostname( + scope: &mut v8::HandleScope, state: &mut OpState, #[string] stack: Option, ) -> Result { + let msg = v8::String::new(scope, "asdf").unwrap(); + let error = v8::Exception::error(scope, msg.into()); + let js_error = JsError::from_v8_exception(scope, error); + eprintln!("js error {:#?}", js_error); + state.borrow_mut::().check_sys2( "hostname", "Deno.hostname()",