mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
32aeec9630
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.
645 lines
17 KiB
Rust
645 lines
17 KiB
Rust
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
|
#![deny(warnings)]
|
|
|
|
#[macro_use]
|
|
extern crate lazy_static;
|
|
#[macro_use]
|
|
extern crate log;
|
|
extern crate futures;
|
|
#[macro_use]
|
|
extern crate serde_json;
|
|
extern crate clap;
|
|
extern crate deno_core;
|
|
extern crate indexmap;
|
|
#[cfg(unix)]
|
|
extern crate nix;
|
|
extern crate rand;
|
|
extern crate regex;
|
|
extern crate reqwest;
|
|
extern crate serde;
|
|
extern crate serde_derive;
|
|
extern crate tokio;
|
|
extern crate url;
|
|
|
|
mod checksum;
|
|
pub mod colors;
|
|
pub mod deno_dir;
|
|
pub mod diagnostics;
|
|
mod disk_cache;
|
|
mod doc;
|
|
mod file_fetcher;
|
|
pub mod flags;
|
|
mod fmt;
|
|
pub mod fmt_errors;
|
|
mod fs;
|
|
pub mod global_state;
|
|
mod global_timer;
|
|
pub mod http_cache;
|
|
mod http_util;
|
|
mod import_map;
|
|
mod inspector;
|
|
pub mod installer;
|
|
mod js;
|
|
mod lockfile;
|
|
mod metrics;
|
|
pub mod msg;
|
|
pub mod op_error;
|
|
pub mod ops;
|
|
pub mod permissions;
|
|
mod repl;
|
|
pub mod resolve_addr;
|
|
pub mod signal;
|
|
pub mod source_maps;
|
|
mod startup_data;
|
|
pub mod state;
|
|
mod swc_util;
|
|
mod test_runner;
|
|
pub mod test_util;
|
|
mod tokio_util;
|
|
mod tsc;
|
|
mod upgrade;
|
|
pub mod version;
|
|
mod web_worker;
|
|
pub mod worker;
|
|
|
|
pub use dprint_plugin_typescript::swc_common;
|
|
pub use dprint_plugin_typescript::swc_ecma_ast;
|
|
pub use dprint_plugin_typescript::swc_ecma_parser;
|
|
|
|
use crate::doc::parser::DocFileLoader;
|
|
use crate::file_fetcher::SourceFile;
|
|
use crate::file_fetcher::SourceFileFetcher;
|
|
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;
|
|
use crate::worker::MainWorker;
|
|
use deno_core::v8_set_flags;
|
|
use deno_core::ErrBox;
|
|
use deno_core::ModuleSpecifier;
|
|
use flags::DenoSubcommand;
|
|
use flags::Flags;
|
|
use futures::future::FutureExt;
|
|
use futures::Future;
|
|
use log::Level;
|
|
use log::Metadata;
|
|
use log::Record;
|
|
use std::env;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use std::pin::Pin;
|
|
use upgrade::upgrade_command;
|
|
use url::Url;
|
|
|
|
static LOGGER: Logger = Logger;
|
|
|
|
// TODO(ry) Switch to env_logger or other standard crate.
|
|
struct Logger;
|
|
|
|
impl log::Log for Logger {
|
|
fn enabled(&self, metadata: &Metadata) -> bool {
|
|
metadata.level() <= log::max_level()
|
|
}
|
|
|
|
fn log(&self, record: &Record) {
|
|
if self.enabled(record.metadata()) {
|
|
let mut target = record.target().to_string();
|
|
|
|
if let Some(line_no) = record.line() {
|
|
target.push_str(":");
|
|
target.push_str(&line_no.to_string());
|
|
}
|
|
|
|
if record.level() >= Level::Info {
|
|
eprintln!("{}", record.args());
|
|
} else {
|
|
eprintln!("{} RS - {} - {}", record.level(), target, record.args());
|
|
}
|
|
}
|
|
}
|
|
fn flush(&self) {}
|
|
}
|
|
|
|
fn write_to_stdout_ignore_sigpipe(bytes: &[u8]) -> Result<(), std::io::Error> {
|
|
use std::io::ErrorKind;
|
|
|
|
match std::io::stdout().write_all(bytes) {
|
|
Ok(()) => Ok(()),
|
|
Err(e) => match e.kind() {
|
|
ErrorKind::BrokenPipe => Ok(()),
|
|
_ => Err(e),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn create_main_worker(
|
|
global_state: GlobalState,
|
|
main_module: ModuleSpecifier,
|
|
) -> Result<MainWorker, ErrBox> {
|
|
let state = State::new(global_state, None, main_module, DebugType::Main)?;
|
|
|
|
let mut worker = MainWorker::new(
|
|
"main".to_string(),
|
|
startup_data::deno_isolate_init(),
|
|
state,
|
|
);
|
|
|
|
{
|
|
let (stdin, stdout, stderr) = get_stdio();
|
|
let mut t = worker.resource_table.borrow_mut();
|
|
t.add("stdin", Box::new(stdin));
|
|
t.add("stdout", Box::new(stdout));
|
|
t.add("stderr", Box::new(stderr));
|
|
}
|
|
|
|
worker.execute("bootstrap.mainRuntime()")?;
|
|
Ok(worker)
|
|
}
|
|
|
|
fn print_cache_info(state: &GlobalState) {
|
|
println!(
|
|
"{} {:?}",
|
|
colors::bold("DENO_DIR location:".to_string()),
|
|
state.dir.root
|
|
);
|
|
println!(
|
|
"{} {:?}",
|
|
colors::bold("Remote modules cache:".to_string()),
|
|
state.file_fetcher.http_cache.location
|
|
);
|
|
println!(
|
|
"{} {:?}",
|
|
colors::bold("TypeScript compiler cache:".to_string()),
|
|
state.dir.gen_cache.location
|
|
);
|
|
}
|
|
|
|
// TODO(bartlomieju): this function de facto repeats
|
|
// whole compilation stack. Can this be done better somehow?
|
|
async fn print_file_info(
|
|
worker: &MainWorker,
|
|
module_specifier: ModuleSpecifier,
|
|
) -> Result<(), ErrBox> {
|
|
let global_state = worker.state.borrow().global_state.clone();
|
|
|
|
let out = global_state
|
|
.file_fetcher
|
|
.fetch_source_file(&module_specifier, None, Permissions::allow_all())
|
|
.await?;
|
|
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("local:".to_string()),
|
|
out.filename.to_str().unwrap()
|
|
);
|
|
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("type:".to_string()),
|
|
msg::enum_name_media_type(out.media_type)
|
|
);
|
|
|
|
let module_specifier_ = module_specifier.clone();
|
|
global_state
|
|
.clone()
|
|
.fetch_compiled_module(
|
|
module_specifier_,
|
|
None,
|
|
TargetLib::Main,
|
|
Permissions::allow_all(),
|
|
)
|
|
.await?;
|
|
|
|
if out.media_type == msg::MediaType::TypeScript
|
|
|| (out.media_type == msg::MediaType::JavaScript
|
|
&& global_state.ts_compiler.compile_js)
|
|
{
|
|
let compiled_source_file = global_state
|
|
.ts_compiler
|
|
.get_compiled_source_file(&out.url)
|
|
.unwrap();
|
|
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("compiled:".to_string()),
|
|
compiled_source_file.filename.to_str().unwrap(),
|
|
);
|
|
}
|
|
|
|
if let Ok(source_map) = global_state
|
|
.clone()
|
|
.ts_compiler
|
|
.get_source_map_file(&module_specifier)
|
|
{
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("map:".to_string()),
|
|
source_map.filename.to_str().unwrap()
|
|
);
|
|
}
|
|
|
|
if let Some(deps) = worker.isolate.modules.deps(&module_specifier) {
|
|
println!("{}{}", colors::bold("deps:\n".to_string()), deps.name);
|
|
if let Some(ref depsdeps) = deps.deps {
|
|
for d in depsdeps {
|
|
println!("{}", d);
|
|
}
|
|
}
|
|
} else {
|
|
println!(
|
|
"{} cannot retrieve full dependency graph",
|
|
colors::bold("deps:".to_string()),
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_types(unstable: bool) -> String {
|
|
if unstable {
|
|
format!(
|
|
"{}\n{}\n{}\n{}",
|
|
crate::js::DENO_NS_LIB,
|
|
crate::js::SHARED_GLOBALS_LIB,
|
|
crate::js::WINDOW_LIB,
|
|
crate::js::UNSTABLE_NS_LIB,
|
|
)
|
|
} else {
|
|
format!(
|
|
"{}\n{}\n{}",
|
|
crate::js::DENO_NS_LIB,
|
|
crate::js::SHARED_GLOBALS_LIB,
|
|
crate::js::WINDOW_LIB,
|
|
)
|
|
}
|
|
}
|
|
|
|
async fn info_command(
|
|
flags: Flags,
|
|
file: Option<String>,
|
|
) -> Result<(), ErrBox> {
|
|
let global_state = GlobalState::new(flags)?;
|
|
// If it was just "deno info" print location of caches and exit
|
|
if file.is_none() {
|
|
print_cache_info(&global_state);
|
|
return Ok(());
|
|
}
|
|
|
|
let main_module = ModuleSpecifier::resolve_url_or_path(&file.unwrap())?;
|
|
let mut worker = create_main_worker(global_state, main_module.clone())?;
|
|
worker.preload_module(&main_module).await?;
|
|
print_file_info(&worker, main_module.clone()).await
|
|
}
|
|
|
|
async fn install_command(
|
|
flags: Flags,
|
|
module_url: String,
|
|
args: Vec<String>,
|
|
name: Option<String>,
|
|
root: Option<PathBuf>,
|
|
force: bool,
|
|
) -> Result<(), ErrBox> {
|
|
// Firstly fetch and compile module, this step ensures that module exists.
|
|
let mut fetch_flags = flags.clone();
|
|
fetch_flags.reload = true;
|
|
let global_state = GlobalState::new(fetch_flags)?;
|
|
let main_module = ModuleSpecifier::resolve_url_or_path(&module_url)?;
|
|
let mut worker = create_main_worker(global_state, main_module.clone())?;
|
|
worker.preload_module(&main_module).await?;
|
|
installer::install(flags, &module_url, args, name, root, force)
|
|
.map_err(ErrBox::from)
|
|
}
|
|
|
|
async fn cache_command(flags: Flags, files: Vec<String>) -> Result<(), ErrBox> {
|
|
let main_module =
|
|
ModuleSpecifier::resolve_url_or_path("./__$deno$fetch.ts").unwrap();
|
|
let global_state = GlobalState::new(flags)?;
|
|
let mut worker =
|
|
create_main_worker(global_state.clone(), main_module.clone())?;
|
|
|
|
for file in files {
|
|
let specifier = ModuleSpecifier::resolve_url_or_path(&file)?;
|
|
worker.preload_module(&specifier).await.map(|_| ())?;
|
|
}
|
|
|
|
if global_state.flags.lock_write {
|
|
if let Some(ref lockfile) = global_state.lockfile {
|
|
let g = lockfile.lock().unwrap();
|
|
g.write()?;
|
|
} else {
|
|
eprintln!("--lock flag must be specified when using --lock-write");
|
|
std::process::exit(11);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn eval_command(
|
|
flags: Flags,
|
|
code: String,
|
|
as_typescript: bool,
|
|
) -> Result<(), ErrBox> {
|
|
// Force TypeScript compile.
|
|
let main_module =
|
|
ModuleSpecifier::resolve_url_or_path("./__$deno$eval.ts").unwrap();
|
|
let global_state = GlobalState::new(flags)?;
|
|
let mut worker = create_main_worker(global_state, main_module.clone())?;
|
|
let main_module_url = main_module.as_url().to_owned();
|
|
// Create a dummy source file.
|
|
let source_file = SourceFile {
|
|
filename: main_module_url.to_file_path().unwrap(),
|
|
url: main_module_url,
|
|
types_url: None,
|
|
media_type: if as_typescript {
|
|
MediaType::TypeScript
|
|
} else {
|
|
MediaType::JavaScript
|
|
},
|
|
source_code: code.clone().into_bytes(),
|
|
};
|
|
// Save our fake file into file fetcher cache
|
|
// to allow module access by TS compiler (e.g. op_fetch_source_files)
|
|
worker
|
|
.state
|
|
.borrow()
|
|
.global_state
|
|
.file_fetcher
|
|
.save_source_file_in_cache(&main_module, source_file);
|
|
debug!("main_module {}", &main_module);
|
|
worker.execute_module(&main_module).await?;
|
|
worker.execute("window.dispatchEvent(new Event('load'))")?;
|
|
(&mut *worker).await?;
|
|
worker.execute("window.dispatchEvent(new Event('unload'))")?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn bundle_command(
|
|
flags: Flags,
|
|
source_file: String,
|
|
out_file: Option<PathBuf>,
|
|
) -> Result<(), ErrBox> {
|
|
let module_name = ModuleSpecifier::resolve_url_or_path(&source_file)?;
|
|
let global_state = GlobalState::new(flags)?;
|
|
debug!(">>>>> bundle START");
|
|
let bundle_result = global_state
|
|
.ts_compiler
|
|
.bundle(global_state.clone(), module_name.to_string(), out_file)
|
|
.await;
|
|
debug!(">>>>> bundle END");
|
|
bundle_result
|
|
}
|
|
|
|
async fn doc_command(
|
|
flags: Flags,
|
|
source_file: Option<String>,
|
|
json: bool,
|
|
maybe_filter: Option<String>,
|
|
) -> Result<(), ErrBox> {
|
|
let global_state = GlobalState::new(flags.clone())?;
|
|
let source_file = source_file.unwrap_or_else(|| "--builtin".to_string());
|
|
|
|
impl DocFileLoader for SourceFileFetcher {
|
|
fn load_source_code(
|
|
&self,
|
|
specifier: &str,
|
|
) -> Pin<Box<dyn Future<Output = Result<String, OpError>>>> {
|
|
let specifier =
|
|
ModuleSpecifier::resolve_url_or_path(specifier).expect("Bad specifier");
|
|
let fetcher = self.clone();
|
|
|
|
async move {
|
|
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()))
|
|
}
|
|
.boxed_local()
|
|
}
|
|
}
|
|
|
|
let loader = Box::new(global_state.file_fetcher.clone());
|
|
let doc_parser = doc::DocParser::new(loader);
|
|
|
|
let parse_result = if source_file == "--builtin" {
|
|
doc_parser.parse_source("lib.deno.d.ts", get_types(flags.unstable).as_str())
|
|
} else {
|
|
let module_specifier =
|
|
ModuleSpecifier::resolve_url_or_path(&source_file).unwrap();
|
|
doc_parser
|
|
.parse_with_reexports(&module_specifier.to_string())
|
|
.await
|
|
};
|
|
|
|
let doc_nodes = match parse_result {
|
|
Ok(nodes) => nodes,
|
|
Err(e) => {
|
|
eprintln!("{}", e);
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
if json {
|
|
let writer = std::io::BufWriter::new(std::io::stdout());
|
|
serde_json::to_writer_pretty(writer, &doc_nodes).map_err(ErrBox::from)
|
|
} else {
|
|
let details = if let Some(filter) = maybe_filter {
|
|
let node = doc::find_node_by_name_recursively(doc_nodes, filter.clone());
|
|
if let Some(node) = node {
|
|
doc::printer::format_details(node)
|
|
} else {
|
|
eprintln!("Node {} was not found!", filter);
|
|
std::process::exit(1);
|
|
}
|
|
} else {
|
|
doc::printer::format(doc_nodes)
|
|
};
|
|
|
|
write_to_stdout_ignore_sigpipe(details.as_bytes()).map_err(ErrBox::from)
|
|
}
|
|
}
|
|
|
|
async fn run_repl(flags: Flags) -> Result<(), ErrBox> {
|
|
let main_module =
|
|
ModuleSpecifier::resolve_url_or_path("./__$deno$repl.ts").unwrap();
|
|
let global_state = GlobalState::new(flags)?;
|
|
let mut worker = create_main_worker(global_state, main_module)?;
|
|
loop {
|
|
(&mut *worker).await?;
|
|
}
|
|
}
|
|
|
|
async fn run_command(flags: Flags, script: String) -> Result<(), ErrBox> {
|
|
let global_state = GlobalState::new(flags.clone())?;
|
|
let main_module = ModuleSpecifier::resolve_url_or_path(&script).unwrap();
|
|
let mut worker =
|
|
create_main_worker(global_state.clone(), main_module.clone())?;
|
|
debug!("main_module {}", main_module);
|
|
worker.execute_module(&main_module).await?;
|
|
worker.execute("window.dispatchEvent(new Event('load'))")?;
|
|
(&mut *worker).await?;
|
|
worker.execute("window.dispatchEvent(new Event('unload'))")?;
|
|
if global_state.flags.lock_write {
|
|
if let Some(ref lockfile) = global_state.lockfile {
|
|
let g = lockfile.lock().unwrap();
|
|
g.write()?;
|
|
} else {
|
|
eprintln!("--lock flag must be specified when using --lock-write");
|
|
std::process::exit(11);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_command(
|
|
flags: Flags,
|
|
include: Option<Vec<String>>,
|
|
fail_fast: bool,
|
|
quiet: bool,
|
|
allow_none: bool,
|
|
filter: Option<String>,
|
|
) -> Result<(), ErrBox> {
|
|
let global_state = GlobalState::new(flags.clone())?;
|
|
let cwd = std::env::current_dir().expect("No current directory");
|
|
let include = include.unwrap_or_else(|| vec![".".to_string()]);
|
|
let test_modules = test_runner::prepare_test_modules_urls(include, &cwd)?;
|
|
|
|
if test_modules.is_empty() {
|
|
println!("No matching test modules found");
|
|
if !allow_none {
|
|
std::process::exit(1);
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
let test_file_path = cwd.join(".deno.test.ts");
|
|
let test_file_url =
|
|
Url::from_file_path(&test_file_path).expect("Should be valid file url");
|
|
let test_file =
|
|
test_runner::render_test_file(test_modules, fail_fast, quiet, filter);
|
|
let main_module =
|
|
ModuleSpecifier::resolve_url(&test_file_url.to_string()).unwrap();
|
|
let mut worker =
|
|
create_main_worker(global_state.clone(), main_module.clone())?;
|
|
// Create a dummy source file.
|
|
let source_file = SourceFile {
|
|
filename: test_file_url.to_file_path().unwrap(),
|
|
url: test_file_url,
|
|
types_url: None,
|
|
media_type: MediaType::TypeScript,
|
|
source_code: test_file.clone().into_bytes(),
|
|
};
|
|
// Save our fake file into file fetcher cache
|
|
// to allow module access by TS compiler (e.g. op_fetch_source_files)
|
|
worker
|
|
.state
|
|
.borrow()
|
|
.global_state
|
|
.file_fetcher
|
|
.save_source_file_in_cache(&main_module, source_file);
|
|
let execute_result = worker.execute_module(&main_module).await;
|
|
execute_result?;
|
|
worker.execute("window.dispatchEvent(new Event('load'))")?;
|
|
(&mut *worker).await?;
|
|
worker.execute("window.dispatchEvent(new Event('unload'))")
|
|
}
|
|
|
|
pub fn main() {
|
|
#[cfg(windows)]
|
|
colors::enable_ansi(); // For Windows 10
|
|
|
|
log::set_logger(&LOGGER).unwrap();
|
|
let args: Vec<String> = env::args().collect();
|
|
let flags = flags::flags_from_vec(args);
|
|
|
|
if let Some(ref v8_flags) = flags.v8_flags {
|
|
let mut v8_flags_ = v8_flags.clone();
|
|
v8_flags_.insert(0, "UNUSED_BUT_NECESSARY_ARG0".to_string());
|
|
v8_set_flags(v8_flags_);
|
|
}
|
|
|
|
let log_level = match flags.log_level {
|
|
Some(level) => level,
|
|
None => Level::Info, // Default log level
|
|
};
|
|
log::set_max_level(log_level.to_level_filter());
|
|
|
|
let fut = match flags.clone().subcommand {
|
|
DenoSubcommand::Bundle {
|
|
source_file,
|
|
out_file,
|
|
} => bundle_command(flags, source_file, out_file).boxed_local(),
|
|
DenoSubcommand::Doc {
|
|
source_file,
|
|
json,
|
|
filter,
|
|
} => doc_command(flags, source_file, json, filter).boxed_local(),
|
|
DenoSubcommand::Eval {
|
|
code,
|
|
as_typescript,
|
|
} => eval_command(flags, code, as_typescript).boxed_local(),
|
|
DenoSubcommand::Cache { files } => {
|
|
cache_command(flags, files).boxed_local()
|
|
}
|
|
DenoSubcommand::Fmt { check, files } => {
|
|
fmt::format(files, check).boxed_local()
|
|
}
|
|
DenoSubcommand::Info { file } => info_command(flags, file).boxed_local(),
|
|
DenoSubcommand::Install {
|
|
module_url,
|
|
args,
|
|
name,
|
|
root,
|
|
force,
|
|
} => {
|
|
install_command(flags, module_url, args, name, root, force).boxed_local()
|
|
}
|
|
DenoSubcommand::Repl => run_repl(flags).boxed_local(),
|
|
DenoSubcommand::Run { script } => run_command(flags, script).boxed_local(),
|
|
DenoSubcommand::Test {
|
|
fail_fast,
|
|
quiet,
|
|
include,
|
|
allow_none,
|
|
filter,
|
|
} => test_command(flags, include, fail_fast, quiet, allow_none, filter)
|
|
.boxed_local(),
|
|
DenoSubcommand::Completions { buf } => {
|
|
if let Err(e) = write_to_stdout_ignore_sigpipe(&buf) {
|
|
eprintln!("{}", e);
|
|
std::process::exit(1);
|
|
}
|
|
return;
|
|
}
|
|
DenoSubcommand::Types => {
|
|
let types = get_types(flags.unstable);
|
|
if let Err(e) = write_to_stdout_ignore_sigpipe(types.as_bytes()) {
|
|
eprintln!("{}", e);
|
|
std::process::exit(1);
|
|
}
|
|
return;
|
|
}
|
|
DenoSubcommand::Upgrade {
|
|
force,
|
|
dry_run,
|
|
version,
|
|
} => upgrade_command(dry_run, force, version).boxed_local(),
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let result = tokio_util::run_basic(fut);
|
|
if let Err(err) = result {
|
|
let msg = format!(
|
|
"{}: {}",
|
|
colors::red_bold("error".to_string()),
|
|
err.to_string(),
|
|
);
|
|
eprintln!("{}", msg);
|
|
std::process::exit(1);
|
|
}
|
|
}
|