Skip to content

Commit b0b4b21

Browse files
committed
Apply more suggestions.
Signed-off-by: Aalekh Patel <[email protected]>
1 parent a5c712a commit b0b4b21

File tree

4 files changed

+98
-40
lines changed

4 files changed

+98
-40
lines changed

src/command.rs

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,23 @@ macro_rules! delegate {
5252
}
5353

5454
/// If a command is `OverSsh` then it can be executed over an SSH session.
55+
///
5556
/// Primarily a way to allow `std::process::Command` to be turned directly into an `openssh::Command`.
5657
pub trait OverSsh {
57-
/// Given a session, return a command that can be executed over that session.
58+
/// Given an ssh session, return a command that can be executed over that ssh session.
5859
///
59-
/// **Note**: The command to be executed on the remote machine does not include
60-
/// any environment variables or the current directory. They must be
61-
/// set explicitly, if desired.
62-
///
63-
/// # Example
64-
/// Consider the implementation of `OverSsh` for `std::process::Command`:
60+
/// ### Notes
61+
///
62+
/// The command to be executed on the remote machine should not explicitly
63+
/// set environment variables or the current working directory. It errors if the source command
64+
/// has environment variables or a current working directory set, since `openssh` doesn't (yet) have
65+
/// a method to set environment variables and `ssh` doesn't support setting a current working directory
66+
/// outside of `bash/dash/zsh` (which is not always available).
67+
///
68+
/// ### Examples
69+
///
70+
/// 1. Consider the implementation of `OverSsh` for `std::process::Command`. Let's build a
71+
/// `ls -l -a -h` command and execute it over an SSH session.
6572
///
6673
/// ```no_run
6774
/// # #[tokio::main(flavor = "current_thread")]
@@ -75,7 +82,7 @@ pub trait OverSsh {
7582
/// .arg("-l")
7683
/// .arg("-a")
7784
/// .arg("-h")
78-
/// .over_session(&session)
85+
/// .over_ssh(&session)?
7986
/// .output()
8087
/// .await?;
8188
///
@@ -84,61 +91,75 @@ pub trait OverSsh {
8491
/// }
8592
///
8693
/// ```
87-
fn over_session<'session>(&self, session: &'session Session) -> crate::Command<'session>;
94+
/// 2. Building a command with environment variables or a current working directory set will
95+
/// results in an error.
96+
///
97+
/// ```no_run
98+
/// # #[tokio::main(flavor = "current_thread")]
99+
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
100+
/// use std::process::Command;
101+
/// use openssh::{Session, KnownHosts, OverSsh};
102+
///
103+
/// let session = Session::connect_mux("[email protected]", KnownHosts::Strict).await?;
104+
/// let echo =
105+
/// Command::new("echo")
106+
/// .arg("$MY_ENV_VAR")
107+
/// .over_ssh(&session);
108+
/// assert_matches!(echo, Err(openssh::Error::CommandHasEnv));
109+
///
110+
/// # Ok(())
111+
/// }
112+
///
113+
/// ```
114+
fn over_ssh<'session>(&self, session: &'session Session) -> Result<crate::Command<'session>, crate::Error>;
88115
}
89116

90117
impl OverSsh for std::process::Command {
91-
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
118+
fn over_ssh<'session>(&self, session: &'session Session) -> Result<Command<'session>, crate::Error> {
119+
120+
// I'd really like `!self.get_envs().is_empty()` here, but that's
121+
// behind a `exact_size_is_empty` feature flag.
122+
if self.get_envs().len() > 0 {
123+
return Err(crate::Error::CommandHasEnv);
124+
}
125+
126+
if self.get_current_dir().is_some() {
127+
return Err(crate::Error::CommandHasCwd);
128+
}
129+
92130
let program_escaped: Cow<'_, OsStr> = escape(self.get_program());
93131
let mut command = session.raw_command(program_escaped);
94132

95133
let args = self.get_args().map(escape);
96134
command.raw_args(args);
97-
command
135+
Ok(command)
98136
}
99137
}
100138

101139
impl OverSsh for tokio::process::Command {
102-
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
103-
self.as_std().over_session(session)
140+
fn over_ssh<'session>(&self, session: &'session Session) -> Result<Command<'session>, crate::Error> {
141+
self.as_std().over_ssh(session)
104142
}
105143
}
106144

107145
impl<S> OverSsh for &S
108146
where
109147
S: OverSsh,
110148
{
111-
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
112-
<S as OverSsh>::over_session(self, session)
113-
}
114-
}
115-
116-
impl<S> OverSsh for Box<S>
117-
where
118-
S: OverSsh,
119-
{
120-
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
121-
<S as OverSsh>::over_session(self, session)
149+
fn over_ssh<'session>(&self, session: &'session Session) -> Result<Command<'session>, crate::Error> {
150+
<S as OverSsh>::over_ssh(self, session)
122151
}
123152
}
124153

