use std::{ffi::OsStr, io::ErrorKind};
use bstr::{BString, ByteSlice, ByteVec};
use gix_url::ArgumentSafety::*;
use crate::{
client::{ssh, ssh::ProgramKind},
Protocol,
};
impl ProgramKind {
pub fn exe(&self) -> Option<&'static OsStr> {
Some(OsStr::new(match self {
ProgramKind::Ssh => "ssh",
ProgramKind::Plink => "plink",
ProgramKind::Putty => "putty",
ProgramKind::TortoisePlink => "tortoiseplink.exe",
ProgramKind::Simple => return None,
}))
}
pub(crate) fn prepare_invocation(
&self,
ssh_cmd: &OsStr,
url: &gix_url::Url,
desired_version: Protocol,
disallow_shell: bool,
) -> Result<gix_command::Prepare, ssh::invocation::Error> {
let mut prepare = gix_command::prepare(ssh_cmd).with_shell();
if disallow_shell {
prepare.use_shell = false;
}
match self {
ProgramKind::Ssh => {
if desired_version != Protocol::V1 {
prepare = prepare
.args(["-o", "SendEnv=GIT_PROTOCOL"])
.env("GIT_PROTOCOL", format!("version={}", desired_version as usize))
}
if let Some(port) = url.port {
prepare = prepare.arg(format!("-p{port}"));
}
}
ProgramKind::Plink | ProgramKind::Putty | ProgramKind::TortoisePlink => {
if *self == ProgramKind::TortoisePlink {
prepare = prepare.arg("-batch");
}
if let Some(port) = url.port {
prepare = prepare.arg("-P");
prepare = prepare.arg(port.to_string());
}
}
ProgramKind::Simple => {
if url.port.is_some() {
return Err(ssh::invocation::Error::Unsupported {
command: ssh_cmd.into(),
function: "setting the port",
});
}
}
};
let host_maybe_with_user_as_ssh_arg = match (url.user_as_argument(), url.host_as_argument()) {
(Usable(user), Usable(host)) => format!("{user}@{host}"),
(Usable(user), Dangerous(host)) => format!("{user}@{host}"), (Absent, Usable(host)) => host.into(),
(Dangerous(user), _) => Err(ssh::invocation::Error::AmbiguousUserName { user: user.into() })?,
(_, Dangerous(host)) => Err(ssh::invocation::Error::AmbiguousHostName { host: host.into() })?,
(_, Absent) => panic!("BUG: host should always be present in SSH URLs"),
};
Ok(prepare
.arg(host_maybe_with_user_as_ssh_arg)
.env("LANG", "C")
.env("LC_ALL", "C"))
}
pub(crate) fn line_to_err(&self, line: BString) -> Result<std::io::Error, BString> {
let kind = match self {
ProgramKind::Ssh | ProgramKind::Simple => {
if line.contains_str(b"Permission denied") || line.contains_str(b"permission denied") {
Some(ErrorKind::PermissionDenied)
} else if line.contains_str(b"resolve hostname") {
Some(ErrorKind::ConnectionRefused)
} else if line.contains_str(b"connect to host")
|| line.contains_str("Connection to ")
|| line.contains_str("Connection closed by ")
{
Some(ErrorKind::NotFound)
} else {
None
}
}
ProgramKind::Plink | ProgramKind::Putty | ProgramKind::TortoisePlink => {
if line.contains_str(b"publickey") {
Some(ErrorKind::PermissionDenied)
} else {
None
}
}
};
match kind {
Some(kind) => Ok(std::io::Error::new(kind, Vec::from(line).into_string_lossy())),
None => Err(line),
}
}
}
impl<'a> From<&'a OsStr> for ProgramKind {
fn from(v: &'a OsStr) -> Self {
let p = std::path::Path::new(v);
match p.file_stem().and_then(OsStr::to_str) {
None => ProgramKind::Simple,
Some(stem) => {
if stem.eq_ignore_ascii_case("ssh") {
ProgramKind::Ssh
} else if stem.eq_ignore_ascii_case("plink") {
ProgramKind::Plink
} else if stem.eq_ignore_ascii_case("putty") {
ProgramKind::Putty
} else if stem.eq_ignore_ascii_case("tortoiseplink") {
ProgramKind::TortoisePlink
} else {
ProgramKind::Simple
}
}
}
}
}