From fa3c35301aa44975776b96c85f200de8eb500c22 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 19 Mar 2019 12:18:05 -0400 Subject: Rename //src/ to //cli/ (#1962) To better distinguish the deno_core crate from the executable deno, which will now be called "the cli" internally. --- BUILD.gn | 8 +- Cargo.toml | 4 + cli/ansi.rs | 70 ++ cli/cli.rs | 90 +++ cli/compiler.rs | 151 ++++ cli/deno_dir.rs | 1369 ++++++++++++++++++++++++++++++++++ cli/errors.rs | 207 ++++++ cli/flags.rs | 291 ++++++++ cli/fs.rs | 110 +++ cli/global_timer.rs | 49 ++ cli/http_body.rs | 112 +++ cli/http_util.rs | 166 +++++ cli/isolate.rs | 236 ++++++ cli/isolate_state.rs | 110 +++ cli/js_errors.rs | 424 +++++++++++ cli/main.rs | 140 ++++ cli/modules.rs | 204 +++++ cli/msg.fbs | 524 +++++++++++++ cli/msg.rs | 26 + cli/msg_util.rs | 127 ++++ cli/ops.rs | 2020 ++++++++++++++++++++++++++++++++++++++++++++++++++ cli/permissions.rs | 343 +++++++++ cli/repl.rs | 114 +++ cli/resolve_addr.rs | 156 ++++ cli/resources.rs | 494 ++++++++++++ cli/startup_data.rs | 57 ++ cli/tokio_util.rs | 118 +++ cli/tokio_write.rs | 62 ++ cli/version.rs | 6 + cli/workers.rs | 181 +++++ js/stat_test.ts | 24 +- src/ansi.rs | 70 -- src/cli.rs | 90 --- src/compiler.rs | 151 ---- src/deno_dir.rs | 1369 ---------------------------------- src/errors.rs | 207 ------ src/flags.rs | 291 -------- src/fs.rs | 110 --- src/global_timer.rs | 49 -- src/http_body.rs | 112 --- src/http_util.rs | 166 ----- src/isolate.rs | 236 ------ src/isolate_state.rs | 110 --- src/js_errors.rs | 424 ----------- src/main.rs | 140 ---- src/modules.rs | 204 ----- src/msg.fbs | 524 ------------- src/msg.rs | 26 - src/msg_util.rs | 127 ---- src/ops.rs | 2020 -------------------------------------------------- src/permissions.rs | 343 --------- src/repl.rs | 114 --- src/resolve_addr.rs | 156 ---- src/resources.rs | 494 ------------ src/startup_data.rs | 57 -- src/tokio_util.rs | 118 --- src/tokio_write.rs | 62 -- src/version.rs | 6 - src/workers.rs | 181 ----- tools/format.py | 2 +- 60 files changed, 7978 insertions(+), 7974 deletions(-) create mode 100644 cli/ansi.rs create mode 100644 cli/cli.rs create mode 100644 cli/compiler.rs create mode 100644 cli/deno_dir.rs create mode 100644 cli/errors.rs create mode 100644 cli/flags.rs create mode 100644 cli/fs.rs create mode 100644 cli/global_timer.rs create mode 100644 cli/http_body.rs create mode 100644 cli/http_util.rs create mode 100644 cli/isolate.rs create mode 100644 cli/isolate_state.rs create mode 100644 cli/js_errors.rs create mode 100644 cli/main.rs create mode 100644 cli/modules.rs create mode 100644 cli/msg.fbs create mode 100644 cli/msg.rs create mode 100644 cli/msg_util.rs create mode 100644 cli/ops.rs create mode 100644 cli/permissions.rs create mode 100644 cli/repl.rs create mode 100644 cli/resolve_addr.rs create mode 100644 cli/resources.rs create mode 100644 cli/startup_data.rs create mode 100644 cli/tokio_util.rs create mode 100644 cli/tokio_write.rs create mode 100644 cli/version.rs create mode 100644 cli/workers.rs delete mode 100644 src/ansi.rs delete mode 100644 src/cli.rs delete mode 100644 src/compiler.rs delete mode 100644 src/deno_dir.rs delete mode 100644 src/errors.rs delete mode 100644 src/flags.rs delete mode 100644 src/fs.rs delete mode 100644 src/global_timer.rs delete mode 100644 src/http_body.rs delete mode 100644 src/http_util.rs delete mode 100644 src/isolate.rs delete mode 100644 src/isolate_state.rs delete mode 100644 src/js_errors.rs delete mode 100644 src/main.rs delete mode 100644 src/modules.rs delete mode 100644 src/msg.fbs delete mode 100644 src/msg.rs delete mode 100644 src/msg_util.rs delete mode 100644 src/ops.rs delete mode 100644 src/permissions.rs delete mode 100644 src/repl.rs delete mode 100644 src/resolve_addr.rs delete mode 100644 src/resources.rs delete mode 100644 src/startup_data.rs delete mode 100644 src/tokio_util.rs delete mode 100644 src/tokio_write.rs delete mode 100644 src/version.rs delete mode 100644 src/workers.rs diff --git a/BUILD.gn b/BUILD.gn index 8b526bc2e..41a84613e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -154,7 +154,7 @@ deno_cargo_info = exec_script("build_extra/rust/get_cargo_info.py", "json") rust_executable("deno") { - source_root = "src/main.rs" + source_root = "cli/main.rs" extern = main_extern deps = [ ":deno_deps", @@ -170,7 +170,7 @@ rust_executable("deno") { } rust_test("test_rs") { - source_root = "src/main.rs" + source_root = "cli/main.rs" extern = main_extern deps = [ ":deno_deps", @@ -257,13 +257,13 @@ bundle("compiler_bundle") { ts_flatbuffer("msg_ts") { sources = [ - "src/msg.fbs", + "cli/msg.fbs", ] } rust_flatbuffer("msg_rs") { sources = [ - "src/msg.fbs", + "cli/msg.fbs", ] } diff --git a/Cargo.toml b/Cargo.toml index b603ac593..5864d268c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,10 @@ members = [ "core", ] +[[bin]] +name = "deno" +path = "cli/main.rs" + [package] name = "deno" version = "0.3.3" diff --git a/cli/ansi.rs b/cli/ansi.rs new file mode 100644 index 000000000..95b5e0694 --- /dev/null +++ b/cli/ansi.rs @@ -0,0 +1,70 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use ansi_term::Color::Fixed; +use ansi_term::Color::Red; +use ansi_term::Style; +use regex::Regex; +use std::env; +use std::fmt; + +lazy_static! { + // STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate. + // Copyright 2017 Armin Ronacher . MIT License. + static ref STRIP_ANSI_RE: Regex = Regex::new( + r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]" + ).unwrap(); + static ref NO_COLOR: bool = { + env::var_os("NO_COLOR").is_some() + }; +} + +/// Helper function to strip ansi codes. +#[cfg(test)] +pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow { + STRIP_ANSI_RE.replace_all(s, "") +} + +pub fn use_color() -> bool { + !(*NO_COLOR) +} + +pub fn red_bold(s: String) -> impl fmt::Display { + let mut style = Style::new(); + if use_color() { + style = style.bold().fg(Red); + } + style.paint(s) +} + +pub fn italic_bold(s: String) -> impl fmt::Display { + let mut style = Style::new(); + if use_color() { + style = style.italic().bold(); + } + style.paint(s) +} + +pub fn yellow(s: String) -> impl fmt::Display { + let mut style = Style::new(); + if use_color() { + // matches TypeScript's ForegroundColorEscapeSequences.Yellow + style = style.fg(Fixed(11)); + } + style.paint(s) +} + +pub fn cyan(s: String) -> impl fmt::Display { + let mut style = Style::new(); + if use_color() { + // matches TypeScript's ForegroundColorEscapeSequences.Cyan + style = style.fg(Fixed(14)); + } + style.paint(s) +} + +pub fn bold(s: String) -> impl fmt::Display { + let mut style = Style::new(); + if use_color() { + style = style.bold(); + } + style.paint(s) +} diff --git a/cli/cli.rs b/cli/cli.rs new file mode 100644 index 000000000..42b2b29f8 --- /dev/null +++ b/cli/cli.rs @@ -0,0 +1,90 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +#![allow(unused_variables)] +#![allow(dead_code)] + +use crate::errors::DenoResult; +use crate::isolate_state::IsolateState; +use crate::ops; +use crate::permissions::DenoPermissions; +use deno_core::deno_buf; +use deno_core::deno_mod; +use deno_core::Behavior; +use deno_core::Op; +use deno_core::StartupData; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +// Buf represents a byte array returned from a "Op". The message might be empty +// (which will be translated into a null object on the javascript side) or it is +// a heap allocated opaque sequence of bytes. Usually a flatbuffer message. +pub type Buf = Box<[u8]>; + +/// Implements deno_core::Behavior for the main Deno command-line. +pub struct Cli { + startup_data: Option, + pub state: Arc, + pub permissions: Arc, // TODO(ry) move to IsolateState +} + +impl Cli { + pub fn new( + startup_data: Option, + state: Arc, + permissions: DenoPermissions, + ) -> Self { + Self { + startup_data, + state, + permissions: Arc::new(permissions), + } + } + + #[inline] + pub fn check_read(&self, filename: &str) -> DenoResult<()> { + self.permissions.check_read(filename) + } + + #[inline] + pub fn check_write(&self, filename: &str) -> DenoResult<()> { + self.permissions.check_write(filename) + } + + #[inline] + pub fn check_env(&self) -> DenoResult<()> { + self.permissions.check_env() + } + + #[inline] + pub fn check_net(&self, filename: &str) -> DenoResult<()> { + self.permissions.check_net(filename) + } + + #[inline] + pub fn check_run(&self) -> DenoResult<()> { + self.permissions.check_run() + } +} + +impl Behavior for Cli { + fn startup_data(&mut self) -> Option { + self.startup_data.take() + } + + fn resolve(&mut self, specifier: &str, referrer: deno_mod) -> deno_mod { + self + .state + .metrics + .resolve_count + .fetch_add(1, Ordering::Relaxed); + let mut modules = self.state.modules.lock().unwrap(); + modules.resolve_cb(&self.state.dir, specifier, referrer) + } + + fn dispatch( + &mut self, + control: &[u8], + zero_copy: deno_buf, + ) -> (bool, Box) { + ops::dispatch(self, control, zero_copy) + } +} diff --git a/cli/compiler.rs b/cli/compiler.rs new file mode 100644 index 000000000..2d6e4a4b7 --- /dev/null +++ b/cli/compiler.rs @@ -0,0 +1,151 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::cli::Buf; +use crate::isolate_state::IsolateState; +use crate::msg; +use crate::permissions::{DenoPermissions, PermissionAccessor}; +use crate::resources; +use crate::resources::Resource; +use crate::resources::ResourceId; +use crate::startup_data; +use crate::workers; +use futures::Future; +use serde_json; +use std::str; +use std::sync::Mutex; + +lazy_static! { + static ref C_RID: Mutex> = Mutex::new(None); +} + +// This corresponds to JS ModuleMetaData. +// TODO Rename one or the other so they correspond. +#[derive(Debug)] +pub struct ModuleMetaData { + pub module_name: String, + pub filename: String, + pub media_type: msg::MediaType, + pub source_code: Vec, + pub maybe_output_code_filename: Option, + pub maybe_output_code: Option>, + pub maybe_source_map_filename: Option, + pub maybe_source_map: Option>, +} + +impl ModuleMetaData { + pub fn js_source(&self) -> String { + if self.media_type == msg::MediaType::Json { + return format!( + "export default {};", + str::from_utf8(&self.source_code).unwrap() + ); + } + match self.maybe_output_code { + None => str::from_utf8(&self.source_code).unwrap().to_string(), + Some(ref output_code) => str::from_utf8(output_code).unwrap().to_string(), + } + } +} + +fn lazy_start(parent_state: &IsolateState) -> Resource { + let mut cell = C_RID.lock().unwrap(); + let startup_data = startup_data::compiler_isolate_init(); + let permissions = DenoPermissions { + allow_read: PermissionAccessor::from(true), + allow_write: PermissionAccessor::from(true), + allow_net: PermissionAccessor::from(true), + ..Default::default() + }; + + let rid = cell.get_or_insert_with(|| { + let resource = workers::spawn( + Some(startup_data), + parent_state, + "compilerMain()".to_string(), + permissions, + ); + resource.rid + }); + Resource { rid: *rid } +} + +fn req(specifier: &str, referrer: &str) -> Buf { + json!({ + "specifier": specifier, + "referrer": referrer, + }).to_string() + .into_boxed_str() + .into_boxed_bytes() +} + +pub fn compile_sync( + parent_state: &IsolateState, + specifier: &str, + referrer: &str, + module_meta_data: &ModuleMetaData, +) -> ModuleMetaData { + let req_msg = req(specifier, referrer); + + let compiler = lazy_start(parent_state); + + let send_future = resources::worker_post_message(compiler.rid, req_msg); + send_future.wait().unwrap(); + + let recv_future = resources::worker_recv_message(compiler.rid); + let result = recv_future.wait().unwrap(); + assert!(result.is_some()); + let res_msg = result.unwrap(); + + let res_json = std::str::from_utf8(&res_msg).unwrap(); + match serde_json::from_str::(res_json) { + Ok(serde_json::Value::Object(map)) => ModuleMetaData { + module_name: module_meta_data.module_name.clone(), + filename: module_meta_data.filename.clone(), + media_type: module_meta_data.media_type, + source_code: module_meta_data.source_code.clone(), + maybe_output_code: match map["outputCode"].as_str() { + Some(str) => Some(str.as_bytes().to_owned()), + _ => None, + }, + maybe_output_code_filename: None, + maybe_source_map: match map["sourceMap"].as_str() { + Some(str) => Some(str.as_bytes().to_owned()), + _ => None, + }, + maybe_source_map_filename: None, + }, + _ => panic!("error decoding compiler response"), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compile_sync() { + let cwd = std::env::current_dir().unwrap(); + let cwd_string = cwd.to_str().unwrap().to_owned(); + + let specifier = "./tests/002_hello.ts"; + let referrer = cwd_string + "/"; + + let mut out = ModuleMetaData { + module_name: "xxx".to_owned(), + filename: "/tests/002_hello.ts".to_owned(), + media_type: msg::MediaType::TypeScript, + source_code: "console.log(\"Hello World\");".as_bytes().to_owned(), + maybe_output_code_filename: None, + maybe_output_code: None, + maybe_source_map_filename: None, + maybe_source_map: None, + }; + + out = compile_sync(&IsolateState::mock(), specifier, &referrer, &mut out); + assert!( + out + .maybe_output_code + .unwrap() + .starts_with("console.log(\"Hello World\");".as_bytes()) + ); + } +} diff --git a/cli/deno_dir.rs b/cli/deno_dir.rs new file mode 100644 index 000000000..829af5aa2 --- /dev/null +++ b/cli/deno_dir.rs @@ -0,0 +1,1369 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::compiler::ModuleMetaData; +use crate::errors; +use crate::errors::DenoError; +use crate::errors::DenoResult; +use crate::errors::ErrorKind; +use crate::fs as deno_fs; +use crate::http_util; +use crate::js_errors::SourceMapGetter; +use crate::msg; +use crate::version; + +use dirs; +use ring; +use std; +use std::fmt::Write; +use std::fs; +use std::path::Path; +use std::path::PathBuf; +use std::result::Result; +use std::str; +use url; +use url::Url; + +/// Gets corresponding MediaType given extension +fn extmap(ext: &str) -> msg::MediaType { + match ext { + "ts" => msg::MediaType::TypeScript, + "js" => msg::MediaType::JavaScript, + "json" => msg::MediaType::Json, + _ => msg::MediaType::Unknown, + } +} + +pub struct DenoDir { + // Example: /Users/rld/.deno/ + pub root: PathBuf, + // In the Go code this was called SrcDir. + // This is where we cache http resources. Example: + // /Users/rld/.deno/deps/github.com/ry/blah.js + pub gen: PathBuf, + // In the Go code this was called CacheDir. + // This is where we cache compilation outputs. Example: + // /Users/rld/.deno/gen/f39a473452321cacd7c346a870efb0e3e1264b43.js + pub deps: PathBuf, + // This splits to http and https deps + pub deps_http: PathBuf, + pub deps_https: PathBuf, + // If remote resources should be reloaded. + reload: bool, + // recompile the typescript files. + // if true, not load cache files + // else, load cache files + recompile: bool, +} + +impl DenoDir { + // Must be called before using any function from this module. + // https://github.com/denoland/deno/blob/golang/deno_dir.go#L99-L111 + pub fn new( + reload: bool, + recompile: bool, + custom_root: Option, + ) -> std::io::Result { + // Only setup once. + let home_dir = dirs::home_dir().expect("Could not get home directory."); + let fallback = home_dir.join(".deno"); + // We use the OS cache dir because all files deno writes are cache files + // Once that changes we need to start using different roots if DENO_DIR + // is not set, and keep a single one if it is. + let default = dirs::cache_dir() + .map(|d| d.join("deno")) + .unwrap_or(fallback); + + let root: PathBuf = custom_root.unwrap_or(default); + let gen = root.as_path().join("gen"); + let deps = root.as_path().join("deps"); + let deps_http = deps.join("http"); + let deps_https = deps.join("https"); + + let deno_dir = Self { + root, + gen, + deps, + deps_http, + deps_https, + reload, + recompile, + }; + + // TODO Lazily create these directories. + deno_fs::mkdir(deno_dir.gen.as_ref(), 0o755, true)?; + deno_fs::mkdir(deno_dir.deps.as_ref(), 0o755, true)?; + deno_fs::mkdir(deno_dir.deps_http.as_ref(), 0o755, true)?; + deno_fs::mkdir(deno_dir.deps_https.as_ref(), 0o755, true)?; + + debug!("root {}", deno_dir.root.display()); + debug!("gen {}", deno_dir.gen.display()); + debug!("deps {}", deno_dir.deps.display()); + debug!("deps_http {}", deno_dir.deps_http.display()); + debug!("deps_https {}", deno_dir.deps_https.display()); + + Ok(deno_dir) + } + + // https://github.com/denoland/deno/blob/golang/deno_dir.go#L32-L35 + pub fn cache_path( + self: &Self, + filename: &str, + source_code: &[u8], + ) -> (PathBuf, PathBuf) { + let cache_key = source_code_hash(filename, source_code, version::DENO); + ( + self.gen.join(cache_key.to_string() + ".js"), + self.gen.join(cache_key.to_string() + ".js.map"), + ) + } + + fn load_cache( + self: &Self, + filename: &str, + source_code: &[u8], + ) -> Result<(Vec, Vec), std::io::Error> { + let (output_code, source_map) = self.cache_path(filename, source_code); + debug!( + "load_cache code: {} map: {}", + output_code.display(), + source_map.display() + ); + let read_output_code = fs::read(&output_code)?; + let read_source_map = fs::read(&source_map)?; + Ok((read_output_code, read_source_map)) + } + + pub fn code_cache( + self: &Self, + module_meta_data: &ModuleMetaData, + ) -> std::io::Result<()> { + let (cache_path, source_map_path) = self + .cache_path(&module_meta_data.filename, &module_meta_data.source_code); + // TODO(ry) This is a race condition w.r.t to exists() -- probably should + // create the file in exclusive mode. A worry is what might happen is there + // are two processes and one reads the cache file while the other is in the + // midst of writing it. + if cache_path.exists() && source_map_path.exists() { + Ok(()) + } else { + match &module_meta_data.maybe_output_code { + Some(output_code) => fs::write(cache_path, output_code), + _ => Ok(()), + }?; + match &module_meta_data.maybe_source_map { + Some(source_map) => fs::write(source_map_path, source_map), + _ => Ok(()), + }?; + Ok(()) + } + } + + // Prototype https://github.com/denoland/deno/blob/golang/deno_dir.go#L37-L73 + /// Fetch remote source code. + fn fetch_remote_source( + self: &Self, + module_name: &str, + filename: &str, + ) -> DenoResult> { + let p = Path::new(&filename); + // We write a special ".mime" file into the `.deno/deps` directory along side the + // cached file, containing just the media type. + let media_type_filename = [&filename, ".mime"].concat(); + let mt = Path::new(&media_type_filename); + eprint!("Downloading {}...", &module_name); // no newline + let maybe_source = http_util::fetch_sync_string(&module_name); + if let Ok((source, content_type)) = maybe_source { + eprintln!(""); // next line + match p.parent() { + Some(ref parent) => fs::create_dir_all(parent), + None => Ok(()), + }?; + deno_fs::write_file(&p, &source, 0o666)?; + // Remove possibly existing stale .mime file + // may not exist. DON'T unwrap + let _ = std::fs::remove_file(&media_type_filename); + // Create .mime file only when content type different from extension + let resolved_content_type = map_content_type(&p, Some(&content_type)); + let ext = p + .extension() + .map(|x| x.to_str().unwrap_or("")) + .unwrap_or(""); + let media_type = extmap(&ext); + if media_type == msg::MediaType::Unknown + || media_type != resolved_content_type + { + deno_fs::write_file(&mt, content_type.as_bytes(), 0o666)? + } + return Ok(Some(ModuleMetaData { + module_name: module_name.to_string(), + filename: filename.to_string(), + media_type: map_content_type(&p, Some(&content_type)), + source_code: source.as_bytes().to_owned(), + maybe_output_code_filename: None, + maybe_output_code: None, + maybe_source_map_filename: None, + maybe_source_map: None, + })); + } else { + eprintln!(" NOT FOUND"); + } + Ok(None) + } + + /// Fetch local or cached source code. + fn fetch_local_source( + self: &Self, + module_name: &str, + filename: &str, + ) -> DenoResult> { + let p = Path::new(&filename); + let media_type_filename = [&filename, ".mime"].concat(); + let mt = Path::new(&media_type_filename); + let source_code = match fs::read(p) { + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + return Ok(None); + } else { + return Err(e.into()); + } + } + Ok(c) => c, + }; + // .mime file might not exists + // this is okay for local source: maybe_content_type_str will be None + let maybe_content_type_string = fs::read_to_string(&mt).ok(); + // Option -> Option<&str> + let maybe_content_type_str = + maybe_content_type_string.as_ref().map(String::as_str); + Ok(Some(ModuleMetaData { + module_name: module_name.to_string(), + filename: filename.to_string(), + media_type: map_content_type(&p, maybe_content_type_str), + source_code, + maybe_output_code_filename: None, + maybe_output_code: None, + maybe_source_map_filename: None, + maybe_source_map: None, + })) + } + + // Prototype: https://github.com/denoland/deno/blob/golang/os.go#L122-L138 + fn get_source_code( + self: &Self, + module_name: &str, + filename: &str, + ) -> DenoResult { + let is_module_remote = is_remote(module_name); + // We try fetch local. Two cases: + // 1. This is a remote module, but no reload provided + // 2. This is a local module + if !is_module_remote || !self.reload { + debug!( + "fetch local or reload {} is_module_remote {}", + module_name, is_module_remote + ); + match self.fetch_local_source(&module_name, &filename)? { + Some(output) => { + debug!("found local source "); + return Ok(output); + } + None => { + debug!("fetch_local_source returned None"); + } + } + } + + // If not remote file, stop here! + if !is_module_remote { + debug!("not remote file stop here"); + return Err(DenoError::from(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("cannot find local file '{}'", filename), + ))); + } + + debug!("is remote but didn't find module"); + + // not cached/local, try remote + let maybe_remote_source = + self.fetch_remote_source(&module_name, &filename)?; + if let Some(output) = maybe_remote_source { + return Ok(output); + } + Err(DenoError::from(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("cannot find remote file '{}'", filename), + ))) + } + + pub fn fetch_module_meta_data( + self: &Self, + specifier: &str, + referrer: &str, + ) -> Result { + debug!( + "fetch_module_meta_data. specifier {} referrer {}", + specifier, referrer + ); + + let (module_name, filename) = self.resolve_module(specifier, referrer)?; + + let result = self.get_source_code(module_name.as_str(), filename.as_str()); + let mut out = match result { + Ok(out) => out, + Err(err) => { + if err.kind() == ErrorKind::NotFound { + // For NotFound, change the message to something better. + return Err(errors::new( + ErrorKind::NotFound, + format!( + "Cannot resolve module \"{}\" from \"{}\"", + specifier, referrer + ), + )); + } else { + return Err(err); + } + } + }; + + if out.source_code.starts_with("#!".as_bytes()) { + out.source_code = filter_shebang(out.source_code); + } + + if out.media_type != msg::MediaType::TypeScript { + return Ok(out); + } + + let (output_code_filename, output_source_map_filename) = + self.cache_path(&out.filename, &out.source_code); + let mut maybe_output_code = None; + let mut maybe_source_map = None; + + if !self.recompile { + let result = self.load_cache(out.filename.as_str(), &out.source_code); + match result { + Err(err) => { + if err.kind() == std::io::ErrorKind::NotFound { + return Ok(out); + } else { + return Err(err.into()); + } + } + Ok((output_code, source_map)) => { + maybe_output_code = Some(output_code); + maybe_source_map = Some(source_map); + } + } + } + + Ok(ModuleMetaData { + module_name: out.module_name, + filename: out.filename, + media_type: out.media_type, + source_code: out.source_code, + maybe_output_code_filename: output_code_filename + .to_str() + .map(String::from), + maybe_output_code, + maybe_source_map_filename: output_source_map_filename + .to_str() + .map(String::from), + maybe_source_map, + }) + } + + // Prototype: https://github.com/denoland/deno/blob/golang/os.go#L56-L68 + fn src_file_to_url(self: &Self, filename: &str) -> String { + let filename_path = Path::new(filename); + if filename_path.starts_with(&self.deps) { + let (rest, prefix) = if filename_path.starts_with(&self.deps_https) { + let rest = filename_path.strip_prefix(&self.deps_https).unwrap(); + let prefix = "https://".to_string(); + (rest, prefix) + } else if filename_path.starts_with(&self.deps_http) { + let rest = filename_path.strip_prefix(&self.deps_http).unwrap(); + let prefix = "http://".to_string(); + (rest, prefix) + } else { + // TODO(kevinkassimo): change this to support other protocols than http + unimplemented!() + }; + // Windows doesn't support ":" in filenames, so we represent port using a + // special string. + // TODO(ry) This current implementation will break on a URL that has + // the default port but contains "_PORT" in the path. + let rest = rest.to_str().unwrap().replacen("_PORT", ":", 1); + prefix + &rest + } else { + String::from(filename) + } + } + + /// Returns (module name, local filename) + pub fn resolve_module_url( + self: &Self, + specifier: &str, + referrer: &str, + ) -> Result { + let specifier = self.src_file_to_url(specifier); + let mut referrer = self.src_file_to_url(referrer); + + debug!( + "resolve_module specifier {} referrer {}", + specifier, referrer + ); + + if referrer.starts_with('.') { + let cwd = std::env::current_dir().unwrap(); + let referrer_path = cwd.join(referrer); + referrer = referrer_path.to_str().unwrap().to_string() + "/"; + } + + let j = if is_remote(&specifier) || Path::new(&specifier).is_absolute() { + parse_local_or_remote(&specifier)? + } else if referrer.ends_with('/') { + let r = Url::from_directory_path(&referrer); + // TODO(ry) Properly handle error. + if r.is_err() { + error!("Url::from_directory_path error {}", referrer); + } + let base = r.unwrap(); + base.join(specifier.as_ref())? + } else { + let base = parse_local_or_remote(&referrer)?; + base.join(specifier.as_ref())? + }; + Ok(j) + } + + /// Returns (module name, local filename) + pub fn resolve_module( + self: &Self, + specifier: &str, + referrer: &str, + ) -> Result<(String, String), url::ParseError> { + let j = self.resolve_module_url(specifier, referrer)?; + + let module_name = j.to_string(); + let filename; + match j.scheme() { + "file" => { + filename = deno_fs::normalize_path(j.to_file_path().unwrap().as_ref()); + } + "https" => { + filename = deno_fs::normalize_path( + get_cache_filename(self.deps_https.as_path(), &j).as_ref(), + ) + } + "http" => { + filename = deno_fs::normalize_path( + get_cache_filename(self.deps_http.as_path(), &j).as_ref(), + ) + } + // TODO(kevinkassimo): change this to support other protocols than http + _ => unimplemented!(), + } + + debug!("module_name: {}, filename: {}", module_name, filename); + Ok((module_name, filename)) + } +} + +impl SourceMapGetter for DenoDir { + fn get_source_map(&self, script_name: &str) -> Option> { + match self.fetch_module_meta_data(script_name, ".") { + Err(_e) => None, + Ok(out) => match out.maybe_source_map { + None => None, + Some(source_map) => Some(source_map), + }, + } + } +} + +fn get_cache_filename(basedir: &Path, url: &Url) -> PathBuf { + let host = url.host_str().unwrap(); + let host_port = match url.port() { + // Windows doesn't support ":" in filenames, so we represent port using a + // special string. + Some(port) => format!("{}_PORT{}", host, port), + None => host.to_string(), + }; + + let mut out = basedir.to_path_buf(); + out.push(host_port); + for path_seg in url.path_segments().unwrap() { + out.push(path_seg); + } + out +} + +fn source_code_hash( + filename: &str, + source_code: &[u8], + version: &str, +) -> String { + let mut ctx = ring::digest::Context::new(&ring::digest::SHA1); + ctx.update(version.as_bytes()); + ctx.update(filename.as_bytes()); + ctx.update(source_code); + let digest = ctx.finish(); + let mut out = String::new(); + // TODO There must be a better way to do this... + for byte in digest.as_ref() { + write!(&mut out, "{:02x}", byte).unwrap(); + } + out +} + +fn is_remote(module_name: &str) -> bool { + module_name.starts_with("http://") || module_name.starts_with("https://") +} + +fn parse_local_or_remote(p: &str) -> Result { + if is_remote(p) || p.starts_with("file:") { + Url::parse(p) + } else { + Url::from_file_path(p).map_err(|_err| url::ParseError::IdnaError) + } +} + +fn map_file_extension(path: &Path) -> msg::MediaType { + match path.extension() { + None => msg::MediaType::Unknown, + Some(os_str) => match os_str.to_str() { + Some("ts") => msg::MediaType::TypeScript, + Some("js") => msg::MediaType::JavaScript, + Some("json") => msg::MediaType::Json, + _ => msg::MediaType::Unknown, + }, + } +} + +// convert a ContentType string into a enumerated MediaType +fn map_content_type(path: &Path, content_type: Option<&str>) -> msg::MediaType { + match content_type { + Some(content_type) => { + // sometimes there is additional data after the media type in + // Content-Type so we have to do a bit of manipulation so we are only + // dealing with the actual media type + let ct_vector: Vec<&str> = content_type.split(';').collect(); + let ct: &str = ct_vector.first().unwrap(); + match ct.to_lowercase().as_ref() { + "application/typescript" + | "text/typescript" + | "video/vnd.dlna.mpeg-tts" + | "video/mp2t" + | "application/x-typescript" => msg::MediaType::TypeScript, + "application/javascript" + | "text/javascript" + | "application/ecmascript" + | "text/ecmascript" + | "application/x-javascript" => msg::MediaType::JavaScript, + "application/json" | "text/json" => msg::MediaType::Json, + "text/plain" => map_file_extension(path), + _ => { + debug!("unknown content type: {}", content_type); + msg::MediaType::Unknown + } + } + } + None => map_file_extension(path), + } +} + +fn filter_shebang(bytes: Vec) -> Vec { + let string = str::from_utf8(&bytes).unwrap(); + if let Some(i) = string.find('\n') { + let (_, rest) = string.split_at(i); + rest.as_bytes().to_owned() + } else { + Vec::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tokio_util; + use tempfile::TempDir; + + fn test_setup(reload: bool, recompile: bool) -> (TempDir, DenoDir) { + let temp_dir = TempDir::new().expect("tempdir fail"); + let deno_dir = + DenoDir::new(reload, recompile, Some(temp_dir.path().to_path_buf())) + .expect("setup fail"); + (temp_dir, deno_dir) + } + + // The `add_root` macro prepends "C:" to a string if on windows; on posix + // systems it returns the input string untouched. This is necessary because + // `Url::from_file_path()` fails if the input path isn't an absolute path. + macro_rules! add_root { + ($path:expr) => { + if cfg!(target_os = "windows") { + concat!("C:", $path) + } else { + $path + } + }; + } + + macro_rules! file_url { + ($path:expr) => { + if cfg!(target_os = "windows") { + concat!("file:///C:", $path) + } else { + concat!("file://", $path) + } + }; + } + + #[test] + fn test_get_cache_filename() { + let url = Url::parse("http://example.com:1234/path/to/file.ts").unwrap(); + let basedir = Path::new("/cache/dir/"); + let cache_file = get_cache_filename(&basedir, &url); + assert_eq!( + cache_file, + Path::new("/cache/dir/example.com_PORT1234/path/to/file.ts") + ); + } + + #[test] + fn test_cache_path() { + let (temp_dir, deno_dir) = test_setup(false, false); + let filename = "hello.js"; + let source_code = "1+2".as_bytes(); + let hash = source_code_hash(filename, source_code, version::DENO); + assert_eq!( + ( + temp_dir.path().join(format!("gen/{}.js", hash)), + temp_dir.path().join(format!("gen/{}.js.map", hash)) + ), + deno_dir.cache_path(filename, source_code) + ); + } + + #[test] + fn test_code_cache() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let filename = "hello.js"; + let source_code = "1+2".as_bytes(); + let output_code = "1+2 // output code".as_bytes(); + let source_map = "{}".as_bytes(); + let hash = source_code_hash(filename, source_code, version::DENO); + let (cache_path, source_map_path) = + deno_dir.cache_path(filename, source_code); + assert!(cache_path.ends_with(format!("gen/{}.js", hash))); + assert!(source_map_path.ends_with(format!("gen/{}.js.map", hash))); + + let out = ModuleMetaData { + filename: filename.to_owned(), + source_code: source_code.to_owned(), + module_name: "hello.js".to_owned(), + media_type: msg::MediaType::TypeScript, + maybe_output_code: Some(output_code.to_owned()), + maybe_output_code_filename: None, + maybe_source_map: Some(source_map.to_owned()), + maybe_source_map_filename: None, + }; + + let r = deno_dir.code_cache(&out); + r.expect("code_cache error"); + assert!(cache_path.exists()); + assert_eq!(output_code.to_owned(), fs::read(&cache_path).unwrap()); + } + + #[test] + fn test_source_code_hash() { + assert_eq!( + "7e44de2ed9e0065da09d835b76b8d70be503d276", + source_code_hash("hello.ts", "1+2".as_bytes(), "0.2.11") + ); + // Different source_code should result in different hash. + assert_eq!( + "57033366cf9db1ef93deca258cdbcd9ef5f4bde1", + source_code_hash("hello.ts", "1".as_bytes(), "0.2.11") + ); + // Different filename should result in different hash. + assert_eq!( + "19657f90b5b0540f87679e2fb362e7bd62b644b0", + source_code_hash("hi.ts", "1+2".as_bytes(), "0.2.11") + ); + // Different version should result in different hash. + assert_eq!( + "e2b4b7162975a02bf2770f16836eb21d5bcb8be1", + source_code_hash("hi.ts", "1+2".as_bytes(), "0.2.0") + ); + } + + #[test] + fn test_get_source_code_1() { + let (temp_dir, deno_dir) = test_setup(false, false); + // http_util::fetch_sync_string requires tokio + tokio_util::init(|| { + let module_name = "http://localhost:4545/tests/subdir/mod2.ts"; + let filename = deno_fs::normalize_path( + deno_dir + .deps_http + .join("localhost_PORT4545/tests/subdir/mod2.ts") + .as_ref(), + ); + let mime_file_name = format!("{}.mime", &filename); + + let result = deno_dir.get_source_code(module_name, &filename); + println!("module_name {} filename {}", module_name, filename); + assert!(result.is_ok()); + let r = result.unwrap(); + assert_eq!( + r.source_code, + "export { printHello } from \"./print_hello.ts\";\n".as_bytes() + ); + assert_eq!(&(r.media_type), &msg::MediaType::TypeScript); + // Should not create .mime file due to matching ext + assert!(fs::read_to_string(&mime_file_name).is_err()); + + // Modify .mime + let _ = fs::write(&mime_file_name, "text/javascript"); + let result2 = deno_dir.get_source_code(module_name, &filename); + assert!(result2.is_ok()); + let r2 = result2.unwrap(); + assert_eq!( + r2.source_code, + "export { printHello } from \"./print_hello.ts\";\n".as_bytes() + ); + // If get_source_code does not call remote, this should be JavaScript + // as we modified before! (we do not overwrite .mime due to no http fetch) + assert_eq!(&(r2.media_type), &msg::MediaType::JavaScript); + assert_eq!( + fs::read_to_string(&mime_file_name).unwrap(), + "text/javascript" + ); + + // Force self.reload + let deno_dir = + DenoDir::new(true, false, Some(temp_dir.path().to_path_buf())) + .expect("setup fail"); + let result3 = deno_dir.get_source_code(module_name, &filename); + assert!(result3.is_ok()); + let r3 = result3.unwrap(); + let expected3 = + "export { printHello } from \"./print_hello.ts\";\n".as_bytes(); + assert_eq!(r3.source_code, expected3); + // Now the old .mime file should have gone! Resolved back to TypeScript + assert_eq!(&(r3.media_type), &msg::MediaType::TypeScript); + assert!(fs::read_to_string(&mime_file_name).is_err()); + }); + } + + #[test] + fn test_get_source_code_2() { + let (temp_dir, deno_dir) = test_setup(false, false); + // http_util::fetch_sync_string requires tokio + tokio_util::init(|| { + let module_name = "http://localhost:4545/tests/subdir/mismatch_ext.ts"; + let filename = deno_fs::normalize_path( + deno_dir + .deps_http + .join("localhost_PORT4545/tests/subdir/mismatch_ext.ts") + .as_ref(), + ); + let mime_file_name = format!("{}.mime", &filename); + + let result = deno_dir.get_source_code(module_name, &filename); + println!("module_name {} filename {}", module_name, filename); + assert!(result.is_ok()); + let r = result.unwrap(); + let expected = "export const loaded = true;\n".as_bytes(); + assert_eq!(r.source_code, expected); + // Mismatch ext with content type, create .mime + assert_eq!(&(r.media_type), &msg::MediaType::JavaScript); + assert_eq!( + fs::read_to_string(&mime_file_name).unwrap(), + "text/javascript" + ); + + // Modify .mime + let _ = fs::write(&mime_file_name, "text/typescript"); + let result2 = deno_dir.get_source_code(module_name, &filename); + assert!(result2.is_ok()); + let r2 = result2.unwrap(); + let expected2 = "export const loaded = true;\n".as_bytes(); + assert_eq!(r2.source_code, expected2); + // If get_source_code does not call remote, this should be TypeScript + // as we modified before! (we do not overwrite .mime due to no http fetch) + assert_eq!(&(r2.media_type), &msg::MediaType::TypeScript); + assert_eq!( + fs::read_to_string(&mime_file_name).unwrap(), + "text/typescript" + ); + + // Force self.reload + let deno_dir = + DenoDir::new(true, false, Some(temp_dir.path().to_path_buf())) + .expect("setup fail"); + let result3 = deno_dir.get_source_code(module_name, &filename); + assert!(result3.is_ok()); + let r3 = result3.unwrap(); + let expected3 = "export const loaded = true;\n".as_bytes(); + assert_eq!(r3.source_code, expected3); + // Now the old .mime file should be overwritten back to JavaScript! + // (due to http fetch) + assert_eq!(&(r3.media_type), &msg::MediaType::JavaScript); + assert_eq!( + fs::read_to_string(&mime_file_name).unwrap(), + "text/javascript" + ); + }); + } + + #[test] + fn test_fetch_source_1() { + use crate::tokio_util; + // http_util::fetch_sync_string requires tokio + tokio_util::init(|| { + let (_temp_dir, deno_dir) = test_setup(false, false); + let module_name = + "http://localhost:4545/tests/subdir/mt_video_mp2t.t3.ts"; + let filename = deno_fs::normalize_path( + deno_dir + .deps_http + .join("localhost_PORT4545/tests/subdir/mt_video_mp2t.t3.ts") + .as_ref(), + ); + let mime_file_name = format!("{}.mime", &filename); + + let result = deno_dir.fetch_remote_source(module_name, &filename); + assert!(result.is_ok()); + let r = result.unwrap().unwrap(); + assert_eq!(r.source_code, "export const loaded = true;\n".as_bytes()); + assert_eq!(&(r.media_type), &msg::MediaType::TypeScript); + // matching ext, no .mime file created + assert!(fs::read_to_string(&mime_file_name).is_err()); + + // Modify .mime, make sure read from local + let _ = fs::write(&mime_file_name, "text/javascript"); + let result2 = deno_dir.fetch_local_source(module_name, &filename); + assert!(result2.is_ok()); + let r2 = result2.unwrap().unwrap(); + assert_eq!(r2.source_code, "export const loaded = true;\n".as_bytes()); + // Not MediaType::TypeScript due to .mime modification + assert_eq!(&(r2.media_type), &msg::MediaType::JavaScript); + }); + } + + #[test] + fn test_fetch_source_2() { + use crate::tokio_util; + // http_util::fetch_sync_string requires tokio + tokio_util::init(|| { + let (_temp_dir, deno_dir) = test_setup(false, false); + let module_name = "http://localhost:4545/tests/subdir/no_ext"; + let filename = deno_fs::normalize_path( + deno_dir + .deps_http + .join("localhost_PORT4545/tests/subdir/no_ext") + .as_ref(), + ); + let mime_file_name = format!("{}.mime", &filename); + let result = deno_dir.fetch_remote_source(module_name, &filename); + assert!(result.is_ok()); + let r = result.unwrap().unwrap(); + assert_eq!(r.source_code, "export const loaded = true;\n".as_bytes()); + assert_eq!(&(r.media_type), &msg::MediaType::TypeScript); + // no ext, should create .mime file + assert_eq!( + fs::read_to_string(&mime_file_name).unwrap(), + "text/typescript" + ); + + let module_name_2 = "http://localhost:4545/tests/subdir/mismatch_ext.ts"; + let filename_2 = deno_fs::normalize_path( + deno_dir + .deps_http + .join("localhost_PORT4545/tests/subdir/mismatch_ext.ts") + .as_ref(), + ); + let mime_file_name_2 = format!("{}.mime", &filename_2); + let result_2 = deno_dir.fetch_remote_source(module_name_2, &filename_2); + assert!(result_2.is_ok()); + let r2 = result_2.unwrap().unwrap(); + assert_eq!(r2.source_code, "export const loaded = true;\n".as_bytes()); + assert_eq!(&(r2.media_type), &msg::MediaType::JavaScript); + // mismatch ext, should create .mime file + assert_eq!( + fs::read_to_string(&mime_file_name_2).unwrap(), + "text/javascript" + ); + + // test unknown extension + let module_name_3 = "http://localhost:4545/tests/subdir/unknown_ext.deno"; + let filename_3 = deno_fs::normalize_path( + deno_dir + .deps_http + .join("localhost_PORT4545/tests/subdir/unknown_ext.deno") + .as_ref(), + ); + let mime_file_name_3 = format!("{}.mime", &filename_3); + let result_3 = deno_dir.fetch_remote_source(module_name_3, &filename_3); + assert!(result_3.is_ok()); + let r3 = result_3.unwrap().unwrap(); + assert_eq!(r3.source_code, "export const loaded = true;\n".as_bytes()); + assert_eq!(&(r3.media_type), &msg::MediaType::TypeScript); + // unknown ext, should create .mime file + assert_eq!( + fs::read_to_string(&mime_file_name_3).unwrap(), + "text/typescript" + ); + }); + } + + #[test] + fn test_fetch_source_3() { + // only local, no http_util::fetch_sync_string called + let (_temp_dir, deno_dir) = test_setup(false, false); + let cwd = std::env::current_dir().unwrap(); + let cwd_string = cwd.to_str().unwrap(); + let module_name = "http://example.com/mt_text_typescript.t1.ts"; // not used + let filename = + format!("{}/tests/subdir/mt_text_typescript.t1.ts", &cwd_string); + + let result = deno_dir.fetch_local_source(module_name, &filename); + assert!(result.is_ok()); + let r = result.unwrap().unwrap(); + assert_eq!(r.source_code, "export const loaded = true;\n".as_bytes()); + assert_eq!(&(r.media_type), &msg::MediaType::TypeScript); + } + + #[test] + fn test_fetch_module_meta_data() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let cwd = std::env::current_dir().unwrap(); + let cwd_string = String::from(cwd.to_str().unwrap()) + "/"; + + // Test failure case. + let specifier = "hello.ts"; + let referrer = add_root!("/baddir/badfile.ts"); + let r = deno_dir.fetch_module_meta_data(specifier, referrer); + assert!(r.is_err()); + + // Assuming cwd is the deno repo root. + let specifier = "./js/main.ts"; + let referrer = cwd_string.as_str(); + let r = deno_dir.fetch_module_meta_data(specifier, referrer); + assert!(r.is_ok()); + //let fetch_module_meta_data_output = r.unwrap(); + //println!("fetch_module_meta_data_output {:?}", fetch_module_meta_data_output); + } + + #[test] + fn test_fetch_module_meta_data_1() { + /*recompile ts file*/ + let (_temp_dir, deno_dir) = test_setup(false, true); + + let cwd = std::env::current_dir().unwrap(); + let cwd_string = String::from(cwd.to_str().unwrap()) + "/"; + + // Test failure case. + let specifier = "hello.ts"; + let referrer = add_root!("/baddir/badfile.ts"); + let r = deno_dir.fetch_module_meta_data(specifier, referrer); + assert!(r.is_err()); + + // Assuming cwd is the deno repo root. + let specifier = "./js/main.ts"; + let referrer = cwd_string.as_str(); + let r = deno_dir.fetch_module_meta_data(specifier, referrer); + assert!(r.is_ok()); + } + + #[test] + fn test_src_file_to_url_1() { + let (_temp_dir, deno_dir) = test_setup(false, false); + assert_eq!("hello", deno_dir.src_file_to_url("hello")); + assert_eq!("/hello", deno_dir.src_file_to_url("/hello")); + let x = deno_dir.deps_http.join("hello/world.txt"); + assert_eq!( + "http://hello/world.txt", + deno_dir.src_file_to_url(x.to_str().unwrap()) + ); + } + + #[test] + fn test_src_file_to_url_2() { + let (_temp_dir, deno_dir) = test_setup(false, false); + assert_eq!("hello", deno_dir.src_file_to_url("hello")); + assert_eq!("/hello", deno_dir.src_file_to_url("/hello")); + let x = deno_dir.deps_https.join("hello/world.txt"); + assert_eq!( + "https://hello/world.txt", + deno_dir.src_file_to_url(x.to_str().unwrap()) + ); + } + + #[test] + fn test_src_file_to_url_3() { + let (_temp_dir, deno_dir) = test_setup(false, false); + let x = deno_dir.deps_http.join("localhost_PORT4545/world.txt"); + assert_eq!( + "http://localhost:4545/world.txt", + deno_dir.src_file_to_url(x.to_str().unwrap()) + ); + } + + #[test] + fn test_src_file_to_url_4() { + let (_temp_dir, deno_dir) = test_setup(false, false); + let x = deno_dir.deps_https.join("localhost_PORT4545/world.txt"); + assert_eq!( + "https://localhost:4545/world.txt", + deno_dir.src_file_to_url(x.to_str().unwrap()) + ); + } + + // https://github.com/denoland/deno/blob/golang/os_test.go#L16-L87 + #[test] + fn test_resolve_module_1() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let test_cases = [ + ( + "./subdir/print_hello.ts", + add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/006_url_imports.ts"), + file_url!("/Users/rld/go/src/github.com/denoland/deno/testdata/subdir/print_hello.ts"), + add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/subdir/print_hello.ts"), + ), + ( + "testdata/001_hello.js", + add_root!("/Users/rld/go/src/github.com/denoland/deno/"), + file_url!("/Users/rld/go/src/github.com/denoland/deno/testdata/001_hello.js"), + add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/001_hello.js"), + ), + ( + add_root!("/Users/rld/src/deno/hello.js"), + ".", + file_url!("/Users/rld/src/deno/hello.js"), + add_root!("/Users/rld/src/deno/hello.js"), + ), + ( + add_root!("/this/module/got/imported.js"), + add_root!("/that/module/did/it.js"), + file_url!("/this/module/got/imported.js"), + add_root!("/this/module/got/imported.js"), + ), + ]; + for &test in test_cases.iter() { + let specifier = String::from(test.0); + let referrer = String::from(test.1); + let (module_name, filename) = + deno_dir.resolve_module(&specifier, &referrer).unwrap(); + assert_eq!(module_name, test.2); + assert_eq!(filename, test.3); + } + } + + #[test] + fn test_resolve_module_2() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let specifier = "http://localhost:4545/testdata/subdir/print_hello.ts"; + let referrer = add_root!("/deno/testdata/006_url_imports.ts"); + + let expected_module_name = + "http://localhost:4545/testdata/subdir/print_hello.ts"; + let expected_filename = deno_fs::normalize_path( + deno_dir + .deps_http + .join("localhost_PORT4545/testdata/subdir/print_hello.ts") + .as_ref(), + ); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, referrer).unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + } + + #[test] + fn test_resolve_module_3() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let specifier_ = + deno_dir.deps_http.join("unpkg.com/liltest@0.0.5/index.ts"); + let specifier = specifier_.to_str().unwrap(); + let referrer = "."; + + let expected_module_name = "http://unpkg.com/liltest@0.0.5/index.ts"; + let expected_filename = deno_fs::normalize_path( + deno_dir + .deps_http + .join("unpkg.com/liltest@0.0.5/index.ts") + .as_ref(), + ); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, referrer).unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + } + + #[test] + fn test_resolve_module_4() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let specifier = "./util"; + let referrer_ = deno_dir.deps_http.join("unpkg.com/liltest@0.0.5/index.ts"); + let referrer = referrer_.to_str().unwrap(); + + // http containing files -> load relative import with http + let expected_module_name = "http://unpkg.com/liltest@0.0.5/util"; + let expected_filename = deno_fs::normalize_path( + deno_dir + .deps_http + .join("unpkg.com/liltest@0.0.5/util") + .as_ref(), + ); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, referrer).unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + } + + #[test] + fn test_resolve_module_5() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let specifier = "./util"; + let referrer_ = + deno_dir.deps_https.join("unpkg.com/liltest@0.0.5/index.ts"); + let referrer = referrer_.to_str().unwrap(); + + // https containing files -> load relative import with https + let expected_module_name = "https://unpkg.com/liltest@0.0.5/util"; + let expected_filename = deno_fs::normalize_path( + deno_dir + .deps_https + .join("unpkg.com/liltest@0.0.5/util") + .as_ref(), + ); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, referrer).unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + } + + #[test] + fn test_resolve_module_6() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let specifier = "http://localhost:4545/tests/subdir/mod2.ts"; + let referrer = add_root!("/deno/tests/006_url_imports.ts"); + let expected_module_name = "http://localhost:4545/tests/subdir/mod2.ts"; + let expected_filename = deno_fs::normalize_path( + deno_dir + .deps_http + .join("localhost_PORT4545/tests/subdir/mod2.ts") + .as_ref(), + ); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, referrer).unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + } + + #[test] + fn test_resolve_module_7() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let specifier = "http_test.ts"; + let referrer = add_root!("/Users/rld/src/deno_net/"); + let expected_module_name = + file_url!("/Users/rld/src/deno_net/http_test.ts"); + let expected_filename = add_root!("/Users/rld/src/deno_net/http_test.ts"); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, referrer).unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + } + + #[test] + fn test_resolve_module_referrer_dot() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let specifier = "tests/001_hello.js"; + + let cwd = std::env::current_dir().unwrap(); + let expected_path = cwd.join(specifier); + let expected_module_name = + Url::from_file_path(&expected_path).unwrap().to_string(); + let expected_filename = deno_fs::normalize_path(&expected_path); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, ".").unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, "./").unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + } + + #[test] + fn test_resolve_module_referrer_dotdot() { + let (_temp_dir, deno_dir) = test_setup(false, false); + + let specifier = "tests/001_hello.js"; + + let cwd = std::env::current_dir().unwrap(); + let expected_path = cwd.join("..").join(specifier); + let expected_module_name = + Url::from_file_path(&expected_path).unwrap().to_string(); + let expected_filename = deno_fs::normalize_path(&expected_path); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, "..").unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + + let (module_name, filename) = + deno_dir.resolve_module(specifier, "../").unwrap(); + assert_eq!(module_name, expected_module_name); + assert_eq!(filename, expected_filename); + } + + #[test] + fn test_map_file_extension() { + assert_eq!( + map_file_extension(Path::new("foo/bar.ts")), + msg::MediaType::TypeScript + ); + assert_eq!( + map_file_extension(Path::new("foo/bar.d.ts")), + msg::MediaType::TypeScript + ); + assert_eq!( + map_file_extension(Path::new("foo/bar.js")), + msg::MediaType::JavaScript + ); + assert_eq!( + map_file_extension(Path::new("foo/bar.json")), + msg::MediaType::Json + ); + assert_eq!( + map_file_extension(Path::new("foo/bar.txt")), + msg::MediaType::Unknown + ); + assert_eq!( + map_file_extension(Path::new("foo/bar")), + msg::MediaType::Unknown + ); + } + + #[test] + fn test_map_content_type() { + // Extension only + assert_eq!( + map_content_type(Path::new("foo/bar.ts"), None), + msg::MediaType::TypeScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar.d.ts"), None), + msg::MediaType::TypeScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar.js"), None), + msg::MediaType::JavaScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar.json"), None), + msg::MediaType::Json + ); + assert_eq!( + map_content_type(Path::new("foo/bar.txt"), None), + msg::MediaType::Unknown + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), None), + msg::MediaType::Unknown + ); + + // Media Type + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("application/typescript")), + msg::MediaType::TypeScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("text/typescript")), + msg::MediaType::TypeScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("video/vnd.dlna.mpeg-tts")), + msg::MediaType::TypeScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("video/mp2t")), + msg::MediaType::TypeScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("application/x-typescript")), + msg::MediaType::TypeScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("application/javascript")), + msg::MediaType::JavaScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("text/javascript")), + msg::MediaType::JavaScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("application/ecmascript")), + msg::MediaType::JavaScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("text/ecmascript")), + msg::MediaType::JavaScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("application/x-javascript")), + msg::MediaType::JavaScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("application/json")), + msg::MediaType::Json + ); + assert_eq!( + map_content_type(Path::new("foo/bar"), Some("text/json")), + msg::MediaType::Json + ); + assert_eq!( + map_content_type(Path::new("foo/bar.ts"), Some("text/plain")), + msg::MediaType::TypeScript + ); + assert_eq!( + map_content_type(Path::new("foo/bar.ts"), Some("foo/bar")), + msg::MediaType::Unknown + ); + } + + #[test] + fn test_filter_shebang() { + assert_eq!(filter_shebang("#!".as_bytes().to_owned()), "".as_bytes()); + assert_eq!( + filter_shebang("#!\n\n".as_bytes().to_owned()), + "\n\n".as_bytes() + ); + let code = "#!/usr/bin/env deno\nconsole.log('hello');\n" + .as_bytes() + .to_owned(); + assert_eq!(filter_shebang(code), "\nconsole.log('hello');\n".as_bytes()); + } +} diff --git a/cli/errors.rs b/cli/errors.rs new file mode 100644 index 000000000..65118d070 --- /dev/null +++ b/cli/errors.rs @@ -0,0 +1,207 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::js_errors::JSErrorColor; +pub use crate::msg::ErrorKind; +use crate::resolve_addr::ResolveAddrError; +use deno_core::JSError; +use hyper; +use std; +use std::fmt; +use std::io; +use url; + +pub type DenoResult = std::result::Result; + +#[derive(Debug)] +pub struct DenoError { + repr: Repr, +} + +#[derive(Debug)] +enum Repr { + Simple(ErrorKind, String), + IoErr(io::Error), + UrlErr(url::ParseError), + HyperErr(hyper::Error), +} + +pub fn new(kind: ErrorKind, msg: String) -> DenoError { + DenoError { + repr: Repr::Simple(kind, msg), + } +} + +impl DenoError { + pub fn kind(&self) -> ErrorKind { + match self.repr { + Repr::Simple(kind, ref _msg) => kind, + // Repr::Simple(kind) => kind, + Repr::IoErr(ref err) => { + use std::io::ErrorKind::*; + match err.kind() { + NotFound => ErrorKind::NotFound, + PermissionDenied => ErrorKind::PermissionDenied, + ConnectionRefused => ErrorKind::ConnectionRefused, + ConnectionReset => ErrorKind::ConnectionReset, + ConnectionAborted => ErrorKind::ConnectionAborted, + NotConnected => ErrorKind::NotConnected, + AddrInUse => ErrorKind::AddrInUse, + AddrNotAvailable => ErrorKind::AddrNotAvailable, + BrokenPipe => ErrorKind::BrokenPipe, + AlreadyExists => ErrorKind::AlreadyExists, + WouldBlock => ErrorKind::WouldBlock, + InvalidInput => ErrorKind::InvalidInput, + InvalidData => ErrorKind::InvalidData, + TimedOut => ErrorKind::TimedOut, + Interrupted => ErrorKind::Interrupted, + WriteZero => ErrorKind::WriteZero, + Other => ErrorKind::Other, + UnexpectedEof => ErrorKind::UnexpectedEof, + _ => unreachable!(), + } + } + Repr::UrlErr(ref err) => { + use url::ParseError::*; + match err { + EmptyHost => ErrorKind::EmptyHost, + IdnaError => ErrorKind::IdnaError, + InvalidPort => ErrorKind::InvalidPort, + InvalidIpv4Address => ErrorKind::InvalidIpv4Address, + InvalidIpv6Address => ErrorKind::InvalidIpv6Address, + InvalidDomainCharacter => ErrorKind::InvalidDomainCharacter, + RelativeUrlWithoutBase => ErrorKind::RelativeUrlWithoutBase, + RelativeUrlWithCannotBeABaseBase => { + ErrorKind::RelativeUrlWithCannotBeABaseBase + } + SetHostOnCannotBeABaseUrl => ErrorKind::SetHostOnCannotBeABaseUrl, + Overflow => ErrorKind::Overflow, + } + } + Repr::HyperErr(ref err) => { + // For some reason hyper::errors::Kind is private. + if err.is_parse() { + ErrorKind::HttpParse + } else if err.is_user() { + ErrorKind::HttpUser + } else if err.is_canceled() { + ErrorKind::HttpCanceled + } else if err.is_closed() { + ErrorKind::HttpClosed + } else { + ErrorKind::HttpOther + } + } + } + } +} + +impl fmt::Display for DenoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.repr { + Repr::Simple(_kind, ref err_str) => f.pad(err_str), + Repr::IoErr(ref err) => err.fmt(f), + Repr::UrlErr(ref err) => err.fmt(f), + Repr::HyperErr(ref err) => err.fmt(f), + } + } +} + +impl std::error::Error for DenoError { + fn description(&self) -> &str { + match self.repr { + Repr::Simple(_kind, ref msg) => msg.as_str(), + Repr::IoErr(ref err) => err.description(), + Repr::UrlErr(ref err) => err.description(), + Repr::HyperErr(ref err) => err.description(), + } + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + match self.repr { + Repr::Simple(_kind, ref _msg) => None, + Repr::IoErr(ref err) => Some(err), + Repr::UrlErr(ref err) => Some(err), + Repr::HyperErr(ref err) => Some(err), + } + } +} + +impl From for DenoError { + #[inline] + fn from(err: io::Error) -> Self { + Self { + repr: Repr::IoErr(err), + } + } +} + +impl From for DenoError { + #[inline] + fn from(err: url::ParseError) -> Self { + Self { + repr: Repr::UrlErr(err), + } + } +} + +impl From for DenoError { + #[inline] + fn from(err: hyper::Error) -> Self { + Self { + repr: Repr::HyperErr(err), + } + } +} + +impl From for DenoError { + fn from(e: ResolveAddrError) -> Self { + match e { + ResolveAddrError::Syntax => Self { + repr: Repr::Simple( + ErrorKind::InvalidInput, + "invalid address syntax".to_string(), + ), + }, + ResolveAddrError::Resolution(io_err) => Self { + repr: Repr::IoErr(io_err), + }, + } + } +} + +pub fn bad_resource() -> DenoError { + new(ErrorKind::BadResource, String::from("bad resource id")) +} + +pub fn permission_denied() -> DenoError { + new( + ErrorKind::PermissionDenied, + String::from("permission denied"), + ) +} + +#[derive(Debug)] +pub enum RustOrJsError { + Rust(DenoError), + Js(JSError), +} + +impl From for RustOrJsError { + fn from(e: DenoError) -> Self { + RustOrJsError::Rust(e) + } +} + +impl From for RustOrJsError { + fn from(e: JSError) -> Self { + RustOrJsError::Js(e) + } +} + +impl fmt::Display for RustOrJsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RustOrJsError::Rust(e) => e.fmt(f), + RustOrJsError::Js(e) => JSErrorColor(e).fmt(f), + } + } +} diff --git a/cli/flags.rs b/cli/flags.rs new file mode 100644 index 000000000..d6a63a9fb --- /dev/null +++ b/cli/flags.rs @@ -0,0 +1,291 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use deno_core::v8_set_flags; +use getopts; +use getopts::Options; + +// Creates vector of strings, Vec +#[cfg(test)] +macro_rules! svec { + ($($x:expr),*) => (vec![$($x.to_string()),*]); +} + +#[cfg_attr(feature = "cargo-clippy", allow(stutter))] +#[derive(Clone, Debug, PartialEq, Default)] +pub struct DenoFlags { + pub help: bool, + pub log_debug: bool, + pub version: bool, + pub reload: bool, + pub recompile: bool, + pub allow_read: bool, + pub allow_write: bool, + pub allow_net: bool, + pub allow_env: bool, + pub allow_run: bool, + pub no_prompts: bool, + pub types: bool, + pub prefetch: bool, + pub info: bool, + pub fmt: bool, +} + +pub fn get_usage(opts: &Options) -> String { + format!( + "Usage: deno script.ts {} +Environment variables: + DENO_DIR Set deno's base directory + NO_COLOR Set to disable color", + opts.usage("") + ) +} + +/// Checks provided arguments for known options and sets appropriate Deno flags +/// for them. Unknown options are returned for further use. +/// Note: +/// +/// 1. This assumes that privileged flags do not accept parameters deno --foo bar. +/// This assumption is currently valid. But if it were to change in the future, +/// this parsing technique would need to be modified. I think we want to keep the +/// privileged flags minimal - so having this restriction is maybe a good thing. +/// +/// 2. Misspelled flags will be forwarded to user code - e.g. --allow-ne would +/// not cause an error. I also think this is ok because missing any of the +/// privileged flags is not destructive. Userland flag parsing would catch these +/// errors. +fn set_recognized_flags( + opts: &Options, + flags: &mut DenoFlags, + args: Vec, +) -> Result, getopts::Fail> { + let mut rest = Vec::::new(); + // getopts doesn't allow parsing unknown options so we check them + // one-by-one and handle unrecognized ones manually + // better solution welcome! + for arg in args { + let fake_args = vec![arg]; + match opts.parse(&fake_args) { + Err(getopts::Fail::UnrecognizedOption(_)) => { + rest.extend(fake_args); + } + Err(e) => { + return Err(e); + } + Ok(matches) => { + if matches.opt_present("help") { + flags.help = true; + } + if matches.opt_present("log-debug") { + flags.log_debug = true; + } + if matches.opt_present("version") { + flags.version = true; + } + if matches.opt_present("reload") { + flags.reload = true; + } + if matches.opt_present("recompile") { + flags.recompile = true; + } + if matches.opt_present("allow-read") { + flags.allow_read = true; + } + if matches.opt_present("allow-write") { + flags.allow_write = true; + } + if matches.opt_present("allow-net") { + flags.allow_net = true; + } + if matches.opt_present("allow-env") { + flags.allow_env = true; + } + if matches.opt_present("allow-run") { + flags.allow_run = true; + } + if matches.opt_present("allow-all") { + flags.allow_read = true; + flags.allow_env = true; + flags.allow_net = true; + flags.allow_run = true; + flags.allow_read = true; + flags.allow_write = true; + } + if matches.opt_present("no-prompt") { + flags.no_prompts = true; + } + if matches.opt_present("types") { + flags.types = true; + } + if matches.opt_present("prefetch") { + flags.prefetch = true; + } + if matches.opt_present("info") { + flags.info = true; + } + if matches.opt_present("fmt") { + flags.fmt = true; + } + + if !matches.free.is_empty() { + rest.extend(matches.free); + } + } + } + } + Ok(rest) +} + +#[cfg_attr(feature = "cargo-clippy", allow(stutter))] +pub fn set_flags( + args: Vec, +) -> Result<(DenoFlags, Vec, String), String> { + // TODO: all flags passed after "--" are swallowed by v8_set_flags + // eg. deno --allow-net ./test.ts -- --title foobar + // args === ["deno", "--allow-net" "./test.ts"] + let args = v8_set_flags(args); + + let mut opts = Options::new(); + // TODO(kevinkassimo): v8_set_flags intercepts '-help' with single '-' + // Resolve that and then uncomment line below (enabling Go style -long-flag) + // opts.long_only(true); + opts.optflag("", "allow-read", "Allow file system read access"); + opts.optflag("", "allow-write", "Allow file system write access"); + opts.optflag("", "allow-net", "Allow network access"); + opts.optflag("", "allow-env", "Allow environment access"); + opts.optflag("", "allow-run", "Allow running subprocesses"); + opts.optflag("A", "allow-all", "Allow all permissions"); + opts.optflag("", "no-prompt", "Do not use prompts"); + opts.optflag("", "recompile", "Force recompilation of TypeScript code"); + opts.optflag("h", "help", "Print this message"); + opts.optflag("D", "log-debug", "Log debug output"); + opts.optflag("v", "version", "Print the version"); + opts.optflag("r", "reload", "Reload cached remote resources"); + opts.optflag("", "v8-options", "Print V8 command line options"); + opts.optflag("", "types", "Print runtime TypeScript declarations"); + opts.optflag("", "prefetch", "Prefetch the dependencies"); + opts.optflag("", "info", "Show source file related info"); + opts.optflag("", "fmt", "Format code"); + + let mut flags = DenoFlags::default(); + + let rest = + set_recognized_flags(&opts, &mut flags, args).map_err(|e| e.to_string())?; + Ok((flags, rest, get_usage(&opts))) +} + +#[test] +fn test_set_flags_1() { + let (flags, rest, _) = set_flags(svec!["deno", "--version"]).unwrap(); + assert_eq!(rest, svec!["deno"]); + assert_eq!( + flags, + DenoFlags { + version: true, + ..DenoFlags::default() + } + ); +} + +#[test] +fn test_set_flags_2() { + let (flags, rest, _) = + set_flags(svec!["deno", "-r", "-D", "script.ts"]).unwrap(); + assert_eq!(rest, svec!["deno", "script.ts"]); + assert_eq!( + flags, + DenoFlags { + log_debug: true, + reload: true, + ..DenoFlags::default() + } + ); +} + +#[test] +fn test_set_flags_3() { + let (flags, rest, _) = + set_flags(svec!["deno", "-r", "script.ts", "--allow-write"]).unwrap(); + assert_eq!(rest, svec!["deno", "script.ts"]); + assert_eq!( + flags, + DenoFlags { + reload: true, + allow_write: true, + ..DenoFlags::default() + } + ); +} + +#[test] +fn test_set_flags_4() { + let (flags, rest, _) = + set_flags(svec!["deno", "-Dr", "script.ts", "--allow-write"]).unwrap(); + assert_eq!(rest, svec!["deno", "script.ts"]); + assert_eq!( + flags, + DenoFlags { + log_debug: true, + reload: true, + allow_write: true, + ..DenoFlags::default() + } + ); +} + +#[test] +fn test_set_flags_5() { + let (flags, rest, _) = set_flags(svec!["deno", "--types"]).unwrap(); + assert_eq!(rest, svec!["deno"]); + assert_eq!( + flags, + DenoFlags { + types: true, + ..DenoFlags::default() + } + ) +} + +#[test] +fn test_set_flags_6() { + let (flags, rest, _) = + set_flags(svec!["deno", "gist.ts", "--title", "X", "--allow-net"]).unwrap(); + assert_eq!(rest, svec!["deno", "gist.ts", "--title", "X"]); + assert_eq!( + flags, + DenoFlags { + allow_net: true, + ..DenoFlags::default() + } + ) +} + +#[test] +fn test_set_flags_7() { + let (flags, rest, _) = + set_flags(svec!["deno", "gist.ts", "--allow-all"]).unwrap(); + assert_eq!(rest, svec!["deno", "gist.ts"]); + assert_eq!( + flags, + DenoFlags { + allow_net: true, + allow_env: true, + allow_run: true, + allow_read: true, + allow_write: true, + ..DenoFlags::default() + } + ) +} + +#[test] +fn test_set_flags_8() { + let (flags, rest, _) = + set_flags(svec!["deno", "gist.ts", "--allow-read"]).unwrap(); + assert_eq!(rest, svec!["deno", "gist.ts"]); + assert_eq!( + flags, + DenoFlags { + allow_read: true, + ..DenoFlags::default() + } + ) +} diff --git a/cli/fs.rs b/cli/fs.rs new file mode 100644 index 000000000..ff0da95e5 --- /dev/null +++ b/cli/fs.rs @@ -0,0 +1,110 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use std; +use std::fs::{create_dir, DirBuilder, File, OpenOptions}; +use std::io::ErrorKind; +use std::io::Write; +use std::path::{Path, PathBuf}; + +use rand; +use rand::Rng; + +#[cfg(any(unix))] +use std::os::unix::fs::DirBuilderExt; +#[cfg(any(unix))] +use std::os::unix::fs::PermissionsExt; + +pub fn write_file>( + filename: &Path, + data: T, + perm: u32, +) -> std::io::Result<()> { + write_file_2(filename, data, true, perm, true, false) +} + +pub fn write_file_2>( + filename: &Path, + data: T, + update_perm: bool, + perm: u32, + is_create: bool, + is_append: bool, +) -> std::io::Result<()> { + let mut file = OpenOptions::new() + .read(false) + .write(true) + .append(is_append) + .truncate(!is_append) + .create(is_create) + .open(filename)?; + + if update_perm { + set_permissions(&mut file, perm)?; + } + + file.write_all(data.as_ref()) +} + +#[cfg(any(unix))] +fn set_permissions(file: &mut File, perm: u32) -> std::io::Result<()> { + debug!("set file perm to {}", perm); + file.set_permissions(PermissionsExt::from_mode(perm & 0o777)) +} +#[cfg(not(any(unix)))] +fn set_permissions(_file: &mut File, _perm: u32) -> std::io::Result<()> { + // NOOP on windows + Ok(()) +} + +pub fn make_temp_dir( + dir: Option<&Path>, + prefix: Option<&str>, + suffix: Option<&str>, +) -> std::io::Result { + let prefix_ = prefix.unwrap_or(""); + let suffix_ = suffix.unwrap_or(""); + let mut buf: PathBuf = match dir { + Some(ref p) => p.to_path_buf(), + None => std::env::temp_dir(), + }.join("_"); + let mut rng = rand::thread_rng(); + loop { + let unique = rng.gen::(); + buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_)); + // TODO: on posix, set mode flags to 0o700. + let r = create_dir(buf.as_path()); + match r { + Err(ref e) if e.kind() == ErrorKind::AlreadyExists => continue, + Ok(_) => return Ok(buf), + Err(e) => return Err(e), + } + } +} + +pub fn mkdir(path: &Path, perm: u32, recursive: bool) -> std::io::Result<()> { + debug!("mkdir -p {}", path.display()); + let mut builder = DirBuilder::new(); + builder.recursive(recursive); + set_dir_permission(&mut builder, perm); + builder.create(path) +} + +#[cfg(any(unix))] +fn set_dir_permission(builder: &mut DirBuilder, perm: u32) { + debug!("set dir perm to {}", perm); + builder.mode(perm & 0o777); +} + +#[cfg(not(any(unix)))] +fn set_dir_permission(_builder: &mut DirBuilder, _perm: u32) { + // NOOP on windows +} + +pub fn normalize_path(path: &Path) -> String { + let s = String::from(path.to_str().unwrap()); + if cfg!(windows) { + // TODO This isn't correct. Probbly should iterate over components. + s.replace("\\", "/") + } else { + s + } +} diff --git a/cli/global_timer.rs b/cli/global_timer.rs new file mode 100644 index 000000000..eef70ddc2 --- /dev/null +++ b/cli/global_timer.rs @@ -0,0 +1,49 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +//! This module helps deno implement timers. +//! +//! As an optimization, we want to avoid an expensive calls into rust for every +//! setTimeout in JavaScript. Thus in //js/timers.ts a data structure is +//! implemented that calls into Rust for only the smallest timeout. Thus we +//! only need to be able to start and cancel a single timer (or Delay, as Tokio +//! calls it) for an entire Isolate. This is what is implemented here. + +use crate::tokio_util::panic_on_error; +use futures::Future; +use std::time::Instant; +use tokio::sync::oneshot; +use tokio::timer::Delay; + +pub struct GlobalTimer { + tx: Option>, +} + +impl GlobalTimer { + pub fn new() -> Self { + Self { tx: None } + } + + pub fn cancel(&mut self) { + if let Some(tx) = self.tx.take() { + tx.send(()).ok(); + } + } + + pub fn new_timeout( + &mut self, + deadline: Instant, + ) -> impl Future { + if self.tx.is_some() { + self.cancel(); + } + assert!(self.tx.is_none()); + + let (tx, rx) = oneshot::channel(); + self.tx = Some(tx); + + let delay = panic_on_error(Delay::new(deadline)); + let rx = panic_on_error(rx); + + delay.select(rx).then(|_| Ok(())) + } +} diff --git a/cli/http_body.rs b/cli/http_body.rs new file mode 100644 index 000000000..235463ff1 --- /dev/null +++ b/cli/http_body.rs @@ -0,0 +1,112 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +use futures::Async; +use futures::Poll; +use hyper::body::Payload; +use hyper::Body; +use hyper::Chunk; +use std::cmp::min; +use std::io; +use std::io::Read; +use tokio::io::AsyncRead; + +/// Wraps `hyper::Body` so that it can be exposed as an `AsyncRead` and integrated +/// into resources more easily. +pub struct HttpBody { + body: Body, + chunk: Option, + pos: usize, +} + +impl HttpBody { + pub fn from(body: Body) -> Self { + Self { + body, + chunk: None, + pos: 0, + } + } +} + +impl Read for HttpBody { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + unimplemented!(); + } +} + +impl AsyncRead for HttpBody { + fn poll_read(&mut self, buf: &mut [u8]) -> Poll { + if let Some(chunk) = self.chunk.take() { + debug!( + "HttpBody Fake Read buf {} chunk {} pos {}", + buf.len(), + chunk.len(), + self.pos + ); + let n = min(buf.len(), chunk.len() - self.pos); + { + let rest = &chunk[self.pos..]; + buf[..n].clone_from_slice(&rest[..n]); + } + self.pos += n; + if self.pos == chunk.len() { + self.pos = 0; + } else { + self.chunk = Some(chunk); + } + return Ok(Async::Ready(n)); + } else { + assert_eq!(self.pos, 0); + } + + let p = self.body.poll_data(); + match p { + Err(e) => Err( + // TODO Need to map hyper::Error into std::io::Error. + io::Error::new(io::ErrorKind::Other, e), + ), + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(maybe_chunk)) => match maybe_chunk { + None => Ok(Async::Ready(0)), + Some(chunk) => { + debug!( + "HttpBody Real Read buf {} chunk {} pos {}", + buf.len(), + chunk.len(), + self.pos + ); + let n = min(buf.len(), chunk.len()); + buf[..n].clone_from_slice(&chunk[..n]); + if buf.len() < chunk.len() { + self.pos = n; + self.chunk = Some(chunk); + } + Ok(Async::Ready(n)) + } + }, + } + } +} + +#[test] +fn test_body_async_read() { + use std::str::from_utf8; + let body = Body::from("hello world"); + let mut body = HttpBody::from(body); + + let buf = &mut [0, 0, 0, 0, 0]; + let r = body.poll_read(buf); + assert!(r.is_ok()); + assert_eq!(r.unwrap(), Async::Ready(5)); + assert_eq!(from_utf8(buf).unwrap(), "hello"); + + let r = body.poll_read(buf); + assert!(r.is_ok()); + assert_eq!(r.unwrap(), Async::Ready(5)); + assert_eq!(from_utf8(buf).unwrap(), " worl"); + + let r = body.poll_read(buf); + assert!(r.is_ok()); + assert_eq!(r.unwrap(), Async::Ready(1)); + assert_eq!(from_utf8(&buf[0..1]).unwrap(), "d"); +} diff --git a/cli/http_util.rs b/cli/http_util.rs new file mode 100644 index 000000000..8aadbe136 --- /dev/null +++ b/cli/http_util.rs @@ -0,0 +1,166 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::errors; +use crate::errors::{DenoError, DenoResult}; +use crate::tokio_util; + +use futures::future::{loop_fn, Loop}; +use futures::{future, Future, Stream}; +use hyper; +use hyper::client::{Client, HttpConnector}; +use hyper::header::CONTENT_TYPE; +use hyper::Uri; +use hyper_rustls; + +type Connector = hyper_rustls::HttpsConnector; + +lazy_static! { + static ref CONNECTOR: Connector = { + let num_dns_threads = 4; + Connector::new(num_dns_threads) + }; +} + +pub fn get_client() -> Client { + // TODO use Hyper's connection pool. + let c = CONNECTOR.clone(); + Client::builder().build(c) +} + +/// Construct the next uri based on base uri and location header fragment +/// See +fn resolve_uri_from_location(base_uri: &Uri, location: &str) -> Uri { + if location.starts_with("http://") || location.starts_with("https://") { + // absolute uri + location + .parse::() + .expect("provided redirect url should be a valid url") + } else if location.starts_with("//") { + // "//" authority path-abempty + format!("{}:{}", base_uri.scheme_part().unwrap().as_str(), location) + .parse::() + .expect("provided redirect url should be a valid url") + } else if location.starts_with('/') { + // path-absolute + let mut new_uri_parts = base_uri.clone().into_parts(); + new_uri_parts.path_and_query = Some(location.parse().unwrap()); + Uri::from_parts(new_uri_parts).unwrap() + } else { + // assuming path-noscheme | path-empty + let mut new_uri_parts = base_uri.clone().into_parts(); + new_uri_parts.path_and_query = + Some(format!("{}/{}", base_uri.path(), location).parse().unwrap()); + Uri::from_parts(new_uri_parts).unwrap() + } +} + +// The CodeFetch message is used to load HTTP javascript resources and expects a +// synchronous response, this utility method supports that. +pub fn fetch_sync_string(module_name: &str) -> DenoResult<(String, String)> { + let url = module_name.parse::().unwrap(); + let client = get_client(); + // TODO(kevinkassimo): consider set a max redirection counter + // to avoid bouncing between 2 or more urls + let fetch_future = loop_fn((client, url), |(client, url)| { + client + .get(url.clone()) + .map_err(DenoError::from) + .and_then(move |response| { + if response.status().is_redirection() { + let location_string = response + .headers() + .get("location") + .expect("url redirection should provide 'location' header") + .to_str() + .unwrap() + .to_string(); + debug!("Redirecting to {}...", &location_string); + let new_url = resolve_uri_from_location(&url, &location_string); + return Ok(Loop::Continue((client, new_url))); + } + if !response.status().is_success() { + return Err(errors::new( + errors::ErrorKind::NotFound, + "module not found".to_string(), + )); + } + Ok(Loop::Break(response)) + }) + }).and_then(|response| { + let content_type = response + .headers() + .get(CONTENT_TYPE) + .map(|content_type| content_type.to_str().unwrap().to_string()); + let body = response + .into_body() + .concat2() + .map(|body| String::from_utf8(body.to_vec()).unwrap()) + .map_err(DenoError::from); + body.join(future::ok(content_type)) + }).and_then(|(body_string, maybe_content_type)| { + future::ok((body_string, maybe_content_type.unwrap())) + }); + + tokio_util::block_on(fetch_future) +} + +#[test] +fn test_fetch_sync_string() { + // Relies on external http server. See tools/http_server.py + tokio_util::init(|| { + let (p, m) = + fetch_sync_string("http://127.0.0.1:4545/package.json").unwrap(); + println!("package.json len {}", p.len()); + assert!(p.len() > 1); + assert!(m == "application/json") + }); +} + +#[test] +fn test_fetch_sync_string_with_redirect() { + // Relies on external http server. See tools/http_server.py + tokio_util::init(|| { + let (p, m) = + fetch_sync_string("http://127.0.0.1:4546/package.json").unwrap(); + println!("package.json len {}", p.len()); + assert!(p.len() > 1); + assert!(m == "application/json") + }); +} + +#[test] +fn test_resolve_uri_from_location_full_1() { + let url = "http://deno.land".parse::().unwrap(); + let new_uri = resolve_uri_from_location(&url, "http://golang.org"); + assert_eq!(new_uri.host().unwrap(), "golang.org"); +} + +#[test] +fn test_resolve_uri_from_location_full_2() { + let url = "https://deno.land".parse::().unwrap(); + let new_uri = resolve_uri_from_location(&url, "https://golang.org"); + assert_eq!(new_uri.host().unwrap(), "golang.org"); +} + +#[test] +fn test_resolve_uri_from_location_relative_1() { + let url = "http://deno.land/x".parse::().unwrap(); + let new_uri = resolve_uri_from_location(&url, "//rust-lang.org/en-US"); + assert_eq!(new_uri.host().unwrap(), "rust-lang.org"); + assert_eq!(new_uri.path(), "/en-US"); +} + +#[test] +fn test_resolve_uri_from_location_relative_2() { + let url = "http://deno.land/x".parse::().unwrap(); + let new_uri = resolve_uri_from_location(&url, "/y"); + assert_eq!(new_uri.host().unwrap(), "deno.land"); + assert_eq!(new_uri.path(), "/y"); +} + +#[test] +fn test_resolve_uri_from_location_relative_3() { + let url = "http://deno.land/x".parse::().unwrap(); + let new_uri = resolve_uri_from_location(&url, "z"); + assert_eq!(new_uri.host().unwrap(), "deno.land"); + assert_eq!(new_uri.path(), "/x/z"); +} diff --git a/cli/isolate.rs b/cli/isolate.rs new file mode 100644 index 000000000..379203dd3 --- /dev/null +++ b/cli/isolate.rs @@ -0,0 +1,236 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::cli::Cli; +use crate::compiler::compile_sync; +use crate::compiler::ModuleMetaData; +use crate::errors::DenoError; +use crate::errors::RustOrJsError; +use crate::isolate_state::IsolateState; +use crate::js_errors; +use crate::msg; +use deno_core; +use deno_core::deno_mod; +use deno_core::JSError; +use futures::Async; +use futures::Future; +use std::sync::Arc; + +type CoreIsolate = deno_core::Isolate; + +/// Wraps deno_core::Isolate to provide source maps, ops for the CLI, and +/// high-level module loading +pub struct Isolate { + inner: CoreIsolate, + state: Arc, +} + +impl Isolate { + pub fn new(cli: Cli) -> Isolate { + let state = cli.state.clone(); + Self { + inner: CoreIsolate::new(cli), + state, + } + } + + /// Same as execute2() but the filename defaults to "". + pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> { + self.execute2("", js_source) + } + + /// Executes the provided JavaScript source code. The js_filename argument is + /// provided only for debugging purposes. + pub fn execute2( + &mut self, + js_filename: &str, + js_source: &str, + ) -> Result<(), JSError> { + self.inner.execute(js_filename, js_source) + } + + // TODO(ry) make this return a future. + fn mod_load_deps(&self, id: deno_mod) -> Result<(), RustOrJsError> { + // basically iterate over the imports, start loading them. + + let referrer_name = { + let g = self.state.modules.lock().unwrap(); + g.get_name(id).unwrap().clone() + }; + + for specifier in self.inner.mod_get_imports(id) { + let (name, _local_filename) = self + .state + .dir + .resolve_module(&specifier, &referrer_name) + .map_err(DenoError::from) + .map_err(RustOrJsError::from)?; + + debug!("mod_load_deps {}", name); + + if !self.state.modules.lock().unwrap().is_registered(&name) { + let out = fetch_module_meta_data_and_maybe_compile( + &self.state, + &specifier, + &referrer_name, + )?; + let child_id = self.mod_new_and_register( + false, + &out.module_name.clone(), + &out.js_source(), + )?; + + self.mod_load_deps(child_id)?; + } + } + + Ok(()) + } + + /// Executes the provided JavaScript module. + pub fn execute_mod( + &mut self, + js_filename: &str, + is_prefetch: bool, + ) -> Result<(), RustOrJsError> { + // TODO move isolate_state::execute_mod impl here. + self + .execute_mod_inner(js_filename, is_prefetch) + .map_err(|err| match err { + RustOrJsError::Js(err) => RustOrJsError::Js(self.apply_source_map(err)), + x => x, + }) + } + + /// High-level way to execute modules. + /// This will issue HTTP requests and file system calls. + /// Blocks. TODO(ry) Don't block. + fn execute_mod_inner( + &mut self, + url: &str, + is_prefetch: bool, + ) -> Result<(), RustOrJsError> { + let out = fetch_module_meta_data_and_maybe_compile(&self.state, url, ".") + .map_err(RustOrJsError::from)?; + + let id = self + .mod_new_and_register(true, &out.module_name.clone(), &out.js_source()) + .map_err(RustOrJsError::from)?; + + self.mod_load_deps(id)?; + + self + .inner + .mod_instantiate(id) + .map_err(RustOrJsError::from)?; + if !is_prefetch { + self.inner.mod_evaluate(id).map_err(RustOrJsError::from)?; + } + Ok(()) + } + + /// Wraps Isolate::mod_new but registers with modules. + fn mod_new_and_register( + &self, + main: bool, + name: &str, + source: &str, + ) -> Result { + let id = self.inner.mod_new(main, name, source)?; + self.state.modules.lock().unwrap().register(id, &name); + Ok(id) + } + + pub fn print_file_info(&self, module: &str) { + let m = self.state.modules.lock().unwrap(); + m.print_file_info(&self.state.dir, module.to_string()); + } + + /// Applies source map to the error. + fn apply_source_map(&self, err: JSError) -> JSError { + js_errors::apply_source_map(&err, &self.state.dir) + } +} + +impl Future for Isolate { + type Item = (); + type Error = JSError; + + fn poll(&mut self) -> Result, Self::Error> { + self.inner.poll().map_err(|err| self.apply_source_map(err)) + } +} + +fn fetch_module_meta_data_and_maybe_compile( + state: &Arc, + specifier: &str, + referrer: &str, +) -> Result { + let mut out = state.dir.fetch_module_meta_data(specifier, referrer)?; + if (out.media_type == msg::MediaType::TypeScript + && out.maybe_output_code.is_none()) + || state.flags.recompile + { + debug!(">>>>> compile_sync START"); + out = compile_sync(state, specifier, &referrer, &out); + debug!(">>>>> compile_sync END"); + state.dir.code_cache(&out)?; + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::flags; + use crate::permissions::DenoPermissions; + use crate::tokio_util; + use futures::future::lazy; + use std::sync::atomic::Ordering; + + #[test] + fn execute_mod() { + let filename = std::env::current_dir() + .unwrap() + .join("tests/esm_imports_a.js"); + let filename = filename.to_str().unwrap().to_string(); + + let argv = vec![String::from("./deno"), filename.clone()]; + let (flags, rest_argv, _) = flags::set_flags(argv).unwrap(); + + let state = Arc::new(IsolateState::new(flags, rest_argv, None)); + let state_ = state.clone(); + tokio_util::run(lazy(move || { + let cli = Cli::new(None, state.clone(), DenoPermissions::default()); + let mut isolate = Isolate::new(cli); + if let Err(err) = isolate.execute_mod(&filename, false) { + eprintln!("execute_mod err {:?}", err); + } + tokio_util::panic_on_error(isolate) + })); + + let metrics = &state_.metrics; + assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 1); + } + + #[test] + fn execute_mod_circular() { + let filename = std::env::current_dir().unwrap().join("tests/circular1.js"); + let filename = filename.to_str().unwrap().to_string(); + + let argv = vec![String::from("./deno"), filename.clone()]; + let (flags, rest_argv, _) = flags::set_flags(argv).unwrap(); + + let state = Arc::new(IsolateState::new(flags, rest_argv, None)); + let state_ = state.clone(); + tokio_util::run(lazy(move || { + let cli = Cli::new(None, state.clone(), DenoPermissions::default()); + let mut isolate = Isolate::new(cli); + if let Err(err) = isolate.execute_mod(&filename, false) { + eprintln!("execute_mod err {:?}", err); + } + tokio_util::panic_on_error(isolate) + })); + + let metrics = &state_.metrics; + assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 2); + } +} diff --git a/cli/isolate_state.rs b/cli/isolate_state.rs new file mode 100644 index 000000000..4cc010389 --- /dev/null +++ b/cli/isolate_state.rs @@ -0,0 +1,110 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::cli::Buf; +use crate::deno_dir; +use crate::flags; +use crate::global_timer::GlobalTimer; +use crate::modules::Modules; +use futures::sync::mpsc as async_mpsc; +use std; +use std::env; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Mutex; + +pub type WorkerSender = async_mpsc::Sender; +pub type WorkerReceiver = async_mpsc::Receiver; +pub type WorkerChannels = (WorkerSender, WorkerReceiver); + +// AtomicU64 is currently unstable +#[derive(Default)] +pub struct Metrics { + pub ops_dispatched: AtomicUsize, + pub ops_completed: AtomicUsize, + pub bytes_sent_control: AtomicUsize, + pub bytes_sent_data: AtomicUsize, + pub bytes_received: AtomicUsize, + pub resolve_count: AtomicUsize, +} + +// Isolate cannot be passed between threads but IsolateState can. +// IsolateState satisfies Send and Sync. +// So any state that needs to be accessed outside the main V8 thread should be +// inside IsolateState. +#[cfg_attr(feature = "cargo-clippy", allow(stutter))] +pub struct IsolateState { + pub dir: deno_dir::DenoDir, + pub argv: Vec, + pub flags: flags::DenoFlags, + pub metrics: Metrics, + pub modules: Mutex, + pub worker_channels: Option>, + pub global_timer: Mutex, +} + +impl IsolateState { + pub fn new( + flags: flags::DenoFlags, + argv_rest: Vec, + worker_channels: Option, + ) -> Self { + let custom_root = env::var("DENO_DIR").map(|s| s.into()).ok(); + + Self { + dir: deno_dir::DenoDir::new(flags.reload, flags.recompile, custom_root) + .unwrap(), + argv: argv_rest, + flags, + metrics: Metrics::default(), + modules: Mutex::new(Modules::new()), + worker_channels: worker_channels.map(Mutex::new), + global_timer: Mutex::new(GlobalTimer::new()), + } + } + + pub fn main_module(&self) -> Option { + if self.argv.len() <= 1 { + None + } else { + let specifier = self.argv[1].clone(); + let referrer = "."; + match self.dir.resolve_module_url(&specifier, referrer) { + Ok(url) => Some(url.to_string()), + Err(e) => { + debug!("Potentially swallowed error {}", e); + None + } + } + } + } + + #[cfg(test)] + pub fn mock() -> IsolateState { + let argv = vec![String::from("./deno"), String::from("hello.js")]; + // For debugging: argv.push_back(String::from("-D")); + let (flags, rest_argv, _) = flags::set_flags(argv).unwrap(); + IsolateState::new(flags, rest_argv, None) + } + + pub fn metrics_op_dispatched( + &self, + bytes_sent_control: usize, + bytes_sent_data: usize, + ) { + self.metrics.ops_dispatched.fetch_add(1, Ordering::SeqCst); + self + .metrics + .bytes_sent_control + .fetch_add(bytes_sent_control, Ordering::SeqCst); + self + .metrics + .bytes_sent_data + .fetch_add(bytes_sent_data, Ordering::SeqCst); + } + + pub fn metrics_op_completed(&self, bytes_received: usize) { + self.metrics.ops_completed.fetch_add(1, Ordering::SeqCst); + self + .metrics + .bytes_received + .fetch_add(bytes_received, Ordering::SeqCst); + } +} diff --git a/cli/js_errors.rs b/cli/js_errors.rs new file mode 100644 index 000000000..90c9f2007 --- /dev/null +++ b/cli/js_errors.rs @@ -0,0 +1,424 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +//! This mod adds source maps and ANSI color display to deno_core::JSError. +use crate::ansi; +use deno_core::JSError; +use deno_core::StackFrame; +use source_map_mappings::parse_mappings; +use source_map_mappings::Bias; +use source_map_mappings::Mappings; +use std::collections::HashMap; +use std::fmt; +use std::str; + +/// Wrapper around JSError which provides color to_string. +pub struct JSErrorColor<'a>(pub &'a JSError); + +struct StackFrameColor<'a>(&'a StackFrame); + +pub trait SourceMapGetter { + /// Returns the raw source map file. + fn get_source_map(&self, script_name: &str) -> Option>; +} + +/// Cached filename lookups. The key can be None if a previous lookup failed to +/// find a SourceMap. +type CachedMaps = HashMap>; + +struct SourceMap { + mappings: Mappings, + sources: Vec, +} + +impl<'a> fmt::Display for StackFrameColor<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let frame = self.0; + // Note when we print to string, we change from 0-indexed to 1-indexed. + let function_name = ansi::italic_bold(frame.function_name.clone()); + let script_line_column = + format_script_line_column(&frame.script_name, frame.line, frame.column); + + if !frame.function_name.is_empty() { + write!(f, " at {} ({})", function_name, script_line_column) + } else if frame.is_eval { + write!(f, " at eval ({})", script_line_column) + } else { + write!(f, " at {}", script_line_column) + } + } +} + +fn format_script_line_column( + script_name: &str, + line: i64, + column: i64, +) -> String { + // TODO match this style with how typescript displays errors. + let line = ansi::yellow((1 + line).to_string()); + let column = ansi::yellow((1 + column).to_string()); + let script_name = ansi::cyan(script_name.to_string()); + format!("{}:{}:{}", script_name, line, column) +} + +impl<'a> fmt::Display for JSErrorColor<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let e = self.0; + if e.script_resource_name.is_some() { + let script_resource_name = e.script_resource_name.as_ref().unwrap(); + // Avoid showing internal code from gen/bundle/main.js + if script_resource_name != "gen/bundle/main.js" + && script_resource_name != "gen/bundle/compiler.js" + { + if e.line_number.is_some() && e.start_column.is_some() { + assert!(e.line_number.is_some()); + assert!(e.start_column.is_some()); + let script_line_column = format_script_line_column( + script_resource_name, + e.line_number.unwrap() - 1, + e.start_column.unwrap() - 1, + ); + write!(f, "{}", script_line_column)?; + } + if e.source_line.is_some() { + write!(f, "\n{}\n", e.source_line.as_ref().unwrap())?; + let mut s = String::new(); + for i in 0..e.end_column.unwrap() { + if i >= e.start_column.unwrap() { + s.push('^'); + } else { + s.push(' '); + } + } + writeln!(f, "{}", ansi::red_bold(s))?; + } + } + } + + write!(f, "{}", ansi::bold(e.message.clone()))?; + + for frame in &e.frames { + write!(f, "\n{}", StackFrameColor(&frame).to_string())?; + } + Ok(()) + } +} + +impl SourceMap { + fn from_json(json_str: &str) -> Option { + // Ugly. Maybe use serde_derive. + match serde_json::from_str::(json_str) { + Ok(serde_json::Value::Object(map)) => match map["mappings"].as_str() { + None => None, + Some(mappings_str) => { + match parse_mappings::<()>(mappings_str.as_bytes()) { + Err(_) => None, + Ok(mappings) => { + if !map["sources"].is_array() { + return None; + } + let sources_val = map["sources"].as_array().unwrap(); + let mut sources = Vec::::new(); + + for source_val in sources_val { + match source_val.as_str() { + None => return None, + Some(source) => { + sources.push(source.to_string()); + } + } + } + + Some(SourceMap { sources, mappings }) + } + } + } + }, + _ => None, + } + } +} + +fn frame_apply_source_map( + frame: &StackFrame, + mappings_map: &mut CachedMaps, + getter: &dyn SourceMapGetter, +) -> StackFrame { + let maybe_sm = get_mappings(frame.script_name.as_ref(), mappings_map, getter); + let frame_pos = ( + frame.script_name.to_owned(), + frame.line as i64, + frame.column as i64, + ); + let (script_name, line, column) = match maybe_sm { + None => frame_pos, + Some(sm) => match sm.mappings.original_location_for( + frame.line as u32, + frame.column as u32, + Bias::default(), + ) { + None => frame_pos, + Some(mapping) => match &mapping.original { + None => frame_pos, + Some(original) => { + let orig_source = sm.sources[original.source as usize].clone(); + ( + orig_source, + i64::from(original.original_line), + i64::from(original.original_column), + ) + } + }, + }, + }; + + StackFrame { + script_name, + function_name: frame.function_name.clone(), + line, + column, + is_eval: frame.is_eval, + is_constructor: frame.is_constructor, + is_wasm: frame.is_wasm, + } +} + +pub fn apply_source_map( + js_error: &JSError, + getter: &dyn SourceMapGetter, +) -> JSError { + let mut mappings_map: CachedMaps = HashMap::new(); + let mut frames = Vec::::new(); + for frame in &js_error.frames { + let f = frame_apply_source_map(&frame, &mut mappings_map, getter); + frames.push(f); + } + JSError { + message: js_error.message.clone(), + frames, + error_level: js_error.error_level, + source_line: js_error.source_line.clone(), + // TODO the following need to be source mapped: + script_resource_name: js_error.script_resource_name.clone(), + line_number: js_error.line_number, + start_position: js_error.start_position, + end_position: js_error.end_position, + start_column: js_error.start_column, + end_column: js_error.end_column, + } +} + +// The bundle does not get built for 'cargo check', so we don't embed the +// bundle source map. +#[cfg(feature = "check-only")] +fn builtin_source_map(script_name: &str) -> Option> { + None +} + +#[cfg(not(feature = "check-only"))] +fn builtin_source_map(script_name: &str) -> Option> { + match script_name { + "gen/bundle/main.js" => Some( + include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js.map")) + .to_vec(), + ), + "gen/bundle/compiler.js" => Some( + include_bytes!(concat!( + env!("GN_OUT_DIR"), + "/gen/bundle/compiler.js.map" + )).to_vec(), + ), + _ => None, + } +} + +fn parse_map_string( + script_name: &str, + getter: &dyn SourceMapGetter, +) -> Option { + builtin_source_map(script_name) + .or_else(|| getter.get_source_map(script_name)) + .and_then(|raw_source_map| { + SourceMap::from_json(str::from_utf8(&raw_source_map).unwrap()) + }) +} + +fn get_mappings<'a>( + script_name: &str, + mappings_map: &'a mut CachedMaps, + getter: &dyn SourceMapGetter, +) -> &'a Option { + mappings_map + .entry(script_name.to_string()) + .or_insert_with(|| parse_map_string(script_name, getter)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ansi::strip_ansi_codes; + + fn error1() -> JSError { + JSError { + message: "Error: foo bar".to_string(), + source_line: None, + script_resource_name: None, + line_number: None, + start_position: None, + end_position: None, + error_level: None, + start_column: None, + end_column: None, + frames: vec![ + StackFrame { + line: 4, + column: 16, + script_name: "foo_bar.ts".to_string(), + function_name: "foo".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + StackFrame { + line: 5, + column: 20, + script_name: "bar_baz.ts".to_string(), + function_name: "qat".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + StackFrame { + line: 1, + column: 1, + script_name: "deno_main.js".to_string(), + function_name: "".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + ], + } + } + + struct MockSourceMapGetter {} + + impl SourceMapGetter for MockSourceMapGetter { + fn get_source_map(&self, script_name: &str) -> Option> { + let s = match script_name { + "foo_bar.ts" => r#"{"sources": ["foo_bar.ts"], "mappings":";;;IAIA,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#, + "bar_baz.ts" => r#"{"sources": ["bar_baz.ts"], "mappings":";;;IAEA,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,GAAG,GAAG,sDAAa,OAAO,2BAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,EAAE,CAAC;IAEQ,QAAA,GAAG,GAAG,KAAK,CAAC;IAEzB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#, + _ => return None, + }; + Some(s.as_bytes().to_owned()) + } + } + + #[test] + fn js_error_to_string() { + let e = error1(); + assert_eq!("Error: foo bar\n at foo (foo_bar.ts:5:17)\n at qat (bar_baz.ts:6:21)\n at deno_main.js:2:2", strip_ansi_codes(&e.to_string())); + } + + #[test] + fn js_error_apply_source_map_1() { + let e = error1(); + let getter = MockSourceMapGetter {}; + let actual = apply_source_map(&e, &getter); + let expected = JSError { + message: "Error: foo bar".to_string(), + source_line: None, + script_resource_name: None, + line_number: None, + start_position: None, + end_position: None, + error_level: None, + start_column: None, + end_column: None, + frames: vec![ + StackFrame { + line: 5, + column: 12, + script_name: "foo_bar.ts".to_string(), + function_name: "foo".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + StackFrame { + line: 4, + column: 14, + script_name: "bar_baz.ts".to_string(), + function_name: "qat".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + StackFrame { + line: 1, + column: 1, + script_name: "deno_main.js".to_string(), + function_name: "".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }, + ], + }; + assert_eq!(actual, expected); + } + + #[test] + fn js_error_apply_source_map_2() { + let e = JSError { + message: "TypeError: baz".to_string(), + source_line: None, + script_resource_name: None, + line_number: None, + start_position: None, + end_position: None, + error_level: None, + start_column: None, + end_column: None, + frames: vec![StackFrame { + line: 11, + column: 12, + script_name: "gen/bundle/main.js".to_string(), + function_name: "setLogDebug".to_string(), + is_eval: false, + is_constructor: false, + is_wasm: false, + }], + }; + let getter = MockSourceMapGetter {}; + let actual = apply_source_map(&e, &getter); + assert_eq!(actual.message, "TypeError: baz"); + // Because this is accessing the live bundle, this test might be more fragile + assert_eq!(actual.frames.len(), 1); + assert!(actual.frames[0].script_name.ends_with("js/util.ts")); + } + + #[test] + fn source_map_from_json() { + let json = r#"{"version":3,"file":"error_001.js","sourceRoot":"","sources":["file:///Users/rld/src/deno/tests/error_001.ts"],"names":[],"mappings":"AAAA,SAAS,GAAG;IACV,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,GAAG;IACV,GAAG,EAAE,CAAC;AACR,CAAC;AAED,GAAG,EAAE,CAAC"}"#; + let sm = SourceMap::from_json(json).unwrap(); + assert_eq!(sm.sources.len(), 1); + assert_eq!( + sm.sources[0], + "file:///Users/rld/src/deno/tests/error_001.ts" + ); + let mapping = sm + .mappings + .original_location_for(1, 10, Bias::default()) + .unwrap(); + assert_eq!(mapping.generated_line, 1); + assert_eq!(mapping.generated_column, 10); + assert_eq!( + mapping.original, + Some(source_map_mappings::OriginalLocation { + source: 0, + original_line: 1, + original_column: 8, + name: None + }) + ); + } +} diff --git a/cli/main.rs b/cli/main.rs new file mode 100644 index 000000000..4657a3a4d --- /dev/null +++ b/cli/main.rs @@ -0,0 +1,140 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate log; +#[macro_use] +extern crate futures; +#[macro_use] +extern crate serde_json; + +mod ansi; +pub mod cli; +pub mod compiler; +pub mod deno_dir; +pub mod errors; +pub mod flags; +mod fs; +mod global_timer; +mod http_body; +mod http_util; +pub mod isolate; +pub mod isolate_state; +pub mod js_errors; +pub mod modules; +pub mod msg; +pub mod msg_util; +pub mod ops; +pub mod permissions; +mod repl; +pub mod resolve_addr; +pub mod resources; +mod startup_data; +mod tokio_util; +mod tokio_write; +pub mod version; +pub mod workers; + +use crate::cli::Cli; +use crate::errors::RustOrJsError; +use crate::isolate::Isolate; +use crate::isolate_state::IsolateState; +use futures::lazy; +use futures::Future; +use log::{LevelFilter, Metadata, Record}; +use std::env; +use std::sync::Arc; + +static LOGGER: Logger = Logger; + +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()) { + println!("{} RS - {}", record.level(), record.args()); + } + } + fn flush(&self) {} +} + +fn print_err_and_exit(err: RustOrJsError) { + eprintln!("{}", err.to_string()); + std::process::exit(1); +} + +fn js_check(r: Result<(), E>) +where + E: Into, +{ + if let Err(err) = r { + print_err_and_exit(err.into()); + } +} + +fn main() { + #[cfg(windows)] + ansi_term::enable_ansi_support().ok(); // For Windows 10 + + log::set_logger(&LOGGER).unwrap(); + let args = env::args().collect(); + let (mut flags, mut rest_argv, usage_string) = flags::set_flags(args) + .unwrap_or_else(|err| { + eprintln!("{}", err); + std::process::exit(1) + }); + + if flags.help { + println!("{}", &usage_string); + std::process::exit(0); + } + + log::set_max_level(if flags.log_debug { + LevelFilter::Debug + } else { + LevelFilter::Warn + }); + + if flags.fmt { + rest_argv.insert(1, "https://deno.land/std/prettier/main.ts".to_string()); + flags.allow_read = true; + flags.allow_write = true; + } + + let should_prefetch = flags.prefetch || flags.info; + let should_display_info = flags.info; + + let state = Arc::new(IsolateState::new(flags, rest_argv, None)); + let state_ = state.clone(); + let startup_data = startup_data::deno_isolate_init(); + let permissions = permissions::DenoPermissions::from_flags(&state.flags); + let cli = Cli::new(Some(startup_data), state_, permissions); + let mut isolate = Isolate::new(cli); + + let main_future = lazy(move || { + // Setup runtime. + js_check(isolate.execute("denoMain()")); + + // Execute main module. + if let Some(main_module) = state.main_module() { + debug!("main_module {}", main_module); + js_check(isolate.execute_mod(&main_module, should_prefetch)); + if should_display_info { + // Display file info and exit. Do not run file + isolate.print_file_info(&main_module); + std::process::exit(0); + } + } + + isolate.then(|result| { + js_check(result); + Ok(()) + }) + }); + + tokio_util::run(main_future); +} diff --git a/cli/modules.rs b/cli/modules.rs new file mode 100644 index 000000000..908c31b6d --- /dev/null +++ b/cli/modules.rs @@ -0,0 +1,204 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::ansi; +use crate::deno_dir::DenoDir; +use crate::msg; +use deno_core::deno_mod; +use std::collections::HashMap; +use std::collections::HashSet; +use std::fmt; + +pub struct ModuleInfo { + name: String, + children: Vec, +} + +/// A collection of JS modules. +#[derive(Default)] +pub struct Modules { + pub info: HashMap, + pub by_name: HashMap, +} + +impl Modules { + pub fn new() -> Modules { + Self { + info: HashMap::new(), + by_name: HashMap::new(), + } + } + + pub fn get_id(&self, name: &str) -> Option { + self.by_name.get(name).cloned() + } + + pub fn get_children(&self, id: deno_mod) -> Option<&Vec> { + self.info.get(&id).map(|i| &i.children) + } + + pub fn get_name(&self, id: deno_mod) -> Option<&String> { + self.info.get(&id).map(|i| &i.name) + } + + pub fn is_registered(&self, name: &str) -> bool { + self.by_name.get(name).is_some() + } + + pub fn register(&mut self, id: deno_mod, name: &str) { + let name = String::from(name); + debug!("register {}", name); + self.by_name.insert(name.clone(), id); + self.info.insert( + id, + ModuleInfo { + name, + children: Vec::new(), + }, + ); + } + + pub fn resolve_cb( + &mut self, + deno_dir: &DenoDir, + specifier: &str, + referrer: deno_mod, + ) -> deno_mod { + debug!("resolve_cb {}", specifier); + + let maybe_info = self.info.get_mut(&referrer); + if maybe_info.is_none() { + debug!("cant find referrer {}", referrer); + return 0; + } + let info = maybe_info.unwrap(); + let referrer_name = &info.name; + let r = deno_dir.resolve_module(specifier, referrer_name); + if let Err(err) = r { + debug!("potentially swallowed err: {}", err); + return 0; + } + let (name, _local_filename) = r.unwrap(); + + if let Some(id) = self.by_name.get(&name) { + let child_id = *id; + info.children.push(child_id); + return child_id; + } else { + return 0; + } + } + + pub fn print_file_info(&self, deno_dir: &DenoDir, filename: String) { + let maybe_out = deno_dir.fetch_module_meta_data(&filename, "."); + if maybe_out.is_err() { + println!("{}", maybe_out.unwrap_err()); + return; + } + let out = maybe_out.unwrap(); + + println!("{} {}", ansi::bold("local:".to_string()), &(out.filename)); + println!( + "{} {}", + ansi::bold("type:".to_string()), + msg::enum_name_media_type(out.media_type) + ); + if out.maybe_output_code_filename.is_some() { + println!( + "{} {}", + ansi::bold("compiled:".to_string()), + out.maybe_output_code_filename.as_ref().unwrap(), + ); + } + if out.maybe_source_map_filename.is_some() { + println!( + "{} {}", + ansi::bold("map:".to_string()), + out.maybe_source_map_filename.as_ref().unwrap() + ); + } + + let deps = Deps::new(self, &out.module_name); + println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name); + if let Some(ref depsdeps) = deps.deps { + for d in depsdeps { + println!("{}", d); + } + } + } +} + +pub struct Deps { + pub name: String, + pub deps: Option>, + prefix: String, + is_last: bool, +} + +impl Deps { + pub fn new(modules: &Modules, module_name: &str) -> Deps { + let mut seen = HashSet::new(); + let id = modules.get_id(module_name).unwrap(); + Self::helper(&mut seen, "".to_string(), true, modules, id) + } + + fn helper( + seen: &mut HashSet, + prefix: String, + is_last: bool, + modules: &Modules, + id: deno_mod, + ) -> Deps { + let name = modules.get_name(id).unwrap().to_string(); + if seen.contains(&id) { + Deps { + name, + prefix, + deps: None, + is_last, + } + } else { + seen.insert(id); + let child_ids = modules.get_children(id).unwrap(); + let child_count = child_ids.iter().count(); + let deps = child_ids + .iter() + .enumerate() + .map(|(index, dep_id)| { + let new_is_last = index == child_count - 1; + let mut new_prefix = prefix.clone(); + new_prefix.push(if is_last { ' ' } else { '│' }); + new_prefix.push(' '); + Self::helper(seen, new_prefix, new_is_last, modules, *dep_id) + }).collect(); + Deps { + name, + prefix, + deps: Some(deps), + is_last, + } + } + } +} + +impl fmt::Display for Deps { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut has_children = false; + if let Some(ref deps) = self.deps { + has_children = !deps.is_empty(); + } + write!( + f, + "{}{}─{} {}", + self.prefix, + if self.is_last { "└" } else { "├" }, + if has_children { "┬" } else { "─" }, + self.name + )?; + + if let Some(ref deps) = self.deps { + for d in deps { + write!(f, "\n{}", d)?; + } + } + Ok(()) + } +} diff --git a/cli/msg.fbs b/cli/msg.fbs new file mode 100644 index 000000000..243034cfb --- /dev/null +++ b/cli/msg.fbs @@ -0,0 +1,524 @@ +union Any { + Accept, + Chdir, + Chmod, + Close, + CopyFile, + Cwd, + CwdRes, + Dial, + Environ, + EnvironRes, + Exit, + Fetch, + FetchModuleMetaData, + FetchModuleMetaDataRes, + FetchRes, + FormatError, + FormatErrorRes, + GlobalTimer, + GlobalTimerRes, + GlobalTimerStop, + IsTTY, + IsTTYRes, + Listen, + ListenRes, + MakeTempDir, + MakeTempDirRes, + Metrics, + MetricsRes, + Mkdir, + NewConn, + Now, + NowRes, + Open, + OpenRes, + PermissionRevoke, + Permissions, + PermissionsRes, + Read, + ReadDir, + ReadDirRes, + ReadFile, + ReadFileRes, + ReadRes, + Readlink, + ReadlinkRes, + Remove, + Rename, + ReplReadline, + ReplReadlineRes, + ReplStart, + ReplStartRes, + Resources, + ResourcesRes, + Run, + RunRes, + RunStatus, + RunStatusRes, + Seek, + SetEnv, + Shutdown, + Start, + StartRes, + Stat, + StatRes, + Symlink, + Truncate, + WorkerGetMessage, + WorkerGetMessageRes, + WorkerPostMessage, + Write, + WriteFile, + WriteRes, +} + +enum ErrorKind: byte { + NoError = 0, + + // io errors + + NotFound, + PermissionDenied, + ConnectionRefused, + ConnectionReset, + ConnectionAborted, + NotConnected, + AddrInUse, + AddrNotAvailable, + BrokenPipe, + AlreadyExists, + WouldBlock, + InvalidInput, + InvalidData, + TimedOut, + Interrupted, + WriteZero, + Other, + UnexpectedEof, + BadResource, + CommandFailed, + + // url errors + + EmptyHost, + IdnaError, + InvalidPort, + InvalidIpv4Address, + InvalidIpv6Address, + InvalidDomainCharacter, + RelativeUrlWithoutBase, + RelativeUrlWithCannotBeABaseBase, + SetHostOnCannotBeABaseUrl, + Overflow, + + // hyper errors + + HttpUser, + HttpClosed, + HttpCanceled, + HttpParse, + HttpOther, + TooLarge, + + // custom errors + InvalidUri, + InvalidSeekMode, +} + +table Cwd {} + +table CwdRes { + cwd: string; +} + +enum MediaType: byte { + JavaScript = 0, + TypeScript, + Json, + Unknown +} + +table Base { + cmd_id: uint32; + sync: bool = false; + error_kind: ErrorKind = NoError; + error: string; + inner: Any; +} + +table Start { + unused: int8; +} + +table StartRes { + cwd: string; + pid: uint32; + argv: [string]; + exec_path: string; + main_module: string; // Absolute URL. + debug_flag: bool; + deps_flag: bool; + types_flag: bool; + version_flag: bool; + deno_version: string; + v8_version: string; + no_color: bool; +} + +table FormatError { + error: string; +} + +table FormatErrorRes { + error: string; +} + +table WorkerGetMessage { + unused: int8; +} + +table WorkerGetMessageRes { + data: [ubyte]; +} + +table WorkerPostMessage { + // data passed thru the zero-copy data parameter. +} + +table FetchModuleMetaData { + specifier: string; + referrer: string; +} + +table FetchModuleMetaDataRes { + // If it's a non-http module, moduleName and filename will be the same. + // For http modules, moduleName is its resolved http URL, and filename + // is the location of the locally downloaded source code. + module_name: string; + filename: string; + media_type: MediaType; + data: [ubyte]; +} + +table Chdir { + directory: string; +} + +table GlobalTimer { + timeout: int; +} + +table GlobalTimerRes { } + +table GlobalTimerStop { } + +table Exit { + code: int; +} + +table Environ {} + +table SetEnv { + key: string; + value: string; +} + +table EnvironRes { + map: [KeyValue]; +} + +table KeyValue { + key: string; + value: string; +} + +table Permissions {} + +table PermissionRevoke { + permission: string; +} + +table PermissionsRes { + run: bool; + read: bool; + write: bool; + net: bool; + env: bool; +} + +// Note this represents The WHOLE header of an http message, not just the key +// value pairs. That means it includes method and url for Requests and status +// for responses. This is why it is singular "Header" instead of "Headers". +table HttpHeader { + is_request: bool; + // Request only: + method: string; + url: string; + // Response only: + status: uint16; + // Both: + fields: [KeyValue]; +} + +table Fetch { + header: HttpHeader; +} + +table FetchRes { + header: HttpHeader; + body_rid: uint32; +} + +table MakeTempDir { + dir: string; + prefix: string; + suffix: string; +} + +table MakeTempDirRes { + path: string; +} + +table Mkdir { + path: string; + recursive: bool; + mode: uint; // Specified by https://godoc.org/os#FileMode +} + +table Chmod { + path: string; + mode: uint; // Specified by https://godoc.org/os#FileMode +} + +table Remove { + path: string; + recursive: bool; +} + +table ReadFile { + filename: string; +} + +table ReadFileRes { + data: [ubyte]; +} + +table ReadDir { + path: string; +} + +table ReadDirRes { + entries: [StatRes]; +} + +table WriteFile { + filename: string; + data: [ubyte]; + update_perm: bool; + perm: uint; + // perm specified by https://godoc.org/os#FileMode + is_create: bool; + is_append: bool; +} + +table CopyFile { + from: string; + to: string; +} + +table Rename { + oldpath: string; + newpath: string; +} + +table Readlink { + name: string; +} + +table ReadlinkRes { + path: string; +} + +table ReplStart { + history_file: string; + // TODO add config +} + +table ReplStartRes { + rid: uint32; +} + +table ReplReadline { + rid: uint32; + prompt: string; +} + +table ReplReadlineRes { + line: string; +} + +table Resources {} + +table Resource { + rid: uint32; + repr: string; +} + +table ResourcesRes { + resources: [Resource]; +} + +table Symlink { + oldname: string; + newname: string; +} + +table Stat { + filename: string; + lstat: bool; +} + +table StatRes { + is_file: bool; + is_symlink: bool; + len: ulong; + modified:ulong; + accessed:ulong; + created:ulong; + mode: uint; + has_mode: bool; // false on windows + name: string; + path: string; +} + +table Truncate { + name: string; + len: uint; +} + +table Open { + filename: string; + perm: uint; + mode: string; +} + +table OpenRes { + rid: uint32; +} + +table Read { + rid: uint32; + // (ptr, len) is passed as second parameter to libdeno.send(). +} + +table ReadRes { + nread: uint; + eof: bool; +} + +table Write { + rid: uint32; +} + +table WriteRes { + nbyte: uint; +} + +table Close { + rid: uint32; +} + +table Shutdown { + rid: uint32; + how: uint; +} + +table Listen { + network: string; + address: string; +} + +table ListenRes { + rid: uint32; +} + +table Accept { + rid: uint32; +} + +table Dial { + network: string; + address: string; +} + +// Response to Accept and Dial. +table NewConn { + rid: uint32; + remote_addr: string; + local_addr: string; +} + +table Metrics {} + +table MetricsRes { + ops_dispatched: uint64; + ops_completed: uint64; + bytes_sent_control: uint64; + bytes_sent_data: uint64; + bytes_received: uint64; +} + +enum ProcessStdio: byte { Inherit, Piped, Null } + +table Run { + args: [string]; + cwd: string; + env: [KeyValue]; + stdin: ProcessStdio; + stdout: ProcessStdio; + stderr: ProcessStdio; +} + +table RunRes { + rid: uint32; + pid: uint32; + // The following stdio rids are only valid if "Piped" was specified for the + // corresponding stdio stream. The caller MUST issue a close op for all valid + // stdio streams. + stdin_rid: uint32; + stdout_rid: uint32; + stderr_rid: uint32; +} + +table RunStatus { + rid: uint32; +} + +table RunStatusRes { + got_signal: bool; + exit_code: int; + exit_signal: int; +} + +table Now {} + +table NowRes { + time: uint64; +} + +table IsTTY {} + +table IsTTYRes { + stdin: bool; + stdout: bool; + stderr: bool; +} + +table Seek { + rid: uint32; + offset: int; + whence: uint; +} + +root_type Base; diff --git a/cli/msg.rs b/cli/msg.rs new file mode 100644 index 000000000..080f39de8 --- /dev/null +++ b/cli/msg.rs @@ -0,0 +1,26 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +#![allow(unused_imports)] +#![allow(dead_code)] +#![cfg_attr( + feature = "cargo-clippy", + allow(clippy::all, clippy::pedantic) +)] +use crate::isolate_state; +use flatbuffers; +use std::sync::atomic::Ordering; + +// GN_OUT_DIR is set either by build.rs (for the Cargo build), or by +// build_extra/rust/run.py (for the GN+Ninja build). +include!(concat!(env!("GN_OUT_DIR"), "/gen/msg_generated.rs")); + +impl<'a> From<&'a isolate_state::Metrics> for MetricsResArgs { + fn from(m: &'a isolate_state::Metrics) -> Self { + MetricsResArgs { + ops_dispatched: m.ops_dispatched.load(Ordering::SeqCst) as u64, + ops_completed: m.ops_completed.load(Ordering::SeqCst) as u64, + bytes_sent_control: m.bytes_sent_control.load(Ordering::SeqCst) as u64, + bytes_sent_data: m.bytes_sent_data.load(Ordering::SeqCst) as u64, + bytes_received: m.bytes_received.load(Ordering::SeqCst) as u64, + } + } +} diff --git a/cli/msg_util.rs b/cli/msg_util.rs new file mode 100644 index 000000000..71bcc19d9 --- /dev/null +++ b/cli/msg_util.rs @@ -0,0 +1,127 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +// Helpers for serialization. +use crate::errors; +use crate::errors::DenoResult; +use crate::msg; + +use flatbuffers; +use http::header::HeaderName; +use http::uri::Uri; +use http::Method; +use hyper::header::HeaderMap; +use hyper::header::HeaderValue; +use hyper::Body; +use hyper::Request; +use hyper::Response; +use std::str::FromStr; + +type Headers = HeaderMap; + +pub fn serialize_key_value<'bldr>( + builder: &mut flatbuffers::FlatBufferBuilder<'bldr>, + key: &str, + value: &str, +) -> flatbuffers::WIPOffset> { + let key = builder.create_string(&key); + let value = builder.create_string(&value); + msg::KeyValue::create( + builder, + &msg::KeyValueArgs { + key: Some(key), + value: Some(value), + }, + ) +} + +pub fn serialize_request_header<'bldr>( + builder: &mut flatbuffers::FlatBufferBuilder<'bldr>, + r: &Request, +) -> flatbuffers::WIPOffset> { + let method = builder.create_string(r.method().as_str()); + let url = builder.create_string(r.uri().to_string().as_ref()); + + let mut fields = Vec::new(); + for (key, val) in r.headers().iter() { + let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap()); + fields.push(kv); + } + let fields = builder.create_vector(fields.as_ref()); + + msg::HttpHeader::create( + builder, + &msg::HttpHeaderArgs { + is_request: true, + method: Some(method), + url: Some(url), + fields: Some(fields), + ..Default::default() + }, + ) +} + +pub fn serialize_fields<'bldr>( + builder: &mut flatbuffers::FlatBufferBuilder<'bldr>, + headers: &Headers, +) -> flatbuffers::WIPOffset< + flatbuffers::Vector< + 'bldr, + flatbuffers::ForwardsUOffset>, + >, +> { + let mut fields = Vec::new(); + for (key, val) in headers.iter() { + let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap()); + fields.push(kv); + } + builder.create_vector(fields.as_ref()) +} + +// Not to be confused with serialize_response which has nothing to do with HTTP. +pub fn serialize_http_response<'bldr>( + builder: &mut flatbuffers::FlatBufferBuilder<'bldr>, + r: &Response, +) -> flatbuffers::WIPOffset> { + let status = r.status().as_u16(); + let fields = serialize_fields(builder, r.headers()); + msg::HttpHeader::create( + builder, + &msg::HttpHeaderArgs { + is_request: false, + status, + fields: Some(fields), + ..Default::default() + }, + ) +} + +pub fn deserialize_request( + header_msg: msg::HttpHeader<'_>, + body: Body, +) -> DenoResult> { + let mut r = Request::new(body); + + assert!(header_msg.is_request()); + + let u = header_msg.url().unwrap(); + let u = Uri::from_str(u) + .map_err(|e| errors::new(msg::ErrorKind::InvalidUri, e.to_string()))?; + *r.uri_mut() = u; + + if let Some(method) = header_msg.method() { + let method = Method::from_str(method).unwrap(); + *r.method_mut() = method; + } + + if let Some(fields) = header_msg.fields() { + let headers = r.headers_mut(); + for i in 0..fields.len() { + let kv = fields.get(i); + let key = kv.key().unwrap(); + let name = HeaderName::from_bytes(key.as_bytes()).unwrap(); + let value = kv.value().unwrap(); + let v = HeaderValue::from_str(value).unwrap(); + headers.insert(name, v); + } + } + Ok(r) +} diff --git a/cli/ops.rs b/cli/ops.rs new file mode 100644 index 000000000..254a21563 --- /dev/null +++ b/cli/ops.rs @@ -0,0 +1,2020 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use atty; +use crate::ansi; +use crate::cli::Buf; +use crate::cli::Cli; +use crate::errors; +use crate::errors::{permission_denied, DenoError, DenoResult, ErrorKind}; +use crate::fs as deno_fs; +use crate::http_util; +use crate::isolate_state::IsolateState; +use crate::js_errors::apply_source_map; +use crate::js_errors::JSErrorColor; +use crate::msg; +use crate::msg_util; +use crate::repl; +use crate::resolve_addr::resolve_addr; +use crate::resources; +use crate::resources::table_entries; +use crate::resources::Resource; +use crate::tokio_util; +use crate::tokio_write; +use crate::version; +use deno_core::deno_buf; +use deno_core::JSError; +use deno_core::Op; +use flatbuffers::FlatBufferBuilder; +use futures; +use futures::Async; +use futures::Poll; +use futures::Sink; +use futures::Stream; +use hyper; +use hyper::rt::Future; +use remove_dir_all::remove_dir_all; +use std; +use std::convert::From; +use std::fs; +use std::net::Shutdown; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use std::sync::Arc; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use tokio; +use tokio::net::TcpListener; +use tokio::net::TcpStream; +use tokio_process::CommandExt; +use tokio_threadpool; + +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +#[cfg(unix)] +use std::os::unix::process::ExitStatusExt; + +type OpResult = DenoResult; + +pub type OpWithError = dyn Future + Send; + +// TODO Ideally we wouldn't have to box the OpWithError being returned. +// The box is just to make it easier to get a prototype refactor working. +type OpCreator = + fn(cli: &Cli, base: &msg::Base<'_>, data: deno_buf) -> Box; + +#[inline] +fn empty_buf() -> Buf { + Box::new([]) +} + +/// Processes raw messages from JavaScript. +/// This functions invoked every time libdeno.send() is called. +/// control corresponds to the first argument of libdeno.send(). +/// data corresponds to the second argument of libdeno.send(). +pub fn dispatch( + cli: &Cli, + control: &[u8], + zero_copy: deno_buf, +) -> (bool, Box) { + let bytes_sent_control = control.len(); + let bytes_sent_zero_copy = zero_copy.len(); + let base = msg::get_root_as_base(&control); + let is_sync = base.sync(); + let inner_type = base.inner_type(); + let cmd_id = base.cmd_id(); + + let op: Box = { + // Handle regular ops. + let op_creator: OpCreator = match inner_type { + msg::Any::Accept => op_accept, + msg::Any::Chdir => op_chdir, + msg::Any::Chmod => op_chmod, + msg::Any::Close => op_close, + msg::Any::CopyFile => op_copy_file, + msg::Any::Cwd => op_cwd, + msg::Any::Dial => op_dial, + msg::Any::Environ => op_env, + msg::Any::Exit => op_exit, + msg::Any::Fetch => op_fetch, + msg::Any::FetchModuleMetaData => op_fetch_module_meta_data, + msg::Any::FormatError => op_format_error, + msg::Any::GlobalTimer => op_global_timer, + msg::Any::GlobalTimerStop => op_global_timer_stop, + msg::Any::IsTTY => op_is_tty, + msg::Any::Listen => op_listen, + msg::Any::MakeTempDir => op_make_temp_dir, + msg::Any::Metrics => op_metrics, + msg::Any::Mkdir => op_mkdir, + msg::Any::Now => op_now, + msg::Any::Open => op_open, + msg::Any::PermissionRevoke => op_revoke_permission, + msg::Any::Permissions => op_permissions, + msg::Any::Read => op_read, + msg::Any::ReadDir => op_read_dir, + msg::Any::ReadFile => op_read_file, + msg::Any::Readlink => op_read_link, + msg::Any::Remove => op_remove, + msg::Any::Rename => op_rename, + msg::Any::ReplReadline => op_repl_readline, + msg::Any::ReplStart => op_repl_start, + msg::Any::Resources => op_resources, + msg::Any::Run => op_run, + msg::Any::RunStatus => op_run_status, + msg::Any::Seek => op_seek, + msg::Any::SetEnv => op_set_env, + msg::Any::Shutdown => op_shutdown, + msg::Any::Start => op_start, + msg::Any::Stat => op_stat, + msg::Any::Symlink => op_symlink, + msg::Any::Truncate => op_truncate, + msg::Any::WorkerGetMessage => op_worker_get_message, + msg::Any::WorkerPostMessage => op_worker_post_message, + msg::Any::Write => op_write, + msg::Any::WriteFile => op_write_file, + _ => panic!(format!( + "Unhandled message {}", + msg::enum_name_any(inner_type) + )), + }; + op_creator(&cli, &base, zero_copy) + }; + + cli + .state + .metrics_op_dispatched(bytes_sent_control, bytes_sent_zero_copy); + let state = cli.state.clone(); + + let boxed_op = Box::new( + op.or_else(move |err: DenoError| -> Result { + debug!("op err {}", err); + // No matter whether we got an Err or Ok, we want a serialized message to + // send back. So transform the DenoError into a deno_buf. + let builder = &mut FlatBufferBuilder::new(); + let errmsg_offset = builder.create_string(&format!("{}", err)); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + error: Some(errmsg_offset), + error_kind: err.kind(), + ..Default::default() + }, + )) + }).and_then(move |buf: Buf| -> Result { + // Handle empty responses. For sync responses we just want + // to send null. For async we want to send a small message + // with the cmd_id. + let buf = if is_sync || buf.len() > 0 { + buf + } else { + let builder = &mut FlatBufferBuilder::new(); + serialize_response( + cmd_id, + builder, + msg::BaseArgs { + ..Default::default() + }, + ) + }; + state.metrics_op_completed(buf.len()); + Ok(buf) + }).map_err(|err| panic!("unexpected error {:?}", err)), + ); + + debug!( + "msg_from_js {} sync {}", + msg::enum_name_any(inner_type), + base.sync() + ); + (base.sync(), boxed_op) +} + +fn op_now( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let start = SystemTime::now(); + let since_the_epoch = start.duration_since(UNIX_EPOCH).unwrap(); + let time = since_the_epoch.as_secs() * 1000 + + u64::from(since_the_epoch.subsec_millis()); + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::NowRes::create(builder, &msg::NowResArgs { time }); + ok_future(serialize_response( + base.cmd_id(), + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::NowRes, + ..Default::default() + }, + )) +} + +fn op_is_tty( + _cli: &Cli, + base: &msg::Base<'_>, + _data: deno_buf, +) -> Box { + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::IsTTYRes::create( + builder, + &msg::IsTTYResArgs { + stdin: atty::is(atty::Stream::Stdin), + stdout: atty::is(atty::Stream::Stdout), + stderr: atty::is(atty::Stream::Stderr), + }, + ); + ok_future(serialize_response( + base.cmd_id(), + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::IsTTYRes, + ..Default::default() + }, + )) +} + +fn op_exit( + _cli: &Cli, + base: &msg::Base<'_>, + _data: deno_buf, +) -> Box { + let inner = base.inner_as_exit().unwrap(); + std::process::exit(inner.code()) +} + +fn op_start( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let mut builder = FlatBufferBuilder::new(); + + let argv = cli + .state + .argv + .iter() + .map(|s| s.as_str()) + .collect::>(); + let argv_off = builder.create_vector_of_strings(argv.as_slice()); + + let cwd_path = std::env::current_dir().unwrap(); + let cwd_off = + builder.create_string(deno_fs::normalize_path(cwd_path.as_ref()).as_ref()); + + let exec_path = + builder.create_string(std::env::current_exe().unwrap().to_str().unwrap()); + + let v8_version = version::v8(); + let v8_version_off = builder.create_string(v8_version); + + let deno_version = version::DENO; + let deno_version_off = builder.create_string(deno_version); + + let main_module = cli.state.main_module().map(|m| builder.create_string(&m)); + + let inner = msg::StartRes::create( + &mut builder, + &msg::StartResArgs { + cwd: Some(cwd_off), + pid: std::process::id(), + argv: Some(argv_off), + main_module, + debug_flag: cli.state.flags.log_debug, + types_flag: cli.state.flags.types, + version_flag: cli.state.flags.version, + v8_version: Some(v8_version_off), + deno_version: Some(deno_version_off), + no_color: !ansi::use_color(), + exec_path: Some(exec_path), + ..Default::default() + }, + ); + + ok_future(serialize_response( + base.cmd_id(), + &mut builder, + msg::BaseArgs { + inner_type: msg::Any::StartRes, + inner: Some(inner.as_union_value()), + ..Default::default() + }, + )) +} + +fn op_format_error( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_format_error().unwrap(); + let orig_error = String::from(inner.error().unwrap()); + + let js_error = JSError::from_v8_exception(&orig_error).unwrap(); + let js_error_mapped = apply_source_map(&js_error, &cli.state.dir); + let js_error_string = JSErrorColor(&js_error_mapped).to_string(); + + let mut builder = FlatBufferBuilder::new(); + let new_error = builder.create_string(&js_error_string); + + let inner = msg::FormatErrorRes::create( + &mut builder, + &msg::FormatErrorResArgs { + error: Some(new_error), + ..Default::default() + }, + ); + + ok_future(serialize_response( + base.cmd_id(), + &mut builder, + msg::BaseArgs { + inner_type: msg::Any::FormatErrorRes, + inner: Some(inner.as_union_value()), + ..Default::default() + }, + )) +} + +fn serialize_response( + cmd_id: u32, + builder: &mut FlatBufferBuilder<'_>, + mut args: msg::BaseArgs<'_>, +) -> Buf { + args.cmd_id = cmd_id; + let base = msg::Base::create(builder, &args); + msg::finish_base_buffer(builder, base); + let data = builder.finished_data(); + // println!("serialize_response {:x?}", data); + data.into() +} + +#[inline] +pub fn ok_future(buf: Buf) -> Box { + Box::new(futures::future::ok(buf)) +} + +// Shout out to Earl Sweatshirt. +#[inline] +pub fn odd_future(err: DenoError) -> Box { + Box::new(futures::future::err(err)) +} + +// https://github.com/denoland/deno/blob/golang/os.go#L100-L154 +fn op_fetch_module_meta_data( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_fetch_module_meta_data().unwrap(); + let cmd_id = base.cmd_id(); + let specifier = inner.specifier().unwrap(); + let referrer = inner.referrer().unwrap(); + + // Check for allow read since this operation could be used to read from the file system. + if !cli.permissions.allows_read() { + debug!("No read permission for fetch_module_meta_data"); + return odd_future(permission_denied()); + } + + // Check for allow write since this operation could be used to write to the file system. + if !cli.permissions.allows_write() { + debug!("No network permission for fetch_module_meta_data"); + return odd_future(permission_denied()); + } + + // Check for allow net since this operation could be used to make https/http requests. + if !cli.permissions.allows_net() { + debug!("No network permission for fetch_module_meta_data"); + return odd_future(permission_denied()); + } + + assert_eq!( + cli.state.dir.root.join("gen"), + cli.state.dir.gen, + "Sanity check" + ); + + Box::new(futures::future::result(|| -> OpResult { + let builder = &mut FlatBufferBuilder::new(); + let out = cli.state.dir.fetch_module_meta_data(specifier, referrer)?; + let data_off = builder.create_vector(out.source_code.as_slice()); + let msg_args = msg::FetchModuleMetaDataResArgs { + module_name: Some(builder.create_string(&out.module_name)), + filename: Some(builder.create_string(&out.filename)), + media_type: out.media_type, + data: Some(data_off), + }; + let inner = msg::FetchModuleMetaDataRes::create(builder, &msg_args); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::FetchModuleMetaDataRes, + ..Default::default() + }, + )) + }())) +} + +fn op_chdir( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_chdir().unwrap(); + let directory = inner.directory().unwrap(); + Box::new(futures::future::result(|| -> OpResult { + std::env::set_current_dir(&directory)?; + Ok(empty_buf()) + }())) +} + +fn op_global_timer_stop( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert!(base.sync()); + assert_eq!(data.len(), 0); + let mut t = cli.state.global_timer.lock().unwrap(); + t.cancel(); + ok_future(empty_buf()) +} + +fn op_global_timer( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert!(!base.sync()); + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + let inner = base.inner_as_global_timer().unwrap(); + let val = inner.timeout(); + assert!(val >= 0); + + let mut t = cli.state.global_timer.lock().unwrap(); + let deadline = Instant::now() + Duration::from_millis(val as u64); + let f = t.new_timeout(deadline); + + Box::new(f.then(move |_| { + let builder = &mut FlatBufferBuilder::new(); + let inner = + msg::GlobalTimerRes::create(builder, &msg::GlobalTimerResArgs {}); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::GlobalTimerRes, + ..Default::default() + }, + )) + })) +} + +fn op_set_env( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_set_env().unwrap(); + let key = inner.key().unwrap(); + let value = inner.value().unwrap(); + if let Err(e) = cli.check_env() { + return odd_future(e); + } + std::env::set_var(key, value); + ok_future(empty_buf()) +} + +fn op_env(cli: &Cli, base: &msg::Base<'_>, data: deno_buf) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + + if let Err(e) = cli.check_env() { + return odd_future(e); + } + + let builder = &mut FlatBufferBuilder::new(); + let vars: Vec<_> = std::env::vars() + .map(|(key, value)| msg_util::serialize_key_value(builder, &key, &value)) + .collect(); + let tables = builder.create_vector(&vars); + let inner = msg::EnvironRes::create( + builder, + &msg::EnvironResArgs { map: Some(tables) }, + ); + ok_future(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::EnvironRes, + ..Default::default() + }, + )) +} + +fn op_permissions( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::PermissionsRes::create( + builder, + &msg::PermissionsResArgs { + run: cli.permissions.allows_run(), + read: cli.permissions.allows_read(), + write: cli.permissions.allows_write(), + net: cli.permissions.allows_net(), + env: cli.permissions.allows_env(), + }, + ); + ok_future(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::PermissionsRes, + ..Default::default() + }, + )) +} + +fn op_revoke_permission( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_permission_revoke().unwrap(); + let permission = inner.permission().unwrap(); + let result = match permission { + "run" => cli.permissions.revoke_run(), + "read" => cli.permissions.revoke_read(), + "write" => cli.permissions.revoke_write(), + "net" => cli.permissions.revoke_net(), + "env" => cli.permissions.revoke_env(), + _ => Ok(()), + }; + if let Err(e) = result { + return odd_future(e); + } + ok_future(empty_buf()) +} + +fn op_fetch( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + let inner = base.inner_as_fetch().unwrap(); + let cmd_id = base.cmd_id(); + + let header = inner.header().unwrap(); + assert!(header.is_request()); + let url = header.url().unwrap(); + + let body = if data.is_empty() { + hyper::Body::empty() + } else { + hyper::Body::from(Vec::from(&*data)) + }; + + let maybe_req = msg_util::deserialize_request(header, body); + if let Err(e) = maybe_req { + return odd_future(e); + } + let req = maybe_req.unwrap(); + + if let Err(e) = cli.check_net(url) { + return odd_future(e); + } + + let client = http_util::get_client(); + + debug!("Before fetch {}", url); + let future = + client + .request(req) + .map_err(DenoError::from) + .and_then(move |res| { + let builder = &mut FlatBufferBuilder::new(); + let header_off = msg_util::serialize_http_response(builder, &res); + let body = res.into_body(); + let body_resource = resources::add_hyper_body(body); + let inner = msg::FetchRes::create( + builder, + &msg::FetchResArgs { + header: Some(header_off), + body_rid: body_resource.rid, + }, + ); + + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::FetchRes, + ..Default::default() + }, + )) + }); + Box::new(future) +} + +// This is just type conversion. Implement From trait? +// See https://github.com/tokio-rs/tokio/blob/ffd73a64e7ec497622b7f939e38017afe7124dc4/tokio-fs/src/lib.rs#L76-L85 +fn convert_blocking(f: F) -> Poll +where + F: FnOnce() -> DenoResult, +{ + use futures::Async::*; + match tokio_threadpool::blocking(f) { + Ok(Ready(Ok(v))) => Ok(v.into()), + Ok(Ready(Err(err))) => Err(err), + Ok(NotReady) => Ok(NotReady), + Err(err) => panic!("blocking error {}", err), + } +} + +fn blocking(is_sync: bool, f: F) -> Box +where + F: 'static + Send + FnOnce() -> DenoResult, +{ + if is_sync { + Box::new(futures::future::result(f())) + } else { + Box::new(tokio_util::poll_fn(move || convert_blocking(f))) + } +} + +fn op_make_temp_dir( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let base = Box::new(*base); + let inner = base.inner_as_make_temp_dir().unwrap(); + let cmd_id = base.cmd_id(); + + // FIXME + if let Err(e) = cli.check_write("make_temp") { + return odd_future(e); + } + + let dir = inner.dir().map(PathBuf::from); + let prefix = inner.prefix().map(String::from); + let suffix = inner.suffix().map(String::from); + + blocking(base.sync(), move || -> OpResult { + // TODO(piscisaureus): use byte vector for paths, not a string. + // See https://github.com/denoland/deno/issues/627. + // We can't assume that paths are always valid utf8 strings. + let path = deno_fs::make_temp_dir( + // Converting Option to Option<&str> + dir.as_ref().map(|x| &**x), + prefix.as_ref().map(|x| &**x), + suffix.as_ref().map(|x| &**x), + )?; + let builder = &mut FlatBufferBuilder::new(); + let path_off = builder.create_string(path.to_str().unwrap()); + let inner = msg::MakeTempDirRes::create( + builder, + &msg::MakeTempDirResArgs { + path: Some(path_off), + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::MakeTempDirRes, + ..Default::default() + }, + )) + }) +} + +fn op_mkdir( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_mkdir().unwrap(); + let path = String::from(inner.path().unwrap()); + let recursive = inner.recursive(); + let mode = inner.mode(); + + if let Err(e) = cli.check_write(&path) { + return odd_future(e); + } + + blocking(base.sync(), move || { + debug!("op_mkdir {}", path); + deno_fs::mkdir(Path::new(&path), mode, recursive)?; + Ok(empty_buf()) + }) +} + +fn op_chmod( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_chmod().unwrap(); + let _mode = inner.mode(); + let path = String::from(inner.path().unwrap()); + + if let Err(e) = cli.check_write(&path) { + return odd_future(e); + } + + blocking(base.sync(), move || { + debug!("op_chmod {}", &path); + let path = PathBuf::from(&path); + // Still check file/dir exists on windows + let _metadata = fs::metadata(&path)?; + // Only work in unix + #[cfg(any(unix))] + { + // We need to use underscore to compile in Windows. + #[cfg_attr( + feature = "cargo-clippy", + allow(clippy::used_underscore_binding) + )] + let mut permissions = _metadata.permissions(); + #[cfg_attr( + feature = "cargo-clippy", + allow(clippy::used_underscore_binding) + )] + permissions.set_mode(_mode); + fs::set_permissions(&path, permissions)?; + } + Ok(empty_buf()) + }) +} + +fn op_open( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + let inner = base.inner_as_open().unwrap(); + let filename_str = inner.filename().unwrap(); + let filename = PathBuf::from(&filename_str); + let mode = inner.mode().unwrap(); + + let mut open_options = tokio::fs::OpenOptions::new(); + + match mode { + "r" => { + open_options.read(true); + } + "r+" => { + open_options.read(true).write(true); + } + "w" => { + open_options.create(true).write(true).truncate(true); + } + "w+" => { + open_options + .read(true) + .create(true) + .write(true) + .truncate(true); + } + "a" => { + open_options.create(true).append(true); + } + "a+" => { + open_options.read(true).create(true).append(true); + } + "x" => { + open_options.create_new(true).write(true); + } + "x+" => { + open_options.create_new(true).read(true).write(true); + } + &_ => { + panic!("Unknown file open mode."); + } + } + + match mode { + "r" => { + if let Err(e) = cli.check_read(&filename_str) { + return odd_future(e); + } + } + "w" | "a" | "x" => { + if let Err(e) = cli.check_write(&filename_str) { + return odd_future(e); + } + } + &_ => { + if let Err(e) = cli.check_read(&filename_str) { + return odd_future(e); + } + if let Err(e) = cli.check_write(&filename_str) { + return odd_future(e); + } + } + } + + let op = open_options + .open(filename) + .map_err(DenoError::from) + .and_then(move |fs_file| -> OpResult { + let resource = resources::add_fs_file(fs_file); + let builder = &mut FlatBufferBuilder::new(); + let inner = + msg::OpenRes::create(builder, &msg::OpenResArgs { rid: resource.rid }); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::OpenRes, + ..Default::default() + }, + )) + }); + Box::new(op) +} + +fn op_close( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_close().unwrap(); + let rid = inner.rid(); + match resources::lookup(rid) { + None => odd_future(errors::bad_resource()), + Some(resource) => { + resource.close(); + ok_future(empty_buf()) + } + } +} + +fn op_shutdown( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_shutdown().unwrap(); + let rid = inner.rid(); + let how = inner.how(); + match resources::lookup(rid) { + None => odd_future(errors::bad_resource()), + Some(mut resource) => { + let shutdown_mode = match how { + 0 => Shutdown::Read, + 1 => Shutdown::Write, + _ => unimplemented!(), + }; + blocking(base.sync(), move || { + // Use UFCS for disambiguation + Resource::shutdown(&mut resource, shutdown_mode)?; + Ok(empty_buf()) + }) + } + } +} + +fn op_read( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + let cmd_id = base.cmd_id(); + let inner = base.inner_as_read().unwrap(); + let rid = inner.rid(); + + match resources::lookup(rid) { + None => odd_future(errors::bad_resource()), + Some(resource) => { + let op = tokio::io::read(resource, data) + .map_err(DenoError::from) + .and_then(move |(_resource, _buf, nread)| { + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::ReadRes::create( + builder, + &msg::ReadResArgs { + nread: nread as u32, + eof: nread == 0, + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::ReadRes, + ..Default::default() + }, + )) + }); + Box::new(op) + } + } +} + +fn op_write( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + let cmd_id = base.cmd_id(); + let inner = base.inner_as_write().unwrap(); + let rid = inner.rid(); + + match resources::lookup(rid) { + None => odd_future(errors::bad_resource()), + Some(resource) => { + let op = tokio_write::write(resource, data) + .map_err(DenoError::from) + .and_then(move |(_resource, _buf, nwritten)| { + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::WriteRes::create( + builder, + &msg::WriteResArgs { + nbyte: nwritten as u32, + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::WriteRes, + ..Default::default() + }, + )) + }); + Box::new(op) + } + } +} + +fn op_seek( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let _cmd_id = base.cmd_id(); + let inner = base.inner_as_seek().unwrap(); + let rid = inner.rid(); + let offset = inner.offset(); + let whence = inner.whence(); + + match resources::lookup(rid) { + None => odd_future(errors::bad_resource()), + Some(resource) => { + let op = resources::seek(resource, offset, whence) + .and_then(move |_| Ok(empty_buf())); + Box::new(op) + } + } +} + +fn op_remove( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_remove().unwrap(); + let path_ = inner.path().unwrap(); + let path = PathBuf::from(path_); + let recursive = inner.recursive(); + + if let Err(e) = cli.check_write(path.to_str().unwrap()) { + return odd_future(e); + } + + blocking(base.sync(), move || { + debug!("op_remove {}", path.display()); + let metadata = fs::metadata(&path)?; + if metadata.is_file() { + fs::remove_file(&path)?; + } else if recursive { + remove_dir_all(&path)?; + } else { + fs::remove_dir(&path)?; + } + Ok(empty_buf()) + }) +} + +// Prototype https://github.com/denoland/deno/blob/golang/os.go#L171-L184 +fn op_read_file( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_read_file().unwrap(); + let cmd_id = base.cmd_id(); + let filename_ = inner.filename().unwrap(); + let filename = PathBuf::from(filename_); + debug!("op_read_file {}", filename.display()); + if let Err(e) = cli.check_read(&filename_) { + return odd_future(e); + } + blocking(base.sync(), move || { + let vec = fs::read(&filename)?; + // Build the response message. memcpy data into inner. + // TODO(ry) zero-copy. + let builder = &mut FlatBufferBuilder::new(); + let data_off = builder.create_vector(vec.as_slice()); + let inner = msg::ReadFileRes::create( + builder, + &msg::ReadFileResArgs { + data: Some(data_off), + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::ReadFileRes, + ..Default::default() + }, + )) + }) +} + +fn op_copy_file( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_copy_file().unwrap(); + let from_ = inner.from().unwrap(); + let from = PathBuf::from(from_); + let to_ = inner.to().unwrap(); + let to = PathBuf::from(to_); + + if let Err(e) = cli.check_read(&from_) { + return odd_future(e); + } + if let Err(e) = cli.check_write(&to_) { + return odd_future(e); + } + + debug!("op_copy_file {} {}", from.display(), to.display()); + blocking(base.sync(), move || { + // On *nix, Rust deem non-existent path as invalid input + // See https://github.com/rust-lang/rust/issues/54800 + // Once the issue is reolved, we should remove this workaround. + if cfg!(unix) && !from.is_file() { + return Err(errors::new( + ErrorKind::NotFound, + "File not found".to_string(), + )); + } + + fs::copy(&from, &to)?; + Ok(empty_buf()) + }) +} + +macro_rules! to_seconds { + ($time:expr) => {{ + // Unwrap is safe here as if the file is before the unix epoch + // something is very wrong. + $time + .and_then(|t| Ok(t.duration_since(UNIX_EPOCH).unwrap().as_secs())) + .unwrap_or(0) + }}; +} + +#[cfg(any(unix))] +fn get_mode(perm: &fs::Permissions) -> u32 { + perm.mode() +} + +#[cfg(not(any(unix)))] +fn get_mode(_perm: &fs::Permissions) -> u32 { + 0 +} + +fn op_cwd( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + Box::new(futures::future::result(|| -> OpResult { + let path = std::env::current_dir()?; + let builder = &mut FlatBufferBuilder::new(); + let cwd = + builder.create_string(&path.into_os_string().into_string().unwrap()); + let inner = + msg::CwdRes::create(builder, &msg::CwdResArgs { cwd: Some(cwd) }); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::CwdRes, + ..Default::default() + }, + )) + }())) +} + +fn op_stat( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_stat().unwrap(); + let cmd_id = base.cmd_id(); + let filename_ = inner.filename().unwrap(); + let filename = PathBuf::from(filename_); + let lstat = inner.lstat(); + + if let Err(e) = cli.check_read(&filename_) { + return odd_future(e); + } + + blocking(base.sync(), move || { + let builder = &mut FlatBufferBuilder::new(); + debug!("op_stat {} {}", filename.display(), lstat); + let metadata = if lstat { + fs::symlink_metadata(&filename)? + } else { + fs::metadata(&filename)? + }; + + let inner = msg::StatRes::create( + builder, + &msg::StatResArgs { + is_file: metadata.is_file(), + is_symlink: metadata.file_type().is_symlink(), + len: metadata.len(), + modified: to_seconds!(metadata.modified()), + accessed: to_seconds!(metadata.accessed()), + created: to_seconds!(metadata.created()), + mode: get_mode(&metadata.permissions()), + has_mode: cfg!(target_family = "unix"), + ..Default::default() + }, + ); + + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::StatRes, + ..Default::default() + }, + )) + }) +} + +fn op_read_dir( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_read_dir().unwrap(); + let cmd_id = base.cmd_id(); + let path = String::from(inner.path().unwrap()); + + if let Err(e) = cli.check_read(&path) { + return odd_future(e); + } + + blocking(base.sync(), move || -> OpResult { + debug!("op_read_dir {}", path); + let builder = &mut FlatBufferBuilder::new(); + let entries: Vec<_> = fs::read_dir(Path::new(&path))? + .map(|entry| { + let entry = entry.unwrap(); + let metadata = entry.metadata().unwrap(); + let file_type = metadata.file_type(); + let name = builder.create_string(entry.file_name().to_str().unwrap()); + let path = builder.create_string(entry.path().to_str().unwrap()); + + msg::StatRes::create( + builder, + &msg::StatResArgs { + is_file: file_type.is_file(), + is_symlink: file_type.is_symlink(), + len: metadata.len(), + modified: to_seconds!(metadata.modified()), + accessed: to_seconds!(metadata.accessed()), + created: to_seconds!(metadata.created()), + name: Some(name), + path: Some(path), + mode: get_mode(&metadata.permissions()), + has_mode: cfg!(target_family = "unix"), + }, + ) + }).collect(); + + let entries = builder.create_vector(&entries); + let inner = msg::ReadDirRes::create( + builder, + &msg::ReadDirResArgs { + entries: Some(entries), + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::ReadDirRes, + ..Default::default() + }, + )) + }) +} + +fn op_write_file( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + let inner = base.inner_as_write_file().unwrap(); + let filename = String::from(inner.filename().unwrap()); + let update_perm = inner.update_perm(); + let perm = inner.perm(); + let is_create = inner.is_create(); + let is_append = inner.is_append(); + + if let Err(e) = cli.check_write(&filename) { + return odd_future(e); + } + + blocking(base.sync(), move || -> OpResult { + debug!("op_write_file {} {}", filename, data.len()); + deno_fs::write_file_2( + Path::new(&filename), + data, + update_perm, + perm, + is_create, + is_append, + )?; + Ok(empty_buf()) + }) +} + +fn op_rename( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_rename().unwrap(); + let oldpath = PathBuf::from(inner.oldpath().unwrap()); + let newpath_ = inner.newpath().unwrap(); + let newpath = PathBuf::from(newpath_); + if let Err(e) = cli.check_write(&newpath_) { + return odd_future(e); + } + blocking(base.sync(), move || -> OpResult { + debug!("op_rename {} {}", oldpath.display(), newpath.display()); + fs::rename(&oldpath, &newpath)?; + Ok(empty_buf()) + }) +} + +fn op_symlink( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_symlink().unwrap(); + let oldname = PathBuf::from(inner.oldname().unwrap()); + let newname_ = inner.newname().unwrap(); + let newname = PathBuf::from(newname_); + + if let Err(e) = cli.check_write(&newname_) { + return odd_future(e); + } + // TODO Use type for Windows. + if cfg!(windows) { + return odd_future(errors::new( + ErrorKind::Other, + "Not implemented".to_string(), + )); + } + blocking(base.sync(), move || -> OpResult { + debug!("op_symlink {} {}", oldname.display(), newname.display()); + #[cfg(any(unix))] + std::os::unix::fs::symlink(&oldname, &newname)?; + Ok(empty_buf()) + }) +} + +fn op_read_link( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_readlink().unwrap(); + let cmd_id = base.cmd_id(); + let name_ = inner.name().unwrap(); + let name = PathBuf::from(name_); + + if let Err(e) = cli.check_read(&name_) { + return odd_future(e); + } + + blocking(base.sync(), move || -> OpResult { + debug!("op_read_link {}", name.display()); + let path = fs::read_link(&name)?; + let builder = &mut FlatBufferBuilder::new(); + let path_off = builder.create_string(path.to_str().unwrap()); + let inner = msg::ReadlinkRes::create( + builder, + &msg::ReadlinkResArgs { + path: Some(path_off), + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::ReadlinkRes, + ..Default::default() + }, + )) + }) +} + +fn op_repl_start( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_repl_start().unwrap(); + let cmd_id = base.cmd_id(); + let history_file = String::from(inner.history_file().unwrap()); + + debug!("op_repl_start {}", history_file); + let history_path = repl::history_path(&cli.state.dir, &history_file); + let repl = repl::Repl::new(history_path); + let resource = resources::add_repl(repl); + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::ReplStartRes::create( + builder, + &msg::ReplStartResArgs { rid: resource.rid }, + ); + ok_future(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::ReplStartRes, + ..Default::default() + }, + )) +} + +fn op_repl_readline( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_repl_readline().unwrap(); + let cmd_id = base.cmd_id(); + let rid = inner.rid(); + let prompt = inner.prompt().unwrap().to_owned(); + debug!("op_repl_readline {} {}", rid, prompt); + + blocking(base.sync(), move || -> OpResult { + let repl = resources::get_repl(rid)?; + let line = repl.lock().unwrap().readline(&prompt)?; + + let builder = &mut FlatBufferBuilder::new(); + let line_off = builder.create_string(&line); + let inner = msg::ReplReadlineRes::create( + builder, + &msg::ReplReadlineResArgs { + line: Some(line_off), + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::ReplReadlineRes, + ..Default::default() + }, + )) + }) +} + +fn op_truncate( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + + let inner = base.inner_as_truncate().unwrap(); + let filename = String::from(inner.name().unwrap()); + let len = inner.len(); + + if let Err(e) = cli.check_write(&filename) { + return odd_future(e); + } + + blocking(base.sync(), move || { + debug!("op_truncate {} {}", filename, len); + let f = fs::OpenOptions::new().write(true).open(&filename)?; + f.set_len(u64::from(len))?; + Ok(empty_buf()) + }) +} + +fn op_listen( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + if let Err(e) = cli.check_net("listen") { + return odd_future(e); + } + + let cmd_id = base.cmd_id(); + let inner = base.inner_as_listen().unwrap(); + let network = inner.network().unwrap(); + assert_eq!(network, "tcp"); + let address = inner.address().unwrap(); + + Box::new(futures::future::result((move || { + let addr = resolve_addr(address).wait()?; + + let listener = TcpListener::bind(&addr)?; + let resource = resources::add_tcp_listener(listener); + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::ListenRes::create( + builder, + &msg::ListenResArgs { rid: resource.rid }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::ListenRes, + ..Default::default() + }, + )) + })())) +} + +fn new_conn(cmd_id: u32, tcp_stream: TcpStream) -> OpResult { + let tcp_stream_resource = resources::add_tcp_stream(tcp_stream); + // TODO forward socket_addr to client. + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::NewConn::create( + builder, + &msg::NewConnArgs { + rid: tcp_stream_resource.rid, + ..Default::default() + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::NewConn, + ..Default::default() + }, + )) +} + +fn op_accept( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + if let Err(e) = cli.check_net("accept") { + return odd_future(e); + } + let cmd_id = base.cmd_id(); + let inner = base.inner_as_accept().unwrap(); + let server_rid = inner.rid(); + + match resources::lookup(server_rid) { + None => odd_future(errors::bad_resource()), + Some(server_resource) => { + let op = tokio_util::accept(server_resource) + .map_err(DenoError::from) + .and_then(move |(tcp_stream, _socket_addr)| { + new_conn(cmd_id, tcp_stream) + }); + Box::new(op) + } + } +} + +fn op_dial( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + if let Err(e) = cli.check_net("dial") { + return odd_future(e); + } + let cmd_id = base.cmd_id(); + let inner = base.inner_as_dial().unwrap(); + let network = inner.network().unwrap(); + assert_eq!(network, "tcp"); // TODO Support others. + let address = inner.address().unwrap(); + + let op = + resolve_addr(address) + .map_err(DenoError::from) + .and_then(move |addr| { + TcpStream::connect(&addr) + .map_err(DenoError::from) + .and_then(move |tcp_stream| new_conn(cmd_id, tcp_stream)) + }); + Box::new(op) +} + +fn op_metrics( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::MetricsRes::create( + builder, + &msg::MetricsResArgs::from(&cli.state.metrics), + ); + ok_future(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::MetricsRes, + ..Default::default() + }, + )) +} + +fn op_resources( + _cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + + let builder = &mut FlatBufferBuilder::new(); + let serialized_resources = table_entries(); + + let res: Vec<_> = serialized_resources + .iter() + .map(|(key, value)| { + let repr = builder.create_string(value); + + msg::Resource::create( + builder, + &msg::ResourceArgs { + rid: *key, + repr: Some(repr), + }, + ) + }).collect(); + + let resources = builder.create_vector(&res); + let inner = msg::ResourcesRes::create( + builder, + &msg::ResourcesResArgs { + resources: Some(resources), + }, + ); + + ok_future(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::ResourcesRes, + ..Default::default() + }, + )) +} + +fn subprocess_stdio_map(v: msg::ProcessStdio) -> std::process::Stdio { + match v { + msg::ProcessStdio::Inherit => std::process::Stdio::inherit(), + msg::ProcessStdio::Piped => std::process::Stdio::piped(), + msg::ProcessStdio::Null => std::process::Stdio::null(), + } +} + +fn op_run(cli: &Cli, base: &msg::Base<'_>, data: deno_buf) -> Box { + assert!(base.sync()); + let cmd_id = base.cmd_id(); + + if let Err(e) = cli.check_run() { + return odd_future(e); + } + + assert_eq!(data.len(), 0); + let inner = base.inner_as_run().unwrap(); + let args = inner.args().unwrap(); + let env = inner.env().unwrap(); + let cwd = inner.cwd(); + + let mut c = Command::new(args.get(0)); + (1..args.len()).for_each(|i| { + let arg = args.get(i); + c.arg(arg); + }); + cwd.map(|d| c.current_dir(d)); + (0..env.len()).for_each(|i| { + let entry = env.get(i); + c.env(entry.key().unwrap(), entry.value().unwrap()); + }); + + c.stdin(subprocess_stdio_map(inner.stdin())); + c.stdout(subprocess_stdio_map(inner.stdout())); + c.stderr(subprocess_stdio_map(inner.stderr())); + + // Spawn the command. + let child = match c.spawn_async() { + Ok(v) => v, + Err(err) => { + return odd_future(err.into()); + } + }; + + let pid = child.id(); + let resources = resources::add_child(child); + + let mut res_args = msg::RunResArgs { + rid: resources.child_rid, + pid, + ..Default::default() + }; + + if let Some(stdin_rid) = resources.stdin_rid { + res_args.stdin_rid = stdin_rid; + } + if let Some(stdout_rid) = resources.stdout_rid { + res_args.stdout_rid = stdout_rid; + } + if let Some(stderr_rid) = resources.stderr_rid { + res_args.stderr_rid = stderr_rid; + } + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::RunRes::create(builder, &res_args); + ok_future(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::RunRes, + ..Default::default() + }, + )) +} + +fn op_run_status( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + let inner = base.inner_as_run_status().unwrap(); + let rid = inner.rid(); + + if let Err(e) = cli.check_run() { + return odd_future(e); + } + + let future = match resources::child_status(rid) { + Err(e) => { + return odd_future(e); + } + Ok(f) => f, + }; + + let future = future.and_then(move |run_status| { + let code = run_status.code(); + + #[cfg(unix)] + let signal = run_status.signal(); + #[cfg(not(unix))] + let signal = None; + + code + .or(signal) + .expect("Should have either an exit code or a signal."); + let got_signal = signal.is_some(); + + let builder = &mut FlatBufferBuilder::new(); + let inner = msg::RunStatusRes::create( + builder, + &msg::RunStatusResArgs { + got_signal, + exit_code: code.unwrap_or(-1), + exit_signal: signal.unwrap_or(-1), + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::RunStatusRes, + ..Default::default() + }, + )) + }); + Box::new(future) +} + +struct GetMessageFuture { + pub state: Arc, +} + +impl Future for GetMessageFuture { + type Item = Option; + type Error = (); + + fn poll(&mut self) -> Result, Self::Error> { + assert!(self.state.worker_channels.is_some()); + match self.state.worker_channels { + None => panic!("expected worker_channels"), + Some(ref wc) => { + let mut wc = wc.lock().unwrap(); + wc.1.poll() + } + } + } +} + +fn op_worker_get_message( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + + let op = GetMessageFuture { + state: cli.state.clone(), + }; + let op = op.map_err(move |_| -> DenoError { unimplemented!() }); + let op = op.and_then(move |maybe_buf| -> DenoResult { + debug!("op_worker_get_message"); + let builder = &mut FlatBufferBuilder::new(); + + let data = maybe_buf.as_ref().map(|buf| builder.create_vector(buf)); + let inner = msg::WorkerGetMessageRes::create( + builder, + &msg::WorkerGetMessageResArgs { data }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::WorkerGetMessageRes, + ..Default::default() + }, + )) + }); + Box::new(op) +} + +fn op_worker_post_message( + cli: &Cli, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box { + let cmd_id = base.cmd_id(); + + let d = Vec::from(data.as_ref()).into_boxed_slice(); + + assert!(cli.state.worker_channels.is_some()); + let tx = match cli.state.worker_channels { + None => panic!("expected worker_channels"), + Some(ref wc) => { + let wc = wc.lock().unwrap(); + wc.0.clone() + } + }; + let op = tx.send(d); + let op = op.map_err(|e| errors::new(ErrorKind::Other, e.to_string())); + let op = op.and_then(move |_| -> DenoResult { + let builder = &mut FlatBufferBuilder::new(); + + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + ..Default::default() + }, + )) + }); + Box::new(op) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::cli::Cli; + use crate::isolate_state::IsolateState; + use crate::permissions::{DenoPermissions, PermissionAccessor}; + + #[test] + fn fetch_module_meta_fails_without_read() { + let state = Arc::new(IsolateState::mock()); + let permissions = DenoPermissions { + allow_write: PermissionAccessor::from(true), + allow_env: PermissionAccessor::from(true), + allow_net: PermissionAccessor::from(true), + allow_run: PermissionAccessor::from(true), + ..Default::default() + }; + let cli = Cli::new(None, state, permissions); + let builder = &mut FlatBufferBuilder::new(); + let fetch_msg_args = msg::FetchModuleMetaDataArgs { + specifier: Some(builder.create_string("./somefile")), + referrer: Some(builder.create_string(".")), + }; + let inner = msg::FetchModuleMetaData::create(builder, &fetch_msg_args); + let base_args = msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::FetchModuleMetaData, + ..Default::default() + }; + let base = msg::Base::create(builder, &base_args); + msg::finish_base_buffer(builder, base); + let data = builder.finished_data(); + let final_msg = msg::get_root_as_base(&data); + let fetch_result = + op_fetch_module_meta_data(&cli, &final_msg, deno_buf::empty()).wait(); + match fetch_result { + Ok(_) => assert!(true), + Err(e) => assert_eq!(e.to_string(), permission_denied().to_string()), + } + } + + #[test] + fn fetch_module_meta_fails_without_write() { + let state = Arc::new(IsolateState::mock()); + let permissions = DenoPermissions { + allow_read: PermissionAccessor::from(true), + allow_env: PermissionAccessor::from(true), + allow_net: PermissionAccessor::from(true), + allow_run: PermissionAccessor::from(true), + ..Default::default() + }; + let cli = Cli::new(None, state, permissions); + let builder = &mut FlatBufferBuilder::new(); + let fetch_msg_args = msg::FetchModuleMetaDataArgs { + specifier: Some(builder.create_string("./somefile")), + referrer: Some(builder.create_string(".")), + }; + let inner = msg::FetchModuleMetaData::create(builder, &fetch_msg_args); + let base_args = msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::FetchModuleMetaData, + ..Default::default() + }; + let base = msg::Base::create(builder, &base_args); + msg::finish_base_buffer(builder, base); + let data = builder.finished_data(); + let final_msg = msg::get_root_as_base(&data); + let fetch_result = + op_fetch_module_meta_data(&cli, &final_msg, deno_buf::empty()).wait(); + match fetch_result { + Ok(_) => assert!(true), + Err(e) => assert_eq!(e.to_string(), permission_denied().to_string()), + } + } + + #[test] + fn fetch_module_meta_fails_without_net() { + let state = Arc::new(IsolateState::mock()); + let permissions = DenoPermissions { + allow_read: PermissionAccessor::from(true), + allow_write: PermissionAccessor::from(true), + allow_env: PermissionAccessor::from(true), + allow_run: PermissionAccessor::from(true), + ..Default::default() + }; + let cli = Cli::new(None, state, permissions); + let builder = &mut FlatBufferBuilder::new(); + let fetch_msg_args = msg::FetchModuleMetaDataArgs { + specifier: Some(builder.create_string("./somefile")), + referrer: Some(builder.create_string(".")), + }; + let inner = msg::FetchModuleMetaData::create(builder, &fetch_msg_args); + let base_args = msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::FetchModuleMetaData, + ..Default::default() + }; + let base = msg::Base::create(builder, &base_args); + msg::finish_base_buffer(builder, base); + let data = builder.finished_data(); + let final_msg = msg::get_root_as_base(&data); + let fetch_result = + op_fetch_module_meta_data(&cli, &final_msg, deno_buf::empty()).wait(); + match fetch_result { + Ok(_) => assert!(true), + Err(e) => assert_eq!(e.to_string(), permission_denied().to_string()), + } + } + + #[test] + fn fetch_module_meta_not_permission_denied_with_permissions() { + let state = Arc::new(IsolateState::mock()); + let permissions = DenoPermissions { + allow_read: PermissionAccessor::from(true), + allow_write: PermissionAccessor::from(true), + allow_net: PermissionAccessor::from(true), + ..Default::default() + }; + let cli = Cli::new(None, state, permissions); + let builder = &mut FlatBufferBuilder::new(); + let fetch_msg_args = msg::FetchModuleMetaDataArgs { + specifier: Some(builder.create_string("./somefile")), + referrer: Some(builder.create_string(".")), + }; + let inner = msg::FetchModuleMetaData::create(builder, &fetch_msg_args); + let base_args = msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::FetchModuleMetaData, + ..Default::default() + }; + let base = msg::Base::create(builder, &base_args); + msg::finish_base_buffer(builder, base); + let data = builder.finished_data(); + let final_msg = msg::get_root_as_base(&data); + let fetch_result = + op_fetch_module_meta_data(&cli, &final_msg, deno_buf::empty()).wait(); + match fetch_result { + Ok(_) => assert!(true), + Err(e) => assert!(e.to_string() != permission_denied().to_string()), + } + } +} diff --git a/cli/permissions.rs b/cli/permissions.rs new file mode 100644 index 000000000..9093c14f0 --- /dev/null +++ b/cli/permissions.rs @@ -0,0 +1,343 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use atty; + +use crate::flags::DenoFlags; + +use ansi_term::Style; +use crate::errors::permission_denied; +use crate::errors::DenoResult; +use std::fmt; +use std::io; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; + +/// Tri-state value for storing permission state +pub enum PermissionAccessorState { + Allow = 0, + Ask = 1, + Deny = 2, +} + +impl From for PermissionAccessorState { + fn from(val: usize) -> Self { + match val { + 0 => PermissionAccessorState::Allow, + 1 => PermissionAccessorState::Ask, + 2 => PermissionAccessorState::Deny, + _ => unreachable!(), + } + } +} + +impl From for PermissionAccessorState { + fn from(val: bool) -> Self { + match val { + true => PermissionAccessorState::Allow, + false => PermissionAccessorState::Ask, + } + } +} + +impl fmt::Display for PermissionAccessorState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PermissionAccessorState::Allow => f.pad("Allow"), + PermissionAccessorState::Ask => f.pad("Ask"), + PermissionAccessorState::Deny => f.pad("Deny"), + } + } +} + +#[derive(Debug)] +pub struct PermissionAccessor { + state: Arc, +} + +impl PermissionAccessor { + pub fn new(state: PermissionAccessorState) -> Self { + Self { + state: Arc::new(AtomicUsize::new(state as usize)), + } + } + + pub fn is_allow(&self) -> bool { + match self.get_state() { + PermissionAccessorState::Allow => true, + _ => false, + } + } + + /// If the state is "Allow" walk it back to the default "Ask" + /// Don't do anything if state is "Deny" + pub fn revoke(&self) { + if self.is_allow() { + self.ask(); + } + } + + pub fn allow(&self) { + self.set_state(PermissionAccessorState::Allow) + } + + pub fn ask(&self) { + self.set_state(PermissionAccessorState::Ask) + } + + pub fn deny(&self) { + self.set_state(PermissionAccessorState::Deny) + } + + /// Update this accessors state based on a PromptResult value + /// This will only update the state if the PromptResult value + /// is one of the "Always" values + pub fn update_with_prompt_result(&self, prompt_result: &PromptResult) { + match prompt_result { + PromptResult::AllowAlways => self.allow(), + PromptResult::DenyAlways => self.deny(), + _ => {} + } + } + + #[inline] + pub fn get_state(&self) -> PermissionAccessorState { + self.state.load(Ordering::SeqCst).into() + } + fn set_state(&self, state: PermissionAccessorState) { + self.state.store(state as usize, Ordering::SeqCst) + } +} + +impl From for PermissionAccessor { + fn from(val: bool) -> Self { + Self::new(PermissionAccessorState::from(val)) + } +} + +impl Default for PermissionAccessor { + fn default() -> Self { + Self { + state: Arc::new(AtomicUsize::new(PermissionAccessorState::Ask as usize)), + } + } +} + +#[cfg_attr(feature = "cargo-clippy", allow(stutter))] +#[derive(Debug, Default)] +pub struct DenoPermissions { + // Keep in sync with src/permissions.ts + pub allow_read: PermissionAccessor, + pub allow_write: PermissionAccessor, + pub allow_net: PermissionAccessor, + pub allow_env: PermissionAccessor, + pub allow_run: PermissionAccessor, + pub no_prompts: AtomicBool, +} + +impl DenoPermissions { + pub fn from_flags(flags: &DenoFlags) -> Self { + Self { + allow_read: PermissionAccessor::from(flags.allow_read), + allow_write: PermissionAccessor::from(flags.allow_write), + allow_env: PermissionAccessor::from(flags.allow_env), + allow_net: PermissionAccessor::from(flags.allow_net), + allow_run: PermissionAccessor::from(flags.allow_run), + no_prompts: AtomicBool::new(flags.no_prompts), + } + } + + pub fn check_run(&self) -> DenoResult<()> { + match self.allow_run.get_state() { + PermissionAccessorState::Allow => Ok(()), + PermissionAccessorState::Ask => { + match self.try_permissions_prompt("access to run a subprocess") { + Err(e) => Err(e), + Ok(v) => { + self.allow_run.update_with_prompt_result(&v); + v.check()?; + Ok(()) + } + } + } + PermissionAccessorState::Deny => Err(permission_denied()), + } + } + + pub fn check_read(&self, filename: &str) -> DenoResult<()> { + match self.allow_read.get_state() { + PermissionAccessorState::Allow => Ok(()), + PermissionAccessorState::Ask => match self + .try_permissions_prompt(&format!("read access to \"{}\"", filename)) + { + Err(e) => Err(e), + Ok(v) => { + self.allow_read.update_with_prompt_result(&v); + v.check()?; + Ok(()) + } + }, + PermissionAccessorState::Deny => Err(permission_denied()), + } + } + + pub fn check_write(&self, filename: &str) -> DenoResult<()> { + match self.allow_write.get_state() { + PermissionAccessorState::Allow => Ok(()), + PermissionAccessorState::Ask => match self + .try_permissions_prompt(&format!("write access to \"{}\"", filename)) + { + Err(e) => Err(e), + Ok(v) => { + self.allow_write.update_with_prompt_result(&v); + v.check()?; + Ok(()) + } + }, + PermissionAccessorState::Deny => Err(permission_denied()), + } + } + + pub fn check_net(&self, domain_name: &str) -> DenoResult<()> { + match self.allow_net.get_state() { + PermissionAccessorState::Allow => Ok(()), + PermissionAccessorState::Ask => match self.try_permissions_prompt( + &format!("network access to \"{}\"", domain_name), + ) { + Err(e) => Err(e), + Ok(v) => { + self.allow_net.update_with_prompt_result(&v); + v.check()?; + Ok(()) + } + }, + PermissionAccessorState::Deny => Err(permission_denied()), + } + } + + pub fn check_env(&self) -> DenoResult<()> { + match self.allow_env.get_state() { + PermissionAccessorState::Allow => Ok(()), + PermissionAccessorState::Ask => { + match self.try_permissions_prompt("access to environment variables") { + Err(e) => Err(e), + Ok(v) => { + self.allow_env.update_with_prompt_result(&v); + v.check()?; + Ok(()) + } + } + } + PermissionAccessorState::Deny => Err(permission_denied()), + } + } + + /// Try to present the user with a permission prompt + /// will error with permission_denied if no_prompts is enabled + fn try_permissions_prompt(&self, message: &str) -> DenoResult { + if self.no_prompts.load(Ordering::SeqCst) { + return Err(permission_denied()); + } + if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) { + return Err(permission_denied()); + }; + permission_prompt(message) + } + + pub fn allows_run(&self) -> bool { + return self.allow_run.is_allow(); + } + + pub fn allows_read(&self) -> bool { + return self.allow_read.is_allow(); + } + + pub fn allows_write(&self) -> bool { + return self.allow_write.is_allow(); + } + + pub fn allows_net(&self) -> bool { + return self.allow_net.is_allow(); + } + + pub fn allows_env(&self) -> bool { + return self.allow_env.is_allow(); + } + + pub fn revoke_run(&self) -> DenoResult<()> { + self.allow_run.revoke(); + return Ok(()); + } + + pub fn revoke_read(&self) -> DenoResult<()> { + self.allow_read.revoke(); + return Ok(()); + } + + pub fn revoke_write(&self) -> DenoResult<()> { + self.allow_write.revoke(); + return Ok(()); + } + + pub fn revoke_net(&self) -> DenoResult<()> { + self.allow_net.revoke(); + return Ok(()); + } + + pub fn revoke_env(&self) -> DenoResult<()> { + self.allow_env.revoke(); + return Ok(()); + } +} + +/// Quad-state value for representing user input on permission prompt +#[derive(Debug, Clone)] +pub enum PromptResult { + AllowAlways = 0, + AllowOnce = 1, + DenyOnce = 2, + DenyAlways = 3, +} + +impl PromptResult { + /// If value is any form of deny this will error with permission_denied + pub fn check(&self) -> DenoResult<()> { + match self { + PromptResult::DenyOnce => Err(permission_denied()), + PromptResult::DenyAlways => Err(permission_denied()), + _ => Ok(()), + } + } +} + +impl fmt::Display for PromptResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PromptResult::AllowAlways => f.pad("AllowAlways"), + PromptResult::AllowOnce => f.pad("AllowOnce"), + PromptResult::DenyOnce => f.pad("DenyOnce"), + PromptResult::DenyAlways => f.pad("DenyAlways"), + } + } +} + +fn permission_prompt(message: &str) -> DenoResult { + let msg = format!("⚠️ Deno requests {}. Grant? [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", message); + // print to stderr so that if deno is > to a file this is still displayed. + eprint!("{}", Style::new().bold().paint(msg)); + loop { + let mut input = String::new(); + let stdin = io::stdin(); + let _nread = stdin.read_line(&mut input)?; + let ch = input.chars().next().unwrap(); + match ch.to_ascii_lowercase() { + 'a' => return Ok(PromptResult::AllowAlways), + 'y' => return Ok(PromptResult::AllowOnce), + 'n' => return Ok(PromptResult::DenyOnce), + 'd' => return Ok(PromptResult::DenyAlways), + _ => { + // If we don't get a recognized option try again. + let msg_again = format!("Unrecognized option '{}' [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", ch); + eprint!("{}", Style::new().bold().paint(msg_again)); + } + }; + } +} diff --git a/cli/repl.rs b/cli/repl.rs new file mode 100644 index 000000000..55bf4a114 --- /dev/null +++ b/cli/repl.rs @@ -0,0 +1,114 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use rustyline; + +use crate::msg::ErrorKind; +use std::error::Error; + +use crate::deno_dir::DenoDir; +use crate::errors::new as deno_error; +use crate::errors::DenoResult; +use std::path::PathBuf; + +#[cfg(not(windows))] +use rustyline::Editor; + +// Work around the issue that on Windows, `struct Editor` does not implement the +// `Send` trait, because it embeds a windows HANDLE which is a type alias for +// *mut c_void. This value isn't actually a pointer and there's nothing that +// can be mutated through it, so hack around it. TODO: a prettier solution. +#[cfg(windows)] +use std::ops::{Deref, DerefMut}; + +#[cfg(windows)] +struct Editor { + inner: rustyline::Editor, +} + +#[cfg(windows)] +unsafe impl Send for Editor {} + +#[cfg(windows)] +impl Editor { + pub fn new() -> Editor { + Editor { + inner: rustyline::Editor::::new(), + } + } +} + +#[cfg(windows)] +impl Deref for Editor { + type Target = rustyline::Editor; + + fn deref(&self) -> &rustyline::Editor { + &self.inner + } +} + +#[cfg(windows)] +impl DerefMut for Editor { + fn deref_mut(&mut self) -> &mut rustyline::Editor { + &mut self.inner + } +} + +pub struct Repl { + editor: Editor<()>, + history_file: PathBuf, +} + +impl Repl { + pub fn new(history_file: PathBuf) -> Self { + let mut repl = Self { + editor: Editor::<()>::new(), + history_file, + }; + + repl.load_history(); + repl + } + + fn load_history(&mut self) { + debug!("Loading REPL history: {:?}", self.history_file); + self + .editor + .load_history(&self.history_file.to_str().unwrap()) + .map_err(|e| debug!("Unable to load history file: {:?} {}", self.history_file, e)) + // ignore this error (e.g. it occurs on first load) + .unwrap_or(()) + } + + fn save_history(&mut self) -> DenoResult<()> { + self + .editor + .save_history(&self.history_file.to_str().unwrap()) + .map(|_| debug!("Saved REPL history to: {:?}", self.history_file)) + .map_err(|e| { + eprintln!("Unable to save REPL history: {:?} {}", self.history_file, e); + deno_error(ErrorKind::Other, e.description().to_string()) + }) + } + + pub fn readline(&mut self, prompt: &str) -> DenoResult { + self + .editor + .readline(&prompt) + .map(|line| { + self.editor.add_history_entry(line.as_ref()); + line + }).map_err(|e| deno_error(ErrorKind::Other, e.description().to_string())) + // Forward error to TS side for processing + } +} + +impl Drop for Repl { + fn drop(&mut self) { + self.save_history().unwrap(); + } +} + +pub fn history_path(dir: &DenoDir, history_file: &str) -> PathBuf { + let mut p: PathBuf = dir.root.clone(); + p.push(history_file); + p +} diff --git a/cli/resolve_addr.rs b/cli/resolve_addr.rs new file mode 100644 index 000000000..f26655be1 --- /dev/null +++ b/cli/resolve_addr.rs @@ -0,0 +1,156 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +use futures::Async; +use futures::Future; +use futures::Poll; +use std::error::Error; +use std::fmt; +use std::net::SocketAddr; +use std::net::ToSocketAddrs; + +/// Go-style network address parsing. Returns a future. +/// Examples: +/// "192.0.2.1:25" +/// ":80" +/// "[2001:db8::1]:80" +/// "198.51.100.1:80" +/// "deno.land:443" +pub fn resolve_addr(address: &str) -> ResolveAddrFuture { + ResolveAddrFuture { + address: address.to_string(), + } +} + +#[derive(Debug)] +pub enum ResolveAddrError { + Syntax, + Resolution(std::io::Error), +} + +impl fmt::Display for ResolveAddrError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str(self.description()) + } +} + +impl Error for ResolveAddrError { + fn description(&self) -> &str { + match self { + ResolveAddrError::Syntax => "invalid address syntax", + ResolveAddrError::Resolution(e) => e.description(), + } + } +} + +pub struct ResolveAddrFuture { + address: String, +} + +impl Future for ResolveAddrFuture { + type Item = SocketAddr; + type Error = ResolveAddrError; + + fn poll(&mut self) -> Poll { + // The implementation of this is not actually async at the moment, + // however we intend to use async DNS resolution in the future and + // so we expose this as a future instead of Result. + match split(&self.address) { + None => Err(ResolveAddrError::Syntax), + Some(addr_port_pair) => { + // I absolutely despise the .to_socket_addrs() API. + let r = addr_port_pair + .to_socket_addrs() + .map_err(ResolveAddrError::Resolution); + + r.and_then(|mut iter| match iter.next() { + Some(a) => Ok(Async::Ready(a)), + None => panic!("There should be at least one result"), + }) + } + } + } +} + +fn split(address: &str) -> Option<(&str, u16)> { + address.rfind(':').and_then(|i| { + let (a, p) = address.split_at(i); + // Default to localhost if given just the port. Example: ":80" + let addr = if !a.is_empty() { a } else { "0.0.0.0" }; + // If this looks like an ipv6 IP address. Example: "[2001:db8::1]" + // Then we remove the brackets. + let addr = if addr.starts_with('[') && addr.ends_with(']') { + let l = addr.len() - 1; + addr.get(1..l).unwrap() + } else { + addr + }; + + let p = p.trim_start_matches(':'); + match p.parse::() { + Err(_) => None, + Ok(port) => Some((addr, port)), + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::Ipv4Addr; + use std::net::Ipv6Addr; + use std::net::SocketAddrV4; + use std::net::SocketAddrV6; + + #[test] + fn split1() { + assert_eq!(split("127.0.0.1:80"), Some(("127.0.0.1", 80))); + } + + #[test] + fn split2() { + assert_eq!(split(":80"), Some(("0.0.0.0", 80))); + } + + #[test] + fn split3() { + assert_eq!(split("no colon"), None); + } + + #[test] + fn split4() { + assert_eq!(split("deno.land:443"), Some(("deno.land", 443))); + } + + #[test] + fn split5() { + assert_eq!(split("[2001:db8::1]:8080"), Some(("2001:db8::1", 8080))); + } + + #[test] + fn resolve_addr1() { + let expected = + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80)); + let actual = resolve_addr("127.0.0.1:80").wait().unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn resolve_addr3() { + let expected = + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25)); + let actual = resolve_addr("192.0.2.1:25").wait().unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn resolve_addr_ipv6() { + let expected = SocketAddr::V6(SocketAddrV6::new( + Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), + 8080, + 0, + 0, + )); + let actual = resolve_addr("[2001:db8::1]:8080").wait().unwrap(); + assert_eq!(actual, expected); + } +} diff --git a/cli/resources.rs b/cli/resources.rs new file mode 100644 index 000000000..1540f4ff7 --- /dev/null +++ b/cli/resources.rs @@ -0,0 +1,494 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +// Think of Resources as File Descriptors. They are integers that are allocated +// by the privileged side of Deno to refer to various resources. The simplest +// example are standard file system files and stdio - but there will be other +// resources added in the future that might not correspond to operating system +// level File Descriptors. To avoid confusion we call them "resources" not "file +// descriptors". This module implements a global resource table. Ops (AKA +// handlers) look up resources by their integer id here. + +use crate::cli::Buf; +use crate::errors; +use crate::errors::bad_resource; +use crate::errors::DenoError; +use crate::errors::DenoResult; +use crate::http_body::HttpBody; +use crate::isolate_state::WorkerChannels; +use crate::repl::Repl; + +use futures; +use futures::Future; +use futures::Poll; +use futures::Sink; +use futures::Stream; +use hyper; +use std; +use std::collections::HashMap; +use std::io::{Error, Read, Seek, SeekFrom, Write}; +use std::net::{Shutdown, SocketAddr}; +use std::process::ExitStatus; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::sync::{Arc, Mutex}; +use tokio; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::net::TcpStream; +use tokio_process; + +pub type ResourceId = u32; // Sometimes referred to RID. + +// These store Deno's file descriptors. These are not necessarily the operating +// system ones. +type ResourceTable = HashMap; + +#[cfg(not(windows))] +use std::os::unix::io::FromRawFd; + +#[cfg(windows)] +use std::os::windows::io::FromRawHandle; + +#[cfg(windows)] +extern crate winapi; + +lazy_static! { + // Starts at 3 because stdio is [0-2]. + static ref NEXT_RID: AtomicUsize = AtomicUsize::new(3); + static ref RESOURCE_TABLE: Mutex = Mutex::new({ + let mut m = HashMap::new(); + // TODO Load these lazily during lookup? + m.insert(0, Repr::Stdin(tokio::io::stdin())); + + m.insert(1, Repr::Stdout({ + #[cfg(not(windows))] + let stdout = unsafe { std::fs::File::from_raw_fd(1) }; + #[cfg(windows)] + let stdout = unsafe { + std::fs::File::from_raw_handle(winapi::um::processenv::GetStdHandle( + winapi::um::winbase::STD_OUTPUT_HANDLE)) + }; + tokio::fs::File::from_std(stdout) + })); + + m.insert(2, Repr::Stderr(tokio::io::stderr())); + m + }); +} + +// Internal representation of Resource. +enum Repr { + Stdin(tokio::io::Stdin), + Stdout(tokio::fs::File), + Stderr(tokio::io::Stderr), + FsFile(tokio::fs::File), + // Since TcpListener might be closed while there is a pending accept task, + // we need to track the task so that when the listener is closed, + // this pending task could be notified and die. + // Currently TcpListener itself does not take care of this issue. + // See: https://github.com/tokio-rs/tokio/issues/846 + TcpListener(tokio::net::TcpListener, Option), + TcpStream(tokio::net::TcpStream), + HttpBody(HttpBody), + Repl(Arc>), + // Enum size is bounded by the largest variant. + // Use `Box` around large `Child` struct. + // https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant + Child(Box), + ChildStdin(tokio_process::ChildStdin), + ChildStdout(tokio_process::ChildStdout), + ChildStderr(tokio_process::ChildStderr), + Worker(WorkerChannels), +} + +/// If the given rid is open, this returns the type of resource, E.G. "worker". +/// If the rid is closed or was never open, it returns None. +pub fn get_type(rid: ResourceId) -> Option { + let table = RESOURCE_TABLE.lock().unwrap(); + table.get(&rid).map(inspect_repr) +} + +pub fn table_entries() -> Vec<(u32, String)> { + let table = RESOURCE_TABLE.lock().unwrap(); + + table + .iter() + .map(|(key, value)| (*key, inspect_repr(&value))) + .collect() +} + +#[test] +fn test_table_entries() { + let mut entries = table_entries(); + entries.sort(); + assert_eq!(entries[0], (0, String::from("stdin"))); + assert_eq!(entries[1], (1, String::from("stdout"))); + assert_eq!(entries[2], (2, String::from("stderr"))); +} + +fn inspect_repr(repr: &Repr) -> String { + let h_repr = match repr { + Repr::Stdin(_) => "stdin", + Repr::Stdout(_) => "stdout", + Repr::Stderr(_) => "stderr", + Repr::FsFile(_) => "fsFile", + Repr::TcpListener(_, _) => "tcpListener", + Repr::TcpStream(_) => "tcpStream", + Repr::HttpBody(_) => "httpBody", + Repr::Repl(_) => "repl", + Repr::Child(_) => "child", + Repr::ChildStdin(_) => "childStdin", + Repr::ChildStdout(_) => "childStdout", + Repr::ChildStderr(_) => "childStderr", + Repr::Worker(_) => "worker", + }; + + String::from(h_repr) +} + +// Abstract async file interface. +// Ideally in unix, if Resource represents an OS rid, it will be the same. +#[derive(Clone, Debug)] +pub struct Resource { + pub rid: ResourceId, +} + +impl Resource { + // TODO Should it return a Resource instead of net::TcpStream? + pub fn poll_accept(&mut self) -> Poll<(TcpStream, SocketAddr), Error> { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&self.rid); + match maybe_repr { + None => Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Listener has been closed", + )), + Some(repr) => match repr { + Repr::TcpListener(ref mut s, _) => s.poll_accept(), + _ => panic!("Cannot accept"), + }, + } + } + + // close(2) is done by dropping the value. Therefore we just need to remove + // the resource from the RESOURCE_TABLE. + pub fn close(&self) { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let r = table.remove(&self.rid); + assert!(r.is_some()); + } + + pub fn shutdown(&mut self, how: Shutdown) -> Result<(), DenoError> { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&self.rid); + match maybe_repr { + None => panic!("bad rid"), + Some(repr) => match repr { + Repr::TcpStream(ref mut f) => { + TcpStream::shutdown(f, how).map_err(DenoError::from) + } + _ => panic!("Cannot shutdown"), + }, + } + } +} + +impl Read for Resource { + fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { + unimplemented!(); + } +} + +impl AsyncRead for Resource { + fn poll_read(&mut self, buf: &mut [u8]) -> Poll { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&self.rid); + match maybe_repr { + None => panic!("bad rid"), + Some(repr) => match repr { + Repr::FsFile(ref mut f) => f.poll_read(buf), + Repr::Stdin(ref mut f) => f.poll_read(buf), + Repr::TcpStream(ref mut f) => f.poll_read(buf), + Repr::HttpBody(ref mut f) => f.poll_read(buf), + Repr::ChildStdout(ref mut f) => f.poll_read(buf), + Repr::ChildStderr(ref mut f) => f.poll_read(buf), + _ => panic!("Cannot read"), + }, + } + } +} + +impl Write for Resource { + fn write(&mut self, _buf: &[u8]) -> std::io::Result { + unimplemented!() + } + + fn flush(&mut self) -> std::io::Result<()> { + unimplemented!() + } +} + +impl AsyncWrite for Resource { + fn poll_write(&mut self, buf: &[u8]) -> Poll { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&self.rid); + match maybe_repr { + None => panic!("bad rid"), + Some(repr) => match repr { + Repr::FsFile(ref mut f) => f.poll_write(buf), + Repr::Stdout(ref mut f) => f.poll_write(buf), + Repr::Stderr(ref mut f) => f.poll_write(buf), + Repr::TcpStream(ref mut f) => f.poll_write(buf), + Repr::ChildStdin(ref mut f) => f.poll_write(buf), + _ => panic!("Cannot write"), + }, + } + } + + fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> { + unimplemented!() + } +} + +fn new_rid() -> ResourceId { + let next_rid = NEXT_RID.fetch_add(1, Ordering::SeqCst); + next_rid as ResourceId +} + +pub fn add_fs_file(fs_file: tokio::fs::File) -> Resource { + let rid = new_rid(); + let mut tg = RESOURCE_TABLE.lock().unwrap(); + match tg.insert(rid, Repr::FsFile(fs_file)) { + Some(_) => panic!("There is already a file with that rid"), + None => Resource { rid }, + } +} + +pub fn add_tcp_listener(listener: tokio::net::TcpListener) -> Resource { + let rid = new_rid(); + let mut tg = RESOURCE_TABLE.lock().unwrap(); + let r = tg.insert(rid, Repr::TcpListener(listener, None)); + assert!(r.is_none()); + Resource { rid } +} + +pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource { + let rid = new_rid(); + let mut tg = RESOURCE_TABLE.lock().unwrap(); + let r = tg.insert(rid, Repr::TcpStream(stream)); + assert!(r.is_none()); + Resource { rid } +} + +pub fn add_hyper_body(body: hyper::Body) -> Resource { + let rid = new_rid(); + let mut tg = RESOURCE_TABLE.lock().unwrap(); + let body = HttpBody::from(body); + let r = tg.insert(rid, Repr::HttpBody(body)); + assert!(r.is_none()); + Resource { rid } +} + +pub fn add_repl(repl: Repl) -> Resource { + let rid = new_rid(); + let mut tg = RESOURCE_TABLE.lock().unwrap(); + let r = tg.insert(rid, Repr::Repl(Arc::new(Mutex::new(repl)))); + assert!(r.is_none()); + Resource { rid } +} + +pub fn add_worker(wc: WorkerChannels) -> Resource { + let rid = new_rid(); + let mut tg = RESOURCE_TABLE.lock().unwrap(); + let r = tg.insert(rid, Repr::Worker(wc)); + assert!(r.is_none()); + Resource { rid } +} + +pub fn worker_post_message( + rid: ResourceId, + buf: Buf, +) -> futures::sink::Send> { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&rid); + match maybe_repr { + Some(Repr::Worker(ref mut wc)) => { + // unwrap here is incorrect, but doing it anyway + wc.0.clone().send(buf) + } + _ => panic!("bad resource"), // futures::future::err(bad_resource()).into(), + } +} + +pub struct WorkerReceiver { + rid: ResourceId, +} + +// Invert the dumbness that tokio_process causes by making Child itself a future. +impl Future for WorkerReceiver { + type Item = Option; + type Error = DenoError; + + fn poll(&mut self) -> Poll, DenoError> { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&self.rid); + match maybe_repr { + Some(Repr::Worker(ref mut wc)) => wc.1.poll().map_err(|()| { + errors::new(errors::ErrorKind::Other, "recv msg error".to_string()) + }), + _ => Err(bad_resource()), + } + } +} + +pub fn worker_recv_message(rid: ResourceId) -> WorkerReceiver { + WorkerReceiver { rid } +} + +#[cfg_attr(feature = "cargo-clippy", allow(stutter))] +pub struct ChildResources { + pub child_rid: ResourceId, + pub stdin_rid: Option, + pub stdout_rid: Option, + pub stderr_rid: Option, +} + +pub fn add_child(mut c: tokio_process::Child) -> ChildResources { + let child_rid = new_rid(); + let mut tg = RESOURCE_TABLE.lock().unwrap(); + + let mut resources = ChildResources { + child_rid, + stdin_rid: None, + stdout_rid: None, + stderr_rid: None, + }; + + if c.stdin().is_some() { + let stdin = c.stdin().take().unwrap(); + let rid = new_rid(); + let r = tg.insert(rid, Repr::ChildStdin(stdin)); + assert!(r.is_none()); + resources.stdin_rid = Some(rid); + } + if c.stdout().is_some() { + let stdout = c.stdout().take().unwrap(); + let rid = new_rid(); + let r = tg.insert(rid, Repr::ChildStdout(stdout)); + assert!(r.is_none()); + resources.stdout_rid = Some(rid); + } + if c.stderr().is_some() { + let stderr = c.stderr().take().unwrap(); + let rid = new_rid(); + let r = tg.insert(rid, Repr::ChildStderr(stderr)); + assert!(r.is_none()); + resources.stderr_rid = Some(rid); + } + + let r = tg.insert(child_rid, Repr::Child(Box::new(c))); + assert!(r.is_none()); + + resources +} + +pub struct ChildStatus { + rid: ResourceId, +} + +// Invert the dumbness that tokio_process causes by making Child itself a future. +impl Future for ChildStatus { + type Item = ExitStatus; + type Error = DenoError; + + fn poll(&mut self) -> Poll { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&self.rid); + match maybe_repr { + Some(Repr::Child(ref mut child)) => child.poll().map_err(DenoError::from), + _ => Err(bad_resource()), + } + } +} + +pub fn child_status(rid: ResourceId) -> DenoResult { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&rid); + match maybe_repr { + Some(Repr::Child(ref mut _child)) => Ok(ChildStatus { rid }), + _ => Err(bad_resource()), + } +} + +pub fn get_repl(rid: ResourceId) -> DenoResult>> { + let mut table = RESOURCE_TABLE.lock().unwrap(); + let maybe_repr = table.get_mut(&rid); + match maybe_repr { + Some(Repr::Repl(ref mut r)) => Ok(r.clone()), + _ => Err(bad_resource()), + } +} + +pub fn lookup(rid: ResourceId) -> Option { + debug!("resource lookup {}", rid); + let table = RESOURCE_TABLE.lock().unwrap(); + table.get(&rid).map(|_| Resource { rid }) +} + +// TODO(kevinkassimo): revamp this after the following lands: +// https://github.com/tokio-rs/tokio/pull/785 +pub fn seek( + resource: Resource, + offset: i32, + whence: u32, +) -> Box + Send> { + let mut table = RESOURCE_TABLE.lock().unwrap(); + // We take ownership of File here. + // It is put back below while still holding the lock. + let maybe_repr = table.remove(&resource.rid); + match maybe_repr { + None => panic!("bad rid"), + Some(Repr::FsFile(f)) => { + let seek_from = match whence { + 0 => SeekFrom::Start(offset as u64), + 1 => SeekFrom::Current(offset as i64), + 2 => SeekFrom::End(offset as i64), + _ => { + return Box::new(futures::future::err(errors::new( + errors::ErrorKind::InvalidSeekMode, + format!("Invalid seek mode: {}", whence), + ))); + } + }; + // Trait Clone not implemented on tokio::fs::File, + // so convert to std File first. + let std_file = f.into_std(); + // Create a copy and immediately put back. + // We don't want to block other resource ops. + // try_clone() would yield a copy containing the same + // underlying fd, so operations on the copy would also + // affect the one in resource table, and we don't need + // to write back. + let maybe_std_file_copy = std_file.try_clone(); + // Insert the entry back with the same rid. + table.insert( + resource.rid, + Repr::FsFile(tokio_fs::File::from_std(std_file)), + ); + if maybe_std_file_copy.is_err() { + return Box::new(futures::future::err(DenoError::from( + maybe_std_file_copy.unwrap_err(), + ))); + } + let mut std_file_copy = maybe_std_file_copy.unwrap(); + return Box::new(futures::future::lazy(move || { + let result = std_file_copy + .seek(seek_from) + .map(|_| { + return (); + }).map_err(DenoError::from); + futures::future::result(result) + })); + } + _ => panic!("cannot seek"), + } +} diff --git a/cli/startup_data.rs b/cli/startup_data.rs new file mode 100644 index 000000000..29ae4db7d --- /dev/null +++ b/cli/startup_data.rs @@ -0,0 +1,57 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use deno_core::deno_buf; +use deno_core::{StartupData, StartupScript}; + +pub fn deno_isolate_init() -> StartupData { + if cfg!(feature = "no-snapshot-init") { + debug!("Deno isolate init without snapshots."); + #[cfg(not(feature = "check-only"))] + let source_bytes = + include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js")); + #[cfg(feature = "check-only")] + let source_bytes = vec![]; + + StartupData::Script(StartupScript { + filename: "gen/bundle/main.js".to_string(), + source: std::str::from_utf8(source_bytes).unwrap().to_string(), + }) + } else { + debug!("Deno isolate init with snapshots."); + #[cfg(not(any(feature = "check-only", feature = "no-snapshot-init")))] + let data = + include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_deno.bin")); + #[cfg(any(feature = "check-only", feature = "no-snapshot-init"))] + let data = vec![]; + + unsafe { + StartupData::Snapshot(deno_buf::from_raw_parts(data.as_ptr(), data.len())) + } + } +} + +pub fn compiler_isolate_init() -> StartupData { + if cfg!(feature = "no-snapshot-init") { + debug!("Deno isolate init without snapshots."); + #[cfg(not(feature = "check-only"))] + let source_bytes = + include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/compiler.js")); + #[cfg(feature = "check-only")] + let source_bytes = vec![]; + + StartupData::Script(StartupScript { + filename: "gen/bundle/compiler.js".to_string(), + source: std::str::from_utf8(source_bytes).unwrap().to_string(), + }) + } else { + debug!("Deno isolate init with snapshots."); + #[cfg(not(any(feature = "check-only", feature = "no-snapshot-init")))] + let data = + include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_compiler.bin")); + #[cfg(any(feature = "check-only", feature = "no-snapshot-init"))] + let data = vec![]; + + unsafe { + StartupData::Snapshot(deno_buf::from_raw_parts(data.as_ptr(), data.len())) + } + } +} diff --git a/cli/tokio_util.rs b/cli/tokio_util.rs new file mode 100644 index 000000000..810b826b4 --- /dev/null +++ b/cli/tokio_util.rs @@ -0,0 +1,118 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::resources::Resource; +use futures; +use futures::Future; +use futures::Poll; +use std::io; +use std::mem; +use std::net::SocketAddr; +use tokio; +use tokio::net::TcpStream; + +pub fn run(future: F) +where + F: Future + Send + 'static, +{ + // tokio::runtime::current_thread::run(future) + tokio::run(future) +} + +pub fn block_on(future: F) -> Result +where + F: Send + 'static + Future, + R: Send + 'static, + E: Send + 'static, +{ + let (tx, rx) = futures::sync::oneshot::channel(); + tokio::spawn(future.then(move |r| tx.send(r).map_err(|_| unreachable!()))); + rx.wait().unwrap() +} + +// Set the default executor so we can use tokio::spawn(). It's difficult to +// pass around mut references to the runtime, so using with_default is +// preferable. Ideally Tokio would provide this function. +#[cfg(test)] +pub fn init(f: F) +where + F: FnOnce(), +{ + use tokio_executor; + let rt = tokio::runtime::Runtime::new().unwrap(); + let mut executor = rt.executor(); + let mut enter = tokio_executor::enter().expect("Multiple executors at once"); + tokio_executor::with_default(&mut executor, &mut enter, move |_enter| f()); +} + +#[derive(Debug)] +enum AcceptState { + Pending(Resource), + Empty, +} + +/// Simply accepts a connection. +pub fn accept(r: Resource) -> Accept { + Accept { + state: AcceptState::Pending(r), + } +} + +/// A future which can be used to easily read available number of bytes to fill +/// a buffer. +/// +/// Created by the [`read`] function. +#[derive(Debug)] +pub struct Accept { + state: AcceptState, +} + +impl Future for Accept { + type Item = (TcpStream, SocketAddr); + type Error = io::Error; + + fn poll(&mut self) -> Poll { + let (stream, addr) = match self.state { + AcceptState::Pending(ref mut r) => try_ready!(r.poll_accept()), + AcceptState::Empty => panic!("poll Accept after it's done"), + }; + + match mem::replace(&mut self.state, AcceptState::Empty) { + AcceptState::Pending(_) => Ok((stream, addr).into()), + AcceptState::Empty => panic!("invalid internal state"), + } + } +} + +/// `futures::future::poll_fn` only support `F: FnMut()->Poll` +/// However, we require that `F: FnOnce()->Poll`. +/// Therefore, we created our version of `poll_fn`. +pub fn poll_fn(f: F) -> PollFn +where + F: FnOnce() -> Poll, +{ + PollFn { inner: Some(f) } +} + +pub struct PollFn { + inner: Option, +} + +impl Future for PollFn +where + F: FnOnce() -> Poll, +{ + type Item = T; + type Error = E; + + fn poll(&mut self) -> Poll { + let f = self.inner.take().expect("Inner fn has been taken."); + f() + } +} + +pub fn panic_on_error(f: F) -> impl Future +where + F: Future, + E: std::fmt::Debug, +{ + f.map_err(|err| panic!("Future got unexpected error: {:?}", err)) +} diff --git a/cli/tokio_write.rs b/cli/tokio_write.rs new file mode 100644 index 000000000..945de375d --- /dev/null +++ b/cli/tokio_write.rs @@ -0,0 +1,62 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +// TODO Submit this file upstream into tokio-io/src/io/write.rs +use std::io; +use std::mem; + +use futures::{Future, Poll}; +use tokio::io::AsyncWrite; + +/// A future used to write some data to a stream. +/// +/// This is created by the [`write`] top-level method. +/// +/// [`write`]: fn.write.html +#[derive(Debug)] +pub struct Write { + state: State, +} + +#[derive(Debug)] +enum State { + Pending { a: A, buf: T }, + Empty, +} + +/// Creates a future that will write some of the buffer `buf` to +/// the stream `a` provided. +/// +/// Any error which happens during writing will cause both the stream and the +/// buffer to get destroyed. +pub fn write(a: A, buf: T) -> Write +where + A: AsyncWrite, + T: AsRef<[u8]>, +{ + Write { + state: State::Pending { a, buf }, + } +} + +impl Future for Write +where + A: AsyncWrite, + T: AsRef<[u8]>, +{ + type Item = (A, T, usize); + type Error = io::Error; + + fn poll(&mut self) -> Poll<(A, T, usize), io::Error> { + let nwritten = match self.state { + State::Pending { + ref mut a, + ref mut buf, + } => try_ready!(a.poll_write(buf.as_ref())), + State::Empty => panic!("poll a Read after it's done"), + }; + + match mem::replace(&mut self.state, State::Empty) { + State::Pending { a, buf } => Ok((a, buf, nwritten).into()), + State::Empty => panic!("invalid internal state"), + } + } +} diff --git a/cli/version.rs b/cli/version.rs new file mode 100644 index 000000000..e6ec9008b --- /dev/null +++ b/cli/version.rs @@ -0,0 +1,6 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +pub const DENO: &str = env!("CARGO_PKG_VERSION"); + +pub fn v8() -> &'static str { + deno_core::v8_version() +} diff --git a/cli/workers.rs b/cli/workers.rs new file mode 100644 index 000000000..edded7756 --- /dev/null +++ b/cli/workers.rs @@ -0,0 +1,181 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +use crate::cli::Buf; +use crate::cli::Cli; +use crate::flags::DenoFlags; +use crate::isolate::Isolate; +use crate::isolate_state::IsolateState; +use crate::isolate_state::WorkerChannels; +use crate::js_errors::JSErrorColor; +use crate::permissions::DenoPermissions; +use crate::resources; +use crate::tokio_util; +use deno_core::JSError; +use deno_core::StartupData; +use futures::future::lazy; +use futures::sync::mpsc; +use futures::sync::oneshot; +use futures::Future; +use futures::Poll; +use std::sync::Arc; +use std::thread; + +/// Rust interface for WebWorkers. +pub struct Worker { + isolate: Isolate, +} + +impl Worker { + pub fn new( + startup_data: Option, + flags: DenoFlags, + argv: Vec, + permissions: DenoPermissions, + ) -> (Self, WorkerChannels) { + let (worker_in_tx, worker_in_rx) = mpsc::channel::(1); + let (worker_out_tx, worker_out_rx) = mpsc::channel::(1); + + let internal_channels = (worker_out_tx, worker_in_rx); + let external_channels = (worker_in_tx, worker_out_rx); + + let state = + Arc::new(IsolateState::new(flags, argv, Some(internal_channels))); + + let cli = Cli::new(startup_data, state, permissions); + let isolate = Isolate::new(cli); + + let worker = Worker { isolate }; + (worker, external_channels) + } + + pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> { + self.isolate.execute(js_source) + } +} + +impl Future for Worker { + type Item = (); + type Error = JSError; + + fn poll(&mut self) -> Poll<(), JSError> { + self.isolate.poll() + } +} + +pub fn spawn( + startup_data: Option, + state: &IsolateState, + js_source: String, + permissions: DenoPermissions, +) -> resources::Resource { + // TODO This function should return a Future, so that the caller can retrieve + // the JSError if one is thrown. Currently it just prints to stderr and calls + // exit(1). + // let (js_error_tx, js_error_rx) = oneshot::channel::(); + let (p, c) = oneshot::channel::(); + let builder = thread::Builder::new().name("worker".to_string()); + + let flags = state.flags.clone(); + let argv = state.argv.clone(); + + let _tid = builder + .spawn(move || { + tokio_util::run(lazy(move || { + let (mut worker, external_channels) = + Worker::new(startup_data, flags, argv, permissions); + let resource = resources::add_worker(external_channels); + p.send(resource.clone()).unwrap(); + + worker + .execute("denoMain()") + .expect("worker denoMain failed"); + worker + .execute("workerMain()") + .expect("worker workerMain failed"); + worker.execute(&js_source).expect("worker js_source failed"); + + worker.then(move |r| -> Result<(), ()> { + resource.close(); + debug!("workers.rs after resource close"); + if let Err(err) = r { + eprintln!("{}", JSErrorColor(&err).to_string()); + std::process::exit(1); + } + Ok(()) + }) + })); + + debug!("workers.rs after spawn"); + }).unwrap(); + + c.wait().unwrap() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::startup_data; + + #[test] + fn test_spawn() { + let startup_data = startup_data::compiler_isolate_init(); + let resource = spawn( + Some(startup_data), + &IsolateState::mock(), + r#" + onmessage = function(e) { + let s = new TextDecoder().decode(e.data);; + console.log("msg from main script", s); + if (s == "exit") { + close(); + return; + } else { + console.assert(s === "hi"); + } + postMessage(new Uint8Array([1, 2, 3])); + console.log("after postMessage"); + } + "#.into(), + DenoPermissions::default(), + ); + let msg = String::from("hi").into_boxed_str().into_boxed_bytes(); + + let r = resources::worker_post_message(resource.rid, msg).wait(); + assert!(r.is_ok()); + + let maybe_msg = + resources::worker_recv_message(resource.rid).wait().unwrap(); + assert!(maybe_msg.is_some()); + assert_eq!(*maybe_msg.unwrap(), [1, 2, 3]); + + let msg = String::from("exit").into_boxed_str().into_boxed_bytes(); + let r = resources::worker_post_message(resource.rid, msg).wait(); + assert!(r.is_ok()); + } + + #[test] + fn removed_from_resource_table_on_close() { + let startup_data = startup_data::compiler_isolate_init(); + let resource = spawn( + Some(startup_data), + &IsolateState::mock(), + "onmessage = () => close();".into(), + DenoPermissions::default(), + ); + + assert_eq!( + resources::get_type(resource.rid), + Some("worker".to_string()) + ); + + let msg = String::from("hi").into_boxed_str().into_boxed_bytes(); + let r = resources::worker_post_message(resource.rid, msg).wait(); + assert!(r.is_ok()); + println!("rid {:?}", resource.rid); + + // TODO Need a way to get a future for when a resource closes. + // For now, just sleep for a bit. + // resource.close(); + thread::sleep(std::time::Duration::from_millis(1000)); + assert_eq!(resources::get_type(resource.rid), None); + } +} diff --git a/js/stat_test.ts b/js/stat_test.ts index 3a109c0b5..ae6477d79 100644 --- a/js/stat_test.ts +++ b/js/stat_test.ts @@ -12,9 +12,9 @@ testPerm({ read: true }, async function statSyncSuccess() { assert(testingInfo.isDirectory()); assert(!testingInfo.isSymlink()); - const srcInfo = Deno.statSync("src"); - assert(srcInfo.isDirectory()); - assert(!srcInfo.isSymlink()); + const testsInfo = Deno.statSync("tests"); + assert(testsInfo.isDirectory()); + assert(!testsInfo.isSymlink()); }); testPerm({ read: false }, async function statSyncPerm() { @@ -54,9 +54,9 @@ testPerm({ read: true }, async function lstatSyncSuccess() { assert(!testingInfo.isDirectory()); assert(testingInfo.isSymlink()); - const srcInfo = Deno.lstatSync("src"); - assert(srcInfo.isDirectory()); - assert(!srcInfo.isSymlink()); + const testsInfo = Deno.lstatSync("tests"); + assert(testsInfo.isDirectory()); + assert(!testsInfo.isSymlink()); }); testPerm({ read: false }, async function lstatSyncPerm() { @@ -96,9 +96,9 @@ testPerm({ read: true }, async function statSuccess() { assert(testingInfo.isDirectory()); assert(!testingInfo.isSymlink()); - const srcInfo = await Deno.stat("src"); - assert(srcInfo.isDirectory()); - assert(!srcInfo.isSymlink()); + const testsInfo = await Deno.stat("tests"); + assert(testsInfo.isDirectory()); + assert(!testsInfo.isSymlink()); }); testPerm({ read: false }, async function statPerm() { @@ -138,9 +138,9 @@ testPerm({ read: true }, async function lstatSuccess() { assert(!testingInfo.isDirectory()); assert(testingInfo.isSymlink()); - const srcInfo = await Deno.lstat("src"); - assert(srcInfo.isDirectory()); - assert(!srcInfo.isSymlink()); + const testsInfo = await Deno.lstat("tests"); + assert(testsInfo.isDirectory()); + assert(!testsInfo.isSymlink()); }); testPerm({ read: false }, async function lstatPerm() { diff --git a/src/ansi.rs b/src/ansi.rs deleted file mode 100644 index 95b5e0694..000000000 --- a/src/ansi.rs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use ansi_term::Color::Fixed; -use ansi_term::Color::Red; -use ansi_term::Style; -use regex::Regex; -use std::env; -use std::fmt; - -lazy_static! { - // STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate. - // Copyright 2017 Armin Ronacher . MIT License. - static ref STRIP_ANSI_RE: Regex = Regex::new( - r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]" - ).unwrap(); - static ref NO_COLOR: bool = { - env::var_os("NO_COLOR").is_some() - }; -} - -/// Helper function to strip ansi codes. -#[cfg(test)] -pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow { - STRIP_ANSI_RE.replace_all(s, "") -} - -pub fn use_color() -> bool { - !(*NO_COLOR) -} - -pub fn red_bold(s: String) -> impl fmt::Display { - let mut style = Style::new(); - if use_color() { - style = style.bold().fg(Red); - } - style.paint(s) -} - -pub fn italic_bold(s: String) -> impl fmt::Display { - let mut style = Style::new(); - if use_color() { - style = style.italic().bold(); - } - style.paint(s) -} - -pub fn yellow(s: String) -> impl fmt::Display { - let mut style = Style::new(); - if use_color() { - // matches TypeScript's ForegroundColorEscapeSequences.Yellow - style = style.fg(Fixed(11)); - } - style.paint(s) -} - -pub fn cyan(s: String) -> impl fmt::Display { - let mut style = Style::new(); - if use_color() { - // matches TypeScript's ForegroundColorEscapeSequences.Cyan - style = style.fg(Fixed(14)); - } - style.paint(s) -} - -pub fn bold(s: String) -> impl fmt::Display { - let mut style = Style::new(); - if use_color() { - style = style.bold(); - } - style.paint(s) -} diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 42b2b29f8..000000000 --- a/src/cli.rs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -#![allow(unused_variables)] -#![allow(dead_code)] - -use crate::errors::DenoResult; -use crate::isolate_state::IsolateState; -use crate::ops; -use crate::permissions::DenoPermissions; -use deno_core::deno_buf; -use deno_core::deno_mod; -use deno_core::Behavior; -use deno_core::Op; -use deno_core::StartupData; -use std::sync::atomic::Ordering; -use std::sync::Arc; - -// Buf represents a byte array returned from a "Op". The message might be empty -// (which will be translated into a null object on the javascript side) or it is -// a heap allocated opaque sequence of bytes. Usually a flatbuffer message. -pub type Buf = Box<[u8]>; - -/// Implements deno_core::Behavior for the main Deno command-line. -pub struct Cli { - startup_data: Option, - pub state: Arc, - pub permissions: Arc, // TODO(ry) move to IsolateState -} - -impl Cli { - pub fn new( - startup_data: Option, - state: Arc, - permissions: DenoPermissions, - ) -> Self { - Self { - startup_data, - state, - permissions: Arc::new(permissions), - } - } - - #[inline] - pub fn check_read(&self, filename: &str) -> DenoResult<()> { - self.permissions.check_read(filename) - } - - #[inline] - pub fn check_write(&self, filename: &str) -> DenoResult<()> { - self.permissions.check_write(filename) - } - - #[inline] - pub fn check_env(&self) -> DenoResult<()> { - self.permissions.check_env() - } - - #[inline] - pub fn check_net(&self, filename: &str) -> DenoResult<()> { - self.permissions.check_net(filename) - } - - #[inline] - pub fn check_run(&self) -> DenoResult<()> { - self.permissions.check_run() - } -} - -impl Behavior for Cli { - fn startup_data(&mut self) -> Option { - self.startup_data.take() - } - - fn resolve(&mut self, specifier: &str, referrer: deno_mod) -> deno_mod { - self - .state - .metrics - .resolve_count - .fetch_add(1, Ordering::Relaxed); - let mut modules = self.state.modules.lock().unwrap(); - modules.resolve_cb(&self.state.dir, specifier, referrer) - } - - fn dispatch( - &mut self, - control: &[u8], - zero_copy: deno_buf, - ) -> (bool, Box) { - ops::dispatch(self, control, zero_copy) - } -} diff --git a/src/compiler.rs b/src/compiler.rs deleted file mode 100644 index 2d6e4a4b7..000000000 --- a/src/compiler.rs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::cli::Buf; -use crate::isolate_state::IsolateState; -use crate::msg; -use crate::permissions::{DenoPermissions, PermissionAccessor}; -use crate::resources; -use crate::resources::Resource; -use crate::resources::ResourceId; -use crate::startup_data; -use crate::workers; -use futures::Future; -use serde_json; -use std::str; -use std::sync::Mutex; - -lazy_static! { - static ref C_RID: Mutex> = Mutex::new(None); -} - -// This corresponds to JS ModuleMetaData. -// TODO Rename one or the other so they correspond. -#[derive(Debug)] -pub struct ModuleMetaData { - pub module_name: String, - pub filename: String, - pub media_type: msg::MediaType, - pub source_code: Vec, - pub maybe_output_code_filename: Option, - pub maybe_output_code: Option>, - pub maybe_source_map_filename: Option, - pub maybe_source_map: Option>, -} - -impl ModuleMetaData { - pub fn js_source(&self) -> String { - if self.media_type == msg::MediaType::Json { - return format!( - "export default {};", - str::from_utf8(&self.source_code).unwrap() - ); - } - match self.maybe_output_code { - None => str::from_utf8(&self.source_code).unwrap().to_string(), - Some(ref output_code) => str::from_utf8(output_code).unwrap().to_string(), - } - } -} - -fn lazy_start(parent_state: &IsolateState) -> Resource { - let mut cell = C_RID.lock().unwrap(); - let startup_data = startup_data::compiler_isolate_init(); - let permissions = DenoPermissions { - allow_read: PermissionAccessor::from(true), - allow_write: PermissionAccessor::from(true), - allow_net: PermissionAccessor::from(true), - ..Default::default() - }; - - let rid = cell.get_or_insert_with(|| { - let resource = workers::spawn( - Some(startup_data), - parent_state, - "compilerMain()".to_string(), - permissions, - ); - resource.rid - }); - Resource { rid: *rid } -} - -fn req(specifier: &str, referrer: &str) -> Buf { - json!({ - "specifier": specifier, - "referrer": referrer, - }).to_string() - .into_boxed_str() - .into_boxed_bytes() -} - -pub fn compile_sync( - parent_state: &IsolateState, - specifier: &str, - referrer: &str, - module_meta_data: &ModuleMetaData, -) -> ModuleMetaData { - let req_msg = req(specifier, referrer); - - let compiler = lazy_start(parent_state); - - let send_future = resources::worker_post_message(compiler.rid, req_msg); - send_future.wait().unwrap(); - - let recv_future = resources::worker_recv_message(compiler.rid); - let result = recv_future.wait().unwrap(); - assert!(result.is_some()); - let res_msg = result.unwrap(); - - let res_json = std::str::from_utf8(&res_msg).unwrap(); - match serde_json::from_str::(res_json) { - Ok(serde_json::Value::Object(map)) => ModuleMetaData { - module_name: module_meta_data.module_name.clone(), - filename: module_meta_data.filename.clone(), - media_type: module_meta_data.media_type, - source_code: module_meta_data.source_code.clone(), - maybe_output_code: match map["outputCode"].as_str() { - Some(str) => Some(str.as_bytes().to_owned()), - _ => None, - }, - maybe_output_code_filename: None, - maybe_source_map: match map["sourceMap"].as_str() { - Some(str) => Some(str.as_bytes().to_owned()), - _ => None, - }, - maybe_source_map_filename: None, - }, - _ => panic!("error decoding compiler response"), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_compile_sync() { - let cwd = std::env::current_dir().unwrap(); - let cwd_string = cwd.to_str().unwrap().to_owned(); - - let specifier = "./tests/002_hello.ts"; - let referrer = cwd_string + "/"; - - let mut out = ModuleMetaData { - module_name: "xxx".to_owned(), - filename: "/tests/002_hello.ts".to_owned(), - media_type: msg::MediaType::TypeScript, - source_code: "console.log(\"Hello World\");".as_bytes().to_owned(), - maybe_output_code_filename: None, - maybe_output_code: None, - maybe_source_map_filename: None, - maybe_source_map: None, - }; - - out = compile_sync(&IsolateState::mock(), specifier, &referrer, &mut out); - assert!( - out - .maybe_output_code - .unwrap() - .starts_with("console.log(\"Hello World\");".as_bytes()) - ); - } -} diff --git a/src/deno_dir.rs b/src/deno_dir.rs deleted file mode 100644 index 829af5aa2..000000000 --- a/src/deno_dir.rs +++ /dev/null @@ -1,1369 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::compiler::ModuleMetaData; -use crate::errors; -use crate::errors::DenoError; -use crate::errors::DenoResult; -use crate::errors::ErrorKind; -use crate::fs as deno_fs; -use crate::http_util; -use crate::js_errors::SourceMapGetter; -use crate::msg; -use crate::version; - -use dirs; -use ring; -use std; -use std::fmt::Write; -use std::fs; -use std::path::Path; -use std::path::PathBuf; -use std::result::Result; -use std::str; -use url; -use url::Url; - -/// Gets corresponding MediaType given extension -fn extmap(ext: &str) -> msg::MediaType { - match ext { - "ts" => msg::MediaType::TypeScript, - "js" => msg::MediaType::JavaScript, - "json" => msg::MediaType::Json, - _ => msg::MediaType::Unknown, - } -} - -pub struct DenoDir { - // Example: /Users/rld/.deno/ - pub root: PathBuf, - // In the Go code this was called SrcDir. - // This is where we cache http resources. Example: - // /Users/rld/.deno/deps/github.com/ry/blah.js - pub gen: PathBuf, - // In the Go code this was called CacheDir. - // This is where we cache compilation outputs. Example: - // /Users/rld/.deno/gen/f39a473452321cacd7c346a870efb0e3e1264b43.js - pub deps: PathBuf, - // This splits to http and https deps - pub deps_http: PathBuf, - pub deps_https: PathBuf, - // If remote resources should be reloaded. - reload: bool, - // recompile the typescript files. - // if true, not load cache files - // else, load cache files - recompile: bool, -} - -impl DenoDir { - // Must be called before using any function from this module. - // https://github.com/denoland/deno/blob/golang/deno_dir.go#L99-L111 - pub fn new( - reload: bool, - recompile: bool, - custom_root: Option, - ) -> std::io::Result { - // Only setup once. - let home_dir = dirs::home_dir().expect("Could not get home directory."); - let fallback = home_dir.join(".deno"); - // We use the OS cache dir because all files deno writes are cache files - // Once that changes we need to start using different roots if DENO_DIR - // is not set, and keep a single one if it is. - let default = dirs::cache_dir() - .map(|d| d.join("deno")) - .unwrap_or(fallback); - - let root: PathBuf = custom_root.unwrap_or(default); - let gen = root.as_path().join("gen"); - let deps = root.as_path().join("deps"); - let deps_http = deps.join("http"); - let deps_https = deps.join("https"); - - let deno_dir = Self { - root, - gen, - deps, - deps_http, - deps_https, - reload, - recompile, - }; - - // TODO Lazily create these directories. - deno_fs::mkdir(deno_dir.gen.as_ref(), 0o755, true)?; - deno_fs::mkdir(deno_dir.deps.as_ref(), 0o755, true)?; - deno_fs::mkdir(deno_dir.deps_http.as_ref(), 0o755, true)?; - deno_fs::mkdir(deno_dir.deps_https.as_ref(), 0o755, true)?; - - debug!("root {}", deno_dir.root.display()); - debug!("gen {}", deno_dir.gen.display()); - debug!("deps {}", deno_dir.deps.display()); - debug!("deps_http {}", deno_dir.deps_http.display()); - debug!("deps_https {}", deno_dir.deps_https.display()); - - Ok(deno_dir) - } - - // https://github.com/denoland/deno/blob/golang/deno_dir.go#L32-L35 - pub fn cache_path( - self: &Self, - filename: &str, - source_code: &[u8], - ) -> (PathBuf, PathBuf) { - let cache_key = source_code_hash(filename, source_code, version::DENO); - ( - self.gen.join(cache_key.to_string() + ".js"), - self.gen.join(cache_key.to_string() + ".js.map"), - ) - } - - fn load_cache( - self: &Self, - filename: &str, - source_code: &[u8], - ) -> Result<(Vec, Vec), std::io::Error> { - let (output_code, source_map) = self.cache_path(filename, source_code); - debug!( - "load_cache code: {} map: {}", - output_code.display(), - source_map.display() - ); - let read_output_code = fs::read(&output_code)?; - let read_source_map = fs::read(&source_map)?; - Ok((read_output_code, read_source_map)) - } - - pub fn code_cache( - self: &Self, - module_meta_data: &ModuleMetaData, - ) -> std::io::Result<()> { - let (cache_path, source_map_path) = self - .cache_path(&module_meta_data.filename, &module_meta_data.source_code); - // TODO(ry) This is a race condition w.r.t to exists() -- probably should - // create the file in exclusive mode. A worry is what might happen is there - // are two processes and one reads the cache file while the other is in the - // midst of writing it. - if cache_path.exists() && source_map_path.exists() { - Ok(()) - } else { - match &module_meta_data.maybe_output_code { - Some(output_code) => fs::write(cache_path, output_code), - _ => Ok(()), - }?; - match &module_meta_data.maybe_source_map { - Some(source_map) => fs::write(source_map_path, source_map), - _ => Ok(()), - }?; - Ok(()) - } - } - - // Prototype https://github.com/denoland/deno/blob/golang/deno_dir.go#L37-L73 - /// Fetch remote source code. - fn fetch_remote_source( - self: &Self, - module_name: &str, - filename: &str, - ) -> DenoResult> { - let p = Path::new(&filename); - // We write a special ".mime" file into the `.deno/deps` directory along side the - // cached file, containing just the media type. - let media_type_filename = [&filename, ".mime"].concat(); - let mt = Path::new(&media_type_filename); - eprint!("Downloading {}...", &module_name); // no newline - let maybe_source = http_util::fetch_sync_string(&module_name); - if let Ok((source, content_type)) = maybe_source { - eprintln!(""); // next line - match p.parent() { - Some(ref parent) => fs::create_dir_all(parent), - None => Ok(()), - }?; - deno_fs::write_file(&p, &source, 0o666)?; - // Remove possibly existing stale .mime file - // may not exist. DON'T unwrap - let _ = std::fs::remove_file(&media_type_filename); - // Create .mime file only when content type different from extension - let resolved_content_type = map_content_type(&p, Some(&content_type)); - let ext = p - .extension() - .map(|x| x.to_str().unwrap_or("")) - .unwrap_or(""); - let media_type = extmap(&ext); - if media_type == msg::MediaType::Unknown - || media_type != resolved_content_type - { - deno_fs::write_file(&mt, content_type.as_bytes(), 0o666)? - } - return Ok(Some(ModuleMetaData { - module_name: module_name.to_string(), - filename: filename.to_string(), - media_type: map_content_type(&p, Some(&content_type)), - source_code: source.as_bytes().to_owned(), - maybe_output_code_filename: None, - maybe_output_code: None, - maybe_source_map_filename: None, - maybe_source_map: None, - })); - } else { - eprintln!(" NOT FOUND"); - } - Ok(None) - } - - /// Fetch local or cached source code. - fn fetch_local_source( - self: &Self, - module_name: &str, - filename: &str, - ) -> DenoResult> { - let p = Path::new(&filename); - let media_type_filename = [&filename, ".mime"].concat(); - let mt = Path::new(&media_type_filename); - let source_code = match fs::read(p) { - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - return Ok(None); - } else { - return Err(e.into()); - } - } - Ok(c) => c, - }; - // .mime file might not exists - // this is okay for local source: maybe_content_type_str will be None - let maybe_content_type_string = fs::read_to_string(&mt).ok(); - // Option -> Option<&str> - let maybe_content_type_str = - maybe_content_type_string.as_ref().map(String::as_str); - Ok(Some(ModuleMetaData { - module_name: module_name.to_string(), - filename: filename.to_string(), - media_type: map_content_type(&p, maybe_content_type_str), - source_code, - maybe_output_code_filename: None, - maybe_output_code: None, - maybe_source_map_filename: None, - maybe_source_map: None, - })) - } - - // Prototype: https://github.com/denoland/deno/blob/golang/os.go#L122-L138 - fn get_source_code( - self: &Self, - module_name: &str, - filename: &str, - ) -> DenoResult { - let is_module_remote = is_remote(module_name); - // We try fetch local. Two cases: - // 1. This is a remote module, but no reload provided - // 2. This is a local module - if !is_module_remote || !self.reload { - debug!( - "fetch local or reload {} is_module_remote {}", - module_name, is_module_remote - ); - match self.fetch_local_source(&module_name, &filename)? { - Some(output) => { - debug!("found local source "); - return Ok(output); - } - None => { - debug!("fetch_local_source returned None"); - } - } - } - - // If not remote file, stop here! - if !is_module_remote { - debug!("not remote file stop here"); - return Err(DenoError::from(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("cannot find local file '{}'", filename), - ))); - } - - debug!("is remote but didn't find module"); - - // not cached/local, try remote - let maybe_remote_source = - self.fetch_remote_source(&module_name, &filename)?; - if let Some(output) = maybe_remote_source { - return Ok(output); - } - Err(DenoError::from(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("cannot find remote file '{}'", filename), - ))) - } - - pub fn fetch_module_meta_data( - self: &Self, - specifier: &str, - referrer: &str, - ) -> Result { - debug!( - "fetch_module_meta_data. specifier {} referrer {}", - specifier, referrer - ); - - let (module_name, filename) = self.resolve_module(specifier, referrer)?; - - let result = self.get_source_code(module_name.as_str(), filename.as_str()); - let mut out = match result { - Ok(out) => out, - Err(err) => { - if err.kind() == ErrorKind::NotFound { - // For NotFound, change the message to something better. - return Err(errors::new( - ErrorKind::NotFound, - format!( - "Cannot resolve module \"{}\" from \"{}\"", - specifier, referrer - ), - )); - } else { - return Err(err); - } - } - }; - - if out.source_code.starts_with("#!".as_bytes()) { - out.source_code = filter_shebang(out.source_code); - } - - if out.media_type != msg::MediaType::TypeScript { - return Ok(out); - } - - let (output_code_filename, output_source_map_filename) = - self.cache_path(&out.filename, &out.source_code); - let mut maybe_output_code = None; - let mut maybe_source_map = None; - - if !self.recompile { - let result = self.load_cache(out.filename.as_str(), &out.source_code); - match result { - Err(err) => { - if err.kind() == std::io::ErrorKind::NotFound { - return Ok(out); - } else { - return Err(err.into()); - } - } - Ok((output_code, source_map)) => { - maybe_output_code = Some(output_code); - maybe_source_map = Some(source_map); - } - } - } - - Ok(ModuleMetaData { - module_name: out.module_name, - filename: out.filename, - media_type: out.media_type, - source_code: out.source_code, - maybe_output_code_filename: output_code_filename - .to_str() - .map(String::from), - maybe_output_code, - maybe_source_map_filename: output_source_map_filename - .to_str() - .map(String::from), - maybe_source_map, - }) - } - - // Prototype: https://github.com/denoland/deno/blob/golang/os.go#L56-L68 - fn src_file_to_url(self: &Self, filename: &str) -> String { - let filename_path = Path::new(filename); - if filename_path.starts_with(&self.deps) { - let (rest, prefix) = if filename_path.starts_with(&self.deps_https) { - let rest = filename_path.strip_prefix(&self.deps_https).unwrap(); - let prefix = "https://".to_string(); - (rest, prefix) - } else if filename_path.starts_with(&self.deps_http) { - let rest = filename_path.strip_prefix(&self.deps_http).unwrap(); - let prefix = "http://".to_string(); - (rest, prefix) - } else { - // TODO(kevinkassimo): change this to support other protocols than http - unimplemented!() - }; - // Windows doesn't support ":" in filenames, so we represent port using a - // special string. - // TODO(ry) This current implementation will break on a URL that has - // the default port but contains "_PORT" in the path. - let rest = rest.to_str().unwrap().replacen("_PORT", ":", 1); - prefix + &rest - } else { - String::from(filename) - } - } - - /// Returns (module name, local filename) - pub fn resolve_module_url( - self: &Self, - specifier: &str, - referrer: &str, - ) -> Result { - let specifier = self.src_file_to_url(specifier); - let mut referrer = self.src_file_to_url(referrer); - - debug!( - "resolve_module specifier {} referrer {}", - specifier, referrer - ); - - if referrer.starts_with('.') { - let cwd = std::env::current_dir().unwrap(); - let referrer_path = cwd.join(referrer); - referrer = referrer_path.to_str().unwrap().to_string() + "/"; - } - - let j = if is_remote(&specifier) || Path::new(&specifier).is_absolute() { - parse_local_or_remote(&specifier)? - } else if referrer.ends_with('/') { - let r = Url::from_directory_path(&referrer); - // TODO(ry) Properly handle error. - if r.is_err() { - error!("Url::from_directory_path error {}", referrer); - } - let base = r.unwrap(); - base.join(specifier.as_ref())? - } else { - let base = parse_local_or_remote(&referrer)?; - base.join(specifier.as_ref())? - }; - Ok(j) - } - - /// Returns (module name, local filename) - pub fn resolve_module( - self: &Self, - specifier: &str, - referrer: &str, - ) -> Result<(String, String), url::ParseError> { - let j = self.resolve_module_url(specifier, referrer)?; - - let module_name = j.to_string(); - let filename; - match j.scheme() { - "file" => { - filename = deno_fs::normalize_path(j.to_file_path().unwrap().as_ref()); - } - "https" => { - filename = deno_fs::normalize_path( - get_cache_filename(self.deps_https.as_path(), &j).as_ref(), - ) - } - "http" => { - filename = deno_fs::normalize_path( - get_cache_filename(self.deps_http.as_path(), &j).as_ref(), - ) - } - // TODO(kevinkassimo): change this to support other protocols than http - _ => unimplemented!(), - } - - debug!("module_name: {}, filename: {}", module_name, filename); - Ok((module_name, filename)) - } -} - -impl SourceMapGetter for DenoDir { - fn get_source_map(&self, script_name: &str) -> Option> { - match self.fetch_module_meta_data(script_name, ".") { - Err(_e) => None, - Ok(out) => match out.maybe_source_map { - None => None, - Some(source_map) => Some(source_map), - }, - } - } -} - -fn get_cache_filename(basedir: &Path, url: &Url) -> PathBuf { - let host = url.host_str().unwrap(); - let host_port = match url.port() { - // Windows doesn't support ":" in filenames, so we represent port using a - // special string. - Some(port) => format!("{}_PORT{}", host, port), - None => host.to_string(), - }; - - let mut out = basedir.to_path_buf(); - out.push(host_port); - for path_seg in url.path_segments().unwrap() { - out.push(path_seg); - } - out -} - -fn source_code_hash( - filename: &str, - source_code: &[u8], - version: &str, -) -> String { - let mut ctx = ring::digest::Context::new(&ring::digest::SHA1); - ctx.update(version.as_bytes()); - ctx.update(filename.as_bytes()); - ctx.update(source_code); - let digest = ctx.finish(); - let mut out = String::new(); - // TODO There must be a better way to do this... - for byte in digest.as_ref() { - write!(&mut out, "{:02x}", byte).unwrap(); - } - out -} - -fn is_remote(module_name: &str) -> bool { - module_name.starts_with("http://") || module_name.starts_with("https://") -} - -fn parse_local_or_remote(p: &str) -> Result { - if is_remote(p) || p.starts_with("file:") { - Url::parse(p) - } else { - Url::from_file_path(p).map_err(|_err| url::ParseError::IdnaError) - } -} - -fn map_file_extension(path: &Path) -> msg::MediaType { - match path.extension() { - None => msg::MediaType::Unknown, - Some(os_str) => match os_str.to_str() { - Some("ts") => msg::MediaType::TypeScript, - Some("js") => msg::MediaType::JavaScript, - Some("json") => msg::MediaType::Json, - _ => msg::MediaType::Unknown, - }, - } -} - -// convert a ContentType string into a enumerated MediaType -fn map_content_type(path: &Path, content_type: Option<&str>) -> msg::MediaType { - match content_type { - Some(content_type) => { - // sometimes there is additional data after the media type in - // Content-Type so we have to do a bit of manipulation so we are only - // dealing with the actual media type - let ct_vector: Vec<&str> = content_type.split(';').collect(); - let ct: &str = ct_vector.first().unwrap(); - match ct.to_lowercase().as_ref() { - "application/typescript" - | "text/typescript" - | "video/vnd.dlna.mpeg-tts" - | "video/mp2t" - | "application/x-typescript" => msg::MediaType::TypeScript, - "application/javascript" - | "text/javascript" - | "application/ecmascript" - | "text/ecmascript" - | "application/x-javascript" => msg::MediaType::JavaScript, - "application/json" | "text/json" => msg::MediaType::Json, - "text/plain" => map_file_extension(path), - _ => { - debug!("unknown content type: {}", content_type); - msg::MediaType::Unknown - } - } - } - None => map_file_extension(path), - } -} - -fn filter_shebang(bytes: Vec) -> Vec { - let string = str::from_utf8(&bytes).unwrap(); - if let Some(i) = string.find('\n') { - let (_, rest) = string.split_at(i); - rest.as_bytes().to_owned() - } else { - Vec::new() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tokio_util; - use tempfile::TempDir; - - fn test_setup(reload: bool, recompile: bool) -> (TempDir, DenoDir) { - let temp_dir = TempDir::new().expect("tempdir fail"); - let deno_dir = - DenoDir::new(reload, recompile, Some(temp_dir.path().to_path_buf())) - .expect("setup fail"); - (temp_dir, deno_dir) - } - - // The `add_root` macro prepends "C:" to a string if on windows; on posix - // systems it returns the input string untouched. This is necessary because - // `Url::from_file_path()` fails if the input path isn't an absolute path. - macro_rules! add_root { - ($path:expr) => { - if cfg!(target_os = "windows") { - concat!("C:", $path) - } else { - $path - } - }; - } - - macro_rules! file_url { - ($path:expr) => { - if cfg!(target_os = "windows") { - concat!("file:///C:", $path) - } else { - concat!("file://", $path) - } - }; - } - - #[test] - fn test_get_cache_filename() { - let url = Url::parse("http://example.com:1234/path/to/file.ts").unwrap(); - let basedir = Path::new("/cache/dir/"); - let cache_file = get_cache_filename(&basedir, &url); - assert_eq!( - cache_file, - Path::new("/cache/dir/example.com_PORT1234/path/to/file.ts") - ); - } - - #[test] - fn test_cache_path() { - let (temp_dir, deno_dir) = test_setup(false, false); - let filename = "hello.js"; - let source_code = "1+2".as_bytes(); - let hash = source_code_hash(filename, source_code, version::DENO); - assert_eq!( - ( - temp_dir.path().join(format!("gen/{}.js", hash)), - temp_dir.path().join(format!("gen/{}.js.map", hash)) - ), - deno_dir.cache_path(filename, source_code) - ); - } - - #[test] - fn test_code_cache() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let filename = "hello.js"; - let source_code = "1+2".as_bytes(); - let output_code = "1+2 // output code".as_bytes(); - let source_map = "{}".as_bytes(); - let hash = source_code_hash(filename, source_code, version::DENO); - let (cache_path, source_map_path) = - deno_dir.cache_path(filename, source_code); - assert!(cache_path.ends_with(format!("gen/{}.js", hash))); - assert!(source_map_path.ends_with(format!("gen/{}.js.map", hash))); - - let out = ModuleMetaData { - filename: filename.to_owned(), - source_code: source_code.to_owned(), - module_name: "hello.js".to_owned(), - media_type: msg::MediaType::TypeScript, - maybe_output_code: Some(output_code.to_owned()), - maybe_output_code_filename: None, - maybe_source_map: Some(source_map.to_owned()), - maybe_source_map_filename: None, - }; - - let r = deno_dir.code_cache(&out); - r.expect("code_cache error"); - assert!(cache_path.exists()); - assert_eq!(output_code.to_owned(), fs::read(&cache_path).unwrap()); - } - - #[test] - fn test_source_code_hash() { - assert_eq!( - "7e44de2ed9e0065da09d835b76b8d70be503d276", - source_code_hash("hello.ts", "1+2".as_bytes(), "0.2.11") - ); - // Different source_code should result in different hash. - assert_eq!( - "57033366cf9db1ef93deca258cdbcd9ef5f4bde1", - source_code_hash("hello.ts", "1".as_bytes(), "0.2.11") - ); - // Different filename should result in different hash. - assert_eq!( - "19657f90b5b0540f87679e2fb362e7bd62b644b0", - source_code_hash("hi.ts", "1+2".as_bytes(), "0.2.11") - ); - // Different version should result in different hash. - assert_eq!( - "e2b4b7162975a02bf2770f16836eb21d5bcb8be1", - source_code_hash("hi.ts", "1+2".as_bytes(), "0.2.0") - ); - } - - #[test] - fn test_get_source_code_1() { - let (temp_dir, deno_dir) = test_setup(false, false); - // http_util::fetch_sync_string requires tokio - tokio_util::init(|| { - let module_name = "http://localhost:4545/tests/subdir/mod2.ts"; - let filename = deno_fs::normalize_path( - deno_dir - .deps_http - .join("localhost_PORT4545/tests/subdir/mod2.ts") - .as_ref(), - ); - let mime_file_name = format!("{}.mime", &filename); - - let result = deno_dir.get_source_code(module_name, &filename); - println!("module_name {} filename {}", module_name, filename); - assert!(result.is_ok()); - let r = result.unwrap(); - assert_eq!( - r.source_code, - "export { printHello } from \"./print_hello.ts\";\n".as_bytes() - ); - assert_eq!(&(r.media_type), &msg::MediaType::TypeScript); - // Should not create .mime file due to matching ext - assert!(fs::read_to_string(&mime_file_name).is_err()); - - // Modify .mime - let _ = fs::write(&mime_file_name, "text/javascript"); - let result2 = deno_dir.get_source_code(module_name, &filename); - assert!(result2.is_ok()); - let r2 = result2.unwrap(); - assert_eq!( - r2.source_code, - "export { printHello } from \"./print_hello.ts\";\n".as_bytes() - ); - // If get_source_code does not call remote, this should be JavaScript - // as we modified before! (we do not overwrite .mime due to no http fetch) - assert_eq!(&(r2.media_type), &msg::MediaType::JavaScript); - assert_eq!( - fs::read_to_string(&mime_file_name).unwrap(), - "text/javascript" - ); - - // Force self.reload - let deno_dir = - DenoDir::new(true, false, Some(temp_dir.path().to_path_buf())) - .expect("setup fail"); - let result3 = deno_dir.get_source_code(module_name, &filename); - assert!(result3.is_ok()); - let r3 = result3.unwrap(); - let expected3 = - "export { printHello } from \"./print_hello.ts\";\n".as_bytes(); - assert_eq!(r3.source_code, expected3); - // Now the old .mime file should have gone! Resolved back to TypeScript - assert_eq!(&(r3.media_type), &msg::MediaType::TypeScript); - assert!(fs::read_to_string(&mime_file_name).is_err()); - }); - } - - #[test] - fn test_get_source_code_2() { - let (temp_dir, deno_dir) = test_setup(false, false); - // http_util::fetch_sync_string requires tokio - tokio_util::init(|| { - let module_name = "http://localhost:4545/tests/subdir/mismatch_ext.ts"; - let filename = deno_fs::normalize_path( - deno_dir - .deps_http - .join("localhost_PORT4545/tests/subdir/mismatch_ext.ts") - .as_ref(), - ); - let mime_file_name = format!("{}.mime", &filename); - - let result = deno_dir.get_source_code(module_name, &filename); - println!("module_name {} filename {}", module_name, filename); - assert!(result.is_ok()); - let r = result.unwrap(); - let expected = "export const loaded = true;\n".as_bytes(); - assert_eq!(r.source_code, expected); - // Mismatch ext with content type, create .mime - assert_eq!(&(r.media_type), &msg::MediaType::JavaScript); - assert_eq!( - fs::read_to_string(&mime_file_name).unwrap(), - "text/javascript" - ); - - // Modify .mime - let _ = fs::write(&mime_file_name, "text/typescript"); - let result2 = deno_dir.get_source_code(module_name, &filename); - assert!(result2.is_ok()); - let r2 = result2.unwrap(); - let expected2 = "export const loaded = true;\n".as_bytes(); - assert_eq!(r2.source_code, expected2); - // If get_source_code does not call remote, this should be TypeScript - // as we modified before! (we do not overwrite .mime due to no http fetch) - assert_eq!(&(r2.media_type), &msg::MediaType::TypeScript); - assert_eq!( - fs::read_to_string(&mime_file_name).unwrap(), - "text/typescript" - ); - - // Force self.reload - let deno_dir = - DenoDir::new(true, false, Some(temp_dir.path().to_path_buf())) - .expect("setup fail"); - let result3 = deno_dir.get_source_code(module_name, &filename); - assert!(result3.is_ok()); - let r3 = result3.unwrap(); - let expected3 = "export const loaded = true;\n".as_bytes(); - assert_eq!(r3.source_code, expected3); - // Now the old .mime file should be overwritten back to JavaScript! - // (due to http fetch) - assert_eq!(&(r3.media_type), &msg::MediaType::JavaScript); - assert_eq!( - fs::read_to_string(&mime_file_name).unwrap(), - "text/javascript" - ); - }); - } - - #[test] - fn test_fetch_source_1() { - use crate::tokio_util; - // http_util::fetch_sync_string requires tokio - tokio_util::init(|| { - let (_temp_dir, deno_dir) = test_setup(false, false); - let module_name = - "http://localhost:4545/tests/subdir/mt_video_mp2t.t3.ts"; - let filename = deno_fs::normalize_path( - deno_dir - .deps_http - .join("localhost_PORT4545/tests/subdir/mt_video_mp2t.t3.ts") - .as_ref(), - ); - let mime_file_name = format!("{}.mime", &filename); - - let result = deno_dir.fetch_remote_source(module_name, &filename); - assert!(result.is_ok()); - let r = result.unwrap().unwrap(); - assert_eq!(r.source_code, "export const loaded = true;\n".as_bytes()); - assert_eq!(&(r.media_type), &msg::MediaType::TypeScript); - // matching ext, no .mime file created - assert!(fs::read_to_string(&mime_file_name).is_err()); - - // Modify .mime, make sure read from local - let _ = fs::write(&mime_file_name, "text/javascript"); - let result2 = deno_dir.fetch_local_source(module_name, &filename); - assert!(result2.is_ok()); - let r2 = result2.unwrap().unwrap(); - assert_eq!(r2.source_code, "export const loaded = true;\n".as_bytes()); - // Not MediaType::TypeScript due to .mime modification - assert_eq!(&(r2.media_type), &msg::MediaType::JavaScript); - }); - } - - #[test] - fn test_fetch_source_2() { - use crate::tokio_util; - // http_util::fetch_sync_string requires tokio - tokio_util::init(|| { - let (_temp_dir, deno_dir) = test_setup(false, false); - let module_name = "http://localhost:4545/tests/subdir/no_ext"; - let filename = deno_fs::normalize_path( - deno_dir - .deps_http - .join("localhost_PORT4545/tests/subdir/no_ext") - .as_ref(), - ); - let mime_file_name = format!("{}.mime", &filename); - let result = deno_dir.fetch_remote_source(module_name, &filename); - assert!(result.is_ok()); - let r = result.unwrap().unwrap(); - assert_eq!(r.source_code, "export const loaded = true;\n".as_bytes()); - assert_eq!(&(r.media_type), &msg::MediaType::TypeScript); - // no ext, should create .mime file - assert_eq!( - fs::read_to_string(&mime_file_name).unwrap(), - "text/typescript" - ); - - let module_name_2 = "http://localhost:4545/tests/subdir/mismatch_ext.ts"; - let filename_2 = deno_fs::normalize_path( - deno_dir - .deps_http - .join("localhost_PORT4545/tests/subdir/mismatch_ext.ts") - .as_ref(), - ); - let mime_file_name_2 = format!("{}.mime", &filename_2); - let result_2 = deno_dir.fetch_remote_source(module_name_2, &filename_2); - assert!(result_2.is_ok()); - let r2 = result_2.unwrap().unwrap(); - assert_eq!(r2.source_code, "export const loaded = true;\n".as_bytes()); - assert_eq!(&(r2.media_type), &msg::MediaType::JavaScript); - // mismatch ext, should create .mime file - assert_eq!( - fs::read_to_string(&mime_file_name_2).unwrap(), - "text/javascript" - ); - - // test unknown extension - let module_name_3 = "http://localhost:4545/tests/subdir/unknown_ext.deno"; - let filename_3 = deno_fs::normalize_path( - deno_dir - .deps_http - .join("localhost_PORT4545/tests/subdir/unknown_ext.deno") - .as_ref(), - ); - let mime_file_name_3 = format!("{}.mime", &filename_3); - let result_3 = deno_dir.fetch_remote_source(module_name_3, &filename_3); - assert!(result_3.is_ok()); - let r3 = result_3.unwrap().unwrap(); - assert_eq!(r3.source_code, "export const loaded = true;\n".as_bytes()); - assert_eq!(&(r3.media_type), &msg::MediaType::TypeScript); - // unknown ext, should create .mime file - assert_eq!( - fs::read_to_string(&mime_file_name_3).unwrap(), - "text/typescript" - ); - }); - } - - #[test] - fn test_fetch_source_3() { - // only local, no http_util::fetch_sync_string called - let (_temp_dir, deno_dir) = test_setup(false, false); - let cwd = std::env::current_dir().unwrap(); - let cwd_string = cwd.to_str().unwrap(); - let module_name = "http://example.com/mt_text_typescript.t1.ts"; // not used - let filename = - format!("{}/tests/subdir/mt_text_typescript.t1.ts", &cwd_string); - - let result = deno_dir.fetch_local_source(module_name, &filename); - assert!(result.is_ok()); - let r = result.unwrap().unwrap(); - assert_eq!(r.source_code, "export const loaded = true;\n".as_bytes()); - assert_eq!(&(r.media_type), &msg::MediaType::TypeScript); - } - - #[test] - fn test_fetch_module_meta_data() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let cwd = std::env::current_dir().unwrap(); - let cwd_string = String::from(cwd.to_str().unwrap()) + "/"; - - // Test failure case. - let specifier = "hello.ts"; - let referrer = add_root!("/baddir/badfile.ts"); - let r = deno_dir.fetch_module_meta_data(specifier, referrer); - assert!(r.is_err()); - - // Assuming cwd is the deno repo root. - let specifier = "./js/main.ts"; - let referrer = cwd_string.as_str(); - let r = deno_dir.fetch_module_meta_data(specifier, referrer); - assert!(r.is_ok()); - //let fetch_module_meta_data_output = r.unwrap(); - //println!("fetch_module_meta_data_output {:?}", fetch_module_meta_data_output); - } - - #[test] - fn test_fetch_module_meta_data_1() { - /*recompile ts file*/ - let (_temp_dir, deno_dir) = test_setup(false, true); - - let cwd = std::env::current_dir().unwrap(); - let cwd_string = String::from(cwd.to_str().unwrap()) + "/"; - - // Test failure case. - let specifier = "hello.ts"; - let referrer = add_root!("/baddir/badfile.ts"); - let r = deno_dir.fetch_module_meta_data(specifier, referrer); - assert!(r.is_err()); - - // Assuming cwd is the deno repo root. - let specifier = "./js/main.ts"; - let referrer = cwd_string.as_str(); - let r = deno_dir.fetch_module_meta_data(specifier, referrer); - assert!(r.is_ok()); - } - - #[test] - fn test_src_file_to_url_1() { - let (_temp_dir, deno_dir) = test_setup(false, false); - assert_eq!("hello", deno_dir.src_file_to_url("hello")); - assert_eq!("/hello", deno_dir.src_file_to_url("/hello")); - let x = deno_dir.deps_http.join("hello/world.txt"); - assert_eq!( - "http://hello/world.txt", - deno_dir.src_file_to_url(x.to_str().unwrap()) - ); - } - - #[test] - fn test_src_file_to_url_2() { - let (_temp_dir, deno_dir) = test_setup(false, false); - assert_eq!("hello", deno_dir.src_file_to_url("hello")); - assert_eq!("/hello", deno_dir.src_file_to_url("/hello")); - let x = deno_dir.deps_https.join("hello/world.txt"); - assert_eq!( - "https://hello/world.txt", - deno_dir.src_file_to_url(x.to_str().unwrap()) - ); - } - - #[test] - fn test_src_file_to_url_3() { - let (_temp_dir, deno_dir) = test_setup(false, false); - let x = deno_dir.deps_http.join("localhost_PORT4545/world.txt"); - assert_eq!( - "http://localhost:4545/world.txt", - deno_dir.src_file_to_url(x.to_str().unwrap()) - ); - } - - #[test] - fn test_src_file_to_url_4() { - let (_temp_dir, deno_dir) = test_setup(false, false); - let x = deno_dir.deps_https.join("localhost_PORT4545/world.txt"); - assert_eq!( - "https://localhost:4545/world.txt", - deno_dir.src_file_to_url(x.to_str().unwrap()) - ); - } - - // https://github.com/denoland/deno/blob/golang/os_test.go#L16-L87 - #[test] - fn test_resolve_module_1() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let test_cases = [ - ( - "./subdir/print_hello.ts", - add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/006_url_imports.ts"), - file_url!("/Users/rld/go/src/github.com/denoland/deno/testdata/subdir/print_hello.ts"), - add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/subdir/print_hello.ts"), - ), - ( - "testdata/001_hello.js", - add_root!("/Users/rld/go/src/github.com/denoland/deno/"), - file_url!("/Users/rld/go/src/github.com/denoland/deno/testdata/001_hello.js"), - add_root!("/Users/rld/go/src/github.com/denoland/deno/testdata/001_hello.js"), - ), - ( - add_root!("/Users/rld/src/deno/hello.js"), - ".", - file_url!("/Users/rld/src/deno/hello.js"), - add_root!("/Users/rld/src/deno/hello.js"), - ), - ( - add_root!("/this/module/got/imported.js"), - add_root!("/that/module/did/it.js"), - file_url!("/this/module/got/imported.js"), - add_root!("/this/module/got/imported.js"), - ), - ]; - for &test in test_cases.iter() { - let specifier = String::from(test.0); - let referrer = String::from(test.1); - let (module_name, filename) = - deno_dir.resolve_module(&specifier, &referrer).unwrap(); - assert_eq!(module_name, test.2); - assert_eq!(filename, test.3); - } - } - - #[test] - fn test_resolve_module_2() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let specifier = "http://localhost:4545/testdata/subdir/print_hello.ts"; - let referrer = add_root!("/deno/testdata/006_url_imports.ts"); - - let expected_module_name = - "http://localhost:4545/testdata/subdir/print_hello.ts"; - let expected_filename = deno_fs::normalize_path( - deno_dir - .deps_http - .join("localhost_PORT4545/testdata/subdir/print_hello.ts") - .as_ref(), - ); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, referrer).unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - } - - #[test] - fn test_resolve_module_3() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let specifier_ = - deno_dir.deps_http.join("unpkg.com/liltest@0.0.5/index.ts"); - let specifier = specifier_.to_str().unwrap(); - let referrer = "."; - - let expected_module_name = "http://unpkg.com/liltest@0.0.5/index.ts"; - let expected_filename = deno_fs::normalize_path( - deno_dir - .deps_http - .join("unpkg.com/liltest@0.0.5/index.ts") - .as_ref(), - ); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, referrer).unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - } - - #[test] - fn test_resolve_module_4() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let specifier = "./util"; - let referrer_ = deno_dir.deps_http.join("unpkg.com/liltest@0.0.5/index.ts"); - let referrer = referrer_.to_str().unwrap(); - - // http containing files -> load relative import with http - let expected_module_name = "http://unpkg.com/liltest@0.0.5/util"; - let expected_filename = deno_fs::normalize_path( - deno_dir - .deps_http - .join("unpkg.com/liltest@0.0.5/util") - .as_ref(), - ); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, referrer).unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - } - - #[test] - fn test_resolve_module_5() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let specifier = "./util"; - let referrer_ = - deno_dir.deps_https.join("unpkg.com/liltest@0.0.5/index.ts"); - let referrer = referrer_.to_str().unwrap(); - - // https containing files -> load relative import with https - let expected_module_name = "https://unpkg.com/liltest@0.0.5/util"; - let expected_filename = deno_fs::normalize_path( - deno_dir - .deps_https - .join("unpkg.com/liltest@0.0.5/util") - .as_ref(), - ); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, referrer).unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - } - - #[test] - fn test_resolve_module_6() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let specifier = "http://localhost:4545/tests/subdir/mod2.ts"; - let referrer = add_root!("/deno/tests/006_url_imports.ts"); - let expected_module_name = "http://localhost:4545/tests/subdir/mod2.ts"; - let expected_filename = deno_fs::normalize_path( - deno_dir - .deps_http - .join("localhost_PORT4545/tests/subdir/mod2.ts") - .as_ref(), - ); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, referrer).unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - } - - #[test] - fn test_resolve_module_7() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let specifier = "http_test.ts"; - let referrer = add_root!("/Users/rld/src/deno_net/"); - let expected_module_name = - file_url!("/Users/rld/src/deno_net/http_test.ts"); - let expected_filename = add_root!("/Users/rld/src/deno_net/http_test.ts"); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, referrer).unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - } - - #[test] - fn test_resolve_module_referrer_dot() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let specifier = "tests/001_hello.js"; - - let cwd = std::env::current_dir().unwrap(); - let expected_path = cwd.join(specifier); - let expected_module_name = - Url::from_file_path(&expected_path).unwrap().to_string(); - let expected_filename = deno_fs::normalize_path(&expected_path); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, ".").unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, "./").unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - } - - #[test] - fn test_resolve_module_referrer_dotdot() { - let (_temp_dir, deno_dir) = test_setup(false, false); - - let specifier = "tests/001_hello.js"; - - let cwd = std::env::current_dir().unwrap(); - let expected_path = cwd.join("..").join(specifier); - let expected_module_name = - Url::from_file_path(&expected_path).unwrap().to_string(); - let expected_filename = deno_fs::normalize_path(&expected_path); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, "..").unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - - let (module_name, filename) = - deno_dir.resolve_module(specifier, "../").unwrap(); - assert_eq!(module_name, expected_module_name); - assert_eq!(filename, expected_filename); - } - - #[test] - fn test_map_file_extension() { - assert_eq!( - map_file_extension(Path::new("foo/bar.ts")), - msg::MediaType::TypeScript - ); - assert_eq!( - map_file_extension(Path::new("foo/bar.d.ts")), - msg::MediaType::TypeScript - ); - assert_eq!( - map_file_extension(Path::new("foo/bar.js")), - msg::MediaType::JavaScript - ); - assert_eq!( - map_file_extension(Path::new("foo/bar.json")), - msg::MediaType::Json - ); - assert_eq!( - map_file_extension(Path::new("foo/bar.txt")), - msg::MediaType::Unknown - ); - assert_eq!( - map_file_extension(Path::new("foo/bar")), - msg::MediaType::Unknown - ); - } - - #[test] - fn test_map_content_type() { - // Extension only - assert_eq!( - map_content_type(Path::new("foo/bar.ts"), None), - msg::MediaType::TypeScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar.d.ts"), None), - msg::MediaType::TypeScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar.js"), None), - msg::MediaType::JavaScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar.json"), None), - msg::MediaType::Json - ); - assert_eq!( - map_content_type(Path::new("foo/bar.txt"), None), - msg::MediaType::Unknown - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), None), - msg::MediaType::Unknown - ); - - // Media Type - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("application/typescript")), - msg::MediaType::TypeScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("text/typescript")), - msg::MediaType::TypeScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("video/vnd.dlna.mpeg-tts")), - msg::MediaType::TypeScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("video/mp2t")), - msg::MediaType::TypeScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("application/x-typescript")), - msg::MediaType::TypeScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("application/javascript")), - msg::MediaType::JavaScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("text/javascript")), - msg::MediaType::JavaScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("application/ecmascript")), - msg::MediaType::JavaScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("text/ecmascript")), - msg::MediaType::JavaScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("application/x-javascript")), - msg::MediaType::JavaScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("application/json")), - msg::MediaType::Json - ); - assert_eq!( - map_content_type(Path::new("foo/bar"), Some("text/json")), - msg::MediaType::Json - ); - assert_eq!( - map_content_type(Path::new("foo/bar.ts"), Some("text/plain")), - msg::MediaType::TypeScript - ); - assert_eq!( - map_content_type(Path::new("foo/bar.ts"), Some("foo/bar")), - msg::MediaType::Unknown - ); - } - - #[test] - fn test_filter_shebang() { - assert_eq!(filter_shebang("#!".as_bytes().to_owned()), "".as_bytes()); - assert_eq!( - filter_shebang("#!\n\n".as_bytes().to_owned()), - "\n\n".as_bytes() - ); - let code = "#!/usr/bin/env deno\nconsole.log('hello');\n" - .as_bytes() - .to_owned(); - assert_eq!(filter_shebang(code), "\nconsole.log('hello');\n".as_bytes()); - } -} diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 65118d070..000000000 --- a/src/errors.rs +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::js_errors::JSErrorColor; -pub use crate::msg::ErrorKind; -use crate::resolve_addr::ResolveAddrError; -use deno_core::JSError; -use hyper; -use std; -use std::fmt; -use std::io; -use url; - -pub type DenoResult = std::result::Result; - -#[derive(Debug)] -pub struct DenoError { - repr: Repr, -} - -#[derive(Debug)] -enum Repr { - Simple(ErrorKind, String), - IoErr(io::Error), - UrlErr(url::ParseError), - HyperErr(hyper::Error), -} - -pub fn new(kind: ErrorKind, msg: String) -> DenoError { - DenoError { - repr: Repr::Simple(kind, msg), - } -} - -impl DenoError { - pub fn kind(&self) -> ErrorKind { - match self.repr { - Repr::Simple(kind, ref _msg) => kind, - // Repr::Simple(kind) => kind, - Repr::IoErr(ref err) => { - use std::io::ErrorKind::*; - match err.kind() { - NotFound => ErrorKind::NotFound, - PermissionDenied => ErrorKind::PermissionDenied, - ConnectionRefused => ErrorKind::ConnectionRefused, - ConnectionReset => ErrorKind::ConnectionReset, - ConnectionAborted => ErrorKind::ConnectionAborted, - NotConnected => ErrorKind::NotConnected, - AddrInUse => ErrorKind::AddrInUse, - AddrNotAvailable => ErrorKind::AddrNotAvailable, - BrokenPipe => ErrorKind::BrokenPipe, - AlreadyExists => ErrorKind::AlreadyExists, - WouldBlock => ErrorKind::WouldBlock, - InvalidInput => ErrorKind::InvalidInput, - InvalidData => ErrorKind::InvalidData, - TimedOut => ErrorKind::TimedOut, - Interrupted => ErrorKind::Interrupted, - WriteZero => ErrorKind::WriteZero, - Other => ErrorKind::Other, - UnexpectedEof => ErrorKind::UnexpectedEof, - _ => unreachable!(), - } - } - Repr::UrlErr(ref err) => { - use url::ParseError::*; - match err { - EmptyHost => ErrorKind::EmptyHost, - IdnaError => ErrorKind::IdnaError, - InvalidPort => ErrorKind::InvalidPort, - InvalidIpv4Address => ErrorKind::InvalidIpv4Address, - InvalidIpv6Address => ErrorKind::InvalidIpv6Address, - InvalidDomainCharacter => ErrorKind::InvalidDomainCharacter, - RelativeUrlWithoutBase => ErrorKind::RelativeUrlWithoutBase, - RelativeUrlWithCannotBeABaseBase => { - ErrorKind::RelativeUrlWithCannotBeABaseBase - } - SetHostOnCannotBeABaseUrl => ErrorKind::SetHostOnCannotBeABaseUrl, - Overflow => ErrorKind::Overflow, - } - } - Repr::HyperErr(ref err) => { - // For some reason hyper::errors::Kind is private. - if err.is_parse() { - ErrorKind::HttpParse - } else if err.is_user() { - ErrorKind::HttpUser - } else if err.is_canceled() { - ErrorKind::HttpCanceled - } else if err.is_closed() { - ErrorKind::HttpClosed - } else { - ErrorKind::HttpOther - } - } - } - } -} - -impl fmt::Display for DenoError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.repr { - Repr::Simple(_kind, ref err_str) => f.pad(err_str), - Repr::IoErr(ref err) => err.fmt(f), - Repr::UrlErr(ref err) => err.fmt(f), - Repr::HyperErr(ref err) => err.fmt(f), - } - } -} - -impl std::error::Error for DenoError { - fn description(&self) -> &str { - match self.repr { - Repr::Simple(_kind, ref msg) => msg.as_str(), - Repr::IoErr(ref err) => err.description(), - Repr::UrlErr(ref err) => err.description(), - Repr::HyperErr(ref err) => err.description(), - } - } - - fn cause(&self) -> Option<&dyn std::error::Error> { - match self.repr { - Repr::Simple(_kind, ref _msg) => None, - Repr::IoErr(ref err) => Some(err), - Repr::UrlErr(ref err) => Some(err), - Repr::HyperErr(ref err) => Some(err), - } - } -} - -impl From for DenoError { - #[inline] - fn from(err: io::Error) -> Self { - Self { - repr: Repr::IoErr(err), - } - } -} - -impl From for DenoError { - #[inline] - fn from(err: url::ParseError) -> Self { - Self { - repr: Repr::UrlErr(err), - } - } -} - -impl From for DenoError { - #[inline] - fn from(err: hyper::Error) -> Self { - Self { - repr: Repr::HyperErr(err), - } - } -} - -impl From for DenoError { - fn from(e: ResolveAddrError) -> Self { - match e { - ResolveAddrError::Syntax => Self { - repr: Repr::Simple( - ErrorKind::InvalidInput, - "invalid address syntax".to_string(), - ), - }, - ResolveAddrError::Resolution(io_err) => Self { - repr: Repr::IoErr(io_err), - }, - } - } -} - -pub fn bad_resource() -> DenoError { - new(ErrorKind::BadResource, String::from("bad resource id")) -} - -pub fn permission_denied() -> DenoError { - new( - ErrorKind::PermissionDenied, - String::from("permission denied"), - ) -} - -#[derive(Debug)] -pub enum RustOrJsError { - Rust(DenoError), - Js(JSError), -} - -impl From for RustOrJsError { - fn from(e: DenoError) -> Self { - RustOrJsError::Rust(e) - } -} - -impl From for RustOrJsError { - fn from(e: JSError) -> Self { - RustOrJsError::Js(e) - } -} - -impl fmt::Display for RustOrJsError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - RustOrJsError::Rust(e) => e.fmt(f), - RustOrJsError::Js(e) => JSErrorColor(e).fmt(f), - } - } -} diff --git a/src/flags.rs b/src/flags.rs deleted file mode 100644 index d6a63a9fb..000000000 --- a/src/flags.rs +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use deno_core::v8_set_flags; -use getopts; -use getopts::Options; - -// Creates vector of strings, Vec -#[cfg(test)] -macro_rules! svec { - ($($x:expr),*) => (vec![$($x.to_string()),*]); -} - -#[cfg_attr(feature = "cargo-clippy", allow(stutter))] -#[derive(Clone, Debug, PartialEq, Default)] -pub struct DenoFlags { - pub help: bool, - pub log_debug: bool, - pub version: bool, - pub reload: bool, - pub recompile: bool, - pub allow_read: bool, - pub allow_write: bool, - pub allow_net: bool, - pub allow_env: bool, - pub allow_run: bool, - pub no_prompts: bool, - pub types: bool, - pub prefetch: bool, - pub info: bool, - pub fmt: bool, -} - -pub fn get_usage(opts: &Options) -> String { - format!( - "Usage: deno script.ts {} -Environment variables: - DENO_DIR Set deno's base directory - NO_COLOR Set to disable color", - opts.usage("") - ) -} - -/// Checks provided arguments for known options and sets appropriate Deno flags -/// for them. Unknown options are returned for further use. -/// Note: -/// -/// 1. This assumes that privileged flags do not accept parameters deno --foo bar. -/// This assumption is currently valid. But if it were to change in the future, -/// this parsing technique would need to be modified. I think we want to keep the -/// privileged flags minimal - so having this restriction is maybe a good thing. -/// -/// 2. Misspelled flags will be forwarded to user code - e.g. --allow-ne would -/// not cause an error. I also think this is ok because missing any of the -/// privileged flags is not destructive. Userland flag parsing would catch these -/// errors. -fn set_recognized_flags( - opts: &Options, - flags: &mut DenoFlags, - args: Vec, -) -> Result, getopts::Fail> { - let mut rest = Vec::::new(); - // getopts doesn't allow parsing unknown options so we check them - // one-by-one and handle unrecognized ones manually - // better solution welcome! - for arg in args { - let fake_args = vec![arg]; - match opts.parse(&fake_args) { - Err(getopts::Fail::UnrecognizedOption(_)) => { - rest.extend(fake_args); - } - Err(e) => { - return Err(e); - } - Ok(matches) => { - if matches.opt_present("help") { - flags.help = true; - } - if matches.opt_present("log-debug") { - flags.log_debug = true; - } - if matches.opt_present("version") { - flags.version = true; - } - if matches.opt_present("reload") { - flags.reload = true; - } - if matches.opt_present("recompile") { - flags.recompile = true; - } - if matches.opt_present("allow-read") { - flags.allow_read = true; - } - if matches.opt_present("allow-write") { - flags.allow_write = true; - } - if matches.opt_present("allow-net") { - flags.allow_net = true; - } - if matches.opt_present("allow-env") { - flags.allow_env = true; - } - if matches.opt_present("allow-run") { - flags.allow_run = true; - } - if matches.opt_present("allow-all") { - flags.allow_read = true; - flags.allow_env = true; - flags.allow_net = true; - flags.allow_run = true; - flags.allow_read = true; - flags.allow_write = true; - } - if matches.opt_present("no-prompt") { - flags.no_prompts = true; - } - if matches.opt_present("types") { - flags.types = true; - } - if matches.opt_present("prefetch") { - flags.prefetch = true; - } - if matches.opt_present("info") { - flags.info = true; - } - if matches.opt_present("fmt") { - flags.fmt = true; - } - - if !matches.free.is_empty() { - rest.extend(matches.free); - } - } - } - } - Ok(rest) -} - -#[cfg_attr(feature = "cargo-clippy", allow(stutter))] -pub fn set_flags( - args: Vec, -) -> Result<(DenoFlags, Vec, String), String> { - // TODO: all flags passed after "--" are swallowed by v8_set_flags - // eg. deno --allow-net ./test.ts -- --title foobar - // args === ["deno", "--allow-net" "./test.ts"] - let args = v8_set_flags(args); - - let mut opts = Options::new(); - // TODO(kevinkassimo): v8_set_flags intercepts '-help' with single '-' - // Resolve that and then uncomment line below (enabling Go style -long-flag) - // opts.long_only(true); - opts.optflag("", "allow-read", "Allow file system read access"); - opts.optflag("", "allow-write", "Allow file system write access"); - opts.optflag("", "allow-net", "Allow network access"); - opts.optflag("", "allow-env", "Allow environment access"); - opts.optflag("", "allow-run", "Allow running subprocesses"); - opts.optflag("A", "allow-all", "Allow all permissions"); - opts.optflag("", "no-prompt", "Do not use prompts"); - opts.optflag("", "recompile", "Force recompilation of TypeScript code"); - opts.optflag("h", "help", "Print this message"); - opts.optflag("D", "log-debug", "Log debug output"); - opts.optflag("v", "version", "Print the version"); - opts.optflag("r", "reload", "Reload cached remote resources"); - opts.optflag("", "v8-options", "Print V8 command line options"); - opts.optflag("", "types", "Print runtime TypeScript declarations"); - opts.optflag("", "prefetch", "Prefetch the dependencies"); - opts.optflag("", "info", "Show source file related info"); - opts.optflag("", "fmt", "Format code"); - - let mut flags = DenoFlags::default(); - - let rest = - set_recognized_flags(&opts, &mut flags, args).map_err(|e| e.to_string())?; - Ok((flags, rest, get_usage(&opts))) -} - -#[test] -fn test_set_flags_1() { - let (flags, rest, _) = set_flags(svec!["deno", "--version"]).unwrap(); - assert_eq!(rest, svec!["deno"]); - assert_eq!( - flags, - DenoFlags { - version: true, - ..DenoFlags::default() - } - ); -} - -#[test] -fn test_set_flags_2() { - let (flags, rest, _) = - set_flags(svec!["deno", "-r", "-D", "script.ts"]).unwrap(); - assert_eq!(rest, svec!["deno", "script.ts"]); - assert_eq!( - flags, - DenoFlags { - log_debug: true, - reload: true, - ..DenoFlags::default() - } - ); -} - -#[test] -fn test_set_flags_3() { - let (flags, rest, _) = - set_flags(svec!["deno", "-r", "script.ts", "--allow-write"]).unwrap(); - assert_eq!(rest, svec!["deno", "script.ts"]); - assert_eq!( - flags, - DenoFlags { - reload: true, - allow_write: true, - ..DenoFlags::default() - } - ); -} - -#[test] -fn test_set_flags_4() { - let (flags, rest, _) = - set_flags(svec!["deno", "-Dr", "script.ts", "--allow-write"]).unwrap(); - assert_eq!(rest, svec!["deno", "script.ts"]); - assert_eq!( - flags, - DenoFlags { - log_debug: true, - reload: true, - allow_write: true, - ..DenoFlags::default() - } - ); -} - -#[test] -fn test_set_flags_5() { - let (flags, rest, _) = set_flags(svec!["deno", "--types"]).unwrap(); - assert_eq!(rest, svec!["deno"]); - assert_eq!( - flags, - DenoFlags { - types: true, - ..DenoFlags::default() - } - ) -} - -#[test] -fn test_set_flags_6() { - let (flags, rest, _) = - set_flags(svec!["deno", "gist.ts", "--title", "X", "--allow-net"]).unwrap(); - assert_eq!(rest, svec!["deno", "gist.ts", "--title", "X"]); - assert_eq!( - flags, - DenoFlags { - allow_net: true, - ..DenoFlags::default() - } - ) -} - -#[test] -fn test_set_flags_7() { - let (flags, rest, _) = - set_flags(svec!["deno", "gist.ts", "--allow-all"]).unwrap(); - assert_eq!(rest, svec!["deno", "gist.ts"]); - assert_eq!( - flags, - DenoFlags { - allow_net: true, - allow_env: true, - allow_run: true, - allow_read: true, - allow_write: true, - ..DenoFlags::default() - } - ) -} - -#[test] -fn test_set_flags_8() { - let (flags, rest, _) = - set_flags(svec!["deno", "gist.ts", "--allow-read"]).unwrap(); - assert_eq!(rest, svec!["deno", "gist.ts"]); - assert_eq!( - flags, - DenoFlags { - allow_read: true, - ..DenoFlags::default() - } - ) -} diff --git a/src/fs.rs b/src/fs.rs deleted file mode 100644 index ff0da95e5..000000000 --- a/src/fs.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use std; -use std::fs::{create_dir, DirBuilder, File, OpenOptions}; -use std::io::ErrorKind; -use std::io::Write; -use std::path::{Path, PathBuf}; - -use rand; -use rand::Rng; - -#[cfg(any(unix))] -use std::os::unix::fs::DirBuilderExt; -#[cfg(any(unix))] -use std::os::unix::fs::PermissionsExt; - -pub fn write_file>( - filename: &Path, - data: T, - perm: u32, -) -> std::io::Result<()> { - write_file_2(filename, data, true, perm, true, false) -} - -pub fn write_file_2>( - filename: &Path, - data: T, - update_perm: bool, - perm: u32, - is_create: bool, - is_append: bool, -) -> std::io::Result<()> { - let mut file = OpenOptions::new() - .read(false) - .write(true) - .append(is_append) - .truncate(!is_append) - .create(is_create) - .open(filename)?; - - if update_perm { - set_permissions(&mut file, perm)?; - } - - file.write_all(data.as_ref()) -} - -#[cfg(any(unix))] -fn set_permissions(file: &mut File, perm: u32) -> std::io::Result<()> { - debug!("set file perm to {}", perm); - file.set_permissions(PermissionsExt::from_mode(perm & 0o777)) -} -#[cfg(not(any(unix)))] -fn set_permissions(_file: &mut File, _perm: u32) -> std::io::Result<()> { - // NOOP on windows - Ok(()) -} - -pub fn make_temp_dir( - dir: Option<&Path>, - prefix: Option<&str>, - suffix: Option<&str>, -) -> std::io::Result { - let prefix_ = prefix.unwrap_or(""); - let suffix_ = suffix.unwrap_or(""); - let mut buf: PathBuf = match dir { - Some(ref p) => p.to_path_buf(), - None => std::env::temp_dir(), - }.join("_"); - let mut rng = rand::thread_rng(); - loop { - let unique = rng.gen::(); - buf.set_file_name(format!("{}{:08x}{}", prefix_, unique, suffix_)); - // TODO: on posix, set mode flags to 0o700. - let r = create_dir(buf.as_path()); - match r { - Err(ref e) if e.kind() == ErrorKind::AlreadyExists => continue, - Ok(_) => return Ok(buf), - Err(e) => return Err(e), - } - } -} - -pub fn mkdir(path: &Path, perm: u32, recursive: bool) -> std::io::Result<()> { - debug!("mkdir -p {}", path.display()); - let mut builder = DirBuilder::new(); - builder.recursive(recursive); - set_dir_permission(&mut builder, perm); - builder.create(path) -} - -#[cfg(any(unix))] -fn set_dir_permission(builder: &mut DirBuilder, perm: u32) { - debug!("set dir perm to {}", perm); - builder.mode(perm & 0o777); -} - -#[cfg(not(any(unix)))] -fn set_dir_permission(_builder: &mut DirBuilder, _perm: u32) { - // NOOP on windows -} - -pub fn normalize_path(path: &Path) -> String { - let s = String::from(path.to_str().unwrap()); - if cfg!(windows) { - // TODO This isn't correct. Probbly should iterate over components. - s.replace("\\", "/") - } else { - s - } -} diff --git a/src/global_timer.rs b/src/global_timer.rs deleted file mode 100644 index eef70ddc2..000000000 --- a/src/global_timer.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - -//! This module helps deno implement timers. -//! -//! As an optimization, we want to avoid an expensive calls into rust for every -//! setTimeout in JavaScript. Thus in //js/timers.ts a data structure is -//! implemented that calls into Rust for only the smallest timeout. Thus we -//! only need to be able to start and cancel a single timer (or Delay, as Tokio -//! calls it) for an entire Isolate. This is what is implemented here. - -use crate::tokio_util::panic_on_error; -use futures::Future; -use std::time::Instant; -use tokio::sync::oneshot; -use tokio::timer::Delay; - -pub struct GlobalTimer { - tx: Option>, -} - -impl GlobalTimer { - pub fn new() -> Self { - Self { tx: None } - } - - pub fn cancel(&mut self) { - if let Some(tx) = self.tx.take() { - tx.send(()).ok(); - } - } - - pub fn new_timeout( - &mut self, - deadline: Instant, - ) -> impl Future { - if self.tx.is_some() { - self.cancel(); - } - assert!(self.tx.is_none()); - - let (tx, rx) = oneshot::channel(); - self.tx = Some(tx); - - let delay = panic_on_error(Delay::new(deadline)); - let rx = panic_on_error(rx); - - delay.select(rx).then(|_| Ok(())) - } -} diff --git a/src/http_body.rs b/src/http_body.rs deleted file mode 100644 index 235463ff1..000000000 --- a/src/http_body.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - -use futures::Async; -use futures::Poll; -use hyper::body::Payload; -use hyper::Body; -use hyper::Chunk; -use std::cmp::min; -use std::io; -use std::io::Read; -use tokio::io::AsyncRead; - -/// Wraps `hyper::Body` so that it can be exposed as an `AsyncRead` and integrated -/// into resources more easily. -pub struct HttpBody { - body: Body, - chunk: Option, - pos: usize, -} - -impl HttpBody { - pub fn from(body: Body) -> Self { - Self { - body, - chunk: None, - pos: 0, - } - } -} - -impl Read for HttpBody { - fn read(&mut self, _buf: &mut [u8]) -> io::Result { - unimplemented!(); - } -} - -impl AsyncRead for HttpBody { - fn poll_read(&mut self, buf: &mut [u8]) -> Poll { - if let Some(chunk) = self.chunk.take() { - debug!( - "HttpBody Fake Read buf {} chunk {} pos {}", - buf.len(), - chunk.len(), - self.pos - ); - let n = min(buf.len(), chunk.len() - self.pos); - { - let rest = &chunk[self.pos..]; - buf[..n].clone_from_slice(&rest[..n]); - } - self.pos += n; - if self.pos == chunk.len() { - self.pos = 0; - } else { - self.chunk = Some(chunk); - } - return Ok(Async::Ready(n)); - } else { - assert_eq!(self.pos, 0); - } - - let p = self.body.poll_data(); - match p { - Err(e) => Err( - // TODO Need to map hyper::Error into std::io::Error. - io::Error::new(io::ErrorKind::Other, e), - ), - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(maybe_chunk)) => match maybe_chunk { - None => Ok(Async::Ready(0)), - Some(chunk) => { - debug!( - "HttpBody Real Read buf {} chunk {} pos {}", - buf.len(), - chunk.len(), - self.pos - ); - let n = min(buf.len(), chunk.len()); - buf[..n].clone_from_slice(&chunk[..n]); - if buf.len() < chunk.len() { - self.pos = n; - self.chunk = Some(chunk); - } - Ok(Async::Ready(n)) - } - }, - } - } -} - -#[test] -fn test_body_async_read() { - use std::str::from_utf8; - let body = Body::from("hello world"); - let mut body = HttpBody::from(body); - - let buf = &mut [0, 0, 0, 0, 0]; - let r = body.poll_read(buf); - assert!(r.is_ok()); - assert_eq!(r.unwrap(), Async::Ready(5)); - assert_eq!(from_utf8(buf).unwrap(), "hello"); - - let r = body.poll_read(buf); - assert!(r.is_ok()); - assert_eq!(r.unwrap(), Async::Ready(5)); - assert_eq!(from_utf8(buf).unwrap(), " worl"); - - let r = body.poll_read(buf); - assert!(r.is_ok()); - assert_eq!(r.unwrap(), Async::Ready(1)); - assert_eq!(from_utf8(&buf[0..1]).unwrap(), "d"); -} diff --git a/src/http_util.rs b/src/http_util.rs deleted file mode 100644 index 8aadbe136..000000000 --- a/src/http_util.rs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::errors; -use crate::errors::{DenoError, DenoResult}; -use crate::tokio_util; - -use futures::future::{loop_fn, Loop}; -use futures::{future, Future, Stream}; -use hyper; -use hyper::client::{Client, HttpConnector}; -use hyper::header::CONTENT_TYPE; -use hyper::Uri; -use hyper_rustls; - -type Connector = hyper_rustls::HttpsConnector; - -lazy_static! { - static ref CONNECTOR: Connector = { - let num_dns_threads = 4; - Connector::new(num_dns_threads) - }; -} - -pub fn get_client() -> Client { - // TODO use Hyper's connection pool. - let c = CONNECTOR.clone(); - Client::builder().build(c) -} - -/// Construct the next uri based on base uri and location header fragment -/// See -fn resolve_uri_from_location(base_uri: &Uri, location: &str) -> Uri { - if location.starts_with("http://") || location.starts_with("https://") { - // absolute uri - location - .parse::() - .expect("provided redirect url should be a valid url") - } else if location.starts_with("//") { - // "//" authority path-abempty - format!("{}:{}", base_uri.scheme_part().unwrap().as_str(), location) - .parse::() - .expect("provided redirect url should be a valid url") - } else if location.starts_with('/') { - // path-absolute - let mut new_uri_parts = base_uri.clone().into_parts(); - new_uri_parts.path_and_query = Some(location.parse().unwrap()); - Uri::from_parts(new_uri_parts).unwrap() - } else { - // assuming path-noscheme | path-empty - let mut new_uri_parts = base_uri.clone().into_parts(); - new_uri_parts.path_and_query = - Some(format!("{}/{}", base_uri.path(), location).parse().unwrap()); - Uri::from_parts(new_uri_parts).unwrap() - } -} - -// The CodeFetch message is used to load HTTP javascript resources and expects a -// synchronous response, this utility method supports that. -pub fn fetch_sync_string(module_name: &str) -> DenoResult<(String, String)> { - let url = module_name.parse::().unwrap(); - let client = get_client(); - // TODO(kevinkassimo): consider set a max redirection counter - // to avoid bouncing between 2 or more urls - let fetch_future = loop_fn((client, url), |(client, url)| { - client - .get(url.clone()) - .map_err(DenoError::from) - .and_then(move |response| { - if response.status().is_redirection() { - let location_string = response - .headers() - .get("location") - .expect("url redirection should provide 'location' header") - .to_str() - .unwrap() - .to_string(); - debug!("Redirecting to {}...", &location_string); - let new_url = resolve_uri_from_location(&url, &location_string); - return Ok(Loop::Continue((client, new_url))); - } - if !response.status().is_success() { - return Err(errors::new( - errors::ErrorKind::NotFound, - "module not found".to_string(), - )); - } - Ok(Loop::Break(response)) - }) - }).and_then(|response| { - let content_type = response - .headers() - .get(CONTENT_TYPE) - .map(|content_type| content_type.to_str().unwrap().to_string()); - let body = response - .into_body() - .concat2() - .map(|body| String::from_utf8(body.to_vec()).unwrap()) - .map_err(DenoError::from); - body.join(future::ok(content_type)) - }).and_then(|(body_string, maybe_content_type)| { - future::ok((body_string, maybe_content_type.unwrap())) - }); - - tokio_util::block_on(fetch_future) -} - -#[test] -fn test_fetch_sync_string() { - // Relies on external http server. See tools/http_server.py - tokio_util::init(|| { - let (p, m) = - fetch_sync_string("http://127.0.0.1:4545/package.json").unwrap(); - println!("package.json len {}", p.len()); - assert!(p.len() > 1); - assert!(m == "application/json") - }); -} - -#[test] -fn test_fetch_sync_string_with_redirect() { - // Relies on external http server. See tools/http_server.py - tokio_util::init(|| { - let (p, m) = - fetch_sync_string("http://127.0.0.1:4546/package.json").unwrap(); - println!("package.json len {}", p.len()); - assert!(p.len() > 1); - assert!(m == "application/json") - }); -} - -#[test] -fn test_resolve_uri_from_location_full_1() { - let url = "http://deno.land".parse::().unwrap(); - let new_uri = resolve_uri_from_location(&url, "http://golang.org"); - assert_eq!(new_uri.host().unwrap(), "golang.org"); -} - -#[test] -fn test_resolve_uri_from_location_full_2() { - let url = "https://deno.land".parse::().unwrap(); - let new_uri = resolve_uri_from_location(&url, "https://golang.org"); - assert_eq!(new_uri.host().unwrap(), "golang.org"); -} - -#[test] -fn test_resolve_uri_from_location_relative_1() { - let url = "http://deno.land/x".parse::().unwrap(); - let new_uri = resolve_uri_from_location(&url, "//rust-lang.org/en-US"); - assert_eq!(new_uri.host().unwrap(), "rust-lang.org"); - assert_eq!(new_uri.path(), "/en-US"); -} - -#[test] -fn test_resolve_uri_from_location_relative_2() { - let url = "http://deno.land/x".parse::().unwrap(); - let new_uri = resolve_uri_from_location(&url, "/y"); - assert_eq!(new_uri.host().unwrap(), "deno.land"); - assert_eq!(new_uri.path(), "/y"); -} - -#[test] -fn test_resolve_uri_from_location_relative_3() { - let url = "http://deno.land/x".parse::().unwrap(); - let new_uri = resolve_uri_from_location(&url, "z"); - assert_eq!(new_uri.host().unwrap(), "deno.land"); - assert_eq!(new_uri.path(), "/x/z"); -} diff --git a/src/isolate.rs b/src/isolate.rs deleted file mode 100644 index 379203dd3..000000000 --- a/src/isolate.rs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::cli::Cli; -use crate::compiler::compile_sync; -use crate::compiler::ModuleMetaData; -use crate::errors::DenoError; -use crate::errors::RustOrJsError; -use crate::isolate_state::IsolateState; -use crate::js_errors; -use crate::msg; -use deno_core; -use deno_core::deno_mod; -use deno_core::JSError; -use futures::Async; -use futures::Future; -use std::sync::Arc; - -type CoreIsolate = deno_core::Isolate; - -/// Wraps deno_core::Isolate to provide source maps, ops for the CLI, and -/// high-level module loading -pub struct Isolate { - inner: CoreIsolate, - state: Arc, -} - -impl Isolate { - pub fn new(cli: Cli) -> Isolate { - let state = cli.state.clone(); - Self { - inner: CoreIsolate::new(cli), - state, - } - } - - /// Same as execute2() but the filename defaults to "". - pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> { - self.execute2("", js_source) - } - - /// Executes the provided JavaScript source code. The js_filename argument is - /// provided only for debugging purposes. - pub fn execute2( - &mut self, - js_filename: &str, - js_source: &str, - ) -> Result<(), JSError> { - self.inner.execute(js_filename, js_source) - } - - // TODO(ry) make this return a future. - fn mod_load_deps(&self, id: deno_mod) -> Result<(), RustOrJsError> { - // basically iterate over the imports, start loading them. - - let referrer_name = { - let g = self.state.modules.lock().unwrap(); - g.get_name(id).unwrap().clone() - }; - - for specifier in self.inner.mod_get_imports(id) { - let (name, _local_filename) = self - .state - .dir - .resolve_module(&specifier, &referrer_name) - .map_err(DenoError::from) - .map_err(RustOrJsError::from)?; - - debug!("mod_load_deps {}", name); - - if !self.state.modules.lock().unwrap().is_registered(&name) { - let out = fetch_module_meta_data_and_maybe_compile( - &self.state, - &specifier, - &referrer_name, - )?; - let child_id = self.mod_new_and_register( - false, - &out.module_name.clone(), - &out.js_source(), - )?; - - self.mod_load_deps(child_id)?; - } - } - - Ok(()) - } - - /// Executes the provided JavaScript module. - pub fn execute_mod( - &mut self, - js_filename: &str, - is_prefetch: bool, - ) -> Result<(), RustOrJsError> { - // TODO move isolate_state::execute_mod impl here. - self - .execute_mod_inner(js_filename, is_prefetch) - .map_err(|err| match err { - RustOrJsError::Js(err) => RustOrJsError::Js(self.apply_source_map(err)), - x => x, - }) - } - - /// High-level way to execute modules. - /// This will issue HTTP requests and file system calls. - /// Blocks. TODO(ry) Don't block. - fn execute_mod_inner( - &mut self, - url: &str, - is_prefetch: bool, - ) -> Result<(), RustOrJsError> { - let out = fetch_module_meta_data_and_maybe_compile(&self.state, url, ".") - .map_err(RustOrJsError::from)?; - - let id = self - .mod_new_and_register(true, &out.module_name.clone(), &out.js_source()) - .map_err(RustOrJsError::from)?; - - self.mod_load_deps(id)?; - - self - .inner - .mod_instantiate(id) - .map_err(RustOrJsError::from)?; - if !is_prefetch { - self.inner.mod_evaluate(id).map_err(RustOrJsError::from)?; - } - Ok(()) - } - - /// Wraps Isolate::mod_new but registers with modules. - fn mod_new_and_register( - &self, - main: bool, - name: &str, - source: &str, - ) -> Result { - let id = self.inner.mod_new(main, name, source)?; - self.state.modules.lock().unwrap().register(id, &name); - Ok(id) - } - - pub fn print_file_info(&self, module: &str) { - let m = self.state.modules.lock().unwrap(); - m.print_file_info(&self.state.dir, module.to_string()); - } - - /// Applies source map to the error. - fn apply_source_map(&self, err: JSError) -> JSError { - js_errors::apply_source_map(&err, &self.state.dir) - } -} - -impl Future for Isolate { - type Item = (); - type Error = JSError; - - fn poll(&mut self) -> Result, Self::Error> { - self.inner.poll().map_err(|err| self.apply_source_map(err)) - } -} - -fn fetch_module_meta_data_and_maybe_compile( - state: &Arc, - specifier: &str, - referrer: &str, -) -> Result { - let mut out = state.dir.fetch_module_meta_data(specifier, referrer)?; - if (out.media_type == msg::MediaType::TypeScript - && out.maybe_output_code.is_none()) - || state.flags.recompile - { - debug!(">>>>> compile_sync START"); - out = compile_sync(state, specifier, &referrer, &out); - debug!(">>>>> compile_sync END"); - state.dir.code_cache(&out)?; - } - Ok(out) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::flags; - use crate::permissions::DenoPermissions; - use crate::tokio_util; - use futures::future::lazy; - use std::sync::atomic::Ordering; - - #[test] - fn execute_mod() { - let filename = std::env::current_dir() - .unwrap() - .join("tests/esm_imports_a.js"); - let filename = filename.to_str().unwrap().to_string(); - - let argv = vec![String::from("./deno"), filename.clone()]; - let (flags, rest_argv, _) = flags::set_flags(argv).unwrap(); - - let state = Arc::new(IsolateState::new(flags, rest_argv, None)); - let state_ = state.clone(); - tokio_util::run(lazy(move || { - let cli = Cli::new(None, state.clone(), DenoPermissions::default()); - let mut isolate = Isolate::new(cli); - if let Err(err) = isolate.execute_mod(&filename, false) { - eprintln!("execute_mod err {:?}", err); - } - tokio_util::panic_on_error(isolate) - })); - - let metrics = &state_.metrics; - assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 1); - } - - #[test] - fn execute_mod_circular() { - let filename = std::env::current_dir().unwrap().join("tests/circular1.js"); - let filename = filename.to_str().unwrap().to_string(); - - let argv = vec![String::from("./deno"), filename.clone()]; - let (flags, rest_argv, _) = flags::set_flags(argv).unwrap(); - - let state = Arc::new(IsolateState::new(flags, rest_argv, None)); - let state_ = state.clone(); - tokio_util::run(lazy(move || { - let cli = Cli::new(None, state.clone(), DenoPermissions::default()); - let mut isolate = Isolate::new(cli); - if let Err(err) = isolate.execute_mod(&filename, false) { - eprintln!("execute_mod err {:?}", err); - } - tokio_util::panic_on_error(isolate) - })); - - let metrics = &state_.metrics; - assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 2); - } -} diff --git a/src/isolate_state.rs b/src/isolate_state.rs deleted file mode 100644 index 4cc010389..000000000 --- a/src/isolate_state.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::cli::Buf; -use crate::deno_dir; -use crate::flags; -use crate::global_timer::GlobalTimer; -use crate::modules::Modules; -use futures::sync::mpsc as async_mpsc; -use std; -use std::env; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Mutex; - -pub type WorkerSender = async_mpsc::Sender; -pub type WorkerReceiver = async_mpsc::Receiver; -pub type WorkerChannels = (WorkerSender, WorkerReceiver); - -// AtomicU64 is currently unstable -#[derive(Default)] -pub struct Metrics { - pub ops_dispatched: AtomicUsize, - pub ops_completed: AtomicUsize, - pub bytes_sent_control: AtomicUsize, - pub bytes_sent_data: AtomicUsize, - pub bytes_received: AtomicUsize, - pub resolve_count: AtomicUsize, -} - -// Isolate cannot be passed between threads but IsolateState can. -// IsolateState satisfies Send and Sync. -// So any state that needs to be accessed outside the main V8 thread should be -// inside IsolateState. -#[cfg_attr(feature = "cargo-clippy", allow(stutter))] -pub struct IsolateState { - pub dir: deno_dir::DenoDir, - pub argv: Vec, - pub flags: flags::DenoFlags, - pub metrics: Metrics, - pub modules: Mutex, - pub worker_channels: Option>, - pub global_timer: Mutex, -} - -impl IsolateState { - pub fn new( - flags: flags::DenoFlags, - argv_rest: Vec, - worker_channels: Option, - ) -> Self { - let custom_root = env::var("DENO_DIR").map(|s| s.into()).ok(); - - Self { - dir: deno_dir::DenoDir::new(flags.reload, flags.recompile, custom_root) - .unwrap(), - argv: argv_rest, - flags, - metrics: Metrics::default(), - modules: Mutex::new(Modules::new()), - worker_channels: worker_channels.map(Mutex::new), - global_timer: Mutex::new(GlobalTimer::new()), - } - } - - pub fn main_module(&self) -> Option { - if self.argv.len() <= 1 { - None - } else { - let specifier = self.argv[1].clone(); - let referrer = "."; - match self.dir.resolve_module_url(&specifier, referrer) { - Ok(url) => Some(url.to_string()), - Err(e) => { - debug!("Potentially swallowed error {}", e); - None - } - } - } - } - - #[cfg(test)] - pub fn mock() -> IsolateState { - let argv = vec![String::from("./deno"), String::from("hello.js")]; - // For debugging: argv.push_back(String::from("-D")); - let (flags, rest_argv, _) = flags::set_flags(argv).unwrap(); - IsolateState::new(flags, rest_argv, None) - } - - pub fn metrics_op_dispatched( - &self, - bytes_sent_control: usize, - bytes_sent_data: usize, - ) { - self.metrics.ops_dispatched.fetch_add(1, Ordering::SeqCst); - self - .metrics - .bytes_sent_control - .fetch_add(bytes_sent_control, Ordering::SeqCst); - self - .metrics - .bytes_sent_data - .fetch_add(bytes_sent_data, Ordering::SeqCst); - } - - pub fn metrics_op_completed(&self, bytes_received: usize) { - self.metrics.ops_completed.fetch_add(1, Ordering::SeqCst); - self - .metrics - .bytes_received - .fetch_add(bytes_received, Ordering::SeqCst); - } -} diff --git a/src/js_errors.rs b/src/js_errors.rs deleted file mode 100644 index 90c9f2007..000000000 --- a/src/js_errors.rs +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -//! This mod adds source maps and ANSI color display to deno_core::JSError. -use crate::ansi; -use deno_core::JSError; -use deno_core::StackFrame; -use source_map_mappings::parse_mappings; -use source_map_mappings::Bias; -use source_map_mappings::Mappings; -use std::collections::HashMap; -use std::fmt; -use std::str; - -/// Wrapper around JSError which provides color to_string. -pub struct JSErrorColor<'a>(pub &'a JSError); - -struct StackFrameColor<'a>(&'a StackFrame); - -pub trait SourceMapGetter { - /// Returns the raw source map file. - fn get_source_map(&self, script_name: &str) -> Option>; -} - -/// Cached filename lookups. The key can be None if a previous lookup failed to -/// find a SourceMap. -type CachedMaps = HashMap>; - -struct SourceMap { - mappings: Mappings, - sources: Vec, -} - -impl<'a> fmt::Display for StackFrameColor<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let frame = self.0; - // Note when we print to string, we change from 0-indexed to 1-indexed. - let function_name = ansi::italic_bold(frame.function_name.clone()); - let script_line_column = - format_script_line_column(&frame.script_name, frame.line, frame.column); - - if !frame.function_name.is_empty() { - write!(f, " at {} ({})", function_name, script_line_column) - } else if frame.is_eval { - write!(f, " at eval ({})", script_line_column) - } else { - write!(f, " at {}", script_line_column) - } - } -} - -fn format_script_line_column( - script_name: &str, - line: i64, - column: i64, -) -> String { - // TODO match this style with how typescript displays errors. - let line = ansi::yellow((1 + line).to_string()); - let column = ansi::yellow((1 + column).to_string()); - let script_name = ansi::cyan(script_name.to_string()); - format!("{}:{}:{}", script_name, line, column) -} - -impl<'a> fmt::Display for JSErrorColor<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let e = self.0; - if e.script_resource_name.is_some() { - let script_resource_name = e.script_resource_name.as_ref().unwrap(); - // Avoid showing internal code from gen/bundle/main.js - if script_resource_name != "gen/bundle/main.js" - && script_resource_name != "gen/bundle/compiler.js" - { - if e.line_number.is_some() && e.start_column.is_some() { - assert!(e.line_number.is_some()); - assert!(e.start_column.is_some()); - let script_line_column = format_script_line_column( - script_resource_name, - e.line_number.unwrap() - 1, - e.start_column.unwrap() - 1, - ); - write!(f, "{}", script_line_column)?; - } - if e.source_line.is_some() { - write!(f, "\n{}\n", e.source_line.as_ref().unwrap())?; - let mut s = String::new(); - for i in 0..e.end_column.unwrap() { - if i >= e.start_column.unwrap() { - s.push('^'); - } else { - s.push(' '); - } - } - writeln!(f, "{}", ansi::red_bold(s))?; - } - } - } - - write!(f, "{}", ansi::bold(e.message.clone()))?; - - for frame in &e.frames { - write!(f, "\n{}", StackFrameColor(&frame).to_string())?; - } - Ok(()) - } -} - -impl SourceMap { - fn from_json(json_str: &str) -> Option { - // Ugly. Maybe use serde_derive. - match serde_json::from_str::(json_str) { - Ok(serde_json::Value::Object(map)) => match map["mappings"].as_str() { - None => None, - Some(mappings_str) => { - match parse_mappings::<()>(mappings_str.as_bytes()) { - Err(_) => None, - Ok(mappings) => { - if !map["sources"].is_array() { - return None; - } - let sources_val = map["sources"].as_array().unwrap(); - let mut sources = Vec::::new(); - - for source_val in sources_val { - match source_val.as_str() { - None => return None, - Some(source) => { - sources.push(source.to_string()); - } - } - } - - Some(SourceMap { sources, mappings }) - } - } - } - }, - _ => None, - } - } -} - -fn frame_apply_source_map( - frame: &StackFrame, - mappings_map: &mut CachedMaps, - getter: &dyn SourceMapGetter, -) -> StackFrame { - let maybe_sm = get_mappings(frame.script_name.as_ref(), mappings_map, getter); - let frame_pos = ( - frame.script_name.to_owned(), - frame.line as i64, - frame.column as i64, - ); - let (script_name, line, column) = match maybe_sm { - None => frame_pos, - Some(sm) => match sm.mappings.original_location_for( - frame.line as u32, - frame.column as u32, - Bias::default(), - ) { - None => frame_pos, - Some(mapping) => match &mapping.original { - None => frame_pos, - Some(original) => { - let orig_source = sm.sources[original.source as usize].clone(); - ( - orig_source, - i64::from(original.original_line), - i64::from(original.original_column), - ) - } - }, - }, - }; - - StackFrame { - script_name, - function_name: frame.function_name.clone(), - line, - column, - is_eval: frame.is_eval, - is_constructor: frame.is_constructor, - is_wasm: frame.is_wasm, - } -} - -pub fn apply_source_map( - js_error: &JSError, - getter: &dyn SourceMapGetter, -) -> JSError { - let mut mappings_map: CachedMaps = HashMap::new(); - let mut frames = Vec::::new(); - for frame in &js_error.frames { - let f = frame_apply_source_map(&frame, &mut mappings_map, getter); - frames.push(f); - } - JSError { - message: js_error.message.clone(), - frames, - error_level: js_error.error_level, - source_line: js_error.source_line.clone(), - // TODO the following need to be source mapped: - script_resource_name: js_error.script_resource_name.clone(), - line_number: js_error.line_number, - start_position: js_error.start_position, - end_position: js_error.end_position, - start_column: js_error.start_column, - end_column: js_error.end_column, - } -} - -// The bundle does not get built for 'cargo check', so we don't embed the -// bundle source map. -#[cfg(feature = "check-only")] -fn builtin_source_map(script_name: &str) -> Option> { - None -} - -#[cfg(not(feature = "check-only"))] -fn builtin_source_map(script_name: &str) -> Option> { - match script_name { - "gen/bundle/main.js" => Some( - include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js.map")) - .to_vec(), - ), - "gen/bundle/compiler.js" => Some( - include_bytes!(concat!( - env!("GN_OUT_DIR"), - "/gen/bundle/compiler.js.map" - )).to_vec(), - ), - _ => None, - } -} - -fn parse_map_string( - script_name: &str, - getter: &dyn SourceMapGetter, -) -> Option { - builtin_source_map(script_name) - .or_else(|| getter.get_source_map(script_name)) - .and_then(|raw_source_map| { - SourceMap::from_json(str::from_utf8(&raw_source_map).unwrap()) - }) -} - -fn get_mappings<'a>( - script_name: &str, - mappings_map: &'a mut CachedMaps, - getter: &dyn SourceMapGetter, -) -> &'a Option { - mappings_map - .entry(script_name.to_string()) - .or_insert_with(|| parse_map_string(script_name, getter)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ansi::strip_ansi_codes; - - fn error1() -> JSError { - JSError { - message: "Error: foo bar".to_string(), - source_line: None, - script_resource_name: None, - line_number: None, - start_position: None, - end_position: None, - error_level: None, - start_column: None, - end_column: None, - frames: vec![ - StackFrame { - line: 4, - column: 16, - script_name: "foo_bar.ts".to_string(), - function_name: "foo".to_string(), - is_eval: false, - is_constructor: false, - is_wasm: false, - }, - StackFrame { - line: 5, - column: 20, - script_name: "bar_baz.ts".to_string(), - function_name: "qat".to_string(), - is_eval: false, - is_constructor: false, - is_wasm: false, - }, - StackFrame { - line: 1, - column: 1, - script_name: "deno_main.js".to_string(), - function_name: "".to_string(), - is_eval: false, - is_constructor: false, - is_wasm: false, - }, - ], - } - } - - struct MockSourceMapGetter {} - - impl SourceMapGetter for MockSourceMapGetter { - fn get_source_map(&self, script_name: &str) -> Option> { - let s = match script_name { - "foo_bar.ts" => r#"{"sources": ["foo_bar.ts"], "mappings":";;;IAIA,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#, - "bar_baz.ts" => r#"{"sources": ["bar_baz.ts"], "mappings":";;;IAEA,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,GAAG,GAAG,sDAAa,OAAO,2BAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,EAAE,CAAC;IAEQ,QAAA,GAAG,GAAG,KAAK,CAAC;IAEzB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#, - _ => return None, - }; - Some(s.as_bytes().to_owned()) - } - } - - #[test] - fn js_error_to_string() { - let e = error1(); - assert_eq!("Error: foo bar\n at foo (foo_bar.ts:5:17)\n at qat (bar_baz.ts:6:21)\n at deno_main.js:2:2", strip_ansi_codes(&e.to_string())); - } - - #[test] - fn js_error_apply_source_map_1() { - let e = error1(); - let getter = MockSourceMapGetter {}; - let actual = apply_source_map(&e, &getter); - let expected = JSError { - message: "Error: foo bar".to_string(), - source_line: None, - script_resource_name: None, - line_number: None, - start_position: None, - end_position: None, - error_level: None, - start_column: None, - end_column: None, - frames: vec![ - StackFrame { - line: 5, - column: 12, - script_name: "foo_bar.ts".to_string(), - function_name: "foo".to_string(), - is_eval: false, - is_constructor: false, - is_wasm: false, - }, - StackFrame { - line: 4, - column: 14, - script_name: "bar_baz.ts".to_string(), - function_name: "qat".to_string(), - is_eval: false, - is_constructor: false, - is_wasm: false, - }, - StackFrame { - line: 1, - column: 1, - script_name: "deno_main.js".to_string(), - function_name: "".to_string(), - is_eval: false, - is_constructor: false, - is_wasm: false, - }, - ], - }; - assert_eq!(actual, expected); - } - - #[test] - fn js_error_apply_source_map_2() { - let e = JSError { - message: "TypeError: baz".to_string(), - source_line: None, - script_resource_name: None, - line_number: None, - start_position: None, - end_position: None, - error_level: None, - start_column: None, - end_column: None, - frames: vec![StackFrame { - line: 11, - column: 12, - script_name: "gen/bundle/main.js".to_string(), - function_name: "setLogDebug".to_string(), - is_eval: false, - is_constructor: false, - is_wasm: false, - }], - }; - let getter = MockSourceMapGetter {}; - let actual = apply_source_map(&e, &getter); - assert_eq!(actual.message, "TypeError: baz"); - // Because this is accessing the live bundle, this test might be more fragile - assert_eq!(actual.frames.len(), 1); - assert!(actual.frames[0].script_name.ends_with("js/util.ts")); - } - - #[test] - fn source_map_from_json() { - let json = r#"{"version":3,"file":"error_001.js","sourceRoot":"","sources":["file:///Users/rld/src/deno/tests/error_001.ts"],"names":[],"mappings":"AAAA,SAAS,GAAG;IACV,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,GAAG;IACV,GAAG,EAAE,CAAC;AACR,CAAC;AAED,GAAG,EAAE,CAAC"}"#; - let sm = SourceMap::from_json(json).unwrap(); - assert_eq!(sm.sources.len(), 1); - assert_eq!( - sm.sources[0], - "file:///Users/rld/src/deno/tests/error_001.ts" - ); - let mapping = sm - .mappings - .original_location_for(1, 10, Bias::default()) - .unwrap(); - assert_eq!(mapping.generated_line, 1); - assert_eq!(mapping.generated_column, 10); - assert_eq!( - mapping.original, - Some(source_map_mappings::OriginalLocation { - source: 0, - original_line: 1, - original_column: 8, - name: None - }) - ); - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 4657a3a4d..000000000 --- a/src/main.rs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate log; -#[macro_use] -extern crate futures; -#[macro_use] -extern crate serde_json; - -mod ansi; -pub mod cli; -pub mod compiler; -pub mod deno_dir; -pub mod errors; -pub mod flags; -mod fs; -mod global_timer; -mod http_body; -mod http_util; -pub mod isolate; -pub mod isolate_state; -pub mod js_errors; -pub mod modules; -pub mod msg; -pub mod msg_util; -pub mod ops; -pub mod permissions; -mod repl; -pub mod resolve_addr; -pub mod resources; -mod startup_data; -mod tokio_util; -mod tokio_write; -pub mod version; -pub mod workers; - -use crate::cli::Cli; -use crate::errors::RustOrJsError; -use crate::isolate::Isolate; -use crate::isolate_state::IsolateState; -use futures::lazy; -use futures::Future; -use log::{LevelFilter, Metadata, Record}; -use std::env; -use std::sync::Arc; - -static LOGGER: Logger = Logger; - -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()) { - println!("{} RS - {}", record.level(), record.args()); - } - } - fn flush(&self) {} -} - -fn print_err_and_exit(err: RustOrJsError) { - eprintln!("{}", err.to_string()); - std::process::exit(1); -} - -fn js_check(r: Result<(), E>) -where - E: Into, -{ - if let Err(err) = r { - print_err_and_exit(err.into()); - } -} - -fn main() { - #[cfg(windows)] - ansi_term::enable_ansi_support().ok(); // For Windows 10 - - log::set_logger(&LOGGER).unwrap(); - let args = env::args().collect(); - let (mut flags, mut rest_argv, usage_string) = flags::set_flags(args) - .unwrap_or_else(|err| { - eprintln!("{}", err); - std::process::exit(1) - }); - - if flags.help { - println!("{}", &usage_string); - std::process::exit(0); - } - - log::set_max_level(if flags.log_debug { - LevelFilter::Debug - } else { - LevelFilter::Warn - }); - - if flags.fmt { - rest_argv.insert(1, "https://deno.land/std/prettier/main.ts".to_string()); - flags.allow_read = true; - flags.allow_write = true; - } - - let should_prefetch = flags.prefetch || flags.info; - let should_display_info = flags.info; - - let state = Arc::new(IsolateState::new(flags, rest_argv, None)); - let state_ = state.clone(); - let startup_data = startup_data::deno_isolate_init(); - let permissions = permissions::DenoPermissions::from_flags(&state.flags); - let cli = Cli::new(Some(startup_data), state_, permissions); - let mut isolate = Isolate::new(cli); - - let main_future = lazy(move || { - // Setup runtime. - js_check(isolate.execute("denoMain()")); - - // Execute main module. - if let Some(main_module) = state.main_module() { - debug!("main_module {}", main_module); - js_check(isolate.execute_mod(&main_module, should_prefetch)); - if should_display_info { - // Display file info and exit. Do not run file - isolate.print_file_info(&main_module); - std::process::exit(0); - } - } - - isolate.then(|result| { - js_check(result); - Ok(()) - }) - }); - - tokio_util::run(main_future); -} diff --git a/src/modules.rs b/src/modules.rs deleted file mode 100644 index 908c31b6d..000000000 --- a/src/modules.rs +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::ansi; -use crate::deno_dir::DenoDir; -use crate::msg; -use deno_core::deno_mod; -use std::collections::HashMap; -use std::collections::HashSet; -use std::fmt; - -pub struct ModuleInfo { - name: String, - children: Vec, -} - -/// A collection of JS modules. -#[derive(Default)] -pub struct Modules { - pub info: HashMap, - pub by_name: HashMap, -} - -impl Modules { - pub fn new() -> Modules { - Self { - info: HashMap::new(), - by_name: HashMap::new(), - } - } - - pub fn get_id(&self, name: &str) -> Option { - self.by_name.get(name).cloned() - } - - pub fn get_children(&self, id: deno_mod) -> Option<&Vec> { - self.info.get(&id).map(|i| &i.children) - } - - pub fn get_name(&self, id: deno_mod) -> Option<&String> { - self.info.get(&id).map(|i| &i.name) - } - - pub fn is_registered(&self, name: &str) -> bool { - self.by_name.get(name).is_some() - } - - pub fn register(&mut self, id: deno_mod, name: &str) { - let name = String::from(name); - debug!("register {}", name); - self.by_name.insert(name.clone(), id); - self.info.insert( - id, - ModuleInfo { - name, - children: Vec::new(), - }, - ); - } - - pub fn resolve_cb( - &mut self, - deno_dir: &DenoDir, - specifier: &str, - referrer: deno_mod, - ) -> deno_mod { - debug!("resolve_cb {}", specifier); - - let maybe_info = self.info.get_mut(&referrer); - if maybe_info.is_none() { - debug!("cant find referrer {}", referrer); - return 0; - } - let info = maybe_info.unwrap(); - let referrer_name = &info.name; - let r = deno_dir.resolve_module(specifier, referrer_name); - if let Err(err) = r { - debug!("potentially swallowed err: {}", err); - return 0; - } - let (name, _local_filename) = r.unwrap(); - - if let Some(id) = self.by_name.get(&name) { - let child_id = *id; - info.children.push(child_id); - return child_id; - } else { - return 0; - } - } - - pub fn print_file_info(&self, deno_dir: &DenoDir, filename: String) { - let maybe_out = deno_dir.fetch_module_meta_data(&filename, "."); - if maybe_out.is_err() { - println!("{}", maybe_out.unwrap_err()); - return; - } - let out = maybe_out.unwrap(); - - println!("{} {}", ansi::bold("local:".to_string()), &(out.filename)); - println!( - "{} {}", - ansi::bold("type:".to_string()), - msg::enum_name_media_type(out.media_type) - ); - if out.maybe_output_code_filename.is_some() { - println!( - "{} {}", - ansi::bold("compiled:".to_string()), - out.maybe_output_code_filename.as_ref().unwrap(), - ); - } - if out.maybe_source_map_filename.is_some() { - println!( - "{} {}", - ansi::bold("map:".to_string()), - out.maybe_source_map_filename.as_ref().unwrap() - ); - } - - let deps = Deps::new(self, &out.module_name); - println!("{}{}", ansi::bold("deps:\n".to_string()), deps.name); - if let Some(ref depsdeps) = deps.deps { - for d in depsdeps { - println!("{}", d); - } - } - } -} - -pub struct Deps { - pub name: String, - pub deps: Option>, - prefix: String, - is_last: bool, -} - -impl Deps { - pub fn new(modules: &Modules, module_name: &str) -> Deps { - let mut seen = HashSet::new(); - let id = modules.get_id(module_name).unwrap(); - Self::helper(&mut seen, "".to_string(), true, modules, id) - } - - fn helper( - seen: &mut HashSet, - prefix: String, - is_last: bool, - modules: &Modules, - id: deno_mod, - ) -> Deps { - let name = modules.get_name(id).unwrap().to_string(); - if seen.contains(&id) { - Deps { - name, - prefix, - deps: None, - is_last, - } - } else { - seen.insert(id); - let child_ids = modules.get_children(id).unwrap(); - let child_count = child_ids.iter().count(); - let deps = child_ids - .iter() - .enumerate() - .map(|(index, dep_id)| { - let new_is_last = index == child_count - 1; - let mut new_prefix = prefix.clone(); - new_prefix.push(if is_last { ' ' } else { '│' }); - new_prefix.push(' '); - Self::helper(seen, new_prefix, new_is_last, modules, *dep_id) - }).collect(); - Deps { - name, - prefix, - deps: Some(deps), - is_last, - } - } - } -} - -impl fmt::Display for Deps { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut has_children = false; - if let Some(ref deps) = self.deps { - has_children = !deps.is_empty(); - } - write!( - f, - "{}{}─{} {}", - self.prefix, - if self.is_last { "└" } else { "├" }, - if has_children { "┬" } else { "─" }, - self.name - )?; - - if let Some(ref deps) = self.deps { - for d in deps { - write!(f, "\n{}", d)?; - } - } - Ok(()) - } -} diff --git a/src/msg.fbs b/src/msg.fbs deleted file mode 100644 index 243034cfb..000000000 --- a/src/msg.fbs +++ /dev/null @@ -1,524 +0,0 @@ -union Any { - Accept, - Chdir, - Chmod, - Close, - CopyFile, - Cwd, - CwdRes, - Dial, - Environ, - EnvironRes, - Exit, - Fetch, - FetchModuleMetaData, - FetchModuleMetaDataRes, - FetchRes, - FormatError, - FormatErrorRes, - GlobalTimer, - GlobalTimerRes, - GlobalTimerStop, - IsTTY, - IsTTYRes, - Listen, - ListenRes, - MakeTempDir, - MakeTempDirRes, - Metrics, - MetricsRes, - Mkdir, - NewConn, - Now, - NowRes, - Open, - OpenRes, - PermissionRevoke, - Permissions, - PermissionsRes, - Read, - ReadDir, - ReadDirRes, - ReadFile, - ReadFileRes, - ReadRes, - Readlink, - ReadlinkRes, - Remove, - Rename, - ReplReadline, - ReplReadlineRes, - ReplStart, - ReplStartRes, - Resources, - ResourcesRes, - Run, - RunRes, - RunStatus, - RunStatusRes, - Seek, - SetEnv, - Shutdown, - Start, - StartRes, - Stat, - StatRes, - Symlink, - Truncate, - WorkerGetMessage, - WorkerGetMessageRes, - WorkerPostMessage, - Write, - WriteFile, - WriteRes, -} - -enum ErrorKind: byte { - NoError = 0, - - // io errors - - NotFound, - PermissionDenied, - ConnectionRefused, - ConnectionReset, - ConnectionAborted, - NotConnected, - AddrInUse, - AddrNotAvailable, - BrokenPipe, - AlreadyExists, - WouldBlock, - InvalidInput, - InvalidData, - TimedOut, - Interrupted, - WriteZero, - Other, - UnexpectedEof, - BadResource, - CommandFailed, - - // url errors - - EmptyHost, - IdnaError, - InvalidPort, - InvalidIpv4Address, - InvalidIpv6Address, - InvalidDomainCharacter, - RelativeUrlWithoutBase, - RelativeUrlWithCannotBeABaseBase, - SetHostOnCannotBeABaseUrl, - Overflow, - - // hyper errors - - HttpUser, - HttpClosed, - HttpCanceled, - HttpParse, - HttpOther, - TooLarge, - - // custom errors - InvalidUri, - InvalidSeekMode, -} - -table Cwd {} - -table CwdRes { - cwd: string; -} - -enum MediaType: byte { - JavaScript = 0, - TypeScript, - Json, - Unknown -} - -table Base { - cmd_id: uint32; - sync: bool = false; - error_kind: ErrorKind = NoError; - error: string; - inner: Any; -} - -table Start { - unused: int8; -} - -table StartRes { - cwd: string; - pid: uint32; - argv: [string]; - exec_path: string; - main_module: string; // Absolute URL. - debug_flag: bool; - deps_flag: bool; - types_flag: bool; - version_flag: bool; - deno_version: string; - v8_version: string; - no_color: bool; -} - -table FormatError { - error: string; -} - -table FormatErrorRes { - error: string; -} - -table WorkerGetMessage { - unused: int8; -} - -table WorkerGetMessageRes { - data: [ubyte]; -} - -table WorkerPostMessage { - // data passed thru the zero-copy data parameter. -} - -table FetchModuleMetaData { - specifier: string; - referrer: string; -} - -table FetchModuleMetaDataRes { - // If it's a non-http module, moduleName and filename will be the same. - // For http modules, moduleName is its resolved http URL, and filename - // is the location of the locally downloaded source code. - module_name: string; - filename: string; - media_type: MediaType; - data: [ubyte]; -} - -table Chdir { - directory: string; -} - -table GlobalTimer { - timeout: int; -} - -table GlobalTimerRes { } - -table GlobalTimerStop { } - -table Exit { - code: int; -} - -table Environ {} - -table SetEnv { - key: string; - value: string; -} - -table EnvironRes { - map: [KeyValue]; -} - -table KeyValue { - key: string; - value: string; -} - -table Permissions {} - -table PermissionRevoke { - permission: string; -} - -table PermissionsRes { - run: bool; - read: bool; - write: bool; - net: bool; - env: bool; -} - -// Note this represents The WHOLE header of an http message, not just the key -// value pairs. That means it includes method and url for Requests and status -// for responses. This is why it is singular "Header" instead of "Headers". -table HttpHeader { - is_request: bool; - // Request only: - method: string; - url: string; - // Response only: - status: uint16; - // Both: - fields: [KeyValue]; -} - -table Fetch { - header: HttpHeader; -} - -table FetchRes { - header: HttpHeader; - body_rid: uint32; -} - -table MakeTempDir { - dir: string; - prefix: string; - suffix: string; -} - -table MakeTempDirRes { - path: string; -} - -table Mkdir { - path: string; - recursive: bool; - mode: uint; // Specified by https://godoc.org/os#FileMode -} - -table Chmod { - path: string; - mode: uint; // Specified by https://godoc.org/os#FileMode -} - -table Remove { - path: string; - recursive: bool; -} - -table ReadFile { - filename: string; -} - -table ReadFileRes { - data: [ubyte]; -} - -table ReadDir { - path: string; -} - -table ReadDirRes { - entries: [StatRes]; -} - -table WriteFile { - filename: string; - data: [ubyte]; - update_perm: bool; - perm: uint; - // perm specified by https://godoc.org/os#FileMode - is_create: bool; - is_append: bool; -} - -table CopyFile { - from: string; - to: string; -} - -table Rename { - oldpath: string; - newpath: string; -} - -table Readlink { - name: string; -} - -table ReadlinkRes { - path: string; -} - -table ReplStart { - history_file: string; - // TODO add config -} - -table ReplStartRes { - rid: uint32; -} - -table ReplReadline { - rid: uint32; - prompt: string; -} - -table ReplReadlineRes { - line: string; -} - -table Resources {} - -table Resource { - rid: uint32; - repr: string; -} - -table ResourcesRes { - resources: [Resource]; -} - -table Symlink { - oldname: string; - newname: string; -} - -table Stat { - filename: string; - lstat: bool; -} - -table StatRes { - is_file: bool; - is_symlink: bool; - len: ulong; - modified:ulong; - accessed:ulong; - created:ulong; - mode: uint; - has_mode: bool; // false on windows - name: string; - path: string; -} - -table Truncate { - name: string; - len: uint; -} - -table Open { - filename: string; - perm: uint; - mode: string; -} - -table OpenRes { - rid: uint32; -} - -table Read { - rid: uint32; - // (ptr, len) is passed as second parameter to libdeno.send(). -} - -table ReadRes { - nread: uint; - eof: bool; -} - -table Write { - rid: uint32; -} - -table WriteRes { - nbyte: uint; -} - -table Close { - rid: uint32; -} - -table Shutdown { - rid: uint32; - how: uint; -} - -table Listen { - network: string; - address: string; -} - -table ListenRes { - rid: uint32; -} - -table Accept { - rid: uint32; -} - -table Dial { - network: string; - address: string; -} - -// Response to Accept and Dial. -table NewConn { - rid: uint32; - remote_addr: string; - local_addr: string; -} - -table Metrics {} - -table MetricsRes { - ops_dispatched: uint64; - ops_completed: uint64; - bytes_sent_control: uint64; - bytes_sent_data: uint64; - bytes_received: uint64; -} - -enum ProcessStdio: byte { Inherit, Piped, Null } - -table Run { - args: [string]; - cwd: string; - env: [KeyValue]; - stdin: ProcessStdio; - stdout: ProcessStdio; - stderr: ProcessStdio; -} - -table RunRes { - rid: uint32; - pid: uint32; - // The following stdio rids are only valid if "Piped" was specified for the - // corresponding stdio stream. The caller MUST issue a close op for all valid - // stdio streams. - stdin_rid: uint32; - stdout_rid: uint32; - stderr_rid: uint32; -} - -table RunStatus { - rid: uint32; -} - -table RunStatusRes { - got_signal: bool; - exit_code: int; - exit_signal: int; -} - -table Now {} - -table NowRes { - time: uint64; -} - -table IsTTY {} - -table IsTTYRes { - stdin: bool; - stdout: bool; - stderr: bool; -} - -table Seek { - rid: uint32; - offset: int; - whence: uint; -} - -root_type Base; diff --git a/src/msg.rs b/src/msg.rs deleted file mode 100644 index 080f39de8..000000000 --- a/src/msg.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -#![allow(unused_imports)] -#![allow(dead_code)] -#![cfg_attr( - feature = "cargo-clippy", - allow(clippy::all, clippy::pedantic) -)] -use crate::isolate_state; -use flatbuffers; -use std::sync::atomic::Ordering; - -// GN_OUT_DIR is set either by build.rs (for the Cargo build), or by -// build_extra/rust/run.py (for the GN+Ninja build). -include!(concat!(env!("GN_OUT_DIR"), "/gen/msg_generated.rs")); - -impl<'a> From<&'a isolate_state::Metrics> for MetricsResArgs { - fn from(m: &'a isolate_state::Metrics) -> Self { - MetricsResArgs { - ops_dispatched: m.ops_dispatched.load(Ordering::SeqCst) as u64, - ops_completed: m.ops_completed.load(Ordering::SeqCst) as u64, - bytes_sent_control: m.bytes_sent_control.load(Ordering::SeqCst) as u64, - bytes_sent_data: m.bytes_sent_data.load(Ordering::SeqCst) as u64, - bytes_received: m.bytes_received.load(Ordering::SeqCst) as u64, - } - } -} diff --git a/src/msg_util.rs b/src/msg_util.rs deleted file mode 100644 index 71bcc19d9..000000000 --- a/src/msg_util.rs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -// Helpers for serialization. -use crate::errors; -use crate::errors::DenoResult; -use crate::msg; - -use flatbuffers; -use http::header::HeaderName; -use http::uri::Uri; -use http::Method; -use hyper::header::HeaderMap; -use hyper::header::HeaderValue; -use hyper::Body; -use hyper::Request; -use hyper::Response; -use std::str::FromStr; - -type Headers = HeaderMap; - -pub fn serialize_key_value<'bldr>( - builder: &mut flatbuffers::FlatBufferBuilder<'bldr>, - key: &str, - value: &str, -) -> flatbuffers::WIPOffset> { - let key = builder.create_string(&key); - let value = builder.create_string(&value); - msg::KeyValue::create( - builder, - &msg::KeyValueArgs { - key: Some(key), - value: Some(value), - }, - ) -} - -pub fn serialize_request_header<'bldr>( - builder: &mut flatbuffers::FlatBufferBuilder<'bldr>, - r: &Request, -) -> flatbuffers::WIPOffset> { - let method = builder.create_string(r.method().as_str()); - let url = builder.create_string(r.uri().to_string().as_ref()); - - let mut fields = Vec::new(); - for (key, val) in r.headers().iter() { - let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap()); - fields.push(kv); - } - let fields = builder.create_vector(fields.as_ref()); - - msg::HttpHeader::create( - builder, - &msg::HttpHeaderArgs { - is_request: true, - method: Some(method), - url: Some(url), - fields: Some(fields), - ..Default::default() - }, - ) -} - -pub fn serialize_fields<'bldr>( - builder: &mut flatbuffers::FlatBufferBuilder<'bldr>, - headers: &Headers, -) -> flatbuffers::WIPOffset< - flatbuffers::Vector< - 'bldr, - flatbuffers::ForwardsUOffset>, - >, -> { - let mut fields = Vec::new(); - for (key, val) in headers.iter() { - let kv = serialize_key_value(builder, key.as_ref(), val.to_str().unwrap()); - fields.push(kv); - } - builder.create_vector(fields.as_ref()) -} - -// Not to be confused with serialize_response which has nothing to do with HTTP. -pub fn serialize_http_response<'bldr>( - builder: &mut flatbuffers::FlatBufferBuilder<'bldr>, - r: &Response, -) -> flatbuffers::WIPOffset> { - let status = r.status().as_u16(); - let fields = serialize_fields(builder, r.headers()); - msg::HttpHeader::create( - builder, - &msg::HttpHeaderArgs { - is_request: false, - status, - fields: Some(fields), - ..Default::default() - }, - ) -} - -pub fn deserialize_request( - header_msg: msg::HttpHeader<'_>, - body: Body, -) -> DenoResult> { - let mut r = Request::new(body); - - assert!(header_msg.is_request()); - - let u = header_msg.url().unwrap(); - let u = Uri::from_str(u) - .map_err(|e| errors::new(msg::ErrorKind::InvalidUri, e.to_string()))?; - *r.uri_mut() = u; - - if let Some(method) = header_msg.method() { - let method = Method::from_str(method).unwrap(); - *r.method_mut() = method; - } - - if let Some(fields) = header_msg.fields() { - let headers = r.headers_mut(); - for i in 0..fields.len() { - let kv = fields.get(i); - let key = kv.key().unwrap(); - let name = HeaderName::from_bytes(key.as_bytes()).unwrap(); - let value = kv.value().unwrap(); - let v = HeaderValue::from_str(value).unwrap(); - headers.insert(name, v); - } - } - Ok(r) -} diff --git a/src/ops.rs b/src/ops.rs deleted file mode 100644 index 254a21563..000000000 --- a/src/ops.rs +++ /dev/null @@ -1,2020 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use atty; -use crate::ansi; -use crate::cli::Buf; -use crate::cli::Cli; -use crate::errors; -use crate::errors::{permission_denied, DenoError, DenoResult, ErrorKind}; -use crate::fs as deno_fs; -use crate::http_util; -use crate::isolate_state::IsolateState; -use crate::js_errors::apply_source_map; -use crate::js_errors::JSErrorColor; -use crate::msg; -use crate::msg_util; -use crate::repl; -use crate::resolve_addr::resolve_addr; -use crate::resources; -use crate::resources::table_entries; -use crate::resources::Resource; -use crate::tokio_util; -use crate::tokio_write; -use crate::version; -use deno_core::deno_buf; -use deno_core::JSError; -use deno_core::Op; -use flatbuffers::FlatBufferBuilder; -use futures; -use futures::Async; -use futures::Poll; -use futures::Sink; -use futures::Stream; -use hyper; -use hyper::rt::Future; -use remove_dir_all::remove_dir_all; -use std; -use std::convert::From; -use std::fs; -use std::net::Shutdown; -use std::path::Path; -use std::path::PathBuf; -use std::process::Command; -use std::sync::Arc; -use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; -use tokio; -use tokio::net::TcpListener; -use tokio::net::TcpStream; -use tokio_process::CommandExt; -use tokio_threadpool; - -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; -#[cfg(unix)] -use std::os::unix::process::ExitStatusExt; - -type OpResult = DenoResult; - -pub type OpWithError = dyn Future + Send; - -// TODO Ideally we wouldn't have to box the OpWithError being returned. -// The box is just to make it easier to get a prototype refactor working. -type OpCreator = - fn(cli: &Cli, base: &msg::Base<'_>, data: deno_buf) -> Box; - -#[inline] -fn empty_buf() -> Buf { - Box::new([]) -} - -/// Processes raw messages from JavaScript. -/// This functions invoked every time libdeno.send() is called. -/// control corresponds to the first argument of libdeno.send(). -/// data corresponds to the second argument of libdeno.send(). -pub fn dispatch( - cli: &Cli, - control: &[u8], - zero_copy: deno_buf, -) -> (bool, Box) { - let bytes_sent_control = control.len(); - let bytes_sent_zero_copy = zero_copy.len(); - let base = msg::get_root_as_base(&control); - let is_sync = base.sync(); - let inner_type = base.inner_type(); - let cmd_id = base.cmd_id(); - - let op: Box = { - // Handle regular ops. - let op_creator: OpCreator = match inner_type { - msg::Any::Accept => op_accept, - msg::Any::Chdir => op_chdir, - msg::Any::Chmod => op_chmod, - msg::Any::Close => op_close, - msg::Any::CopyFile => op_copy_file, - msg::Any::Cwd => op_cwd, - msg::Any::Dial => op_dial, - msg::Any::Environ => op_env, - msg::Any::Exit => op_exit, - msg::Any::Fetch => op_fetch, - msg::Any::FetchModuleMetaData => op_fetch_module_meta_data, - msg::Any::FormatError => op_format_error, - msg::Any::GlobalTimer => op_global_timer, - msg::Any::GlobalTimerStop => op_global_timer_stop, - msg::Any::IsTTY => op_is_tty, - msg::Any::Listen => op_listen, - msg::Any::MakeTempDir => op_make_temp_dir, - msg::Any::Metrics => op_metrics, - msg::Any::Mkdir => op_mkdir, - msg::Any::Now => op_now, - msg::Any::Open => op_open, - msg::Any::PermissionRevoke => op_revoke_permission, - msg::Any::Permissions => op_permissions, - msg::Any::Read => op_read, - msg::Any::ReadDir => op_read_dir, - msg::Any::ReadFile => op_read_file, - msg::Any::Readlink => op_read_link, - msg::Any::Remove => op_remove, - msg::Any::Rename => op_rename, - msg::Any::ReplReadline => op_repl_readline, - msg::Any::ReplStart => op_repl_start, - msg::Any::Resources => op_resources, - msg::Any::Run => op_run, - msg::Any::RunStatus => op_run_status, - msg::Any::Seek => op_seek, - msg::Any::SetEnv => op_set_env, - msg::Any::Shutdown => op_shutdown, - msg::Any::Start => op_start, - msg::Any::Stat => op_stat, - msg::Any::Symlink => op_symlink, - msg::Any::Truncate => op_truncate, - msg::Any::WorkerGetMessage => op_worker_get_message, - msg::Any::WorkerPostMessage => op_worker_post_message, - msg::Any::Write => op_write, - msg::Any::WriteFile => op_write_file, - _ => panic!(format!( - "Unhandled message {}", - msg::enum_name_any(inner_type) - )), - }; - op_creator(&cli, &base, zero_copy) - }; - - cli - .state - .metrics_op_dispatched(bytes_sent_control, bytes_sent_zero_copy); - let state = cli.state.clone(); - - let boxed_op = Box::new( - op.or_else(move |err: DenoError| -> Result { - debug!("op err {}", err); - // No matter whether we got an Err or Ok, we want a serialized message to - // send back. So transform the DenoError into a deno_buf. - let builder = &mut FlatBufferBuilder::new(); - let errmsg_offset = builder.create_string(&format!("{}", err)); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - error: Some(errmsg_offset), - error_kind: err.kind(), - ..Default::default() - }, - )) - }).and_then(move |buf: Buf| -> Result { - // Handle empty responses. For sync responses we just want - // to send null. For async we want to send a small message - // with the cmd_id. - let buf = if is_sync || buf.len() > 0 { - buf - } else { - let builder = &mut FlatBufferBuilder::new(); - serialize_response( - cmd_id, - builder, - msg::BaseArgs { - ..Default::default() - }, - ) - }; - state.metrics_op_completed(buf.len()); - Ok(buf) - }).map_err(|err| panic!("unexpected error {:?}", err)), - ); - - debug!( - "msg_from_js {} sync {}", - msg::enum_name_any(inner_type), - base.sync() - ); - (base.sync(), boxed_op) -} - -fn op_now( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let start = SystemTime::now(); - let since_the_epoch = start.duration_since(UNIX_EPOCH).unwrap(); - let time = since_the_epoch.as_secs() * 1000 - + u64::from(since_the_epoch.subsec_millis()); - - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::NowRes::create(builder, &msg::NowResArgs { time }); - ok_future(serialize_response( - base.cmd_id(), - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::NowRes, - ..Default::default() - }, - )) -} - -fn op_is_tty( - _cli: &Cli, - base: &msg::Base<'_>, - _data: deno_buf, -) -> Box { - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::IsTTYRes::create( - builder, - &msg::IsTTYResArgs { - stdin: atty::is(atty::Stream::Stdin), - stdout: atty::is(atty::Stream::Stdout), - stderr: atty::is(atty::Stream::Stderr), - }, - ); - ok_future(serialize_response( - base.cmd_id(), - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::IsTTYRes, - ..Default::default() - }, - )) -} - -fn op_exit( - _cli: &Cli, - base: &msg::Base<'_>, - _data: deno_buf, -) -> Box { - let inner = base.inner_as_exit().unwrap(); - std::process::exit(inner.code()) -} - -fn op_start( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let mut builder = FlatBufferBuilder::new(); - - let argv = cli - .state - .argv - .iter() - .map(|s| s.as_str()) - .collect::>(); - let argv_off = builder.create_vector_of_strings(argv.as_slice()); - - let cwd_path = std::env::current_dir().unwrap(); - let cwd_off = - builder.create_string(deno_fs::normalize_path(cwd_path.as_ref()).as_ref()); - - let exec_path = - builder.create_string(std::env::current_exe().unwrap().to_str().unwrap()); - - let v8_version = version::v8(); - let v8_version_off = builder.create_string(v8_version); - - let deno_version = version::DENO; - let deno_version_off = builder.create_string(deno_version); - - let main_module = cli.state.main_module().map(|m| builder.create_string(&m)); - - let inner = msg::StartRes::create( - &mut builder, - &msg::StartResArgs { - cwd: Some(cwd_off), - pid: std::process::id(), - argv: Some(argv_off), - main_module, - debug_flag: cli.state.flags.log_debug, - types_flag: cli.state.flags.types, - version_flag: cli.state.flags.version, - v8_version: Some(v8_version_off), - deno_version: Some(deno_version_off), - no_color: !ansi::use_color(), - exec_path: Some(exec_path), - ..Default::default() - }, - ); - - ok_future(serialize_response( - base.cmd_id(), - &mut builder, - msg::BaseArgs { - inner_type: msg::Any::StartRes, - inner: Some(inner.as_union_value()), - ..Default::default() - }, - )) -} - -fn op_format_error( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_format_error().unwrap(); - let orig_error = String::from(inner.error().unwrap()); - - let js_error = JSError::from_v8_exception(&orig_error).unwrap(); - let js_error_mapped = apply_source_map(&js_error, &cli.state.dir); - let js_error_string = JSErrorColor(&js_error_mapped).to_string(); - - let mut builder = FlatBufferBuilder::new(); - let new_error = builder.create_string(&js_error_string); - - let inner = msg::FormatErrorRes::create( - &mut builder, - &msg::FormatErrorResArgs { - error: Some(new_error), - ..Default::default() - }, - ); - - ok_future(serialize_response( - base.cmd_id(), - &mut builder, - msg::BaseArgs { - inner_type: msg::Any::FormatErrorRes, - inner: Some(inner.as_union_value()), - ..Default::default() - }, - )) -} - -fn serialize_response( - cmd_id: u32, - builder: &mut FlatBufferBuilder<'_>, - mut args: msg::BaseArgs<'_>, -) -> Buf { - args.cmd_id = cmd_id; - let base = msg::Base::create(builder, &args); - msg::finish_base_buffer(builder, base); - let data = builder.finished_data(); - // println!("serialize_response {:x?}", data); - data.into() -} - -#[inline] -pub fn ok_future(buf: Buf) -> Box { - Box::new(futures::future::ok(buf)) -} - -// Shout out to Earl Sweatshirt. -#[inline] -pub fn odd_future(err: DenoError) -> Box { - Box::new(futures::future::err(err)) -} - -// https://github.com/denoland/deno/blob/golang/os.go#L100-L154 -fn op_fetch_module_meta_data( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_fetch_module_meta_data().unwrap(); - let cmd_id = base.cmd_id(); - let specifier = inner.specifier().unwrap(); - let referrer = inner.referrer().unwrap(); - - // Check for allow read since this operation could be used to read from the file system. - if !cli.permissions.allows_read() { - debug!("No read permission for fetch_module_meta_data"); - return odd_future(permission_denied()); - } - - // Check for allow write since this operation could be used to write to the file system. - if !cli.permissions.allows_write() { - debug!("No network permission for fetch_module_meta_data"); - return odd_future(permission_denied()); - } - - // Check for allow net since this operation could be used to make https/http requests. - if !cli.permissions.allows_net() { - debug!("No network permission for fetch_module_meta_data"); - return odd_future(permission_denied()); - } - - assert_eq!( - cli.state.dir.root.join("gen"), - cli.state.dir.gen, - "Sanity check" - ); - - Box::new(futures::future::result(|| -> OpResult { - let builder = &mut FlatBufferBuilder::new(); - let out = cli.state.dir.fetch_module_meta_data(specifier, referrer)?; - let data_off = builder.create_vector(out.source_code.as_slice()); - let msg_args = msg::FetchModuleMetaDataResArgs { - module_name: Some(builder.create_string(&out.module_name)), - filename: Some(builder.create_string(&out.filename)), - media_type: out.media_type, - data: Some(data_off), - }; - let inner = msg::FetchModuleMetaDataRes::create(builder, &msg_args); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::FetchModuleMetaDataRes, - ..Default::default() - }, - )) - }())) -} - -fn op_chdir( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_chdir().unwrap(); - let directory = inner.directory().unwrap(); - Box::new(futures::future::result(|| -> OpResult { - std::env::set_current_dir(&directory)?; - Ok(empty_buf()) - }())) -} - -fn op_global_timer_stop( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert!(base.sync()); - assert_eq!(data.len(), 0); - let mut t = cli.state.global_timer.lock().unwrap(); - t.cancel(); - ok_future(empty_buf()) -} - -fn op_global_timer( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert!(!base.sync()); - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - let inner = base.inner_as_global_timer().unwrap(); - let val = inner.timeout(); - assert!(val >= 0); - - let mut t = cli.state.global_timer.lock().unwrap(); - let deadline = Instant::now() + Duration::from_millis(val as u64); - let f = t.new_timeout(deadline); - - Box::new(f.then(move |_| { - let builder = &mut FlatBufferBuilder::new(); - let inner = - msg::GlobalTimerRes::create(builder, &msg::GlobalTimerResArgs {}); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::GlobalTimerRes, - ..Default::default() - }, - )) - })) -} - -fn op_set_env( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_set_env().unwrap(); - let key = inner.key().unwrap(); - let value = inner.value().unwrap(); - if let Err(e) = cli.check_env() { - return odd_future(e); - } - std::env::set_var(key, value); - ok_future(empty_buf()) -} - -fn op_env(cli: &Cli, base: &msg::Base<'_>, data: deno_buf) -> Box { - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - - if let Err(e) = cli.check_env() { - return odd_future(e); - } - - let builder = &mut FlatBufferBuilder::new(); - let vars: Vec<_> = std::env::vars() - .map(|(key, value)| msg_util::serialize_key_value(builder, &key, &value)) - .collect(); - let tables = builder.create_vector(&vars); - let inner = msg::EnvironRes::create( - builder, - &msg::EnvironResArgs { map: Some(tables) }, - ); - ok_future(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::EnvironRes, - ..Default::default() - }, - )) -} - -fn op_permissions( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::PermissionsRes::create( - builder, - &msg::PermissionsResArgs { - run: cli.permissions.allows_run(), - read: cli.permissions.allows_read(), - write: cli.permissions.allows_write(), - net: cli.permissions.allows_net(), - env: cli.permissions.allows_env(), - }, - ); - ok_future(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::PermissionsRes, - ..Default::default() - }, - )) -} - -fn op_revoke_permission( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_permission_revoke().unwrap(); - let permission = inner.permission().unwrap(); - let result = match permission { - "run" => cli.permissions.revoke_run(), - "read" => cli.permissions.revoke_read(), - "write" => cli.permissions.revoke_write(), - "net" => cli.permissions.revoke_net(), - "env" => cli.permissions.revoke_env(), - _ => Ok(()), - }; - if let Err(e) = result { - return odd_future(e); - } - ok_future(empty_buf()) -} - -fn op_fetch( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - let inner = base.inner_as_fetch().unwrap(); - let cmd_id = base.cmd_id(); - - let header = inner.header().unwrap(); - assert!(header.is_request()); - let url = header.url().unwrap(); - - let body = if data.is_empty() { - hyper::Body::empty() - } else { - hyper::Body::from(Vec::from(&*data)) - }; - - let maybe_req = msg_util::deserialize_request(header, body); - if let Err(e) = maybe_req { - return odd_future(e); - } - let req = maybe_req.unwrap(); - - if let Err(e) = cli.check_net(url) { - return odd_future(e); - } - - let client = http_util::get_client(); - - debug!("Before fetch {}", url); - let future = - client - .request(req) - .map_err(DenoError::from) - .and_then(move |res| { - let builder = &mut FlatBufferBuilder::new(); - let header_off = msg_util::serialize_http_response(builder, &res); - let body = res.into_body(); - let body_resource = resources::add_hyper_body(body); - let inner = msg::FetchRes::create( - builder, - &msg::FetchResArgs { - header: Some(header_off), - body_rid: body_resource.rid, - }, - ); - - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::FetchRes, - ..Default::default() - }, - )) - }); - Box::new(future) -} - -// This is just type conversion. Implement From trait? -// See https://github.com/tokio-rs/tokio/blob/ffd73a64e7ec497622b7f939e38017afe7124dc4/tokio-fs/src/lib.rs#L76-L85 -fn convert_blocking(f: F) -> Poll -where - F: FnOnce() -> DenoResult, -{ - use futures::Async::*; - match tokio_threadpool::blocking(f) { - Ok(Ready(Ok(v))) => Ok(v.into()), - Ok(Ready(Err(err))) => Err(err), - Ok(NotReady) => Ok(NotReady), - Err(err) => panic!("blocking error {}", err), - } -} - -fn blocking(is_sync: bool, f: F) -> Box -where - F: 'static + Send + FnOnce() -> DenoResult, -{ - if is_sync { - Box::new(futures::future::result(f())) - } else { - Box::new(tokio_util::poll_fn(move || convert_blocking(f))) - } -} - -fn op_make_temp_dir( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let base = Box::new(*base); - let inner = base.inner_as_make_temp_dir().unwrap(); - let cmd_id = base.cmd_id(); - - // FIXME - if let Err(e) = cli.check_write("make_temp") { - return odd_future(e); - } - - let dir = inner.dir().map(PathBuf::from); - let prefix = inner.prefix().map(String::from); - let suffix = inner.suffix().map(String::from); - - blocking(base.sync(), move || -> OpResult { - // TODO(piscisaureus): use byte vector for paths, not a string. - // See https://github.com/denoland/deno/issues/627. - // We can't assume that paths are always valid utf8 strings. - let path = deno_fs::make_temp_dir( - // Converting Option to Option<&str> - dir.as_ref().map(|x| &**x), - prefix.as_ref().map(|x| &**x), - suffix.as_ref().map(|x| &**x), - )?; - let builder = &mut FlatBufferBuilder::new(); - let path_off = builder.create_string(path.to_str().unwrap()); - let inner = msg::MakeTempDirRes::create( - builder, - &msg::MakeTempDirResArgs { - path: Some(path_off), - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::MakeTempDirRes, - ..Default::default() - }, - )) - }) -} - -fn op_mkdir( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_mkdir().unwrap(); - let path = String::from(inner.path().unwrap()); - let recursive = inner.recursive(); - let mode = inner.mode(); - - if let Err(e) = cli.check_write(&path) { - return odd_future(e); - } - - blocking(base.sync(), move || { - debug!("op_mkdir {}", path); - deno_fs::mkdir(Path::new(&path), mode, recursive)?; - Ok(empty_buf()) - }) -} - -fn op_chmod( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_chmod().unwrap(); - let _mode = inner.mode(); - let path = String::from(inner.path().unwrap()); - - if let Err(e) = cli.check_write(&path) { - return odd_future(e); - } - - blocking(base.sync(), move || { - debug!("op_chmod {}", &path); - let path = PathBuf::from(&path); - // Still check file/dir exists on windows - let _metadata = fs::metadata(&path)?; - // Only work in unix - #[cfg(any(unix))] - { - // We need to use underscore to compile in Windows. - #[cfg_attr( - feature = "cargo-clippy", - allow(clippy::used_underscore_binding) - )] - let mut permissions = _metadata.permissions(); - #[cfg_attr( - feature = "cargo-clippy", - allow(clippy::used_underscore_binding) - )] - permissions.set_mode(_mode); - fs::set_permissions(&path, permissions)?; - } - Ok(empty_buf()) - }) -} - -fn op_open( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - let inner = base.inner_as_open().unwrap(); - let filename_str = inner.filename().unwrap(); - let filename = PathBuf::from(&filename_str); - let mode = inner.mode().unwrap(); - - let mut open_options = tokio::fs::OpenOptions::new(); - - match mode { - "r" => { - open_options.read(true); - } - "r+" => { - open_options.read(true).write(true); - } - "w" => { - open_options.create(true).write(true).truncate(true); - } - "w+" => { - open_options - .read(true) - .create(true) - .write(true) - .truncate(true); - } - "a" => { - open_options.create(true).append(true); - } - "a+" => { - open_options.read(true).create(true).append(true); - } - "x" => { - open_options.create_new(true).write(true); - } - "x+" => { - open_options.create_new(true).read(true).write(true); - } - &_ => { - panic!("Unknown file open mode."); - } - } - - match mode { - "r" => { - if let Err(e) = cli.check_read(&filename_str) { - return odd_future(e); - } - } - "w" | "a" | "x" => { - if let Err(e) = cli.check_write(&filename_str) { - return odd_future(e); - } - } - &_ => { - if let Err(e) = cli.check_read(&filename_str) { - return odd_future(e); - } - if let Err(e) = cli.check_write(&filename_str) { - return odd_future(e); - } - } - } - - let op = open_options - .open(filename) - .map_err(DenoError::from) - .and_then(move |fs_file| -> OpResult { - let resource = resources::add_fs_file(fs_file); - let builder = &mut FlatBufferBuilder::new(); - let inner = - msg::OpenRes::create(builder, &msg::OpenResArgs { rid: resource.rid }); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::OpenRes, - ..Default::default() - }, - )) - }); - Box::new(op) -} - -fn op_close( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_close().unwrap(); - let rid = inner.rid(); - match resources::lookup(rid) { - None => odd_future(errors::bad_resource()), - Some(resource) => { - resource.close(); - ok_future(empty_buf()) - } - } -} - -fn op_shutdown( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_shutdown().unwrap(); - let rid = inner.rid(); - let how = inner.how(); - match resources::lookup(rid) { - None => odd_future(errors::bad_resource()), - Some(mut resource) => { - let shutdown_mode = match how { - 0 => Shutdown::Read, - 1 => Shutdown::Write, - _ => unimplemented!(), - }; - blocking(base.sync(), move || { - // Use UFCS for disambiguation - Resource::shutdown(&mut resource, shutdown_mode)?; - Ok(empty_buf()) - }) - } - } -} - -fn op_read( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - let cmd_id = base.cmd_id(); - let inner = base.inner_as_read().unwrap(); - let rid = inner.rid(); - - match resources::lookup(rid) { - None => odd_future(errors::bad_resource()), - Some(resource) => { - let op = tokio::io::read(resource, data) - .map_err(DenoError::from) - .and_then(move |(_resource, _buf, nread)| { - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::ReadRes::create( - builder, - &msg::ReadResArgs { - nread: nread as u32, - eof: nread == 0, - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ReadRes, - ..Default::default() - }, - )) - }); - Box::new(op) - } - } -} - -fn op_write( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - let cmd_id = base.cmd_id(); - let inner = base.inner_as_write().unwrap(); - let rid = inner.rid(); - - match resources::lookup(rid) { - None => odd_future(errors::bad_resource()), - Some(resource) => { - let op = tokio_write::write(resource, data) - .map_err(DenoError::from) - .and_then(move |(_resource, _buf, nwritten)| { - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::WriteRes::create( - builder, - &msg::WriteResArgs { - nbyte: nwritten as u32, - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::WriteRes, - ..Default::default() - }, - )) - }); - Box::new(op) - } - } -} - -fn op_seek( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let _cmd_id = base.cmd_id(); - let inner = base.inner_as_seek().unwrap(); - let rid = inner.rid(); - let offset = inner.offset(); - let whence = inner.whence(); - - match resources::lookup(rid) { - None => odd_future(errors::bad_resource()), - Some(resource) => { - let op = resources::seek(resource, offset, whence) - .and_then(move |_| Ok(empty_buf())); - Box::new(op) - } - } -} - -fn op_remove( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_remove().unwrap(); - let path_ = inner.path().unwrap(); - let path = PathBuf::from(path_); - let recursive = inner.recursive(); - - if let Err(e) = cli.check_write(path.to_str().unwrap()) { - return odd_future(e); - } - - blocking(base.sync(), move || { - debug!("op_remove {}", path.display()); - let metadata = fs::metadata(&path)?; - if metadata.is_file() { - fs::remove_file(&path)?; - } else if recursive { - remove_dir_all(&path)?; - } else { - fs::remove_dir(&path)?; - } - Ok(empty_buf()) - }) -} - -// Prototype https://github.com/denoland/deno/blob/golang/os.go#L171-L184 -fn op_read_file( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_read_file().unwrap(); - let cmd_id = base.cmd_id(); - let filename_ = inner.filename().unwrap(); - let filename = PathBuf::from(filename_); - debug!("op_read_file {}", filename.display()); - if let Err(e) = cli.check_read(&filename_) { - return odd_future(e); - } - blocking(base.sync(), move || { - let vec = fs::read(&filename)?; - // Build the response message. memcpy data into inner. - // TODO(ry) zero-copy. - let builder = &mut FlatBufferBuilder::new(); - let data_off = builder.create_vector(vec.as_slice()); - let inner = msg::ReadFileRes::create( - builder, - &msg::ReadFileResArgs { - data: Some(data_off), - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ReadFileRes, - ..Default::default() - }, - )) - }) -} - -fn op_copy_file( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_copy_file().unwrap(); - let from_ = inner.from().unwrap(); - let from = PathBuf::from(from_); - let to_ = inner.to().unwrap(); - let to = PathBuf::from(to_); - - if let Err(e) = cli.check_read(&from_) { - return odd_future(e); - } - if let Err(e) = cli.check_write(&to_) { - return odd_future(e); - } - - debug!("op_copy_file {} {}", from.display(), to.display()); - blocking(base.sync(), move || { - // On *nix, Rust deem non-existent path as invalid input - // See https://github.com/rust-lang/rust/issues/54800 - // Once the issue is reolved, we should remove this workaround. - if cfg!(unix) && !from.is_file() { - return Err(errors::new( - ErrorKind::NotFound, - "File not found".to_string(), - )); - } - - fs::copy(&from, &to)?; - Ok(empty_buf()) - }) -} - -macro_rules! to_seconds { - ($time:expr) => {{ - // Unwrap is safe here as if the file is before the unix epoch - // something is very wrong. - $time - .and_then(|t| Ok(t.duration_since(UNIX_EPOCH).unwrap().as_secs())) - .unwrap_or(0) - }}; -} - -#[cfg(any(unix))] -fn get_mode(perm: &fs::Permissions) -> u32 { - perm.mode() -} - -#[cfg(not(any(unix)))] -fn get_mode(_perm: &fs::Permissions) -> u32 { - 0 -} - -fn op_cwd( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - Box::new(futures::future::result(|| -> OpResult { - let path = std::env::current_dir()?; - let builder = &mut FlatBufferBuilder::new(); - let cwd = - builder.create_string(&path.into_os_string().into_string().unwrap()); - let inner = - msg::CwdRes::create(builder, &msg::CwdResArgs { cwd: Some(cwd) }); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::CwdRes, - ..Default::default() - }, - )) - }())) -} - -fn op_stat( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_stat().unwrap(); - let cmd_id = base.cmd_id(); - let filename_ = inner.filename().unwrap(); - let filename = PathBuf::from(filename_); - let lstat = inner.lstat(); - - if let Err(e) = cli.check_read(&filename_) { - return odd_future(e); - } - - blocking(base.sync(), move || { - let builder = &mut FlatBufferBuilder::new(); - debug!("op_stat {} {}", filename.display(), lstat); - let metadata = if lstat { - fs::symlink_metadata(&filename)? - } else { - fs::metadata(&filename)? - }; - - let inner = msg::StatRes::create( - builder, - &msg::StatResArgs { - is_file: metadata.is_file(), - is_symlink: metadata.file_type().is_symlink(), - len: metadata.len(), - modified: to_seconds!(metadata.modified()), - accessed: to_seconds!(metadata.accessed()), - created: to_seconds!(metadata.created()), - mode: get_mode(&metadata.permissions()), - has_mode: cfg!(target_family = "unix"), - ..Default::default() - }, - ); - - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::StatRes, - ..Default::default() - }, - )) - }) -} - -fn op_read_dir( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_read_dir().unwrap(); - let cmd_id = base.cmd_id(); - let path = String::from(inner.path().unwrap()); - - if let Err(e) = cli.check_read(&path) { - return odd_future(e); - } - - blocking(base.sync(), move || -> OpResult { - debug!("op_read_dir {}", path); - let builder = &mut FlatBufferBuilder::new(); - let entries: Vec<_> = fs::read_dir(Path::new(&path))? - .map(|entry| { - let entry = entry.unwrap(); - let metadata = entry.metadata().unwrap(); - let file_type = metadata.file_type(); - let name = builder.create_string(entry.file_name().to_str().unwrap()); - let path = builder.create_string(entry.path().to_str().unwrap()); - - msg::StatRes::create( - builder, - &msg::StatResArgs { - is_file: file_type.is_file(), - is_symlink: file_type.is_symlink(), - len: metadata.len(), - modified: to_seconds!(metadata.modified()), - accessed: to_seconds!(metadata.accessed()), - created: to_seconds!(metadata.created()), - name: Some(name), - path: Some(path), - mode: get_mode(&metadata.permissions()), - has_mode: cfg!(target_family = "unix"), - }, - ) - }).collect(); - - let entries = builder.create_vector(&entries); - let inner = msg::ReadDirRes::create( - builder, - &msg::ReadDirResArgs { - entries: Some(entries), - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ReadDirRes, - ..Default::default() - }, - )) - }) -} - -fn op_write_file( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - let inner = base.inner_as_write_file().unwrap(); - let filename = String::from(inner.filename().unwrap()); - let update_perm = inner.update_perm(); - let perm = inner.perm(); - let is_create = inner.is_create(); - let is_append = inner.is_append(); - - if let Err(e) = cli.check_write(&filename) { - return odd_future(e); - } - - blocking(base.sync(), move || -> OpResult { - debug!("op_write_file {} {}", filename, data.len()); - deno_fs::write_file_2( - Path::new(&filename), - data, - update_perm, - perm, - is_create, - is_append, - )?; - Ok(empty_buf()) - }) -} - -fn op_rename( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_rename().unwrap(); - let oldpath = PathBuf::from(inner.oldpath().unwrap()); - let newpath_ = inner.newpath().unwrap(); - let newpath = PathBuf::from(newpath_); - if let Err(e) = cli.check_write(&newpath_) { - return odd_future(e); - } - blocking(base.sync(), move || -> OpResult { - debug!("op_rename {} {}", oldpath.display(), newpath.display()); - fs::rename(&oldpath, &newpath)?; - Ok(empty_buf()) - }) -} - -fn op_symlink( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_symlink().unwrap(); - let oldname = PathBuf::from(inner.oldname().unwrap()); - let newname_ = inner.newname().unwrap(); - let newname = PathBuf::from(newname_); - - if let Err(e) = cli.check_write(&newname_) { - return odd_future(e); - } - // TODO Use type for Windows. - if cfg!(windows) { - return odd_future(errors::new( - ErrorKind::Other, - "Not implemented".to_string(), - )); - } - blocking(base.sync(), move || -> OpResult { - debug!("op_symlink {} {}", oldname.display(), newname.display()); - #[cfg(any(unix))] - std::os::unix::fs::symlink(&oldname, &newname)?; - Ok(empty_buf()) - }) -} - -fn op_read_link( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_readlink().unwrap(); - let cmd_id = base.cmd_id(); - let name_ = inner.name().unwrap(); - let name = PathBuf::from(name_); - - if let Err(e) = cli.check_read(&name_) { - return odd_future(e); - } - - blocking(base.sync(), move || -> OpResult { - debug!("op_read_link {}", name.display()); - let path = fs::read_link(&name)?; - let builder = &mut FlatBufferBuilder::new(); - let path_off = builder.create_string(path.to_str().unwrap()); - let inner = msg::ReadlinkRes::create( - builder, - &msg::ReadlinkResArgs { - path: Some(path_off), - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ReadlinkRes, - ..Default::default() - }, - )) - }) -} - -fn op_repl_start( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_repl_start().unwrap(); - let cmd_id = base.cmd_id(); - let history_file = String::from(inner.history_file().unwrap()); - - debug!("op_repl_start {}", history_file); - let history_path = repl::history_path(&cli.state.dir, &history_file); - let repl = repl::Repl::new(history_path); - let resource = resources::add_repl(repl); - - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::ReplStartRes::create( - builder, - &msg::ReplStartResArgs { rid: resource.rid }, - ); - ok_future(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ReplStartRes, - ..Default::default() - }, - )) -} - -fn op_repl_readline( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let inner = base.inner_as_repl_readline().unwrap(); - let cmd_id = base.cmd_id(); - let rid = inner.rid(); - let prompt = inner.prompt().unwrap().to_owned(); - debug!("op_repl_readline {} {}", rid, prompt); - - blocking(base.sync(), move || -> OpResult { - let repl = resources::get_repl(rid)?; - let line = repl.lock().unwrap().readline(&prompt)?; - - let builder = &mut FlatBufferBuilder::new(); - let line_off = builder.create_string(&line); - let inner = msg::ReplReadlineRes::create( - builder, - &msg::ReplReadlineResArgs { - line: Some(line_off), - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ReplReadlineRes, - ..Default::default() - }, - )) - }) -} - -fn op_truncate( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - - let inner = base.inner_as_truncate().unwrap(); - let filename = String::from(inner.name().unwrap()); - let len = inner.len(); - - if let Err(e) = cli.check_write(&filename) { - return odd_future(e); - } - - blocking(base.sync(), move || { - debug!("op_truncate {} {}", filename, len); - let f = fs::OpenOptions::new().write(true).open(&filename)?; - f.set_len(u64::from(len))?; - Ok(empty_buf()) - }) -} - -fn op_listen( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - if let Err(e) = cli.check_net("listen") { - return odd_future(e); - } - - let cmd_id = base.cmd_id(); - let inner = base.inner_as_listen().unwrap(); - let network = inner.network().unwrap(); - assert_eq!(network, "tcp"); - let address = inner.address().unwrap(); - - Box::new(futures::future::result((move || { - let addr = resolve_addr(address).wait()?; - - let listener = TcpListener::bind(&addr)?; - let resource = resources::add_tcp_listener(listener); - - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::ListenRes::create( - builder, - &msg::ListenResArgs { rid: resource.rid }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ListenRes, - ..Default::default() - }, - )) - })())) -} - -fn new_conn(cmd_id: u32, tcp_stream: TcpStream) -> OpResult { - let tcp_stream_resource = resources::add_tcp_stream(tcp_stream); - // TODO forward socket_addr to client. - - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::NewConn::create( - builder, - &msg::NewConnArgs { - rid: tcp_stream_resource.rid, - ..Default::default() - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::NewConn, - ..Default::default() - }, - )) -} - -fn op_accept( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - if let Err(e) = cli.check_net("accept") { - return odd_future(e); - } - let cmd_id = base.cmd_id(); - let inner = base.inner_as_accept().unwrap(); - let server_rid = inner.rid(); - - match resources::lookup(server_rid) { - None => odd_future(errors::bad_resource()), - Some(server_resource) => { - let op = tokio_util::accept(server_resource) - .map_err(DenoError::from) - .and_then(move |(tcp_stream, _socket_addr)| { - new_conn(cmd_id, tcp_stream) - }); - Box::new(op) - } - } -} - -fn op_dial( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - if let Err(e) = cli.check_net("dial") { - return odd_future(e); - } - let cmd_id = base.cmd_id(); - let inner = base.inner_as_dial().unwrap(); - let network = inner.network().unwrap(); - assert_eq!(network, "tcp"); // TODO Support others. - let address = inner.address().unwrap(); - - let op = - resolve_addr(address) - .map_err(DenoError::from) - .and_then(move |addr| { - TcpStream::connect(&addr) - .map_err(DenoError::from) - .and_then(move |tcp_stream| new_conn(cmd_id, tcp_stream)) - }); - Box::new(op) -} - -fn op_metrics( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::MetricsRes::create( - builder, - &msg::MetricsResArgs::from(&cli.state.metrics), - ); - ok_future(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::MetricsRes, - ..Default::default() - }, - )) -} - -fn op_resources( - _cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - - let builder = &mut FlatBufferBuilder::new(); - let serialized_resources = table_entries(); - - let res: Vec<_> = serialized_resources - .iter() - .map(|(key, value)| { - let repr = builder.create_string(value); - - msg::Resource::create( - builder, - &msg::ResourceArgs { - rid: *key, - repr: Some(repr), - }, - ) - }).collect(); - - let resources = builder.create_vector(&res); - let inner = msg::ResourcesRes::create( - builder, - &msg::ResourcesResArgs { - resources: Some(resources), - }, - ); - - ok_future(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::ResourcesRes, - ..Default::default() - }, - )) -} - -fn subprocess_stdio_map(v: msg::ProcessStdio) -> std::process::Stdio { - match v { - msg::ProcessStdio::Inherit => std::process::Stdio::inherit(), - msg::ProcessStdio::Piped => std::process::Stdio::piped(), - msg::ProcessStdio::Null => std::process::Stdio::null(), - } -} - -fn op_run(cli: &Cli, base: &msg::Base<'_>, data: deno_buf) -> Box { - assert!(base.sync()); - let cmd_id = base.cmd_id(); - - if let Err(e) = cli.check_run() { - return odd_future(e); - } - - assert_eq!(data.len(), 0); - let inner = base.inner_as_run().unwrap(); - let args = inner.args().unwrap(); - let env = inner.env().unwrap(); - let cwd = inner.cwd(); - - let mut c = Command::new(args.get(0)); - (1..args.len()).for_each(|i| { - let arg = args.get(i); - c.arg(arg); - }); - cwd.map(|d| c.current_dir(d)); - (0..env.len()).for_each(|i| { - let entry = env.get(i); - c.env(entry.key().unwrap(), entry.value().unwrap()); - }); - - c.stdin(subprocess_stdio_map(inner.stdin())); - c.stdout(subprocess_stdio_map(inner.stdout())); - c.stderr(subprocess_stdio_map(inner.stderr())); - - // Spawn the command. - let child = match c.spawn_async() { - Ok(v) => v, - Err(err) => { - return odd_future(err.into()); - } - }; - - let pid = child.id(); - let resources = resources::add_child(child); - - let mut res_args = msg::RunResArgs { - rid: resources.child_rid, - pid, - ..Default::default() - }; - - if let Some(stdin_rid) = resources.stdin_rid { - res_args.stdin_rid = stdin_rid; - } - if let Some(stdout_rid) = resources.stdout_rid { - res_args.stdout_rid = stdout_rid; - } - if let Some(stderr_rid) = resources.stderr_rid { - res_args.stderr_rid = stderr_rid; - } - - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::RunRes::create(builder, &res_args); - ok_future(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::RunRes, - ..Default::default() - }, - )) -} - -fn op_run_status( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - let inner = base.inner_as_run_status().unwrap(); - let rid = inner.rid(); - - if let Err(e) = cli.check_run() { - return odd_future(e); - } - - let future = match resources::child_status(rid) { - Err(e) => { - return odd_future(e); - } - Ok(f) => f, - }; - - let future = future.and_then(move |run_status| { - let code = run_status.code(); - - #[cfg(unix)] - let signal = run_status.signal(); - #[cfg(not(unix))] - let signal = None; - - code - .or(signal) - .expect("Should have either an exit code or a signal."); - let got_signal = signal.is_some(); - - let builder = &mut FlatBufferBuilder::new(); - let inner = msg::RunStatusRes::create( - builder, - &msg::RunStatusResArgs { - got_signal, - exit_code: code.unwrap_or(-1), - exit_signal: signal.unwrap_or(-1), - }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::RunStatusRes, - ..Default::default() - }, - )) - }); - Box::new(future) -} - -struct GetMessageFuture { - pub state: Arc, -} - -impl Future for GetMessageFuture { - type Item = Option; - type Error = (); - - fn poll(&mut self) -> Result, Self::Error> { - assert!(self.state.worker_channels.is_some()); - match self.state.worker_channels { - None => panic!("expected worker_channels"), - Some(ref wc) => { - let mut wc = wc.lock().unwrap(); - wc.1.poll() - } - } - } -} - -fn op_worker_get_message( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - assert_eq!(data.len(), 0); - let cmd_id = base.cmd_id(); - - let op = GetMessageFuture { - state: cli.state.clone(), - }; - let op = op.map_err(move |_| -> DenoError { unimplemented!() }); - let op = op.and_then(move |maybe_buf| -> DenoResult { - debug!("op_worker_get_message"); - let builder = &mut FlatBufferBuilder::new(); - - let data = maybe_buf.as_ref().map(|buf| builder.create_vector(buf)); - let inner = msg::WorkerGetMessageRes::create( - builder, - &msg::WorkerGetMessageResArgs { data }, - ); - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::WorkerGetMessageRes, - ..Default::default() - }, - )) - }); - Box::new(op) -} - -fn op_worker_post_message( - cli: &Cli, - base: &msg::Base<'_>, - data: deno_buf, -) -> Box { - let cmd_id = base.cmd_id(); - - let d = Vec::from(data.as_ref()).into_boxed_slice(); - - assert!(cli.state.worker_channels.is_some()); - let tx = match cli.state.worker_channels { - None => panic!("expected worker_channels"), - Some(ref wc) => { - let wc = wc.lock().unwrap(); - wc.0.clone() - } - }; - let op = tx.send(d); - let op = op.map_err(|e| errors::new(ErrorKind::Other, e.to_string())); - let op = op.and_then(move |_| -> DenoResult { - let builder = &mut FlatBufferBuilder::new(); - - Ok(serialize_response( - cmd_id, - builder, - msg::BaseArgs { - ..Default::default() - }, - )) - }); - Box::new(op) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::cli::Cli; - use crate::isolate_state::IsolateState; - use crate::permissions::{DenoPermissions, PermissionAccessor}; - - #[test] - fn fetch_module_meta_fails_without_read() { - let state = Arc::new(IsolateState::mock()); - let permissions = DenoPermissions { - allow_write: PermissionAccessor::from(true), - allow_env: PermissionAccessor::from(true), - allow_net: PermissionAccessor::from(true), - allow_run: PermissionAccessor::from(true), - ..Default::default() - }; - let cli = Cli::new(None, state, permissions); - let builder = &mut FlatBufferBuilder::new(); - let fetch_msg_args = msg::FetchModuleMetaDataArgs { - specifier: Some(builder.create_string("./somefile")), - referrer: Some(builder.create_string(".")), - }; - let inner = msg::FetchModuleMetaData::create(builder, &fetch_msg_args); - let base_args = msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::FetchModuleMetaData, - ..Default::default() - }; - let base = msg::Base::create(builder, &base_args); - msg::finish_base_buffer(builder, base); - let data = builder.finished_data(); - let final_msg = msg::get_root_as_base(&data); - let fetch_result = - op_fetch_module_meta_data(&cli, &final_msg, deno_buf::empty()).wait(); - match fetch_result { - Ok(_) => assert!(true), - Err(e) => assert_eq!(e.to_string(), permission_denied().to_string()), - } - } - - #[test] - fn fetch_module_meta_fails_without_write() { - let state = Arc::new(IsolateState::mock()); - let permissions = DenoPermissions { - allow_read: PermissionAccessor::from(true), - allow_env: PermissionAccessor::from(true), - allow_net: PermissionAccessor::from(true), - allow_run: PermissionAccessor::from(true), - ..Default::default() - }; - let cli = Cli::new(None, state, permissions); - let builder = &mut FlatBufferBuilder::new(); - let fetch_msg_args = msg::FetchModuleMetaDataArgs { - specifier: Some(builder.create_string("./somefile")), - referrer: Some(builder.create_string(".")), - }; - let inner = msg::FetchModuleMetaData::create(builder, &fetch_msg_args); - let base_args = msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::FetchModuleMetaData, - ..Default::default() - }; - let base = msg::Base::create(builder, &base_args); - msg::finish_base_buffer(builder, base); - let data = builder.finished_data(); - let final_msg = msg::get_root_as_base(&data); - let fetch_result = - op_fetch_module_meta_data(&cli, &final_msg, deno_buf::empty()).wait(); - match fetch_result { - Ok(_) => assert!(true), - Err(e) => assert_eq!(e.to_string(), permission_denied().to_string()), - } - } - - #[test] - fn fetch_module_meta_fails_without_net() { - let state = Arc::new(IsolateState::mock()); - let permissions = DenoPermissions { - allow_read: PermissionAccessor::from(true), - allow_write: PermissionAccessor::from(true), - allow_env: PermissionAccessor::from(true), - allow_run: PermissionAccessor::from(true), - ..Default::default() - }; - let cli = Cli::new(None, state, permissions); - let builder = &mut FlatBufferBuilder::new(); - let fetch_msg_args = msg::FetchModuleMetaDataArgs { - specifier: Some(builder.create_string("./somefile")), - referrer: Some(builder.create_string(".")), - }; - let inner = msg::FetchModuleMetaData::create(builder, &fetch_msg_args); - let base_args = msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::FetchModuleMetaData, - ..Default::default() - }; - let base = msg::Base::create(builder, &base_args); - msg::finish_base_buffer(builder, base); - let data = builder.finished_data(); - let final_msg = msg::get_root_as_base(&data); - let fetch_result = - op_fetch_module_meta_data(&cli, &final_msg, deno_buf::empty()).wait(); - match fetch_result { - Ok(_) => assert!(true), - Err(e) => assert_eq!(e.to_string(), permission_denied().to_string()), - } - } - - #[test] - fn fetch_module_meta_not_permission_denied_with_permissions() { - let state = Arc::new(IsolateState::mock()); - let permissions = DenoPermissions { - allow_read: PermissionAccessor::from(true), - allow_write: PermissionAccessor::from(true), - allow_net: PermissionAccessor::from(true), - ..Default::default() - }; - let cli = Cli::new(None, state, permissions); - let builder = &mut FlatBufferBuilder::new(); - let fetch_msg_args = msg::FetchModuleMetaDataArgs { - specifier: Some(builder.create_string("./somefile")), - referrer: Some(builder.create_string(".")), - }; - let inner = msg::FetchModuleMetaData::create(builder, &fetch_msg_args); - let base_args = msg::BaseArgs { - inner: Some(inner.as_union_value()), - inner_type: msg::Any::FetchModuleMetaData, - ..Default::default() - }; - let base = msg::Base::create(builder, &base_args); - msg::finish_base_buffer(builder, base); - let data = builder.finished_data(); - let final_msg = msg::get_root_as_base(&data); - let fetch_result = - op_fetch_module_meta_data(&cli, &final_msg, deno_buf::empty()).wait(); - match fetch_result { - Ok(_) => assert!(true), - Err(e) => assert!(e.to_string() != permission_denied().to_string()), - } - } -} diff --git a/src/permissions.rs b/src/permissions.rs deleted file mode 100644 index 9093c14f0..000000000 --- a/src/permissions.rs +++ /dev/null @@ -1,343 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use atty; - -use crate::flags::DenoFlags; - -use ansi_term::Style; -use crate::errors::permission_denied; -use crate::errors::DenoResult; -use std::fmt; -use std::io; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::Arc; - -/// Tri-state value for storing permission state -pub enum PermissionAccessorState { - Allow = 0, - Ask = 1, - Deny = 2, -} - -impl From for PermissionAccessorState { - fn from(val: usize) -> Self { - match val { - 0 => PermissionAccessorState::Allow, - 1 => PermissionAccessorState::Ask, - 2 => PermissionAccessorState::Deny, - _ => unreachable!(), - } - } -} - -impl From for PermissionAccessorState { - fn from(val: bool) -> Self { - match val { - true => PermissionAccessorState::Allow, - false => PermissionAccessorState::Ask, - } - } -} - -impl fmt::Display for PermissionAccessorState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PermissionAccessorState::Allow => f.pad("Allow"), - PermissionAccessorState::Ask => f.pad("Ask"), - PermissionAccessorState::Deny => f.pad("Deny"), - } - } -} - -#[derive(Debug)] -pub struct PermissionAccessor { - state: Arc, -} - -impl PermissionAccessor { - pub fn new(state: PermissionAccessorState) -> Self { - Self { - state: Arc::new(AtomicUsize::new(state as usize)), - } - } - - pub fn is_allow(&self) -> bool { - match self.get_state() { - PermissionAccessorState::Allow => true, - _ => false, - } - } - - /// If the state is "Allow" walk it back to the default "Ask" - /// Don't do anything if state is "Deny" - pub fn revoke(&self) { - if self.is_allow() { - self.ask(); - } - } - - pub fn allow(&self) { - self.set_state(PermissionAccessorState::Allow) - } - - pub fn ask(&self) { - self.set_state(PermissionAccessorState::Ask) - } - - pub fn deny(&self) { - self.set_state(PermissionAccessorState::Deny) - } - - /// Update this accessors state based on a PromptResult value - /// This will only update the state if the PromptResult value - /// is one of the "Always" values - pub fn update_with_prompt_result(&self, prompt_result: &PromptResult) { - match prompt_result { - PromptResult::AllowAlways => self.allow(), - PromptResult::DenyAlways => self.deny(), - _ => {} - } - } - - #[inline] - pub fn get_state(&self) -> PermissionAccessorState { - self.state.load(Ordering::SeqCst).into() - } - fn set_state(&self, state: PermissionAccessorState) { - self.state.store(state as usize, Ordering::SeqCst) - } -} - -impl From for PermissionAccessor { - fn from(val: bool) -> Self { - Self::new(PermissionAccessorState::from(val)) - } -} - -impl Default for PermissionAccessor { - fn default() -> Self { - Self { - state: Arc::new(AtomicUsize::new(PermissionAccessorState::Ask as usize)), - } - } -} - -#[cfg_attr(feature = "cargo-clippy", allow(stutter))] -#[derive(Debug, Default)] -pub struct DenoPermissions { - // Keep in sync with src/permissions.ts - pub allow_read: PermissionAccessor, - pub allow_write: PermissionAccessor, - pub allow_net: PermissionAccessor, - pub allow_env: PermissionAccessor, - pub allow_run: PermissionAccessor, - pub no_prompts: AtomicBool, -} - -impl DenoPermissions { - pub fn from_flags(flags: &DenoFlags) -> Self { - Self { - allow_read: PermissionAccessor::from(flags.allow_read), - allow_write: PermissionAccessor::from(flags.allow_write), - allow_env: PermissionAccessor::from(flags.allow_env), - allow_net: PermissionAccessor::from(flags.allow_net), - allow_run: PermissionAccessor::from(flags.allow_run), - no_prompts: AtomicBool::new(flags.no_prompts), - } - } - - pub fn check_run(&self) -> DenoResult<()> { - match self.allow_run.get_state() { - PermissionAccessorState::Allow => Ok(()), - PermissionAccessorState::Ask => { - match self.try_permissions_prompt("access to run a subprocess") { - Err(e) => Err(e), - Ok(v) => { - self.allow_run.update_with_prompt_result(&v); - v.check()?; - Ok(()) - } - } - } - PermissionAccessorState::Deny => Err(permission_denied()), - } - } - - pub fn check_read(&self, filename: &str) -> DenoResult<()> { - match self.allow_read.get_state() { - PermissionAccessorState::Allow => Ok(()), - PermissionAccessorState::Ask => match self - .try_permissions_prompt(&format!("read access to \"{}\"", filename)) - { - Err(e) => Err(e), - Ok(v) => { - self.allow_read.update_with_prompt_result(&v); - v.check()?; - Ok(()) - } - }, - PermissionAccessorState::Deny => Err(permission_denied()), - } - } - - pub fn check_write(&self, filename: &str) -> DenoResult<()> { - match self.allow_write.get_state() { - PermissionAccessorState::Allow => Ok(()), - PermissionAccessorState::Ask => match self - .try_permissions_prompt(&format!("write access to \"{}\"", filename)) - { - Err(e) => Err(e), - Ok(v) => { - self.allow_write.update_with_prompt_result(&v); - v.check()?; - Ok(()) - } - }, - PermissionAccessorState::Deny => Err(permission_denied()), - } - } - - pub fn check_net(&self, domain_name: &str) -> DenoResult<()> { - match self.allow_net.get_state() { - PermissionAccessorState::Allow => Ok(()), - PermissionAccessorState::Ask => match self.try_permissions_prompt( - &format!("network access to \"{}\"", domain_name), - ) { - Err(e) => Err(e), - Ok(v) => { - self.allow_net.update_with_prompt_result(&v); - v.check()?; - Ok(()) - } - }, - PermissionAccessorState::Deny => Err(permission_denied()), - } - } - - pub fn check_env(&self) -> DenoResult<()> { - match self.allow_env.get_state() { - PermissionAccessorState::Allow => Ok(()), - PermissionAccessorState::Ask => { - match self.try_permissions_prompt("access to environment variables") { - Err(e) => Err(e), - Ok(v) => { - self.allow_env.update_with_prompt_result(&v); - v.check()?; - Ok(()) - } - } - } - PermissionAccessorState::Deny => Err(permission_denied()), - } - } - - /// Try to present the user with a permission prompt - /// will error with permission_denied if no_prompts is enabled - fn try_permissions_prompt(&self, message: &str) -> DenoResult { - if self.no_prompts.load(Ordering::SeqCst) { - return Err(permission_denied()); - } - if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) { - return Err(permission_denied()); - }; - permission_prompt(message) - } - - pub fn allows_run(&self) -> bool { - return self.allow_run.is_allow(); - } - - pub fn allows_read(&self) -> bool { - return self.allow_read.is_allow(); - } - - pub fn allows_write(&self) -> bool { - return self.allow_write.is_allow(); - } - - pub fn allows_net(&self) -> bool { - return self.allow_net.is_allow(); - } - - pub fn allows_env(&self) -> bool { - return self.allow_env.is_allow(); - } - - pub fn revoke_run(&self) -> DenoResult<()> { - self.allow_run.revoke(); - return Ok(()); - } - - pub fn revoke_read(&self) -> DenoResult<()> { - self.allow_read.revoke(); - return Ok(()); - } - - pub fn revoke_write(&self) -> DenoResult<()> { - self.allow_write.revoke(); - return Ok(()); - } - - pub fn revoke_net(&self) -> DenoResult<()> { - self.allow_net.revoke(); - return Ok(()); - } - - pub fn revoke_env(&self) -> DenoResult<()> { - self.allow_env.revoke(); - return Ok(()); - } -} - -/// Quad-state value for representing user input on permission prompt -#[derive(Debug, Clone)] -pub enum PromptResult { - AllowAlways = 0, - AllowOnce = 1, - DenyOnce = 2, - DenyAlways = 3, -} - -impl PromptResult { - /// If value is any form of deny this will error with permission_denied - pub fn check(&self) -> DenoResult<()> { - match self { - PromptResult::DenyOnce => Err(permission_denied()), - PromptResult::DenyAlways => Err(permission_denied()), - _ => Ok(()), - } - } -} - -impl fmt::Display for PromptResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PromptResult::AllowAlways => f.pad("AllowAlways"), - PromptResult::AllowOnce => f.pad("AllowOnce"), - PromptResult::DenyOnce => f.pad("DenyOnce"), - PromptResult::DenyAlways => f.pad("DenyAlways"), - } - } -} - -fn permission_prompt(message: &str) -> DenoResult { - let msg = format!("⚠️ Deno requests {}. Grant? [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", message); - // print to stderr so that if deno is > to a file this is still displayed. - eprint!("{}", Style::new().bold().paint(msg)); - loop { - let mut input = String::new(); - let stdin = io::stdin(); - let _nread = stdin.read_line(&mut input)?; - let ch = input.chars().next().unwrap(); - match ch.to_ascii_lowercase() { - 'a' => return Ok(PromptResult::AllowAlways), - 'y' => return Ok(PromptResult::AllowOnce), - 'n' => return Ok(PromptResult::DenyOnce), - 'd' => return Ok(PromptResult::DenyAlways), - _ => { - // If we don't get a recognized option try again. - let msg_again = format!("Unrecognized option '{}' [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", ch); - eprint!("{}", Style::new().bold().paint(msg_again)); - } - }; - } -} diff --git a/src/repl.rs b/src/repl.rs deleted file mode 100644 index 55bf4a114..000000000 --- a/src/repl.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use rustyline; - -use crate::msg::ErrorKind; -use std::error::Error; - -use crate::deno_dir::DenoDir; -use crate::errors::new as deno_error; -use crate::errors::DenoResult; -use std::path::PathBuf; - -#[cfg(not(windows))] -use rustyline::Editor; - -// Work around the issue that on Windows, `struct Editor` does not implement the -// `Send` trait, because it embeds a windows HANDLE which is a type alias for -// *mut c_void. This value isn't actually a pointer and there's nothing that -// can be mutated through it, so hack around it. TODO: a prettier solution. -#[cfg(windows)] -use std::ops::{Deref, DerefMut}; - -#[cfg(windows)] -struct Editor { - inner: rustyline::Editor, -} - -#[cfg(windows)] -unsafe impl Send for Editor {} - -#[cfg(windows)] -impl Editor { - pub fn new() -> Editor { - Editor { - inner: rustyline::Editor::::new(), - } - } -} - -#[cfg(windows)] -impl Deref for Editor { - type Target = rustyline::Editor; - - fn deref(&self) -> &rustyline::Editor { - &self.inner - } -} - -#[cfg(windows)] -impl DerefMut for Editor { - fn deref_mut(&mut self) -> &mut rustyline::Editor { - &mut self.inner - } -} - -pub struct Repl { - editor: Editor<()>, - history_file: PathBuf, -} - -impl Repl { - pub fn new(history_file: PathBuf) -> Self { - let mut repl = Self { - editor: Editor::<()>::new(), - history_file, - }; - - repl.load_history(); - repl - } - - fn load_history(&mut self) { - debug!("Loading REPL history: {:?}", self.history_file); - self - .editor - .load_history(&self.history_file.to_str().unwrap()) - .map_err(|e| debug!("Unable to load history file: {:?} {}", self.history_file, e)) - // ignore this error (e.g. it occurs on first load) - .unwrap_or(()) - } - - fn save_history(&mut self) -> DenoResult<()> { - self - .editor - .save_history(&self.history_file.to_str().unwrap()) - .map(|_| debug!("Saved REPL history to: {:?}", self.history_file)) - .map_err(|e| { - eprintln!("Unable to save REPL history: {:?} {}", self.history_file, e); - deno_error(ErrorKind::Other, e.description().to_string()) - }) - } - - pub fn readline(&mut self, prompt: &str) -> DenoResult { - self - .editor - .readline(&prompt) - .map(|line| { - self.editor.add_history_entry(line.as_ref()); - line - }).map_err(|e| deno_error(ErrorKind::Other, e.description().to_string())) - // Forward error to TS side for processing - } -} - -impl Drop for Repl { - fn drop(&mut self) { - self.save_history().unwrap(); - } -} - -pub fn history_path(dir: &DenoDir, history_file: &str) -> PathBuf { - let mut p: PathBuf = dir.root.clone(); - p.push(history_file); - p -} diff --git a/src/resolve_addr.rs b/src/resolve_addr.rs deleted file mode 100644 index f26655be1..000000000 --- a/src/resolve_addr.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - -use futures::Async; -use futures::Future; -use futures::Poll; -use std::error::Error; -use std::fmt; -use std::net::SocketAddr; -use std::net::ToSocketAddrs; - -/// Go-style network address parsing. Returns a future. -/// Examples: -/// "192.0.2.1:25" -/// ":80" -/// "[2001:db8::1]:80" -/// "198.51.100.1:80" -/// "deno.land:443" -pub fn resolve_addr(address: &str) -> ResolveAddrFuture { - ResolveAddrFuture { - address: address.to_string(), - } -} - -#[derive(Debug)] -pub enum ResolveAddrError { - Syntax, - Resolution(std::io::Error), -} - -impl fmt::Display for ResolveAddrError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.write_str(self.description()) - } -} - -impl Error for ResolveAddrError { - fn description(&self) -> &str { - match self { - ResolveAddrError::Syntax => "invalid address syntax", - ResolveAddrError::Resolution(e) => e.description(), - } - } -} - -pub struct ResolveAddrFuture { - address: String, -} - -impl Future for ResolveAddrFuture { - type Item = SocketAddr; - type Error = ResolveAddrError; - - fn poll(&mut self) -> Poll { - // The implementation of this is not actually async at the moment, - // however we intend to use async DNS resolution in the future and - // so we expose this as a future instead of Result. - match split(&self.address) { - None => Err(ResolveAddrError::Syntax), - Some(addr_port_pair) => { - // I absolutely despise the .to_socket_addrs() API. - let r = addr_port_pair - .to_socket_addrs() - .map_err(ResolveAddrError::Resolution); - - r.and_then(|mut iter| match iter.next() { - Some(a) => Ok(Async::Ready(a)), - None => panic!("There should be at least one result"), - }) - } - } - } -} - -fn split(address: &str) -> Option<(&str, u16)> { - address.rfind(':').and_then(|i| { - let (a, p) = address.split_at(i); - // Default to localhost if given just the port. Example: ":80" - let addr = if !a.is_empty() { a } else { "0.0.0.0" }; - // If this looks like an ipv6 IP address. Example: "[2001:db8::1]" - // Then we remove the brackets. - let addr = if addr.starts_with('[') && addr.ends_with(']') { - let l = addr.len() - 1; - addr.get(1..l).unwrap() - } else { - addr - }; - - let p = p.trim_start_matches(':'); - match p.parse::() { - Err(_) => None, - Ok(port) => Some((addr, port)), - } - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::net::Ipv4Addr; - use std::net::Ipv6Addr; - use std::net::SocketAddrV4; - use std::net::SocketAddrV6; - - #[test] - fn split1() { - assert_eq!(split("127.0.0.1:80"), Some(("127.0.0.1", 80))); - } - - #[test] - fn split2() { - assert_eq!(split(":80"), Some(("0.0.0.0", 80))); - } - - #[test] - fn split3() { - assert_eq!(split("no colon"), None); - } - - #[test] - fn split4() { - assert_eq!(split("deno.land:443"), Some(("deno.land", 443))); - } - - #[test] - fn split5() { - assert_eq!(split("[2001:db8::1]:8080"), Some(("2001:db8::1", 8080))); - } - - #[test] - fn resolve_addr1() { - let expected = - SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 80)); - let actual = resolve_addr("127.0.0.1:80").wait().unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn resolve_addr3() { - let expected = - SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 0, 2, 1), 25)); - let actual = resolve_addr("192.0.2.1:25").wait().unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn resolve_addr_ipv6() { - let expected = SocketAddr::V6(SocketAddrV6::new( - Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), - 8080, - 0, - 0, - )); - let actual = resolve_addr("[2001:db8::1]:8080").wait().unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/src/resources.rs b/src/resources.rs deleted file mode 100644 index 1540f4ff7..000000000 --- a/src/resources.rs +++ /dev/null @@ -1,494 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - -// Think of Resources as File Descriptors. They are integers that are allocated -// by the privileged side of Deno to refer to various resources. The simplest -// example are standard file system files and stdio - but there will be other -// resources added in the future that might not correspond to operating system -// level File Descriptors. To avoid confusion we call them "resources" not "file -// descriptors". This module implements a global resource table. Ops (AKA -// handlers) look up resources by their integer id here. - -use crate::cli::Buf; -use crate::errors; -use crate::errors::bad_resource; -use crate::errors::DenoError; -use crate::errors::DenoResult; -use crate::http_body::HttpBody; -use crate::isolate_state::WorkerChannels; -use crate::repl::Repl; - -use futures; -use futures::Future; -use futures::Poll; -use futures::Sink; -use futures::Stream; -use hyper; -use std; -use std::collections::HashMap; -use std::io::{Error, Read, Seek, SeekFrom, Write}; -use std::net::{Shutdown, SocketAddr}; -use std::process::ExitStatus; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering; -use std::sync::{Arc, Mutex}; -use tokio; -use tokio::io::{AsyncRead, AsyncWrite}; -use tokio::net::TcpStream; -use tokio_process; - -pub type ResourceId = u32; // Sometimes referred to RID. - -// These store Deno's file descriptors. These are not necessarily the operating -// system ones. -type ResourceTable = HashMap; - -#[cfg(not(windows))] -use std::os::unix::io::FromRawFd; - -#[cfg(windows)] -use std::os::windows::io::FromRawHandle; - -#[cfg(windows)] -extern crate winapi; - -lazy_static! { - // Starts at 3 because stdio is [0-2]. - static ref NEXT_RID: AtomicUsize = AtomicUsize::new(3); - static ref RESOURCE_TABLE: Mutex = Mutex::new({ - let mut m = HashMap::new(); - // TODO Load these lazily during lookup? - m.insert(0, Repr::Stdin(tokio::io::stdin())); - - m.insert(1, Repr::Stdout({ - #[cfg(not(windows))] - let stdout = unsafe { std::fs::File::from_raw_fd(1) }; - #[cfg(windows)] - let stdout = unsafe { - std::fs::File::from_raw_handle(winapi::um::processenv::GetStdHandle( - winapi::um::winbase::STD_OUTPUT_HANDLE)) - }; - tokio::fs::File::from_std(stdout) - })); - - m.insert(2, Repr::Stderr(tokio::io::stderr())); - m - }); -} - -// Internal representation of Resource. -enum Repr { - Stdin(tokio::io::Stdin), - Stdout(tokio::fs::File), - Stderr(tokio::io::Stderr), - FsFile(tokio::fs::File), - // Since TcpListener might be closed while there is a pending accept task, - // we need to track the task so that when the listener is closed, - // this pending task could be notified and die. - // Currently TcpListener itself does not take care of this issue. - // See: https://github.com/tokio-rs/tokio/issues/846 - TcpListener(tokio::net::TcpListener, Option), - TcpStream(tokio::net::TcpStream), - HttpBody(HttpBody), - Repl(Arc>), - // Enum size is bounded by the largest variant. - // Use `Box` around large `Child` struct. - // https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant - Child(Box), - ChildStdin(tokio_process::ChildStdin), - ChildStdout(tokio_process::ChildStdout), - ChildStderr(tokio_process::ChildStderr), - Worker(WorkerChannels), -} - -/// If the given rid is open, this returns the type of resource, E.G. "worker". -/// If the rid is closed or was never open, it returns None. -pub fn get_type(rid: ResourceId) -> Option { - let table = RESOURCE_TABLE.lock().unwrap(); - table.get(&rid).map(inspect_repr) -} - -pub fn table_entries() -> Vec<(u32, String)> { - let table = RESOURCE_TABLE.lock().unwrap(); - - table - .iter() - .map(|(key, value)| (*key, inspect_repr(&value))) - .collect() -} - -#[test] -fn test_table_entries() { - let mut entries = table_entries(); - entries.sort(); - assert_eq!(entries[0], (0, String::from("stdin"))); - assert_eq!(entries[1], (1, String::from("stdout"))); - assert_eq!(entries[2], (2, String::from("stderr"))); -} - -fn inspect_repr(repr: &Repr) -> String { - let h_repr = match repr { - Repr::Stdin(_) => "stdin", - Repr::Stdout(_) => "stdout", - Repr::Stderr(_) => "stderr", - Repr::FsFile(_) => "fsFile", - Repr::TcpListener(_, _) => "tcpListener", - Repr::TcpStream(_) => "tcpStream", - Repr::HttpBody(_) => "httpBody", - Repr::Repl(_) => "repl", - Repr::Child(_) => "child", - Repr::ChildStdin(_) => "childStdin", - Repr::ChildStdout(_) => "childStdout", - Repr::ChildStderr(_) => "childStderr", - Repr::Worker(_) => "worker", - }; - - String::from(h_repr) -} - -// Abstract async file interface. -// Ideally in unix, if Resource represents an OS rid, it will be the same. -#[derive(Clone, Debug)] -pub struct Resource { - pub rid: ResourceId, -} - -impl Resource { - // TODO Should it return a Resource instead of net::TcpStream? - pub fn poll_accept(&mut self) -> Poll<(TcpStream, SocketAddr), Error> { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&self.rid); - match maybe_repr { - None => Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Listener has been closed", - )), - Some(repr) => match repr { - Repr::TcpListener(ref mut s, _) => s.poll_accept(), - _ => panic!("Cannot accept"), - }, - } - } - - // close(2) is done by dropping the value. Therefore we just need to remove - // the resource from the RESOURCE_TABLE. - pub fn close(&self) { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let r = table.remove(&self.rid); - assert!(r.is_some()); - } - - pub fn shutdown(&mut self, how: Shutdown) -> Result<(), DenoError> { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&self.rid); - match maybe_repr { - None => panic!("bad rid"), - Some(repr) => match repr { - Repr::TcpStream(ref mut f) => { - TcpStream::shutdown(f, how).map_err(DenoError::from) - } - _ => panic!("Cannot shutdown"), - }, - } - } -} - -impl Read for Resource { - fn read(&mut self, _buf: &mut [u8]) -> std::io::Result { - unimplemented!(); - } -} - -impl AsyncRead for Resource { - fn poll_read(&mut self, buf: &mut [u8]) -> Poll { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&self.rid); - match maybe_repr { - None => panic!("bad rid"), - Some(repr) => match repr { - Repr::FsFile(ref mut f) => f.poll_read(buf), - Repr::Stdin(ref mut f) => f.poll_read(buf), - Repr::TcpStream(ref mut f) => f.poll_read(buf), - Repr::HttpBody(ref mut f) => f.poll_read(buf), - Repr::ChildStdout(ref mut f) => f.poll_read(buf), - Repr::ChildStderr(ref mut f) => f.poll_read(buf), - _ => panic!("Cannot read"), - }, - } - } -} - -impl Write for Resource { - fn write(&mut self, _buf: &[u8]) -> std::io::Result { - unimplemented!() - } - - fn flush(&mut self) -> std::io::Result<()> { - unimplemented!() - } -} - -impl AsyncWrite for Resource { - fn poll_write(&mut self, buf: &[u8]) -> Poll { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&self.rid); - match maybe_repr { - None => panic!("bad rid"), - Some(repr) => match repr { - Repr::FsFile(ref mut f) => f.poll_write(buf), - Repr::Stdout(ref mut f) => f.poll_write(buf), - Repr::Stderr(ref mut f) => f.poll_write(buf), - Repr::TcpStream(ref mut f) => f.poll_write(buf), - Repr::ChildStdin(ref mut f) => f.poll_write(buf), - _ => panic!("Cannot write"), - }, - } - } - - fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> { - unimplemented!() - } -} - -fn new_rid() -> ResourceId { - let next_rid = NEXT_RID.fetch_add(1, Ordering::SeqCst); - next_rid as ResourceId -} - -pub fn add_fs_file(fs_file: tokio::fs::File) -> Resource { - let rid = new_rid(); - let mut tg = RESOURCE_TABLE.lock().unwrap(); - match tg.insert(rid, Repr::FsFile(fs_file)) { - Some(_) => panic!("There is already a file with that rid"), - None => Resource { rid }, - } -} - -pub fn add_tcp_listener(listener: tokio::net::TcpListener) -> Resource { - let rid = new_rid(); - let mut tg = RESOURCE_TABLE.lock().unwrap(); - let r = tg.insert(rid, Repr::TcpListener(listener, None)); - assert!(r.is_none()); - Resource { rid } -} - -pub fn add_tcp_stream(stream: tokio::net::TcpStream) -> Resource { - let rid = new_rid(); - let mut tg = RESOURCE_TABLE.lock().unwrap(); - let r = tg.insert(rid, Repr::TcpStream(stream)); - assert!(r.is_none()); - Resource { rid } -} - -pub fn add_hyper_body(body: hyper::Body) -> Resource { - let rid = new_rid(); - let mut tg = RESOURCE_TABLE.lock().unwrap(); - let body = HttpBody::from(body); - let r = tg.insert(rid, Repr::HttpBody(body)); - assert!(r.is_none()); - Resource { rid } -} - -pub fn add_repl(repl: Repl) -> Resource { - let rid = new_rid(); - let mut tg = RESOURCE_TABLE.lock().unwrap(); - let r = tg.insert(rid, Repr::Repl(Arc::new(Mutex::new(repl)))); - assert!(r.is_none()); - Resource { rid } -} - -pub fn add_worker(wc: WorkerChannels) -> Resource { - let rid = new_rid(); - let mut tg = RESOURCE_TABLE.lock().unwrap(); - let r = tg.insert(rid, Repr::Worker(wc)); - assert!(r.is_none()); - Resource { rid } -} - -pub fn worker_post_message( - rid: ResourceId, - buf: Buf, -) -> futures::sink::Send> { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&rid); - match maybe_repr { - Some(Repr::Worker(ref mut wc)) => { - // unwrap here is incorrect, but doing it anyway - wc.0.clone().send(buf) - } - _ => panic!("bad resource"), // futures::future::err(bad_resource()).into(), - } -} - -pub struct WorkerReceiver { - rid: ResourceId, -} - -// Invert the dumbness that tokio_process causes by making Child itself a future. -impl Future for WorkerReceiver { - type Item = Option; - type Error = DenoError; - - fn poll(&mut self) -> Poll, DenoError> { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&self.rid); - match maybe_repr { - Some(Repr::Worker(ref mut wc)) => wc.1.poll().map_err(|()| { - errors::new(errors::ErrorKind::Other, "recv msg error".to_string()) - }), - _ => Err(bad_resource()), - } - } -} - -pub fn worker_recv_message(rid: ResourceId) -> WorkerReceiver { - WorkerReceiver { rid } -} - -#[cfg_attr(feature = "cargo-clippy", allow(stutter))] -pub struct ChildResources { - pub child_rid: ResourceId, - pub stdin_rid: Option, - pub stdout_rid: Option, - pub stderr_rid: Option, -} - -pub fn add_child(mut c: tokio_process::Child) -> ChildResources { - let child_rid = new_rid(); - let mut tg = RESOURCE_TABLE.lock().unwrap(); - - let mut resources = ChildResources { - child_rid, - stdin_rid: None, - stdout_rid: None, - stderr_rid: None, - }; - - if c.stdin().is_some() { - let stdin = c.stdin().take().unwrap(); - let rid = new_rid(); - let r = tg.insert(rid, Repr::ChildStdin(stdin)); - assert!(r.is_none()); - resources.stdin_rid = Some(rid); - } - if c.stdout().is_some() { - let stdout = c.stdout().take().unwrap(); - let rid = new_rid(); - let r = tg.insert(rid, Repr::ChildStdout(stdout)); - assert!(r.is_none()); - resources.stdout_rid = Some(rid); - } - if c.stderr().is_some() { - let stderr = c.stderr().take().unwrap(); - let rid = new_rid(); - let r = tg.insert(rid, Repr::ChildStderr(stderr)); - assert!(r.is_none()); - resources.stderr_rid = Some(rid); - } - - let r = tg.insert(child_rid, Repr::Child(Box::new(c))); - assert!(r.is_none()); - - resources -} - -pub struct ChildStatus { - rid: ResourceId, -} - -// Invert the dumbness that tokio_process causes by making Child itself a future. -impl Future for ChildStatus { - type Item = ExitStatus; - type Error = DenoError; - - fn poll(&mut self) -> Poll { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&self.rid); - match maybe_repr { - Some(Repr::Child(ref mut child)) => child.poll().map_err(DenoError::from), - _ => Err(bad_resource()), - } - } -} - -pub fn child_status(rid: ResourceId) -> DenoResult { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&rid); - match maybe_repr { - Some(Repr::Child(ref mut _child)) => Ok(ChildStatus { rid }), - _ => Err(bad_resource()), - } -} - -pub fn get_repl(rid: ResourceId) -> DenoResult>> { - let mut table = RESOURCE_TABLE.lock().unwrap(); - let maybe_repr = table.get_mut(&rid); - match maybe_repr { - Some(Repr::Repl(ref mut r)) => Ok(r.clone()), - _ => Err(bad_resource()), - } -} - -pub fn lookup(rid: ResourceId) -> Option { - debug!("resource lookup {}", rid); - let table = RESOURCE_TABLE.lock().unwrap(); - table.get(&rid).map(|_| Resource { rid }) -} - -// TODO(kevinkassimo): revamp this after the following lands: -// https://github.com/tokio-rs/tokio/pull/785 -pub fn seek( - resource: Resource, - offset: i32, - whence: u32, -) -> Box + Send> { - let mut table = RESOURCE_TABLE.lock().unwrap(); - // We take ownership of File here. - // It is put back below while still holding the lock. - let maybe_repr = table.remove(&resource.rid); - match maybe_repr { - None => panic!("bad rid"), - Some(Repr::FsFile(f)) => { - let seek_from = match whence { - 0 => SeekFrom::Start(offset as u64), - 1 => SeekFrom::Current(offset as i64), - 2 => SeekFrom::End(offset as i64), - _ => { - return Box::new(futures::future::err(errors::new( - errors::ErrorKind::InvalidSeekMode, - format!("Invalid seek mode: {}", whence), - ))); - } - }; - // Trait Clone not implemented on tokio::fs::File, - // so convert to std File first. - let std_file = f.into_std(); - // Create a copy and immediately put back. - // We don't want to block other resource ops. - // try_clone() would yield a copy containing the same - // underlying fd, so operations on the copy would also - // affect the one in resource table, and we don't need - // to write back. - let maybe_std_file_copy = std_file.try_clone(); - // Insert the entry back with the same rid. - table.insert( - resource.rid, - Repr::FsFile(tokio_fs::File::from_std(std_file)), - ); - if maybe_std_file_copy.is_err() { - return Box::new(futures::future::err(DenoError::from( - maybe_std_file_copy.unwrap_err(), - ))); - } - let mut std_file_copy = maybe_std_file_copy.unwrap(); - return Box::new(futures::future::lazy(move || { - let result = std_file_copy - .seek(seek_from) - .map(|_| { - return (); - }).map_err(DenoError::from); - futures::future::result(result) - })); - } - _ => panic!("cannot seek"), - } -} diff --git a/src/startup_data.rs b/src/startup_data.rs deleted file mode 100644 index 29ae4db7d..000000000 --- a/src/startup_data.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use deno_core::deno_buf; -use deno_core::{StartupData, StartupScript}; - -pub fn deno_isolate_init() -> StartupData { - if cfg!(feature = "no-snapshot-init") { - debug!("Deno isolate init without snapshots."); - #[cfg(not(feature = "check-only"))] - let source_bytes = - include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js")); - #[cfg(feature = "check-only")] - let source_bytes = vec![]; - - StartupData::Script(StartupScript { - filename: "gen/bundle/main.js".to_string(), - source: std::str::from_utf8(source_bytes).unwrap().to_string(), - }) - } else { - debug!("Deno isolate init with snapshots."); - #[cfg(not(any(feature = "check-only", feature = "no-snapshot-init")))] - let data = - include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_deno.bin")); - #[cfg(any(feature = "check-only", feature = "no-snapshot-init"))] - let data = vec![]; - - unsafe { - StartupData::Snapshot(deno_buf::from_raw_parts(data.as_ptr(), data.len())) - } - } -} - -pub fn compiler_isolate_init() -> StartupData { - if cfg!(feature = "no-snapshot-init") { - debug!("Deno isolate init without snapshots."); - #[cfg(not(feature = "check-only"))] - let source_bytes = - include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/compiler.js")); - #[cfg(feature = "check-only")] - let source_bytes = vec![]; - - StartupData::Script(StartupScript { - filename: "gen/bundle/compiler.js".to_string(), - source: std::str::from_utf8(source_bytes).unwrap().to_string(), - }) - } else { - debug!("Deno isolate init with snapshots."); - #[cfg(not(any(feature = "check-only", feature = "no-snapshot-init")))] - let data = - include_bytes!(concat!(env!("GN_OUT_DIR"), "/gen/snapshot_compiler.bin")); - #[cfg(any(feature = "check-only", feature = "no-snapshot-init"))] - let data = vec![]; - - unsafe { - StartupData::Snapshot(deno_buf::from_raw_parts(data.as_ptr(), data.len())) - } - } -} diff --git a/src/tokio_util.rs b/src/tokio_util.rs deleted file mode 100644 index 810b826b4..000000000 --- a/src/tokio_util.rs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::resources::Resource; -use futures; -use futures::Future; -use futures::Poll; -use std::io; -use std::mem; -use std::net::SocketAddr; -use tokio; -use tokio::net::TcpStream; - -pub fn run(future: F) -where - F: Future + Send + 'static, -{ - // tokio::runtime::current_thread::run(future) - tokio::run(future) -} - -pub fn block_on(future: F) -> Result -where - F: Send + 'static + Future, - R: Send + 'static, - E: Send + 'static, -{ - let (tx, rx) = futures::sync::oneshot::channel(); - tokio::spawn(future.then(move |r| tx.send(r).map_err(|_| unreachable!()))); - rx.wait().unwrap() -} - -// Set the default executor so we can use tokio::spawn(). It's difficult to -// pass around mut references to the runtime, so using with_default is -// preferable. Ideally Tokio would provide this function. -#[cfg(test)] -pub fn init(f: F) -where - F: FnOnce(), -{ - use tokio_executor; - let rt = tokio::runtime::Runtime::new().unwrap(); - let mut executor = rt.executor(); - let mut enter = tokio_executor::enter().expect("Multiple executors at once"); - tokio_executor::with_default(&mut executor, &mut enter, move |_enter| f()); -} - -#[derive(Debug)] -enum AcceptState { - Pending(Resource), - Empty, -} - -/// Simply accepts a connection. -pub fn accept(r: Resource) -> Accept { - Accept { - state: AcceptState::Pending(r), - } -} - -/// A future which can be used to easily read available number of bytes to fill -/// a buffer. -/// -/// Created by the [`read`] function. -#[derive(Debug)] -pub struct Accept { - state: AcceptState, -} - -impl Future for Accept { - type Item = (TcpStream, SocketAddr); - type Error = io::Error; - - fn poll(&mut self) -> Poll { - let (stream, addr) = match self.state { - AcceptState::Pending(ref mut r) => try_ready!(r.poll_accept()), - AcceptState::Empty => panic!("poll Accept after it's done"), - }; - - match mem::replace(&mut self.state, AcceptState::Empty) { - AcceptState::Pending(_) => Ok((stream, addr).into()), - AcceptState::Empty => panic!("invalid internal state"), - } - } -} - -/// `futures::future::poll_fn` only support `F: FnMut()->Poll` -/// However, we require that `F: FnOnce()->Poll`. -/// Therefore, we created our version of `poll_fn`. -pub fn poll_fn(f: F) -> PollFn -where - F: FnOnce() -> Poll, -{ - PollFn { inner: Some(f) } -} - -pub struct PollFn { - inner: Option, -} - -impl Future for PollFn -where - F: FnOnce() -> Poll, -{ - type Item = T; - type Error = E; - - fn poll(&mut self) -> Poll { - let f = self.inner.take().expect("Inner fn has been taken."); - f() - } -} - -pub fn panic_on_error(f: F) -> impl Future -where - F: Future, - E: std::fmt::Debug, -{ - f.map_err(|err| panic!("Future got unexpected error: {:?}", err)) -} diff --git a/src/tokio_write.rs b/src/tokio_write.rs deleted file mode 100644 index 945de375d..000000000 --- a/src/tokio_write.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -// TODO Submit this file upstream into tokio-io/src/io/write.rs -use std::io; -use std::mem; - -use futures::{Future, Poll}; -use tokio::io::AsyncWrite; - -/// A future used to write some data to a stream. -/// -/// This is created by the [`write`] top-level method. -/// -/// [`write`]: fn.write.html -#[derive(Debug)] -pub struct Write { - state: State, -} - -#[derive(Debug)] -enum State { - Pending { a: A, buf: T }, - Empty, -} - -/// Creates a future that will write some of the buffer `buf` to -/// the stream `a` provided. -/// -/// Any error which happens during writing will cause both the stream and the -/// buffer to get destroyed. -pub fn write(a: A, buf: T) -> Write -where - A: AsyncWrite, - T: AsRef<[u8]>, -{ - Write { - state: State::Pending { a, buf }, - } -} - -impl Future for Write -where - A: AsyncWrite, - T: AsRef<[u8]>, -{ - type Item = (A, T, usize); - type Error = io::Error; - - fn poll(&mut self) -> Poll<(A, T, usize), io::Error> { - let nwritten = match self.state { - State::Pending { - ref mut a, - ref mut buf, - } => try_ready!(a.poll_write(buf.as_ref())), - State::Empty => panic!("poll a Read after it's done"), - }; - - match mem::replace(&mut self.state, State::Empty) { - State::Pending { a, buf } => Ok((a, buf, nwritten).into()), - State::Empty => panic!("invalid internal state"), - } - } -} diff --git a/src/version.rs b/src/version.rs deleted file mode 100644 index e6ec9008b..000000000 --- a/src/version.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -pub const DENO: &str = env!("CARGO_PKG_VERSION"); - -pub fn v8() -> &'static str { - deno_core::v8_version() -} diff --git a/src/workers.rs b/src/workers.rs deleted file mode 100644 index edded7756..000000000 --- a/src/workers.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -use crate::cli::Buf; -use crate::cli::Cli; -use crate::flags::DenoFlags; -use crate::isolate::Isolate; -use crate::isolate_state::IsolateState; -use crate::isolate_state::WorkerChannels; -use crate::js_errors::JSErrorColor; -use crate::permissions::DenoPermissions; -use crate::resources; -use crate::tokio_util; -use deno_core::JSError; -use deno_core::StartupData; -use futures::future::lazy; -use futures::sync::mpsc; -use futures::sync::oneshot; -use futures::Future; -use futures::Poll; -use std::sync::Arc; -use std::thread; - -/// Rust interface for WebWorkers. -pub struct Worker { - isolate: Isolate, -} - -impl Worker { - pub fn new( - startup_data: Option, - flags: DenoFlags, - argv: Vec, - permissions: DenoPermissions, - ) -> (Self, WorkerChannels) { - let (worker_in_tx, worker_in_rx) = mpsc::channel::(1); - let (worker_out_tx, worker_out_rx) = mpsc::channel::(1); - - let internal_channels = (worker_out_tx, worker_in_rx); - let external_channels = (worker_in_tx, worker_out_rx); - - let state = - Arc::new(IsolateState::new(flags, argv, Some(internal_channels))); - - let cli = Cli::new(startup_data, state, permissions); - let isolate = Isolate::new(cli); - - let worker = Worker { isolate }; - (worker, external_channels) - } - - pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> { - self.isolate.execute(js_source) - } -} - -impl Future for Worker { - type Item = (); - type Error = JSError; - - fn poll(&mut self) -> Poll<(), JSError> { - self.isolate.poll() - } -} - -pub fn spawn( - startup_data: Option, - state: &IsolateState, - js_source: String, - permissions: DenoPermissions, -) -> resources::Resource { - // TODO This function should return a Future, so that the caller can retrieve - // the JSError if one is thrown. Currently it just prints to stderr and calls - // exit(1). - // let (js_error_tx, js_error_rx) = oneshot::channel::(); - let (p, c) = oneshot::channel::(); - let builder = thread::Builder::new().name("worker".to_string()); - - let flags = state.flags.clone(); - let argv = state.argv.clone(); - - let _tid = builder - .spawn(move || { - tokio_util::run(lazy(move || { - let (mut worker, external_channels) = - Worker::new(startup_data, flags, argv, permissions); - let resource = resources::add_worker(external_channels); - p.send(resource.clone()).unwrap(); - - worker - .execute("denoMain()") - .expect("worker denoMain failed"); - worker - .execute("workerMain()") - .expect("worker workerMain failed"); - worker.execute(&js_source).expect("worker js_source failed"); - - worker.then(move |r| -> Result<(), ()> { - resource.close(); - debug!("workers.rs after resource close"); - if let Err(err) = r { - eprintln!("{}", JSErrorColor(&err).to_string()); - std::process::exit(1); - } - Ok(()) - }) - })); - - debug!("workers.rs after spawn"); - }).unwrap(); - - c.wait().unwrap() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::startup_data; - - #[test] - fn test_spawn() { - let startup_data = startup_data::compiler_isolate_init(); - let resource = spawn( - Some(startup_data), - &IsolateState::mock(), - r#" - onmessage = function(e) { - let s = new TextDecoder().decode(e.data);; - console.log("msg from main script", s); - if (s == "exit") { - close(); - return; - } else { - console.assert(s === "hi"); - } - postMessage(new Uint8Array([1, 2, 3])); - console.log("after postMessage"); - } - "#.into(), - DenoPermissions::default(), - ); - let msg = String::from("hi").into_boxed_str().into_boxed_bytes(); - - let r = resources::worker_post_message(resource.rid, msg).wait(); - assert!(r.is_ok()); - - let maybe_msg = - resources::worker_recv_message(resource.rid).wait().unwrap(); - assert!(maybe_msg.is_some()); - assert_eq!(*maybe_msg.unwrap(), [1, 2, 3]); - - let msg = String::from("exit").into_boxed_str().into_boxed_bytes(); - let r = resources::worker_post_message(resource.rid, msg).wait(); - assert!(r.is_ok()); - } - - #[test] - fn removed_from_resource_table_on_close() { - let startup_data = startup_data::compiler_isolate_init(); - let resource = spawn( - Some(startup_data), - &IsolateState::mock(), - "onmessage = () => close();".into(), - DenoPermissions::default(), - ); - - assert_eq!( - resources::get_type(resource.rid), - Some("worker".to_string()) - ); - - let msg = String::from("hi").into_boxed_str().into_boxed_bytes(); - let r = resources::worker_post_message(resource.rid, msg).wait(); - assert!(r.is_ok()); - println!("rid {:?}", resource.rid); - - // TODO Need a way to get a future for when a resource closes. - // For now, just sleep for a bit. - // resource.close(); - thread::sleep(std::time::Duration::from_millis(1000)); - assert_eq!(resources::get_type(resource.rid), None); - } -} diff --git a/tools/format.py b/tools/format.py index 74323708a..bc6a3b532 100755 --- a/tools/format.py +++ b/tools/format.py @@ -45,4 +45,4 @@ print "rustfmt" qrun([ "third_party/rustfmt/" + platform() + "/rustfmt", "--config-path", rustfmt_config, "build.rs" -] + find_exts(["src", "core"], [".rs"])) +] + find_exts(["cli", "core"], [".rs"])) -- cgit v1.2.3