1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 16:49:18 -05:00

feat: add --cert flag for http client (#3972)

This commit is contained in:
geoFlux 2020-02-17 11:59:51 -05:00 committed by GitHub
parent 98e585a284
commit 2e7d449623
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 654 additions and 21 deletions

View file

@ -112,7 +112,8 @@ impl SourceFileFetcher {
cache_blacklist: Vec<String>, cache_blacklist: Vec<String>,
no_remote: bool, no_remote: bool,
cached_only: bool, cached_only: bool,
) -> std::io::Result<Self> { ca_file: Option<String>,
) -> Result<Self, ErrBox> {
let file_fetcher = Self { let file_fetcher = Self {
deps_cache, deps_cache,
progress, progress,
@ -121,7 +122,7 @@ impl SourceFileFetcher {
use_disk_cache, use_disk_cache,
no_remote, no_remote,
cached_only, cached_only,
http_client: create_http_client(), http_client: create_http_client(ca_file)?,
}; };
Ok(file_fetcher) Ok(file_fetcher)
@ -862,6 +863,7 @@ mod tests {
vec![], vec![],
false, false,
false, false,
None,
) )
.expect("setup fail") .expect("setup fail")
} }

View file

@ -102,6 +102,7 @@ pub struct DenoFlags {
pub lock: Option<String>, pub lock: Option<String>,
pub lock_write: bool, pub lock_write: bool,
pub ca_file: Option<String>,
} }
fn join_paths(whitelist: &[PathBuf], d: &str) -> 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) { fn install_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
permission_args_parse(flags, matches); permission_args_parse(flags, matches);
ca_file_arg_parse(flags, matches);
let dir = if matches.is_present("dir") { let dir = if matches.is_present("dir") {
let install_dir = matches.value_of("dir").unwrap(); 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) { 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 source_file = matches.value_of("source_file").unwrap().to_string();
let out_file = if let Some(out_file) = matches.value_of("out_file") { 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) { fn repl_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
v8_flags_arg_parse(flags, matches); v8_flags_arg_parse(flags, matches);
ca_file_arg_parse(flags, matches);
flags.subcommand = DenoSubcommand::Repl; flags.subcommand = DenoSubcommand::Repl;
flags.allow_net = true; flags.allow_net = true;
flags.allow_env = 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) { fn eval_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
v8_flags_arg_parse(flags, matches); v8_flags_arg_parse(flags, matches);
ca_file_arg_parse(flags, matches);
flags.allow_net = true; flags.allow_net = true;
flags.allow_env = true; flags.allow_env = true;
flags.allow_run = 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) { fn info_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
ca_file_arg_parse(flags, matches);
flags.subcommand = DenoSubcommand::Info { flags.subcommand = DenoSubcommand::Info {
file: matches.value_of("file").map(|f| f.to_string()), 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); importmap_arg_parse(flags, matches);
config_arg_parse(flags, matches); config_arg_parse(flags, matches);
no_remote_arg_parse(flags, matches); no_remote_arg_parse(flags, matches);
ca_file_arg_parse(flags, matches);
let files = matches let files = matches
.values_of("file") .values_of("file")
.unwrap() .unwrap()
@ -444,6 +453,7 @@ fn run_test_args_parse(flags: &mut DenoFlags, matches: &clap::ArgMatches) {
v8_flags_arg_parse(flags, matches); v8_flags_arg_parse(flags, matches);
no_remote_arg_parse(flags, matches); no_remote_arg_parse(flags, matches);
permission_args_parse(flags, matches); permission_args_parse(flags, matches);
ca_file_arg_parse(flags, matches);
if matches.is_present("cached-only") { if matches.is_present("cached-only") {
flags.cached_only = true; flags.cached_only = true;
@ -558,6 +568,7 @@ fn repl_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("repl") SubCommand::with_name("repl")
.about("Read Eval Print Loop") .about("Read Eval Print Loop")
.arg(v8_flags_arg()) .arg(v8_flags_arg())
.arg(ca_file_arg())
} }
fn install_subcommand<'a, 'b>() -> App<'a, 'b> { fn install_subcommand<'a, 'b>() -> App<'a, 'b> {
@ -586,6 +597,7 @@ fn install_subcommand<'a, 'b>() -> App<'a, 'b> {
.multiple(true) .multiple(true)
.allow_hyphen_values(true) .allow_hyphen_values(true)
) )
.arg(ca_file_arg())
.about("Install script as executable") .about("Install script as executable")
.long_about( .long_about(
"Installs a script as executable. The default installation directory is "Installs a script as executable. The default installation directory is
@ -608,6 +620,7 @@ fn bundle_subcommand<'a, 'b>() -> App<'a, 'b> {
.required(true), .required(true),
) )
.arg(Arg::with_name("out_file").takes_value(true).required(false)) .arg(Arg::with_name("out_file").takes_value(true).required(false))
.arg(ca_file_arg())
.about("Bundle module and dependencies into single file") .about("Bundle module and dependencies into single file")
.long_about( .long_about(
"Output a single JavaScript file with all dependencies. "Output a single JavaScript file with all dependencies.
@ -642,6 +655,7 @@ Example:
fn eval_subcommand<'a, 'b>() -> App<'a, 'b> { fn eval_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("eval") SubCommand::with_name("eval")
.arg(ca_file_arg())
.about("Eval script") .about("Eval script")
.long_about( .long_about(
"Evaluate JavaScript from command-line "Evaluate JavaScript from command-line
@ -677,6 +691,7 @@ Remote modules cache: directory containing remote modules
TypeScript compiler cache: directory containing TS compiler output", TypeScript compiler cache: directory containing TS compiler output",
) )
.arg(Arg::with_name("file").takes_value(true).required(false)) .arg(Arg::with_name("file").takes_value(true).required(false))
.arg(ca_file_arg())
} }
fn fetch_subcommand<'a, 'b>() -> App<'a, 'b> { fn fetch_subcommand<'a, 'b>() -> App<'a, 'b> {
@ -693,6 +708,7 @@ fn fetch_subcommand<'a, 'b>() -> App<'a, 'b> {
.required(true) .required(true)
.min_values(1), .min_values(1),
) )
.arg(ca_file_arg())
.about("Fetch the dependencies") .about("Fetch the dependencies")
.long_about( .long_about(
"Fetch and compile remote dependencies recursively. "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(lock_write_arg())
.arg(no_remote_arg()) .arg(no_remote_arg())
.arg(v8_flags_arg()) .arg(v8_flags_arg())
.arg(ca_file_arg())
.arg( .arg(
Arg::with_name("cached-only") Arg::with_name("cached-only")
.long("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); 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> { fn reload_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name("reload") Arg::with_name("reload")
.short("r") .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()
}
);
}

View file

@ -81,6 +81,7 @@ impl GlobalState {
flags.cache_blacklist.clone(), flags.cache_blacklist.clone(),
flags.no_remote, flags.no_remote,
flags.cached_only, flags.cached_only,
flags.ca_file.clone(),
)?; )?;
let ts_compiler = TsCompiler::new( let ts_compiler = TsCompiler::new(

View file

@ -1,6 +1,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::deno_error; use crate::deno_error;
use crate::deno_error::DenoError; use crate::deno_error::DenoError;
use crate::deno_error::ErrorKind;
use crate::version; use crate::version;
use brotli2::read::BrotliDecoder; use brotli2::read::BrotliDecoder;
use bytes::Bytes; use bytes::Bytes;
@ -21,6 +22,7 @@ use reqwest::Client;
use reqwest::Response; use reqwest::Response;
use reqwest::StatusCode; use reqwest::StatusCode;
use std::cmp::min; use std::cmp::min;
use std::fs::File;
use std::future::Future; use std::future::Future;
use std::io; use std::io;
use std::io::Read; use std::io::Read;
@ -32,20 +34,31 @@ use url::Url;
/// Create new instance of async reqwest::Client. This client supports /// Create new instance of async reqwest::Client. This client supports
/// proxies and doesn't follow redirects. /// 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(); let mut headers = HeaderMap::new();
headers.insert( headers.insert(
USER_AGENT, USER_AGENT,
format!("Deno/{}", version::DENO).parse().unwrap(), format!("Deno/{}", version::DENO).parse().unwrap(),
); );
Client::builder() let mut builder = Client::builder()
.redirect(Policy::none()) .redirect(Policy::none())
.default_headers(headers) .default_headers(headers)
.use_rustls_tls() .use_rustls_tls();
.build()
.unwrap()
}
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 /// Construct the next uri based on base uri and location header fragment
/// See <https://tools.ietf.org/html/rfc3986#section-4.2> /// See <https://tools.ietf.org/html/rfc3986#section-4.2>
fn resolve_url_from_location(base_url: &Url, location: &str) -> Url { 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 // Relies on external http server. See tools/http_server.py
let url = let url =
Url::parse("http://127.0.0.1:4545/cli/tests/fixture.json").unwrap(); 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; let result = fetch_once(client, &url, None).await;
if let Ok(FetchOnceResult::Code(payload)) = result { if let Ok(FetchOnceResult::Code(payload)) = result {
assert!(!payload.body.is_empty()); assert!(!payload.body.is_empty());
@ -297,7 +310,7 @@ mod tests {
"http://127.0.0.1:4545/cli/tests/053_import_compression/gziped", "http://127.0.0.1:4545/cli/tests/053_import_compression/gziped",
) )
.unwrap(); .unwrap();
let client = create_http_client(); let client = create_http_client(None).unwrap();
let result = fetch_once(client, &url, None).await; let result = fetch_once(client, &url, None).await;
if let Ok(FetchOnceResult::Code(payload)) = result { if let Ok(FetchOnceResult::Code(payload)) = result {
assert_eq!( assert_eq!(
@ -320,7 +333,7 @@ mod tests {
async fn test_fetch_with_etag() { async fn test_fetch_with_etag() {
let http_server_guard = crate::test_util::http_server(); let http_server_guard = crate::test_util::http_server();
let url = Url::parse("http://127.0.0.1:4545/etag_script.ts").unwrap(); 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; let result = fetch_once(client.clone(), &url, None).await;
if let Ok(FetchOnceResult::Code(ResultPayload { if let Ok(FetchOnceResult::Code(ResultPayload {
body, body,
@ -353,7 +366,7 @@ mod tests {
"http://127.0.0.1:4545/cli/tests/053_import_compression/brotli", "http://127.0.0.1:4545/cli/tests/053_import_compression/brotli",
) )
.unwrap(); .unwrap();
let client = create_http_client(); let client = create_http_client(None).unwrap();
let result = fetch_once(client, &url, None).await; let result = fetch_once(client, &url, None).await;
if let Ok(FetchOnceResult::Code(payload)) = result { if let Ok(FetchOnceResult::Code(payload)) = result {
assert!(!payload.body.is_empty()); assert!(!payload.body.is_empty());
@ -382,7 +395,7 @@ mod tests {
// Dns resolver substitutes `127.0.0.1` with `localhost` // Dns resolver substitutes `127.0.0.1` with `localhost`
let target_url = let target_url =
Url::parse("http://localhost:4545/cli/tests/fixture.json").unwrap(); 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; let result = fetch_once(client, &url, None).await;
if let Ok(FetchOnceResult::Redirect(url)) = result { if let Ok(FetchOnceResult::Redirect(url)) = result {
assert_eq!(url, target_url); assert_eq!(url, target_url);
@ -429,4 +442,133 @@ mod tests {
assert_eq!(new_uri.host_str().unwrap(), "deno.land"); assert_eq!(new_uri.host_str().unwrap(), "deno.land");
assert_eq!(new_uri.path(), "/z"); 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);
}
} }

View file

@ -155,6 +155,10 @@ pub fn install(
let mut executable_args = vec!["run".to_string()]; let mut executable_args = vec!["run".to_string()];
executable_args.extend_from_slice(&flags.to_permission_args()); 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.push(module_url.to_string());
executable_args.extend_from_slice(&args); executable_args.extend_from_slice(&args);

View file

@ -31,7 +31,8 @@ pub fn op_fetch(
let args: FetchArgs = serde_json::from_value(args)?; let args: FetchArgs = serde_json::from_value(args)?;
let url = args.url; 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 { let method = match args.method {
Some(method_str) => Method::from_bytes(method_str.as_bytes())?, Some(method_str) => Method::from_bytes(method_str.as_bytes())?,

View file

@ -74,7 +74,20 @@ pub fn http_server() -> HttpServerGuard {
println!("tools/http_server.py starting..."); println!("tools/http_server.py starting...");
let mut child = Command::new("python") let mut child = Command::new("python")
.current_dir(root_path()) .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()) .stdout(Stdio::piped())
.spawn() .spawn()
.expect("failed to execute child"); .expect("failed to execute child");

24
cli/tests/cafile_info.ts Normal file
View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
Hello

View file

@ -0,0 +1,3 @@
import { printHello } from "https://localhost:5545/cli/tests/subdir/mod2.ts";
printHello();
console.log("success");

View file

@ -0,0 +1,2 @@
Hello
success

View file

@ -929,6 +929,174 @@ itest!(import_wasm_via_network {
http_server: true, 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 { mod util {
use deno::colors::strip_ansi_codes; use deno::colors::strip_ansi_codes;
pub use deno::test_util::*; pub use deno::test_util::*;

View file

@ -5,7 +5,7 @@ https://gist.github.com/cecilemuller/9492b848eb8fe46d462abeb26656c4f8
## Certificate authority (CA) ## Certificate authority (CA)
Generate RootCA.pem, RootCA.key & RootCA.crt: Generate RootCA.pem, RootCA.key, RootCA.crt:
```shell ```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" openssl req -x509 -nodes -new -sha256 -days 36135 -newkey rsa:2048 -keyout RootCA.key -out RootCA.pem -subj "/C=US/CN=Example-Root-CA"

View file

@ -12,6 +12,9 @@ import sys
from time import sleep from time import sleep
from threading import Thread from threading import Thread
from util import root_path from util import root_path
import ssl
import getopt
import argparse
PORT = 4545 PORT = 4545
REDIRECT_PORT = 4546 REDIRECT_PORT = 4546
@ -19,7 +22,52 @@ ANOTHER_REDIRECT_PORT = 4547
DOUBLE_REDIRECTS_PORT = 4548 DOUBLE_REDIRECTS_PORT = 4548
INF_REDIRECTS_PORT = 4549 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): class QuietSimpleHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
@ -169,7 +217,7 @@ class ContentTypeHandler(QuietSimpleHTTPRequestHandler):
RunningServer = namedtuple("RunningServer", ["server", "thread"]) RunningServer = namedtuple("RunningServer", ["server", "thread"])
def get_socket(port, handler): def get_socket(port, handler, use_https):
SocketServer.TCPServer.allow_reuse_address = True SocketServer.TCPServer.allow_reuse_address = True
if os.name != "nt": if os.name != "nt":
# We use AF_INET6 to avoid flaky test issue, particularly with # 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... # flaky tests, but it does appear to...
# See https://github.com/denoland/deno/issues/3332 # See https://github.com/denoland/deno/issues/3332
SocketServer.TCPServer.address_family = socket.AF_INET6 SocketServer.TCPServer.address_family = socket.AF_INET6
if use_https:
return SSLThreadingTCPServer(("", port), handler, CERT_FILE, KEY_FILE)
return SocketServer.TCPServer(("", port), handler) return SocketServer.TCPServer(("", port), handler)
@ -190,7 +241,7 @@ def server():
".jsx": "application/javascript", ".jsx": "application/javascript",
".json": "application/json", ".json": "application/json",
}) })
s = get_socket(PORT, Handler) s = get_socket(PORT, Handler, False)
if not QUIET: if not QUIET:
print "Deno test server http://localhost:%d/" % PORT print "Deno test server http://localhost:%d/" % PORT
return RunningServer(s, start(s)) 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) target_host + extra_path_segment + self.path)
self.end_headers() self.end_headers()
s = get_socket(host_port, RedirectHandler) s = get_socket(host_port, RedirectHandler, False)
if not QUIET: if not QUIET:
print "redirect server http://localhost:%d/ -> http://localhost:%d/" % ( print "redirect server http://localhost:%d/ -> http://localhost:%d/" % (
host_port, target_port) host_port, target_port)
@ -236,6 +287,22 @@ def inf_redirects_server():
return base_redirect_server(INF_REDIRECTS_PORT, INF_REDIRECTS_PORT) 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): def start(s):
thread = Thread(target=s.serve_forever, kwargs={"poll_interval": 0.05}) thread = Thread(target=s.serve_forever, kwargs={"poll_interval": 0.05})
thread.daemon = True thread.daemon = True
@ -246,7 +313,7 @@ def start(s):
@contextmanager @contextmanager
def spawn(): def spawn():
servers = (server(), redirect_server(), another_redirect_server(), 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): while any(not s.thread.is_alive() for s in servers):
sleep(0.01) sleep(0.01)
try: try: