Skip to content

Commit 69783f2

Browse files
committed
Refactor: Move Session into mod session
Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
1 parent d659d21 commit 69783f2

File tree

2 files changed

+244
-234
lines changed

2 files changed

+244
-234
lines changed

src/lib.rs

Lines changed: 3 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,12 @@
153153
#[cfg(not(unix))]
154154
compile_error!("This crate can only be used on unix");
155155

156-
use std::borrow::Cow;
157-
use std::ffi::OsStr;
158-
use std::path::Path;
159-
160156
mod stdio;
161157
pub use stdio::{ChildStderr, ChildStdin, ChildStdout, Stdio};
162158

159+
mod session;
160+
pub use session::Session;
161+
163162
mod builder;
164163
pub use builder::{KnownHosts, SessionBuilder};
165164

@@ -189,233 +188,3 @@ pub use port_forwarding::*;
189188
pub mod process {
190189
pub use super::{ChildStderr, ChildStdin, ChildStdout, Command, RemoteChild, Stdio};
191190
}
192-
193-
#[derive(Debug)]
194-
pub(crate) enum SessionImp {
195-
#[cfg(feature = "process-mux")]
196-
ProcessImpl(process_impl::Session),
197-
198-
#[cfg(feature = "native-mux")]
199-
NativeMuxImpl(native_mux_impl::Session),
200-
}
201-
202-
#[cfg(any(feature = "process-mux", feature = "native-mux"))]
203-
macro_rules! delegate {
204-
($impl:expr, $var:ident, $then:block) => {{
205-
match $impl {
206-
#[cfg(feature = "process-mux")]
207-
SessionImp::ProcessImpl($var) => $then,
208-
209-
#[cfg(feature = "native-mux")]
210-
SessionImp::NativeMuxImpl($var) => $then,
211-
}
212-
}};
213-
}
214-
215-
#[cfg(not(any(feature = "process-mux", feature = "native-mux")))]
216-
macro_rules! delegate {
217-
($impl:expr, $var:ident, $then:block) => {{
218-
unreachable!("Neither feature process-mux nor native-mux is enabled")
219-
}};
220-
}
221-
222-
/// A single SSH session to a remote host.
223-
///
224-
/// You can use [`command`](Session::command) to start a new command on the connected machine.
225-
///
226-
/// When the `Session` is dropped, the connection to the remote host is severed, and any errors
227-
/// silently ignored. To disconnect and be alerted to errors, use [`close`](Session::close).
228-
#[derive(Debug)]
229-
pub struct Session(SessionImp);
230-
231-
#[cfg(feature = "process-mux")]
232-
impl From<process_impl::Session> for Session {
233-
fn from(imp: process_impl::Session) -> Self {
234-
Self(SessionImp::ProcessImpl(imp))
235-
}
236-
}
237-
238-
#[cfg(feature = "native-mux")]
239-
impl From<native_mux_impl::Session> for Session {
240-
fn from(imp: native_mux_impl::Session) -> Self {
241-
Self(SessionImp::NativeMuxImpl(imp))
242-
}
243-
}
244-
245-
// TODO: UserKnownHostsFile for custom known host fingerprint.
246-
247-
impl Session {
248-
/// Connect to the host at the given `host` over SSH using process impl, which will
249-
/// spawn a new ssh process for each `Child` created.
250-
///
251-
/// The format of `destination` is the same as the `destination` argument to `ssh`. It may be
252-
/// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`.
253-
///
254-
/// If connecting requires interactive authentication based on `STDIN` (such as reading a
255-
/// password), the connection will fail. Consider setting up keypair-based authentication
256-
/// instead.
257-
///
258-
/// For more options, see [`SessionBuilder`].
259-
#[cfg(feature = "process-mux")]
260-
#[cfg_attr(docsrs, doc(cfg(feature = "process-mux")))]
261-
pub async fn connect<S: AsRef<str>>(destination: S, check: KnownHosts) -> Result<Self, Error> {
262-
let mut s = SessionBuilder::default();
263-
s.known_hosts_check(check);
264-
s.connect(destination.as_ref()).await
265-
}
266-
267-
/// Connect to the host at the given `host` over SSH using native mux impl, which
268-
/// will create a new socket connection for each `Child` created.
269-
///
270-
/// See the crate-level documentation for more details on the difference between native and process-based mux.
271-
///
272-
/// The format of `destination` is the same as the `destination` argument to `ssh`. It may be
273-
/// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`.
274-
///
275-
/// If connecting requires interactive authentication based on `STDIN` (such as reading a
276-
/// password), the connection will fail. Consider setting up keypair-based authentication
277-
/// instead.
278-
///
279-
/// For more options, see [`SessionBuilder`].
280-
#[cfg(feature = "native-mux")]
281-
#[cfg_attr(docsrs, doc(cfg(feature = "native-mux")))]
282-
pub async fn connect_mux<S: AsRef<str>>(
283-
destination: S,
284-
check: KnownHosts,
285-
) -> Result<Self, Error> {
286-
let mut s = SessionBuilder::default();
287-
s.known_hosts_check(check);
288-
s.connect_mux(destination.as_ref()).await
289-
}
290-
291-
/// Check the status of the underlying SSH connection.
292-
#[cfg(not(windows))]
293-
#[cfg_attr(docsrs, doc(cfg(not(windows))))]
294-
pub async fn check(&self) -> Result<(), Error> {
295-
delegate!(&self.0, imp, { imp.check().await })
296-
}
297-
298-
/// Get the SSH connection's control socket path.
299-
#[cfg(not(windows))]
300-
#[cfg_attr(docsrs, doc(cfg(not(windows))))]
301-
pub fn control_socket(&self) -> &Path {
302-
delegate!(&self.0, imp, { imp.ctl() })
303-
}
304-
305-
/// Constructs a new [`Command`] for launching the program at path `program` on the remote
306-
/// host.
307-
///
308-
/// Before it is passed to the remote host, `program` is escaped so that special characters
309-
/// aren't evaluated by the remote shell. If you do not want this behavior, use
310-
/// [`raw_command`](Session::raw_command).
311-
///
312-
/// The returned `Command` is a builder, with the following default configuration:
313-
///
314-
/// * No arguments to the program
315-
/// * Empty stdin and dsicard stdout/stderr for `spawn` or `status`, but create output pipes for
316-
/// `output`
317-
///
318-
/// Builder methods are provided to change these defaults and otherwise configure the process.
319-
///
320-
/// If `program` is not an absolute path, the `PATH` will be searched in an OS-defined way on
321-
/// the host.
322-
pub fn command<'a, S: Into<Cow<'a, str>>>(&self, program: S) -> Command<'_> {
323-
self.raw_command(&*shell_escape::unix::escape(program.into()))
324-
}
325-
326-
/// Constructs a new [`Command`] for launching the program at path `program` on the remote
327-
/// host.
328-
///
329-
/// Unlike [`command`](Session::command), this method does not shell-escape `program`, so it may be evaluated in
330-
/// unforeseen ways by the remote shell.
331-
///
332-
/// The returned `Command` is a builder, with the following default configuration:
333-
///
334-
/// * No arguments to the program
335-
/// * Empty stdin and dsicard stdout/stderr for `spawn` or `status`, but create output pipes for
336-
/// `output`
337-
///
338-
/// Builder methods are provided to change these defaults and otherwise configure the process.
339-
///
340-
/// If `program` is not an absolute path, the `PATH` will be searched in an OS-defined way on
341-
/// the host.
342-
pub fn raw_command<S: AsRef<OsStr>>(&self, program: S) -> Command<'_> {
343-
Command::new(
344-
self,
345-
delegate!(&self.0, imp, { imp.raw_command(program).into() }),
346-
)
347-
}
348-
349-
/// Constructs a new [`Command`] that runs the provided shell command on the remote host.
350-
///
351-
/// The provided command is passed as a single, escaped argument to `sh -c`, and from that
352-
/// point forward the behavior is up to `sh`. Since this executes a shell command, keep in mind
353-
/// that you are subject to the shell's rules around argument parsing, such as whitespace
354-
/// splitting, variable expansion, and other funkyness. I _highly_ recommend you read
355-
/// [this article] if you observe strange things.
356-
///
357-
/// While the returned `Command` is a builder, like for [`command`](Session::command), you should not add
358-
/// additional arguments to it, since the arguments are already passed within the shell
359-
/// command.
360-
///
361-
/// # Non-standard Remote Shells
362-
///
363-
/// It is worth noting that there are really _two_ shells at work here: the one that sshd
364-
/// launches for the session, and that launches are command; and the instance of `sh` that we
365-
/// launch _in_ that session. This method tries hard to ensure that the provided `command` is
366-
/// passed exactly as-is to `sh`, but this is complicated by the presence of the "outer" shell.
367-
/// That outer shell may itself perform argument splitting, variable expansion, and the like,
368-
/// which might produce unintuitive results. For example, the outer shell may try to expand a
369-
/// variable that is only defined in the inner shell, and simply produce an empty string in the
370-
/// variable's place by the time it gets to `sh`.
371-
///
372-
/// To counter this, this method assumes that the remote shell (the one launched by `sshd`) is
373-
/// [POSIX compliant]. This is more or less equivalent to "supports `bash` syntax" if you don't
374-
/// look too closely. It uses [`shell-escape`] to escape `command` before sending it to the
375-
/// remote shell, with the expectation that the remote shell will only end up undoing that one
376-
/// "level" of escaping, thus producing the original `command` as an argument to `sh`. This
377-
/// works _most of the time_.
378-
///
379-
/// With sufficiently complex or weird commands, the escaping of `shell-escape` may not fully
380-
/// match the "un-escaping" of the remote shell. This will manifest as escape characters
381-
/// appearing in the `sh` command that you did not intend to be there. If this happens, try
382-
/// changing the remote shell if you can, or fall back to [`command`](Session::command)
383-
/// and do the escaping manually instead.
384-
///
385-
/// [POSIX compliant]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xcu_chap02.html
386-
/// [this article]: https://mywiki.wooledge.org/Arguments
387-
/// [`shell-escape`]: https://crates.io/crates/shell-escape
388-
pub fn shell<S: AsRef<str>>(&self, command: S) -> Command<'_> {
389-
let mut cmd = self.command("sh");
390-
cmd.arg("-c").arg(command);
391-
cmd
392-
}
393-
394-
/// Request to open a local/remote port forwarding.
395-
/// The `Socket` can be either a unix socket or a tcp socket.
396-
///
397-
/// If `forward_type` == Local, then `listen_socket` on local machine will be
398-
/// forwarded to `connect_socket` on remote machine.
399-
///
400-
/// Otherwise, `listen_socket` on the remote machine will be forwarded to `connect_socket`
401-
/// on the local machine.
402-
///
403-
/// Currently, there is no way of stopping a port forwarding due to the fact that
404-
/// openssh multiplex server/master does not support this.
405-
pub async fn request_port_forward(
406-
&self,
407-
forward_type: ForwardType,
408-
listen_socket: Socket<'_>,
409-
connect_socket: Socket<'_>,
410-
) -> Result<(), Error> {
411-
delegate!(&self.0, imp, {
412-
imp.request_port_forward(forward_type, listen_socket, connect_socket)
413-
.await
414-
})
415-
}
416-
417-
/// Terminate the remote connection.
418-
pub async fn close(self) -> Result<(), Error> {
419-
delegate!(self.0, imp, { imp.close().await })
420-
}
421-
}

0 commit comments

Comments
 (0)