use std::{ffi::OsStr, io::ErrorKind};
use bstr::{BString, ByteSlice, ByteVec};
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_as_ssh_arg = match url.user() {
Some(user) => {
let host = url.host().expect("present in ssh urls");
format!("{user}@{host}")
}
None => {
let host = url
.host_argument_safe()
.ok_or_else(|| ssh::invocation::Error::AmbiguousHostName {
host: url.host().expect("ssh host always set").into(),
})?;
host.into()
}
};
Ok(prepare.arg(host_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
}
}
}
}
}