Skip to content

Commit ba707c1

Browse files
committed
Apply more suggestions from the code review.
Signed-off-by: Aalekh Patel <aalekh.gwpeck.7998@icloud.com>
1 parent 2615ec1 commit ba707c1

File tree

3 files changed

+104
-47
lines changed

3 files changed

+104
-47
lines changed

src/command.rs

Lines changed: 20 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use crate::escape;
2+
13
use super::stdio::TryFromChildIo;
24
use super::RemoteChild;
35
use super::Stdio;
46
use super::{Error, Session};
57

68
use std::borrow::Cow;
79
use std::ffi::OsStr;
10+
use std::os::unix::prelude::OsStrExt;
811
use std::process;
912

1013
#[derive(Debug)]
@@ -57,15 +60,10 @@ pub trait OverSsh {
5760
/// **Note**: The command to be executed on the remote machine does not include
5861
/// any environment variables or the current directory. They must be
5962
/// set explicitly, if desired.
60-
fn over_session<'session>(&self, session: &'session Session) -> crate::Command<'session>;
61-
}
62-
63-
impl OverSsh for std::process::Command {
64-
/// Given a session, convert a `std::process::Command` into an `openssh::Command`
65-
/// that can be executed over that session.
66-
/// **Note**: The command to be executed on the remote machine does not include
67-
/// any environment variables or the current directory. They must be
68-
/// set explicitly, if desired.
63+
///
64+
/// # Example
65+
/// Consider the implementation of `OverSsh` for `std::process::Command`:
66+
///
6967
/// ```no_run
7068
/// # #[tokio::main(flavor = "current_thread")]
7169
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -87,42 +85,26 @@ impl OverSsh for std::process::Command {
8785
/// }
8886
///
8987
/// ```
88+
fn over_session<'session>(&self, session: &'session Session) -> crate::Command<'session>;
89+
}
90+
91+
impl OverSsh for std::process::Command {
9092
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
91-
let mut command = session.command(self.get_program().to_string_lossy());
92-
command.raw_args(self.get_args());
93+
let program_escaped = escape(self.get_program().as_bytes());
94+
let mut command = session.command(Cow::Borrowed(program_escaped.as_str()));
95+
96+
self
97+
.get_args()
98+
.for_each(|arg| {
99+
let arg_escaped = escape(arg.as_bytes());
100+
command.arg(Cow::Borrowed(arg_escaped.as_str()));
101+
});
93102
command
94103
}
95104
}
96105

97106

98107
impl OverSsh for tokio::process::Command {
99-
/// Given a session, convert a `tokio::process::Command` into an `openssh::Command`
100-
/// that can be executed over that session.
101-
///
102-
/// **Note**: The command to be executed on the remote machine does not include
103-
/// any environment variables or the current directory. They must be
104-
/// set explicitly, if desired.
105-
/// ```no_run
106-
/// # #[tokio::main(flavor = "current_thread")]
107-
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
108-
/// use tokio::process::Command;
109-
/// use openssh::{Session, KnownHosts, OverSsh};
110-
111-
/// let session = Session::connect_mux("me@ssh.example.com", KnownHosts::Strict).await?;
112-
/// let ls =
113-
/// Command::new("ls")
114-
/// .arg("-l")
115-
/// .arg("-a")
116-
/// .arg("-h")
117-
/// .over_session(&session)
118-
/// .output()
119-
/// .await?;
120-
///
121-
/// assert!(String::from_utf8(ls.stdout).unwrap().contains("total"));
122-
/// # Ok(())
123-
/// # }
124-
///
125-
/// ```
126108
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
127109
self.as_std().over_session(session)
128110
}
@@ -138,15 +120,6 @@ where
138120
}
139121
}
140122

141-
impl<S> OverSsh for &mut S
142-
where
143-
S: OverSsh
144-
{
145-
fn over_session<'session>(&self, session: &'session Session) -> Command<'session> {
146-
<S as OverSsh>::over_session(self, session)
147-
}
148-
}
149-
150123
impl<S> OverSsh for Box<S>
151124
where
152125
S: OverSsh

src/escape.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! Escape characters that may have special meaning in a shell, including spaces.
2+
//! This is a modified version of the [`shell-escape::unix`] module of [`shell-escape`] crate.
3+
//!
4+
//! [`shell-escape`]: https://crates.io/crates/shell-escape
5+
//! [`shell-escape::unix`]: https://docs.rs/shell-escape/latest/src/shell_escape/lib.rs.html#101
6+
7+
8+
fn whitelisted(ch: char) -> bool {
9+
match ch {
10+
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '=' | '/' | ',' | '.' | '+' => true,
11+
_ => false,
12+
}
13+
}
14+
15+
/// Escape characters that may have special meaning in a shell, including spaces.
16+
///
17+
/// **Note**: This function is an adaptation of [`shell-escape::unix::escape`].
18+
/// This function exists only for type compatibility and the implementation is
19+
/// almost exactly the same as [`shell-escape::unix::escape`].
20+
///
21+
/// [`shell-escape::unix::escape`]: https://docs.rs/shell-escape/latest/src/shell_escape/lib.rs.html#101
22+
///
23+
pub fn escape(s: &[u8]) -> String {
24+
let all_whitelisted = s.iter().all(|x| whitelisted(*x as char));
25+
26+
if !s.is_empty() && all_whitelisted {
27+
// All bytes are whitelisted and valid single-byte UTF-8,
28+
// so we can build the original string and return as is.
29+
return String::from_utf8(s.to_vec()).unwrap();
30+
}
31+
32+
let mut escaped = String::with_capacity(s.len() + 2);
33+
escaped.push('\'');
34+
35+
for &b in s {
36+
match b {
37+
b'\'' | b'!' => {
38+
escaped.push_str("'\\");
39+
escaped.push(b as char);
40+
escaped.push('\'');
41+
}
42+
_ => escaped.push(b as char),
43+
}
44+
}
45+
escaped.push('\'');
46+
escaped
47+
}
48+
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use super::*;
53+
54+
// These tests are courtesy of the `shell-escape` crate.
55+
#[test]
56+
fn test_escape() {
57+
assert_eq!(
58+
escape(b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"),
59+
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"
60+
);
61+
assert_eq!(
62+
escape(b"--aaa=bbb-ccc"),
63+
"--aaa=bbb-ccc"
64+
);
65+
assert_eq!(
66+
escape(b"linker=gcc -L/foo -Wl,bar"),
67+
r#"'linker=gcc -L/foo -Wl,bar'"#
68+
);
69+
assert_eq!(
70+
escape(br#"--features="default""#),
71+
r#"'--features="default"'"#
72+
);
73+
assert_eq!(
74+
escape(br#"'!\$`\\\n "#),
75+
r#"''\'''\!'\$`\\\n '"#
76+
);
77+
assert_eq!(escape(b""), r#"''"#);
78+
assert_eq!(escape(b" "), r#"' '"#);
79+
assert_eq!(escape(b"\xC4b"), r#"'Äb'"#);
80+
}
81+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ pub use builder::{KnownHosts, SessionBuilder};
169169
mod command;
170170
pub use command::{Command, OverSsh};
171171

172+
mod escape;
173+
pub use escape::escape;
174+
172175
mod child;
173176
pub use child::RemoteChild;
174177

0 commit comments

Comments
 (0)