diff options
Diffstat (limited to 'vendor/gix/src/repository/config/transport.rs')
-rw-r--r-- | vendor/gix/src/repository/config/transport.rs | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/vendor/gix/src/repository/config/transport.rs b/vendor/gix/src/repository/config/transport.rs new file mode 100644 index 000000000..dcfbc0bf6 --- /dev/null +++ b/vendor/gix/src/repository/config/transport.rs @@ -0,0 +1,425 @@ +#![allow(clippy::result_large_err)] +use std::any::Any; + +use crate::bstr::BStr; + +impl crate::Repository { + /// Produce configuration suitable for `url`, as differentiated by its protocol/scheme, to be passed to a transport instance via + /// [configure()][gix_transport::client::TransportWithoutIO::configure()] (via `&**config` to pass the contained `Any` and not the `Box`). + /// `None` is returned if there is no known configuration. If `remote_name` is not `None`, the remote's name may contribute to + /// configuration overrides, typically for the HTTP transport. + /// + /// Note that the caller may cast the instance themselves to modify it before passing it on. + /// + /// For transports that support proxy authentication, the + /// [default authentication method](crate::config::Snapshot::credential_helpers()) will be used with the url of the proxy + /// if it contains a user name. + #[cfg_attr( + not(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + )), + allow(unused_variables) + )] + pub fn transport_options<'a>( + &self, + url: impl Into<&'a BStr>, + remote_name: Option<&BStr>, + ) -> Result<Option<Box<dyn Any>>, crate::config::transport::Error> { + let url = gix_url::parse(url.into())?; + use gix_url::Scheme::*; + + match &url.scheme { + Http | Https => { + #[cfg(not(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + )))] + { + Ok(None) + } + #[cfg(any( + feature = "blocking-http-transport-reqwest", + feature = "blocking-http-transport-curl" + ))] + { + use std::{ + borrow::Cow, + sync::{Arc, Mutex}, + }; + + use gix_transport::client::{ + http, + http::options::{ProxyAuthMethod, SslVersion, SslVersionRangeInclusive}, + }; + + use crate::{ + config, + config::{ + cache::util::ApplyLeniency, + tree::{gitoxide, Key, Remote}, + }, + }; + fn try_cow_to_string( + v: Cow<'_, BStr>, + lenient: bool, + key_str: impl Into<Cow<'static, BStr>>, + key: &'static config::tree::keys::String, + ) -> Result<Option<String>, config::transport::Error> { + key.try_into_string(v) + .map_err(|err| config::transport::Error::IllformedUtf8 { + source: err, + key: key_str.into(), + }) + .map(Some) + .with_leniency(lenient) + } + + fn cow_bstr(v: &str) -> Cow<'_, BStr> { + Cow::Borrowed(v.into()) + } + + fn proxy_auth_method( + value_and_key: Option<( + Cow<'_, BStr>, + Cow<'static, BStr>, + &'static config::tree::http::ProxyAuthMethod, + )>, + ) -> Result<ProxyAuthMethod, config::transport::Error> { + let value = value_and_key + .map(|(method, key, key_type)| { + key_type.try_into_proxy_auth_method(method).map_err(|err| { + config::transport::http::Error::InvalidProxyAuthMethod { source: err, key } + }) + }) + .transpose()? + .unwrap_or_default(); + Ok(value) + } + + fn ssl_version( + config: &gix_config::File<'static>, + key_str: &'static str, + key: &'static config::tree::http::SslVersion, + mut filter: fn(&gix_config::file::Metadata) -> bool, + lenient: bool, + ) -> Result<Option<SslVersion>, config::transport::Error> { + debug_assert_eq!( + key_str, + key.logical_name(), + "BUG: hardcoded and generated key names must match" + ); + config + .string_filter_by_key(key_str, &mut filter) + .filter(|v| !v.is_empty()) + .map(|v| { + key.try_into_ssl_version(v) + .map_err(crate::config::transport::http::Error::from) + }) + .transpose() + .with_leniency(lenient) + .map_err(Into::into) + } + + fn proxy( + value: Option<(Cow<'_, BStr>, Cow<'static, BStr>, &'static config::tree::keys::String)>, + lenient: bool, + ) -> Result<Option<String>, config::transport::Error> { + Ok(value + .and_then(|(v, k, key)| try_cow_to_string(v, lenient, k.clone(), key).transpose()) + .transpose()? + .map(|mut proxy| { + if !proxy.trim().is_empty() && !proxy.contains("://") { + proxy.insert_str(0, "http://"); + proxy + } else { + proxy + } + })) + } + + let mut opts = http::Options::default(); + let config = &self.config.resolved; + let mut trusted_only = self.filter_config_section(); + let lenient = self.config.lenient_config; + opts.extra_headers = { + let key = "http.extraHeader"; + debug_assert_eq!(key, &config::tree::Http::EXTRA_HEADER.logical_name()); + config + .strings_filter_by_key(key, &mut trusted_only) + .map(|values| config::tree::Http::EXTRA_HEADER.try_into_extra_header(values)) + .transpose() + .map_err(|err| config::transport::Error::IllformedUtf8 { + source: err, + key: Cow::Borrowed(key.into()), + })? + .unwrap_or_default() + }; + + opts.follow_redirects = { + let key = "http.followRedirects"; + + config::tree::Http::FOLLOW_REDIRECTS + .try_into_follow_redirects( + config.string_filter_by_key(key, &mut trusted_only).unwrap_or_default(), + || { + config + .boolean_filter_by_key(key, &mut trusted_only) + .transpose() + .with_leniency(lenient) + }, + ) + .map_err(config::transport::http::Error::InvalidFollowRedirects)? + }; + + opts.low_speed_time_seconds = config + .integer_filter_by_key("http.lowSpeedTime", &mut trusted_only) + .map(|value| config::tree::Http::LOW_SPEED_TIME.try_into_u64(value)) + .transpose() + .with_leniency(lenient) + .map_err(config::transport::http::Error::from)? + .unwrap_or_default(); + opts.low_speed_limit_bytes_per_second = config + .integer_filter_by_key("http.lowSpeedLimit", &mut trusted_only) + .map(|value| config::tree::Http::LOW_SPEED_LIMIT.try_into_u32(value)) + .transpose() + .with_leniency(lenient) + .map_err(config::transport::http::Error::from)? + .unwrap_or_default(); + opts.proxy = proxy( + remote_name + .and_then(|name| { + config + .string_filter("remote", Some(name), Remote::PROXY.name, &mut trusted_only) + .map(|v| (v, Cow::Owned(format!("remote.{name}.proxy").into()), &Remote::PROXY)) + }) + .or_else(|| { + let key = "http.proxy"; + debug_assert_eq!(key, config::tree::Http::PROXY.logical_name()); + let http_proxy = config + .string_filter_by_key(key, &mut trusted_only) + .map(|v| (v, cow_bstr(key), &config::tree::Http::PROXY)) + .or_else(|| { + let key = "gitoxide.http.proxy"; + debug_assert_eq!(key, gitoxide::Http::PROXY.logical_name()); + config + .string_filter_by_key(key, &mut trusted_only) + .map(|v| (v, cow_bstr(key), &gitoxide::Http::PROXY)) + }); + if url.scheme == Https { + http_proxy.or_else(|| { + let key = "gitoxide.https.proxy"; + debug_assert_eq!(key, gitoxide::Https::PROXY.logical_name()); + config + .string_filter_by_key(key, &mut trusted_only) + .map(|v| (v, cow_bstr(key), &gitoxide::Https::PROXY)) + }) + } else { + http_proxy + } + }) + .or_else(|| { + let key = "gitoxide.http.allProxy"; + debug_assert_eq!(key, gitoxide::Http::ALL_PROXY.logical_name()); + config + .string_filter_by_key(key, &mut trusted_only) + .map(|v| (v, cow_bstr(key), &gitoxide::Http::ALL_PROXY)) + }), + lenient, + )?; + { + let key = "gitoxide.http.noProxy"; + debug_assert_eq!(key, gitoxide::Http::NO_PROXY.logical_name()); + opts.no_proxy = config + .string_filter_by_key(key, &mut trusted_only) + .and_then(|v| { + try_cow_to_string(v, lenient, Cow::Borrowed(key.into()), &gitoxide::Http::NO_PROXY) + .transpose() + }) + .transpose()?; + } + opts.proxy_auth_method = proxy_auth_method({ + let key = "gitoxide.http.proxyAuthMethod"; + debug_assert_eq!(key, gitoxide::Http::PROXY_AUTH_METHOD.logical_name()); + config + .string_filter_by_key(key, &mut trusted_only) + .map(|v| (v, Cow::Borrowed(key.into()), &gitoxide::Http::PROXY_AUTH_METHOD)) + .or_else(|| { + remote_name + .and_then(|name| { + config + .string_filter("remote", Some(name), "proxyAuthMethod", &mut trusted_only) + .map(|v| { + ( + v, + Cow::Owned(format!("remote.{name}.proxyAuthMethod").into()), + &Remote::PROXY_AUTH_METHOD, + ) + }) + }) + .or_else(|| { + let key = "http.proxyAuthMethod"; + debug_assert_eq!(key, config::tree::Http::PROXY_AUTH_METHOD.logical_name()); + config.string_filter_by_key(key, &mut trusted_only).map(|v| { + (v, Cow::Borrowed(key.into()), &config::tree::Http::PROXY_AUTH_METHOD) + }) + }) + }) + })?; + opts.proxy_authenticate = opts + .proxy + .as_deref() + .filter(|url| !url.is_empty()) + .map(|url| gix_url::parse(url.into())) + .transpose()? + .filter(|url| url.user().is_some()) + .map(|url| -> Result<_, config::transport::http::Error> { + let (mut cascade, action_with_normalized_url, prompt_opts) = + self.config_snapshot().credential_helpers(url)?; + Ok(( + action_with_normalized_url, + Arc::new(Mutex::new(move |action| cascade.invoke(action, prompt_opts.clone()))) + as Arc<Mutex<http::options::AuthenticateFn>>, + )) + }) + .transpose()?; + opts.connect_timeout = { + let key = "gitoxide.http.connectTimeout"; + config + .integer_filter_by_key(key, &mut trusted_only) + .map(|v| { + debug_assert_eq!(key, gitoxide::Http::CONNECT_TIMEOUT.logical_name()); + gitoxide::Http::CONNECT_TIMEOUT + .try_into_duration(v) + .map_err(crate::config::transport::http::Error::from) + }) + .transpose() + .with_leniency(lenient)? + }; + { + let key = "http.userAgent"; + opts.user_agent = config + .string_filter_by_key(key, &mut trusted_only) + .and_then(|v| { + try_cow_to_string( + v, + lenient, + Cow::Borrowed(key.into()), + &config::tree::Http::USER_AGENT, + ) + .transpose() + }) + .transpose()? + .or_else(|| Some(crate::env::agent().into())); + } + + { + let key = "http.version"; + opts.http_version = config + .string_filter_by_key(key, &mut trusted_only) + .map(|v| { + config::tree::Http::VERSION + .try_into_http_version(v) + .map_err(config::transport::http::Error::InvalidHttpVersion) + }) + .transpose()?; + } + + { + opts.verbose = config + .boolean_filter( + "gitoxide", + Some("http".into()), + gitoxide::Http::VERBOSE.name, + &mut trusted_only, + ) + .and_then(Result::ok) + .unwrap_or_default(); + } + + let may_use_cainfo = { + let key = "http.schannelUseSSLCAInfo"; + config + .boolean_filter_by_key(key, &mut trusted_only) + .map(|value| config::tree::Http::SCHANNEL_USE_SSL_CA_INFO.enrich_error(value)) + .transpose() + .with_leniency(lenient) + .map_err(config::transport::http::Error::from)? + .unwrap_or(true) + }; + + if may_use_cainfo { + let key = "http.sslCAInfo"; + debug_assert_eq!(key, config::tree::Http::SSL_CA_INFO.logical_name()); + opts.ssl_ca_info = config + .path_filter_by_key(key, &mut trusted_only) + .map(|p| { + use crate::config::cache::interpolate_context; + p.interpolate(interpolate_context( + self.install_dir().ok().as_deref(), + self.config.home_dir().as_deref(), + )) + .map(|cow| cow.into_owned()) + }) + .transpose() + .with_leniency(lenient) + .map_err(|err| config::transport::Error::InterpolatePath { source: err, key })?; + } + + { + opts.ssl_version = ssl_version( + config, + "http.sslVersion", + &config::tree::Http::SSL_VERSION, + trusted_only, + lenient, + )? + .map(|v| SslVersionRangeInclusive { min: v, max: v }); + let min_max = ssl_version( + config, + "gitoxide.http.sslVersionMin", + &gitoxide::Http::SSL_VERSION_MIN, + trusted_only, + lenient, + ) + .and_then(|min| { + ssl_version( + config, + "gitoxide.http.sslVersionMax", + &gitoxide::Http::SSL_VERSION_MAX, + trusted_only, + lenient, + ) + .map(|max| min.and_then(|min| max.map(|max| (min, max)))) + })?; + if let Some((min, max)) = min_max { + let v = opts.ssl_version.get_or_insert(SslVersionRangeInclusive { + min: SslVersion::TlsV1_3, + max: SslVersion::TlsV1_3, + }); + v.min = min; + v.max = max; + } + } + + #[cfg(feature = "blocking-http-transport-curl")] + { + let key = "http.schannelCheckRevoke"; + let schannel_check_revoke = config + .boolean_filter_by_key(key, &mut trusted_only) + .map(|value| config::tree::Http::SCHANNEL_CHECK_REVOKE.enrich_error(value)) + .transpose() + .with_leniency(lenient) + .map_err(config::transport::http::Error::from)?; + let backend = gix_protocol::transport::client::http::curl::Options { schannel_check_revoke }; + opts.backend = + Some(Arc::new(Mutex::new(backend)) as Arc<Mutex<dyn Any + Send + Sync + 'static>>); + } + + Ok(Some(Box::new(opts))) + } + } + File | Git | Ssh | Ext(_) => Ok(None), + } + } +} |