message.rs
133 lines
| 3.8 KiB
| application/rls-services+xml
|
RustLexer
Yuya Nishihara
|
r40007 | // Copyright 2018 Yuya Nishihara <yuya@tcha.org> | ||
// | ||||
// This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | ||||
//! Utility for parsing and building command-server messages. | ||||
use bytes::Bytes; | ||||
use std::error; | ||||
use std::ffi::{OsStr, OsString}; | ||||
use std::io; | ||||
use std::os::unix::ffi::OsStrExt; | ||||
Gregory Szorc
|
r44270 | pub use tokio_hglib::message::*; // re-exports | ||
Yuya Nishihara
|
r40007 | |||
/// Shell command type requested by the server. | ||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||||
pub enum CommandType { | ||||
/// Pager should be spawned. | ||||
Pager, | ||||
/// Shell command should be executed to send back the result code. | ||||
System, | ||||
} | ||||
/// Shell command requested by the server. | ||||
#[derive(Clone, Debug, Eq, PartialEq)] | ||||
pub struct CommandSpec { | ||||
pub command: OsString, | ||||
pub current_dir: OsString, | ||||
pub envs: Vec<(OsString, OsString)>, | ||||
} | ||||
/// Parses "S" channel request into command type and spec. | ||||
pub fn parse_command_spec(data: Bytes) -> io::Result<(CommandType, CommandSpec)> { | ||||
let mut split = data.split(|&c| c == b'\0'); | ||||
let ctype = parse_command_type(split.next().ok_or(new_parse_error("missing type"))?)?; | ||||
let command = split.next().ok_or(new_parse_error("missing command"))?; | ||||
let current_dir = split.next().ok_or(new_parse_error("missing current dir"))?; | ||||
let mut envs = Vec::new(); | ||||
for l in split { | ||||
let mut s = l.splitn(2, |&c| c == b'='); | ||||
let k = s.next().unwrap(); | ||||
let v = s.next().ok_or(new_parse_error("malformed env"))?; | ||||
Gregory Szorc
|
r44270 | envs.push(( | ||
OsStr::from_bytes(k).to_owned(), | ||||
OsStr::from_bytes(v).to_owned(), | ||||
)); | ||||
Yuya Nishihara
|
r40007 | } | ||
let spec = CommandSpec { | ||||
command: OsStr::from_bytes(command).to_owned(), | ||||
current_dir: OsStr::from_bytes(current_dir).to_owned(), | ||||
envs: envs, | ||||
}; | ||||
Ok((ctype, spec)) | ||||
} | ||||
fn parse_command_type(value: &[u8]) -> io::Result<CommandType> { | ||||
match value { | ||||
b"pager" => Ok(CommandType::Pager), | ||||
b"system" => Ok(CommandType::System), | ||||
Gregory Szorc
|
r44270 | _ => Err(new_parse_error(format!( | ||
"unknown command type: {}", | ||||
decode_latin1(value) | ||||
))), | ||||
Yuya Nishihara
|
r40007 | } | ||
} | ||||
fn decode_latin1<S>(s: S) -> String | ||||
Gregory Szorc
|
r44270 | where | ||
S: AsRef<[u8]>, | ||||
Yuya Nishihara
|
r40007 | { | ||
s.as_ref().iter().map(|&c| c as char).collect() | ||||
} | ||||
fn new_parse_error<E>(error: E) -> io::Error | ||||
Gregory Szorc
|
r44270 | where | ||
E: Into<Box<error::Error + Send + Sync>>, | ||||
Yuya Nishihara
|
r40007 | { | ||
io::Error::new(io::ErrorKind::InvalidData, error) | ||||
} | ||||
#[cfg(test)] | ||||
mod tests { | ||||
Gregory Szorc
|
r44270 | use super::*; | ||
Yuya Nishihara
|
r40007 | use std::os::unix::ffi::OsStringExt; | ||
#[test] | ||||
fn parse_command_spec_good() { | ||||
Gregory Szorc
|
r44270 | let src = [ | ||
b"pager".as_ref(), | ||||
b"less -FRX".as_ref(), | ||||
b"/tmp".as_ref(), | ||||
b"LANG=C".as_ref(), | ||||
b"HGPLAIN=".as_ref(), | ||||
] | ||||
.join(&0); | ||||
Yuya Nishihara
|
r40007 | let spec = CommandSpec { | ||
command: os_string_from(b"less -FRX"), | ||||
current_dir: os_string_from(b"/tmp"), | ||||
Gregory Szorc
|
r44270 | envs: vec![ | ||
(os_string_from(b"LANG"), os_string_from(b"C")), | ||||
(os_string_from(b"HGPLAIN"), os_string_from(b"")), | ||||
], | ||||
Yuya Nishihara
|
r40007 | }; | ||
Gregory Szorc
|
r44270 | assert_eq!( | ||
parse_command_spec(Bytes::from(src)).unwrap(), | ||||
(CommandType::Pager, spec) | ||||
); | ||||
Yuya Nishihara
|
r40007 | } | ||
#[test] | ||||
fn parse_command_spec_too_short() { | ||||
assert!(parse_command_spec(Bytes::from_static(b"")).is_err()); | ||||
assert!(parse_command_spec(Bytes::from_static(b"pager")).is_err()); | ||||
assert!(parse_command_spec(Bytes::from_static(b"pager\0less")).is_err()); | ||||
} | ||||
#[test] | ||||
fn parse_command_spec_malformed_env() { | ||||
assert!(parse_command_spec(Bytes::from_static(b"pager\0less\0/tmp\0HOME")).is_err()); | ||||
} | ||||
#[test] | ||||
fn parse_command_spec_unknown_type() { | ||||
assert!(parse_command_spec(Bytes::from_static(b"paper\0less")).is_err()); | ||||
} | ||||
fn os_string_from(s: &[u8]) -> OsString { | ||||
OsString::from_vec(s.to_vec()) | ||||
} | ||||
} | ||||