mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 16:19:12 -05:00
feat: add --cert flag for http client (#3972)
This commit is contained in:
parent
98e585a284
commit
2e7d449623
16 changed files with 654 additions and 21 deletions
|
@ -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")
|
||||
}
|
||||
|
|
188
cli/flags.rs
188
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()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
164
cli/http_util.rs
164
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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())?,
|
||||
|
|
|
@ -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");
|
||||
|
|
24
cli/tests/cafile_info.ts
Normal file
24
cli/tests/cafile_info.ts
Normal 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
|
||||
);
|
14
cli/tests/cafile_info.ts.out
Normal file
14
cli/tests/cafile_info.ts.out
Normal 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
|
3
cli/tests/cafile_ts_fetch.ts
Normal file
3
cli/tests/cafile_ts_fetch.ts
Normal 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()));
|
1
cli/tests/cafile_ts_fetch.ts.out
Normal file
1
cli/tests/cafile_ts_fetch.ts.out
Normal file
|
@ -0,0 +1 @@
|
|||
Hello
|
3
cli/tests/cafile_url_imports.ts
Normal file
3
cli/tests/cafile_url_imports.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { printHello } from "https://localhost:5545/cli/tests/subdir/mod2.ts";
|
||||
printHello();
|
||||
console.log("success");
|
2
cli/tests/cafile_url_imports.ts.out
Normal file
2
cli/tests/cafile_url_imports.ts.out
Normal file
|
@ -0,0 +1,2 @@
|
|||
Hello
|
||||
success
|
|
@ -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::*;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue