diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index c4e0e4fec9c7a..2e75517e63252 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -6,6 +6,7 @@ use crate::http_util::create_http_client; use crate::http_util::FetchOnceResult; use crate::msg; use crate::op_error::OpError; +use crate::permissions::Permissions; use deno_core::ErrBox; use deno_core::ModuleSpecifier; use futures::future::FutureExt; @@ -109,6 +110,7 @@ impl SourceFileFetcher { pub fn fetch_cached_source_file( &self, specifier: &ModuleSpecifier, + permissions: Permissions, ) -> Option { let maybe_source_file = self.source_file_cache.get(specifier.to_string()); @@ -123,7 +125,7 @@ impl SourceFileFetcher { // future, because it doesn't actually do any asynchronous // action in that path. if let Ok(maybe_source_file) = - self.get_source_file_from_local_cache(specifier.as_url()) + self.get_source_file_from_local_cache(specifier.as_url(), &permissions) { return maybe_source_file; } @@ -148,6 +150,7 @@ impl SourceFileFetcher { &self, specifier: &ModuleSpecifier, maybe_referrer: Option, + permissions: Permissions, ) -> Result { let module_url = specifier.as_url().to_owned(); debug!("fetch_source_file specifier: {} ", &module_url); @@ -167,6 +170,7 @@ impl SourceFileFetcher { self.use_disk_cache, self.no_remote, self.cached_only, + &permissions, ) .await; @@ -222,6 +226,7 @@ impl SourceFileFetcher { fn get_source_file_from_local_cache( &self, module_url: &Url, + permissions: &Permissions, ) -> Result, ErrBox> { let url_scheme = module_url.scheme(); let is_local_file = url_scheme == "file"; @@ -229,7 +234,7 @@ impl SourceFileFetcher { // Local files are always fetched from disk bypassing cache entirely. if is_local_file { - return self.fetch_local_file(&module_url).map(Some); + return self.fetch_local_file(&module_url, permissions).map(Some); } self.fetch_cached_remote_source(&module_url) @@ -252,6 +257,7 @@ impl SourceFileFetcher { use_disk_cache: bool, no_remote: bool, cached_only: bool, + permissions: &Permissions, ) -> Result { let url_scheme = module_url.scheme(); let is_local_file = url_scheme == "file"; @@ -259,7 +265,7 @@ impl SourceFileFetcher { // Local files are always fetched from disk bypassing cache entirely. if is_local_file { - return self.fetch_local_file(&module_url); + return self.fetch_local_file(&module_url, permissions); } // The file is remote, fail if `no_remote` is true. @@ -276,18 +282,29 @@ impl SourceFileFetcher { // Fetch remote file and cache on-disk for subsequent access self - .fetch_remote_source(&module_url, use_disk_cache, cached_only, 10) + .fetch_remote_source( + &module_url, + use_disk_cache, + cached_only, + 10, + permissions, + ) .await } /// Fetch local source file. - fn fetch_local_file(&self, module_url: &Url) -> Result { + fn fetch_local_file( + &self, + module_url: &Url, + permissions: &Permissions, + ) -> Result { let filepath = module_url.to_file_path().map_err(|()| { ErrBox::from(OpError::uri_error( "File URL contains invalid path".to_owned(), )) })?; + permissions.check_read(&filepath)?; let source_code = match fs::read(filepath.clone()) { Ok(c) => c, Err(e) => return Err(e.into()), @@ -390,12 +407,17 @@ impl SourceFileFetcher { use_disk_cache: bool, cached_only: bool, redirect_limit: i64, + permissions: &Permissions, ) -> Pin>>> { if redirect_limit < 0 { let e = OpError::http("too many redirects".to_string()); return futures::future::err(e.into()).boxed_local(); } + if let Err(e) = permissions.check_net_url(&module_url) { + return futures::future::err(e.into()).boxed_local(); + } + let is_blacklisted = check_cache_blacklist(module_url, self.cache_blacklist.as_ref()); // First try local cache @@ -441,6 +463,7 @@ impl SourceFileFetcher { Ok((_, headers)) => headers.get("etag").map(String::from), Err(_) => None, }; + let permissions = permissions.clone(); let http_client = self.http_client.clone(); // Single pass fetch, either yields code or yields redirect. let f = async move { @@ -463,6 +486,7 @@ impl SourceFileFetcher { use_disk_cache, cached_only, redirect_limit - 1, + &permissions, ) .await } @@ -752,11 +776,15 @@ mod tests { if cfg!(windows) { // Should fail: missing drive letter. let u = Url::parse("file:///etc/passwd").unwrap(); - fetcher.fetch_local_file(&u).unwrap_err(); + fetcher + .fetch_local_file(&u, &Permissions::allow_all()) + .unwrap_err(); } else { // Should fail: local network paths are not supported on unix. let u = Url::parse("file://server/etc/passwd").unwrap(); - fetcher.fetch_local_file(&u).unwrap_err(); + fetcher + .fetch_local_file(&u, &Permissions::allow_all()) + .unwrap_err(); } } @@ -774,7 +802,13 @@ mod tests { let cache_filename = fetcher.http_cache.get_cache_filename(&module_url); let result = fetcher - .get_source_file(&module_url, true, false, false) + .get_source_file( + &module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r = result.unwrap(); @@ -795,7 +829,13 @@ mod tests { metadata.write(&cache_filename).unwrap(); let result2 = fetcher_1 - .get_source_file(&module_url, true, false, false) + .get_source_file( + &module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result2.is_ok()); let r2 = result2.unwrap(); @@ -818,7 +858,13 @@ mod tests { metadata.write(&cache_filename).unwrap(); let result3 = fetcher_2 - .get_source_file(&module_url_1, true, false, false) + .get_source_file( + &module_url_1, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result3.is_ok()); let r3 = result3.unwrap(); @@ -839,7 +885,13 @@ mod tests { // and don't use cache let fetcher = setup_file_fetcher(temp_dir.path()); let result4 = fetcher - .get_source_file(&module_url_2, false, false, false) + .get_source_file( + &module_url_2, + false, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result4.is_ok()); let r4 = result4.unwrap(); @@ -863,7 +915,13 @@ mod tests { let cache_filename = fetcher.http_cache.get_cache_filename(&module_url); let result = fetcher - .get_source_file(&module_url, true, false, false) + .get_source_file( + &module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r = result.unwrap(); @@ -883,7 +941,13 @@ mod tests { metadata.write(&cache_filename).unwrap(); let result2 = fetcher - .get_source_file(&module_url, true, false, false) + .get_source_file( + &module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result2.is_ok()); let r2 = result2.unwrap(); @@ -903,7 +967,13 @@ mod tests { // process) and don't use cache let fetcher = setup_file_fetcher(temp_dir.path()); let result3 = fetcher - .get_source_file(&module_url_1, false, false, false) + .get_source_file( + &module_url_1, + false, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result3.is_ok()); let r3 = result3.unwrap(); @@ -930,7 +1000,9 @@ mod tests { fetcher.http_cache.get_cache_filename(&specifier.as_url()); // first download - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); let headers_file_name = @@ -946,7 +1018,9 @@ mod tests { // `use_disk_cache` is set to false, this can be verified using source // header file creation timestamp (should be the same as after first // download) - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); let result = fs::File::open(&headers_file_name); @@ -984,7 +1058,13 @@ mod tests { // Test basic follow and headers recording let result = fetcher - .get_source_file(&redirect_module_url, true, false, false) + .get_source_file( + &redirect_module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let mod_meta = result.unwrap(); @@ -1033,7 +1113,13 @@ mod tests { // Test double redirects and headers recording let result = fetcher - .get_source_file(&double_redirect_url, true, false, false) + .get_source_file( + &double_redirect_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let mod_meta = result.unwrap(); @@ -1080,7 +1166,13 @@ mod tests { // Test that redirect target is not downloaded twice for different redirect source. let result = fetcher - .get_source_file(&double_redirect_url, true, false, false) + .get_source_file( + &double_redirect_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let result = fs::File::open(&target_path); @@ -1095,7 +1187,13 @@ mod tests { // using source header file creation timestamp (should be the same as // after first `get_source_file`) let result = fetcher - .get_source_file(&redirect_url, true, false, false) + .get_source_file( + &redirect_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let result = fs::File::open(&target_path_); @@ -1121,12 +1219,24 @@ mod tests { // Test that redirections can be limited let result = fetcher - .fetch_remote_source(&double_redirect_url, false, false, 2) + .fetch_remote_source( + &double_redirect_url, + false, + false, + 2, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let result = fetcher - .fetch_remote_source(&double_redirect_url, false, false, 1) + .fetch_remote_source( + &double_redirect_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await; assert!(result.is_err()); // FIXME(bartlomieju): @@ -1161,7 +1271,13 @@ mod tests { // Test basic follow and headers recording let result = fetcher - .get_source_file(&redirect_module_url, true, false, false) + .get_source_file( + &redirect_module_url, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let mod_meta = result.unwrap(); @@ -1193,7 +1309,13 @@ mod tests { Url::parse("http://localhost:4545/cli/tests/002_hello.ts").unwrap(); // Remote modules are not allowed let result = fetcher - .get_source_file(&module_url, true, true, false) + .get_source_file( + &module_url, + true, + true, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_err()); // FIXME(bartlomieju): @@ -1216,7 +1338,13 @@ mod tests { // file hasn't been cached before let result = fetcher - .get_source_file(&module_url, true, false, true) + .get_source_file( + &module_url, + true, + false, + true, + &Permissions::allow_all(), + ) .await; assert!(result.is_err()); // FIXME(bartlomieju): @@ -1225,12 +1353,24 @@ mod tests { // download and cache file let result = fetcher_1 - .get_source_file(&module_url_1, true, false, false) + .get_source_file( + &module_url_1, + true, + false, + false, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); // module is already cached, should be ok even with `cached_only` let result = fetcher_2 - .get_source_file(&module_url_2, true, false, true) + .get_source_file( + &module_url_2, + true, + false, + true, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); drop(http_server_guard); @@ -1244,7 +1384,13 @@ mod tests { Url::parse("http://127.0.0.1:4545/cli/tests/subdir/mt_video_mp2t.t3.ts") .unwrap(); let result = fetcher - .fetch_remote_source(&module_url, false, false, 10) + .fetch_remote_source( + &module_url, + false, + false, + 10, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r = result.unwrap(); @@ -1289,7 +1435,13 @@ mod tests { let module_url_3_ = module_url_3.clone(); let result = fetcher - .fetch_remote_source(&module_url, false, false, 10) + .fetch_remote_source( + &module_url, + false, + false, + 10, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r = result.unwrap(); @@ -1298,7 +1450,13 @@ mod tests { let (_, headers) = fetcher.http_cache.get(&module_url).unwrap(); assert_eq!(headers.get("content-type").unwrap(), "text/typescript"); let result = fetcher_1 - .fetch_remote_source(&module_url_2, false, false, 10) + .fetch_remote_source( + &module_url_2, + false, + false, + 10, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r2 = result.unwrap(); @@ -1309,7 +1467,13 @@ mod tests { // test unknown extension let result = fetcher_2 - .fetch_remote_source(&module_url_3, false, false, 10) + .fetch_remote_source( + &module_url_3, + false, + false, + 10, + &Permissions::allow_all(), + ) .await; assert!(result.is_ok()); let r3 = result.unwrap(); @@ -1328,14 +1492,18 @@ mod tests { // Test failure case. let specifier = ModuleSpecifier::resolve_url(file_url!("/baddir/hello.ts")).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_err()); let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("js/main.ts"); let specifier = ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); } @@ -1347,14 +1515,18 @@ mod tests { // Test failure case. let specifier = ModuleSpecifier::resolve_url(file_url!("/baddir/hello.ts")).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_err()); let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("js/main.ts"); let specifier = ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); } @@ -1367,7 +1539,9 @@ mod tests { .join("tests/001_hello.js"); let specifier = ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap(); - let r = fetcher.fetch_source_file(&specifier, None).await; + let r = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await; assert!(r.is_ok()); } @@ -1611,7 +1785,13 @@ mod tests { Url::parse("http://127.0.0.1:4545/etag_script.ts").unwrap(); let source = fetcher - .fetch_remote_source(&module_url, false, false, 1) + .fetch_remote_source( + &module_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await; assert!(source.is_ok()); let source = source.unwrap(); @@ -1633,7 +1813,13 @@ mod tests { let file_name = fetcher.http_cache.get_cache_filename(&module_url); let _ = fs::write(&file_name, "changed content"); let cached_source = fetcher - .fetch_remote_source(&module_url, false, false, 1) + .fetch_remote_source( + &module_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await .unwrap(); assert_eq!(cached_source.source_code, b"changed content"); @@ -1732,7 +1918,13 @@ mod tests { let module_url = Url::parse("http://127.0.0.1:4545/xTypeScriptTypes.js").unwrap(); let source = fetcher - .fetch_remote_source(&module_url, false, false, 1) + .fetch_remote_source( + &module_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await; assert!(source.is_ok()); let source = source.unwrap(); @@ -1752,7 +1944,13 @@ mod tests { let module_url = Url::parse("http://127.0.0.1:4545/referenceTypes.js").unwrap(); let source = fetcher - .fetch_remote_source(&module_url, false, false, 1) + .fetch_remote_source( + &module_url, + false, + false, + 1, + &Permissions::allow_all(), + ) .await; assert!(source.is_ok()); let source = source.unwrap(); diff --git a/cli/global_state.rs b/cli/global_state.rs index b91ba5b108933..4e9bdbb99c941 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -86,7 +86,6 @@ impl GlobalState { compiler_starts: AtomicUsize::new(0), compile_lock: AsyncMutex::new(()), }; - Ok(GlobalState(Arc::new(inner))) } @@ -95,6 +94,7 @@ impl GlobalState { module_specifier: ModuleSpecifier, maybe_referrer: Option, target_lib: TargetLib, + permissions: Permissions, ) -> Result { let state1 = self.clone(); let state2 = self.clone(); @@ -102,7 +102,7 @@ impl GlobalState { let out = self .file_fetcher - .fetch_source_file(&module_specifier, maybe_referrer) + .fetch_source_file(&module_specifier, maybe_referrer, permissions.clone()) .await?; // TODO(ry) Try to lift compile_lock as high up in the call stack for @@ -115,14 +115,14 @@ impl GlobalState { | msg::MediaType::JSX => { state1 .ts_compiler - .compile(state1.clone(), &out, target_lib) + .compile(state1.clone(), &out, target_lib, permissions.clone()) .await } msg::MediaType::JavaScript => { if state1.ts_compiler.compile_js { state2 .ts_compiler - .compile(state1.clone(), &out, target_lib) + .compile(state1.clone(), &out, target_lib, permissions.clone()) .await } else { if let Some(types_url) = out.types_url.clone() { @@ -132,6 +132,7 @@ impl GlobalState { .fetch_source_file( &types_specifier, Some(module_specifier.clone()), + permissions.clone(), ) .await .ok(); diff --git a/cli/lib.rs b/cli/lib.rs index 97b497e9566d6..0b88d73460920 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -73,6 +73,7 @@ use crate::global_state::GlobalState; use crate::msg::MediaType; use crate::op_error::OpError; use crate::ops::io::get_stdio; +use crate::permissions::Permissions; use crate::state::DebugType; use crate::state::State; use crate::tsc::TargetLib; @@ -187,7 +188,7 @@ async fn print_file_info( let out = global_state .file_fetcher - .fetch_source_file(&module_specifier, None) + .fetch_source_file(&module_specifier, None, Permissions::allow_all()) .await?; println!( @@ -205,7 +206,12 @@ async fn print_file_info( let module_specifier_ = module_specifier.clone(); global_state .clone() - .fetch_compiled_module(module_specifier_, None, TargetLib::Main) + .fetch_compiled_module( + module_specifier_, + None, + TargetLib::Main, + Permissions::allow_all(), + ) .await?; if out.media_type == msg::MediaType::TypeScript @@ -407,7 +413,9 @@ async fn doc_command( let fetcher = self.clone(); async move { - let source_file = fetcher.fetch_source_file(&specifier, None).await?; + let source_file = fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await?; String::from_utf8(source_file.source_code) .map_err(|_| OpError::other("failed to parse".to_string())) } diff --git a/cli/ops/compiler.rs b/cli/ops/compiler.rs index 83b6d944c886c..b844011877c39 100644 --- a/cli/ops/compiler.rs +++ b/cli/ops/compiler.rs @@ -69,7 +69,11 @@ fn op_fetch_source_files( None }; - let global_state = state.borrow().global_state.clone(); + let s = state.borrow(); + let global_state = s.global_state.clone(); + let permissions = s.permissions.clone(); + let perms_ = permissions.clone(); + drop(s); let file_fetcher = global_state.file_fetcher.clone(); let specifiers = args.specifiers.clone(); let future = async move { @@ -78,6 +82,7 @@ fn op_fetch_source_files( .map(|specifier| { let file_fetcher_ = file_fetcher.clone(); let ref_specifier_ = ref_specifier.clone(); + let perms_ = perms_.clone(); async move { let resolved_specifier = ModuleSpecifier::resolve_url(&specifier) .expect("Invalid specifier"); @@ -100,7 +105,7 @@ fn op_fetch_source_files( } } file_fetcher_ - .fetch_source_file(&resolved_specifier, ref_specifier_) + .fetch_source_file(&resolved_specifier, ref_specifier_, perms_) .await } .boxed_local() @@ -118,7 +123,11 @@ fn op_fetch_source_files( let types_specifier = ModuleSpecifier::from(types_url); global_state .file_fetcher - .fetch_source_file(&types_specifier, ref_specifier.clone()) + .fetch_source_file( + &types_specifier, + ref_specifier.clone(), + permissions.clone(), + ) .await .map_err(OpError::from)? } diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index e44d6fa8b2bd3..f3b741861a866 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -30,10 +30,13 @@ fn op_compile( ) -> Result { state.check_unstable("Deno.compile"); let args: CompileArgs = serde_json::from_value(args)?; - let global_state = state.borrow().global_state.clone(); + let s = state.borrow(); + let global_state = s.global_state.clone(); + let permissions = s.permissions.clone(); let fut = async move { runtime_compile( global_state, + permissions, &args.root_name, &args.sources, args.bundle, @@ -58,9 +61,12 @@ fn op_transpile( ) -> Result { state.check_unstable("Deno.transpile"); let args: TranspileArgs = serde_json::from_value(args)?; - let global_state = state.borrow().global_state.clone(); + let s = state.borrow(); + let global_state = s.global_state.clone(); + let permissions = s.permissions.clone(); let fut = async move { - runtime_transpile(global_state, &args.sources, &args.options).await + runtime_transpile(global_state, permissions, &args.sources, &args.options) + .await } .boxed_local(); Ok(JsonOp::Async(fut)) diff --git a/cli/permissions.rs b/cli/permissions.rs index a56754f18d584..bc4d0844e0ec6 100644 --- a/cli/permissions.rs +++ b/cli/permissions.rs @@ -134,6 +134,19 @@ impl Permissions { } } + pub fn allow_all() -> Self { + Self { + allow_read: PermissionState::from(true), + allow_write: PermissionState::from(true), + allow_net: PermissionState::from(true), + allow_env: PermissionState::from(true), + allow_run: PermissionState::from(true), + allow_plugin: PermissionState::from(true), + allow_hrtime: PermissionState::from(true), + ..Default::default() + } + } + pub fn check_run(&self) -> Result<(), OpError> { self .allow_run diff --git a/cli/state.rs b/cli/state.rs index 8c425d7005cd2..9501ed2866f10 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -67,6 +67,7 @@ pub struct StateInner { pub seeded_rng: Option, pub target_lib: TargetLib, pub debug_type: DebugType, + pub is_main: bool, } impl State { @@ -314,9 +315,20 @@ impl ModuleLoader for State { let module_url_specified = module_specifier.to_string(); let global_state = state.global_state.clone(); let target_lib = state.target_lib.clone(); + let permissions = if state.is_main { + Permissions::allow_all() + } else { + state.permissions.clone() + }; + let fut = async move { let compiled_module = global_state - .fetch_compiled_module(module_specifier, maybe_referrer, target_lib) + .fetch_compiled_module( + module_specifier, + maybe_referrer, + target_lib, + permissions, + ) .await?; Ok(deno_core::ModuleSource { // Real module name, might be different from initial specifier @@ -395,6 +407,7 @@ impl State { seeded_rng, target_lib: TargetLib::Main, debug_type, + is_main: true, })); Ok(Self(state)) @@ -430,6 +443,7 @@ impl State { seeded_rng, target_lib: TargetLib::Worker, debug_type: DebugType::Dependent, + is_main: false, })); Ok(Self(state)) diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 4897422b9d766..61e1fca7da32a 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1019,6 +1019,7 @@ fn workers() { .arg("test") .arg("--reload") .arg("--allow-net") + .arg("--allow-read") .arg("--unstable") .arg("workers_test.ts") .spawn() @@ -1036,6 +1037,7 @@ fn compiler_api() { .arg("test") .arg("--unstable") .arg("--reload") + .arg("--allow-read") .arg("compiler_api_test.ts") .spawn() .unwrap() diff --git a/cli/tsc.rs b/cli/tsc.rs index f0b6172465fe8..f207558a5c6f1 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -11,6 +11,7 @@ use crate::global_state::GlobalState; use crate::msg; use crate::op_error::OpError; use crate::ops; +use crate::permissions::Permissions; use crate::source_maps::SourceMapGetter; use crate::startup_data; use crate::state::State; @@ -343,12 +344,19 @@ impl TsCompiler { /// Create a new V8 worker with snapshot of TS compiler and setup compiler's /// runtime. - fn setup_worker(global_state: GlobalState) -> CompilerWorker { + fn setup_worker( + global_state: GlobalState, + permissions: Permissions, + ) -> CompilerWorker { let entry_point = ModuleSpecifier::resolve_url_or_path("./__$deno$ts_compiler.ts").unwrap(); - let worker_state = - State::new(global_state.clone(), None, entry_point, DebugType::Internal) - .expect("Unable to create worker state"); + let worker_state = State::new( + global_state.clone(), + Some(permissions), + entry_point, + DebugType::Internal, + ) + .expect("Unable to create worker state"); // Count how many times we start the compiler worker. global_state.compiler_starts.fetch_add(1, Ordering::SeqCst); @@ -384,7 +392,9 @@ impl TsCompiler { global_state.flags.unstable, ); - let msg = execute_in_thread(global_state.clone(), req_msg).await?; + let permissions = Permissions::allow_all(); + let msg = + execute_in_thread(global_state.clone(), permissions, req_msg).await?; let json_str = std::str::from_utf8(&msg).unwrap(); debug!("Message: {}", json_str); @@ -440,6 +450,7 @@ impl TsCompiler { global_state: GlobalState, source_file: &SourceFile, target: TargetLib, + permissions: Permissions, ) -> Result { if self.has_compiled(&source_file.url) { return self.get_compiled_module(&source_file.url); @@ -492,7 +503,8 @@ impl TsCompiler { module_url.to_string() ); - let msg = execute_in_thread(global_state.clone(), req_msg).await?; + let msg = + execute_in_thread(global_state.clone(), permissions, req_msg).await?; let json_str = std::str::from_utf8(&msg).unwrap(); let compile_response: CompileResponse = serde_json::from_str(json_str)?; @@ -597,7 +609,7 @@ impl TsCompiler { ) -> std::io::Result<()> { let source_file = self .file_fetcher - .fetch_cached_source_file(&module_specifier) + .fetch_cached_source_file(&module_specifier, Permissions::allow_all()) .expect("Source file not found"); // NOTE: JavaScript files are only cached to disk if `checkJs` @@ -612,6 +624,10 @@ impl TsCompiler { .get_cache_filename_with_extension(module_specifier.as_url(), "js"); self.disk_cache.set(&js_key, contents.as_bytes())?; self.mark_compiled(module_specifier.as_url()); + let source_file = self + .file_fetcher + .fetch_cached_source_file(&module_specifier, Permissions::allow_all()) + .expect("Source file not found"); let version_hash = source_code_version_hash( &source_file.source_code, @@ -665,7 +681,7 @@ impl TsCompiler { ) -> std::io::Result<()> { let source_file = self .file_fetcher - .fetch_cached_source_file(&module_specifier) + .fetch_cached_source_file(&module_specifier, Permissions::allow_all()) .expect("Source file not found"); // NOTE: JavaScript files are only cached to disk if `checkJs` @@ -720,7 +736,7 @@ impl TsCompiler { if let Some(module_specifier) = self.try_to_resolve(script_name) { return self .file_fetcher - .fetch_cached_source_file(&module_specifier); + .fetch_cached_source_file(&module_specifier, Permissions::allow_all()); } None @@ -743,6 +759,7 @@ impl TsCompiler { async fn execute_in_thread( global_state: GlobalState, + permissions: Permissions, req: Buf, ) -> Result { let (handle_sender, handle_receiver) = @@ -750,7 +767,7 @@ async fn execute_in_thread( let builder = std::thread::Builder::new().name("deno-ts-compiler".to_string()); let join_handle = builder.spawn(move || { - let worker = TsCompiler::setup_worker(global_state.clone()); + let worker = TsCompiler::setup_worker(global_state.clone(), permissions); handle_sender.send(Ok(worker.thread_safe_handle())).unwrap(); drop(handle_sender); tokio_util::run_basic(worker).expect("Panic in event loop"); @@ -772,6 +789,7 @@ async fn execute_in_thread( /// This function is used by `Deno.compile()` and `Deno.bundle()` APIs. pub async fn runtime_compile( global_state: GlobalState, + permissions: Permissions, root_name: &str, sources: &Option>, bundle: bool, @@ -792,7 +810,7 @@ pub async fn runtime_compile( let compiler = global_state.ts_compiler.clone(); - let msg = execute_in_thread(global_state, req_msg).await?; + let msg = execute_in_thread(global_state, permissions, req_msg).await?; let json_str = std::str::from_utf8(&msg).unwrap(); // TODO(bartlomieju): factor `bundle` path into separate function `runtime_bundle` @@ -816,6 +834,7 @@ pub async fn runtime_compile( /// This function is used by `Deno.transpileOnly()` API. pub async fn runtime_transpile( global_state: GlobalState, + permissions: Permissions, sources: &HashMap, options: &Option, ) -> Result { @@ -828,7 +847,7 @@ pub async fn runtime_transpile( .into_boxed_str() .into_boxed_bytes(); - let msg = execute_in_thread(global_state, req_msg).await?; + let msg = execute_in_thread(global_state, permissions, req_msg).await?; let json_str = std::str::from_utf8(&msg).unwrap(); let v = serde_json::from_str::(json_str) .expect("Error decoding JSON string."); @@ -862,7 +881,12 @@ mod tests { GlobalState::mock(vec![String::from("deno"), String::from("hello.js")]); let result = mock_state .ts_compiler - .compile(mock_state.clone(), &out, TargetLib::Main) + .compile( + mock_state.clone(), + &out, + TargetLib::Main, + Permissions::allow_all(), + ) .await; assert!(result.is_ok()); assert!(result diff --git a/docs/runtime/compiler_apis.md b/docs/runtime/compiler_apis.md index 3a06b0b4affb3..30d583331c859 100644 --- a/docs/runtime/compiler_apis.md +++ b/docs/runtime/compiler_apis.md @@ -15,9 +15,11 @@ fully qualified module name, and the value is the text source of the module. If `sources` is passed, Deno will resolve all the modules from within that hash and not attempt to resolve them outside of Deno. If `sources` are not provided, Deno will resolve modules as if the root module had been passed on the command line. -Deno will also cache any of these resources. The `options` argument is a set of -options of type `Deno.CompilerOptions`, which is a subset of the TypeScript -compiler options containing the ones supported by Deno. +Deno will also cache any of these resources. All resolved resources are treated +as dynamic imports and require read or net permissions depending if they're +local or remote. The `options` argument is a set of options of type +`Deno.CompilerOptions`, which is a subset of the TypeScript compiler options +containing the ones supported by Deno. The method resolves with a tuple. The first argument contains any diagnostics (syntax or type errors) related to the code. The second argument is a map where @@ -63,10 +65,12 @@ The `sources` is a hash where the key is the fully qualified module name, and the value is the text source of the module. If `sources` is passed, Deno will resolve all the modules from within that hash and not attempt to resolve them outside of Deno. If `sources` are not provided, Deno will resolve modules as if -the root module had been passed on the command line. Deno will also cache any of -these resources. The `options` argument is a set of options of type -`Deno.CompilerOptions`, which is a subset of the TypeScript compiler options -containing the ones supported by Deno. +the root module had been passed on the command line. All resolved resources are +treated as dynamic imports and require read or net permissions depending if +they're local or remote. Deno will also cache any of these resources. The +`options` argument is a set of options of type `Deno.CompilerOptions`, which is +a subset of the TypeScript compiler options containing the ones supported by +Deno. An example of providing sources: diff --git a/docs/runtime/workers.md b/docs/runtime/workers.md index 3c39d2cee7f6e..16099a35bcacc 100644 --- a/docs/runtime/workers.md +++ b/docs/runtime/workers.md @@ -18,6 +18,49 @@ new Worker("./worker.js"); new Worker("./worker.js", { type: "classic" }); ``` +### Permissions + +Creating a new `Worker` instance is similar to a dynamic import; therefore Deno +requires appropriate permission for this action. + +For workers using local modules; `--allow-read` permission is required: + +```ts +// main.ts +new Worker("./worker.ts", { type: "module" }); + +// worker.ts +console.log("hello world"); +self.close(); +``` + +```shell +$ deno run main.ts +error: Uncaught PermissionDenied: read access to "./worker.ts", run again with the --allow-read flag + +$ deno run --allow-read main.ts +hello world +``` + +For workers using remote modules; `--allow-read` permission is required: + +```ts +// main.ts +new Worker("https://example.com/worker.ts", { type: "module" }); + +// worker.ts +console.log("hello world"); +self.close(); +``` + +```shell +$ deno run main.ts +error: Uncaught PermissionDenied: net access to "https://example.com/worker.ts", run again with the --allow-net flag + +$ deno run --allow-net main.ts +hello world +``` + ### Using Deno in worker > This is an unstable Deno feature. Learn more about