From 7d12dd18992cc58ce9866c2e42d914c8c8cf6d7a Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Thu, 18 Mar 2021 23:54:26 +0100 Subject: [PATCH] fix: fallback to default UA and CA data for Deno.createHttpClient() (#9830) --- cli/tests/unit/fetch_test.ts | 21 ++++++++++++++ op_crates/fetch/lib.rs | 53 ++++++++++++++++++++++++++++-------- runtime/http_util.rs | 42 ---------------------------- runtime/lib.rs | 1 - runtime/ops/fetch.rs | 9 ++++-- 5 files changed, 69 insertions(+), 57 deletions(-) delete mode 100644 runtime/http_util.rs diff --git a/cli/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts index 71307cd69d..fa013f0c5b 100644 --- a/cli/tests/unit/fetch_test.ts +++ b/cli/tests/unit/fetch_test.ts @@ -1063,6 +1063,27 @@ MNf4EgWfK+tZMnuqfpfO9740KzfcVoMNo4QJD4yn5YxroUOO/Azi }, ); +unitTest( + { perms: { net: true } }, + async function fetchCustomClientUserAgent(): Promise< + void + > { + const data = "Hello World"; + const client = Deno.createHttpClient({}); + const response = await fetch("http://localhost:4545/echo_server", { + client, + method: "POST", + body: new TextEncoder().encode(data), + }); + assertEquals( + response.headers.get("user-agent"), + `Deno/${Deno.version.deno}`, + ); + await response.text(); + client.close(); + }, +); + unitTest( { perms: { net: true }, diff --git a/op_crates/fetch/lib.rs b/op_crates/fetch/lib.rs index c376708528..f6f3e9607b 100644 --- a/op_crates/fetch/lib.rs +++ b/op_crates/fetch/lib.rs @@ -23,8 +23,10 @@ use deno_core::RcRef; use deno_core::Resource; use deno_core::ZeroCopyBuf; +use reqwest::header::HeaderMap; use reqwest::header::HeaderName; use reqwest::header::HeaderValue; +use reqwest::header::USER_AGENT; use reqwest::redirect::Policy; use reqwest::Body; use reqwest::Client; @@ -80,6 +82,11 @@ pub fn init(isolate: &mut JsRuntime) { } } +pub struct HttpClientDefaults { + pub user_agent: String, + pub ca_data: Option>, +} + pub trait FetchPermissions { fn check_net_url(&self, _url: &Url) -> Result<(), AnyError>; fn check_read(&self, _p: &Path) -> Result<(), AnyError>; @@ -399,31 +406,53 @@ where permissions.check_read(&PathBuf::from(ca_file))?; } - let client = - create_http_client(args.ca_file.as_deref(), args.ca_data.as_deref()) - .unwrap(); + let defaults = state.borrow::(); + + let cert_data = + get_cert_data(args.ca_file.as_deref(), args.ca_data.as_deref())?; + let client = create_http_client( + defaults.user_agent.clone(), + cert_data.or_else(|| defaults.ca_data.clone()), + ) + .unwrap(); let rid = state.resource_table.add(HttpClientResource::new(client)); Ok(json!(rid)) } -/// Create new instance of async reqwest::Client. This client supports -/// proxies and doesn't follow redirects. -fn create_http_client( +fn get_cert_data( ca_file: Option<&str>, ca_data: Option<&str>, -) -> Result { - let mut builder = Client::builder().redirect(Policy::none()).use_rustls_tls(); +) -> Result>, AnyError> { if let Some(ca_data) = ca_data { - let ca_data_vec = ca_data.as_bytes().to_vec(); - let cert = reqwest::Certificate::from_pem(&ca_data_vec)?; - builder = builder.add_root_certificate(cert); + Ok(Some(ca_data.as_bytes().to_vec())) } else 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)?; + Ok(Some(buf)) + } else { + Ok(None) + } +} + +/// Create new instance of async reqwest::Client. This client supports +/// proxies and doesn't follow redirects. +pub fn create_http_client( + user_agent: String, + ca_data: Option>, +) -> Result { + let mut headers = HeaderMap::new(); + headers.insert(USER_AGENT, user_agent.parse().unwrap()); + let mut builder = Client::builder() + .redirect(Policy::none()) + .default_headers(headers) + .use_rustls_tls(); + + if let Some(ca_data) = ca_data { + let cert = reqwest::Certificate::from_pem(&ca_data)?; builder = builder.add_root_certificate(cert); } + builder .build() .map_err(|e| generic_error(format!("Unable to build http client: {}", e))) diff --git a/runtime/http_util.rs b/runtime/http_util.rs deleted file mode 100644 index 72d41d6e35..0000000000 --- a/runtime/http_util.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. - -use deno_core::error::generic_error; -use deno_core::error::AnyError; -use deno_fetch::reqwest; -use deno_fetch::reqwest::header::HeaderMap; -use deno_fetch::reqwest::header::USER_AGENT; -use deno_fetch::reqwest::redirect::Policy; -use deno_fetch::reqwest::Client; - -/// Create new instance of async reqwest::Client. This client supports -/// proxies and doesn't follow redirects. -pub fn create_http_client( - user_agent: String, - ca_data: Option>, -) -> Result { - let mut headers = HeaderMap::new(); - headers.insert(USER_AGENT, user_agent.parse().unwrap()); - let mut builder = Client::builder() - .redirect(Policy::none()) - .default_headers(headers) - .use_rustls_tls(); - - if let Some(ca_data) = ca_data { - let cert = reqwest::Certificate::from_pem(&ca_data)?; - builder = builder.add_root_certificate(cert); - } - - builder - .build() - .map_err(|e| generic_error(format!("Unable to build http client: {}", e))) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn create_test_client() { - create_http_client("test_client".to_string(), None).unwrap(); - } -} diff --git a/runtime/lib.rs b/runtime/lib.rs index f4cf559d25..b30f10bd17 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -19,7 +19,6 @@ pub use deno_websocket; pub mod colors; pub mod errors; pub mod fs_util; -pub mod http_util; pub mod inspector; pub mod js; pub mod metrics; diff --git a/runtime/ops/fetch.rs b/runtime/ops/fetch.rs index e1b43c9102..9ab86858de 100644 --- a/runtime/ops/fetch.rs +++ b/runtime/ops/fetch.rs @@ -1,7 +1,7 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use crate::http_util; use crate::permissions::Permissions; use deno_fetch::reqwest; +use deno_fetch::HttpClientDefaults; pub fn init( rt: &mut deno_core::JsRuntime, @@ -12,7 +12,12 @@ pub fn init( let op_state = rt.op_state(); let mut state = op_state.borrow_mut(); state.put::({ - http_util::create_http_client(user_agent, ca_data).unwrap() + deno_fetch::create_http_client(user_agent.clone(), ca_data.clone()) + .unwrap() + }); + state.put::(HttpClientDefaults { + ca_data, + user_agent, }); } super::reg_json_sync(rt, "op_fetch", deno_fetch::op_fetch::);