isahc/config/
mod.rs

1//! Definition of all client and request configuration options.
2//!
3//! Individual options are separated out into multiple types. Each type acts
4//! both as a "field name" and the value of that option.
5
6// Options are implemented as structs of various kinds that can be "applied" to
7// a curl easy handle. This helps to reduce code duplication as there are many
8// supported options, and also helps avoid having a massive function that does
9// all the configuring.
10//
11// When adding new config options, remember to add methods for setting the
12// option both in HttpClientBuilder and RequestBuilderExt. In addition, be sure
13// to update the client code to apply the option when configuring an easy
14// handle.
15
16use self::{proxy::Proxy, request::SetOpt};
17use crate::{
18    auth::{Authentication, Credentials},
19    is_http_version_supported,
20};
21use curl::easy::Easy2;
22use std::{net::IpAddr, time::Duration};
23
24pub(crate) mod client;
25pub(crate) mod dial;
26pub(crate) mod dns;
27pub(crate) mod proxy;
28pub(crate) mod redirect;
29pub(crate) mod request;
30pub(crate) mod ssl;
31
32pub use dial::{Dialer, DialerParseError};
33pub use dns::{DnsCache, ResolveMap};
34pub use redirect::RedirectPolicy;
35pub use ssl::{CaCertificate, ClientCertificate, PrivateKey, SslOption};
36
37/// Provides additional methods when building a request for configuring various
38/// execution-related options on how the request should be sent.
39///
40/// This trait can be used to either configure requests individually by invoking
41/// them on an [`http::request::Builder`], or to configure the default settings
42/// for an [`HttpClient`](crate::HttpClient) by invoking them on an
43/// [`HttpClientBuilder`](crate::HttpClientBuilder).
44///
45/// This trait is sealed and cannot be implemented for types outside of Isahc.
46pub trait Configurable: request::WithRequestConfig {
47    /// Specify a maximum amount of time that a complete request/response cycle
48    /// is allowed to take before being aborted. This includes DNS resolution,
49    /// connecting to the server, writing the request, and reading the response.
50    ///
51    /// Since response bodies are streamed, you will likely receive a
52    /// [`Response`](crate::http::Response) before the response body stream has
53    /// been fully consumed. This means that the configured timeout will still
54    /// be active for that request, and if it expires, further attempts to read
55    /// from the stream will return a [`TimedOut`](std::io::ErrorKind::TimedOut)
56    /// I/O error.
57    ///
58    /// This also means that if you receive a response with a body but do not
59    /// immediately start reading from it, then the timeout timer will still be
60    /// active and may expire before you even attempt to read the body. Keep
61    /// this in mind when consuming responses and consider handling the response
62    /// body right after you receive it if you are using this option.
63    ///
64    /// If not set, no timeout will be enforced.
65    ///
66    /// # Examples
67    ///
68    /// ```no_run
69    /// use isahc::{prelude::*, Request};
70    /// use std::time::Duration;
71    ///
72    /// // This page is too slow and won't respond in time.
73    /// let response = Request::get("https://httpbin.org/delay/10")
74    ///     .timeout(Duration::from_secs(5))
75    ///     .body(())?
76    ///     .send()
77    ///     .expect_err("page should time out");
78    /// # Ok::<(), isahc::Error>(())
79    /// ```
80    #[must_use = "builders have no effect if unused"]
81    fn timeout(self, timeout: Duration) -> Self {
82        self.with_config(move |config| {
83            config.timeout = Some(timeout);
84        })
85    }
86
87    /// Set a timeout for establishing connections to a host.
88    ///
89    /// If not set, a default connect timeout of 300 seconds will be used.
90    #[must_use = "builders have no effect if unused"]
91    fn connect_timeout(self, timeout: Duration) -> Self {
92        self.with_config(move |config| {
93            config.connect_timeout = Some(timeout);
94        })
95    }
96
97    /// Specify a maximum amount of time where transfer rate can go below
98    /// a minimum speed limit. `low_speed` is that limit in bytes/s.
99    ///
100    /// If not set, no low speed limits are imposed.
101    #[must_use = "builders have no effect if unused"]
102    fn low_speed_timeout(self, low_speed: u32, timeout: Duration) -> Self {
103        self.with_config(move |config| {
104            config.low_speed_timeout = Some((low_speed, timeout));
105        })
106    }
107
108    /// Configure how the use of HTTP versions should be negotiated with the
109    /// server.
110    ///
111    /// The default is [`VersionNegotiation::latest_compatible`].
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use isahc::{
117    ///     config::VersionNegotiation,
118    ///     prelude::*,
119    ///     HttpClient,
120    /// };
121    ///
122    /// // Never use anything newer than HTTP/1.x for this client.
123    /// let http11_client = HttpClient::builder()
124    ///     .version_negotiation(VersionNegotiation::http11())
125    ///     .build()?;
126    ///
127    /// // HTTP/2 with prior knowledge.
128    /// let http2_client = HttpClient::builder()
129    ///     .version_negotiation(VersionNegotiation::http2())
130    ///     .build()?;
131    /// # Ok::<(), isahc::Error>(())
132    /// ```
133    #[must_use = "builders have no effect if unused"]
134    fn version_negotiation(self, negotiation: VersionNegotiation) -> Self {
135        self.with_config(move |config| {
136            config.version_negotiation = Some(negotiation);
137        })
138    }
139
140    /// Set a policy for automatically following server redirects.
141    ///
142    /// The default is to not follow redirects.
143    ///
144    /// # Examples
145    ///
146    /// ```no_run
147    /// use isahc::{config::RedirectPolicy, prelude::*, Request};
148    ///
149    /// // This URL redirects us to where we want to go.
150    /// let response = Request::get("https://httpbin.org/redirect/1")
151    ///     .redirect_policy(RedirectPolicy::Follow)
152    ///     .body(())?
153    ///     .send()?;
154    ///
155    /// // This URL redirects too much!
156    /// let error = Request::get("https://httpbin.org/redirect/10")
157    ///     .redirect_policy(RedirectPolicy::Limit(5))
158    ///     .body(())?
159    ///     .send()
160    ///     .expect_err("too many redirects");
161    /// # Ok::<(), isahc::Error>(())
162    /// ```
163    #[must_use = "builders have no effect if unused"]
164    fn redirect_policy(self, policy: RedirectPolicy) -> Self {
165        self.with_config(move |config| {
166            config.redirect_policy = Some(policy);
167        })
168    }
169
170    /// Update the `Referer` header automatically when following redirects.
171    #[must_use = "builders have no effect if unused"]
172    fn auto_referer(self) -> Self {
173        self.with_config(move |config| {
174            config.auto_referer = Some(true);
175        })
176    }
177
178    /// Set a cookie jar to use to accept, store, and supply cookies for
179    /// incoming responses and outgoing requests.
180    ///
181    /// A cookie jar can be shared across multiple requests or with an entire
182    /// client, allowing cookies to be persisted across multiple requests.
183    ///
184    /// # Availability
185    ///
186    /// This method is only available when the [`cookies`](index.html#cookies)
187    /// feature is enabled.
188    #[cfg(feature = "cookies")]
189    #[must_use = "builders have no effect if unused"]
190    fn cookie_jar(self, cookie_jar: crate::cookies::CookieJar) -> Self;
191
192    /// Enable or disable automatic decompression of the response body for
193    /// various compression algorithms as returned by the server in the
194    /// [`Content-Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding)
195    /// response header.
196    ///
197    /// If set to true (the default), Isahc will automatically and transparently
198    /// decode the HTTP response body for known and available compression
199    /// algorithms. If the server returns a response with an unknown or
200    /// unavailable encoding, Isahc will return an
201    /// [`InvalidContentEncoding`](crate::error::ErrorKind::InvalidContentEncoding)
202    /// error.
203    ///
204    /// If you do not specify a specific value for the
205    /// [`Accept-Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding)
206    /// header, Isahc will set one for you automatically based on this option.
207    #[must_use = "builders have no effect if unused"]
208    fn automatic_decompression(self, decompress: bool) -> Self {
209        self.with_config(move |config| {
210            config.automatic_decompression = Some(decompress);
211        })
212    }
213
214    /// Configure the use of the `Expect` request header when sending request
215    /// bodies with HTTP/1.1.
216    ///
217    /// By default, when sending requests containing a body of large or unknown
218    /// length over HTTP/1.1, Isahc will send the request headers first without
219    /// the body and wait for the server to respond with a 100 (Continue) status
220    /// code, as defined by [RFC 7231, Section
221    /// 5.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1).
222    /// This gives the opportunity for the server to reject the response without
223    /// needing to first transmit the request body over the network, if the body
224    /// contents are not necessary for the server to determine an appropriate
225    /// response.
226    ///
227    /// For servers that do not support this behavior and instead simply wait
228    /// for the request body without responding with a 100 (Continue), there is
229    /// a limited timeout before the response body is sent anyway without
230    /// confirmation. The default timeout is 1 second, but this can be
231    /// configured.
232    ///
233    /// The `Expect` behavior can also be disabled entirely.
234    ///
235    /// This configuration only takes effect when using HTTP/1.1.
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// use std::time::Duration;
241    /// use isahc::{
242    ///     config::ExpectContinue,
243    ///     prelude::*,
244    ///     HttpClient,
245    /// };
246    ///
247    /// // Use the default behavior (enabled).
248    /// let client = HttpClient::builder()
249    ///     .expect_continue(ExpectContinue::enabled())
250    ///     // or equivalently...
251    ///     .expect_continue(ExpectContinue::default())
252    ///     // or equivalently...
253    ///     .expect_continue(true)
254    ///     .build()?;
255    ///
256    /// // Customize the timeout if the server doesn't respond with a 100
257    /// // (Continue) status.
258    /// let client = HttpClient::builder()
259    ///     .expect_continue(ExpectContinue::timeout(Duration::from_millis(200)))
260    ///     // or equivalently...
261    ///     .expect_continue(Duration::from_millis(200))
262    ///     .build()?;
263    ///
264    /// // Disable the Expect header entirely.
265    /// let client = HttpClient::builder()
266    ///     .expect_continue(ExpectContinue::disabled())
267    ///     // or equivalently...
268    ///     .expect_continue(false)
269    ///     .build()?;
270    /// # Ok::<(), isahc::Error>(())
271    /// ```
272    #[must_use = "builders have no effect if unused"]
273    fn expect_continue<T>(self, expect: T) -> Self
274    where
275        T: Into<ExpectContinue>,
276    {
277        self.with_config(move |config| {
278            config.expect_continue = Some(expect.into());
279        })
280    }
281
282    /// Set one or more default HTTP authentication methods to attempt to use
283    /// when authenticating with the server.
284    ///
285    /// Depending on the authentication schemes enabled, you will also need to
286    /// set credentials to use for authentication using
287    /// [`Configurable::credentials`].
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// use isahc::{
293    ///     auth::{Authentication, Credentials},
294    ///     prelude::*,
295    ///     HttpClient,
296    /// };
297    ///
298    /// let client = HttpClient::builder()
299    ///     .authentication(Authentication::basic() | Authentication::digest())
300    ///     .credentials(Credentials::new("clark", "qwerty"))
301    ///     .build()?;
302    /// # Ok::<(), isahc::Error>(())
303    /// ```
304    #[must_use = "builders have no effect if unused"]
305    fn authentication(self, authentication: Authentication) -> Self {
306        self.with_config(move |config| {
307            config.authentication = Some(authentication);
308        })
309    }
310
311    /// Set the credentials to use for HTTP authentication.
312    ///
313    /// This setting will do nothing unless you also set one or more
314    /// authentication methods using [`Configurable::authentication`].
315    #[must_use = "builders have no effect if unused"]
316    fn credentials(self, credentials: Credentials) -> Self {
317        self.with_config(move |config| {
318            config.credentials = Some(credentials);
319        })
320    }
321
322    /// Enable TCP keepalive with a given probe interval.
323    #[must_use = "builders have no effect if unused"]
324    fn tcp_keepalive(self, interval: Duration) -> Self {
325        self.with_config(move |config| {
326            config.tcp_keepalive = Some(interval);
327        })
328    }
329
330    /// Enables the `TCP_NODELAY` option on connect.
331    #[must_use = "builders have no effect if unused"]
332    fn tcp_nodelay(self) -> Self {
333        self.with_config(move |config| {
334            config.tcp_nodelay = Some(true);
335        })
336    }
337
338    /// Bind local socket connections to a particular network interface.
339    ///
340    /// # Examples
341    ///
342    /// Bind to an IP address.
343    ///
344    /// ```
345    /// use isahc::{
346    ///     prelude::*,
347    ///     config::NetworkInterface,
348    ///     HttpClient,
349    ///     Request,
350    /// };
351    /// use std::net::IpAddr;
352    ///
353    /// // Bind to an IP address.
354    /// let client = HttpClient::builder()
355    ///     .interface(IpAddr::from([192, 168, 1, 2]))
356    ///     .build()?;
357    ///
358    /// // Bind to an interface by name (not supported on Windows).
359    /// # #[cfg(unix)]
360    /// let client = HttpClient::builder()
361    ///     .interface(NetworkInterface::name("eth0"))
362    ///     .build()?;
363    ///
364    /// // Reset to using whatever interface the TCP stack finds suitable (the
365    /// // default).
366    /// let request = Request::get("https://example.org")
367    ///     .interface(NetworkInterface::any())
368    ///     .body(())?;
369    /// # Ok::<(), isahc::Error>(())
370    /// ```
371    #[must_use = "builders have no effect if unused"]
372    fn interface<I>(self, interface: I) -> Self
373    where
374        I: Into<NetworkInterface>,
375    {
376        self.with_config(move |config| {
377            config.interface = Some(interface.into());
378        })
379    }
380
381    /// Select a specific IP version when resolving hostnames. If a given
382    /// hostname does not resolve to an IP address of the desired version, then
383    /// the request will fail with a connection error.
384    ///
385    /// This does not affect requests with an explicit IP address as the host.
386    ///
387    /// The default is [`IpVersion::Any`].
388    #[must_use = "builders have no effect if unused"]
389    fn ip_version(self, version: IpVersion) -> Self {
390        self.with_config(move |config| {
391            config.ip_version = Some(version);
392        })
393    }
394
395    /// Specify a socket to connect to instead of the using the host and port
396    /// defined in the request URI.
397    ///
398    /// # Examples
399    ///
400    /// Connecting to a Unix socket:
401    ///
402    /// ```
403    /// use isahc::{
404    ///     config::Dialer,
405    ///     prelude::*,
406    ///     Request,
407    /// };
408    ///
409    /// # #[cfg(unix)]
410    /// let request = Request::get("http://localhost/containers")
411    ///     .dial(Dialer::unix_socket("/path/to/my.sock"))
412    ///     .body(())?;
413    /// # Ok::<(), isahc::Error>(())
414    /// ```
415    ///
416    /// Connecting to a specific Internet socket address:
417    ///
418    /// ```
419    /// use isahc::{
420    ///     config::Dialer,
421    ///     prelude::*,
422    ///     Request,
423    /// };
424    /// use std::net::Ipv4Addr;
425    ///
426    /// let request = Request::get("http://exmaple.org")
427    ///     // Actually issue the request to localhost on port 8080. The host
428    ///     // header will remain unchanged.
429    ///     .dial(Dialer::ip_socket((Ipv4Addr::LOCALHOST, 8080)))
430    ///     .body(())?;
431    /// # Ok::<(), isahc::Error>(())
432    /// ```
433    #[must_use = "builders have no effect if unused"]
434    fn dial<D>(self, dialer: D) -> Self
435    where
436        D: Into<Dialer>,
437    {
438        self.with_config(move |config| {
439            config.dial = Some(dialer.into());
440        })
441    }
442
443    /// Set a proxy to use for requests.
444    ///
445    /// The proxy protocol is specified by the URI scheme.
446    ///
447    /// - **`http`**: Proxy. Default when no scheme is specified.
448    /// - **`https`**: HTTPS Proxy. (Added in 7.52.0 for OpenSSL, GnuTLS and
449    ///   NSS)
450    /// - **`socks4`**: SOCKS4 Proxy.
451    /// - **`socks4a`**: SOCKS4a Proxy. Proxy resolves URL hostname.
452    /// - **`socks5`**: SOCKS5 Proxy.
453    /// - **`socks5h`**: SOCKS5 Proxy. Proxy resolves URL hostname.
454    ///
455    /// By default no proxy will be used, unless one is specified in either the
456    /// `http_proxy` or `https_proxy` environment variables.
457    ///
458    /// Setting to `None` explicitly disables the use of a proxy.
459    ///
460    /// # Examples
461    ///
462    /// Using `http://proxy:80` as a proxy:
463    ///
464    /// ```
465    /// use isahc::{prelude::*, HttpClient};
466    ///
467    /// let client = HttpClient::builder()
468    ///     .proxy(Some("http://proxy:80".parse()?))
469    ///     .build()?;
470    /// # Ok::<(), Box<dyn std::error::Error>>(())
471    /// ```
472    ///
473    /// Explicitly disable the use of a proxy:
474    ///
475    /// ```
476    /// use isahc::{prelude::*, HttpClient};
477    ///
478    /// let client = HttpClient::builder()
479    ///     .proxy(None)
480    ///     .build()?;
481    /// # Ok::<(), Box<dyn std::error::Error>>(())
482    /// ```
483    #[must_use = "builders have no effect if unused"]
484    fn proxy(self, proxy: impl Into<Option<http::Uri>>) -> Self {
485        self.with_config(move |config| {
486            config.proxy = Some(proxy.into());
487        })
488    }
489
490    /// Disable proxy usage for the provided list of hosts.
491    ///
492    /// # Examples
493    ///
494    /// ```
495    /// use isahc::{prelude::*, HttpClient};
496    ///
497    /// let client = HttpClient::builder()
498    ///     // Disable proxy for specified hosts.
499    ///     .proxy_blacklist(vec!["a.com", "b.org"])
500    ///     .build()?;
501    /// # Ok::<(), isahc::Error>(())
502    /// ```
503    #[must_use = "builders have no effect if unused"]
504    fn proxy_blacklist<I, T>(self, hosts: I) -> Self
505    where
506        I: IntoIterator<Item = T>,
507        T: Into<String>,
508    {
509        self.with_config(move |config| {
510            config.proxy_blacklist = Some(hosts.into_iter().map(T::into).collect());
511        })
512    }
513
514    /// Set one or more HTTP authentication methods to attempt to use when
515    /// authenticating with a proxy.
516    ///
517    /// Depending on the authentication schemes enabled, you will also need to
518    /// set credentials to use for authentication using
519    /// [`Configurable::proxy_credentials`].
520    ///
521    /// # Examples
522    ///
523    /// ```
524    /// use isahc::{
525    ///     auth::{Authentication, Credentials},
526    ///     prelude::*,
527    ///     HttpClient,
528    /// };
529    ///
530    /// let client = HttpClient::builder()
531    ///     .proxy("http://proxy:80".parse::<http::Uri>()?)
532    ///     .proxy_authentication(Authentication::basic())
533    ///     .proxy_credentials(Credentials::new("clark", "qwerty"))
534    ///     .build()?;
535    /// # Ok::<(), Box<dyn std::error::Error>>(())
536    /// ```
537    #[must_use = "builders have no effect if unused"]
538    fn proxy_authentication(self, authentication: Authentication) -> Self {
539        self.with_config(move |config| {
540            config.proxy_authentication = Some(Proxy(authentication));
541        })
542    }
543
544    /// Set the credentials to use for proxy authentication.
545    ///
546    /// This setting will do nothing unless you also set one or more proxy
547    /// authentication methods using
548    /// [`Configurable::proxy_authentication`].
549    #[must_use = "builders have no effect if unused"]
550    fn proxy_credentials(self, credentials: Credentials) -> Self {
551        self.with_config(move |config| {
552            config.proxy_credentials = Some(Proxy(credentials));
553        })
554    }
555
556    /// Set a maximum upload speed for the request body, in bytes per second.
557    ///
558    /// The default is unlimited.
559    #[must_use = "builders have no effect if unused"]
560    fn max_upload_speed(self, max: u64) -> Self {
561        self.with_config(move |config| {
562            config.max_upload_speed = Some(max);
563        })
564    }
565
566    /// Set a maximum download speed for the response body, in bytes per second.
567    ///
568    /// The default is unlimited.
569    #[must_use = "builders have no effect if unused"]
570    fn max_download_speed(self, max: u64) -> Self {
571        self.with_config(move |config| {
572            config.max_download_speed = Some(max);
573        })
574    }
575
576    /// Set a custom SSL/TLS client certificate to use for client connections.
577    ///
578    /// If a format is not supported by the underlying SSL/TLS engine, an error
579    /// will be returned when attempting to send a request using the offending
580    /// certificate.
581    ///
582    /// The default value is none.
583    ///
584    /// # Examples
585    ///
586    /// ```no_run
587    /// use isahc::{
588    ///     config::{ClientCertificate, PrivateKey},
589    ///     prelude::*,
590    ///     Request,
591    /// };
592    ///
593    /// let response = Request::get("localhost:3999")
594    ///     .ssl_client_certificate(ClientCertificate::pem_file(
595    ///         "client.pem",
596    ///         PrivateKey::pem_file("key.pem", String::from("secret")),
597    ///     ))
598    ///     .body(())?
599    ///     .send()?;
600    /// # Ok::<(), isahc::Error>(())
601    /// ```
602    ///
603    /// ```
604    /// use isahc::{
605    ///     config::{ClientCertificate, PrivateKey},
606    ///     prelude::*,
607    ///     HttpClient,
608    /// };
609    ///
610    /// let client = HttpClient::builder()
611    ///     .ssl_client_certificate(ClientCertificate::pem_file(
612    ///         "client.pem",
613    ///         PrivateKey::pem_file("key.pem", String::from("secret")),
614    ///     ))
615    ///     .build()?;
616    /// # Ok::<(), isahc::Error>(())
617    /// ```
618    #[must_use = "builders have no effect if unused"]
619    fn ssl_client_certificate(self, certificate: ClientCertificate) -> Self {
620        self.with_config(move |config| {
621            config.ssl_client_certificate = Some(certificate);
622        })
623    }
624
625    /// Set a custom SSL/TLS CA certificate bundle to use for client
626    /// connections.
627    ///
628    /// The default value is none.
629    ///
630    /// # Notes
631    ///
632    /// On Windows it may be necessary to combine this with
633    /// [`SslOption::DANGER_ACCEPT_REVOKED_CERTS`] in order to work depending on
634    /// the contents of your CA bundle.
635    ///
636    /// # Examples
637    ///
638    /// ```
639    /// use isahc::{config::CaCertificate, prelude::*, HttpClient};
640    ///
641    /// let client = HttpClient::builder()
642    ///     .ssl_ca_certificate(CaCertificate::file("ca.pem"))
643    ///     .build()?;
644    /// # Ok::<(), isahc::Error>(())
645    /// ```
646    #[must_use = "builders have no effect if unused"]
647    fn ssl_ca_certificate(self, certificate: CaCertificate) -> Self {
648        self.with_config(move |config| {
649            config.ssl_ca_certificate = Some(certificate);
650        })
651    }
652
653    /// Set a list of ciphers to use for SSL/TLS connections.
654    ///
655    /// The list of valid cipher names is dependent on the underlying SSL/TLS
656    /// engine in use. You can find an up-to-date list of potential cipher names
657    /// at <https://curl.haxx.se/docs/ssl-ciphers.html>.
658    ///
659    /// The default is unset and will result in the system defaults being used.
660    #[must_use = "builders have no effect if unused"]
661    fn ssl_ciphers<I, T>(self, ciphers: I) -> Self
662    where
663        I: IntoIterator<Item = T>,
664        T: Into<String>,
665    {
666        self.with_config(move |config| {
667            config.ssl_ciphers = Some(ciphers.into_iter().map(T::into).collect());
668        })
669    }
670
671    /// Set various options for this request that control SSL/TLS behavior.
672    ///
673    /// Most options are for disabling security checks that introduce security
674    /// risks, but may be required as a last resort. Note that the most secure
675    /// options are already the default and do not need to be specified.
676    ///
677    /// The default value is [`SslOption::NONE`].
678    ///
679    /// # Warning
680    ///
681    /// You should think very carefully before using this method. Using *any*
682    /// options that alter how certificates are validated can introduce
683    /// significant security vulnerabilities.
684    ///
685    /// # Examples
686    ///
687    /// ```no_run
688    /// use isahc::{config::SslOption, prelude::*, Request};
689    ///
690    /// let response = Request::get("https://badssl.com")
691    ///     .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS)
692    ///     .body(())?
693    ///     .send()?;
694    /// # Ok::<(), isahc::Error>(())
695    /// ```
696    ///
697    /// ```
698    /// use isahc::{config::SslOption, prelude::*, HttpClient};
699    ///
700    /// let client = HttpClient::builder()
701    ///     .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS | SslOption::DANGER_ACCEPT_REVOKED_CERTS)
702    ///     .build()?;
703    /// # Ok::<(), isahc::Error>(())
704    /// ```
705    #[must_use = "builders have no effect if unused"]
706    fn ssl_options(self, options: SslOption) -> Self {
707        self.with_config(move |config| {
708            config.ssl_options = Some(options);
709        })
710    }
711
712    /// Enable or disable sending HTTP header names in Title-Case instead of
713    /// lowercase form.
714    ///
715    /// This option only affects user-supplied headers and does not affect
716    /// low-level headers that are automatically supplied for HTTP protocol
717    /// details, such as `Connection` and `Host` (unless you override such a
718    /// header yourself).
719    ///
720    /// This option has no effect when using HTTP/2 or newer where headers are
721    /// required to be lowercase.
722    #[must_use = "builders have no effect if unused"]
723    fn title_case_headers(self, enable: bool) -> Self {
724        self.with_config(move |config| {
725            config.title_case_headers = Some(enable);
726        })
727    }
728
729    /// Enable or disable comprehensive per-request metrics collection.
730    ///
731    /// When enabled, detailed timing metrics will be tracked while a request is
732    /// in progress, such as bytes sent and received, estimated size, DNS lookup
733    /// time, etc. For a complete list of the available metrics that can be
734    /// inspected, see the [`Metrics`](crate::Metrics) documentation.
735    ///
736    /// When enabled, to access a view of the current metrics values you can use
737    /// [`ResponseExt::metrics`](crate::ResponseExt::metrics).
738    ///
739    /// While effort is taken to optimize hot code in metrics collection, it is
740    /// likely that enabling it will have a small effect on overall throughput.
741    /// Disabling metrics may be necessary for absolute peak performance.
742    ///
743    /// By default metrics are disabled.
744    #[must_use = "builders have no effect if unused"]
745    fn metrics(self, enable: bool) -> Self {
746        self.with_config(move |config| {
747            config.enable_metrics = Some(enable);
748        })
749    }
750}
751
752/// A strategy for selecting what HTTP versions should be used when
753/// communicating with a server.
754///
755/// You can set a version negotiation strategy on a given request or on a client
756/// with [`Configurable::version_negotiation`].
757///
758/// Attempting to use an HTTP version without client-side support at runtime
759/// will result in an error. For example, using the system libcurl on an old
760/// machine may not have an HTTP/2 implementation. Using static linking and the
761/// [`http2`](../index.html#http2) crate feature can help guarantee that HTTP/2
762/// will be available to use.
763#[derive(Clone, Debug)]
764pub struct VersionNegotiation(VersionNegotiationInner);
765
766#[derive(Clone, Copy, Debug)]
767enum VersionNegotiationInner {
768    LatestCompatible,
769    Strict(curl::easy::HttpVersion),
770}
771
772impl Default for VersionNegotiation {
773    fn default() -> Self {
774        Self::latest_compatible()
775    }
776}
777
778impl VersionNegotiation {
779    /// Always prefer the latest supported version announced by the server,
780    /// falling back to older versions if not explicitly listed as supported.
781    /// This is the default.
782    ///
783    /// Secure connections will begin with a TLS handshake, after which the
784    /// highest supported HTTP version listed by the server via ALPN will be
785    /// used. Once connected, additional upgrades to newer versions may also
786    /// occur if the server lists support for it. In the future, headers such as
787    /// `Alt-Svc` will be used.
788    ///
789    /// Insecure connections always use HTTP/1.x since there is no standard
790    /// mechanism for a server to declare support for insecure HTTP versions,
791    /// and only HTTP/0.9, HTTP/1.x, and HTTP/2 support insecure transfers.
792    pub const fn latest_compatible() -> Self {
793        Self(VersionNegotiationInner::LatestCompatible)
794    }
795
796    /// Connect via HTTP/1.0 and do not attempt to use a higher version.
797    pub const fn http10() -> Self {
798        Self(VersionNegotiationInner::Strict(
799            curl::easy::HttpVersion::V10,
800        ))
801    }
802
803    /// Connect via HTTP/1.1 and do not attempt to use a higher version.
804    pub const fn http11() -> Self {
805        Self(VersionNegotiationInner::Strict(
806            curl::easy::HttpVersion::V11,
807        ))
808    }
809
810    /// Connect via HTTP/2. Failure to connect will not fall back to old
811    /// versions, unless HTTP/1.1 is negotiated via TLS ALPN before the session
812    /// begins.
813    ///
814    /// If HTTP/2 support is not compiled in, then using this strategy will
815    /// always result in an error.
816    ///
817    /// This strategy is often referred to as [HTTP/2 with Prior
818    /// Knowledge](https://http2.github.io/http2-spec/#known-http).
819    pub const fn http2() -> Self {
820        Self(VersionNegotiationInner::Strict(
821            curl::easy::HttpVersion::V2PriorKnowledge,
822        ))
823    }
824
825    /// Connect via HTTP/3. Failure to connect will not fall back to old
826    /// versions.
827    pub const fn http3() -> Self {
828        Self(VersionNegotiationInner::Strict(curl::easy::HttpVersion::V3))
829    }
830}
831
832impl SetOpt for VersionNegotiation {
833    fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
834        match self.0 {
835            VersionNegotiationInner::LatestCompatible => {
836                // If HTTP/2 support is available, this basically the most
837                // lenient way of using it. Alt-Svc is used to upgrade to newer
838                // versions, and old versions are used if the server doesn't
839                // list HTTP/2 via ALPN.
840                //
841                // If HTTP/2 is not available, leaving it the default setting is
842                // the ideal behavior.
843                if is_http_version_supported(http::Version::HTTP_2) {
844                    easy.http_version(curl::easy::HttpVersion::V2TLS)
845                } else {
846                    Ok(())
847                }
848            }
849            VersionNegotiationInner::Strict(version) => easy.http_version(version),
850        }
851    }
852}
853
854/// Used to configure which local addresses or interfaces should be used to send
855/// network traffic from.
856///
857/// Note that this type is "lazy" in the sense that errors are not returned if
858/// the given interfaces are not checked for validity until you actually attempt
859/// to use it in a network request.
860#[derive(Clone, Debug)]
861pub struct NetworkInterface {
862    /// Interface in verbose curl format.
863    interface: Option<String>,
864}
865
866impl NetworkInterface {
867    /// Bind to whatever the networking stack finds suitable. This is the
868    /// default behavior.
869    pub fn any() -> Self {
870        Self {
871            interface: None,
872        }
873    }
874
875    /// Bind to the interface with the given name (such as `eth0`). This method
876    /// is not available on Windows as it does not really have names for network
877    /// devices.
878    ///
879    /// # Examples
880    ///
881    /// ```
882    /// # use isahc::config::NetworkInterface;
883    /// let loopback = NetworkInterface::name("lo");
884    /// let wifi = NetworkInterface::name("wlan0");
885    /// ```
886    #[cfg(unix)]
887    pub fn name(name: impl AsRef<str>) -> Self {
888        Self {
889            interface: Some(format!("if!{}", name.as_ref())),
890        }
891    }
892
893    /// Bind to the given local host or address. This can either be a host name
894    /// or an IP address.
895    ///
896    /// # Examples
897    ///
898    /// ```
899    /// # use isahc::config::NetworkInterface;
900    /// let local = NetworkInterface::host("server.local");
901    /// let addr = NetworkInterface::host("192.168.1.2");
902    /// ```
903    pub fn host(host: impl AsRef<str>) -> Self {
904        Self {
905            interface: Some(format!("host!{}", host.as_ref())),
906        }
907    }
908}
909
910impl Default for NetworkInterface {
911    fn default() -> Self {
912        Self::any()
913    }
914}
915
916impl From<IpAddr> for NetworkInterface {
917    fn from(ip: IpAddr) -> Self {
918        Self {
919            interface: Some(format!("host!{}", ip)),
920        }
921    }
922}
923
924impl SetOpt for NetworkInterface {
925    fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
926        #[allow(unsafe_code)]
927        match self.interface.as_ref() {
928            Some(interface) => easy.interface(interface),
929
930            // Use raw FFI because safe wrapper doesn't let us set to null.
931            None => unsafe {
932                match curl_sys::curl_easy_setopt(easy.raw(), curl_sys::CURLOPT_INTERFACE, 0) {
933                    curl_sys::CURLE_OK => Ok(()),
934                    code => Err(curl::Error::new(code)),
935                }
936            },
937        }
938    }
939}
940
941/// Supported IP versions that can be used.
942#[derive(Clone, Debug)]
943pub enum IpVersion {
944    /// Use IPv4 addresses only. IPv6 addresses will be ignored.
945    V4,
946
947    /// Use IPv6 addresses only. IPv4 addresses will be ignored.
948    V6,
949
950    /// Use either IPv4 or IPv6 addresses. By default IPv6 addresses are
951    /// preferred if available, otherwise an IPv4 address will be used. IPv6
952    /// addresses are tried first by following the recommendations of [RFC
953    /// 6555 "Happy Eyeballs"](https://tools.ietf.org/html/rfc6555).
954    Any,
955}
956
957impl Default for IpVersion {
958    fn default() -> Self {
959        Self::Any
960    }
961}
962
963impl SetOpt for IpVersion {
964    fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
965        easy.ip_resolve(match &self {
966            IpVersion::V4 => curl::easy::IpResolve::V4,
967            IpVersion::V6 => curl::easy::IpResolve::V6,
968            IpVersion::Any => curl::easy::IpResolve::Any,
969        })
970    }
971}
972
973/// Controls the use of the `Expect` request header when sending request bodies
974/// with HTTP/1.1.
975///
976/// By default, when sending requests containing a body of large or unknown
977/// length over HTTP/1.1, Isahc will send the request headers first without the
978/// body and wait for the server to respond with a 100 (Continue) status code,
979/// as defined by [RFC 7231, Section
980/// 5.1.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1). This
981/// gives the opportunity for the server to reject the response without needing
982/// to first transmit the request body over the network, if the body contents
983/// are not necessary for the server to determine an appropriate response.
984///
985/// For servers that do not support this behavior and instead simply wait for
986/// the request body without responding with a 100 (Continue), there is a
987/// limited timeout before the response body is sent anyway without
988/// confirmation. The default timeout is 1 second, but this can be configured.
989///
990/// The `Expect` behavior can also be disabled entirely.
991///
992/// This configuration only takes effect when using HTTP/1.1.
993#[derive(Clone, Debug)]
994pub struct ExpectContinue {
995    timeout: Option<Duration>,
996}
997
998impl ExpectContinue {
999    /// Enable the use of `Expect` and wait for a 100 (Continue) response with a
1000    /// default timeout before sending a request body.
1001    pub const fn enabled() -> Self {
1002        Self::timeout(Duration::from_secs(1))
1003    }
1004
1005    /// Enable the use of `Expect` and wait for a 100 (Continue) response, up to
1006    /// the given timeout, before sending a request body.
1007    pub const fn timeout(timeout: Duration) -> Self {
1008        Self {
1009            timeout: Some(timeout),
1010        }
1011    }
1012
1013    /// Disable the use and handling of the `Expect` request header.
1014    pub const fn disabled() -> Self {
1015        Self {
1016            timeout: None,
1017        }
1018    }
1019
1020    pub(crate) fn is_disabled(&self) -> bool {
1021        self.timeout.is_none()
1022    }
1023}
1024
1025impl Default for ExpectContinue {
1026    fn default() -> Self {
1027        Self::enabled()
1028    }
1029}
1030
1031impl From<bool> for ExpectContinue {
1032    fn from(value: bool) -> Self {
1033        if value {
1034            Self::enabled()
1035        } else {
1036            Self::disabled()
1037        }
1038    }
1039}
1040
1041impl From<Duration> for ExpectContinue {
1042    fn from(value: Duration) -> Self {
1043        Self::timeout(value)
1044    }
1045}
1046
1047impl SetOpt for ExpectContinue {
1048    fn set_opt<H>(&self, easy: &mut Easy2<H>) -> Result<(), curl::Error> {
1049        if let Some(timeout) = self.timeout {
1050            easy.expect_100_timeout(timeout)
1051        } else {
1052            Ok(())
1053        }
1054    }
1055}