]>
Commit | Line | Data |
---|---|---|
0a29b90c FG |
1 | use gix_features::{progress, progress::Progress}; |
2 | use gix_transport::{client, client::SetServiceResponse, Service}; | |
3 | use maybe_async::maybe_async; | |
4 | ||
5 | use super::{Error, Outcome}; | |
6 | use crate::{credentials, handshake::refs}; | |
7 | ||
8 | /// Perform a handshake with the server on the other side of `transport`, with `authenticate` being used if authentication | |
9 | /// turns out to be required. `extra_parameters` are the parameters `(name, optional value)` to add to the handshake, | |
10 | /// each time it is performed in case authentication is required. | |
11 | /// `progress` is used to inform about what's currently happening. | |
12 | #[allow(clippy::result_large_err)] | |
13 | #[maybe_async] | |
14 | pub async fn handshake<AuthFn, T>( | |
15 | mut transport: T, | |
16 | service: Service, | |
17 | mut authenticate: AuthFn, | |
18 | extra_parameters: Vec<(String, Option<String>)>, | |
19 | progress: &mut impl Progress, | |
20 | ) -> Result<Outcome, Error> | |
21 | where | |
22 | AuthFn: FnMut(credentials::helper::Action) -> credentials::protocol::Result, | |
23 | T: client::Transport, | |
24 | { | |
4b012472 | 25 | let _span = gix_features::trace::detail!("gix_protocol::handshake()", service = ?service, extra_parameters = ?extra_parameters); |
0a29b90c FG |
26 | let (server_protocol_version, refs, capabilities) = { |
27 | progress.init(None, progress::steps()); | |
781aab86 | 28 | progress.set_name("handshake".into()); |
0a29b90c FG |
29 | progress.step(); |
30 | ||
31 | let extra_parameters: Vec<_> = extra_parameters | |
32 | .iter() | |
781aab86 | 33 | .map(|(k, v)| (k.as_str(), v.as_deref())) |
0a29b90c FG |
34 | .collect(); |
35 | let supported_versions: Vec<_> = transport.supported_protocol_versions().into(); | |
36 | ||
37 | let result = transport.handshake(service, &extra_parameters).await; | |
38 | let SetServiceResponse { | |
39 | actual_protocol, | |
40 | capabilities, | |
41 | refs, | |
42 | } = match result { | |
43 | Ok(v) => Ok(v), | |
44 | Err(client::Error::Io(ref err)) if err.kind() == std::io::ErrorKind::PermissionDenied => { | |
45 | drop(result); // needed to workaround this: https://github.com/rust-lang/rust/issues/76149 | |
46 | let url = transport.to_url().into_owned(); | |
781aab86 | 47 | progress.set_name("authentication".into()); |
0a29b90c FG |
48 | let credentials::protocol::Outcome { identity, next } = |
49 | authenticate(credentials::helper::Action::get_for_url(url.clone()))? | |
49aad941 | 50 | .ok_or(Error::EmptyCredentials)?; |
0a29b90c FG |
51 | transport.set_identity(identity)?; |
52 | progress.step(); | |
781aab86 | 53 | progress.set_name("handshake (authenticated)".into()); |
0a29b90c FG |
54 | match transport.handshake(service, &extra_parameters).await { |
55 | Ok(v) => { | |
56 | authenticate(next.store())?; | |
57 | Ok(v) | |
58 | } | |
59 | // Still no permission? Reject the credentials. | |
60 | Err(client::Error::Io(err)) if err.kind() == std::io::ErrorKind::PermissionDenied => { | |
61 | authenticate(next.erase())?; | |
62 | return Err(Error::InvalidCredentials { url, source: err }); | |
63 | } | |
64 | // Otherwise, do nothing, as we don't know if it actually got to try the credentials. | |
65 | // If they were previously stored, they remain. In the worst case, the user has to enter them again | |
66 | // next time they try. | |
67 | Err(err) => Err(err), | |
68 | } | |
69 | } | |
70 | Err(err) => Err(err), | |
71 | }?; | |
72 | ||
73 | if !supported_versions.is_empty() && !supported_versions.contains(&actual_protocol) { | |
74 | return Err(Error::TransportProtocolPolicyViolation { | |
75 | actual_version: actual_protocol, | |
76 | }); | |
77 | } | |
78 | ||
79 | let parsed_refs = match refs { | |
80 | Some(mut refs) => { | |
fe692bf9 FG |
81 | assert!( |
82 | matches!( | |
83 | actual_protocol, | |
84 | gix_transport::Protocol::V0 | gix_transport::Protocol::V1 | |
85 | ), | |
86 | "Only V(0|1) auto-responds with refs" | |
0a29b90c FG |
87 | ); |
88 | Some( | |
89 | refs::from_v1_refs_received_as_part_of_handshake_and_capabilities(&mut refs, capabilities.iter()) | |
90 | .await?, | |
91 | ) | |
92 | } | |
93 | None => None, | |
94 | }; | |
95 | (actual_protocol, parsed_refs, capabilities) | |
96 | }; // this scope is needed, see https://github.com/rust-lang/rust/issues/76149 | |
97 | ||
98 | Ok(Outcome { | |
99 | server_protocol_version, | |
100 | refs, | |
101 | capabilities, | |
102 | }) | |
103 | } |