use crate::config::{FetchPolicy, NpmConfig};
use std::path::Path;
fn apply_extra_root_certs(
mut builder: reqwest::ClientBuilder,
ca: &[String],
cafile: Option<&Path>,
scope: &str,
) -> reqwest::ClientBuilder {
for pem in ca {
match reqwest::Certificate::from_pem(pem.as_bytes()) {
Ok(cert) => builder = builder.add_root_certificate(cert),
Err(e) => tracing::warn!(
code = aube_codes::warnings::WARN_AUBE_INVALID_CA,
"ignoring invalid {scope} ca: {e}"
),
}
}
if let Some(cafile) = cafile {
match std::fs::read(cafile) {
Ok(bytes) => match reqwest::Certificate::from_pem_bundle(&bytes) {
Ok(certs) => {
for cert in certs {
builder = builder.add_root_certificate(cert);
}
}
Err(e) => tracing::warn!(
code = aube_codes::warnings::WARN_AUBE_INVALID_CAFILE,
"ignoring invalid {scope} cafile {}: {e}",
cafile.display()
),
},
Err(e) => tracing::warn!(
code = aube_codes::warnings::WARN_AUBE_UNREADABLE_CAFILE,
"ignoring unreadable {scope} cafile {}: {e}",
cafile.display()
),
}
}
builder
}
pub(super) fn build_http_client(
config: &NpmConfig,
registry_config: Option<&crate::config::AuthConfig>,
fetch_policy: &FetchPolicy,
) -> reqwest::Client {
build_http_client_inner(config, registry_config, fetch_policy, false)
}
pub(super) fn build_http_tarball_client(
config: &NpmConfig,
registry_config: Option<&crate::config::AuthConfig>,
fetch_policy: &FetchPolicy,
) -> reqwest::Client {
build_http_client_inner(config, registry_config, fetch_policy, true)
}
fn build_http_client_inner(
config: &NpmConfig,
registry_config: Option<&crate::config::AuthConfig>,
fetch_policy: &FetchPolicy,
for_tarball: bool,
) -> reqwest::Client {
let pool_max_idle = config.max_sockets.unwrap_or(64);
static UA: std::sync::OnceLock<String> = std::sync::OnceLock::new();
let user_agent = UA.get_or_init(|| {
format!(
"aube/{} ({} {})",
env!("CARGO_PKG_VERSION"),
std::env::consts::OS,
std::env::consts::ARCH
)
});
let mut builder = aube_util::http::with_webpki_root_fallback(reqwest::Client::builder())
.user_agent(user_agent)
.gzip(true)
.brotli(true)
.zstd(true)
.timeout(std::time::Duration::from_millis(fetch_policy.timeout_ms))
.pool_max_idle_per_host(pool_max_idle)
.pool_idle_timeout(std::time::Duration::from_secs(90))
.tcp_nodelay(true);
if !for_tarball {
builder = builder
.http2_keep_alive_interval(std::time::Duration::from_secs(30))
.http2_keep_alive_timeout(std::time::Duration::from_secs(20))
.http2_keep_alive_while_idle(true)
.http2_adaptive_window(true)
.http2_initial_stream_window_size(Some(16 * 1024 * 1024))
.http2_initial_connection_window_size(Some(16 * 1024 * 1024))
.http2_max_frame_size(Some(16 * 1024 * 1024 - 1));
} else {
builder = builder.http1_only();
}
builder = builder
.tcp_keepalive(std::time::Duration::from_secs(60))
.hickory_dns(true)
.danger_accept_invalid_certs(!config.strict_ssl)
.min_tls_version(reqwest::tls::Version::TLS_1_2)
.redirect(reqwest::redirect::Policy::custom(|attempt| {
if attempt.previous().len() >= 10 {
return attempt.error("too many redirects");
}
if let Some(prev) = attempt.previous().last()
&& prev.scheme() == "https"
&& attempt.url().scheme() != "https"
{
return attempt.stop();
}
attempt.follow()
}))
.no_proxy();
if let Some(ip) = config.local_address {
builder = builder.local_address(Some(ip));
}
let no_proxy = config
.no_proxy
.as_deref()
.and_then(reqwest::NoProxy::from_string);
if let Some(ref url) = config.https_proxy {
match reqwest::Proxy::https(url) {
Ok(mut p) => {
if let Some(ref np) = no_proxy {
p = p.no_proxy(Some(np.clone()));
}
builder = builder.proxy(p);
}
Err(e) => tracing::warn!(
code = aube_codes::warnings::WARN_AUBE_INVALID_HTTPS_PROXY,
"ignoring https-proxy {url:?}: {e}"
),
}
}
if let Some(ref url) = config.http_proxy {
match reqwest::Proxy::http(url) {
Ok(mut p) => {
if let Some(ref np) = no_proxy {
p = p.no_proxy(Some(np.clone()));
}
builder = builder.proxy(p);
}
Err(e) => tracing::warn!(
code = aube_codes::warnings::WARN_AUBE_INVALID_HTTP_PROXY,
"ignoring http-proxy {url:?}: {e}"
),
}
}
builder = apply_extra_root_certs(builder, &config.ca, config.cafile.as_deref(), "top-level");
if let Some(registry_config) = registry_config {
builder = apply_extra_root_certs(
builder,
®istry_config.tls.ca,
registry_config.tls.cafile.as_deref(),
"per-registry",
);
if let (Some(cert), Some(key)) = (®istry_config.tls.cert, ®istry_config.tls.key) {
let mut pem = Vec::with_capacity(cert.len() + key.len() + 1);
pem.extend_from_slice(cert.as_bytes());
if !cert.ends_with('\n') {
pem.push(b'\n');
}
pem.extend_from_slice(key.as_bytes());
match reqwest::Identity::from_pem(&pem) {
Ok(identity) => builder = builder.identity(identity),
Err(e) => tracing::warn!(
code = aube_codes::warnings::WARN_AUBE_INVALID_CLIENT_CERT,
"ignoring invalid per-registry client cert/key: {e}"
),
}
}
}
builder.build().expect("failed to build HTTP client")
}
pub(super) fn force_full_packument() -> bool {
std::env::var("AUBE_INTERNAL_FORCE_FULL_PACKUMENT").as_deref() == Ok("1")
}