async_imap/extensions/
id.rs1use async_channel as channel;
4use futures::io;
5use futures::prelude::*;
6use imap_proto::{self, RequestId, Response};
7use std::collections::HashMap;
8
9use crate::types::ResponseData;
10use crate::types::*;
11use crate::{
12 error::Result,
13 parse::{filter, handle_unilateral},
14};
15
16fn escape(s: &str) -> String {
17 s.replace('\\', r"\\").replace('\"', "\\\"")
18}
19
20pub(crate) fn format_identification<'a, 'b>(
24 id: impl IntoIterator<Item = (&'a str, Option<&'b str>)>,
25) -> String {
26 id.into_iter()
27 .map(|(k, v)| {
28 format!(
29 "\"{}\" {}",
30 escape(k),
31 v.map_or("NIL".to_string(), |v| format!("\"{}\"", escape(v)))
32 )
33 })
34 .collect::<Vec<String>>()
35 .join(" ")
36}
37
38pub(crate) async fn parse_id<T: Stream<Item = io::Result<ResponseData>> + Unpin>(
39 stream: &mut T,
40 unsolicited: channel::Sender<UnsolicitedResponse>,
41 command_tag: RequestId,
42) -> Result<Option<HashMap<String, String>>> {
43 let mut id = None;
44 while let Some(resp) = stream
45 .take_while(|res| filter(res, &command_tag))
46 .next()
47 .await
48 {
49 let resp = resp?;
50 match resp.parsed() {
51 Response::Id(res) => {
52 id = res.as_ref().map(|m| {
53 m.iter()
54 .map(|(k, v)| (k.to_string(), v.to_string()))
55 .collect()
56 })
57 }
58 _ => {
59 handle_unilateral(resp, unsolicited.clone());
60 }
61 }
62 }
63
64 Ok(id)
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_format_identification() {
73 assert_eq!(
74 format_identification([("name", Some("MyClient"))]),
75 r#""name" "MyClient""#
76 );
77
78 assert_eq!(
79 format_identification([("name", Some(r#""MyClient"\"#))]),
80 r#""name" "\"MyClient\"\\""#
81 );
82
83 assert_eq!(
84 format_identification([("name", Some("MyClient")), ("version", Some("2.0"))]),
85 r#""name" "MyClient" "version" "2.0""#
86 );
87
88 assert_eq!(
89 format_identification([("name", None), ("version", Some("2.0"))]),
90 r#""name" NIL "version" "2.0""#
91 );
92 }
93}