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 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 self.timeout.configure2(handle)?;
230
231 crate::try_old_curl_http2_pipewait!(self.multiplexing, handle);
233 Ok(())
234 }
235}
236
237pub(crate) fn debug(kind: InfoType, data: &[u8]) {
238 enum LogLevel {
239 Debug,
240 Trace,
241 }
242 use LogLevel::*;
243 let (prefix, level) = match kind {
244 InfoType::Text => ("*", Debug),
245 InfoType::HeaderIn => ("<", Debug),
246 InfoType::HeaderOut => (">", Debug),
247 InfoType::DataIn => ("{", Trace),
248 InfoType::DataOut => ("}", Trace),
249 InfoType::SslDataIn | InfoType::SslDataOut => return,
250 _ => return,
251 };
252 let starts_with_ignore_case = |line: &str, text: &str| -> bool {
253 let line = line.as_bytes();
254 let text = text.as_bytes();
255 line[..line.len().min(text.len())].eq_ignore_ascii_case(text)
256 };
257 match str::from_utf8(data) {
258 Ok(s) => {
259 for mut line in s.lines() {
260 if starts_with_ignore_case(line, "authorization:") {
261 line = "Authorization: [REDACTED]";
262 } else if starts_with_ignore_case(line, "h2h3 [authorization:") {
263 line = "h2h3 [Authorization: [REDACTED]]";
264 } else if starts_with_ignore_case(line, "set-cookie") {
265 line = "set-cookie: [REDACTED]";
266 }
267 match level {
268 Debug => debug!(target: "network", "http-debug: {prefix} {line}"),
269 Trace => trace!(target: "network", "http-debug: {prefix} {line}"),
270 }
271 }
272 }
273 Err(_) => {
274 let len = data.len();
275 match level {
276 Debug => {
277 debug!(target: "network", "http-debug: {prefix} ({len} bytes of data)")
278 }
279 Trace => {
280 trace!(target: "network", "http-debug: {prefix} ({len} bytes of data)")
281 }
282 }
283 }
284 }
285}
286
287#[must_use]
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}