140 lines · 5.9 KB · Rust
RawDownload
use git_transport::client::Capabilities; use crate::fetch::Ref; /// The result of the [`handshake()`][super::handshake()] function.#[derive(Default, Debug, Clone)]#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]pub struct Outcome {    /// The protocol version the server responded with. It might have downgraded the desired version.    pub server_protocol_version: git_transport::Protocol,    /// The references reported as part of the Protocol::V1 handshake, or `None` otherwise as V2 requires a separate request.    pub refs: Option<Vec<Ref>>,    /// The server capabilities.    pub capabilities: Capabilities,} mod error {    use git_transport::client;     use crate::{credentials, fetch::refs};     /// The error returned by [`handshake()`][crate::fetch::handshake()].    #[derive(Debug, thiserror::Error)]    #[allow(missing_docs)]    pub enum Error {        #[error(transparent)]        Credentials(#[from] credentials::protocol::Error),        #[error(transparent)]        Transport(#[from] client::Error),        #[error("The transport didn't accept the advertised server version {actual_version:?} and closed the connection client side")]        TransportProtocolPolicyViolation { actual_version: git_transport::Protocol },        #[error(transparent)]        ParseRefs(#[from] refs::parse::Error),    }}pub use error::Error; pub(crate) mod function {    use git_features::{progress, progress::Progress};    use git_transport::{client, client::SetServiceResponse, Service};    use maybe_async::maybe_async;     use super::{Error, Outcome};    use crate::{credentials, fetch::refs};     /// Perform a handshake with the server on the other side of `transport`, with `authenticate` being used if authentication    /// turns out to be required. `extra_parameters` are the parameters `(name, optional value)` to add to the handshake,    /// each time it is performed in case authentication is required.    /// `progress` is used to inform about what's currently happening.    #[maybe_async]    pub async fn handshake<AuthFn, T>(        mut transport: T,        mut authenticate: AuthFn,        extra_parameters: Vec<(String, Option<String>)>,        progress: &mut impl Progress,    ) -> Result<Outcome, Error>    where        AuthFn: FnMut(credentials::helper::Action) -> credentials::protocol::Result,        T: client::Transport,    {        let (server_protocol_version, refs, capabilities) = {            progress.init(None, progress::steps());            progress.set_name("handshake");            progress.step();             let extra_parameters: Vec<_> = extra_parameters                .iter()                .map(|(k, v)| (k.as_str(), v.as_ref().map(|s| s.as_str())))                .collect();            let supported_versions: Vec<_> = transport.supported_protocol_versions().into();             let result = transport.handshake(Service::UploadPack, &extra_parameters).await;            let SetServiceResponse {                actual_protocol,                capabilities,                refs,            } = match result {                Ok(v) => Ok(v),                Err(client::Error::Io { ref err }) if err.kind() == std::io::ErrorKind::PermissionDenied => {                    drop(result); // needed to workaround this: https://github.com/rust-lang/rust/issues/76149                    let url = transport.to_url();                    progress.set_name("authentication");                    let credentials::protocol::Outcome { identity, next } =                        authenticate(credentials::helper::Action::get_for_url(url))?                            .expect("FILL provides an identity or errors");                    transport.set_identity(identity)?;                    progress.step();                    progress.set_name("handshake (authenticated)");                    match transport.handshake(Service::UploadPack, &extra_parameters).await {                        Ok(v) => {                            authenticate(next.store())?;                            Ok(v)                        }                        // Still no permission? Reject the credentials.                        Err(client::Error::Io { err }) if err.kind() == std::io::ErrorKind::PermissionDenied => {                            authenticate(next.erase())?;                            Err(client::Error::Io { err })                        }                        // Otherwise, do nothing, as we don't know if it actually got to try the credentials.                        // If they were previously stored, they remain. In the worst case, the user has to enter them again                        // next time they try.                        Err(err) => Err(err),                    }                }                Err(err) => Err(err),            }?;             if !supported_versions.is_empty() && !supported_versions.contains(&actual_protocol) {                return Err(Error::TransportProtocolPolicyViolation {                    actual_version: actual_protocol,                });            }             let parsed_refs = match refs {                Some(mut refs) => {                    assert_eq!(                        actual_protocol,                        git_transport::Protocol::V1,                        "Only V1 auto-responds with refs"                    );                    Some(                        refs::from_v1_refs_received_as_part_of_handshake_and_capabilities(                            &mut refs,                            capabilities.iter(),                        )                        .await?,                    )                }                None => None,            };            (actual_protocol, parsed_refs, capabilities)        }; // this scope is needed, see https://github.com/rust-lang/rust/issues/76149         Ok(Outcome {            server_protocol_version,            refs,            capabilities,        })    }}