1use std::{collections::HashMap, str::FromStr};
3
4use snafu::ResultExt;
5
6pub fn parse_hashmap(input: Vec<String>, unescape: bool) -> HashMap<String, Option<String>> {
25 let mut map: HashMap<String, Option<String>> = HashMap::new();
26 input.into_iter().for_each(|s| {
27 parse_single_line_hashmap(&s, &mut map, unescape);
28 });
29 map
30}
31
32fn parse_single_line_hashmap(
34 line: &str,
35 map: &mut HashMap<String, Option<String>>,
36 unescape: bool,
37) {
38 line.split_whitespace().for_each(|e| {
39 let mut entries = e.split('=');
40 if let (Some(k), Some(v)) = (entries.next(), entries.next()) {
41 let v = if unescape {
42 unescape_val(v)
43 } else {
44 v.to_string()
45 };
46 map.insert(k.to_string(), Some(v));
47 } else if !e.is_empty() {
48 map.insert(e.to_string(), None);
49 }
50 });
51}
52
53pub fn parse_multi_hashmap(
62 input: Vec<String>,
63 unescape: bool,
64) -> Vec<HashMap<String, Option<String>>> {
65 let v: Vec<HashMap<String, Option<String>>> = input
66 .into_iter()
67 .map(|l| {
68 l.split('|')
69 .map(|s| {
70 let mut map = HashMap::new();
71 parse_single_line_hashmap(s, &mut map, unescape);
72 map
73 })
74 .collect::<Vec<HashMap<String, Option<String>>>>()
75 })
76 .flatten()
77 .collect();
78 v
79}
80
81pub fn escape_arg<T: AsRef<str>>(input: T) -> String {
83 let res: Vec<u8> = Escape::new(input.as_ref().bytes()).collect();
84 String::from_utf8(res).unwrap()
85}
86
87pub fn unescape_val<T: AsRef<str>>(it: T) -> String {
89 let mut res: Vec<u8> = Vec::new();
90 let mut escaped = false;
91 for n in it.as_ref().as_bytes().iter() {
92 if !escaped && *n == b'\\' {
93 escaped = true;
94 } else if escaped {
95 let ch = match n {
96 b's' => b' ',
97 b'p' => b'|',
98 b'a' => 7,
99 b'b' => 8,
100 b'f' => 12,
101 b'n' => b'\n',
102 b'r' => b'\r',
103 b't' => b'\t',
104 b'v' => 11,
105 _ => *n, };
107 res.push(ch);
108 escaped = false;
109 } else {
110 res.push(*n);
111 }
112 }
113 unsafe {
114 String::from_utf8_unchecked(res)
116 }
117}
118
119const LONGEST_ESCAPE: usize = 2;
120
121#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
125struct Escape<I: Iterator<Item = u8>> {
126 inner: I,
127 buffer: u8,
128}
129
130impl<I: Iterator<Item = u8>> Escape<I> {
131 pub fn new(i: I) -> Escape<I> {
133 Escape {
134 inner: i,
135 buffer: 0,
136 }
137 }
138}
139
140impl<I: Iterator<Item = u8>> Iterator for Escape<I> {
141 type Item = u8;
142
143 fn next(&mut self) -> Option<u8> {
144 if self.buffer != 0 {
145 let ret = Some(self.buffer as u8);
146 self.buffer = 0;
147 ret
148 } else if let Some(ch) = self.inner.next() {
149 match ch {
150 b'\\' | b'/' => {
153 self.buffer = ch;
154 Some(b'\\')
155 }
156 b' ' => {
157 self.buffer = b's';
158 Some(b'\\')
159 }
160 b'|' => {
161 self.buffer = b'p';
162 Some(b'\\')
163 }
164 7 => {
165 self.buffer = b'a';
166 Some(b'\\')
167 }
168 8 => {
169 self.buffer = b'b';
170 Some(b'\\')
171 }
172 12 => {
173 self.buffer = b'f';
174 Some(b'\\')
175 }
176 b'\n' => {
177 self.buffer = b'n';
178 Some(b'\\')
179 }
180 b'\r' => {
181 self.buffer = b'r';
182 Some(b'\\')
183 }
184 b'\t' => {
185 self.buffer = b't';
186 Some(b'\\')
187 }
188 11 => {
189 self.buffer = b'v';
190 Some(b'\\')
191 }
192 _ => Some(ch),
193 }
194 } else {
195 None
196 }
197 }
198
199 fn size_hint(&self) -> (usize, Option<usize>) {
200 let (l, u) = self.inner.size_hint();
201 (
202 l,
203 if let Some(u_) = u {
204 u_.checked_mul(LONGEST_ESCAPE)
205 } else {
206 None
207 },
208 )
209 }
210}
211
212pub fn int_list_val_parser<T>(
226 data: &mut HashMap<String, Option<String>>,
227 key: &'static str,
228) -> crate::Result<Vec<T>>
229where
230 T: FromStr<Err = std::num::ParseIntError>,
231{
232 let v = string_val_parser(data, key)?;
233 let values: Vec<T> = v
234 .split(",")
235 .map(|v| {
236 v.parse::<T>()
237 .with_context(|| crate::InvalidIntResponse { data: v })
238 })
239 .collect::<crate::Result<Vec<T>>>()?;
240
241 Ok(values)
242}
243
244pub fn bool_val_parser(
259 data: &mut HashMap<String, Option<String>>,
260 key: &'static str,
261) -> crate::Result<bool> {
262 let val: i32 = int_val_parser(data, key)?;
263 Ok(val > 0)
264}
265
266pub fn string_val_parser_opt(
280 data: &mut HashMap<String, Option<String>>,
281 key: &'static str,
282) -> crate::Result<Option<String>> {
283 Ok(data
284 .remove(key)
285 .ok_or_else(|| crate::NoEntryResponse { key }.build())?
286 .map(unescape_val))
287}
288
289pub fn int_val_parser_opt<T>(
303 data: &mut HashMap<String, Option<String>>,
304 key: &'static str,
305) -> crate::Result<Option<T>>
306where
307 T: FromStr<Err = std::num::ParseIntError>,
308{
309 let v = data
310 .remove(key)
311 .ok_or_else(|| crate::NoEntryResponse { key }.build())?;
312
313 if let Some(v) = v {
314 return Ok(Some(
315 v.parse()
316 .with_context(|| crate::InvalidIntResponse { data: v })?,
317 ));
318 } else {
319 return Ok(None);
320 }
321}
322
323pub fn int_val_parser<T>(
337 data: &mut HashMap<String, Option<String>>,
338 key: &'static str,
339) -> crate::Result<T>
340where
341 T: FromStr<Err = std::num::ParseIntError>,
342{
343 let v = data
344 .remove(key)
345 .ok_or_else(|| crate::NoEntryResponse { key }.build())?
346 .ok_or_else(|| crate::NoValueResponse { key }.build())?;
347 Ok(v.parse()
348 .with_context(|| crate::InvalidIntResponse { data: v })?)
349}
350
351pub fn string_val_parser(
365 data: &mut HashMap<String, Option<String>>,
366 key: &'static str,
367) -> crate::Result<String> {
368 Ok(string_val_parser_opt(data, key)?.ok_or_else(|| crate::NoValueResponse { key }.build())?)
369}
370
371#[cfg(test)]
372mod test {
373 use super::*;
374 #[test]
379 pub fn test_escaped_input() {
380 let v: Vec<u8> = vec![b'\\', b'/', 7, 8, 12, 11, b'\t', b'\r', b'\n'];
381
382 assert_eq!(true, String::from_utf8(v).is_ok());
383 }
384
385 #[test]
386 pub fn verify_single_map() {
387 let v = "clid=1776 client_database_id=18106 client_nickname=FOOBAR\\s\\p\\sNora\\s\\p\\sLaptop client_type=1";
388 let mut map = HashMap::new();
389 parse_single_line_hashmap(v, &mut map, false);
390 assert_eq!(
391 Some("1776"),
392 map.get("clid")
393 .map(|v| v.as_ref().map(|v| v.as_str()))
394 .flatten()
395 );
396 assert_eq!(
397 Some("18106"),
398 map.get("client_database_id")
399 .map(|v| v.as_ref().map(|v| v.as_str()))
400 .flatten()
401 );
402 assert_eq!(
403 Some("FOOBAR\\s\\p\\sNora\\s\\p\\sLaptop"),
404 map.get("client_nickname")
405 .map(|v| v.as_ref().map(|v| v.as_str()))
406 .flatten()
407 );
408 assert_eq!(
409 Some("1"),
410 map.get("client_type")
411 .map(|v| v.as_ref().map(|v| v.as_str()))
412 .flatten()
413 );
414 assert_eq!(map, parse_hashmap(vec![v.to_string()], false));
416
417 let mut map = HashMap::new();
418 parse_single_line_hashmap(v, &mut map, true);
419 assert_eq!(
420 Some("1776"),
421 map.get("clid")
422 .map(|v| v.as_ref().map(|v| v.as_str()))
423 .flatten()
424 );
425 assert_eq!(
426 Some("18106"),
427 map.get("client_database_id")
428 .map(|v| v.as_ref().map(|v| v.as_str()))
429 .flatten()
430 );
431 assert_eq!(
432 Some(r#"FOOBAR | Nora | Laptop"#),
433 map.get("client_nickname")
434 .map(|v| v.as_ref().map(|v| v.as_str()))
435 .flatten()
436 );
437 assert_eq!(
438 Some("1"),
439 map.get("client_type")
440 .map(|v| v.as_ref().map(|v| v.as_str()))
441 .flatten()
442 );
443 assert_eq!(map, parse_hashmap(vec![v.to_string()], true));
445 }
446
447 #[test]
448 pub fn verify_single_map_optional() {
449 let v = "client_type=123 client_away=456 client_away_message client_flag_talking=789";
450 let mut map = HashMap::new();
451 parse_single_line_hashmap(v, &mut map, false);
452
453 let mut expected = HashMap::new();
454 expected.insert("client_type".to_string(), Some("123".to_string()));
455 expected.insert("client_away".to_string(), Some("456".to_string()));
456 expected.insert("client_away_message".to_string(), None);
457 expected.insert("client_flag_talking".to_string(), Some("789".to_string()));
458
459 assert_eq!(map, expected);
460 }
461
462 #[test]
463 pub fn verify_multi_map() {
464 let v = "clid=1776 client_database_id=18106|client_nickname=FOOBAR\\s\\p\\sNora\\s\\p\\sLaptop client_type=1";
465 let result = parse_multi_hashmap(vec![v.to_string()], true);
466 let first = &result[0];
467 assert_eq!(
468 Some("1776"),
469 first
470 .get("clid")
471 .map(|v| v.as_ref().map(|v| v.as_str()))
472 .flatten()
473 );
474 assert_eq!(
475 Some("18106"),
476 first
477 .get("client_database_id")
478 .map(|v| v.as_ref().map(|v| v.as_str()))
479 .flatten()
480 );
481 let second = &result[1];
482 assert_eq!(
483 Some(r#"FOOBAR | Nora | Laptop"#),
484 second
485 .get("client_nickname")
486 .map(|v| v.as_ref().map(|v| v.as_str()))
487 .flatten()
488 );
489 assert_eq!(
490 Some("1"),
491 second
492 .get("client_type")
493 .map(|v| v.as_ref().map(|v| v.as_str()))
494 .flatten()
495 );
496 }
497}