// Copyright 2018 Yuya Nishihara // // 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 { 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) -> String where S: AsRef<[u8]>, { s.as_ref().iter().map(|&c| c as char).collect() } fn new_parse_error(error: E) -> io::Error where E: Into>, { 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()) } }