125-
impl<S> OverSsh for std::rc::Rc<S>
154+
impl<S> OverSsh for &mut S
126155
where
127156
S: OverSsh,
128157
{
129-
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
130-
<S as OverSsh>::over_session(self, session)
158+
fn over_ssh<'session>(&self, session: &'session Session) -> Result<Command<'session>, crate::Error> {
159+
<S as OverSsh>::over_ssh(self, session)
131160
}
132161
}
133162

134-
impl<S> OverSsh for std::sync::Arc<S>
135-
where
136-
S: OverSsh,
137-
{
138-
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
139-
<S as OverSsh>::over_session(self, session)
140-
}
141-
}
142163

143164
/// A remote process builder, providing fine-grained control over how a new remote process should
144165
/// be spawned.

src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ pub enum Error {
6767
/// IO Error when creating/reading/writing from ChildStdin, ChildStdout, ChildStderr.
6868
#[error("failure while accessing standard i/o of remote process")]
6969
ChildIo(#[source] io::Error),
70+
71+
/// The command has some env variables that it expects to carry over ssh.
72+
/// However, OverSsh does not support passing env variables over ssh.
73+
#[error("rejected runing a command over ssh that expects env variables to be carried over to remote.")]
74+
CommandHasEnv,
75+
76+
/// The command expects to be in a specific working directory in remote.
77+
/// However, OverSsh does not support setting a working directory for commands to be executed over ssh.
78+
#[error("rejected runing a command over ssh that expects a specific working directory to be carried over to remote.")]
79+
CommandHasCwd,
7080
}
7181

7282
#[cfg(feature = "native-mux")]

src/escape.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::{
1111
os::unix::ffi::OsStringExt,
1212
};
1313

14-
fn whitelisted(byte: u8) -> bool {
14+
fn allowed(byte: u8) -> bool {
1515
matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'=' | b'/' | b',' | b'.' | b'+')
1616
}
1717

@@ -25,9 +25,9 @@ fn whitelisted(byte: u8) -> bool {
2525
///
2626
pub(crate) fn escape(s: &OsStr) -> Cow<'_, OsStr> {
2727
let as_bytes = s.as_bytes();
28-
let all_whitelisted = as_bytes.iter().copied().all(whitelisted);
28+
let all_allowed = as_bytes.iter().copied().all(allowed);
2929

30-
if !as_bytes.is_empty() && all_whitelisted {
30+
if !as_bytes.is_empty() && all_allowed {
3131
return Cow::Borrowed(s);
3232
}
3333

@@ -81,6 +81,7 @@ mod tests {
8181
test_escape_case(r#"'!\$`\\\n "#, r#"''\'''\!'\$`\\\n '"#);
8282
test_escape_case("", r#"''"#);
8383
test_escape_case(" ", r#"' '"#);
84+
test_escape_case("*", r#"'*'"#);
8485

8586
test_escape_from_bytes(
8687
&[0x66, 0x6f, 0x80, 0x6f],

tests/openssh.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,11 @@ async fn stdout() {
294294

295295
#[tokio::test]
296296
#[cfg_attr(not(ci), ignore)]
297-
async fn over_session() {
297+
async fn over_session_ok() {
298298
for session in connects().await {
299299
let mut command = std::process::Command::new("echo")
300300
.arg("foo")
301-
.over_session(&session);
301+
.over_ssh(&session).expect("No env vars or current working dir is set.");
302302

303303
let child = command.output().await.unwrap();
304304
assert_eq!(child.stdout, b"foo\n");
@@ -317,6 +317,32 @@ async fn over_session() {
317317
}
318318
}
319319

320+
/// Test that `over_ssh` errors if the source command has env vars specified.
321+
#[tokio::test]
322+
#[cfg_attr(not(ci), ignore)]
323+
async fn over_session_err_because_env_var() {
324+
for session in connects().await {
325+
let command_with_env = std::process::Command::new("printenv")
326+
.arg("MY_ENV_VAR")
327+
.env("MY_ENV_VAR", "foo")
328+
.over_ssh(&session);
329+
assert!(matches!(command_with_env, Err(openssh::Error::CommandHasEnv)));
330+
}
331+
}
332+
333+
/// Test that `over_ssh` errors if the source command has a `current_dir` specified.
334+
#[tokio::test]
335+
#[cfg_attr(not(ci), ignore)]
336+
async fn over_session_err_because_cwd() {
337+
for session in connects().await {
338+
let command_with_current_dir = std::process::Command::new("echo")
339+
.arg("foo")
340+
.current_dir("/tmp")
341+
.over_ssh(&session);
342+
assert!(matches!(command_with_current_dir, Err(openssh::Error::CommandHasCwd)));
343+
}
344+
}
345+
320346
#[tokio::test]
321347
#[cfg_attr(not(ci), ignore)]
322348
async fn shell() {

0 commit comments

Comments
 (0)