##// END OF EJS Templates
rust-chg: add helper to pack environment variables...
Yuya Nishihara -
r45164:8a7beeea default
parent child Browse files
Show More
@@ -1,133 +1,232 b''
1 // Copyright 2018 Yuya Nishihara <yuya@tcha.org>
1 // Copyright 2018 Yuya Nishihara <yuya@tcha.org>
2 //
2 //
3 // This software may be used and distributed according to the terms of the
3 // This software may be used and distributed according to the terms of the
4 // GNU General Public License version 2 or any later version.
4 // GNU General Public License version 2 or any later version.
5
5
6 //! Utility for parsing and building command-server messages.
6 //! Utility for parsing and building command-server messages.
7
7
8 use bytes::Bytes;
8 use bytes::{BufMut, Bytes, BytesMut};
9 use std::error;
9 use std::error;
10 use std::ffi::{OsStr, OsString};
10 use std::ffi::{OsStr, OsString};
11 use std::io;
11 use std::io;
12 use std::os::unix::ffi::OsStrExt;
12 use std::os::unix::ffi::OsStrExt;
13
13
14 pub use tokio_hglib::message::*; // re-exports
14 pub use tokio_hglib::message::*; // re-exports
15
15
16 /// Shell command type requested by the server.
16 /// Shell command type requested by the server.
17 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
17 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
18 pub enum CommandType {
18 pub enum CommandType {
19 /// Pager should be spawned.
19 /// Pager should be spawned.
20 Pager,
20 Pager,
21 /// Shell command should be executed to send back the result code.
21 /// Shell command should be executed to send back the result code.
22 System,
22 System,
23 }
23 }
24
24
25 /// Shell command requested by the server.
25 /// Shell command requested by the server.
26 #[derive(Clone, Debug, Eq, PartialEq)]
26 #[derive(Clone, Debug, Eq, PartialEq)]
27 pub struct CommandSpec {
27 pub struct CommandSpec {
28 pub command: OsString,
28 pub command: OsString,
29 pub current_dir: OsString,
29 pub current_dir: OsString,
30 pub envs: Vec<(OsString, OsString)>,
30 pub envs: Vec<(OsString, OsString)>,
31 }
31 }
32
32
33 /// Parses "S" channel request into command type and spec.
33 /// Parses "S" channel request into command type and spec.
34 pub fn parse_command_spec(data: Bytes) -> io::Result<(CommandType, CommandSpec)> {
34 pub fn parse_command_spec(data: Bytes) -> io::Result<(CommandType, CommandSpec)> {
35 let mut split = data.split(|&c| c == b'\0');
35 let mut split = data.split(|&c| c == b'\0');
36 let ctype = parse_command_type(split.next().ok_or(new_parse_error("missing type"))?)?;
36 let ctype = parse_command_type(split.next().ok_or(new_parse_error("missing type"))?)?;
37 let command = split.next().ok_or(new_parse_error("missing command"))?;
37 let command = split.next().ok_or(new_parse_error("missing command"))?;
38 let current_dir = split.next().ok_or(new_parse_error("missing current dir"))?;
38 let current_dir = split.next().ok_or(new_parse_error("missing current dir"))?;
39
39
40 let mut envs = Vec::new();
40 let mut envs = Vec::new();
41 for l in split {
41 for l in split {
42 let mut s = l.splitn(2, |&c| c == b'=');
42 let mut s = l.splitn(2, |&c| c == b'=');
43 let k = s.next().unwrap();
43 let k = s.next().unwrap();
44 let v = s.next().ok_or(new_parse_error("malformed env"))?;
44 let v = s.next().ok_or(new_parse_error("malformed env"))?;
45 envs.push((
45 envs.push((
46 OsStr::from_bytes(k).to_owned(),
46 OsStr::from_bytes(k).to_owned(),
47 OsStr::from_bytes(v).to_owned(),
47 OsStr::from_bytes(v).to_owned(),
48 ));
48 ));
49 }
49 }
50
50
51 let spec = CommandSpec {
51 let spec = CommandSpec {
52 command: OsStr::from_bytes(command).to_owned(),
52 command: OsStr::from_bytes(command).to_owned(),
53 current_dir: OsStr::from_bytes(current_dir).to_owned(),
53 current_dir: OsStr::from_bytes(current_dir).to_owned(),
54 envs: envs,
54 envs: envs,
55 };
55 };
56 Ok((ctype, spec))
56 Ok((ctype, spec))
57 }
57 }
58
58
59 fn parse_command_type(value: &[u8]) -> io::Result<CommandType> {
59 fn parse_command_type(value: &[u8]) -> io::Result<CommandType> {
60 match value {
60 match value {
61 b"pager" => Ok(CommandType::Pager),
61 b"pager" => Ok(CommandType::Pager),
62 b"system" => Ok(CommandType::System),
62 b"system" => Ok(CommandType::System),
63 _ => Err(new_parse_error(format!(
63 _ => Err(new_parse_error(format!(
64 "unknown command type: {}",
64 "unknown command type: {}",
65 decode_latin1(value)
65 decode_latin1(value)
66 ))),
66 ))),
67 }
67 }
68 }
68 }
69
69
70 // allocate large buffer as environment variables can be quite long
71 const INITIAL_PACKED_ENV_VARS_CAPACITY: usize = 4096;
72
73 /// Packs environment variables of platform encoding into bytes.
74 ///
75 /// # Panics
76 ///
77 /// Panics if key or value contains `\0` character, or key contains '='
78 /// character.
79 pub fn pack_env_vars_os<I, P>(vars: I) -> Bytes
80 where
81 I: IntoIterator<Item = (P, P)>,
82 P: AsRef<OsStr>,
83 {
84 let mut vars_iter = vars.into_iter();
85 if let Some((k, v)) = vars_iter.next() {
86 let mut dst = BytesMut::with_capacity(INITIAL_PACKED_ENV_VARS_CAPACITY);
87 pack_env_into(&mut dst, k.as_ref(), v.as_ref());
88 for (k, v) in vars_iter {
89 dst.reserve(1);
90 dst.put_u8(b'\0');
91 pack_env_into(&mut dst, k.as_ref(), v.as_ref());
92 }
93 dst.freeze()
94 } else {
95 Bytes::new()
96 }
97 }
98
99 fn pack_env_into(dst: &mut BytesMut, k: &OsStr, v: &OsStr) {
100 assert!(!k.as_bytes().contains(&0), "key shouldn't contain NUL");
101 assert!(!k.as_bytes().contains(&b'='), "key shouldn't contain '='");
102 assert!(!v.as_bytes().contains(&0), "value shouldn't contain NUL");
103 dst.reserve(k.as_bytes().len() + 1 + v.as_bytes().len());
104 dst.put_slice(k.as_bytes());
105 dst.put_u8(b'=');
106 dst.put_slice(v.as_bytes());
107 }
108
70 fn decode_latin1<S>(s: S) -> String
109 fn decode_latin1<S>(s: S) -> String
71 where
110 where
72 S: AsRef<[u8]>,
111 S: AsRef<[u8]>,
73 {
112 {
74 s.as_ref().iter().map(|&c| c as char).collect()
113 s.as_ref().iter().map(|&c| c as char).collect()
75 }
114 }
76
115
77 fn new_parse_error<E>(error: E) -> io::Error
116 fn new_parse_error<E>(error: E) -> io::Error
78 where
117 where
79 E: Into<Box<error::Error + Send + Sync>>,
118 E: Into<Box<error::Error + Send + Sync>>,
80 {
119 {
81 io::Error::new(io::ErrorKind::InvalidData, error)
120 io::Error::new(io::ErrorKind::InvalidData, error)
82 }
121 }
83
122
84 #[cfg(test)]
123 #[cfg(test)]
85 mod tests {
124 mod tests {
86 use super::*;
125 use super::*;
87 use std::os::unix::ffi::OsStringExt;
126 use std::os::unix::ffi::OsStringExt;
127 use std::panic;
88
128
89 #[test]
129 #[test]
90 fn parse_command_spec_good() {
130 fn parse_command_spec_good() {
91 let src = [
131 let src = [
92 b"pager".as_ref(),
132 b"pager".as_ref(),
93 b"less -FRX".as_ref(),
133 b"less -FRX".as_ref(),
94 b"/tmp".as_ref(),
134 b"/tmp".as_ref(),
95 b"LANG=C".as_ref(),
135 b"LANG=C".as_ref(),
96 b"HGPLAIN=".as_ref(),
136 b"HGPLAIN=".as_ref(),
97 ]
137 ]
98 .join(&0);
138 .join(&0);
99 let spec = CommandSpec {
139 let spec = CommandSpec {
100 command: os_string_from(b"less -FRX"),
140 command: os_string_from(b"less -FRX"),
101 current_dir: os_string_from(b"/tmp"),
141 current_dir: os_string_from(b"/tmp"),
102 envs: vec![
142 envs: vec![
103 (os_string_from(b"LANG"), os_string_from(b"C")),
143 (os_string_from(b"LANG"), os_string_from(b"C")),
104 (os_string_from(b"HGPLAIN"), os_string_from(b"")),
144 (os_string_from(b"HGPLAIN"), os_string_from(b"")),
105 ],
145 ],
106 };
146 };
107 assert_eq!(
147 assert_eq!(
108 parse_command_spec(Bytes::from(src)).unwrap(),
148 parse_command_spec(Bytes::from(src)).unwrap(),
109 (CommandType::Pager, spec)
149 (CommandType::Pager, spec)
110 );
150 );
111 }
151 }
112
152
113 #[test]
153 #[test]
114 fn parse_command_spec_too_short() {
154 fn parse_command_spec_too_short() {
115 assert!(parse_command_spec(Bytes::from_static(b"")).is_err());
155 assert!(parse_command_spec(Bytes::from_static(b"")).is_err());
116 assert!(parse_command_spec(Bytes::from_static(b"pager")).is_err());
156 assert!(parse_command_spec(Bytes::from_static(b"pager")).is_err());
117 assert!(parse_command_spec(Bytes::from_static(b"pager\0less")).is_err());
157 assert!(parse_command_spec(Bytes::from_static(b"pager\0less")).is_err());
118 }
158 }
119
159
120 #[test]
160 #[test]
121 fn parse_command_spec_malformed_env() {
161 fn parse_command_spec_malformed_env() {
122 assert!(parse_command_spec(Bytes::from_static(b"pager\0less\0/tmp\0HOME")).is_err());
162 assert!(parse_command_spec(Bytes::from_static(b"pager\0less\0/tmp\0HOME")).is_err());
123 }
163 }
124
164
125 #[test]
165 #[test]
126 fn parse_command_spec_unknown_type() {
166 fn parse_command_spec_unknown_type() {
127 assert!(parse_command_spec(Bytes::from_static(b"paper\0less")).is_err());
167 assert!(parse_command_spec(Bytes::from_static(b"paper\0less")).is_err());
128 }
168 }
129
169
170 #[test]
171 fn pack_env_vars_os_good() {
172 assert_eq!(
173 pack_env_vars_os(vec![] as Vec<(OsString, OsString)>),
174 Bytes::new()
175 );
176 assert_eq!(
177 pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"bar")]),
178 Bytes::from_static(b"FOO=bar")
179 );
180 assert_eq!(
181 pack_env_vars_os(vec![
182 os_string_pair_from(b"FOO", b""),
183 os_string_pair_from(b"BAR", b"baz")
184 ]),
185 Bytes::from_static(b"FOO=\0BAR=baz")
186 );
187 }
188
189 #[test]
190 fn pack_env_vars_os_large_key() {
191 let mut buf = vec![b'A'; INITIAL_PACKED_ENV_VARS_CAPACITY];
192 let envs = vec![os_string_pair_from(&buf, b"")];
193 buf.push(b'=');
194 assert_eq!(pack_env_vars_os(envs), Bytes::from(buf));
195 }
196
197 #[test]
198 fn pack_env_vars_os_large_value() {
199 let mut buf = vec![b'A', b'='];
200 buf.resize(INITIAL_PACKED_ENV_VARS_CAPACITY + 1, b'a');
201 let envs = vec![os_string_pair_from(&buf[..1], &buf[2..])];
202 assert_eq!(pack_env_vars_os(envs), Bytes::from(buf));
203 }
204
205 #[test]
206 fn pack_env_vars_os_nul_eq() {
207 assert!(panic::catch_unwind(|| {
208 pack_env_vars_os(vec![os_string_pair_from(b"\0", b"")])
209 })
210 .is_err());
211 assert!(panic::catch_unwind(|| {
212 pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"\0bar")])
213 })
214 .is_err());
215 assert!(panic::catch_unwind(|| {
216 pack_env_vars_os(vec![os_string_pair_from(b"FO=", b"bar")])
217 })
218 .is_err());
219 assert_eq!(
220 pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"=ba")]),
221 Bytes::from_static(b"FOO==ba")
222 );
223 }
224
130 fn os_string_from(s: &[u8]) -> OsString {
225 fn os_string_from(s: &[u8]) -> OsString {
131 OsString::from_vec(s.to_vec())
226 OsString::from_vec(s.to_vec())
132 }
227 }
228
229 fn os_string_pair_from(k: &[u8], v: &[u8]) -> (OsString, OsString) {
230 (os_string_from(k), os_string_from(v))
231 }
133 }
232 }
General Comments 0
You need to be logged in to leave comments. Login now