--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rust/chg/src/message.rs Mon Sep 24 16:33:24 2018 +0900
@@ -0,0 +1,117 @@
+// 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())
+ }
+}