mirror of
				https://github.com/rust-lang/cargo.git
				synced 2025-11-03 13:12:53 +00:00 
			
		
		
		
	refactor: move http handle constructor to its own module
This commit is contained in:
		
							parent
							
								
									677cd5ece2
								
							
						
					
					
						commit
						b465ed9f4b
					
				@ -2,6 +2,8 @@
 | 
			
		||||
#![allow(clippy::all)]
 | 
			
		||||
#![warn(clippy::disallowed_methods)]
 | 
			
		||||
 | 
			
		||||
use cargo::util::network::http::http_handle;
 | 
			
		||||
use cargo::util::network::http::needs_custom_http_transport;
 | 
			
		||||
use cargo::util::toml::StringOrVec;
 | 
			
		||||
use cargo::util::CliError;
 | 
			
		||||
use cargo::util::{self, closest_msg, command_prelude, CargoResult, CliResult, Config};
 | 
			
		||||
@ -293,12 +295,12 @@ fn init_git(config: &Config) {
 | 
			
		||||
/// configured to use libcurl instead of the built-in networking support so
 | 
			
		||||
/// that those configuration settings can be used.
 | 
			
		||||
fn init_git_transports(config: &Config) {
 | 
			
		||||
    match cargo::ops::needs_custom_http_transport(config) {
 | 
			
		||||
    match needs_custom_http_transport(config) {
 | 
			
		||||
        Ok(true) => {}
 | 
			
		||||
        _ => return,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let handle = match cargo::ops::http_handle(config) {
 | 
			
		||||
    let handle = match http_handle(config) {
 | 
			
		||||
        Ok(handle) => handle,
 | 
			
		||||
        Err(..) => return,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -24,10 +24,11 @@ use crate::core::resolver::{HasDevUnits, Resolve};
 | 
			
		||||
use crate::core::source::MaybePackage;
 | 
			
		||||
use crate::core::{Dependency, Manifest, PackageId, SourceId, Target};
 | 
			
		||||
use crate::core::{SourceMap, Summary, Workspace};
 | 
			
		||||
use crate::ops;
 | 
			
		||||
use crate::util::config::PackageCacheLock;
 | 
			
		||||
use crate::util::errors::{CargoResult, HttpNotSuccessful, DEBUG_HEADERS};
 | 
			
		||||
use crate::util::interning::InternedString;
 | 
			
		||||
use crate::util::network::http::http_handle_and_timeout;
 | 
			
		||||
use crate::util::network::http::HttpTimeout;
 | 
			
		||||
use crate::util::network::retry::{Retry, RetryResult};
 | 
			
		||||
use crate::util::network::sleep::SleepTracker;
 | 
			
		||||
use crate::util::{self, internal, Config, Progress, ProgressStyle};
 | 
			
		||||
@ -348,7 +349,7 @@ pub struct Downloads<'a, 'cfg> {
 | 
			
		||||
    /// Note that timeout management is done manually here instead of in libcurl
 | 
			
		||||
    /// because we want to apply timeouts to an entire batch of operations, not
 | 
			
		||||
    /// any one particular single operation.
 | 
			
		||||
    timeout: ops::HttpTimeout,
 | 
			
		||||
    timeout: HttpTimeout,
 | 
			
		||||
    /// Last time bytes were received.
 | 
			
		||||
    updated_at: Cell<Instant>,
 | 
			
		||||
    /// This is a slow-speed check. It is reset to `now + timeout_duration`
 | 
			
		||||
@ -441,7 +442,7 @@ impl<'cfg> PackageSet<'cfg> {
 | 
			
		||||
 | 
			
		||||
    pub fn enable_download<'a>(&'a self) -> CargoResult<Downloads<'a, 'cfg>> {
 | 
			
		||||
        assert!(!self.downloading.replace(true));
 | 
			
		||||
        let timeout = ops::HttpTimeout::new(self.config)?;
 | 
			
		||||
        let timeout = HttpTimeout::new(self.config)?;
 | 
			
		||||
        Ok(Downloads {
 | 
			
		||||
            start: Instant::now(),
 | 
			
		||||
            set: self,
 | 
			
		||||
@ -713,7 +714,7 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
 | 
			
		||||
        debug!("downloading {} as {}", id, token);
 | 
			
		||||
        assert!(self.pending_ids.insert(id));
 | 
			
		||||
 | 
			
		||||
        let (mut handle, _timeout) = ops::http_handle_and_timeout(self.set.config)?;
 | 
			
		||||
        let (mut handle, _timeout) = http_handle_and_timeout(self.set.config)?;
 | 
			
		||||
        handle.get(true)?;
 | 
			
		||||
        handle.url(&url)?;
 | 
			
		||||
        handle.follow_location(true)?; // follow redirects
 | 
			
		||||
 | 
			
		||||
@ -21,11 +21,15 @@ pub use self::cargo_test::{run_benches, run_tests, TestOptions};
 | 
			
		||||
pub use self::cargo_uninstall::uninstall;
 | 
			
		||||
pub use self::fix::{fix, fix_exec_rustc, fix_get_proxy_lock_addr, FixOptions};
 | 
			
		||||
pub use self::lockfile::{load_pkg_lockfile, resolve_to_string, write_pkg_lockfile};
 | 
			
		||||
pub use self::registry::HttpTimeout;
 | 
			
		||||
pub use self::registry::{configure_http_handle, http_handle, http_handle_and_timeout};
 | 
			
		||||
pub use self::registry::{modify_owners, yank, OwnersOptions, PublishOpts};
 | 
			
		||||
pub use self::registry::{needs_custom_http_transport, registry_login, registry_logout, search};
 | 
			
		||||
pub use self::registry::{publish, RegistryCredentialConfig};
 | 
			
		||||
pub use self::registry::modify_owners;
 | 
			
		||||
pub use self::registry::publish;
 | 
			
		||||
pub use self::registry::registry_login;
 | 
			
		||||
pub use self::registry::registry_logout;
 | 
			
		||||
pub use self::registry::search;
 | 
			
		||||
pub use self::registry::yank;
 | 
			
		||||
pub use self::registry::OwnersOptions;
 | 
			
		||||
pub use self::registry::PublishOpts;
 | 
			
		||||
pub use self::registry::RegistryCredentialConfig;
 | 
			
		||||
pub use self::resolve::{
 | 
			
		||||
    add_overrides, get_resolved_packages, resolve_with_previous, resolve_ws, resolve_ws_with_opts,
 | 
			
		||||
    WorkspaceResolve,
 | 
			
		||||
 | 
			
		||||
@ -13,22 +13,18 @@ use std::collections::HashSet;
 | 
			
		||||
use std::path::PathBuf;
 | 
			
		||||
use std::str;
 | 
			
		||||
use std::task::Poll;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
use anyhow::{bail, format_err, Context as _};
 | 
			
		||||
use crates_io::{self, Registry};
 | 
			
		||||
use curl::easy::{Easy, InfoType, SslOpt, SslVersion};
 | 
			
		||||
use log::{log, Level};
 | 
			
		||||
 | 
			
		||||
use crate::core::source::Source;
 | 
			
		||||
use crate::core::SourceId;
 | 
			
		||||
use crate::sources::{RegistrySource, SourceConfigMap};
 | 
			
		||||
use crate::util::auth::{self, Secret};
 | 
			
		||||
use crate::util::config::{Config, SslVersionConfig, SslVersionConfigRange};
 | 
			
		||||
use crate::util::config::Config;
 | 
			
		||||
use crate::util::errors::CargoResult;
 | 
			
		||||
use crate::util::network;
 | 
			
		||||
use crate::util::network::http::http_handle;
 | 
			
		||||
use crate::util::IntoUrl;
 | 
			
		||||
use crate::version;
 | 
			
		||||
 | 
			
		||||
pub use self::login::registry_login;
 | 
			
		||||
pub use self::logout::registry_logout;
 | 
			
		||||
@ -158,204 +154,6 @@ fn registry(
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Creates a new HTTP handle with appropriate global configuration for cargo.
 | 
			
		||||
pub fn http_handle(config: &Config) -> CargoResult<Easy> {
 | 
			
		||||
    let (mut handle, timeout) = http_handle_and_timeout(config)?;
 | 
			
		||||
    timeout.configure(&mut handle)?;
 | 
			
		||||
    Ok(handle)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn http_handle_and_timeout(config: &Config) -> CargoResult<(Easy, HttpTimeout)> {
 | 
			
		||||
    if config.frozen() {
 | 
			
		||||
        bail!(
 | 
			
		||||
            "attempting to make an HTTP request, but --frozen was \
 | 
			
		||||
             specified"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    if config.offline() {
 | 
			
		||||
        bail!(
 | 
			
		||||
            "attempting to make an HTTP request, but --offline was \
 | 
			
		||||
             specified"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The timeout option for libcurl by default times out the entire transfer,
 | 
			
		||||
    // but we probably don't want this. Instead we only set timeouts for the
 | 
			
		||||
    // connect phase as well as a "low speed" timeout so if we don't receive
 | 
			
		||||
    // many bytes in a large-ish period of time then we time out.
 | 
			
		||||
    let mut handle = Easy::new();
 | 
			
		||||
    let timeout = configure_http_handle(config, &mut handle)?;
 | 
			
		||||
    Ok((handle, timeout))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Only use a custom transport if any HTTP options are specified,
 | 
			
		||||
// such as proxies or custom certificate authorities.
 | 
			
		||||
//
 | 
			
		||||
// The custom transport, however, is not as well battle-tested.
 | 
			
		||||
pub fn needs_custom_http_transport(config: &Config) -> CargoResult<bool> {
 | 
			
		||||
    Ok(
 | 
			
		||||
        network::proxy::http_proxy_exists(config.http_config()?, config)
 | 
			
		||||
            || *config.http_config()? != Default::default()
 | 
			
		||||
            || config.get_env_os("HTTP_TIMEOUT").is_some(),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Configure a libcurl http handle with the defaults options for Cargo
 | 
			
		||||
pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult<HttpTimeout> {
 | 
			
		||||
    let http = config.http_config()?;
 | 
			
		||||
    if let Some(proxy) = network::proxy::http_proxy(http) {
 | 
			
		||||
        handle.proxy(&proxy)?;
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(cainfo) = &http.cainfo {
 | 
			
		||||
        let cainfo = cainfo.resolve_path(config);
 | 
			
		||||
        handle.cainfo(&cainfo)?;
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(check) = http.check_revoke {
 | 
			
		||||
        handle.ssl_options(SslOpt::new().no_revoke(!check))?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(user_agent) = &http.user_agent {
 | 
			
		||||
        handle.useragent(user_agent)?;
 | 
			
		||||
    } else {
 | 
			
		||||
        handle.useragent(&format!("cargo {}", version()))?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn to_ssl_version(s: &str) -> CargoResult<SslVersion> {
 | 
			
		||||
        let version = match s {
 | 
			
		||||
            "default" => SslVersion::Default,
 | 
			
		||||
            "tlsv1" => SslVersion::Tlsv1,
 | 
			
		||||
            "tlsv1.0" => SslVersion::Tlsv10,
 | 
			
		||||
            "tlsv1.1" => SslVersion::Tlsv11,
 | 
			
		||||
            "tlsv1.2" => SslVersion::Tlsv12,
 | 
			
		||||
            "tlsv1.3" => SslVersion::Tlsv13,
 | 
			
		||||
            _ => bail!(
 | 
			
		||||
                "Invalid ssl version `{s}`,\
 | 
			
		||||
                 choose from 'default', 'tlsv1', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'."
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
        Ok(version)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Empty string accept encoding expands to the encodings supported by the current libcurl.
 | 
			
		||||
    handle.accept_encoding("")?;
 | 
			
		||||
    if let Some(ssl_version) = &http.ssl_version {
 | 
			
		||||
        match ssl_version {
 | 
			
		||||
            SslVersionConfig::Single(s) => {
 | 
			
		||||
                let version = to_ssl_version(s.as_str())?;
 | 
			
		||||
                handle.ssl_version(version)?;
 | 
			
		||||
            }
 | 
			
		||||
            SslVersionConfig::Range(SslVersionConfigRange { min, max }) => {
 | 
			
		||||
                let min_version = min
 | 
			
		||||
                    .as_ref()
 | 
			
		||||
                    .map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
 | 
			
		||||
                let max_version = max
 | 
			
		||||
                    .as_ref()
 | 
			
		||||
                    .map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
 | 
			
		||||
                handle.ssl_min_max_version(min_version, max_version)?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else if cfg!(windows) {
 | 
			
		||||
        // This is a temporary workaround for some bugs with libcurl and
 | 
			
		||||
        // schannel and TLS 1.3.
 | 
			
		||||
        //
 | 
			
		||||
        // Our libcurl on Windows is usually built with schannel.
 | 
			
		||||
        // On Windows 11 (or Windows Server 2022), libcurl recently (late
 | 
			
		||||
        // 2022) gained support for TLS 1.3 with schannel, and it now defaults
 | 
			
		||||
        // to 1.3. Unfortunately there have been some bugs with this.
 | 
			
		||||
        // https://github.com/curl/curl/issues/9431 is the most recent. Once
 | 
			
		||||
        // that has been fixed, and some time has passed where we can be more
 | 
			
		||||
        // confident that the 1.3 support won't cause issues, this can be
 | 
			
		||||
        // removed.
 | 
			
		||||
        //
 | 
			
		||||
        // Windows 10 is unaffected. libcurl does not support TLS 1.3 on
 | 
			
		||||
        // Windows 10. (Windows 10 sorta had support, but it required enabling
 | 
			
		||||
        // an advanced option in the registry which was buggy, and libcurl
 | 
			
		||||
        // does runtime checks to prevent it.)
 | 
			
		||||
        handle.ssl_min_max_version(SslVersion::Default, SslVersion::Tlsv12)?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(true) = http.debug {
 | 
			
		||||
        handle.verbose(true)?;
 | 
			
		||||
        log::debug!("{:#?}", curl::Version::get());
 | 
			
		||||
        handle.debug_function(|kind, data| {
 | 
			
		||||
            let (prefix, level) = match kind {
 | 
			
		||||
                InfoType::Text => ("*", Level::Debug),
 | 
			
		||||
                InfoType::HeaderIn => ("<", Level::Debug),
 | 
			
		||||
                InfoType::HeaderOut => (">", Level::Debug),
 | 
			
		||||
                InfoType::DataIn => ("{", Level::Trace),
 | 
			
		||||
                InfoType::DataOut => ("}", Level::Trace),
 | 
			
		||||
                InfoType::SslDataIn | InfoType::SslDataOut => return,
 | 
			
		||||
                _ => return,
 | 
			
		||||
            };
 | 
			
		||||
            let starts_with_ignore_case = |line: &str, text: &str| -> bool {
 | 
			
		||||
                line[..line.len().min(text.len())].eq_ignore_ascii_case(text)
 | 
			
		||||
            };
 | 
			
		||||
            match str::from_utf8(data) {
 | 
			
		||||
                Ok(s) => {
 | 
			
		||||
                    for mut line in s.lines() {
 | 
			
		||||
                        if starts_with_ignore_case(line, "authorization:") {
 | 
			
		||||
                            line = "Authorization: [REDACTED]";
 | 
			
		||||
                        } else if starts_with_ignore_case(line, "h2h3 [authorization:") {
 | 
			
		||||
                            line = "h2h3 [Authorization: [REDACTED]]";
 | 
			
		||||
                        } else if starts_with_ignore_case(line, "set-cookie") {
 | 
			
		||||
                            line = "set-cookie: [REDACTED]";
 | 
			
		||||
                        }
 | 
			
		||||
                        log!(level, "http-debug: {} {}", prefix, line);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Err(_) => {
 | 
			
		||||
                    log!(
 | 
			
		||||
                        level,
 | 
			
		||||
                        "http-debug: {} ({} bytes of data)",
 | 
			
		||||
                        prefix,
 | 
			
		||||
                        data.len()
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HttpTimeout::new(config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub struct HttpTimeout {
 | 
			
		||||
    pub dur: Duration,
 | 
			
		||||
    pub low_speed_limit: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HttpTimeout {
 | 
			
		||||
    pub fn new(config: &Config) -> CargoResult<HttpTimeout> {
 | 
			
		||||
        let http_config = config.http_config()?;
 | 
			
		||||
        let low_speed_limit = http_config.low_speed_limit.unwrap_or(10);
 | 
			
		||||
        let seconds = http_config
 | 
			
		||||
            .timeout
 | 
			
		||||
            .or_else(|| {
 | 
			
		||||
                config
 | 
			
		||||
                    .get_env("HTTP_TIMEOUT")
 | 
			
		||||
                    .ok()
 | 
			
		||||
                    .and_then(|s| s.parse().ok())
 | 
			
		||||
            })
 | 
			
		||||
            .unwrap_or(30);
 | 
			
		||||
        Ok(HttpTimeout {
 | 
			
		||||
            dur: Duration::new(seconds, 0),
 | 
			
		||||
            low_speed_limit,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn configure(&self, handle: &mut Easy) -> CargoResult<()> {
 | 
			
		||||
        // The timeout option for libcurl by default times out the entire
 | 
			
		||||
        // transfer, but we probably don't want this. Instead we only set
 | 
			
		||||
        // timeouts for the connect phase as well as a "low speed" timeout so
 | 
			
		||||
        // if we don't receive many bytes in a large-ish period of time then we
 | 
			
		||||
        // time out.
 | 
			
		||||
        handle.connect_timeout(self.dur)?;
 | 
			
		||||
        handle.low_speed_time(self.dur)?;
 | 
			
		||||
        handle.low_speed_limit(self.low_speed_limit)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Gets the SourceId for an index or registry setting.
 | 
			
		||||
///
 | 
			
		||||
/// The `index` and `reg` values are from the command-line or config settings.
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
//! This module contains all code sporting `gitoxide` for operations on `git` repositories and it mirrors
 | 
			
		||||
//! `utils` closely for now. One day it can be renamed into `utils` once `git2` isn't required anymore.
 | 
			
		||||
 | 
			
		||||
use crate::ops::HttpTimeout;
 | 
			
		||||
use crate::util::network::http::HttpTimeout;
 | 
			
		||||
use crate::util::{human_readable_bytes, network, MetricsCounter, Progress};
 | 
			
		||||
use crate::{CargoResult, Config};
 | 
			
		||||
use cargo_util::paths;
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,11 @@
 | 
			
		||||
//! Access to a HTTP-based crate registry. See [`HttpRegistry`] for details.
 | 
			
		||||
 | 
			
		||||
use crate::core::{PackageId, SourceId};
 | 
			
		||||
use crate::ops;
 | 
			
		||||
use crate::sources::registry::download;
 | 
			
		||||
use crate::sources::registry::MaybeLock;
 | 
			
		||||
use crate::sources::registry::{LoadResponse, RegistryConfig, RegistryData};
 | 
			
		||||
use crate::util::errors::{CargoResult, HttpNotSuccessful, DEBUG_HEADERS};
 | 
			
		||||
use crate::util::network::http::http_handle;
 | 
			
		||||
use crate::util::network::retry::{Retry, RetryResult};
 | 
			
		||||
use crate::util::network::sleep::SleepTracker;
 | 
			
		||||
use crate::util::{auth, Config, Filesystem, IntoUrl, Progress, ProgressStyle};
 | 
			
		||||
@ -610,7 +610,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
 | 
			
		||||
        // Looks like we're going to have to do a network request.
 | 
			
		||||
        self.start_fetch()?;
 | 
			
		||||
 | 
			
		||||
        let mut handle = ops::http_handle(self.config)?;
 | 
			
		||||
        let mut handle = http_handle(self.config)?;
 | 
			
		||||
        let full_url = self.full_url(path);
 | 
			
		||||
        debug!("fetch {}", full_url);
 | 
			
		||||
        handle.get(true)?;
 | 
			
		||||
 | 
			
		||||
@ -69,9 +69,11 @@ use self::ConfigValue as CV;
 | 
			
		||||
use crate::core::compiler::rustdoc::RustdocExternMap;
 | 
			
		||||
use crate::core::shell::Verbosity;
 | 
			
		||||
use crate::core::{features, CliUnstable, Shell, SourceId, Workspace, WorkspaceRootConfig};
 | 
			
		||||
use crate::ops::{self, RegistryCredentialConfig};
 | 
			
		||||
use crate::ops::RegistryCredentialConfig;
 | 
			
		||||
use crate::util::auth::Secret;
 | 
			
		||||
use crate::util::errors::CargoResult;
 | 
			
		||||
use crate::util::network::http::configure_http_handle;
 | 
			
		||||
use crate::util::network::http::http_handle;
 | 
			
		||||
use crate::util::CanonicalUrl;
 | 
			
		||||
use crate::util::{internal, toml as cargo_toml};
 | 
			
		||||
use crate::util::{try_canonicalize, validate_package_name};
 | 
			
		||||
@ -1706,11 +1708,11 @@ impl Config {
 | 
			
		||||
    pub fn http(&self) -> CargoResult<&RefCell<Easy>> {
 | 
			
		||||
        let http = self
 | 
			
		||||
            .easy
 | 
			
		||||
            .try_borrow_with(|| ops::http_handle(self).map(RefCell::new))?;
 | 
			
		||||
            .try_borrow_with(|| http_handle(self).map(RefCell::new))?;
 | 
			
		||||
        {
 | 
			
		||||
            let mut http = http.borrow_mut();
 | 
			
		||||
            http.reset();
 | 
			
		||||
            let timeout = ops::configure_http_handle(self, &mut http)?;
 | 
			
		||||
            let timeout = configure_http_handle(self, &mut http)?;
 | 
			
		||||
            timeout.configure(&mut http)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(http)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										216
									
								
								src/cargo/util/network/http.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/cargo/util/network/http.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,216 @@
 | 
			
		||||
//! Configures libcurl's http handles.
 | 
			
		||||
 | 
			
		||||
use std::str;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
use anyhow::bail;
 | 
			
		||||
use curl::easy::Easy;
 | 
			
		||||
use curl::easy::InfoType;
 | 
			
		||||
use curl::easy::SslOpt;
 | 
			
		||||
use curl::easy::SslVersion;
 | 
			
		||||
use log::log;
 | 
			
		||||
use log::Level;
 | 
			
		||||
 | 
			
		||||
use crate::util::config::SslVersionConfig;
 | 
			
		||||
use crate::util::config::SslVersionConfigRange;
 | 
			
		||||
use crate::version;
 | 
			
		||||
use crate::CargoResult;
 | 
			
		||||
use crate::Config;
 | 
			
		||||
 | 
			
		||||
/// Creates a new HTTP handle with appropriate global configuration for cargo.
 | 
			
		||||
pub fn http_handle(config: &Config) -> CargoResult<Easy> {
 | 
			
		||||
    let (mut handle, timeout) = http_handle_and_timeout(config)?;
 | 
			
		||||
    timeout.configure(&mut handle)?;
 | 
			
		||||
    Ok(handle)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn http_handle_and_timeout(config: &Config) -> CargoResult<(Easy, HttpTimeout)> {
 | 
			
		||||
    if config.frozen() {
 | 
			
		||||
        bail!(
 | 
			
		||||
            "attempting to make an HTTP request, but --frozen was \
 | 
			
		||||
             specified"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    if config.offline() {
 | 
			
		||||
        bail!(
 | 
			
		||||
            "attempting to make an HTTP request, but --offline was \
 | 
			
		||||
             specified"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // The timeout option for libcurl by default times out the entire transfer,
 | 
			
		||||
    // but we probably don't want this. Instead we only set timeouts for the
 | 
			
		||||
    // connect phase as well as a "low speed" timeout so if we don't receive
 | 
			
		||||
    // many bytes in a large-ish period of time then we time out.
 | 
			
		||||
    let mut handle = Easy::new();
 | 
			
		||||
    let timeout = configure_http_handle(config, &mut handle)?;
 | 
			
		||||
    Ok((handle, timeout))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Only use a custom transport if any HTTP options are specified,
 | 
			
		||||
// such as proxies or custom certificate authorities.
 | 
			
		||||
//
 | 
			
		||||
// The custom transport, however, is not as well battle-tested.
 | 
			
		||||
pub fn needs_custom_http_transport(config: &Config) -> CargoResult<bool> {
 | 
			
		||||
    Ok(
 | 
			
		||||
        super::proxy::http_proxy_exists(config.http_config()?, config)
 | 
			
		||||
            || *config.http_config()? != Default::default()
 | 
			
		||||
            || config.get_env_os("HTTP_TIMEOUT").is_some(),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Configure a libcurl http handle with the defaults options for Cargo
 | 
			
		||||
pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult<HttpTimeout> {
 | 
			
		||||
    let http = config.http_config()?;
 | 
			
		||||
    if let Some(proxy) = super::proxy::http_proxy(http) {
 | 
			
		||||
        handle.proxy(&proxy)?;
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(cainfo) = &http.cainfo {
 | 
			
		||||
        let cainfo = cainfo.resolve_path(config);
 | 
			
		||||
        handle.cainfo(&cainfo)?;
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(check) = http.check_revoke {
 | 
			
		||||
        handle.ssl_options(SslOpt::new().no_revoke(!check))?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(user_agent) = &http.user_agent {
 | 
			
		||||
        handle.useragent(user_agent)?;
 | 
			
		||||
    } else {
 | 
			
		||||
        handle.useragent(&format!("cargo {}", version()))?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn to_ssl_version(s: &str) -> CargoResult<SslVersion> {
 | 
			
		||||
        let version = match s {
 | 
			
		||||
            "default" => SslVersion::Default,
 | 
			
		||||
            "tlsv1" => SslVersion::Tlsv1,
 | 
			
		||||
            "tlsv1.0" => SslVersion::Tlsv10,
 | 
			
		||||
            "tlsv1.1" => SslVersion::Tlsv11,
 | 
			
		||||
            "tlsv1.2" => SslVersion::Tlsv12,
 | 
			
		||||
            "tlsv1.3" => SslVersion::Tlsv13,
 | 
			
		||||
            _ => bail!(
 | 
			
		||||
                "Invalid ssl version `{s}`,\
 | 
			
		||||
                 choose from 'default', 'tlsv1', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'."
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
        Ok(version)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Empty string accept encoding expands to the encodings supported by the current libcurl.
 | 
			
		||||
    handle.accept_encoding("")?;
 | 
			
		||||
    if let Some(ssl_version) = &http.ssl_version {
 | 
			
		||||
        match ssl_version {
 | 
			
		||||
            SslVersionConfig::Single(s) => {
 | 
			
		||||
                let version = to_ssl_version(s.as_str())?;
 | 
			
		||||
                handle.ssl_version(version)?;
 | 
			
		||||
            }
 | 
			
		||||
            SslVersionConfig::Range(SslVersionConfigRange { min, max }) => {
 | 
			
		||||
                let min_version = min
 | 
			
		||||
                    .as_ref()
 | 
			
		||||
                    .map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
 | 
			
		||||
                let max_version = max
 | 
			
		||||
                    .as_ref()
 | 
			
		||||
                    .map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
 | 
			
		||||
                handle.ssl_min_max_version(min_version, max_version)?;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else if cfg!(windows) {
 | 
			
		||||
        // This is a temporary workaround for some bugs with libcurl and
 | 
			
		||||
        // schannel and TLS 1.3.
 | 
			
		||||
        //
 | 
			
		||||
        // Our libcurl on Windows is usually built with schannel.
 | 
			
		||||
        // On Windows 11 (or Windows Server 2022), libcurl recently (late
 | 
			
		||||
        // 2022) gained support for TLS 1.3 with schannel, and it now defaults
 | 
			
		||||
        // to 1.3. Unfortunately there have been some bugs with this.
 | 
			
		||||
        // https://github.com/curl/curl/issues/9431 is the most recent. Once
 | 
			
		||||
        // that has been fixed, and some time has passed where we can be more
 | 
			
		||||
        // confident that the 1.3 support won't cause issues, this can be
 | 
			
		||||
        // removed.
 | 
			
		||||
        //
 | 
			
		||||
        // Windows 10 is unaffected. libcurl does not support TLS 1.3 on
 | 
			
		||||
        // Windows 10. (Windows 10 sorta had support, but it required enabling
 | 
			
		||||
        // an advanced option in the registry which was buggy, and libcurl
 | 
			
		||||
        // does runtime checks to prevent it.)
 | 
			
		||||
        handle.ssl_min_max_version(SslVersion::Default, SslVersion::Tlsv12)?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(true) = http.debug {
 | 
			
		||||
        handle.verbose(true)?;
 | 
			
		||||
        log::debug!("{:#?}", curl::Version::get());
 | 
			
		||||
        handle.debug_function(|kind, data| {
 | 
			
		||||
            let (prefix, level) = match kind {
 | 
			
		||||
                InfoType::Text => ("*", Level::Debug),
 | 
			
		||||
                InfoType::HeaderIn => ("<", Level::Debug),
 | 
			
		||||
                InfoType::HeaderOut => (">", Level::Debug),
 | 
			
		||||
                InfoType::DataIn => ("{", Level::Trace),
 | 
			
		||||
                InfoType::DataOut => ("}", Level::Trace),
 | 
			
		||||
                InfoType::SslDataIn | InfoType::SslDataOut => return,
 | 
			
		||||
                _ => return,
 | 
			
		||||
            };
 | 
			
		||||
            let starts_with_ignore_case = |line: &str, text: &str| -> bool {
 | 
			
		||||
                line[..line.len().min(text.len())].eq_ignore_ascii_case(text)
 | 
			
		||||
            };
 | 
			
		||||
            match str::from_utf8(data) {
 | 
			
		||||
                Ok(s) => {
 | 
			
		||||
                    for mut line in s.lines() {
 | 
			
		||||
                        if starts_with_ignore_case(line, "authorization:") {
 | 
			
		||||
                            line = "Authorization: [REDACTED]";
 | 
			
		||||
                        } else if starts_with_ignore_case(line, "h2h3 [authorization:") {
 | 
			
		||||
                            line = "h2h3 [Authorization: [REDACTED]]";
 | 
			
		||||
                        } else if starts_with_ignore_case(line, "set-cookie") {
 | 
			
		||||
                            line = "set-cookie: [REDACTED]";
 | 
			
		||||
                        }
 | 
			
		||||
                        log!(level, "http-debug: {} {}", prefix, line);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                Err(_) => {
 | 
			
		||||
                    log!(
 | 
			
		||||
                        level,
 | 
			
		||||
                        "http-debug: {} ({} bytes of data)",
 | 
			
		||||
                        prefix,
 | 
			
		||||
                        data.len()
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HttpTimeout::new(config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub struct HttpTimeout {
 | 
			
		||||
    pub dur: Duration,
 | 
			
		||||
    pub low_speed_limit: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HttpTimeout {
 | 
			
		||||
    pub fn new(config: &Config) -> CargoResult<HttpTimeout> {
 | 
			
		||||
        let http_config = config.http_config()?;
 | 
			
		||||
        let low_speed_limit = http_config.low_speed_limit.unwrap_or(10);
 | 
			
		||||
        let seconds = http_config
 | 
			
		||||
            .timeout
 | 
			
		||||
            .or_else(|| {
 | 
			
		||||
                config
 | 
			
		||||
                    .get_env("HTTP_TIMEOUT")
 | 
			
		||||
                    .ok()
 | 
			
		||||
                    .and_then(|s| s.parse().ok())
 | 
			
		||||
            })
 | 
			
		||||
            .unwrap_or(30);
 | 
			
		||||
        Ok(HttpTimeout {
 | 
			
		||||
            dur: Duration::new(seconds, 0),
 | 
			
		||||
            low_speed_limit,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn configure(&self, handle: &mut Easy) -> CargoResult<()> {
 | 
			
		||||
        // The timeout option for libcurl by default times out the entire
 | 
			
		||||
        // transfer, but we probably don't want this. Instead we only set
 | 
			
		||||
        // timeouts for the connect phase as well as a "low speed" timeout so
 | 
			
		||||
        // if we don't receive many bytes in a large-ish period of time then we
 | 
			
		||||
        // time out.
 | 
			
		||||
        handle.connect_timeout(self.dur)?;
 | 
			
		||||
        handle.low_speed_time(self.dur)?;
 | 
			
		||||
        handle.low_speed_limit(self.low_speed_limit)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
use std::task::Poll;
 | 
			
		||||
 | 
			
		||||
pub mod http;
 | 
			
		||||
pub mod proxy;
 | 
			
		||||
pub mod retry;
 | 
			
		||||
pub mod sleep;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user