diff options
author | geoFlux <blakejason5@gmail.com> | 2020-02-17 11:59:51 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-17 11:59:51 -0500 |
commit | 2e7d449623b7faee8feb0a2970e0d6bb7c66b409 (patch) | |
tree | 4b3cae1d91709391fcc8facc2d2dca409349f026 /cli/http_util.rs | |
parent | 98e585a284f6b8bb568cec52ca3599612b663ef3 (diff) |
feat: add --cert flag for http client (#3972)
Diffstat (limited to 'cli/http_util.rs')
-rw-r--r-- | cli/http_util.rs | 164 |
1 files changed, 153 insertions, 11 deletions
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); + } } |