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::context::ConfigRelativePathResolveExt;
19use crate::context::SslVersionConfig;
20use crate::context::SslVersionConfigRange;
21
22pub fn http_handle(gctx: &GlobalContext) -> CargoResult<Easy> {
24 let (mut handle, timeout) = http_handle_and_timeout(gctx)?;
25 timeout.configure(&mut handle)?;
26 Ok(handle)
27}
28
29pub fn http_handle_and_timeout(gctx: &GlobalContext) -> CargoResult<(Easy, HttpTimeout)> {
30 let mut handle = Easy::new();
35 let timeout = configure_http_handle(gctx, &mut handle)?;
36 Ok((handle, timeout))
37}
38
39pub fn needs_custom_http_transport(gctx: &GlobalContext) -> CargoResult<bool> {
44 Ok(super::proxy::http_proxy_exists(gctx.http_config()?, gctx)
45 || *gctx.http_config()? != Default::default()
46 || gctx.get_env_os("HTTP_TIMEOUT").is_some())
47}
48pub struct HandleConfiguration {
50 proxy: Option<String>,
51 cainfo: Option<PathBuf>,
52 proxy_cainfo: Option<String>,
53 ssl_options: Option<SslOpt>,
54 useragent: String,
55 ssl_version: Option<SslVersion>,
56 ssl_min_max_version: Option<(SslVersion, SslVersion)>,
57 pub timeout: HttpTimeout,
58 pub verbose: bool,
59 pub multiplexing: bool,
60}
61
62pub fn configure_http_handle(gctx: &GlobalContext, handle: &mut Easy) -> CargoResult<HttpTimeout> {
63 let configuration = HandleConfiguration::new(gctx)?;
64 configuration.configure(handle)?;
65 Ok(configuration.timeout)
66}
67
68impl HandleConfiguration {
69 pub fn new(gctx: &GlobalContext) -> CargoResult<Self> {
70 if let Some(offline_flag) = gctx.offline_flag() {
71 bail!(
72 "attempting to make an HTTP request, but {offline_flag} was \
73 specified"
74 )
75 }
76
77 let http = gctx.http_config()?;
78 let timeout = HttpTimeout::new(gctx)?;
79 let useragent = if let Some(user_agent) = http.user_agent.clone() {
80 user_agent
81 } else {
82 format!("cargo/{}", crate::version())
83 };
84 let multiplexing = http.multiplexing.unwrap_or(true);
85 let mut handle = HandleConfiguration {
86 proxy: None,
87 cainfo: None,
88 proxy_cainfo: None,
89 ssl_options: None,
90 useragent,
91 ssl_version: None,
92 ssl_min_max_version: None,
93 verbose: false,
94 timeout,
95 multiplexing,
96 };
97 if let Some(proxy) = super::proxy::http_proxy(http) {
98 handle.proxy = Some(proxy);
99 }
100 if let Some(cainfo) = &http.cainfo {
101 let cainfo = cainfo.resolve_path(gctx);
102 handle.cainfo = Some(cainfo);
103 }
104 if let Some(proxy_cainfo) = http.proxy_cainfo.as_ref().or(http.cainfo.as_ref()) {
106 let proxy_cainfo = proxy_cainfo.resolve_path(gctx);
107 handle.proxy_cainfo = Some(format!("{}", proxy_cainfo.display()));
108 }
109 if let Some(check) = http.check_revoke {
110 let mut v = SslOpt::new();
111 v.no_revoke(!check);
112 handle.ssl_options = Some(v);
113 }
114
115 fn to_ssl_version(s: &str) -> CargoResult<SslVersion> {
116 let version = match s {
117 "default" => SslVersion::Default,
118 "tlsv1" => SslVersion::Tlsv1,
119 "tlsv1.0" => SslVersion::Tlsv10,
120 "tlsv1.1" => SslVersion::Tlsv11,
121 "tlsv1.2" => SslVersion::Tlsv12,
122 "tlsv1.3" => SslVersion::Tlsv13,
123 _ => bail!(
124 "Invalid ssl version `{s}`,\
125 choose from 'default', 'tlsv1', 'tlsv1.0', 'tlsv1.1', 'tlsv1.2', 'tlsv1.3'."
126 ),
127 };
128 Ok(version)
129 }
130
131 if let Some(ssl_version) = &http.ssl_version {
132 match ssl_version {
133 SslVersionConfig::Single(s) => {
134 let version = to_ssl_version(s.as_str())?;
135 handle.ssl_version = Some(version);
136 }
137 SslVersionConfig::Range(SslVersionConfigRange { min, max }) => {
138 let min_version = min
139 .as_ref()
140 .map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
141 let max_version = max
142 .as_ref()
143 .map_or(Ok(SslVersion::Default), |s| to_ssl_version(s))?;
144 handle.ssl_min_max_version = Some((min_version, max_version));
145 }
146 }
147 } else if cfg!(windows) {
148 handle.ssl_min_max_version = Some((SslVersion::Default, SslVersion::Tlsv12));
165 }
166
167 if let Some(true) = http.debug {
168 handle.verbose = true;
169 }
170
171 Ok(handle)
172 }
173
174 pub fn configure(&self, handle: &mut Easy) -> Result<(), curl::Error> {
175 if let Some(v) = &self.proxy {
176 handle.proxy(&v)?;
177 }
178 if let Some(v) = &self.cainfo {
179 handle.cainfo(&v)?;
180 }
181 if let Some(v) = &self.proxy_cainfo {
182 handle.proxy_cainfo(&v)?;
183 }
184 if let Some(v) = &self.ssl_options {
185 handle.ssl_options(&v)?;
186 }
187 handle.useragent(&self.useragent)?;
188 handle.accept_encoding("")?;
190 if let Some(v) = &self.ssl_version {
191 handle.ssl_version(v.clone())?;
192 }
193 if let Some((min, max)) = &self.ssl_min_max_version {
194 handle.ssl_min_max_version(min.clone(), max.clone())?;
195 }
196 if self.verbose {
197 handle.verbose(true)?;
198 tracing::debug!(target: "network", "{:#?}", curl::Version::get());
199 handle.debug_function(debug)?;
200 }
201 Ok(())
202 }
203
204 pub fn configure2<T>(&self, handle: &mut Easy2<T>) -> Result<(), curl::Error> {
205 if let Some(v) = &self.proxy {
206 handle.proxy(&v)?;
207 }
208 if let Some(v) = &self.cainfo {
209 handle.cainfo(&v)?;
210 }
211 if let Some(v) = &self.proxy_cainfo {
212 handle.proxy_cainfo(&v)?;
213 }
214 if let Some(v) = &self.ssl_options {
215 handle.ssl_options(&v)?;
216 }
217 handle.useragent(&self.useragent)?;
218 handle.accept_encoding("")?;
220 if let Some(v) = &self.ssl_version {
221 handle.ssl_version(v.clone())?;
222 }
223 if let Some((min, max)) = &self.ssl_min_max_version {
224 handle.ssl_min_max_version(min.clone(), max.clone())?;
225 }
226 if self.verbose {
227 handle.verbose(true)?;
228 tracing::debug!(target: "network", "{:#?}", curl::Version::get());
229 }
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]
288#[derive(Clone)]
289pub struct HttpTimeout {
290 pub dur: Duration,
291 pub low_speed_limit: u32,
292}
293
294impl HttpTimeout {
295 pub fn new(gctx: &GlobalContext) -> CargoResult<HttpTimeout> {
296 let http_config = gctx.http_config()?;
297 let low_speed_limit = http_config.low_speed_limit.unwrap_or(10);
298 let seconds = http_config
299 .timeout
300 .or_else(|| {
301 gctx.get_env("HTTP_TIMEOUT")
302 .ok()
303 .and_then(|s| s.parse().ok())
304 })
305 .unwrap_or(30);
306 Ok(HttpTimeout {
307 dur: Duration::new(seconds, 0),
308 low_speed_limit,
309 })
310 }
311
312 pub fn configure(&self, handle: &mut Easy) -> CargoResult<()> {
313 handle.connect_timeout(self.dur)?;
319 handle.low_speed_time(self.dur)?;
320 handle.low_speed_limit(self.low_speed_limit)?;
321 Ok(())
322 }
323
324 pub fn configure2<T>(&self, handle: &mut Easy2<T>) -> Result<(), curl::Error> {
325 handle.connect_timeout(self.dur)?;
331 handle.low_speed_time(self.dur)?;
332 handle.low_speed_limit(self.low_speed_limit)?;
333 Ok(())
334 }
335}