diff options
Diffstat (limited to 'vendor/gix/src/repository/config')
-rw-r--r-- | vendor/gix/src/repository/config/mod.rs | 191 | ||||
-rw-r--r-- | vendor/gix/src/repository/config/transport.rs | 425 |
2 files changed, 616 insertions, 0 deletions
diff --git a/vendor/gix/src/repository/config/mod.rs b/vendor/gix/src/repository/config/mod.rs new file mode 100644 index 000000000..92b2618cc --- /dev/null +++ b/vendor/gix/src/repository/config/mod.rs @@ -0,0 +1,191 @@ +use std::collections::BTreeSet; + +use crate::{bstr::ByteSlice, config}; + +/// General Configuration +impl crate::Repository { + /// Return a snapshot of the configuration as seen upon opening the repository. + pub fn config_snapshot(&self) -> config::Snapshot<'_> { + config::Snapshot { repo: self } + } + + /// Return a mutable snapshot of the configuration as seen upon opening the repository, starting a transaction. + /// When the returned instance is dropped, it is applied in full, even if the reason for the drop is an error. + /// + /// Note that changes to the configuration are in-memory only and are observed only the this instance + /// of the [`Repository`][crate::Repository]. + pub fn config_snapshot_mut(&mut self) -> config::SnapshotMut<'_> { + let config = self.config.resolved.as_ref().clone(); + config::SnapshotMut { + repo: Some(self), + config, + } + } + + /// The options used to open the repository. + pub fn open_options(&self) -> &crate::open::Options { + &self.options + } + + /// Obtain options for use when connecting via `ssh`. + #[cfg(feature = "blocking-network-client")] + pub fn ssh_connect_options( + &self, + ) -> Result<gix_protocol::transport::client::ssh::connect::Options, config::ssh_connect_options::Error> { + use crate::config::{ + cache::util::ApplyLeniency, + tree::{gitoxide, Core, Ssh}, + }; + + let config = &self.config.resolved; + let mut trusted = self.filter_config_section(); + let mut fallback_active = false; + let ssh_command = config + .string_filter("core", None, Core::SSH_COMMAND.name, &mut trusted) + .or_else(|| { + fallback_active = true; + config.string_filter( + "gitoxide", + Some("ssh".into()), + gitoxide::Ssh::COMMAND_WITHOUT_SHELL_FALLBACK.name, + &mut trusted, + ) + }) + .map(|cmd| gix_path::from_bstr(cmd).into_owned().into()); + let opts = gix_protocol::transport::client::ssh::connect::Options { + disallow_shell: fallback_active, + command: ssh_command, + kind: config + .string_filter_by_key("ssh.variant", &mut trusted) + .and_then(|variant| Ssh::VARIANT.try_into_variant(variant).transpose()) + .transpose() + .with_leniency(self.options.lenient_config)?, + }; + Ok(opts) + } + + /// The kind of object hash the repository is configured to use. + pub fn object_hash(&self) -> gix_hash::Kind { + self.config.object_hash + } +} + +#[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))] +mod transport; + +mod remote { + use std::{borrow::Cow, collections::BTreeSet}; + + use crate::{bstr::ByteSlice, remote}; + + impl crate::Repository { + /// Returns a sorted list unique of symbolic names of remotes that + /// we deem [trustworthy][crate::open::Options::filter_config_section()]. + // TODO: Use `remote::Name` here + pub fn remote_names(&self) -> BTreeSet<&str> { + self.subsection_names_of("remote") + } + + /// Obtain the branch-independent name for a remote for use in the given `direction`, or `None` if it could not be determined. + /// + /// For _fetching_, use the only configured remote, or default to `origin` if it exists. + /// For _pushing_, use the `remote.pushDefault` trusted configuration key, or fall back to the rules for _fetching_. + /// + /// # Notes + /// + /// It's up to the caller to determine what to do if the current `head` is unborn or detached. + // TODO: use remote::Name here + pub fn remote_default_name(&self, direction: remote::Direction) -> Option<Cow<'_, str>> { + let name = (direction == remote::Direction::Push) + .then(|| { + self.config + .resolved + .string_filter("remote", None, "pushDefault", &mut self.filter_config_section()) + .and_then(|s| match s { + Cow::Borrowed(s) => s.to_str().ok().map(Cow::Borrowed), + Cow::Owned(s) => s.to_str().ok().map(|s| Cow::Owned(s.into())), + }) + }) + .flatten(); + name.or_else(|| { + let names = self.remote_names(); + match names.len() { + 0 => None, + 1 => names.iter().next().copied().map(Cow::Borrowed), + _more_than_one => names.get("origin").copied().map(Cow::Borrowed), + } + }) + } + } +} + +mod branch { + use std::{borrow::Cow, collections::BTreeSet, convert::TryInto}; + + use gix_ref::FullNameRef; + use gix_validate::reference::name::Error as ValidateNameError; + + use crate::bstr::BStr; + + impl crate::Repository { + /// Return a set of unique short branch names for which custom configuration exists in the configuration, + /// if we deem them [trustworthy][crate::open::Options::filter_config_section()]. + pub fn branch_names(&self) -> BTreeSet<&str> { + self.subsection_names_of("branch") + } + + /// Returns the validated reference on the remote associated with the given `short_branch_name`, + /// always `main` instead of `refs/heads/main`. + /// + /// The returned reference is the one we track on the remote side for merging and pushing. + /// Returns `None` if the remote reference was not found. + /// May return an error if the reference is invalid. + pub fn branch_remote_ref<'a>( + &self, + short_branch_name: impl Into<&'a BStr>, + ) -> Option<Result<Cow<'_, FullNameRef>, ValidateNameError>> { + self.config + .resolved + .string("branch", Some(short_branch_name.into()), "merge") + .map(crate::config::tree::branch::Merge::try_into_fullrefname) + } + + /// Returns the unvalidated name of the remote associated with the given `short_branch_name`, + /// typically `main` instead of `refs/heads/main`. + /// In some cases, the returned name will be an URL. + /// Returns `None` if the remote was not found or if the name contained illformed UTF-8. + /// + /// See also [Reference::remote_name()][crate::Reference::remote_name()] for a more typesafe version + /// to be used when a `Reference` is available. + pub fn branch_remote_name<'a>( + &self, + short_branch_name: impl Into<&'a BStr>, + ) -> Option<crate::remote::Name<'_>> { + self.config + .resolved + .string("branch", Some(short_branch_name.into()), "remote") + .and_then(|name| name.try_into().ok()) + } + } +} + +impl crate::Repository { + pub(crate) fn filter_config_section(&self) -> fn(&gix_config::file::Metadata) -> bool { + self.options + .filter_config_section + .unwrap_or(config::section::is_trusted) + } + + fn subsection_names_of<'a>(&'a self, header_name: &'a str) -> BTreeSet<&'a str> { + self.config + .resolved + .sections_by_name(header_name) + .map(|it| { + let filter = self.filter_config_section(); + it.filter(move |s| filter(s.meta())) + .filter_map(|section| section.header().subsection_name().and_then(|b| b.to_str().ok())) + .collect() + }) + .unwrap_or_default() + } +} 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), + } + } +} |