use std::io::{BufRead, BufReader, Read, Write};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use super::{ConnectError, ConnectRequest, Connection, Transport};
pub(crate) struct RemoteHelper {
pub(crate) program: PathBuf,
pub(crate) remote: String,
pub(crate) environment: Vec<(std::ffi::OsString, std::ffi::OsString)>,
}
impl Transport for RemoteHelper {
fn connect(&self, request: &ConnectRequest<'_>) -> Result<Box<dyn Connection>, ConnectError> {
let url = String::from_utf8_lossy(request.url()).into_owned();
let address = url
.split_once("::")
.map_or(url.as_str(), |(_, address)| address);
let mut child = Command::new(&self.program)
.args([self.remote.as_str(), address])
.env_clear()
.envs(self.environment.iter().map(|(name, value)| (name, value)))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.map_err(ConnectError::new)?;
let mut input = child.stdin.take().expect("piped");
let mut output = BufReader::new(child.stdout.take().expect("piped"));
let handshake = (|| {
input.write_all(b"capabilities\n")?;
input.flush()?;
let mut connect = false;
for line in lines(&mut output)? {
connect |= line.trim_start_matches('*') == "connect";
}
if !connect {
return Err(ConnectError::new(
"remote helper does not offer `connect`, the only helper protocol girt speaks",
));
}
writeln!(input, "connect {}", request.service().name())?;
input.flush()?;
match read_line(&mut output)?.as_str() {
"" => Ok(()),
"fallback" => Err(ConnectError::new(
"remote helper cannot connect to this service directly",
)),
_ => Err(ConnectError::new(
"unexpected remote helper reply to `connect`",
)),
}
})();
if let Err(error) = handshake {
let _ = child.kill();
let _ = child.wait();
return Err(error);
}
let buffered = output.buffer().to_vec();
let output = output.into_inner();
child.stdin = Some(input);
child.stdout = Some(output);
Ok(Box::new(Prefixed {
prefix: std::io::Cursor::new(buffered),
inner: super::ChildConnection::new(child),
}))
}
}
impl From<std::io::Error> for ConnectError {
fn from(error: std::io::Error) -> Self {
Self::new(error)
}
}
fn lines(reader: &mut impl BufRead) -> Result<Vec<String>, ConnectError> {
let mut lines = Vec::new();
loop {
let line = read_line(reader)?;
if line.is_empty() {
return Ok(lines);
}
if lines.len() >= 256 {
return Err(ConnectError::new("remote helper capability list too long"));
}
lines.push(line);
}
}
fn read_line(reader: &mut impl BufRead) -> Result<String, ConnectError> {
let mut line = Vec::new();
reader.take(4096).read_until(b'\n', &mut line)?;
if line.last() != Some(&b'\n') {
return Err(ConnectError::new("remote helper ended its reply early"));
}
line.pop();
String::from_utf8(line).map_err(|_| ConnectError::new("remote helper reply is not UTF-8"))
}
struct Prefixed {
prefix: std::io::Cursor<Vec<u8>>,
inner: super::ChildConnection,
}
impl Read for Prefixed {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
match self.prefix.read(buffer)? {
0 => self.inner.read(buffer),
read => Ok(read),
}
}
}
impl Write for Prefixed {
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
self.inner.write(buffer)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
impl Connection for Prefixed {
fn close_write(&mut self) -> std::io::Result<()> {
self.inner.close_write()
}
fn finish(self: Box<Self>) -> Result<(), ConnectError> {
Box::new(self.inner).finish()
}
}