gix_transport/client/blocking_io/http/curl/
mod.rs1use std::{
2 sync::mpsc::{Receiver, SyncSender},
3 thread,
4};
5
6use gix_features::io;
7
8use crate::client::blocking_io::http::{self, traits::PostBodyDataKind};
9
10mod remote;
11
12#[derive(Default)]
14pub struct Options {
15 pub schannel_check_revoke: Option<bool>,
19}
20
21#[derive(Debug, thiserror::Error)]
25#[allow(missing_docs)]
26pub enum Error {
27 #[error(transparent)]
28 Curl(#[from] curl::Error),
29 #[error(transparent)]
30 Redirect(#[from] http::redirect::Error),
31 #[error("Could not finish reading all data to post to the remote")]
32 ReadPostBody(#[from] std::io::Error),
33 #[error(transparent)]
34 Authenticate(#[from] gix_credentials::protocol::Error),
35}
36
37impl crate::IsSpuriousError for Error {
38 fn is_spurious(&self) -> bool {
39 match self {
40 Error::Curl(err) => curl_is_spurious(err),
41 _ => false,
42 }
43 }
44}
45
46pub(crate) fn curl_is_spurious(err: &curl::Error) -> bool {
47 err.is_couldnt_connect()
48 || err.is_couldnt_resolve_proxy()
49 || err.is_couldnt_resolve_host()
50 || err.is_operation_timedout()
51 || err.is_recv_error()
52 || err.is_send_error()
53 || err.is_http2_error()
54 || err.is_http2_stream_error()
55 || err.is_ssl_connect_error()
56 || err.is_partial_file()
57}
58
59pub struct Curl {
61 req: SyncSender<remote::Request>,
62 res: Receiver<remote::Response>,
63 handle: Option<thread::JoinHandle<Result<(), Error>>>,
64 config: http::Options,
65}
66
67impl Curl {
68 fn restore_thread_after_failure(&mut self) -> http::Error {
69 let err_that_brought_thread_down = self
70 .handle
71 .take()
72 .expect("thread handle present")
73 .join()
74 .expect("handler thread should never panic")
75 .expect_err("something should have gone wrong with curl (we join on error only)");
76 let (handle, req, res) = remote::new();
77 self.handle = Some(handle);
78 self.req = req;
79 self.res = res;
80 err_that_brought_thread_down.into()
81 }
82
83 fn make_request(
84 &mut self,
85 url: &str,
86 base_url: &str,
87 headers: impl IntoIterator<Item = impl AsRef<str>>,
88 upload_body_kind: Option<PostBodyDataKind>,
89 ) -> Result<http::PostResponse<io::pipe::Reader, io::pipe::Reader, io::pipe::Writer>, http::Error> {
90 let mut list = curl::easy::List::new();
91 for header in headers {
92 list.append(header.as_ref())?;
93 }
94 if self
95 .req
96 .send(remote::Request {
97 url: url.to_owned(),
98 base_url: base_url.to_owned(),
99 headers: list,
100 upload_body_kind,
101 config: self.config.clone(),
102 })
103 .is_err()
104 {
105 return Err(self.restore_thread_after_failure());
106 }
107 let remote::Response {
108 headers,
109 body,
110 upload_body,
111 } = match self.res.recv() {
112 Ok(res) => res,
113 Err(_) => return Err(self.restore_thread_after_failure()),
114 };
115 Ok(http::PostResponse {
116 post_body: upload_body,
117 headers,
118 body,
119 })
120 }
121}
122
123impl Default for Curl {
124 fn default() -> Self {
125 let (handle, req, res) = remote::new();
126 Curl {
127 handle: Some(handle),
128 req,
129 res,
130 config: http::Options::default(),
131 }
132 }
133}
134
135#[allow(clippy::type_complexity)]
136impl http::Http for Curl {
137 type Headers = io::pipe::Reader;
138 type ResponseBody = io::pipe::Reader;
139 type PostBody = io::pipe::Writer;
140
141 fn get(
142 &mut self,
143 url: &str,
144 base_url: &str,
145 headers: impl IntoIterator<Item = impl AsRef<str>>,
146 ) -> Result<http::GetResponse<Self::Headers, Self::ResponseBody>, http::Error> {
147 self.make_request(url, base_url, headers, None).map(Into::into)
148 }
149
150 fn post(
151 &mut self,
152 url: &str,
153 base_url: &str,
154 headers: impl IntoIterator<Item = impl AsRef<str>>,
155 body: PostBodyDataKind,
156 ) -> Result<http::PostResponse<Self::Headers, Self::ResponseBody, Self::PostBody>, http::Error> {
157 self.make_request(url, base_url, headers, Some(body))
158 }
159
160 fn configure(
161 &mut self,
162 config: &dyn std::any::Any,
163 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
164 if let Some(config) = config.downcast_ref::<http::Options>() {
165 self.config = config.clone();
166 }
167 Ok(())
168 }
169}