Sebastian Thielmake fmt511ed003 years agoHistory
169 lines · 4.9 KB · Rust
RawDownload
use std::{    sync::mpsc::{Receiver, SyncSender},    thread,}; use git_features::io; use crate::client::{blocking_io::http, http::traits::PostBodyDataKind}; mod remote; /// Options to configure the `curl` HTTP handler.#[derive(Default)]pub struct Options {    /// If `true` and runtime configuration is possible for `curl` backends, certificates revocation will be checked.    ///    /// This only works on windows apparently. Ignored if `None`.    pub schannel_check_revoke: Option<bool>,} /// The error returned by the 'remote' helper, a purely internal construct to perform http requests.////// It can be used for downcasting errors, which are boxed to hide the actual implementation.#[derive(Debug, thiserror::Error)]#[allow(missing_docs)]pub enum Error {    #[error(transparent)]    Curl(#[from] curl::Error),    #[error(transparent)]    Redirect(#[from] http::redirect::Error),    #[error("Could not finish reading all data to post to the remote")]    ReadPostBody(#[from] std::io::Error),    #[error(transparent)]    Authenticate(#[from] git_credentials::protocol::Error),} impl crate::IsSpuriousError for Error {    fn is_spurious(&self) -> bool {        match self {            Error::Curl(err) => curl_is_spurious(err),            _ => false,        }    }} pub(crate) fn curl_is_spurious(err: &curl::Error) -> bool {    err.is_couldnt_connect()        || err.is_couldnt_resolve_proxy()        || err.is_couldnt_resolve_host()        || err.is_operation_timedout()        || err.is_recv_error()        || err.is_send_error()        || err.is_http2_error()        || err.is_http2_stream_error()        || err.is_ssl_connect_error()        || err.is_partial_file()} /// A utility to abstract interactions with curl handles.pub struct Curl {    req: SyncSender<remote::Request>,    res: Receiver<remote::Response>,    handle: Option<thread::JoinHandle<Result<(), Error>>>,    config: http::Options,} impl Curl {    fn restore_thread_after_failure(&mut self) -> http::Error {        let err_that_brought_thread_down = self            .handle            .take()            .expect("thread handle present")            .join()            .expect("handler thread should never panic")            .expect_err("something should have gone wrong with curl (we join on error only)");        let (handle, req, res) = remote::new();        self.handle = Some(handle);        self.req = req;        self.res = res;        err_that_brought_thread_down.into()    }     fn make_request(        &mut self,        url: &str,        base_url: &str,        headers: impl IntoIterator<Item = impl AsRef<str>>,        upload_body_kind: Option<PostBodyDataKind>,    ) -> Result<http::PostResponse<io::pipe::Reader, io::pipe::Reader, io::pipe::Writer>, http::Error> {        let mut list = curl::easy::List::new();        for header in headers {            list.append(header.as_ref())?;        }        if self            .req            .send(remote::Request {                url: url.to_owned(),                base_url: base_url.to_owned(),                headers: list,                upload_body_kind,                config: self.config.clone(),            })            .is_err()        {            return Err(self.restore_thread_after_failure());        }        let remote::Response {            headers,            body,            upload_body,        } = match self.res.recv() {            Ok(res) => res,            Err(_) => return Err(self.restore_thread_after_failure()),        };        Ok(http::PostResponse {            post_body: upload_body,            headers,            body,        })    }} impl Default for Curl {    fn default() -> Self {        let (handle, req, res) = remote::new();        Curl {            handle: Some(handle),            req,            res,            config: http::Options::default(),        }    }} #[allow(clippy::type_complexity)]impl http::Http for Curl {    type Headers = io::pipe::Reader;    type ResponseBody = io::pipe::Reader;    type PostBody = io::pipe::Writer;     fn get(        &mut self,        url: &str,        base_url: &str,        headers: impl IntoIterator<Item = impl AsRef<str>>,    ) -> Result<http::GetResponse<Self::Headers, Self::ResponseBody>, http::Error> {        self.make_request(url, base_url, headers, None).map(Into::into)    }     fn post(        &mut self,        url: &str,        base_url: &str,        headers: impl IntoIterator<Item = impl AsRef<str>>,        body: PostBodyDataKind,    ) -> Result<http::PostResponse<Self::Headers, Self::ResponseBody, Self::PostBody>, http::Error> {        self.make_request(url, base_url, headers, Some(body))    }     fn configure(        &mut self,        config: &dyn std::any::Any,    ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {        if let Some(config) = config.downcast_ref::<http::Options>() {            self.config = config.clone();        }        Ok(())    }}