Show More
@@ -1,232 +1,317 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::{BufMut, Bytes, BytesMut}; |
|
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 | use std::path::PathBuf; | |||
13 |
|
14 | |||
14 | pub use tokio_hglib::message::*; // re-exports |
|
15 | pub use tokio_hglib::message::*; // re-exports | |
15 |
|
16 | |||
16 | /// Shell command type requested by the server. |
|
17 | /// Shell command type requested by the server. | |
17 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
|
18 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] | |
18 | pub enum CommandType { |
|
19 | pub enum CommandType { | |
19 | /// Pager should be spawned. |
|
20 | /// Pager should be spawned. | |
20 | Pager, |
|
21 | Pager, | |
21 | /// Shell command should be executed to send back the result code. |
|
22 | /// Shell command should be executed to send back the result code. | |
22 | System, |
|
23 | System, | |
23 | } |
|
24 | } | |
24 |
|
25 | |||
25 | /// Shell command requested by the server. |
|
26 | /// Shell command requested by the server. | |
26 | #[derive(Clone, Debug, Eq, PartialEq)] |
|
27 | #[derive(Clone, Debug, Eq, PartialEq)] | |
27 | pub struct CommandSpec { |
|
28 | pub struct CommandSpec { | |
28 | pub command: OsString, |
|
29 | pub command: OsString, | |
29 | pub current_dir: OsString, |
|
30 | pub current_dir: OsString, | |
30 | pub envs: Vec<(OsString, OsString)>, |
|
31 | pub envs: Vec<(OsString, OsString)>, | |
31 | } |
|
32 | } | |
32 |
|
33 | |||
33 | /// Parses "S" channel request into command type and spec. |
|
34 | /// Parses "S" channel request into command type and spec. | |
34 | pub fn parse_command_spec(data: Bytes) -> io::Result<(CommandType, CommandSpec)> { |
|
35 | pub fn parse_command_spec(data: Bytes) -> io::Result<(CommandType, CommandSpec)> { | |
35 | let mut split = data.split(|&c| c == b'\0'); |
|
36 | let mut split = data.split(|&c| c == b'\0'); | |
36 | let ctype = parse_command_type(split.next().ok_or(new_parse_error("missing type"))?)?; |
|
37 | 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"))?; |
|
38 | 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"))?; |
|
39 | let current_dir = split.next().ok_or(new_parse_error("missing current dir"))?; | |
39 |
|
40 | |||
40 | let mut envs = Vec::new(); |
|
41 | let mut envs = Vec::new(); | |
41 | for l in split { |
|
42 | for l in split { | |
42 | let mut s = l.splitn(2, |&c| c == b'='); |
|
43 | let mut s = l.splitn(2, |&c| c == b'='); | |
43 | let k = s.next().unwrap(); |
|
44 | let k = s.next().unwrap(); | |
44 | let v = s.next().ok_or(new_parse_error("malformed env"))?; |
|
45 | let v = s.next().ok_or(new_parse_error("malformed env"))?; | |
45 | envs.push(( |
|
46 | envs.push(( | |
46 | OsStr::from_bytes(k).to_owned(), |
|
47 | OsStr::from_bytes(k).to_owned(), | |
47 | OsStr::from_bytes(v).to_owned(), |
|
48 | OsStr::from_bytes(v).to_owned(), | |
48 | )); |
|
49 | )); | |
49 | } |
|
50 | } | |
50 |
|
51 | |||
51 | let spec = CommandSpec { |
|
52 | let spec = CommandSpec { | |
52 | command: OsStr::from_bytes(command).to_owned(), |
|
53 | command: OsStr::from_bytes(command).to_owned(), | |
53 | current_dir: OsStr::from_bytes(current_dir).to_owned(), |
|
54 | current_dir: OsStr::from_bytes(current_dir).to_owned(), | |
54 | envs: envs, |
|
55 | envs: envs, | |
55 | }; |
|
56 | }; | |
56 | Ok((ctype, spec)) |
|
57 | Ok((ctype, spec)) | |
57 | } |
|
58 | } | |
58 |
|
59 | |||
59 | fn parse_command_type(value: &[u8]) -> io::Result<CommandType> { |
|
60 | fn parse_command_type(value: &[u8]) -> io::Result<CommandType> { | |
60 | match value { |
|
61 | match value { | |
61 | b"pager" => Ok(CommandType::Pager), |
|
62 | b"pager" => Ok(CommandType::Pager), | |
62 | b"system" => Ok(CommandType::System), |
|
63 | b"system" => Ok(CommandType::System), | |
63 | _ => Err(new_parse_error(format!( |
|
64 | _ => Err(new_parse_error(format!( | |
64 | "unknown command type: {}", |
|
65 | "unknown command type: {}", | |
65 | decode_latin1(value) |
|
66 | decode_latin1(value) | |
66 | ))), |
|
67 | ))), | |
67 | } |
|
68 | } | |
68 | } |
|
69 | } | |
69 |
|
70 | |||
|
71 | /// Client-side instruction requested by the server. | |||
|
72 | #[derive(Clone, Debug, Eq, PartialEq)] | |||
|
73 | pub enum Instruction { | |||
|
74 | Exit(i32), | |||
|
75 | Reconnect, | |||
|
76 | Redirect(PathBuf), | |||
|
77 | Unlink(PathBuf), | |||
|
78 | } | |||
|
79 | ||||
|
80 | /// Parses validation result into instructions. | |||
|
81 | pub fn parse_instructions(data: Bytes) -> io::Result<Vec<Instruction>> { | |||
|
82 | let mut instructions = Vec::new(); | |||
|
83 | for l in data.split(|&c| c == b'\0') { | |||
|
84 | if l.is_empty() { | |||
|
85 | continue; | |||
|
86 | } | |||
|
87 | let mut s = l.splitn(2, |&c| c == b' '); | |||
|
88 | let inst = match (s.next().unwrap(), s.next()) { | |||
|
89 | (b"exit", Some(arg)) => decode_latin1(arg) | |||
|
90 | .parse() | |||
|
91 | .map(Instruction::Exit) | |||
|
92 | .map_err(|_| new_parse_error(format!("invalid exit code: {:?}", arg)))?, | |||
|
93 | (b"reconnect", None) => Instruction::Reconnect, | |||
|
94 | (b"redirect", Some(arg)) => { | |||
|
95 | Instruction::Redirect(OsStr::from_bytes(arg).to_owned().into()) | |||
|
96 | } | |||
|
97 | (b"unlink", Some(arg)) => Instruction::Unlink(OsStr::from_bytes(arg).to_owned().into()), | |||
|
98 | _ => { | |||
|
99 | return Err(new_parse_error(format!("unknown command: {:?}", l))); | |||
|
100 | } | |||
|
101 | }; | |||
|
102 | instructions.push(inst); | |||
|
103 | } | |||
|
104 | Ok(instructions) | |||
|
105 | } | |||
|
106 | ||||
70 | // allocate large buffer as environment variables can be quite long |
|
107 | // allocate large buffer as environment variables can be quite long | |
71 | const INITIAL_PACKED_ENV_VARS_CAPACITY: usize = 4096; |
|
108 | const INITIAL_PACKED_ENV_VARS_CAPACITY: usize = 4096; | |
72 |
|
109 | |||
73 | /// Packs environment variables of platform encoding into bytes. |
|
110 | /// Packs environment variables of platform encoding into bytes. | |
74 | /// |
|
111 | /// | |
75 | /// # Panics |
|
112 | /// # Panics | |
76 | /// |
|
113 | /// | |
77 | /// Panics if key or value contains `\0` character, or key contains '=' |
|
114 | /// Panics if key or value contains `\0` character, or key contains '=' | |
78 | /// character. |
|
115 | /// character. | |
79 | pub fn pack_env_vars_os<I, P>(vars: I) -> Bytes |
|
116 | pub fn pack_env_vars_os<I, P>(vars: I) -> Bytes | |
80 | where |
|
117 | where | |
81 | I: IntoIterator<Item = (P, P)>, |
|
118 | I: IntoIterator<Item = (P, P)>, | |
82 | P: AsRef<OsStr>, |
|
119 | P: AsRef<OsStr>, | |
83 | { |
|
120 | { | |
84 | let mut vars_iter = vars.into_iter(); |
|
121 | let mut vars_iter = vars.into_iter(); | |
85 | if let Some((k, v)) = vars_iter.next() { |
|
122 | if let Some((k, v)) = vars_iter.next() { | |
86 | let mut dst = BytesMut::with_capacity(INITIAL_PACKED_ENV_VARS_CAPACITY); |
|
123 | let mut dst = BytesMut::with_capacity(INITIAL_PACKED_ENV_VARS_CAPACITY); | |
87 | pack_env_into(&mut dst, k.as_ref(), v.as_ref()); |
|
124 | pack_env_into(&mut dst, k.as_ref(), v.as_ref()); | |
88 | for (k, v) in vars_iter { |
|
125 | for (k, v) in vars_iter { | |
89 | dst.reserve(1); |
|
126 | dst.reserve(1); | |
90 | dst.put_u8(b'\0'); |
|
127 | dst.put_u8(b'\0'); | |
91 | pack_env_into(&mut dst, k.as_ref(), v.as_ref()); |
|
128 | pack_env_into(&mut dst, k.as_ref(), v.as_ref()); | |
92 | } |
|
129 | } | |
93 | dst.freeze() |
|
130 | dst.freeze() | |
94 | } else { |
|
131 | } else { | |
95 | Bytes::new() |
|
132 | Bytes::new() | |
96 | } |
|
133 | } | |
97 | } |
|
134 | } | |
98 |
|
135 | |||
99 | fn pack_env_into(dst: &mut BytesMut, k: &OsStr, v: &OsStr) { |
|
136 | fn pack_env_into(dst: &mut BytesMut, k: &OsStr, v: &OsStr) { | |
100 | assert!(!k.as_bytes().contains(&0), "key shouldn't contain NUL"); |
|
137 | assert!(!k.as_bytes().contains(&0), "key shouldn't contain NUL"); | |
101 | assert!(!k.as_bytes().contains(&b'='), "key shouldn't contain '='"); |
|
138 | assert!(!k.as_bytes().contains(&b'='), "key shouldn't contain '='"); | |
102 | assert!(!v.as_bytes().contains(&0), "value shouldn't contain NUL"); |
|
139 | assert!(!v.as_bytes().contains(&0), "value shouldn't contain NUL"); | |
103 | dst.reserve(k.as_bytes().len() + 1 + v.as_bytes().len()); |
|
140 | dst.reserve(k.as_bytes().len() + 1 + v.as_bytes().len()); | |
104 | dst.put_slice(k.as_bytes()); |
|
141 | dst.put_slice(k.as_bytes()); | |
105 | dst.put_u8(b'='); |
|
142 | dst.put_u8(b'='); | |
106 | dst.put_slice(v.as_bytes()); |
|
143 | dst.put_slice(v.as_bytes()); | |
107 | } |
|
144 | } | |
108 |
|
145 | |||
109 | fn decode_latin1<S>(s: S) -> String |
|
146 | fn decode_latin1<S>(s: S) -> String | |
110 | where |
|
147 | where | |
111 | S: AsRef<[u8]>, |
|
148 | S: AsRef<[u8]>, | |
112 | { |
|
149 | { | |
113 | s.as_ref().iter().map(|&c| c as char).collect() |
|
150 | s.as_ref().iter().map(|&c| c as char).collect() | |
114 | } |
|
151 | } | |
115 |
|
152 | |||
116 | fn new_parse_error<E>(error: E) -> io::Error |
|
153 | fn new_parse_error<E>(error: E) -> io::Error | |
117 | where |
|
154 | where | |
118 | E: Into<Box<error::Error + Send + Sync>>, |
|
155 | E: Into<Box<error::Error + Send + Sync>>, | |
119 | { |
|
156 | { | |
120 | io::Error::new(io::ErrorKind::InvalidData, error) |
|
157 | io::Error::new(io::ErrorKind::InvalidData, error) | |
121 | } |
|
158 | } | |
122 |
|
159 | |||
123 | #[cfg(test)] |
|
160 | #[cfg(test)] | |
124 | mod tests { |
|
161 | mod tests { | |
125 | use super::*; |
|
162 | use super::*; | |
126 | use std::os::unix::ffi::OsStringExt; |
|
163 | use std::os::unix::ffi::OsStringExt; | |
127 | use std::panic; |
|
164 | use std::panic; | |
128 |
|
165 | |||
129 | #[test] |
|
166 | #[test] | |
130 | fn parse_command_spec_good() { |
|
167 | fn parse_command_spec_good() { | |
131 | let src = [ |
|
168 | let src = [ | |
132 | b"pager".as_ref(), |
|
169 | b"pager".as_ref(), | |
133 | b"less -FRX".as_ref(), |
|
170 | b"less -FRX".as_ref(), | |
134 | b"/tmp".as_ref(), |
|
171 | b"/tmp".as_ref(), | |
135 | b"LANG=C".as_ref(), |
|
172 | b"LANG=C".as_ref(), | |
136 | b"HGPLAIN=".as_ref(), |
|
173 | b"HGPLAIN=".as_ref(), | |
137 | ] |
|
174 | ] | |
138 | .join(&0); |
|
175 | .join(&0); | |
139 | let spec = CommandSpec { |
|
176 | let spec = CommandSpec { | |
140 | command: os_string_from(b"less -FRX"), |
|
177 | command: os_string_from(b"less -FRX"), | |
141 | current_dir: os_string_from(b"/tmp"), |
|
178 | current_dir: os_string_from(b"/tmp"), | |
142 | envs: vec![ |
|
179 | envs: vec![ | |
143 | (os_string_from(b"LANG"), os_string_from(b"C")), |
|
180 | (os_string_from(b"LANG"), os_string_from(b"C")), | |
144 | (os_string_from(b"HGPLAIN"), os_string_from(b"")), |
|
181 | (os_string_from(b"HGPLAIN"), os_string_from(b"")), | |
145 | ], |
|
182 | ], | |
146 | }; |
|
183 | }; | |
147 | assert_eq!( |
|
184 | assert_eq!( | |
148 | parse_command_spec(Bytes::from(src)).unwrap(), |
|
185 | parse_command_spec(Bytes::from(src)).unwrap(), | |
149 | (CommandType::Pager, spec) |
|
186 | (CommandType::Pager, spec) | |
150 | ); |
|
187 | ); | |
151 | } |
|
188 | } | |
152 |
|
189 | |||
153 | #[test] |
|
190 | #[test] | |
154 | fn parse_command_spec_too_short() { |
|
191 | fn parse_command_spec_too_short() { | |
155 | assert!(parse_command_spec(Bytes::from_static(b"")).is_err()); |
|
192 | assert!(parse_command_spec(Bytes::from_static(b"")).is_err()); | |
156 | assert!(parse_command_spec(Bytes::from_static(b"pager")).is_err()); |
|
193 | assert!(parse_command_spec(Bytes::from_static(b"pager")).is_err()); | |
157 | assert!(parse_command_spec(Bytes::from_static(b"pager\0less")).is_err()); |
|
194 | assert!(parse_command_spec(Bytes::from_static(b"pager\0less")).is_err()); | |
158 | } |
|
195 | } | |
159 |
|
196 | |||
160 | #[test] |
|
197 | #[test] | |
161 | fn parse_command_spec_malformed_env() { |
|
198 | fn parse_command_spec_malformed_env() { | |
162 | assert!(parse_command_spec(Bytes::from_static(b"pager\0less\0/tmp\0HOME")).is_err()); |
|
199 | assert!(parse_command_spec(Bytes::from_static(b"pager\0less\0/tmp\0HOME")).is_err()); | |
163 | } |
|
200 | } | |
164 |
|
201 | |||
165 | #[test] |
|
202 | #[test] | |
166 | fn parse_command_spec_unknown_type() { |
|
203 | fn parse_command_spec_unknown_type() { | |
167 | assert!(parse_command_spec(Bytes::from_static(b"paper\0less")).is_err()); |
|
204 | assert!(parse_command_spec(Bytes::from_static(b"paper\0less")).is_err()); | |
168 | } |
|
205 | } | |
169 |
|
206 | |||
170 | #[test] |
|
207 | #[test] | |
|
208 | fn parse_instructions_good() { | |||
|
209 | let src = [ | |||
|
210 | b"exit 123".as_ref(), | |||
|
211 | b"reconnect".as_ref(), | |||
|
212 | b"redirect /whatever".as_ref(), | |||
|
213 | b"unlink /someother".as_ref(), | |||
|
214 | ] | |||
|
215 | .join(&0); | |||
|
216 | let insts = vec![ | |||
|
217 | Instruction::Exit(123), | |||
|
218 | Instruction::Reconnect, | |||
|
219 | Instruction::Redirect(path_buf_from(b"/whatever")), | |||
|
220 | Instruction::Unlink(path_buf_from(b"/someother")), | |||
|
221 | ]; | |||
|
222 | assert_eq!(parse_instructions(Bytes::from(src)).unwrap(), insts); | |||
|
223 | } | |||
|
224 | ||||
|
225 | #[test] | |||
|
226 | fn parse_instructions_empty() { | |||
|
227 | assert_eq!(parse_instructions(Bytes::new()).unwrap(), vec![]); | |||
|
228 | assert_eq!( | |||
|
229 | parse_instructions(Bytes::from_static(b"\0")).unwrap(), | |||
|
230 | vec![] | |||
|
231 | ); | |||
|
232 | } | |||
|
233 | ||||
|
234 | #[test] | |||
|
235 | fn parse_instructions_malformed_exit_code() { | |||
|
236 | assert!(parse_instructions(Bytes::from_static(b"exit foo")).is_err()); | |||
|
237 | } | |||
|
238 | ||||
|
239 | #[test] | |||
|
240 | fn parse_instructions_missing_argument() { | |||
|
241 | assert!(parse_instructions(Bytes::from_static(b"exit")).is_err()); | |||
|
242 | assert!(parse_instructions(Bytes::from_static(b"redirect")).is_err()); | |||
|
243 | assert!(parse_instructions(Bytes::from_static(b"unlink")).is_err()); | |||
|
244 | } | |||
|
245 | ||||
|
246 | #[test] | |||
|
247 | fn parse_instructions_unknown_command() { | |||
|
248 | assert!(parse_instructions(Bytes::from_static(b"quit 0")).is_err()); | |||
|
249 | } | |||
|
250 | ||||
|
251 | #[test] | |||
171 | fn pack_env_vars_os_good() { |
|
252 | fn pack_env_vars_os_good() { | |
172 | assert_eq!( |
|
253 | assert_eq!( | |
173 | pack_env_vars_os(vec![] as Vec<(OsString, OsString)>), |
|
254 | pack_env_vars_os(vec![] as Vec<(OsString, OsString)>), | |
174 | Bytes::new() |
|
255 | Bytes::new() | |
175 | ); |
|
256 | ); | |
176 | assert_eq!( |
|
257 | assert_eq!( | |
177 | pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"bar")]), |
|
258 | pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"bar")]), | |
178 | Bytes::from_static(b"FOO=bar") |
|
259 | Bytes::from_static(b"FOO=bar") | |
179 | ); |
|
260 | ); | |
180 | assert_eq!( |
|
261 | assert_eq!( | |
181 | pack_env_vars_os(vec![ |
|
262 | pack_env_vars_os(vec![ | |
182 | os_string_pair_from(b"FOO", b""), |
|
263 | os_string_pair_from(b"FOO", b""), | |
183 | os_string_pair_from(b"BAR", b"baz") |
|
264 | os_string_pair_from(b"BAR", b"baz") | |
184 | ]), |
|
265 | ]), | |
185 | Bytes::from_static(b"FOO=\0BAR=baz") |
|
266 | Bytes::from_static(b"FOO=\0BAR=baz") | |
186 | ); |
|
267 | ); | |
187 | } |
|
268 | } | |
188 |
|
269 | |||
189 | #[test] |
|
270 | #[test] | |
190 | fn pack_env_vars_os_large_key() { |
|
271 | fn pack_env_vars_os_large_key() { | |
191 | let mut buf = vec![b'A'; INITIAL_PACKED_ENV_VARS_CAPACITY]; |
|
272 | let mut buf = vec![b'A'; INITIAL_PACKED_ENV_VARS_CAPACITY]; | |
192 | let envs = vec![os_string_pair_from(&buf, b"")]; |
|
273 | let envs = vec![os_string_pair_from(&buf, b"")]; | |
193 | buf.push(b'='); |
|
274 | buf.push(b'='); | |
194 | assert_eq!(pack_env_vars_os(envs), Bytes::from(buf)); |
|
275 | assert_eq!(pack_env_vars_os(envs), Bytes::from(buf)); | |
195 | } |
|
276 | } | |
196 |
|
277 | |||
197 | #[test] |
|
278 | #[test] | |
198 | fn pack_env_vars_os_large_value() { |
|
279 | fn pack_env_vars_os_large_value() { | |
199 | let mut buf = vec![b'A', b'=']; |
|
280 | let mut buf = vec![b'A', b'=']; | |
200 | buf.resize(INITIAL_PACKED_ENV_VARS_CAPACITY + 1, b'a'); |
|
281 | buf.resize(INITIAL_PACKED_ENV_VARS_CAPACITY + 1, b'a'); | |
201 | let envs = vec![os_string_pair_from(&buf[..1], &buf[2..])]; |
|
282 | let envs = vec![os_string_pair_from(&buf[..1], &buf[2..])]; | |
202 | assert_eq!(pack_env_vars_os(envs), Bytes::from(buf)); |
|
283 | assert_eq!(pack_env_vars_os(envs), Bytes::from(buf)); | |
203 | } |
|
284 | } | |
204 |
|
285 | |||
205 | #[test] |
|
286 | #[test] | |
206 | fn pack_env_vars_os_nul_eq() { |
|
287 | fn pack_env_vars_os_nul_eq() { | |
207 | assert!(panic::catch_unwind(|| { |
|
288 | assert!(panic::catch_unwind(|| { | |
208 | pack_env_vars_os(vec![os_string_pair_from(b"\0", b"")]) |
|
289 | pack_env_vars_os(vec![os_string_pair_from(b"\0", b"")]) | |
209 | }) |
|
290 | }) | |
210 | .is_err()); |
|
291 | .is_err()); | |
211 | assert!(panic::catch_unwind(|| { |
|
292 | assert!(panic::catch_unwind(|| { | |
212 | pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"\0bar")]) |
|
293 | pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"\0bar")]) | |
213 | }) |
|
294 | }) | |
214 | .is_err()); |
|
295 | .is_err()); | |
215 | assert!(panic::catch_unwind(|| { |
|
296 | assert!(panic::catch_unwind(|| { | |
216 | pack_env_vars_os(vec![os_string_pair_from(b"FO=", b"bar")]) |
|
297 | pack_env_vars_os(vec![os_string_pair_from(b"FO=", b"bar")]) | |
217 | }) |
|
298 | }) | |
218 | .is_err()); |
|
299 | .is_err()); | |
219 | assert_eq!( |
|
300 | assert_eq!( | |
220 | pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"=ba")]), |
|
301 | pack_env_vars_os(vec![os_string_pair_from(b"FOO", b"=ba")]), | |
221 | Bytes::from_static(b"FOO==ba") |
|
302 | Bytes::from_static(b"FOO==ba") | |
222 | ); |
|
303 | ); | |
223 | } |
|
304 | } | |
224 |
|
305 | |||
225 | fn os_string_from(s: &[u8]) -> OsString { |
|
306 | fn os_string_from(s: &[u8]) -> OsString { | |
226 | OsString::from_vec(s.to_vec()) |
|
307 | OsString::from_vec(s.to_vec()) | |
227 | } |
|
308 | } | |
228 |
|
309 | |||
229 | fn os_string_pair_from(k: &[u8], v: &[u8]) -> (OsString, OsString) { |
|
310 | fn os_string_pair_from(k: &[u8], v: &[u8]) -> (OsString, OsString) { | |
230 | (os_string_from(k), os_string_from(v)) |
|
311 | (os_string_from(k), os_string_from(v)) | |
231 | } |
|
312 | } | |
|
313 | ||||
|
314 | fn path_buf_from(s: &[u8]) -> PathBuf { | |||
|
315 | os_string_from(s).into() | |||
|
316 | } | |||
232 | } |
|
317 | } |
General Comments 0
You need to be logged in to leave comments.
Login now