summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/file_fetcher.rs6
-rw-r--r--cli/flags.rs188
-rw-r--r--cli/global_state.rs1
-rw-r--r--cli/http_util.rs164
-rw-r--r--cli/installer.rs4
-rw-r--r--cli/ops/fetch.rs3
-rw-r--r--cli/test_util.rs15
-rw-r--r--cli/tests/cafile_info.ts24
-rw-r--r--cli/tests/cafile_info.ts.out14
-rw-r--r--cli/tests/cafile_ts_fetch.ts3
-rw-r--r--cli/tests/cafile_ts_fetch.ts.out1
-rw-r--r--cli/tests/cafile_url_imports.ts3
-rw-r--r--cli/tests/cafile_url_imports.ts.out2
-rw-r--r--cli/tests/integration_tests.rs168
-rw-r--r--cli/tests/tls/README.md2
-rwxr-xr-xtools/http_server.py77
16 files changed, 654 insertions, 21 deletions
diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs
index 18d78c514..e69756b20 100644
--- a/cli/file_fetcher.rs
+++ b/cli/file_fetcher.rs
@@ -112,7 +112,8 @@ impl SourceFileFetcher {
cache_blacklist: Vec<String>,
no_remote: bool,
cached_only: bool,
- ) -> std::io::Result<Self> {
+ ca_file: Option<String>,
+ ) -> Result<Self, ErrBox> {
let file_fetcher = Self {
deps_cache,
progress,
@@ -121,7 +122,7 @@ impl SourceFileFetcher {
use_disk_cache,
no_remote,
cached_only,
- http_client: create_http_client(),
+ http_client: create_http_client(ca_file)?,
};
Ok(file_fetcher)
@@ -862,6 +863,7 @@ mod tests {
vec![],
false,
false,
+ None,
)
.expect("setup fail")
}
diff --git a/cli/flags.rs b/cli/flags.rs
index 445a08c0b..82cd59ca7 100644
--- a/cli/flags.rs
+++ b/cli/flags.rs
@@ -102,6 +102,7 @@ pub struct DenoFlags {
pub lock: Option<String>,
pub lock_write: bool,
+ pub ca_file: Option<String>,
}
fn join_paths(whitelist: &[PathBuf], d: &str) -> String {
@@ -313,6 +314,7 @@ fn fmt_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
fn install_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
permission_args_parse(flags, matches);
+ ca_file_arg_parse(flags, matches);
let dir = if matches.is_present("dir") {
let install_dir = matches.value_of("dir").unwrap();
@@ -343,6 +345,8 @@ fn install_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
}
fn bundle_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
+ ca_file_arg_parse(flags, matches);
+
let source_file = matches.value_of("source_file").unwrap().to_string();
let out_file = if let Some(out_file) = matches.value_of("out_file") {
@@ -375,6 +379,7 @@ fn completions_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
fn repl_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
v8_flags_arg_parse(flags, matches);
+ ca_file_arg_parse(flags, matches);
flags.subcommand = DenoSubcommand::Repl;
flags.allow_net = true;
flags.allow_env = true;
@@ -387,6 +392,7 @@ fn repl_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
fn eval_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
v8_flags_arg_parse(flags, matches);
+ ca_file_arg_parse(flags, matches);
flags.allow_net = true;
flags.allow_env = true;
flags.allow_run = true;
@@ -399,6 +405,8 @@ fn eval_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
}
fn info_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
+ ca_file_arg_parse(flags, matches);
+
flags.subcommand = DenoSubcommand::Info {
file: matches.value_of("file").map(|f| f.to_string()),
};
@@ -410,6 +418,7 @@ fn fetch_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
importmap_arg_parse(flags, matches);
config_arg_parse(flags, matches);
no_remote_arg_parse(flags, matches);
+ ca_file_arg_parse(flags, matches);
let files = matches
.values_of("file")
.unwrap()
@@ -444,6 +453,7 @@ fn run_test_args_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
v8_flags_arg_parse(flags, matches);
no_remote_arg_parse(flags, matches);
permission_args_parse(flags, matches);
+ ca_file_arg_parse(flags, matches);
if matches.is_present("cached-only") {
flags.cached_only = true;
@@ -558,6 +568,7 @@ fn repl_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("repl")
.about("Read Eval Print Loop")
.arg(v8_flags_arg())
+ .arg(ca_file_arg())
}
fn install_subcommand<'a, 'b>() -> App<'a, 'b> {
@@ -586,6 +597,7 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> {
.multiple(true)
.allow_hyphen_values(true)
)
+ .arg(ca_file_arg())
.about("Install script as executable")
.long_about(
"Installs a script as executable. The default installation directory is
@@ -608,6 +620,7 @@ fn bundle_subcommand<'a, 'b>() -> App<'a, 'b> {
.required(true),
)
.arg(Arg::with_name("out_file").takes_value(true).required(false))
+ .arg(ca_file_arg())
.about("Bundle module and dependencies into single file")
.long_about(
"Output a single JavaScript file with all dependencies.
@@ -642,6 +655,7 @@ Example:
fn eval_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("eval")
+ .arg(ca_file_arg())
.about("Eval script")
.long_about(
"Evaluate JavaScript from command-line
@@ -677,6 +691,7 @@ Remote modules cache: directory containing remote modules
TypeScript compiler cache: directory containing TS compiler output",
)
.arg(Arg::with_name("file").takes_value(true).required(false))
+ .arg(ca_file_arg())
}
fn fetch_subcommand<'a, 'b>() -> App<'a, 'b> {
@@ -693,6 +708,7 @@ fn fetch_subcommand<'a, 'b>() -> App<'a, 'b> {
.required(true)
.min_values(1),
)
+ .arg(ca_file_arg())
.about("Fetch the dependencies")
.long_about(
"Fetch and compile remote dependencies recursively.
@@ -777,6 +793,7 @@ fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
.arg(lock_write_arg())
.arg(no_remote_arg())
.arg(v8_flags_arg())
+ .arg(ca_file_arg())
.arg(
Arg::with_name("cached-only")
.long("cached-only")
@@ -896,6 +913,17 @@ fn config_arg_parse(flags: &mut DenoFlags, matches: &ArgMatches) {
flags.config_path = matches.value_of("config").map(ToOwned::to_owned);
}
+fn ca_file_arg<'a, 'b>() -> Arg<'a, 'b> {
+ Arg::with_name("cert")
+ .long("cert")
+ .value_name("FILE")
+ .help("Load certificate authority from PEM encoded file")
+ .takes_value(true)
+}
+fn ca_file_arg_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
+ flags.ca_file = matches.value_of("cert").map(ToOwned::to_owned);
+}
+
fn reload_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("reload")
.short("r")
@@ -2045,3 +2073,163 @@ mod tests {
);
}
}
+
+#[test]
+fn run_with_cafile() {
+ let r = flags_from_vec_safe(svec![
+ "deno",
+ "run",
+ "--cert",
+ "example.crt",
+ "script.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ DenoFlags {
+ subcommand: DenoSubcommand::Run {
+ script: "script.ts".to_string(),
+ },
+ ca_file: Some("example.crt".to_owned()),
+ ..DenoFlags::default()
+ }
+ );
+}
+
+#[test]
+fn bundle_with_cafile() {
+ let r = flags_from_vec_safe(svec![
+ "deno",
+ "bundle",
+ "--cert",
+ "example.crt",
+ "source.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ DenoFlags {
+ subcommand: DenoSubcommand::Bundle {
+ source_file: "source.ts".to_string(),
+ out_file: None,
+ },
+ ca_file: Some("example.crt".to_owned()),
+ ..DenoFlags::default()
+ }
+ );
+}
+
+#[test]
+fn eval_with_cafile() {
+ let r = flags_from_vec_safe(svec![
+ "deno",
+ "eval",
+ "--cert",
+ "example.crt",
+ "console.log('hello world')"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ DenoFlags {
+ subcommand: DenoSubcommand::Eval {
+ code: "console.log('hello world')".to_string(),
+ },
+ ca_file: Some("example.crt".to_owned()),
+ allow_net: true,
+ allow_env: true,
+ allow_run: true,
+ allow_read: true,
+ allow_write: true,
+ allow_plugin: true,
+ allow_hrtime: true,
+ ..DenoFlags::default()
+ }
+ );
+}
+
+#[test]
+fn fetch_with_cafile() {
+ let r = flags_from_vec_safe(svec![
+ "deno",
+ "fetch",
+ "--cert",
+ "example.crt",
+ "script.ts",
+ "script_two.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ DenoFlags {
+ subcommand: DenoSubcommand::Fetch {
+ files: svec!["script.ts", "script_two.ts"],
+ },
+ ca_file: Some("example.crt".to_owned()),
+ ..DenoFlags::default()
+ }
+ );
+}
+
+#[test]
+fn info_with_cafile() {
+ let r = flags_from_vec_safe(svec![
+ "deno",
+ "info",
+ "--cert",
+ "example.crt",
+ "https://example.com"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ DenoFlags {
+ subcommand: DenoSubcommand::Info {
+ file: Some("https://example.com".to_string()),
+ },
+ ca_file: Some("example.crt".to_owned()),
+ ..DenoFlags::default()
+ }
+ );
+}
+
+#[test]
+fn install_with_cafile() {
+ let r = flags_from_vec_safe(svec![
+ "deno",
+ "install",
+ "--cert",
+ "example.crt",
+ "deno_colors",
+ "https://deno.land/std/examples/colors.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ DenoFlags {
+ subcommand: DenoSubcommand::Install {
+ dir: None,
+ exe_name: "deno_colors".to_string(),
+ module_url: "https://deno.land/std/examples/colors.ts".to_string(),
+ args: vec![],
+ force: false,
+ },
+ ca_file: Some("example.crt".to_owned()),
+ ..DenoFlags::default()
+ }
+ );
+}
+
+#[test]
+fn repl_with_cafile() {
+ let r = flags_from_vec_safe(svec!["deno", "repl", "--cert", "example.crt"]);
+ assert_eq!(
+ r.unwrap(),
+ DenoFlags {
+ subcommand: DenoSubcommand::Repl {},
+ ca_file: Some("example.crt".to_owned()),
+ allow_read: true,
+ allow_write: true,
+ allow_net: true,
+ allow_env: true,
+ allow_run: true,
+ allow_plugin: true,
+ allow_hrtime: true,
+ ..DenoFlags::default()
+ }
+ );
+}
diff --git a/cli/global_state.rs b/cli/global_state.rs
index 0bbd213aa..a11900218 100644
--- a/cli/global_state.rs
+++ b/cli/global_state.rs
@@ -81,6 +81,7 @@ impl GlobalState {
flags.cache_blacklist.clone(),
flags.no_remote,
flags.cached_only,
+ flags.ca_file.clone(),
)?;
let ts_compiler = TsCompiler::new(
diff --git a/cli/http_util.rs b/cli/http_util.rs
index 6bff0f8bb..0140d014a 100644
--- a/cli/http_util.rs
+++ b/cli/http_util.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::deno_error;
use crate::deno_error::DenoError;
+use crate::deno_error::ErrorKind;
use crate::version;
use brotli2::read::BrotliDecoder;
use bytes::Bytes;
@@ -21,6 +22,7 @@ use reqwest::Client;
use reqwest::Response;
use reqwest::StatusCode;
use std::cmp::min;
+use std::fs::File;
use std::future::Future;
use std::io;
use std::io::Read;
@@ -32,20 +34,31 @@ use url::Url;
/// Create new instance of async reqwest::Client. This client supports
/// proxies and doesn't follow redirects.
-pub fn create_http_client() -> Client {
+pub fn create_http_client(ca_file: Option<String>) -> Result<Client, ErrBox> {
let mut headers = HeaderMap::new();
headers.insert(
USER_AGENT,
format!("Deno/{}", version::DENO).parse().unwrap(),
);
- Client::builder()
+ let mut builder = Client::builder()
.redirect(Policy::none())
.default_headers(headers)
- .use_rustls_tls()
- .build()
- .unwrap()
-}
+ .use_rustls_tls();
+
+ if let Some(ca_file) = ca_file {
+ let mut buf = Vec::new();
+ File::open(ca_file)?.read_to_end(&mut buf)?;
+ let cert = reqwest::Certificate::from_pem(&buf)?;
+ builder = builder.add_root_certificate(cert);
+ }
+ builder.build().map_err(|_| {
+ ErrBox::from(DenoError::new(
+ ErrorKind::Other,
+ "Unable to build http client".to_string(),
+ ))
+ })
+}
/// Construct the next uri based on base uri and location header fragment
/// See <https://tools.ietf.org/html/rfc3986#section-4.2>
fn resolve_url_from_location(base_url: &Url, location: &str) -> Url {
@@ -276,7 +289,7 @@ mod tests {
// Relies on external http server. See tools/http_server.py
let url =
Url::parse("http://127.0.0.1:4545/cli/tests/fixture.json").unwrap();
- let client = create_http_client();
+ let client = create_http_client(None).unwrap();
let result = fetch_once(client, &url, None).await;
if let Ok(FetchOnceResult::Code(payload)) = result {
assert!(!payload.body.is_empty());
@@ -297,7 +310,7 @@ mod tests {
"http://127.0.0.1:4545/cli/tests/053_import_compression/gziped",
)
.unwrap();
- let client = create_http_client();
+ let client = create_http_client(None).unwrap();
let result = fetch_once(client, &url, None).await;
if let Ok(FetchOnceResult::Code(payload)) = result {
assert_eq!(
@@ -320,7 +333,7 @@ mod tests {
async fn test_fetch_with_etag() {
let http_server_guard = crate::test_util::http_server();
let url = Url::parse("http://127.0.0.1:4545/etag_script.ts").unwrap();
- let client = create_http_client();
+ let client = create_http_client(None).unwrap();
let result = fetch_once(client.clone(), &url, None).await;
if let Ok(FetchOnceResult::Code(ResultPayload {
body,
@@ -353,7 +366,7 @@ mod tests {
"http://127.0.0.1:4545/cli/tests/053_import_compression/brotli",
)
.unwrap();
- let client = create_http_client();
+ let client = create_http_client(None).unwrap();
let result = fetch_once(client, &url, None).await;
if let Ok(FetchOnceResult::Code(payload)) = result {
assert!(!payload.body.is_empty());
@@ -382,7 +395,7 @@ mod tests {
// Dns resolver substitutes `127.0.0.1` with `localhost`
let target_url =
Url::parse("http://localhost:4545/cli/tests/fixture.json").unwrap();
- let client = create_http_client();
+ let client = create_http_client(None).unwrap();
let result = fetch_once(client, &url, None).await;
if let Ok(FetchOnceResult::Redirect(url)) = result {
assert_eq!(url, target_url);
@@ -429,4 +442,133 @@ mod tests {
assert_eq!(new_uri.host_str().unwrap(), "deno.land");
assert_eq!(new_uri.path(), "/z");
}
+
+ #[tokio::test]
+ async fn test_fetch_with_cafile_sync_string() {
+ let http_server_guard = crate::test_util::http_server();
+ // Relies on external http server. See tools/http_server.py
+ let url =
+ Url::parse("https://localhost:5545/cli/tests/fixture.json").unwrap();
+
+ let client = create_http_client(Some(String::from(
+ crate::test_util::root_path()
+ .join("std/http/testdata/tls/RootCA.pem")
+ .to_str()
+ .unwrap(),
+ )))
+ .unwrap();
+ let result = fetch_once(client, &url, None).await;
+
+ if let Ok(FetchOnceResult::Code(payload)) = result {
+ assert!(!payload.body.is_empty());
+ assert_eq!(payload.content_type, Some("application/json".to_string()));
+ assert_eq!(payload.etag, None);
+ assert_eq!(payload.x_typescript_types, None);
+ } else {
+ panic!();
+ }
+ drop(http_server_guard);
+ }
+
+ #[tokio::test]
+ async fn test_fetch_with_cafile_gzip() {
+ let http_server_guard = crate::test_util::http_server();
+ // Relies on external http server. See tools/http_server.py
+ let url = Url::parse(
+ "https://localhost:5545/cli/tests/053_import_compression/gziped",
+ )
+ .unwrap();
+ let client = create_http_client(Some(String::from(
+ crate::test_util::root_path()
+ .join("std/http/testdata/tls/RootCA.pem")
+ .to_str()
+ .unwrap(),
+ )))
+ .unwrap();
+ let result = fetch_once(client, &url, None).await;
+ if let Ok(FetchOnceResult::Code(payload)) = result {
+ assert_eq!(
+ String::from_utf8(payload.body).unwrap(),
+ "console.log('gzip')"
+ );
+ assert_eq!(
+ payload.content_type,
+ Some("application/javascript".to_string())
+ );
+ assert_eq!(payload.etag, None);
+ assert_eq!(payload.x_typescript_types, None);
+ } else {
+ panic!();
+ }
+ drop(http_server_guard);
+ }
+
+ #[tokio::test]
+ async fn test_fetch_with_cafile_with_etag() {
+ let http_server_guard = crate::test_util::http_server();
+ let url = Url::parse("https://localhost:5545/etag_script.ts").unwrap();
+ let client = create_http_client(Some(String::from(
+ crate::test_util::root_path()
+ .join("std/http/testdata/tls/RootCA.pem")
+ .to_str()
+ .unwrap(),
+ )))
+ .unwrap();
+ let result = fetch_once(client.clone(), &url, None).await;
+ if let Ok(FetchOnceResult::Code(ResultPayload {
+ body,
+ content_type,
+ etag,
+ x_typescript_types,
+ })) = result
+ {
+ assert!(!body.is_empty());
+ assert_eq!(String::from_utf8(body).unwrap(), "console.log('etag')");
+ assert_eq!(content_type, Some("application/typescript".to_string()));
+ assert_eq!(etag, Some("33a64df551425fcc55e".to_string()));
+ assert_eq!(x_typescript_types, None);
+ } else {
+ panic!();
+ }
+
+ let res =
+ fetch_once(client, &url, Some("33a64df551425fcc55e".to_string())).await;
+ assert_eq!(res.unwrap(), FetchOnceResult::NotModified);
+
+ drop(http_server_guard);
+ }
+
+ #[tokio::test]
+ async fn test_fetch_with_cafile_brotli() {
+ let http_server_guard = crate::test_util::http_server();
+ // Relies on external http server. See tools/http_server.py
+ let url = Url::parse(
+ "https://localhost:5545/cli/tests/053_import_compression/brotli",
+ )
+ .unwrap();
+ let client = create_http_client(Some(String::from(
+ crate::test_util::root_path()
+ .join("std/http/testdata/tls/RootCA.pem")
+ .to_str()
+ .unwrap(),
+ )))
+ .unwrap();
+ let result = fetch_once(client, &url, None).await;
+ if let Ok(FetchOnceResult::Code(payload)) = result {
+ assert!(!payload.body.is_empty());
+ assert_eq!(
+ String::from_utf8(payload.body).unwrap(),
+ "console.log('brotli');"
+ );
+ assert_eq!(
+ payload.content_type,
+ Some("application/javascript".to_string())
+ );
+ assert_eq!(payload.etag, None);
+ assert_eq!(payload.x_typescript_types, None);
+ } else {
+ panic!();
+ }
+ drop(http_server_guard);
+ }
}
diff --git a/cli/installer.rs b/cli/installer.rs
index eeae35c44..d2d263447 100644
--- a/cli/installer.rs
+++ b/cli/installer.rs
@@ -155,6 +155,10 @@ pub fn install(
let mut executable_args = vec!["run".to_string()];
executable_args.extend_from_slice(&flags.to_permission_args());
+ if let Some(ca_file) = flags.ca_file {
+ executable_args.push("--cert".to_string());
+ executable_args.push(ca_file)
+ }
executable_args.push(module_url.to_string());
executable_args.extend_from_slice(&args);
diff --git a/cli/ops/fetch.rs b/cli/ops/fetch.rs
index f43133d7f..580fd993a 100644
--- a/cli/ops/fetch.rs
+++ b/cli/ops/fetch.rs
@@ -31,7 +31,8 @@ pub fn op_fetch(
let args: FetchArgs = serde_json::from_value(args)?;
let url = args.url;
- let client = create_http_client();
+ let client =
+ create_http_client(state.borrow().global_state.flags.ca_file.clone())?;
let method = match args.method {
Some(method_str) => Method::from_bytes(method_str.as_bytes())?,
diff --git a/cli/test_util.rs b/cli/test_util.rs
index 9c0307096..d2a49d05f 100644
--- a/cli/test_util.rs
+++ b/cli/test_util.rs
@@ -74,7 +74,20 @@ pub fn http_server() -> HttpServerGuard {
println!("tools/http_server.py starting...");
let mut child = Command::new("python")
.current_dir(root_path())
- .args(&["-u", "tools/http_server.py"])
+ .args(&[
+ "-u",
+ "tools/http_server.py",
+ "--certfile",
+ root_path()
+ .join("std/http/testdata/tls/localhost.crt")
+ .to_str()
+ .unwrap(),
+ "--keyfile",
+ root_path()
+ .join("std/http/testdata/tls/localhost.key")
+ .to_str()
+ .unwrap(),
+ ])
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute child");
diff --git a/cli/tests/cafile_info.ts b/cli/tests/cafile_info.ts
new file mode 100644
index 000000000..e11d106e6
--- /dev/null
+++ b/cli/tests/cafile_info.ts
@@ -0,0 +1,24 @@
+// When run against the test HTTP server, it will serve different media types
+// based on the URL containing `.t#.` strings, which exercises the different
+// mapping of media types end to end.
+
+import { loaded as loadedTs1 } from "https://localhost:5545/cli/tests/subdir/mt_text_typescript.t1.ts";
+import { loaded as loadedTs2 } from "https://localhost:5545/cli/tests/subdir/mt_video_vdn.t2.ts";
+import { loaded as loadedTs3 } from "https://localhost:5545/cli/tests/subdir/mt_video_mp2t.t3.ts";
+import { loaded as loadedTs4 } from "https://localhost:5545/cli/tests/subdir/mt_application_x_typescript.t4.ts";
+import { loaded as loadedJs1 } from "https://localhost:5545/cli/tests/subdir/mt_text_javascript.j1.js";
+import { loaded as loadedJs2 } from "https://localhost:5545/cli/tests/subdir/mt_application_ecmascript.j2.js";
+import { loaded as loadedJs3 } from "https://localhost:5545/cli/tests/subdir/mt_text_ecmascript.j3.js";
+import { loaded as loadedJs4 } from "https://localhost:5545/cli/tests/subdir/mt_application_x_javascript.j4.js";
+
+console.log(
+ "success",
+ loadedTs1,
+ loadedTs2,
+ loadedTs3,
+ loadedTs4,
+ loadedJs1,
+ loadedJs2,
+ loadedJs3,
+ loadedJs4
+);
diff --git a/cli/tests/cafile_info.ts.out b/cli/tests/cafile_info.ts.out
new file mode 100644
index 000000000..443b92eea
--- /dev/null
+++ b/cli/tests/cafile_info.ts.out
@@ -0,0 +1,14 @@
+local: [WILDCARD]cafile_info.ts
+type: TypeScript
+compiled: [WILDCARD].js
+map: [WILDCARD].js.map
+deps:
+https://localhost:5545/cli/tests/cafile_info.ts
+ ├── https://localhost:5545/cli/tests/subdir/mt_text_typescript.t1.ts
+ ├── https://localhost:5545/cli/tests/subdir/mt_video_vdn.t2.ts
+ ├── https://localhost:5545/cli/tests/subdir/mt_video_mp2t.t3.ts
+ ├── https://localhost:5545/cli/tests/subdir/mt_application_x_typescript.t4.ts
+ ├── https://localhost:5545/cli/tests/subdir/mt_text_javascript.j1.js
+ ├── https://localhost:5545/cli/tests/subdir/mt_application_ecmascript.j2.js
+ ├── https://localhost:5545/cli/tests/subdir/mt_text_ecmascript.j3.js
+ └── https://localhost:5545/cli/tests/subdir/mt_application_x_javascript.j4.js
diff --git a/cli/tests/cafile_ts_fetch.ts b/cli/tests/cafile_ts_fetch.ts
new file mode 100644
index 000000000..be158bf70
--- /dev/null
+++ b/cli/tests/cafile_ts_fetch.ts
@@ -0,0 +1,3 @@
+fetch("https://localhost:5545/cli/tests/cafile_ts_fetch.ts.out")
+ .then(r => r.text())
+ .then(t => console.log(t.trimEnd()));
diff --git a/cli/tests/cafile_ts_fetch.ts.out b/cli/tests/cafile_ts_fetch.ts.out
new file mode 100644
index 000000000..e965047ad
--- /dev/null
+++ b/cli/tests/cafile_ts_fetch.ts.out
@@ -0,0 +1 @@
+Hello
diff --git a/cli/tests/cafile_url_imports.ts b/cli/tests/cafile_url_imports.ts
new file mode 100644
index 000000000..f781f32f5
--- /dev/null
+++ b/cli/tests/cafile_url_imports.ts
@@ -0,0 +1,3 @@
+import { printHello } from "https://localhost:5545/cli/tests/subdir/mod2.ts";
+printHello();
+console.log("success");
diff --git a/cli/tests/cafile_url_imports.ts.out b/cli/tests/cafile_url_imports.ts.out
new file mode 100644
index 000000000..989ce33e9
--- /dev/null
+++ b/cli/tests/cafile_url_imports.ts.out
@@ -0,0 +1,2 @@
+Hello
+success
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index fb35163f0..38c870200 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -929,6 +929,174 @@ itest!(import_wasm_via_network {
http_server: true,
});
+itest!(cafile_url_imports {
+ args: "run --reload --cert tls/RootCA.pem cafile_url_imports.ts",
+ output: "cafile_url_imports.ts.out",
+ http_server: true,
+});
+
+itest!(cafile_ts_fetch {
+ args: "run --reload --allow-net --cert tls/RootCA.pem cafile_ts_fetch.ts",
+ output: "cafile_ts_fetch.ts.out",
+ http_server: true,
+});
+
+itest!(cafile_eval {
+ args: "eval --cert tls/RootCA.pem fetch('https://localhost:5545/cli/tests/cafile_ts_fetch.ts.out').then(r=>r.text()).then(t=>console.log(t.trimEnd()))",
+ output: "cafile_ts_fetch.ts.out",
+ http_server: true,
+});
+
+itest!(cafile_info {
+ args:
+ "info --cert tls/RootCA.pem https://localhost:5545/cli/tests/cafile_info.ts",
+ output: "cafile_info.ts.out",
+ http_server: true,
+});
+
+#[test]
+fn cafile_fetch() {
+ pub use deno::test_util::*;
+ use std::process::Command;
+ use tempfile::TempDir;
+
+ let g = util::http_server();
+
+ let deno_dir = TempDir::new().expect("tempdir fail");
+ let t = util::root_path().join("cli/tests/cafile_url_imports.ts");
+ let cafile = util::root_path().join("cli/tests/tls/RootCA.pem");
+ let output = Command::new(deno_exe_path())
+ .env("DENO_DIR", deno_dir.path())
+ .current_dir(util::root_path())
+ .arg("fetch")
+ .arg("--cert")
+ .arg(cafile)
+ .arg(t)
+ .output()
+ .expect("Failed to spawn script");
+
+ let code = output.status.code();
+ let out = std::str::from_utf8(&output.stdout).unwrap();
+
+ assert_eq!(Some(0), code);
+ assert_eq!(out, "");
+
+ let expected_path = deno_dir
+ .path()
+ .join("deps/https/localhost_PORT5545/cli/tests/subdir/mod2.ts");
+ assert_eq!(expected_path.exists(), true);
+
+ drop(g);
+}
+
+#[test]
+fn cafile_install_remote_module() {
+ pub use deno::test_util::*;
+ use std::env;
+ use std::path::PathBuf;
+ use std::process::Command;
+ use tempfile::TempDir;
+
+ let g = util::http_server();
+ let temp_dir = TempDir::new().expect("tempdir fail");
+ let deno_dir = TempDir::new().expect("tempdir fail");
+ let cafile = util::root_path().join("cli/tests/tls/RootCA.pem");
+
+ let install_output = Command::new(deno_exe_path())
+ .env("DENO_DIR", deno_dir.path())
+ .current_dir(util::root_path())
+ .arg("install")
+ .arg("--cert")
+ .arg(cafile)
+ .arg("--dir")
+ .arg(temp_dir.path())
+ .arg("echo_test")
+ .arg("https://localhost:5545/cli/tests/echo.ts")
+ .output()
+ .expect("Failed to spawn script");
+
+ let code = install_output.status.code();
+ assert_eq!(Some(0), code);
+
+ let mut file_path = temp_dir.path().join("echo_test");
+ if cfg!(windows) {
+ file_path = file_path.with_extension(".cmd");
+ }
+ assert!(file_path.exists());
+
+ let path_var_name = if cfg!(windows) { "Path" } else { "PATH" };
+ let paths_var = env::var_os(path_var_name).expect("PATH not set");
+ let mut paths: Vec<PathBuf> = env::split_paths(&paths_var).collect();
+ paths.push(temp_dir.path().to_owned());
+ paths.push(util::target_dir());
+ let path_var_value = env::join_paths(paths).expect("Can't create PATH");
+
+ let output = Command::new(file_path)
+ .current_dir(temp_dir.path())
+ .arg("foo")
+ .env(path_var_name, path_var_value)
+ .output()
+ .expect("failed to spawn script");
+ assert!(std::str::from_utf8(&output.stdout)
+ .unwrap()
+ .trim()
+ .ends_with("foo"));
+
+ drop(deno_dir);
+ drop(temp_dir);
+ drop(g)
+}
+
+#[test]
+fn cafile_bundle_remote_exports() {
+ use tempfile::TempDir;
+
+ let g = util::http_server();
+
+ // First we have to generate a bundle of some remote module that has exports.
+ let mod1 = "https://localhost:5545/cli/tests/subdir/mod1.ts";
+ let cafile = util::root_path().join("cli/tests/tls/RootCA.pem");
+ let t = TempDir::new().expect("tempdir fail");
+ let bundle = t.path().join("mod1.bundle.js");
+ let mut deno = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("bundle")
+ .arg("--cert")
+ .arg(cafile)
+ .arg(mod1)
+ .arg(&bundle)
+ .spawn()
+ .expect("failed to spawn script");
+ let status = deno.wait().expect("failed to wait for the child process");
+ assert!(status.success());
+ assert!(bundle.is_file());
+
+ // Now we try to use that bundle from another module.
+ let test = t.path().join("test.js");
+ std::fs::write(
+ &test,
+ "
+ import { printHello3 } from \"./mod1.bundle.js\";
+ printHello3(); ",
+ )
+ .expect("error writing file");
+
+ let output = util::deno_cmd()
+ .current_dir(util::root_path())
+ .arg("run")
+ .arg(&test)
+ .output()
+ .expect("failed to spawn script");
+ // check the output of the test.ts program.
+ assert!(std::str::from_utf8(&output.stdout)
+ .unwrap()
+ .trim()
+ .ends_with("Hello"));
+ assert_eq!(output.stderr, b"");
+
+ drop(g)
+}
+
mod util {
use deno::colors::strip_ansi_codes;
pub use deno::test_util::*;
diff --git a/cli/tests/tls/README.md b/cli/tests/tls/README.md
index 14399ae82..34de47dea 100644
--- a/cli/tests/tls/README.md
+++ b/cli/tests/tls/README.md
@@ -5,7 +5,7 @@ https://gist.github.com/cecilemuller/9492b848eb8fe46d462abeb26656c4f8
## Certificate authority (CA)
-Generate RootCA.pem, RootCA.key & RootCA.crt:
+Generate RootCA.pem, RootCA.key, RootCA.crt:
```shell
openssl req -x509 -nodes -new -sha256 -days 36135 -newkey rsa:2048 -keyout RootCA.key -out RootCA.pem -subj "/C=US/CN=Example-Root-CA"
diff --git a/tools/http_server.py b/tools/http_server.py
index 871888a4e..2097c153d 100755
--- a/tools/http_server.py
+++ b/tools/http_server.py
@@ -12,6 +12,9 @@ import sys
from time import sleep
from threading import Thread
from util import root_path
+import ssl
+import getopt
+import argparse
PORT = 4545
REDIRECT_PORT = 4546
@@ -19,7 +22,52 @@ ANOTHER_REDIRECT_PORT = 4547
DOUBLE_REDIRECTS_PORT = 4548
INF_REDIRECTS_PORT = 4549
-QUIET = '-v' not in sys.argv and '--verbose' not in sys.argv
+HTTPS_PORT = 5545
+
+
+def create_http_arg_parser():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--certfile')
+ parser.add_argument('--keyfile')
+ parser.add_argument('--verbose', '-v', action='store_true')
+ return parser
+
+
+HttpArgParser = create_http_arg_parser()
+
+args, unknown = HttpArgParser.parse_known_args(sys.argv[1:])
+CERT_FILE = args.certfile
+KEY_FILE = args.keyfile
+QUIET = not args.verbose
+
+
+class SSLTCPServer(SocketServer.TCPServer):
+ def __init__(self,
+ server_address,
+ request_handler,
+ certfile,
+ keyfile,
+ ssl_version=ssl.PROTOCOL_TLSv1_2,
+ bind_and_activate=True):
+ SocketServer.TCPServer.__init__(self, server_address, request_handler,
+ bind_and_activate)
+ self.certfile = certfile
+ self.keyfile = keyfile
+ self.ssl_version = ssl_version
+
+ def get_request(self):
+ newsocket, fromaddr = self.socket.accept()
+ connstream = ssl.wrap_socket(
+ newsocket,
+ server_side=True,
+ certfile=self.certfile,
+ keyfile=self.keyfile,
+ ssl_version=self.ssl_version)
+ return connstream, fromaddr
+
+
+class SSLThreadingTCPServer(SocketServer.ThreadingMixIn, SSLTCPServer):
+ pass
class QuietSimpleHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
@@ -169,7 +217,7 @@ class ContentTypeHandler(QuietSimpleHTTPRequestHandler):
RunningServer = namedtuple("RunningServer", ["server", "thread"])
-def get_socket(port, handler):
+def get_socket(port, handler, use_https):
SocketServer.TCPServer.allow_reuse_address = True
if os.name != "nt":
# We use AF_INET6 to avoid flaky test issue, particularly with
@@ -177,6 +225,9 @@ def get_socket(port, handler):
# flaky tests, but it does appear to...
# See https://github.com/denoland/deno/issues/3332
SocketServer.TCPServer.address_family = socket.AF_INET6
+
+ if use_https:
+ return SSLThreadingTCPServer(("", port), handler, CERT_FILE, KEY_FILE)
return SocketServer.TCPServer(("", port), handler)
@@ -190,7 +241,7 @@ def server():
".jsx": "application/javascript",
".json": "application/json",
})
- s = get_socket(PORT, Handler)
+ s = get_socket(PORT, Handler, False)
if not QUIET:
print "Deno test server http://localhost:%d/" % PORT
return RunningServer(s, start(s))
@@ -207,7 +258,7 @@ def base_redirect_server(host_port, target_port, extra_path_segment=""):
target_host + extra_path_segment + self.path)
self.end_headers()
- s = get_socket(host_port, RedirectHandler)
+ s = get_socket(host_port, RedirectHandler, False)
if not QUIET:
print "redirect server http://localhost:%d/ -> http://localhost:%d/" % (
host_port, target_port)
@@ -236,6 +287,22 @@ def inf_redirects_server():
return base_redirect_server(INF_REDIRECTS_PORT, INF_REDIRECTS_PORT)
+def https_server():
+ os.chdir(root_path) # Hopefully the main thread doesn't also chdir.
+ Handler = ContentTypeHandler
+ Handler.extensions_map.update({
+ ".ts": "application/typescript",
+ ".js": "application/javascript",
+ ".tsx": "application/typescript",
+ ".jsx": "application/javascript",
+ ".json": "application/json",
+ })
+ s = get_socket(HTTPS_PORT, Handler, True)
+ if not QUIET:
+ print "Deno https test server https://localhost:%d/" % HTTPS_PORT
+ return RunningServer(s, start(s))
+
+
def start(s):
thread = Thread(target=s.serve_forever, kwargs={"poll_interval": 0.05})
thread.daemon = True
@@ -246,7 +313,7 @@ def start(s):
@contextmanager
def spawn():
servers = (server(), redirect_server(), another_redirect_server(),
- double_redirects_server())
+ double_redirects_server(), https_server())
while any(not s.thread.is_alive() for s in servers):
sleep(0.01)
try: