message.rs
117 lines
| 3.7 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; | ||||
pub use tokio_hglib::message::*; // re-exports | ||||
/// 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"))?; | ||||
envs.push((OsStr::from_bytes(k).to_owned(), OsStr::from_bytes(v).to_owned())); | ||||
} | ||||
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), | ||||
_ => Err(new_parse_error(format!("unknown command type: {}", decode_latin1(value)))), | ||||
} | ||||
} | ||||
fn decode_latin1<S>(s: S) -> String | ||||
where S: AsRef<[u8]>, | ||||
{ | ||||
s.as_ref().iter().map(|&c| c as char).collect() | ||||
} | ||||
fn new_parse_error<E>(error: E) -> io::Error | ||||
where E: Into<Box<error::Error + Send + Sync>>, | ||||
{ | ||||
io::Error::new(io::ErrorKind::InvalidData, error) | ||||
} | ||||
#[cfg(test)] | ||||
mod tests { | ||||
use std::os::unix::ffi::OsStringExt; | ||||
use super::*; | ||||
#[test] | ||||
fn parse_command_spec_good() { | ||||
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); | ||||
let spec = CommandSpec { | ||||
command: os_string_from(b"less -FRX"), | ||||
current_dir: os_string_from(b"/tmp"), | ||||
envs: vec![(os_string_from(b"LANG"), os_string_from(b"C")), | ||||
(os_string_from(b"HGPLAIN"), os_string_from(b""))], | ||||
}; | ||||
assert_eq!(parse_command_spec(Bytes::from(src)).unwrap(), (CommandType::Pager, spec)); | ||||
} | ||||
#[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()) | ||||
} | ||||
} | ||||