Skip to content

Commit

Permalink
feat: make more HTTP options available
Browse files Browse the repository at this point in the history
- `http.schannelCheckRevoke`
  • Loading branch information
Byron committed Dec 24, 2022
1 parent d59d362 commit 38ae61a
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 30 deletions.
2 changes: 2 additions & 0 deletions git-repository/src/config/cache/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ fn apply_environment_overrides(
("GIT_HTTP_PROXY_AUTHMETHOD", "proxyAuthMethod"),
("all_proxy", "all-proxy-lower"),
("ALL_PROXY", "all-proxy"),
("GIT_SSL_CAINFO", "sslCAInfo"),
("GIT_SSL_VERSION", "sslVersion"),
] {
if let Some(value) = var_as_bstring(var, http_transport) {
section.push_with_comment(
Expand Down
11 changes: 10 additions & 1 deletion git-repository/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ pub mod transport {
source: git_config::value::Error,
key: &'static str,
},
#[error("Could not interpolate path at key {key:?}")]
InterpolatePath {
source: git_config::path::interpolate::Error,
key: &'static str,
},
#[error("Could not decode value at key {key:?} as UTF-8 string")]
IllformedUtf8 {
key: Cow<'static, BStr>,
Expand All @@ -154,7 +159,7 @@ pub mod transport {
pub mod http {
use std::borrow::Cow;

use crate::bstr::BStr;
use crate::bstr::{BStr, BString};

/// The error produced when configuring a HTTP transport.
#[derive(Debug, thiserror::Error)]
Expand All @@ -164,6 +169,10 @@ pub mod transport {
InvalidProxyAuthMethod { value: String, key: Cow<'static, BStr> },
#[error("Could not configure the credential helpers for the authenticated proxy url")]
ConfigureProxyAuthenticate(#[from] crate::config::snapshot::credential_helpers::Error),
#[error("The SSL version at key `{key} named {name:?} is unknown")]
InvalidSslVersion { key: &'static str, name: BString },
#[error("The HTTP version at key `{key} named {name:?} is unknown")]
InvalidHttpVersion { key: &'static str, name: BString },
}
}
}
Expand Down
141 changes: 129 additions & 12 deletions git-repository/src/repository/config/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ impl crate::Repository {
sync::{Arc, Mutex},
};

use git_transport::client::http::options::{HttpVersion, SslVersion, SslVersionRangeInclusive};
use git_transport::client::{http, http::options::ProxyAuthMethod};

use crate::{bstr::ByteVec, config::cache::util::ApplyLeniency};
Expand Down Expand Up @@ -142,6 +143,39 @@ impl crate::Repository {
Ok(value)
}

fn ssl_version(
config: &git_config::File<'static>,
key: &'static str,
mut filter: fn(&git_config::file::Metadata) -> bool,
lenient: bool,
) -> Result<Option<SslVersion>, crate::config::transport::Error> {
config
.string_filter_by_key(key, &mut filter)
.filter(|v| !v.is_empty())
.map(|v| {
use git_protocol::transport::client::http::options::SslVersion::*;
Ok(match v.as_ref().as_ref() {
b"default" => Default,
b"tlsv1" => TlsV1,
b"sslv2" => SslV2,
b"sslv3" => SslV3,
b"tlsv1.0" => TlsV1_0,
b"tlsv1.1" => TlsV1_1,
b"tlsv1.2" => TlsV1_2,
b"tlsv1.3" => TlsV1_3,
_ => {
return Err(crate::config::transport::http::Error::InvalidSslVersion {
key,
name: v.into_owned(),
})
}
})
})
.transpose()
.with_leniency(lenient)
.map_err(Into::into)
}

fn proxy(
value: Option<(Cow<'_, BStr>, Cow<'static, BStr>)>,
lenient: bool,
Expand Down Expand Up @@ -286,18 +320,101 @@ impl crate::Repository {
opts.connect_timeout =
integer_opt(config, lenient, "gitoxide.http.connectTimeout", "u64", trusted_only)?
.map(std::time::Duration::from_millis);
opts.user_agent = config
.string_filter_by_key("http.userAgent", &mut trusted_only)
.and_then(|v| try_cow_to_string(v, lenient, Cow::Borrowed("http.userAgent".into())).transpose())
.transpose()?
.or_else(|| Some(crate::env::agent().into()));
let key = "gitoxide.http.verbose";
opts.verbose = config
.boolean_filter_by_key(key, &mut trusted_only)
.transpose()
.with_leniency(lenient)
.map_err(|err| crate::config::transport::Error::ConfigValue { source: err, key })?
.unwrap_or_default();
{
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())).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| {
Ok(match v.as_ref().as_ref() {
b"HTTP/1.1" => HttpVersion::V1_1,
b"HTTP/2" => HttpVersion::V2,
_ => {
return Err(crate::config::transport::http::Error::InvalidHttpVersion {
name: v.into_owned(),
key,
})
}
})
})
.transpose()?;
}

{
let key = "gitoxide.http.verbose";
opts.verbose = config
.boolean_filter_by_key(key, &mut trusted_only)
.transpose()
.with_leniency(lenient)
.map_err(|err| crate::config::transport::Error::ConfigValue { source: err, key })?
.unwrap_or_default();
}

let may_use_cainfo = {
let key = "http.schannelUseSSLCAInfo";
config
.boolean_filter_by_key(key, &mut trusted_only)
.transpose()
.with_leniency(lenient)
.map_err(|err| crate::config::transport::Error::ConfigValue { source: err, key })?
.unwrap_or(true)
};

if may_use_cainfo {
let key = "http.sslCAInfo";
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| crate::config::transport::Error::InterpolatePath { source: err, key })?;
}

{
opts.ssl_version = ssl_version(config, "http.sslVersion", trusted_only, lenient)?
.map(|v| SslVersionRangeInclusive { min: v, max: v });
let min_max = ssl_version(config, "gitoxide.http.sslVersionMin", trusted_only, lenient)
.and_then(|min| {
ssl_version(config, "gitoxide.http.sslVersionMax", 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_with(|| 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)
.transpose()
.with_leniency(lenient)
.map_err(|err| crate::config::transport::Error::ConfigValue { source: err, key })?;
let backend = git_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)))
}
Expand Down
Git LFS file not shown
21 changes: 21 additions & 0 deletions git-repository/tests/fixtures/make_config_repos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ git init http-config
git config http.proxyAuthMethod basic
git config http.userAgent agentJustForHttp
git config gitoxide.http.connectTimeout 60k
git config http.schannelCheckRevoke true
git config http.sslCAInfo ./CA.pem
git config http.sslVersion sslv2
git config http.version HTTP/1.1
)

git clone --shared http-config http-remote-override
Expand All @@ -33,6 +37,23 @@ git init http-no-proxy
git config gitoxide.http.noProxy "no validation done here"
)

git init http-ssl-version-min-max
(cd http-ssl-version-min-max
git config http.sslVersion sslv3
git config gitoxide.http.sslVersionMin tlsv1.1
git config gitoxide.http.sslVersionMax tlsv1.2
)

git init http-ssl-version-default
(cd http-ssl-version-default
git config http.sslVersion default
)

git init http-disabled-cainfo
(cd http-disabled-cainfo
git config http.sslCAInfo ./CA.pem
git config http.schannelUseSSLCAInfo false
)

git init http-proxy-empty
(cd http-proxy-empty
Expand Down
92 changes: 87 additions & 5 deletions git-repository/tests/repository/config/transport_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
))]
mod http {
use git_repository as git;
use git_transport::client::http::options::{FollowRedirects, ProxyAuthMethod};
use git_transport::client::http::options::{
FollowRedirects, HttpVersion, ProxyAuthMethod, SslVersion, SslVersionRangeInclusive,
};

pub(crate) fn repo(name: &str) -> git::Repository {
repo_opts(name, |opts| opts.strict_config(true))
}

pub(crate) fn repo_opts(
name: &str,
modify: impl FnOnce(git::open::Options) -> git::open::Options,
) -> git::Repository {
let dir = git_testtools::scripted_fixture_read_only("make_config_repos.sh").unwrap();
git::open_opts(dir.join(name), git::open::Options::isolated()).unwrap()
git::open_opts(dir.join(name), modify(git::open::Options::isolated())).unwrap()
}

fn http_options(
Expand Down Expand Up @@ -55,6 +64,9 @@ mod http {
user_agent,
connect_timeout,
verbose,
ssl_ca_info,
ssl_version,
http_version,
backend,
} = http_options(&repo, None, "https://example.com/does/not/matter");
assert_eq!(
Expand All @@ -75,10 +87,80 @@ mod http {
assert_eq!(connect_timeout, Some(std::time::Duration::from_millis(60 * 1024)));
assert_eq!(no_proxy, None);
assert!(!verbose, "verbose is disabled by default");
assert_eq!(ssl_ca_info.as_deref(), Some(std::path::Path::new("./CA.pem")));
#[cfg(feature = "blocking-http-transport-reqwest")]
{
assert!(
backend.is_none(),
"backed is never set as it's backend specific, rather custom options typically"
)
}
#[cfg(feature = "blocking-http-transport-curl")]
{
let backend = backend
.as_ref()
.map(|b| b.lock().expect("not poisoned"))
.expect("backend is set for curl due to specific options");
match backend.downcast_ref::<git_protocol::transport::client::http::curl::Options>() {
Some(opts) => {
assert_eq!(opts.schannel_check_revoke, Some(true));
}
None => panic!("Correct backend option type is used"),
}
}

let version = SslVersion::SslV2;
assert_eq!(
ssl_version,
Some(SslVersionRangeInclusive {
min: version,
max: version
})
);
assert_eq!(http_version, Some(HttpVersion::V1_1));
}

#[test]
fn http_ssl_cainfo_suppressed_by_() {
let repo = repo("http-disabled-cainfo");
let opts = http_options(&repo, None, "https://example.com/does/not/matter");
assert!(
backend.is_none(),
"backed is never set as it's backend specific, rather custom options typically"
)
opts.ssl_ca_info.is_none(),
"http.schannelUseSSLCAInfo is explicitly set and prevents the ssl_ca_info to be set"
);
}

#[test]
fn http_ssl_version_min_max_overrides_ssl_version() {
let repo = repo("http-ssl-version-min-max");
let opts = http_options(&repo, None, "https://example.com/does/not/matter");
assert_eq!(
opts.ssl_version,
Some(SslVersionRangeInclusive {
min: SslVersion::TlsV1_1,
max: SslVersion::TlsV1_2
})
);
}

#[test]
fn http_ssl_version_default() {
let repo = repo("http-ssl-version-default");
let opts = http_options(&repo, None, "https://example.com/does/not/matter");
assert_eq!(
opts.ssl_version,
Some(SslVersionRangeInclusive {
min: SslVersion::Default,
max: SslVersion::Default
})
);
}

#[test]
fn http_ssl_version_empty_resets_prior_values() {
let repo = repo_opts("http-config", |opts| opts.config_overrides(["http.sslVersion="]));
let opts = http_options(&repo, None, "https://example.com/does/not/matter");
assert!(opts.ssl_version.is_none(), "empty strings reset what was there");
}

#[test]
Expand Down
Loading

0 comments on commit 38ae61a

Please sign in to comment.