refactor: check permissions in SourceFileFetcher (#5011)

This PR hot-fixes permission escapes in dynamic imports, workers
and runtime compiler APIs.

"permissions" parameter was added to public APIs of SourceFileFetcher
and appropriate permission checks are performed during loading of
local and remote files.
This commit is contained in:
Bartek Iwańczuk 2020-05-11 13:13:27 +02:00 committed by GitHub
parent 0d148c6e80
commit 32aeec9630
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 396 additions and 74 deletions

View file

@ -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<SourceFile> {
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<ModuleSpecifier>,
permissions: Permissions,
) -> Result<SourceFile, ErrBox> {
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<Option<SourceFile>, 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<SourceFile, ErrBox> {
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<SourceFile, ErrBox> {
fn fetch_local_file(
&self,
module_url: &Url,
permissions: &Permissions,
) -> Result<SourceFile, ErrBox> {
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<Box<dyn Future<Output = Result<SourceFile, ErrBox>>>> {
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();

View file

@ -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<ModuleSpecifier>,
target_lib: TargetLib,
permissions: Permissions,
) -> Result<CompiledModule, ErrBox> {
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();

View file

@ -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()))
}

View file

@ -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)?
}

View file

@ -30,10 +30,13 @@ fn op_compile(
) -> Result<JsonOp, OpError> {
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<JsonOp, OpError> {
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))

View file

@ -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

View file

@ -67,6 +67,7 @@ pub struct StateInner {
pub seeded_rng: Option<StdRng>,
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))

View file

@ -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()

View file

@ -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<CompiledModule, ErrBox> {
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<Buf, ErrBox> {
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<S: BuildHasher>(
global_state: GlobalState,
permissions: Permissions,
root_name: &str,
sources: &Option<HashMap<String, String, S>>,
bundle: bool,
@ -792,7 +810,7 @@ pub async fn runtime_compile<S: BuildHasher>(
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<S: BuildHasher>(
/// This function is used by `Deno.transpileOnly()` API.
pub async fn runtime_transpile<S: BuildHasher>(
global_state: GlobalState,
permissions: Permissions,
sources: &HashMap<String, String, S>,
options: &Option<String>,
) -> Result<Value, OpError> {
@ -828,7 +847,7 @@ pub async fn runtime_transpile<S: BuildHasher>(
.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::<serde_json::Value>(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

View file

@ -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:

View file

@ -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