1use std::path::PathBuf;
4use std::str;
5use std::time::Duration;
6
7use anyhow::bail;
8use curl::easy::Easy;
9use curl::easy::Easy2;
10use curl::easy::InfoType;
11use curl::easy::SslOpt;
12use curl::easy::SslVersion;
13use tracing::debug;
14use tracing::trace;
15
16use crate::CargoResult;
17use crate::GlobalContext;
18use crate::util::context::SslVersionConfig;
19use crate::util::context::SslVersionConfigRange;
20
21pub fn http_handle(gctx: &GlobalContext) -> CargoResult<Easy> {
23 let (mut handle, timeout) = http_handle_and_timeout(gctx)?;
24 timeout.configure(&mut handle)?;
25 Ok(handle)
26}
27
28pub fn http_handle_and_timeout(gctx: &GlobalContext) -> CargoResult<(Easy, HttpTimeout)> {
29 let mut handle = Easy::new();
34 let timeout = configure_http_handle(gctx, &mut handle)?;
35 Ok((handle, timeout))
36}
37
38pub fn needs_custom_http_transport(gctx: &GlobalContext) -> CargoResult<bool> {
43 Ok(super::proxy::http_proxy_exists(gctx.http_config()?, gctx)
44 || *gctx.http_config()? != Default::default()
45 || gctx.get_env_os("HTTP_TIMEOUT").is_some())
46}
47pub struct HandleConfiguration {
49 proxy: Option<String>,
50 cainfo: Option<PathBuf>,
51 proxy_cainfo: Option<String>,
52 ssl_options: Option<SslOpt>,
53 useragent: String,
54 ssl_version: Option<SslVersion>,
55 ssl_min_max_version: Option<(SslVersion, SslVersion)>,
56 pub timeout: HttpTimeout,
57 pub verbose: bool,
58 pub multiplexing: bool,
59}
60
61pub fn configure_http_handle(gctx: &GlobalContext, handle: &mut Easy) -> CargoResult<HttpTimeout> {
62 let configuration = HandleConfiguration::new(gctx)?;
63 configuration.configure(handle)?;
64 Ok(configuration.timeout)
65}
66
67impl HandleConfiguration {
68 pub fn new(gctx: &GlobalContext) -> CargoResult<Self> {
69 if let Some(offline_flag) = gctx.offline_flag() {
70 bail!(
71 "attempting to make an HTTP request, but {offline_flag} was \
72 specified"
73 )
74 }
75
76 let http = gctx.http_config()?;
77 let timeout = HttpTimeout::new(gctx)?;
78 let useragent = if let Some(user_agent) = http.user_agent.clone() {
79 user_agent
80 } else {
81 format!("cargo/{}", crate::version())
82 };
83 let multiplexing = http.multiplexing.unwrap_or(true);
84 let mut handle = HandleConfiguration {
85 proxy: None,
86 cainfo: None,
87 proxy_cainfo: None,
88 ssl_options: None,
89 useragent,
90 ssl_version: None,
91 ssl_min_max_version: None,
92 verbose: false,
93 timeout,
94 multiplexing,
95 };
96 if let Some(proxy) = super::proxy::http_proxy(http) {
97 handle.proxy = Some(proxy);
98 }
99 if let Some(cainfo) = &http.cainfo {
100 let cainfo = cainfo.resolve_path(gctx);
101 handle.cainfo = Some(cainfo);
102 }
103 if let Some(proxy_cainfo) = http.proxy_cainfo.as_ref().or(http.cainfo.as_ref()) {
105 let proxy_cainfo = proxy_cainfo.resolve_path(gctx);
106 handle.proxy_cainfo = Some(format!("{}", proxy_cainfo.display()));
107 }
108 if let Some(check) = http.check_revoke {
109 let mut v = SslOpt::new();
110 v.no_revoke(!check);
111 handle.ssl_options = Some(v);
112 }
113
114 fn to_ssl_version(s: &str) -> CargoResult<SslVersion> {
115 let version = match s {
116 "default" => SslVersion::Default,
117 "tlsv1" => SslVersion::Tlsv1,
118 "tlsv1.0" => SslVersion::Tlsv10,
119 "tlsv1.1" => SslVersion::Tlsv11,
120 "tlsv1.2" => SslVersion::Tlsv12,
121 "tlsv1.3" => SslVersion::Tlsv13,
122 _ => bail!(
123 "Invalid ssl version `{s}`,\
124 choose from 'default', 'tlsv1', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'."
125 ),
126 };
127 Ok(version)
128 }
129
130 if let Some(ssl_version) = &http.ssl_version {
131 match ssl_version {
132 SslVersionConfig::Single(s) => {
133 let version = to_ssl_version(s.as_str())?;
134 handle.ssl_version = Some(version);
135 }
136 SslVersionConfig::Range(SslVersionConfigRange { min, max }) => {
137 let min_version = min
138 .as_ref()
139 .map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
140 let max_version = max
141 .as_ref()
142 .map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
143 handle.ssl_min_max_version = Some((min_version, max_version));
144 }
145 }
146 } else if cfg!(windows) {
147 handle.ssl_min_max_version = Some((SslVersion::Default, SslVersion::Tlsv12));
164 }
165
166 if let Some(true) = http.debug {
167 handle.verbose = true;
168 }
169
170 Ok(handle)
171 }
172
173 pub fn configure(&self, handle: &mut Easy) -> Result<(), curl::Error> {
174 if let Some(v) = &self.proxy {
175 handle.proxy(&v)?;
176 }
177 if let Some(v) = &self.cainfo {
178 handle.cainfo(&v)?;
179 }
180 if let Some(v) = &self.proxy_cainfo {
181 handle.proxy_cainfo(&v)?;
182 }
183 if let Some(v) = &self.ssl_options {
184 handle.ssl_options(&v)?;
185 }
186 handle.useragent(&self.useragent)?;
187 handle.accept_encoding("")?;
189 if let Some(v) = &self.ssl_version {
190 handle.ssl_version(v.clone())?;
191 }
192 if let Some((min, max)) = &self.ssl_min_max_version {
193 handle.ssl_min_max_version(min.clone(), max.clone())?;
194 }
195 if self.verbose {
196 handle.verbose(true)?;
197 tracing::debug!(target: "network", "{:#?}", curl::Version::get());
198 handle.debug_function(debug)?;
199 }
200 Ok(())
201 }
202
203 pub fn configure2<T>(&self, handle: &mut Easy2<T>) -> Result<(), curl::Error> {
204 if let Some(v) = &self.proxy {
205 handle.proxy(&v)?;
206 }
207 if let Some(v) = &self.cainfo {
208 handle.cainfo(&v)?;
209 }
210 if let Some(v) = &self.proxy_cainfo {
211 handle.proxy_cainfo(&v)?;
212 }
213 if let Some(v) = &self.ssl_options {
214 handle.ssl_options(&v)?;
215 }
216 handle.useragent(&self.useragent)?;
217 handle.accept_encoding("")?;
219 if let Some(v) = &self.ssl_version {
220 handle.ssl_version(v.clone())?;
221 }
222 if let Some((min, max)) = &self.ssl_min_max_version {
223 handle.ssl_min_max_version(min.clone(), max.clone())?;
224 }
225 if self.verbose {
226 handle.verbose(true)?;
227 tracing::debug!(target: "network", "{:#?}", curl::Version::get());
228 }
229
230 crate::try_old_curl_http2_pipewait!(self.multiplexing, handle);
232 Ok(())
233 }
234}
235
236pub(crate) fn debug(kind: InfoType, data: &[u8]) {
237 enum LogLevel {
238 Debug,
239 Trace,
240 }
241 use LogLevel::*;
242 let (prefix, level) = match kind {
243 InfoType::Text => ("*", Debug),
244 InfoType::HeaderIn => ("<", Debug),
245 InfoType::HeaderOut => (">", Debug),
246 InfoType::DataIn => ("{", Trace),
247 InfoType::DataOut => ("}", Trace),
248 InfoType::SslDataIn | InfoType::SslDataOut => return,
249 _ => return,
250 };
251 let starts_with_ignore_case = |line: &str, text: &str| -> bool {
252 let line = line.as_bytes();
253 let text = text.as_bytes();
254 line[..line.len().min(text.len())].eq_ignore_ascii_case(text)
255 };
256 match str::from_utf8(data) {
257 Ok(s) => {
258 for mut line in s.lines() {
259 if starts_with_ignore_case(line, "authorization:") {
260 line = "Authorization: [REDACTED]";
261 } else if starts_with_ignore_case(line, "h2h3 [authorization:") {
262 line = "h2h3 [Authorization: [REDACTED]]";
263 } else if starts_with_ignore_case(line, "set-cookie") {
264 line = "set-cookie: [REDACTED]";
265 }
266 match level {
267 Debug => debug!(target: "network", "http-debug: {prefix} {line}"),
268 Trace => trace!(target: "network", "http-debug: {prefix} {line}"),
269 }
270 }
271 }
272 Err(_) => {
273 let len = data.len();
274 match level {
275 Debug => {
276 debug!(target: "network", "http-debug: {prefix} ({len} bytes of data)")
277 }
278 Trace => {
279 trace!(target: "network", "http-debug: {prefix} ({len} bytes of data)")
280 }
281 }
282 }
283 }
284}
285
286#[must_use]
287#[derive(Clone)]
288pub struct HttpTimeout {
289 pub dur: Duration,
290 pub low_speed_limit: u32,
291}
292
293impl HttpTimeout {
294 pub fn new(gctx: &GlobalContext) -> CargoResult<HttpTimeout> {
295 let http_config = gctx.http_config()?;
296 let low_speed_limit = http_config.low_speed_limit.unwrap_or(10);
297 let seconds = http_config
298 .timeout
299 .or_else(|| {
300 gctx.get_env("HTTP_TIMEOUT")
301 .ok()
302 .and_then(|s| s.parse().ok())
303 })
304 .unwrap_or(30);
305 Ok(HttpTimeout {
306 dur: Duration::new(seconds, 0),
307 low_speed_limit,
308 })
309 }
310
311 pub fn configure(&self, handle: &mut Easy) -> CargoResult<()> {
312 handle.connect_timeout(self.dur)?;
318 handle.low_speed_time(self.dur)?;
319 handle.low_speed_limit(self.low_speed_limit)?;
320 Ok(())
321 }
322
323 pub fn configure2<T>(&self, handle: &mut Easy2<T>) -> Result<(), curl::Error> {
324 handle.connect_timeout(self.dur)?;
330 handle.low_speed_time(self.dur)?;
331 handle.low_speed_limit(self.low_speed_limit)?;
332 Ok(())
333 }
334}