1 use std
::process
::Stdio
;
3 use crate::{client::blocking_io, Protocol}
;
5 /// The error used in [`connect()`].
6 #[derive(Debug, thiserror::Error)]
9 #[error("The scheme in \"{}\" is not usable for an ssh connection", .0.to_bstring())]
10 UnsupportedScheme(gix_url
::Url
),
11 #[error("Host name '{host}' could be mistaken for a command-line argument")]
12 AmbiguousHostName { host: String }
,
15 impl crate::IsSpuriousError
for Error {}
17 /// The kind of SSH programs we have built-in support for.
19 /// Various different programs exists with different capabilities, and we have a few built in.
20 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
21 pub enum ProgramKind
{
22 /// The standard linux ssh program
24 /// The `(plink|putty).exe` binaries, typically only on windows.
26 /// The `putty.exe` binary, typically only on windows.
28 /// The `tortoiseplink.exe` binary, only on windows.
30 /// A minimal ssh client that supports on options.
38 use std
::ffi
::OsString
;
40 /// The error returned when producing ssh invocation arguments based on a selected invocation kind.
41 #[derive(Debug, thiserror::Error)]
42 #[allow(missing_docs)]
44 #[error("Host name '{host}' could be mistaken for a command-line argument")]
45 AmbiguousHostName { host: String }
,
46 #[error("The 'Simple' ssh variant doesn't support {function}")]
48 /// The simple command that should have been invoked.
50 /// The function that was unsupported
51 function
: &'
static str,
58 use std
::ffi
::{OsStr, OsString}
;
60 use crate::client
::ssh
::ProgramKind
;
62 /// The options for use when [connecting][super::connect()] via the `ssh` protocol.
63 #[derive(Debug, Clone, Default)]
65 /// The program or script to use.
66 /// If unset, it defaults to `ssh` or `ssh.exe`, or the program implied by `kind` if that one is set.
67 pub command
: Option
<OsString
>,
68 /// If `true`, a shell must not be used to execute `command`.
69 /// This defaults to `false`, and a shell can then be used if `command` seems to require it, but won't be
70 /// used unnecessarily.
71 pub disallow_shell
: bool
,
72 /// The ssh variant further identifying `program`. This determines which arguments will be used
73 /// when invoking the program.
74 /// If unset, the `program` basename determines the variant, or an invocation of the `command` itself.
75 pub kind
: Option
<ProgramKind
>,
79 /// Return the configured ssh command, defaulting to `ssh` if neither the `command` nor the `kind` fields are set.
80 pub fn ssh_command(&self) -> &OsStr
{
83 .or_else(|| self.kind
.and_then(|kind
| kind
.exe()))
84 .unwrap_or_else(|| OsStr
::new("ssh"))
89 /// Connect to `host` using the ssh program to obtain data from the repository at `path` on the remote.
91 /// The optional `user` identifies the user's account to which to connect, while `port` allows to specify non-standard
94 /// The `desired_version` is the preferred protocol version when establishing the connection, but note that it can be
95 /// downgraded by servers not supporting it.
96 /// If `trace` is `true`, all packetlines received or sent will be passed to the facilities of the `gix-trace` crate.
97 #[allow(clippy::result_large_err)]
100 desired_version
: Protocol
,
101 options
: connect
::Options
,
103 ) -> Result
<blocking_io
::file
::SpawnProcessOnDemand
, Error
> {
104 if url
.scheme
!= gix_url
::Scheme
::Ssh
|| url
.host().is_none() {
105 return Err(Error
::UnsupportedScheme(url
));
107 let ssh_cmd
= options
.ssh_command();
108 let mut kind
= options
.kind
.unwrap_or_else(|| ProgramKind
::from(ssh_cmd
));
109 if options
.kind
.is_none() && kind
== ProgramKind
::Simple
{
110 let mut cmd
= std
::process
::Command
::from(
111 gix_command
::prepare(ssh_cmd
)
112 .stderr(Stdio
::null())
113 .stdout(Stdio
::null())
114 .stdin(Stdio
::null())
117 .arg(url
.host_argument_safe().ok_or_else(|| Error
::AmbiguousHostName
{
118 host
: url
.host().expect("set in ssh urls").into(),
121 gix_features
::trace
::debug
!(cmd
= ?cmd
, "invoking `ssh` for feature check");
122 kind
= if cmd
.status().ok().map_or(false, |status
| status
.success()) {
129 let path
= gix_url
::expand_path
::for_shell(url
.path
.clone());
130 Ok(blocking_io
::file
::SpawnProcessOnDemand
::new_ssh(
135 options
.disallow_shell
,