use self::{proxy::Proxy, request::SetOpt};
use crate::{
auth::{Authentication, Credentials},
is_http_version_supported,
};
use curl::easy::Easy2;
use std::{net::IpAddr, time::Duration};
pub(crate) mod client;
pub(crate) mod dial;
pub(crate) mod dns;
pub(crate) mod proxy;
pub(crate) mod redirect;
pub(crate) mod request;
pub(crate) mod ssl;
pub use dial::{Dialer, DialerParseError};
pub use dns::{DnsCache, ResolveMap};
pub use redirect::RedirectPolicy;
pub use ssl::{CaCertificate, ClientCertificate, PrivateKey, SslOption};
pub trait Configurable: request::WithRequestConfig {
#[must_use = "builders have no effect if unused"]
fn timeout(self, timeout: Duration) -> Self {
self.with_config(move |config| {
config.timeout = Some(timeout);
})
}
#[must_use = "builders have no effect if unused"]
fn connect_timeout(self, timeout: Duration) -> Self {
self.with_config(move |config| {
config.connect_timeout = Some(timeout);
})
}
#[must_use = "builders have no effect if unused"]
fn low_speed_timeout(self, low_speed: u32, timeout: Duration) -> Self {
self.with_config(move |config| {
config.low_speed_timeout = Some((low_speed, timeout));
})
}
#[must_use = "builders have no effect if unused"]
fn version_negotiation(self, negotiation: VersionNegotiation) -> Self {
self.with_config(move |config| {
config.version_negotiation = Some(negotiation);
})
}
#[must_use = "builders have no effect if unused"]
fn redirect_policy(self, policy: RedirectPolicy) -> Self {
self.with_config(move |config| {
config.redirect_policy = Some(policy);
})
}
#[must_use = "builders have no effect if unused"]
fn auto_referer(self) -> Self {
self.with_config(move |config| {
config.auto_referer = Some(true);
})
}
#[cfg(feature = "cookies")]
#[must_use = "builders have no effect if unused"]
fn cookie_jar(self, cookie_jar: crate::cookies::CookieJar) -> Self;
#[must_use = "builders have no effect if unused"]
fn automatic_decompression(self, decompress: bool) -> Self {
self.with_config(move |config| {
config.automatic_decompression = Some(decompress);
})
}
#[must_use = "builders have no effect if unused"]
fn expect_continue<T>(self, expect: T) -> Self
where
T: Into<ExpectContinue>,
{
self.with_config(move |config| {
config.expect_continue = Some(expect.into());
})
}
#[must_use = "builders have no effect if unused"]
fn authentication(self, authentication: Authentication) -> Self {
self.with_config(move |config| {
config.authentication = Some(authentication);
})
}
#[must_use = "builders have no effect if unused"]
fn credentials(self, credentials: Credentials) -> Self {
self.with_config(move |config| {
config.credentials = Some(credentials);
})
}
#[must_use = "builders have no effect if unused"]
fn tcp_keepalive(self, interval: Duration) -> Self {
self.with_config(move |config| {
config.tcp_keepalive = Some(interval);
})
}
#[must_use = "builders have no effect if unused"]
fn tcp_nodelay(self) -> Self {
self.with_config(move |config| {
config.tcp_nodelay = Some(true);
})
}
#[must_use = "builders have no effect if unused"]
fn interface<I>(self, interface: I) -> Self
where
I: Into<NetworkInterface>,
{
self.with_config(move |config| {
config.interface = Some(interface.into());
})
}
#[must_use = "builders have no effect if unused"]
fn ip_version(self, version: IpVersion) -> Self {
self.with_config(move |config| {
config.ip_version = Some(version);
})
}
#[must_use = "builders have no effect if unused"]
fn dial<D>(self, dialer: D) -> Self
where
D: Into<Dialer>,
{
self.with_config(move |config| {
config.dial = Some(dialer.into());
})
}
#[must_use = "builders have no effect if unused"]
fn proxy(self, proxy: impl Into<Option<http::Uri>>) -> Self {
self.with_config(move |config| {
config.proxy = Some(proxy.into());
})
}
#[must_use = "builders have no effect if unused"]
fn proxy_blacklist<I, T>(self, hosts: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<String>,
{
self.with_config(move |config| {
config.proxy_blacklist = Some(hosts.into_iter().map(T::into).collect());
})
}
#[must_use = "builders have no effect if unused"]
fn proxy_authentication(self, authentication: Authentication) -> Self {
self.with_config(move |config| {
config.proxy_authentication = Some(Proxy(authentication));
})
}
#[must_use = "builders have no effect if unused"]
fn proxy_credentials(self, credentials: Credentials) -> Self {
self.with_config(move |config| {
config.proxy_credentials = Some(Proxy(credentials));
})
}
#[must_use = "builders have no effect if unused"]
fn max_upload_speed(self, max: u64) -> Self {
self.with_config(move |config| {
config.max_upload_speed = Some(max);
})
}
#[must_use = "builders have no effect if unused"]
fn max_download_speed(self, max: u64) -> Self {
self.with_config(move |config| {
config.max_download_speed = Some(max);
})
}
#[must_use = "builders have no effect if unused"]
fn ssl_client_certificate(self, certificate: ClientCertificate) -> Self {
self.with_config(move |config| {
config.ssl_client_certificate = Some(certificate);
})
}
#[must_use = "builders have no effect if unused"]
fn ssl_ca_certificate(self, certificate: CaCertificate) -> Self {
self.with_config(move |config| {
config.ssl_ca_certificate = Some(certificate);
})
}
#[must_use = "builders have no effect if unused"]
fn ssl_ciphers<I, T>(self, ciphers: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<String>,
{
self.with_config(move |config| {
config.ssl_ciphers = Some(ciphers.into_iter().map(T::into).collect());
})
}
#[must_use = "builders have no effect if unused"]
fn ssl_options(self, options: SslOption) -> Self {
self.with_config(move |config| {
config.ssl_options = Some(options);
})
}
#[must_use = "builders have no effect if unused"]
fn title_case_headers(self, enable: bool) -> Self {
self.with_config(move |config| {
config.title_case_headers = Some(enable);
})
}
#[must_use = "builders have no effect if unused"]
fn metrics(self, enable: bool) -> Self {
self.with_config(move |config| {
config.enable_metrics = Some(enable);
})
}
}
#[derive(Clone, Debug)]
pub struct VersionNegotiation(VersionNegotiationInner);
#[derive(Clone, Copy, Debug)]
enum VersionNegotiationInner {
LatestCompatible,
Strict(curl::easy::HttpVersion),
}
impl Default for VersionNegotiation {
fn default() -> Self {
Self::latest_compatible()
}
}
impl VersionNegotiation {
pub const fn latest_compatible() -> Self {
Self(VersionNegotiationInner::LatestCompatible)
}
pub const fn http10() -> Self {
Self(VersionNegotiationInner::Strict(
curl::easy::HttpVersion::V10,
))
}
pub const fn http11() -> Self {
Self(VersionNegotiationInner::Strict(
curl::easy::HttpVersion::V11,
))
}
pub const fn http2() -> Self {
Self(VersionNegotiationInner::Strict(
curl::easy::HttpVersion::V2PriorKnowledge,
))
}
pub const fn http3() -> Self {
Self(VersionNegotiationInner::Strict(curl::easy::HttpVersion::V3))
}
}
impl SetOpt for VersionNegotiation {
fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
match self.0 {
VersionNegotiationInner::LatestCompatible => {
if is_http_version_supported(http::Version::HTTP_2) {
easy.http_version(curl::easy::HttpVersion::V2TLS)
} else {
Ok(())
}
}
VersionNegotiationInner::Strict(version) => easy.http_version(version),
}
}
}
#[derive(Clone, Debug)]
pub struct NetworkInterface {
interface: Option<String>,
}
impl NetworkInterface {
pub fn any() -> Self {
Self {
interface: None,
}
}
#[cfg(unix)]
pub fn name(name: impl AsRef<str>) -> Self {
Self {
interface: Some(format!("if!{}", name.as_ref())),
}
}
pub fn host(host: impl AsRef<str>) -> Self {
Self {
interface: Some(format!("host!{}", host.as_ref())),
}
}
}
impl Default for NetworkInterface {
fn default() -> Self {
Self::any()
}
}
impl From<IpAddr> for NetworkInterface {
fn from(ip: IpAddr) -> Self {
Self {
interface: Some(format!("host!{}", ip)),
}
}
}
impl SetOpt for NetworkInterface {
fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
#[allow(unsafe_code)]
match self.interface.as_ref() {
Some(interface) => easy.interface(interface),
None => unsafe {
match curl_sys::curl_easy_setopt(easy.raw(), curl_sys::CURLOPT_INTERFACE, 0) {
curl_sys::CURLE_OK => Ok(()),
code => Err(curl::Error::new(code)),
}
},
}
}
}
#[derive(Clone, Debug)]
pub enum IpVersion {
V4,
V6,
Any,
}
impl Default for IpVersion {
fn default() -> Self {
Self::Any
}
}
impl SetOpt for IpVersion {
fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
easy.ip_resolve(match &self {
IpVersion::V4 => curl::easy::IpResolve::V4,
IpVersion::V6 => curl::easy::IpResolve::V6,
IpVersion::Any => curl::easy::IpResolve::Any,
})
}
}
#[derive(Clone, Debug)]
pub struct ExpectContinue {
timeout: Option<Duration>,
}
impl ExpectContinue {
pub const fn enabled() -> Self {
Self::timeout(Duration::from_secs(1))
}
pub const fn timeout(timeout: Duration) -> Self {
Self {
timeout: Some(timeout),
}
}
pub const fn disabled() -> Self {
Self {
timeout: None,
}
}
pub(crate) fn is_disabled(&self) -> bool {
self.timeout.is_none()
}
}
impl Default for ExpectContinue {
fn default() -> Self {
Self::enabled()
}
}
impl From<bool> for ExpectContinue {
fn from(value: bool) -> Self {
if value {
Self::enabled()
} else {
Self::disabled()
}
}
}
impl From<Duration> for ExpectContinue {
fn from(value: Duration) -> Self {
Self::timeout(value)
}
}
impl SetOpt for ExpectContinue {
fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
if let Some(timeout) = self.timeout {
easy.expect_100_timeout(timeout)
} else {
Ok(())
}
}
}