//! Git exec channels over the shared SSH library, with host-mediated signing. use crate::{Result, repo::error}; use gix::bstr::ByteSlice; use std::io; fn parse(input: &str) -> io::Result { let url = gix::url::parse(input.as_bytes().as_bstr()) .map_err(|_| io::Error::other("invalid SSH URL"))?; if url.scheme != gix::url::Scheme::Ssh || url.host.as_deref().is_none_or(str::is_empty) || url.password.is_some() || url.path.is_empty() || url.path.iter().any(|b| matches!(b, 0 | b'\n' | b'\r')) { return Err(io::Error::other( "use ssh://[user@]host[:port]/path or [user@]host:path without a password", )); } Ok(url) } pub fn clean_url(input: &str) -> io::Result { if input.starts_with("http://") || input.starts_with("https://") { crate::http::clean_url(input).map(|url| url.to_string()) } else { parse(input)?; Ok(input.into()) } } pub fn is_ssh(input: &str) -> bool { !input.starts_with("http://") && !input.starts_with("https://") } pub type Connection = gix_transport::client::git::blocking_io::Connection; pub fn connection(input: &str, service: gix_transport::Service) -> Result { let url = parse(input).map_err(|e| error("SSH URL", e))?; let host = url.host.as_deref().unwrap(); let mut session = sausage_ssh::identity::connect(host, url.port.unwrap_or(22), url.user.as_deref(), false) .map_err(|e| error("SSH", e))?; let mut command = service.as_str().as_bytes().to_vec(); command.extend_from_slice(b" '"); for byte in url.path.iter() { if *byte == b'\'' { command.extend_from_slice(b"'\\''"); } else { command.push(*byte); } } command.push(b'\''); session .open_command(&command) .map_err(|e| error("SSH exec", e))?; let (read, write) = session.split(); Ok(Connection::new( read, write, gix_transport::Protocol::V1, url.path, None::<(&str, Option)>, gix_transport::client::git::ConnectMode::Process, false, ) .custom_url(Some(input.into()))) }