summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/ansi.rs70
-rw-r--r--cli/cli.rs90
-rw-r--r--cli/compiler.rs151
-rw-r--r--cli/deno_dir.rs1369
-rw-r--r--cli/errors.rs207
-rw-r--r--cli/flags.rs291
-rw-r--r--cli/fs.rs110
-rw-r--r--cli/global_timer.rs49
-rw-r--r--cli/http_body.rs112
-rw-r--r--cli/http_util.rs166
-rw-r--r--cli/isolate.rs236
-rw-r--r--cli/isolate_state.rs110
-rw-r--r--cli/js_errors.rs424
-rw-r--r--cli/main.rs140
-rw-r--r--cli/modules.rs204
-rw-r--r--cli/msg.fbs524
-rw-r--r--cli/msg.rs26
-rw-r--r--cli/msg_util.rs127
-rw-r--r--cli/ops.rs2020
-rw-r--r--cli/permissions.rs343
-rw-r--r--cli/repl.rs114
-rw-r--r--cli/resolve_addr.rs156
-rw-r--r--cli/resources.rs494
-rw-r--r--cli/startup_data.rs57
-rw-r--r--cli/tokio_util.rs118
-rw-r--r--cli/tokio_write.rs62
-rw-r--r--cli/version.rs6
-rw-r--r--cli/workers.rs181
28 files changed, 7957 insertions, 0 deletions
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 <armin.ronacher@active-4.com>. 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<str> {
+ 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<StartupData>,
+ pub state: Arc<IsolateState>,
+ pub permissions: Arc<DenoPermissions>, // TODO(ry) move to IsolateState
+}
+
+impl Cli {
+ pub fn new(
+ startup_data: Option<StartupData>,
+ state: Arc<IsolateState>,
+ 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<StartupData> {
+ 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<Op>) {
+ 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<Option<ResourceId>> = 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<u8>,
+ pub maybe_output_code_filename: Option<String>,
+ pub maybe_output_code: Option<Vec<u8>>,
+ pub maybe_source_map_filename: Option<String>,
+ pub maybe_source_map: Option<Vec<u8>>,
+}
+
+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::<serde_json::Value>(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<PathBuf>,
+ ) -> std::io::Result<Self> {
+ // 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<u8>, Vec<u8>), 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<Option<ModuleMetaData>> {
+ 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<Option<ModuleMetaData>> {
+ 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<String> -> 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<ModuleMetaData> {
+ 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<ModuleMetaData, errors::DenoError> {
+ 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<Url, url::ParseError> {
+ 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<Vec<u8>> {
+ 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<url::Url, url::ParseError> {
+ 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<u8>) -> Vec<u8> {
+ 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<T> = std::result::Result<T, DenoError>;
+
+#[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<io::Error> for DenoError {
+ #[inline]
+ fn from(err: io::Error) -> Self {
+ Self {
+ repr: Repr::IoErr(err),
+ }
+ }
+}
+
+impl From<url::ParseError> for DenoError {
+ #[inline]
+ fn from(err: url::ParseError) -> Self {
+ Self {
+ repr: Repr::UrlErr(err),
+ }
+ }
+}
+
+impl From<hyper::Error> for DenoError {
+ #[inline]
+ fn from(err: hyper::Error) -> Self {
+ Self {
+ repr: Repr::HyperErr(err),
+ }
+ }
+}
+
+impl From<ResolveAddrError> 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<DenoError> for RustOrJsError {
+ fn from(e: DenoError) -> Self {
+ RustOrJsError::Rust(e)
+ }
+}
+
+impl From<JSError> 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<String>
+#[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<String>,
+) -> Result<Vec<String>, getopts::Fail> {
+ let mut rest = Vec::<String>::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<String>,
+) -> Result<(DenoFlags, Vec<String>, 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<T: AsRef<[u8]>>(
+ filename: &Path,
+ data: T,
+ perm: u32,
+) -> std::io::Result<()> {
+ write_file_2(filename, data, true, perm, true, false)
+}
+
+pub fn write_file_2<T: AsRef<[u8]>>(
+ 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<PathBuf> {
+ 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::<u32>();
+ 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<oneshot::Sender<()>>,
+}
+
+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<Item = (), Error = ()> {
+ 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<Chunk>,
+ 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<usize> {
+ unimplemented!();
+ }
+}
+
+impl AsyncRead for HttpBody {
+ fn poll_read(&mut self, buf: &mut [u8]) -> Poll<usize, io::Error> {
+ 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<HttpConnector>;
+
+lazy_static! {
+ static ref CONNECTOR: Connector = {
+ let num_dns_threads = 4;
+ Connector::new(num_dns_threads)
+ };
+}
+
+pub fn get_client() -> Client<Connector, hyper::Body> {
+ // 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 <https://tools.ietf.org/html/rfc3986#section-4.2>
+fn resolve_uri_from_location(base_uri: &Uri, location: &str) -> Uri {
+ if location.starts_with("http://") || location.starts_with("https://") {
+ // absolute uri
+ location
+ .parse::<Uri>()
+ .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::<Uri>()
+ .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::<Uri>().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::<Uri>().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::<Uri>().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::<Uri>().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::<Uri>().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::<Uri>().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<Cli>;
+
+/// Wraps deno_core::Isolate to provide source maps, ops for the CLI, and
+/// high-level module loading
+pub struct Isolate {
+ inner: CoreIsolate,
+ state: Arc<IsolateState>,
+}
+
+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 "<anonymous>".
+ pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> {
+ self.execute2("<anonymous>", 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<deno_mod, JSError> {
+ 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<Async<()>, Self::Error> {
+ self.inner.poll().map_err(|err| self.apply_source_map(err))
+ }
+}
+
+fn fetch_module_meta_data_and_maybe_compile(
+ state: &Arc<IsolateState>,
+ specifier: &str,
+ referrer: &str,
+) -> Result<ModuleMetaData, DenoError> {
+ 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<Buf>;
+pub type WorkerReceiver = async_mpsc::Receiver<Buf>;
+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<String>,
+ pub flags: flags::DenoFlags,
+ pub metrics: Metrics,
+ pub modules: Mutex<Modules>,
+ pub worker_channels: Option<Mutex<WorkerChannels>>,
+ pub global_timer: Mutex<GlobalTimer>,
+}
+
+impl IsolateState {
+ pub fn new(
+ flags: flags::DenoFlags,
+ argv_rest: Vec<String>,
+ worker_channels: Option<WorkerChannels>,
+ ) -> 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<String> {
+ 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<Vec<u8>>;
+}
+
+/// Cached filename lookups. The key can be None if a previous lookup failed to
+/// find a SourceMap.
+type CachedMaps = HashMap<String, Option<SourceMap>>;
+
+struct SourceMap {
+ mappings: Mappings,
+ sources: Vec<String>,
+}
+
+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<Self> {
+ // Ugly. Maybe use serde_derive.
+ match serde_json::from_str::<serde_json::Value>(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::<String>::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::<StackFrame>::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<Vec<u8>> {
+ None
+}
+
+#[cfg(not(feature = "check-only"))]
+fn builtin_source_map(script_name: &str) -> Option<Vec<u8>> {
+ 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<SourceMap> {
+ 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<SourceMap> {
+ 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<Vec<u8>> {
+ 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<E>(r: Result<(), E>)
+where
+ E: Into<RustOrJsError>,
+{
+ 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<deno_mod>,
+}
+
+/// A collection of JS modules.
+#[derive(Default)]
+pub struct Modules {
+ pub info: HashMap<deno_mod, ModuleInfo>,
+ pub by_name: HashMap<String, deno_mod>,
+}
+
+impl Modules {
+ pub fn new() -> Modules {
+ Self {
+ info: HashMap::new(),
+ by_name: HashMap::new(),
+ }
+ }
+
+ pub fn get_id(&self, name: &str) -> Option<deno_mod> {
+ self.by_name.get(name).cloned()
+ }
+
+ pub fn get_children(&self, id: deno_mod) -> Option<&Vec<deno_mod>> {
+ 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<Vec<Deps>>,
+ 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<deno_mod>,
+ 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<HeaderValue>;
+
+pub fn serialize_key_value<'bldr>(
+ builder: &mut flatbuffers::FlatBufferBuilder<'bldr>,
+ key: &str,
+ value: &str,
+) -> flatbuffers::WIPOffset<msg::KeyValue<'bldr>> {
+ 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<Body>,
+) -> flatbuffers::WIPOffset<msg::HttpHeader<'bldr>> {
+ 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<msg::KeyValue<'bldr>>,
+ >,
+> {
+ 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<Body>,
+) -> flatbuffers::WIPOffset<msg::HttpHeader<'bldr>> {
+ 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<Request<Body>> {
+ 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<Buf>;
+
+pub type OpWithError = dyn Future<Item = Buf, Error = DenoError> + 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<OpWithError>;
+
+#[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<Op>) {
+ 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<OpWithError> = {
+ // 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<Buf, ()> {
+ 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<Buf, ()> {
+ // 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ let inner = base.inner_as_exit().unwrap();
+ std::process::exit(inner.code())
+}
+
+fn op_start(
+ cli: &Cli,
+ base: &msg::Base<'_>,
+ data: deno_buf,
+) -> Box<OpWithError> {
+ assert_eq!(data.len(), 0);
+ let mut builder = FlatBufferBuilder::new();
+
+ let argv = cli
+ .state
+ .argv
+ .iter()
+ .map(|s| s.as_str())
+ .collect::<Vec<_>>();
+ 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<OpWithError> {
+ 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<OpWithError> {
+ Box::new(futures::future::ok(buf))
+}
+
+// Shout out to Earl Sweatshirt.
+#[inline]
+pub fn odd_future(err: DenoError) -> Box<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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: F) -> Poll<Buf, DenoError>
+where
+ F: FnOnce() -> DenoResult<Buf>,
+{
+ 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<F>(is_sync: bool, f: F) -> Box<OpWithError>
+where
+ F: 'static + Send + FnOnce() -> DenoResult<Buf>,
+{
+ 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<OpWithError> {
+ 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<String> 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<OpWithError> {
+ 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<IsolateState>,
+}
+
+impl Future for GetMessageFuture {
+ type Item = Option<Buf>;
+ type Error = ();
+
+ fn poll(&mut self) -> Result<Async<Self::Item>, 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<OpWithError> {
+ 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<Buf> {
+ 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<OpWithError> {
+ 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<Buf> {
+ 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<usize> for PermissionAccessorState {
+ fn from(val: usize) -> Self {
+ match val {
+ 0 => PermissionAccessorState::Allow,
+ 1 => PermissionAccessorState::Ask,
+ 2 => PermissionAccessorState::Deny,
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl From<bool> 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<AtomicUsize>,
+}
+
+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<bool> 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<PromptResult> {
+ 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<PromptResult> {
+ 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<T: rustyline::Helper> {
+ inner: rustyline::Editor<T>,
+}
+
+#[cfg(windows)]
+unsafe impl<T: rustyline::Helper> Send for Editor<T> {}
+
+#[cfg(windows)]
+impl<T: rustyline::Helper> Editor<T> {
+ pub fn new() -> Editor<T> {
+ Editor {
+ inner: rustyline::Editor::<T>::new(),
+ }
+ }
+}
+
+#[cfg(windows)]
+impl<T: rustyline::Helper> Deref for Editor<T> {
+ type Target = rustyline::Editor<T>;
+
+ fn deref(&self) -> &rustyline::Editor<T> {
+ &self.inner
+ }
+}
+
+#[cfg(windows)]
+impl<T: rustyline::Helper> DerefMut for Editor<T> {
+ fn deref_mut(&mut self) -> &mut rustyline::Editor<T> {
+ &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<String> {
+ 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<Self::Item, Self::Error> {
+ // 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::<u16>() {
+ 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<ResourceId, Repr>;
+
+#[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<ResourceTable> = 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<futures::task::Task>),
+ TcpStream(tokio::net::TcpStream),
+ HttpBody(HttpBody),
+ Repl(Arc<Mutex<Repl>>),
+ // 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<tokio_process::Child>),
+ 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<String> {
+ 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<usize> {
+ unimplemented!();
+ }
+}
+
+impl AsyncRead for Resource {
+ fn poll_read(&mut self, buf: &mut [u8]) -> Poll<usize, Error> {
+ 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<usize> {
+ unimplemented!()
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ unimplemented!()
+ }
+}
+
+impl AsyncWrite for Resource {
+ fn poll_write(&mut self, buf: &[u8]) -> Poll<usize, Error> {
+ 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<futures::sync::mpsc::Sender<Buf>> {
+ 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<Buf>;
+ type Error = DenoError;
+
+ fn poll(&mut self) -> Poll<Option<Buf>, 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<ResourceId>,
+ pub stdout_rid: Option<ResourceId>,
+ pub stderr_rid: Option<ResourceId>,
+}
+
+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<ExitStatus, DenoError> {
+ 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<ChildStatus> {
+ 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<Arc<Mutex<Repl>>> {
+ 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<Resource> {
+ 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<dyn Future<Item = (), Error = DenoError> + 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<F>(future: F)
+where
+ F: Future<Item = (), Error = ()> + Send + 'static,
+{
+ // tokio::runtime::current_thread::run(future)
+ tokio::run(future)
+}
+
+pub fn block_on<F, R, E>(future: F) -> Result<R, E>
+where
+ F: Send + 'static + Future<Item = R, Error = E>,
+ 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: 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<Self::Item, Self::Error> {
+ 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<T, E>`
+/// However, we require that `F: FnOnce()->Poll<T, E>`.
+/// Therefore, we created our version of `poll_fn`.
+pub fn poll_fn<T, E, F>(f: F) -> PollFn<F>
+where
+ F: FnOnce() -> Poll<T, E>,
+{
+ PollFn { inner: Some(f) }
+}
+
+pub struct PollFn<F> {
+ inner: Option<F>,
+}
+
+impl<T, E, F> Future for PollFn<F>
+where
+ F: FnOnce() -> Poll<T, E>,
+{
+ type Item = T;
+ type Error = E;
+
+ fn poll(&mut self) -> Poll<T, E> {
+ let f = self.inner.take().expect("Inner fn has been taken.");
+ f()
+ }
+}
+
+pub fn panic_on_error<I, E, F>(f: F) -> impl Future<Item = I, Error = ()>
+where
+ F: Future<Item = I, Error = E>,
+ 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<A, T> {
+ state: State<A, T>,
+}
+
+#[derive(Debug)]
+enum State<A, T> {
+ 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, T>(a: A, buf: T) -> Write<A, T>
+where
+ A: AsyncWrite,
+ T: AsRef<[u8]>,
+{
+ Write {
+ state: State::Pending { a, buf },
+ }
+}
+
+impl<A, T> Future for Write<A, T>
+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<StartupData>,
+ flags: DenoFlags,
+ argv: Vec<String>,
+ permissions: DenoPermissions,
+ ) -> (Self, WorkerChannels) {
+ let (worker_in_tx, worker_in_rx) = mpsc::channel::<Buf>(1);
+ let (worker_out_tx, worker_out_rx) = mpsc::channel::<Buf>(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<StartupData>,
+ 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::<JSError>();
+ let (p, c) = oneshot::channel::<resources::Resource>();
+ 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);
+ }
+}