1use std::collections::{HashMap, HashSet};
2use std::fmt;
3use std::ops::{Deref, DerefMut};
4use std::pin::Pin;
5use std::str;
6
7use async_channel::{self as channel, bounded};
8#[cfg(feature = "runtime-async-std")]
9use async_std::io::{Read, Write, WriteExt};
10use base64::Engine as _;
11use extensions::id::{format_identification, parse_id};
12use extensions::quota::parse_get_quota_root;
13use futures::{io, Stream, StreamExt};
14use imap_proto::{Metadata, RequestId, Response};
15#[cfg(feature = "runtime-tokio")]
16use tokio::io::{AsyncRead as Read, AsyncWrite as Write, AsyncWriteExt};
17
18use super::authenticator::Authenticator;
19use super::error::{Error, ParseError, Result, ValidateError};
20use super::parse::*;
21use super::types::*;
22use crate::extensions::{self, quota::parse_get_quota};
23use crate::imap_stream::ImapStream;
24
25macro_rules! quote {
26 ($x:expr) => {
27 format!("\"{}\"", $x.replace(r"\", r"\\").replace("\"", "\\\""))
28 };
29}
30
31#[derive(Debug)]
41pub struct Session<T: Read + Write + Unpin + fmt::Debug> {
42 pub(crate) conn: Connection<T>,
43 pub(crate) unsolicited_responses_tx: channel::Sender<UnsolicitedResponse>,
44
45 pub unsolicited_responses: channel::Receiver<UnsolicitedResponse>,
48}
49
50impl<T: Read + Write + Unpin + fmt::Debug> Unpin for Session<T> {}
51impl<T: Read + Write + Unpin + fmt::Debug> Unpin for Client<T> {}
52impl<T: Read + Write + Unpin + fmt::Debug> Unpin for Connection<T> {}
53
54impl<T: Read + Write + Unpin + fmt::Debug> AsMut<T> for Session<T> {
57 fn as_mut(&mut self) -> &mut T {
58 self.conn.stream.as_mut()
59 }
60}
61
62#[derive(Debug)]
70pub struct Client<T: Read + Write + Unpin + fmt::Debug> {
71 conn: Connection<T>,
72}
73
74#[derive(Debug)]
77pub struct Connection<T: Read + Write + Unpin + fmt::Debug> {
78 pub(crate) stream: ImapStream<T>,
79
80 pub(crate) request_ids: IdGenerator,
82}
83
84impl<T: Read + Write + Unpin + fmt::Debug> Deref for Client<T> {
87 type Target = Connection<T>;
88
89 fn deref(&self) -> &Connection<T> {
90 &self.conn
91 }
92}
93
94impl<T: Read + Write + Unpin + fmt::Debug> DerefMut for Client<T> {
95 fn deref_mut(&mut self) -> &mut Connection<T> {
96 &mut self.conn
97 }
98}
99
100impl<T: Read + Write + Unpin + fmt::Debug> Deref for Session<T> {
101 type Target = Connection<T>;
102
103 fn deref(&self) -> &Connection<T> {
104 &self.conn
105 }
106}
107
108impl<T: Read + Write + Unpin + fmt::Debug> DerefMut for Session<T> {
109 fn deref_mut(&mut self) -> &mut Connection<T> {
110 &mut self.conn
111 }
112}
113
114macro_rules! ok_or_unauth_client_err {
122 ($r:expr, $self:expr) => {
123 match $r {
124 Ok(o) => o,
125 Err(e) => return Err((e, $self)),
126 }
127 };
128}
129
130impl<T: Read + Write + Unpin + fmt::Debug + Send> Client<T> {
131 pub fn new(stream: T) -> Client<T> {
136 let stream = ImapStream::new(stream);
137
138 Client {
139 conn: Connection {
140 stream,
141 request_ids: IdGenerator::new(),
142 },
143 }
144 }
145
146 pub fn into_inner(self) -> T {
148 let Self { conn, .. } = self;
149 conn.into_inner()
150 }
151
152 pub fn into_session(self) -> Session<T> {
158 Session::new(self.conn)
159 }
160
161 pub async fn login<U: AsRef<str>, P: AsRef<str>>(
193 mut self,
194 username: U,
195 password: P,
196 ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
197 let u = ok_or_unauth_client_err!(validate_str(username.as_ref()), self);
198 let p = ok_or_unauth_client_err!(validate_str(password.as_ref()), self);
199 ok_or_unauth_client_err!(
200 self.run_command_and_check_ok(&format!("LOGIN {} {}", u, p), None)
201 .await,
202 self
203 );
204
205 Ok(self.into_session())
206 }
207
208 pub async fn authenticate<A: Authenticator, S: AsRef<str>>(
252 mut self,
253 auth_type: S,
254 authenticator: A,
255 ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
256 let id = ok_or_unauth_client_err!(
257 self.run_command(&format!("AUTHENTICATE {}", auth_type.as_ref()))
258 .await,
259 self
260 );
261 let session = self.do_auth_handshake(id, authenticator).await?;
262 Ok(session)
263 }
264
265 async fn do_auth_handshake<A: Authenticator>(
267 mut self,
268 id: RequestId,
269 mut authenticator: A,
270 ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
271 loop {
274 if let Some(res) = self.read_response().await {
275 let res = ok_or_unauth_client_err!(res.map_err(Into::into), self);
276 match res.parsed() {
277 Response::Continue { information, .. } => {
278 let challenge = if let Some(text) = information {
279 ok_or_unauth_client_err!(
280 base64::engine::general_purpose::STANDARD
281 .decode(text.as_ref())
282 .map_err(|e| Error::Parse(ParseError::Authentication(
283 (*text).to_string(),
284 Some(e)
285 ))),
286 self
287 )
288 } else {
289 Vec::new()
290 };
291 let raw_response = &mut authenticator.process(&challenge);
292 let auth_response =
293 base64::engine::general_purpose::STANDARD.encode(raw_response);
294
295 ok_or_unauth_client_err!(
296 self.conn.run_command_untagged(&auth_response).await,
297 self
298 );
299 }
300 _ => {
301 ok_or_unauth_client_err!(
302 self.check_done_ok_from(&id, None, res).await,
303 self
304 );
305 return Ok(self.into_session());
306 }
307 }
308 } else {
309 return Err((Error::ConnectionLost, self));
310 }
311 }
312 }
313}
314
315impl<T: Read + Write + Unpin + fmt::Debug + Send> Session<T> {
316 unsafe_pinned!(conn: Connection<T>);
317
318 pub(crate) fn get_stream(self: Pin<&mut Self>) -> Pin<&mut ImapStream<T>> {
319 self.conn().stream()
320 }
321
322 fn new(conn: Connection<T>) -> Self {
324 let (tx, rx) = bounded(100);
325 Session {
326 conn,
327 unsolicited_responses: rx,
328 unsolicited_responses_tx: tx,
329 }
330 }
331
332 pub async fn select<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
350 let id = self
352 .run_command(&format!("SELECT {}", validate_str(mailbox_name.as_ref())?))
353 .await?;
354 let mbox = parse_mailbox(
355 &mut self.conn.stream,
356 self.unsolicited_responses_tx.clone(),
357 id,
358 )
359 .await?;
360
361 Ok(mbox)
362 }
363
364 pub async fn select_condstore<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
367 let id = self
368 .run_command(&format!(
369 "SELECT {} (CONDSTORE)",
370 validate_str(mailbox_name.as_ref())?
371 ))
372 .await?;
373 let mbox = parse_mailbox(
374 &mut self.conn.stream,
375 self.unsolicited_responses_tx.clone(),
376 id,
377 )
378 .await?;
379
380 Ok(mbox)
381 }
382
383 pub async fn examine<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
388 let id = self
389 .run_command(&format!("EXAMINE {}", validate_str(mailbox_name.as_ref())?))
390 .await?;
391 let mbox = parse_mailbox(
392 &mut self.conn.stream,
393 self.unsolicited_responses_tx.clone(),
394 id,
395 )
396 .await?;
397
398 Ok(mbox)
399 }
400
401 pub async fn fetch<S1, S2>(
460 &mut self,
461 sequence_set: S1,
462 query: S2,
463 ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
464 where
465 S1: AsRef<str>,
466 S2: AsRef<str>,
467 {
468 let id = self
469 .run_command(&format!(
470 "FETCH {} {}",
471 sequence_set.as_ref(),
472 query.as_ref()
473 ))
474 .await?;
475 let res = parse_fetches(
476 &mut self.conn.stream,
477 self.unsolicited_responses_tx.clone(),
478 id,
479 );
480
481 Ok(res)
482 }
483
484 pub async fn uid_fetch<S1, S2>(
487 &mut self,
488 uid_set: S1,
489 query: S2,
490 ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send + Unpin>
491 where
492 S1: AsRef<str>,
493 S2: AsRef<str>,
494 {
495 let id = self
496 .run_command(&format!(
497 "UID FETCH {} {}",
498 uid_set.as_ref(),
499 query.as_ref()
500 ))
501 .await?;
502 let res = parse_fetches(
503 &mut self.conn.stream,
504 self.unsolicited_responses_tx.clone(),
505 id,
506 );
507 Ok(res)
508 }
509
510 pub async fn noop(&mut self) -> Result<()> {
512 let id = self.run_command("NOOP").await?;
513 parse_noop(
514 &mut self.conn.stream,
515 self.unsolicited_responses_tx.clone(),
516 id,
517 )
518 .await?;
519 Ok(())
520 }
521
522 pub async fn logout(&mut self) -> Result<()> {
524 self.run_command_and_check_ok("LOGOUT").await?;
525 Ok(())
526 }
527
528 pub async fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<()> {
551 self.run_command_and_check_ok(&format!("CREATE {}", validate_str(mailbox_name.as_ref())?))
552 .await?;
553
554 Ok(())
555 }
556
557 pub async fn delete<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<()> {
577 self.run_command_and_check_ok(&format!("DELETE {}", validate_str(mailbox_name.as_ref())?))
578 .await?;
579
580 Ok(())
581 }
582
583 pub async fn rename<S1: AsRef<str>, S2: AsRef<str>>(&mut self, from: S1, to: S2) -> Result<()> {
609 self.run_command_and_check_ok(&format!(
610 "RENAME {} {}",
611 quote!(from.as_ref()),
612 quote!(to.as_ref())
613 ))
614 .await?;
615
616 Ok(())
617 }
618
619 pub async fn subscribe<S: AsRef<str>>(&mut self, mailbox: S) -> Result<()> {
628 self.run_command_and_check_ok(&format!("SUBSCRIBE {}", quote!(mailbox.as_ref())))
629 .await?;
630 Ok(())
631 }
632
633 pub async fn unsubscribe<S: AsRef<str>>(&mut self, mailbox: S) -> Result<()> {
638 self.run_command_and_check_ok(&format!("UNSUBSCRIBE {}", quote!(mailbox.as_ref())))
639 .await?;
640 Ok(())
641 }
642
643 pub async fn capabilities(&mut self) -> Result<Capabilities> {
647 let id = self.run_command("CAPABILITY").await?;
648 let c = parse_capabilities(
649 &mut self.conn.stream,
650 self.unsolicited_responses_tx.clone(),
651 id,
652 )
653 .await?;
654 Ok(c)
655 }
656
657 pub async fn expunge(&mut self) -> Result<impl Stream<Item = Result<Seq>> + '_ + Send> {
661 let id = self.run_command("EXPUNGE").await?;
662 let res = parse_expunge(
663 &mut self.conn.stream,
664 self.unsolicited_responses_tx.clone(),
665 id,
666 );
667 Ok(res)
668 }
669
670 pub async fn uid_expunge<S: AsRef<str>>(
693 &mut self,
694 uid_set: S,
695 ) -> Result<impl Stream<Item = Result<Uid>> + '_ + Send> {
696 let id = self
697 .run_command(&format!("UID EXPUNGE {}", uid_set.as_ref()))
698 .await?;
699 let res = parse_expunge(
700 &mut self.conn.stream,
701 self.unsolicited_responses_tx.clone(),
702 id,
703 );
704 Ok(res)
705 }
706
707 pub async fn check(&mut self) -> Result<()> {
718 self.run_command_and_check_ok("CHECK").await?;
719 Ok(())
720 }
721
722 pub async fn close(&mut self) -> Result<()> {
738 self.run_command_and_check_ok("CLOSE").await?;
739 Ok(())
740 }
741
742 pub async fn store<S1, S2>(
792 &mut self,
793 sequence_set: S1,
794 query: S2,
795 ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
796 where
797 S1: AsRef<str>,
798 S2: AsRef<str>,
799 {
800 let id = self
801 .run_command(&format!(
802 "STORE {} {}",
803 sequence_set.as_ref(),
804 query.as_ref()
805 ))
806 .await?;
807 let res = parse_fetches(
808 &mut self.conn.stream,
809 self.unsolicited_responses_tx.clone(),
810 id,
811 );
812 Ok(res)
813 }
814
815 pub async fn uid_store<S1, S2>(
818 &mut self,
819 uid_set: S1,
820 query: S2,
821 ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
822 where
823 S1: AsRef<str>,
824 S2: AsRef<str>,
825 {
826 let id = self
827 .run_command(&format!(
828 "UID STORE {} {}",
829 uid_set.as_ref(),
830 query.as_ref()
831 ))
832 .await?;
833 let res = parse_fetches(
834 &mut self.conn.stream,
835 self.unsolicited_responses_tx.clone(),
836 id,
837 );
838 Ok(res)
839 }
840
841 pub async fn copy<S1: AsRef<str>, S2: AsRef<str>>(
849 &mut self,
850 sequence_set: S1,
851 mailbox_name: S2,
852 ) -> Result<()> {
853 self.run_command_and_check_ok(&format!(
854 "COPY {} {}",
855 sequence_set.as_ref(),
856 mailbox_name.as_ref()
857 ))
858 .await?;
859
860 Ok(())
861 }
862
863 pub async fn uid_copy<S1: AsRef<str>, S2: AsRef<str>>(
866 &mut self,
867 uid_set: S1,
868 mailbox_name: S2,
869 ) -> Result<()> {
870 self.run_command_and_check_ok(&format!(
871 "UID COPY {} {}",
872 uid_set.as_ref(),
873 mailbox_name.as_ref()
874 ))
875 .await?;
876
877 Ok(())
878 }
879
880 pub async fn mv<S1: AsRef<str>, S2: AsRef<str>>(
911 &mut self,
912 sequence_set: S1,
913 mailbox_name: S2,
914 ) -> Result<()> {
915 self.run_command_and_check_ok(&format!(
916 "MOVE {} {}",
917 sequence_set.as_ref(),
918 validate_str(mailbox_name.as_ref())?
919 ))
920 .await?;
921
922 Ok(())
923 }
924
925 pub async fn uid_mv<S1: AsRef<str>, S2: AsRef<str>>(
930 &mut self,
931 uid_set: S1,
932 mailbox_name: S2,
933 ) -> Result<()> {
934 self.run_command_and_check_ok(&format!(
935 "UID MOVE {} {}",
936 uid_set.as_ref(),
937 validate_str(mailbox_name.as_ref())?
938 ))
939 .await?;
940
941 Ok(())
942 }
943
944 pub async fn list(
976 &mut self,
977 reference_name: Option<&str>,
978 mailbox_pattern: Option<&str>,
979 ) -> Result<impl Stream<Item = Result<Name>> + '_ + Send> {
980 let id = self
981 .run_command(&format!(
982 "LIST {} {}",
983 quote!(reference_name.unwrap_or("")),
984 mailbox_pattern.unwrap_or("\"\"")
985 ))
986 .await?;
987
988 Ok(parse_names(
989 &mut self.conn.stream,
990 self.unsolicited_responses_tx.clone(),
991 id,
992 ))
993 }
994
995 pub async fn lsub(
1011 &mut self,
1012 reference_name: Option<&str>,
1013 mailbox_pattern: Option<&str>,
1014 ) -> Result<impl Stream<Item = Result<Name>> + '_ + Send> {
1015 let id = self
1016 .run_command(&format!(
1017 "LSUB {} {}",
1018 quote!(reference_name.unwrap_or("")),
1019 mailbox_pattern.unwrap_or("")
1020 ))
1021 .await?;
1022 let names = parse_names(
1023 &mut self.conn.stream,
1024 self.unsolicited_responses_tx.clone(),
1025 id,
1026 );
1027
1028 Ok(names)
1029 }
1030
1031 pub async fn status<S1: AsRef<str>, S2: AsRef<str>>(
1066 &mut self,
1067 mailbox_name: S1,
1068 data_items: S2,
1069 ) -> Result<Mailbox> {
1070 let id = self
1071 .run_command(&format!(
1072 "STATUS {} {}",
1073 validate_str(mailbox_name.as_ref())?,
1074 data_items.as_ref()
1075 ))
1076 .await?;
1077 let mbox = parse_status(
1078 &mut self.conn.stream,
1079 mailbox_name.as_ref(),
1080 self.unsolicited_responses_tx.clone(),
1081 id,
1082 )
1083 .await?;
1084 Ok(mbox)
1085 }
1086
1087 pub fn idle(self) -> extensions::idle::Handle<T> {
1106 extensions::idle::Handle::new(self)
1107 }
1108
1109 pub async fn append(
1129 &mut self,
1130 mailbox: impl AsRef<str>,
1131 flags: Option<&str>,
1132 internaldate: Option<&str>,
1133 content: impl AsRef<[u8]>,
1134 ) -> Result<()> {
1135 let content = content.as_ref();
1136 let id = self
1137 .run_command(&format!(
1138 "APPEND \"{}\"{}{}{}{} {{{}}}",
1139 mailbox.as_ref(),
1140 if flags.is_some() { " " } else { "" },
1141 flags.unwrap_or(""),
1142 if internaldate.is_some() { " " } else { "" },
1143 internaldate.unwrap_or(""),
1144 content.len()
1145 ))
1146 .await?;
1147
1148 match self.read_response().await {
1149 Some(Ok(res)) => {
1150 if let Response::Continue { .. } = res.parsed() {
1151 self.stream.as_mut().write_all(content).await?;
1152 self.stream.as_mut().write_all(b"\r\n").await?;
1153 self.stream.flush().await?;
1154 self.conn
1155 .check_done_ok(&id, Some(self.unsolicited_responses_tx.clone()))
1156 .await?;
1157 Ok(())
1158 } else {
1159 Err(Error::Append)
1160 }
1161 }
1162 Some(Err(err)) => Err(err.into()),
1163 _ => Err(Error::Append),
1164 }
1165 }
1166
1167 pub async fn search<S: AsRef<str>>(&mut self, query: S) -> Result<HashSet<Seq>> {
1212 let id = self
1213 .run_command(&format!("SEARCH {}", query.as_ref()))
1214 .await?;
1215 let seqs = parse_ids(
1216 &mut self.conn.stream,
1217 self.unsolicited_responses_tx.clone(),
1218 id,
1219 )
1220 .await?;
1221
1222 Ok(seqs)
1223 }
1224
1225 pub async fn uid_search<S: AsRef<str>>(&mut self, query: S) -> Result<HashSet<Uid>> {
1229 let id = self
1230 .run_command(&format!("UID SEARCH {}", query.as_ref()))
1231 .await?;
1232 let uids = parse_ids(
1233 &mut self.conn.stream,
1234 self.unsolicited_responses_tx.clone(),
1235 id,
1236 )
1237 .await?;
1238
1239 Ok(uids)
1240 }
1241
1242 pub async fn get_quota(&mut self, quota_root: &str) -> Result<Quota> {
1244 let id = self
1245 .run_command(format!("GETQUOTA {}", quote!(quota_root)))
1246 .await?;
1247 let c = parse_get_quota(
1248 &mut self.conn.stream,
1249 self.unsolicited_responses_tx.clone(),
1250 id,
1251 )
1252 .await?;
1253 Ok(c)
1254 }
1255
1256 pub async fn get_quota_root(
1258 &mut self,
1259 mailbox_name: &str,
1260 ) -> Result<(Vec<QuotaRoot>, Vec<Quota>)> {
1261 let id = self
1262 .run_command(format!("GETQUOTAROOT {}", quote!(mailbox_name)))
1263 .await?;
1264 let c = parse_get_quota_root(
1265 &mut self.conn.stream,
1266 self.unsolicited_responses_tx.clone(),
1267 id,
1268 )
1269 .await?;
1270 Ok(c)
1271 }
1272
1273 pub async fn get_metadata(
1275 &mut self,
1276 mailbox_name: &str,
1277 options: &str,
1278 entry_specifier: &str,
1279 ) -> Result<Vec<Metadata>> {
1280 let options = if options.is_empty() {
1281 String::new()
1282 } else {
1283 format!(" {options}")
1284 };
1285 let id = self
1286 .run_command(format!(
1287 "GETMETADATA {} {}{}",
1288 quote!(mailbox_name),
1289 options,
1290 entry_specifier
1291 ))
1292 .await?;
1293 let metadata = parse_metadata(
1294 &mut self.conn.stream,
1295 mailbox_name,
1296 self.unsolicited_responses_tx.clone(),
1297 id,
1298 )
1299 .await?;
1300 Ok(metadata)
1301 }
1302
1303 pub async fn id(
1307 &mut self,
1308 identification: impl IntoIterator<Item = (&str, Option<&str>)>,
1309 ) -> Result<Option<HashMap<String, String>>> {
1310 let id = self
1311 .run_command(format!("ID ({})", format_identification(identification)))
1312 .await?;
1313 let server_identification = parse_id(
1314 &mut self.conn.stream,
1315 self.unsolicited_responses_tx.clone(),
1316 id,
1317 )
1318 .await?;
1319 Ok(server_identification)
1320 }
1321
1322 pub async fn id_nil(&mut self) -> Result<Option<HashMap<String, String>>> {
1326 let id = self.run_command("ID NIL").await?;
1327 let server_identification = parse_id(
1328 &mut self.conn.stream,
1329 self.unsolicited_responses_tx.clone(),
1330 id,
1331 )
1332 .await?;
1333 Ok(server_identification)
1334 }
1335
1336 pub async fn run_command_and_check_ok<S: AsRef<str>>(&mut self, command: S) -> Result<()> {
1339 self.conn
1340 .run_command_and_check_ok(
1341 command.as_ref(),
1342 Some(self.unsolicited_responses_tx.clone()),
1343 )
1344 .await?;
1345
1346 Ok(())
1347 }
1348
1349 pub async fn run_command<S: AsRef<str>>(&mut self, command: S) -> Result<RequestId> {
1351 let id = self.conn.run_command(command.as_ref()).await?;
1352
1353 Ok(id)
1354 }
1355
1356 pub async fn run_command_untagged<S: AsRef<str>>(&mut self, command: S) -> Result<()> {
1358 self.conn.run_command_untagged(command.as_ref()).await?;
1359
1360 Ok(())
1361 }
1362
1363 pub async fn read_response(&mut self) -> Option<io::Result<ResponseData>> {
1365 self.conn.read_response().await
1366 }
1367
1368 pub async fn enable<S: AsRef<str>>(&mut self, caps: &[S]) -> Result<Capabilities> {
1370 let command = format!(
1371 "ENABLE {}",
1372 caps.iter()
1373 .map(|cap| cap.as_ref())
1374 .collect::<Vec<_>>()
1375 .join(" ")
1376 );
1377 let id = self.run_command(command).await?;
1378 parse_capabilities(
1379 &mut self.conn.stream,
1380 self.unsolicited_responses_tx.clone(),
1381 id,
1382 )
1383 .await
1384 }
1385
1386 pub async fn namespace(&mut self) -> Result<Namespace> {
1388 let responses = self.collect_responses("NAMESPACE").await?;
1389 let mut namespace = Namespace::default();
1390 for response in &responses {
1391 if let Some(raw) = response.raw_str() {
1392 if raw.starts_with("* NAMESPACE ") {
1393 namespace = parse_namespace_response(raw)?;
1394 }
1395 }
1396 }
1397 Ok(namespace)
1398 }
1399
1400 pub async fn list_status<S1: AsRef<str>, S2: AsRef<str>>(
1402 &mut self,
1403 reference_name: S1,
1404 mailbox_pattern: S2,
1405 ) -> Result<Vec<ListStatus>> {
1406 let command = format!(
1407 "LIST {} {} RETURN (STATUS (MESSAGES UNSEEN UIDNEXT UIDVALIDITY HIGHESTMODSEQ))",
1408 quote!(reference_name.as_ref()),
1409 quote!(mailbox_pattern.as_ref())
1410 );
1411 let id = self.run_command(command).await?;
1412 parse_list_status(
1413 &mut self.conn.stream,
1414 self.unsolicited_responses_tx.clone(),
1415 id,
1416 )
1417 .await
1418 }
1419
1420 pub async fn select_qresync<S1: AsRef<str>, S2: AsRef<str>>(
1422 &mut self,
1423 mailbox_name: S1,
1424 qresync_args: S2,
1425 ) -> Result<QresyncResponse> {
1426 let command = format!(
1427 "SELECT {} (QRESYNC ({}))",
1428 validate_str(mailbox_name.as_ref())?,
1429 qresync_args.as_ref()
1430 );
1431 let id = self.run_command(command).await?;
1432 parse_qresync(
1433 &mut self.conn.stream,
1434 self.unsolicited_responses_tx.clone(),
1435 id,
1436 )
1437 .await
1438 }
1439
1440 async fn collect_responses<S: AsRef<str>>(&mut self, command: S) -> Result<Vec<ResponseData>> {
1441 let id = self.run_command(command).await?;
1442 let mut responses = Vec::new();
1443
1444 while let Some(response) = self.read_response().await {
1445 let response = response?;
1446 match response.parsed() {
1447 Response::Done {
1448 tag,
1449 status,
1450 code,
1451 information,
1452 ..
1453 } if tag == &id => {
1454 self.conn
1455 .check_status_ok(status, code.as_ref(), information.as_deref())?;
1456 break;
1457 }
1458 _ => responses.push(response),
1459 }
1460 }
1461
1462 Ok(responses)
1463 }
1464}
1465
1466fn parse_namespace_response(raw: &str) -> Result<Namespace> {
1467 let payload = raw
1468 .trim()
1469 .strip_prefix("* NAMESPACE ")
1470 .ok_or_else(|| Error::Parse(ParseError::Invalid(raw.as_bytes().to_vec())))?;
1471 let sections = split_namespace_sections(payload)?;
1472 Ok(Namespace {
1473 personal: parse_namespace_group(sections.first().copied().unwrap_or("NIL")),
1474 other_users: parse_namespace_group(sections.get(1).copied().unwrap_or("NIL")),
1475 shared: parse_namespace_group(sections.get(2).copied().unwrap_or("NIL")),
1476 })
1477}
1478
1479fn split_namespace_sections(payload: &str) -> Result<Vec<&str>> {
1480 let mut sections = Vec::new();
1481 let mut depth = 0i32;
1482 let mut in_quotes = false;
1483 let bytes = payload.as_bytes();
1484 let mut start = 0usize;
1485
1486 for (idx, ch) in payload.char_indices() {
1487 match ch {
1488 '"' if idx == 0 || bytes[idx.saturating_sub(1)] != b'\\' => in_quotes = !in_quotes,
1489 '(' if !in_quotes => depth += 1,
1490 ')' if !in_quotes => depth -= 1,
1491 ' ' if !in_quotes && depth == 0 => {
1492 sections.push(payload[start..idx].trim());
1493 start = idx + 1;
1494 }
1495 _ => {}
1496 }
1497 }
1498
1499 sections.push(payload[start..].trim());
1500 if sections.len() == 3 {
1501 Ok(sections)
1502 } else {
1503 Err(Error::Parse(ParseError::Invalid(
1504 payload.as_bytes().to_vec(),
1505 )))
1506 }
1507}
1508
1509fn parse_namespace_group(section: &str) -> Vec<NamespaceEntry> {
1510 if section.eq_ignore_ascii_case("NIL") {
1511 return Vec::new();
1512 }
1513
1514 let mut entries = Vec::new();
1515 let mut rest = section;
1516 while let Some(start) = rest.find("(\"") {
1517 let slice = &rest[start + 2..];
1518 let Some(prefix_end) = slice.find('"') else {
1519 break;
1520 };
1521 let prefix = &slice[..prefix_end];
1522 let slice = &slice[prefix_end + 1..].trim_start();
1523
1524 let (delimiter, next_rest) = if let Some(stripped) = slice.strip_prefix("NIL") {
1525 (None, stripped)
1526 } else if let Some(stripped) = slice.strip_prefix('"') {
1527 let Some(delim_end) = stripped.find('"') else {
1528 break;
1529 };
1530 (
1531 Some(stripped[..delim_end].to_string()),
1532 &stripped[delim_end + 1..],
1533 )
1534 } else {
1535 break;
1536 };
1537
1538 entries.push(NamespaceEntry {
1539 prefix: prefix.to_string(),
1540 delimiter,
1541 });
1542 rest = next_rest;
1543 }
1544
1545 entries
1546}
1547
1548impl<T: Read + Write + Unpin + fmt::Debug> Connection<T> {
1549 unsafe_pinned!(stream: ImapStream<T>);
1550
1551 pub fn into_inner(self) -> T {
1553 let Self { stream, .. } = self;
1554 stream.into_inner()
1555 }
1556
1557 pub async fn read_response(&mut self) -> Option<io::Result<ResponseData>> {
1559 self.stream.next().await
1560 }
1561
1562 pub(crate) async fn run_command_untagged(&mut self, command: &str) -> Result<()> {
1563 self.stream
1564 .encode(Request(None, command.as_bytes().into()))
1565 .await?;
1566 self.stream.flush().await?;
1567 Ok(())
1568 }
1569
1570 pub(crate) async fn run_command(&mut self, command: &str) -> Result<RequestId> {
1571 let request_id = self.request_ids.next().unwrap(); self.stream
1573 .encode(Request(Some(request_id.clone()), command.as_bytes().into()))
1574 .await?;
1575 self.stream.flush().await?;
1576 Ok(request_id)
1577 }
1578
1579 pub async fn run_command_and_check_ok(
1581 &mut self,
1582 command: &str,
1583 unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1584 ) -> Result<()> {
1585 let id = self.run_command(command).await?;
1586 self.check_done_ok(&id, unsolicited).await?;
1587
1588 Ok(())
1589 }
1590
1591 pub(crate) async fn check_done_ok(
1592 &mut self,
1593 id: &RequestId,
1594 unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1595 ) -> Result<()> {
1596 if let Some(first_res) = self.stream.next().await {
1597 self.check_done_ok_from(id, unsolicited, first_res?).await
1598 } else {
1599 Err(Error::ConnectionLost)
1600 }
1601 }
1602
1603 pub(crate) async fn check_done_ok_from(
1604 &mut self,
1605 id: &RequestId,
1606 unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1607 mut response: ResponseData,
1608 ) -> Result<()> {
1609 loop {
1610 if let Response::Done {
1611 status,
1612 code,
1613 information,
1614 tag,
1615 } = response.parsed()
1616 {
1617 self.check_status_ok(status, code.as_ref(), information.as_deref())?;
1618
1619 if tag == id {
1620 return Ok(());
1621 }
1622 }
1623
1624 if let Some(unsolicited) = unsolicited.clone() {
1625 handle_unilateral(response, unsolicited);
1626 }
1627
1628 if let Some(res) = self.stream.next().await {
1629 response = res?;
1630 } else {
1631 return Err(Error::ConnectionLost);
1632 }
1633 }
1634 }
1635
1636 pub(crate) fn check_status_ok(
1637 &self,
1638 status: &imap_proto::Status,
1639 code: Option<&imap_proto::ResponseCode<'_>>,
1640 information: Option<&str>,
1641 ) -> Result<()> {
1642 use imap_proto::Status;
1643 match status {
1644 Status::Ok => Ok(()),
1645 Status::Bad => Err(Error::Bad(format!(
1646 "code: {:?}, info: {:?}",
1647 code, information
1648 ))),
1649 Status::No => Err(Error::No(format!(
1650 "code: {:?}, info: {:?}",
1651 code, information
1652 ))),
1653 _ => Err(Error::Io(io::Error::other(format!(
1654 "status: {:?}, code: {:?}, information: {:?}",
1655 status, code, information
1656 )))),
1657 }
1658 }
1659}
1660
1661fn validate_str(value: &str) -> Result<String> {
1662 let quoted = quote!(value);
1663 if quoted.find('\n').is_some() {
1664 return Err(Error::Validate(ValidateError('\n')));
1665 }
1666 if quoted.find('\r').is_some() {
1667 return Err(Error::Validate(ValidateError('\r')));
1668 }
1669 Ok(quoted)
1670}
1671
1672#[cfg(test)]
1673mod tests {
1674 use pretty_assertions::assert_eq;
1675
1676 use super::super::error::Result;
1677 use super::super::mock_stream::MockStream;
1678 use super::*;
1679 use std::borrow::Cow;
1680 use std::future::Future;
1681
1682 use async_std::sync::{Arc, Mutex};
1683 use imap_proto::Status;
1684
1685 macro_rules! mock_client {
1686 ($s:expr) => {
1687 Client::new($s)
1688 };
1689 }
1690
1691 macro_rules! mock_session {
1692 ($s:expr) => {
1693 Session::new(mock_client!($s).conn)
1694 };
1695 }
1696
1697 macro_rules! assert_eq_bytes {
1698 ($a:expr, $b:expr, $c:expr) => {
1699 assert_eq!(
1700 std::str::from_utf8($a).unwrap(),
1701 std::str::from_utf8($b).unwrap(),
1702 $c
1703 )
1704 };
1705 }
1706
1707 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1708 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1709 async fn fetch_body() {
1710 let response = "a0 OK Logged in.\r\n\
1711 * 2 FETCH (BODY[TEXT] {3}\r\nfoo)\r\n\
1712 a0 OK FETCH completed\r\n";
1713 let mut session = mock_session!(MockStream::new(response.as_bytes().to_vec()));
1714 session.read_response().await.unwrap().unwrap();
1715 session.read_response().await.unwrap().unwrap();
1716 }
1717
1718 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1719 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1720 async fn readline_delay_read() {
1721 let greeting = "* OK Dovecot ready.\r\n";
1722 let mock_stream = MockStream::default()
1723 .with_buf(greeting.as_bytes().to_vec())
1724 .with_delay();
1725
1726 let mut client = mock_client!(mock_stream);
1727 let actual_response = client.read_response().await.unwrap().unwrap();
1728 assert_eq!(
1729 actual_response.parsed(),
1730 &Response::Data {
1731 status: Status::Ok,
1732 code: None,
1733 information: Some(Cow::Borrowed("Dovecot ready.")),
1734 }
1735 );
1736 }
1737
1738 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1739 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1740 async fn readline_eof() {
1741 let mock_stream = MockStream::default().with_eof();
1742 let mut client = mock_client!(mock_stream);
1743 let res = client.read_response().await;
1744 assert!(res.is_none());
1745 }
1746
1747 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1748 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1749 #[should_panic]
1750 async fn readline_err() {
1751 let mock_stream = MockStream::default().with_err();
1753 let mut client = mock_client!(mock_stream);
1754 client.read_response().await.unwrap().unwrap();
1755 }
1756
1757 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1758 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1759 async fn authenticate() {
1760 let response = b"+ YmFy\r\n\
1761 A0001 OK Logged in\r\n"
1762 .to_vec();
1763 let command = "A0001 AUTHENTICATE PLAIN\r\n\
1764 Zm9v\r\n";
1765 let mock_stream = MockStream::new(response);
1766 let client = mock_client!(mock_stream);
1767 enum Authenticate {
1768 Auth,
1769 }
1770 impl Authenticator for &Authenticate {
1771 type Response = Vec<u8>;
1772 fn process(&mut self, challenge: &[u8]) -> Self::Response {
1773 assert!(challenge == b"bar", "Invalid authenticate challenge");
1774 b"foo".to_vec()
1775 }
1776 }
1777 let session = client
1778 .authenticate("PLAIN", &Authenticate::Auth)
1779 .await
1780 .ok()
1781 .unwrap();
1782 assert_eq_bytes!(
1783 &session.stream.inner.written_buf,
1784 command.as_bytes(),
1785 "Invalid authenticate command"
1786 );
1787 }
1788
1789 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1790 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1791 async fn login() {
1792 let response = b"A0001 OK Logged in\r\n".to_vec();
1793 let username = "username";
1794 let password = "password";
1795 let command = format!("A0001 LOGIN {} {}\r\n", quote!(username), quote!(password));
1796 let mock_stream = MockStream::new(response);
1797 let client = mock_client!(mock_stream);
1798 if let Ok(session) = client.login(username, password).await {
1799 assert_eq!(
1800 session.stream.inner.written_buf,
1801 command.as_bytes().to_vec(),
1802 "Invalid login command"
1803 );
1804 } else {
1805 unreachable!("invalid login");
1806 }
1807 }
1808
1809 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1810 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1811 async fn logout() {
1812 let response = b"A0001 OK Logout completed.\r\n".to_vec();
1813 let command = "A0001 LOGOUT\r\n";
1814 let mock_stream = MockStream::new(response);
1815 let mut session = mock_session!(mock_stream);
1816 session.logout().await.unwrap();
1817 assert!(
1818 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1819 "Invalid logout command"
1820 );
1821 }
1822
1823 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1824 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1825 async fn rename() {
1826 let response = b"A0001 OK RENAME completed\r\n".to_vec();
1827 let current_mailbox_name = "INBOX";
1828 let new_mailbox_name = "NEWINBOX";
1829 let command = format!(
1830 "A0001 RENAME {} {}\r\n",
1831 quote!(current_mailbox_name),
1832 quote!(new_mailbox_name)
1833 );
1834 let mock_stream = MockStream::new(response);
1835 let mut session = mock_session!(mock_stream);
1836 session
1837 .rename(current_mailbox_name, new_mailbox_name)
1838 .await
1839 .unwrap();
1840 assert!(
1841 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1842 "Invalid rename command"
1843 );
1844 }
1845
1846 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1847 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1848 async fn subscribe() {
1849 let response = b"A0001 OK SUBSCRIBE completed\r\n".to_vec();
1850 let mailbox = "INBOX";
1851 let command = format!("A0001 SUBSCRIBE {}\r\n", quote!(mailbox));
1852 let mock_stream = MockStream::new(response);
1853 let mut session = mock_session!(mock_stream);
1854 session.subscribe(mailbox).await.unwrap();
1855 assert!(
1856 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1857 "Invalid subscribe command"
1858 );
1859 }
1860
1861 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1862 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1863 async fn unsubscribe() {
1864 let response = b"A0001 OK UNSUBSCRIBE completed\r\n".to_vec();
1865 let mailbox = "INBOX";
1866 let command = format!("A0001 UNSUBSCRIBE {}\r\n", quote!(mailbox));
1867 let mock_stream = MockStream::new(response);
1868 let mut session = mock_session!(mock_stream);
1869 session.unsubscribe(mailbox).await.unwrap();
1870 assert!(
1871 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1872 "Invalid unsubscribe command"
1873 );
1874 }
1875
1876 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1877 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1878 async fn expunge() {
1879 let response = b"A0001 OK EXPUNGE completed\r\n".to_vec();
1880 let mock_stream = MockStream::new(response);
1881 let mut session = mock_session!(mock_stream);
1882 session.expunge().await.unwrap().collect::<Vec<_>>().await;
1883 assert!(
1884 session.stream.inner.written_buf == b"A0001 EXPUNGE\r\n".to_vec(),
1885 "Invalid expunge command"
1886 );
1887 }
1888
1889 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1890 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1891 async fn uid_expunge() {
1892 let response = b"* 2 EXPUNGE\r\n\
1893 * 3 EXPUNGE\r\n\
1894 * 4 EXPUNGE\r\n\
1895 A0001 OK UID EXPUNGE completed\r\n"
1896 .to_vec();
1897 let mock_stream = MockStream::new(response);
1898 let mut session = mock_session!(mock_stream);
1899 session
1900 .uid_expunge("2:4")
1901 .await
1902 .unwrap()
1903 .collect::<Vec<_>>()
1904 .await;
1905 assert!(
1906 session.stream.inner.written_buf == b"A0001 UID EXPUNGE 2:4\r\n".to_vec(),
1907 "Invalid expunge command"
1908 );
1909 }
1910
1911 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1912 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1913 async fn check() {
1914 let response = b"A0001 OK CHECK completed\r\n".to_vec();
1915 let mock_stream = MockStream::new(response);
1916 let mut session = mock_session!(mock_stream);
1917 session.check().await.unwrap();
1918 assert!(
1919 session.stream.inner.written_buf == b"A0001 CHECK\r\n".to_vec(),
1920 "Invalid check command"
1921 );
1922 }
1923
1924 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1925 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1926 async fn examine() {
1927 let response = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n\
1928 * OK [PERMANENTFLAGS ()] Read-only mailbox.\r\n\
1929 * 1 EXISTS\r\n\
1930 * 1 RECENT\r\n\
1931 * OK [UNSEEN 1] First unseen.\r\n\
1932 * OK [UIDVALIDITY 1257842737] UIDs valid\r\n\
1933 * OK [UIDNEXT 2] Predicted next UID\r\n\
1934 A0001 OK [READ-ONLY] Select completed.\r\n"
1935 .to_vec();
1936 let expected_mailbox = Mailbox {
1937 flags: vec![
1938 Flag::Answered,
1939 Flag::Flagged,
1940 Flag::Deleted,
1941 Flag::Seen,
1942 Flag::Draft,
1943 ],
1944 exists: 1,
1945 recent: 1,
1946 unseen: Some(1),
1947 permanent_flags: vec![],
1948 uid_next: Some(2),
1949 uid_validity: Some(1257842737),
1950 highest_modseq: None,
1951 };
1952 let mailbox_name = "INBOX";
1953 let command = format!("A0001 EXAMINE {}\r\n", quote!(mailbox_name));
1954 let mock_stream = MockStream::new(response);
1955 let mut session = mock_session!(mock_stream);
1956 let mailbox = session.examine(mailbox_name).await.unwrap();
1957 assert!(
1958 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1959 "Invalid examine command"
1960 );
1961 assert_eq!(mailbox, expected_mailbox);
1962 }
1963
1964 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1965 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1966 async fn select() {
1967 let response = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n\
1968 * OK [PERMANENTFLAGS (\\* \\Answered \\Flagged \\Deleted \\Draft \\Seen)] \
1969 Read-only mailbox.\r\n\
1970 * 1 EXISTS\r\n\
1971 * 1 RECENT\r\n\
1972 * OK [UNSEEN 1] First unseen.\r\n\
1973 * OK [UIDVALIDITY 1257842737] UIDs valid\r\n\
1974 * OK [UIDNEXT 2] Predicted next UID\r\n\
1975 * OK [HIGHESTMODSEQ 90060115205545359] Highest mailbox modsequence\r\n\
1976 A0001 OK [READ-ONLY] Select completed.\r\n"
1977 .to_vec();
1978 let expected_mailbox = Mailbox {
1979 flags: vec![
1980 Flag::Answered,
1981 Flag::Flagged,
1982 Flag::Deleted,
1983 Flag::Seen,
1984 Flag::Draft,
1985 ],
1986 exists: 1,
1987 recent: 1,
1988 unseen: Some(1),
1989 permanent_flags: vec![
1990 Flag::MayCreate,
1991 Flag::Answered,
1992 Flag::Flagged,
1993 Flag::Deleted,
1994 Flag::Draft,
1995 Flag::Seen,
1996 ],
1997 uid_next: Some(2),
1998 uid_validity: Some(1257842737),
1999 highest_modseq: Some(90060115205545359),
2000 };
2001 let mailbox_name = "INBOX";
2002 let command = format!("A0001 SELECT {}\r\n", quote!(mailbox_name));
2003 let mock_stream = MockStream::new(response);
2004 let mut session = mock_session!(mock_stream);
2005 let mailbox = session.select(mailbox_name).await.unwrap();
2006 assert!(
2007 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2008 "Invalid select command"
2009 );
2010 assert_eq!(mailbox, expected_mailbox);
2011 }
2012
2013 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2014 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2015 async fn search() {
2016 let response = b"* SEARCH 1 2 3 4 5\r\n\
2017 A0001 OK Search completed\r\n"
2018 .to_vec();
2019 let mock_stream = MockStream::new(response);
2020 let mut session = mock_session!(mock_stream);
2021 let ids = session.search("Unseen").await.unwrap();
2022 let ids: HashSet<u32> = ids.iter().cloned().collect();
2023 assert!(
2024 session.stream.inner.written_buf == b"A0001 SEARCH Unseen\r\n".to_vec(),
2025 "Invalid search command"
2026 );
2027 assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
2028 }
2029
2030 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2031 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2032 async fn uid_search() {
2033 let response = b"* SEARCH 1 2 3 4 5\r\n\
2034 A0001 OK Search completed\r\n"
2035 .to_vec();
2036 let mock_stream = MockStream::new(response);
2037 let mut session = mock_session!(mock_stream);
2038 let ids = session.uid_search("Unseen").await.unwrap();
2039 let ids: HashSet<Uid> = ids.iter().cloned().collect();
2040 assert!(
2041 session.stream.inner.written_buf == b"A0001 UID SEARCH Unseen\r\n".to_vec(),
2042 "Invalid search command"
2043 );
2044 assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
2045 }
2046
2047 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2048 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2049 async fn uid_search_unordered() {
2050 let response = b"* SEARCH 1 2 3 4 5\r\n\
2051 A0002 OK CAPABILITY completed\r\n\
2052 A0001 OK Search completed\r\n"
2053 .to_vec();
2054 let mock_stream = MockStream::new(response);
2055 let mut session = mock_session!(mock_stream);
2056 let ids = session.uid_search("Unseen").await.unwrap();
2057 let ids: HashSet<Uid> = ids.iter().cloned().collect();
2058 assert!(
2059 session.stream.inner.written_buf == b"A0001 UID SEARCH Unseen\r\n".to_vec(),
2060 "Invalid search command"
2061 );
2062 assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
2063 }
2064
2065 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2066 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2067 async fn capability() {
2068 let response = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n\
2069 A0001 OK CAPABILITY completed\r\n"
2070 .to_vec();
2071 let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"];
2072 let mock_stream = MockStream::new(response);
2073 let mut session = mock_session!(mock_stream);
2074 let capabilities = session.capabilities().await.unwrap();
2075 assert!(
2076 session.stream.inner.written_buf == b"A0001 CAPABILITY\r\n".to_vec(),
2077 "Invalid capability command"
2078 );
2079 assert_eq!(capabilities.len(), 4);
2080 for e in expected_capabilities {
2081 assert!(capabilities.has_str(e));
2082 }
2083 }
2084
2085 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2086 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2087 async fn create() {
2088 let response = b"A0001 OK CREATE completed\r\n".to_vec();
2089 let mailbox_name = "INBOX";
2090 let command = format!("A0001 CREATE {}\r\n", quote!(mailbox_name));
2091 let mock_stream = MockStream::new(response);
2092 let mut session = mock_session!(mock_stream);
2093 session.create(mailbox_name).await.unwrap();
2094 assert!(
2095 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2096 "Invalid create command"
2097 );
2098 }
2099
2100 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2101 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2102 async fn delete() {
2103 let response = b"A0001 OK DELETE completed\r\n".to_vec();
2104 let mailbox_name = "INBOX";
2105 let command = format!("A0001 DELETE {}\r\n", quote!(mailbox_name));
2106 let mock_stream = MockStream::new(response);
2107 let mut session = mock_session!(mock_stream);
2108 session.delete(mailbox_name).await.unwrap();
2109 assert!(
2110 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2111 "Invalid delete command"
2112 );
2113 }
2114
2115 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2116 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2117 async fn noop() {
2118 let response = b"A0001 OK NOOP completed\r\n".to_vec();
2119 let mock_stream = MockStream::new(response);
2120 let mut session = mock_session!(mock_stream);
2121 session.noop().await.unwrap();
2122 assert!(
2123 session.stream.inner.written_buf == b"A0001 NOOP\r\n".to_vec(),
2124 "Invalid noop command"
2125 );
2126 }
2127
2128 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2129 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2130 async fn close() {
2131 let response = b"A0001 OK CLOSE completed\r\n".to_vec();
2132 let mock_stream = MockStream::new(response);
2133 let mut session = mock_session!(mock_stream);
2134 session.close().await.unwrap();
2135 assert!(
2136 session.stream.inner.written_buf == b"A0001 CLOSE\r\n".to_vec(),
2137 "Invalid close command"
2138 );
2139 }
2140
2141 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2142 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2143 async fn store() {
2144 generic_store(" ", |c, set, query| async move {
2145 c.lock()
2146 .await
2147 .store(set, query)
2148 .await?
2149 .collect::<Vec<_>>()
2150 .await;
2151 Ok(())
2152 })
2153 .await;
2154 }
2155
2156 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2157 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2158 async fn uid_store() {
2159 generic_store(" UID ", |c, set, query| async move {
2160 c.lock()
2161 .await
2162 .uid_store(set, query)
2163 .await?
2164 .collect::<Vec<_>>()
2165 .await;
2166 Ok(())
2167 })
2168 .await;
2169 }
2170
2171 async fn generic_store<'a, F, T, K>(prefix: &'a str, op: F)
2172 where
2173 F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2174 K: 'a + Future<Output = Result<T>>,
2175 {
2176 let res = "* 2 FETCH (FLAGS (\\Deleted \\Seen))\r\n\
2177 * 3 FETCH (FLAGS (\\Deleted))\r\n\
2178 * 4 FETCH (FLAGS (\\Deleted \\Flagged \\Seen))\r\n\
2179 A0001 OK STORE completed\r\n";
2180
2181 generic_with_uid(res, "STORE", "2.4", "+FLAGS (\\Deleted)", prefix, op).await;
2182 }
2183
2184 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2185 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2186 async fn copy() {
2187 generic_copy(" ", |c, set, query| async move {
2188 c.lock().await.copy(set, query).await?;
2189 Ok(())
2190 })
2191 .await;
2192 }
2193
2194 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2195 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2196 async fn uid_copy() {
2197 generic_copy(" UID ", |c, set, query| async move {
2198 c.lock().await.uid_copy(set, query).await?;
2199 Ok(())
2200 })
2201 .await;
2202 }
2203
2204 async fn generic_copy<'a, F, T, K>(prefix: &'a str, op: F)
2205 where
2206 F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2207 K: 'a + Future<Output = Result<T>>,
2208 {
2209 generic_with_uid(
2210 "A0001 OK COPY completed\r\n",
2211 "COPY",
2212 "2:4",
2213 "MEETING",
2214 prefix,
2215 op,
2216 )
2217 .await;
2218 }
2219
2220 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2221 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2222 async fn mv() {
2223 let response = b"* OK [COPYUID 1511554416 142,399 41:42] Moved UIDs.\r\n\
2224 * 2 EXPUNGE\r\n\
2225 * 1 EXPUNGE\r\n\
2226 A0001 OK Move completed\r\n"
2227 .to_vec();
2228 let mailbox_name = "MEETING";
2229 let command = format!("A0001 MOVE 1:2 {}\r\n", quote!(mailbox_name));
2230 let mock_stream = MockStream::new(response);
2231 let mut session = mock_session!(mock_stream);
2232 session.mv("1:2", mailbox_name).await.unwrap();
2233 assert!(
2234 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2235 "Invalid move command"
2236 );
2237 }
2238
2239 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2240 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2241 async fn uid_mv() {
2242 let response = b"* OK [COPYUID 1511554416 142,399 41:42] Moved UIDs.\r\n\
2243 * 2 EXPUNGE\r\n\
2244 * 1 EXPUNGE\r\n\
2245 A0001 OK Move completed\r\n"
2246 .to_vec();
2247 let mailbox_name = "MEETING";
2248 let command = format!("A0001 UID MOVE 41:42 {}\r\n", quote!(mailbox_name));
2249 let mock_stream = MockStream::new(response);
2250 let mut session = mock_session!(mock_stream);
2251 session.uid_mv("41:42", mailbox_name).await.unwrap();
2252 assert!(
2253 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2254 "Invalid uid move command"
2255 );
2256 }
2257
2258 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2259 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2260 async fn fetch() {
2261 generic_fetch(" ", |c, seq, query| async move {
2262 c.lock()
2263 .await
2264 .fetch(seq, query)
2265 .await?
2266 .collect::<Vec<_>>()
2267 .await;
2268
2269 Ok(())
2270 })
2271 .await;
2272 }
2273
2274 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2275 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2276 async fn uid_fetch() {
2277 generic_fetch(" UID ", |c, seq, query| async move {
2278 c.lock()
2279 .await
2280 .uid_fetch(seq, query)
2281 .await?
2282 .collect::<Vec<_>>()
2283 .await;
2284 Ok(())
2285 })
2286 .await;
2287 }
2288
2289 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2290 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2291 async fn fetch_unexpected_eof() {
2292 let response = b"".to_vec();
2294
2295 let mock_stream = MockStream::new(response);
2296 let mut session = mock_session!(mock_stream);
2297
2298 {
2299 let mut fetch_result = session
2300 .uid_fetch("1:*", "(FLAGS BODY.PEEK[])")
2301 .await
2302 .unwrap();
2303
2304 let err = fetch_result.next().await.unwrap().unwrap_err();
2306 let Error::Io(io_err) = err else {
2307 panic!("Unexpected error type: {err}")
2308 };
2309 assert_eq!(io_err.kind(), io::ErrorKind::UnexpectedEof);
2310 }
2311
2312 assert_eq!(
2313 session.stream.inner.written_buf,
2314 b"A0001 UID FETCH 1:* (FLAGS BODY.PEEK[])\r\n".to_vec()
2315 );
2316 }
2317
2318 async fn generic_fetch<'a, F, T, K>(prefix: &'a str, op: F)
2319 where
2320 F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2321 K: 'a + Future<Output = Result<T>>,
2322 {
2323 generic_with_uid(
2324 "A0001 OK FETCH completed\r\n",
2325 "FETCH",
2326 "1",
2327 "BODY[]",
2328 prefix,
2329 op,
2330 )
2331 .await;
2332 }
2333
2334 async fn generic_with_uid<'a, F, T, K>(
2335 res: &'a str,
2336 cmd: &'a str,
2337 seq: &'a str,
2338 query: &'a str,
2339 prefix: &'a str,
2340 op: F,
2341 ) where
2342 F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2343 K: 'a + Future<Output = Result<T>>,
2344 {
2345 let resp = res.as_bytes().to_vec();
2346 let line = format!("A0001{}{} {} {}\r\n", prefix, cmd, seq, query);
2347 let session = Arc::new(Mutex::new(mock_session!(MockStream::new(resp))));
2348
2349 {
2350 let _ = op(session.clone(), seq, query).await.unwrap();
2351 }
2352 assert!(
2353 session.lock().await.stream.inner.written_buf == line.as_bytes().to_vec(),
2354 "Invalid command"
2355 );
2356 }
2357
2358 #[test]
2359 fn quote_backslash() {
2360 assert_eq!("\"test\\\\text\"", quote!(r"test\text"));
2361 }
2362
2363 #[test]
2364 fn quote_dquote() {
2365 assert_eq!("\"test\\\"text\"", quote!("test\"text"));
2366 }
2367
2368 #[test]
2369 fn validate_random() {
2370 assert_eq!(
2371 "\"~iCQ_k;>[&\\\"sVCvUW`e<<P!wJ\"",
2372 &validate_str("~iCQ_k;>[&\"sVCvUW`e<<P!wJ").unwrap()
2373 );
2374 }
2375
2376 #[test]
2377 fn validate_newline() {
2378 if let Err(ref e) = validate_str("test\nstring") {
2379 if let Error::Validate(ref ve) = e {
2380 if ve.0 == '\n' {
2381 return;
2382 }
2383 }
2384 panic!("Wrong error: {:?}", e);
2385 }
2386 panic!("No error");
2387 }
2388
2389 #[test]
2390 #[allow(unreachable_patterns)]
2391 fn validate_carriage_return() {
2392 if let Err(ref e) = validate_str("test\rstring") {
2393 if let Error::Validate(ref ve) = e {
2394 if ve.0 == '\r' {
2395 return;
2396 }
2397 }
2398 panic!("Wrong error: {:?}", e);
2399 }
2400 panic!("No error");
2401 }
2402
2403 #[cfg(feature = "runtime-tokio")]
2407 async fn handle_client(stream: tokio::io::DuplexStream) -> Result<()> {
2408 use tokio::io::AsyncBufReadExt;
2409
2410 let (reader, mut writer) = tokio::io::split(stream);
2411 let reader = tokio::io::BufReader::new(reader);
2412
2413 let mut lines = reader.lines();
2414 while let Some(line) = lines.next_line().await? {
2415 let (request_id, request) = line.split_once(' ').unwrap();
2416 eprintln!("Received request {request_id}.");
2417
2418 let (id, _) = request
2419 .strip_prefix("FETCH ")
2420 .unwrap()
2421 .split_once(' ')
2422 .unwrap();
2423 let id = id.parse().unwrap();
2424
2425 let mut body = concat!(
2426 "From: Bob <bob@example.com>\r\n",
2427 "To: Alice <alice@example.org>\r\n",
2428 "Subject: Test\r\n",
2429 "Message-Id: <foobar@example.com>\r\n",
2430 "Date: Sun, 22 Mar 2020 00:00:00 +0100\r\n",
2431 "\r\n",
2432 )
2433 .to_string();
2434 for _ in 1..id {
2435 body +=
2436 "012345678901234567890123456789012345678901234567890123456789012345678901\r\n";
2437 }
2438 let body_len = body.len();
2439
2440 let response = format!("* {id} FETCH (RFC822.SIZE {body_len} BODY[] {{{body_len}}}\r\n{body} FLAGS (\\Seen))\r\n");
2441 writer.write_all(response.as_bytes()).await?;
2442 writer
2443 .write_all(format!("{request_id} OK FETCH completed\r\n").as_bytes())
2444 .await?;
2445 writer.flush().await?;
2446 }
2447
2448 Ok(())
2449 }
2450
2451 #[cfg(feature = "runtime-tokio")]
2458 #[cfg_attr(
2459 feature = "runtime-tokio",
2460 tokio::test(flavor = "multi_thread", worker_threads = 2)
2461 )]
2462 async fn large_fetch() -> Result<()> {
2463 use futures::TryStreamExt;
2464
2465 let (client, server) = tokio::io::duplex(4096);
2466 tokio::spawn(handle_client(server));
2467
2468 let client = crate::Client::new(client);
2469 let mut imap_session = Session::new(client.conn);
2470
2471 for i in 200..300 {
2472 eprintln!("Fetching {i}.");
2473 let mut messages_stream = imap_session
2474 .fetch(format!("{i}"), "(RFC822.SIZE BODY.PEEK[] FLAGS)")
2475 .await?;
2476 let fetch = messages_stream
2477 .try_next()
2478 .await?
2479 .expect("no FETCH returned");
2480 let body = fetch.body().expect("message did not have a body!");
2481 assert_eq!(body.len(), 76 + 74 * i);
2482
2483 let no_fetch = messages_stream.try_next().await?;
2484 assert!(no_fetch.is_none());
2485 drop(messages_stream);
2486 }
2487
2488 Ok(())
2489 }
2490
2491 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2492 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2493 async fn status() {
2494 {
2495 let response = b"* STATUS INBOX (UIDNEXT 25)\r\n\
2496 A0001 OK [CLIENTBUG] Status on selected mailbox completed (0.001 + 0.000 secs).\r\n"
2497 .to_vec();
2498
2499 let mock_stream = MockStream::new(response);
2500 let mut session = mock_session!(mock_stream);
2501 let status = session.status("INBOX", "(UIDNEXT)").await.unwrap();
2502 assert_eq!(
2503 session.stream.inner.written_buf,
2504 b"A0001 STATUS \"INBOX\" (UIDNEXT)\r\n".to_vec()
2505 );
2506 assert_eq!(status.uid_next, Some(25));
2507 }
2508
2509 {
2510 let response = b"* STATUS INBOX (RECENT 15)\r\n\
2511 A0001 OK STATUS completed\r\n"
2512 .to_vec();
2513
2514 let mock_stream = MockStream::new(response);
2515 let mut session = mock_session!(mock_stream);
2516 let status = session.status("INBOX", "(RECENT)").await.unwrap();
2517 assert_eq!(
2518 session.stream.inner.written_buf,
2519 b"A0001 STATUS \"INBOX\" (RECENT)\r\n".to_vec()
2520 );
2521 assert_eq!(status.recent, 15);
2522 }
2523
2524 {
2525 let response = b"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)\r\n\
2527 A0001 OK STATUS completed\r\n"
2528 .to_vec();
2529
2530 let mock_stream = MockStream::new(response);
2531 let mut session = mock_session!(mock_stream);
2532 let status = session
2533 .status("blurdybloop", "(UIDNEXT MESSAGES)")
2534 .await
2535 .unwrap();
2536 assert_eq!(
2537 session.stream.inner.written_buf,
2538 b"A0001 STATUS \"blurdybloop\" (UIDNEXT MESSAGES)\r\n".to_vec()
2539 );
2540 assert_eq!(status.uid_next, Some(44292));
2541 assert_eq!(status.exists, 231);
2542 }
2543 }
2544
2545 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2546 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2547 async fn append() {
2548 {
2549 let response = b"+ OK\r\nA0001 OK [APPENDUID 1725735035 2] Append completed (0.052 + 12.097 + 0.049 secs).\r\n".to_vec();
2553
2554 let mock_stream = MockStream::new(response);
2555 let mut session = mock_session!(mock_stream);
2556 session
2557 .append("INBOX", Some(r"(\Seen)"), None, "foobarbaz")
2558 .await
2559 .unwrap();
2560 assert_eq!(
2561 session.stream.inner.written_buf,
2562 b"A0001 APPEND \"INBOX\" (\\Seen) {9}\r\nfoobarbaz\r\n".to_vec()
2563 );
2564 }
2565
2566 {
2567 let response = b"+ OK\r\n* 3 EXISTS\r\n* 2 RECENT\r\nA0001 OK [APPENDUID 1725735035 2] Append completed (0.052 + 12.097 + 0.049 secs).\r\n".to_vec();
2571
2572 let mock_stream = MockStream::new(response);
2573 let mut session = mock_session!(mock_stream);
2574 session
2575 .append("INBOX", Some(r"(\Seen)"), None, "foobarbaz")
2576 .await
2577 .unwrap();
2578 assert_eq!(
2579 session.stream.inner.written_buf,
2580 b"A0001 APPEND \"INBOX\" (\\Seen) {9}\r\nfoobarbaz\r\n".to_vec()
2581 );
2582 let exists_response = session.unsolicited_responses.recv().await.unwrap();
2583 assert_eq!(exists_response, UnsolicitedResponse::Exists(3));
2584 let recent_response = session.unsolicited_responses.recv().await.unwrap();
2585 assert_eq!(recent_response, UnsolicitedResponse::Recent(2));
2586 }
2587
2588 {
2589 let response =
2591 b"A0001 NO [TRYCREATE] Mailbox doesn't exist: foobar (0.001 + 0.000 secs)."
2592 .to_vec();
2593 let mock_stream = MockStream::new(response);
2594 let mut session = mock_session!(mock_stream);
2595 session
2596 .append("foobar", None, None, "foobarbaz")
2597 .await
2598 .unwrap_err();
2599 assert_eq!(
2600 session.stream.inner.written_buf,
2601 b"A0001 APPEND \"foobar\" {9}\r\n".to_vec()
2602 );
2603 }
2604 }
2605
2606 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2607 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2608 async fn get_metadata() {
2609 {
2610 let response = b"* METADATA \"INBOX\" (/private/comment \"My own comment\")\r\n\
2611 A0001 OK GETMETADATA complete\r\n"
2612 .to_vec();
2613
2614 let mock_stream = MockStream::new(response);
2615 let mut session = mock_session!(mock_stream);
2616 let metadata = session
2617 .get_metadata("INBOX", "", "/private/comment")
2618 .await
2619 .unwrap();
2620 assert_eq!(
2621 session.stream.inner.written_buf,
2622 b"A0001 GETMETADATA \"INBOX\" /private/comment\r\n".to_vec()
2623 );
2624 assert_eq!(metadata.len(), 1);
2625 assert_eq!(metadata[0].entry, "/private/comment");
2626 assert_eq!(metadata[0].value.as_ref().unwrap(), "My own comment");
2627 }
2628
2629 {
2630 let response = b"* METADATA \"INBOX\" (/shared/comment \"Shared comment\" /private/comment \"My own comment\")\r\n\
2631 A0001 OK GETMETADATA complete\r\n"
2632 .to_vec();
2633
2634 let mock_stream = MockStream::new(response);
2635 let mut session = mock_session!(mock_stream);
2636 let metadata = session
2637 .get_metadata("INBOX", "", "(/shared/comment /private/comment)")
2638 .await
2639 .unwrap();
2640 assert_eq!(
2641 session.stream.inner.written_buf,
2642 b"A0001 GETMETADATA \"INBOX\" (/shared/comment /private/comment)\r\n".to_vec()
2643 );
2644 assert_eq!(metadata.len(), 2);
2645 assert_eq!(metadata[0].entry, "/shared/comment");
2646 assert_eq!(metadata[0].value.as_ref().unwrap(), "Shared comment");
2647 assert_eq!(metadata[1].entry, "/private/comment");
2648 assert_eq!(metadata[1].value.as_ref().unwrap(), "My own comment");
2649 }
2650
2651 {
2652 let response = b"* METADATA \"\" (/shared/comment {15}\r\nChatmail server /shared/admin {28}\r\nmailto:root@nine.testrun.org)\r\n\
2653 A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\r\n"
2654 .to_vec();
2655
2656 let mock_stream = MockStream::new(response);
2657 let mut session = mock_session!(mock_stream);
2658 let metadata = session
2659 .get_metadata("", "", "(/shared/comment /shared/admin)")
2660 .await
2661 .unwrap();
2662 assert_eq!(
2663 session.stream.inner.written_buf,
2664 b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2665 );
2666 assert_eq!(metadata.len(), 2);
2667 assert_eq!(metadata[0].entry, "/shared/comment");
2668 assert_eq!(metadata[0].value.as_ref().unwrap(), "Chatmail server");
2669 assert_eq!(metadata[1].entry, "/shared/admin");
2670 assert_eq!(
2671 metadata[1].value.as_ref().unwrap(),
2672 "mailto:root@nine.testrun.org"
2673 );
2674 }
2675
2676 {
2677 let response = b"* METADATA \"\" (/shared/comment \"Chatmail server\")\r\n\
2678 * METADATA \"\" (/shared/admin \"mailto:root@nine.testrun.org\")\r\n\
2679 A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\r\n"
2680 .to_vec();
2681
2682 let mock_stream = MockStream::new(response);
2683 let mut session = mock_session!(mock_stream);
2684 let metadata = session
2685 .get_metadata("", "", "(/shared/comment /shared/admin)")
2686 .await
2687 .unwrap();
2688 assert_eq!(
2689 session.stream.inner.written_buf,
2690 b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2691 );
2692 assert_eq!(metadata.len(), 2);
2693 assert_eq!(metadata[0].entry, "/shared/comment");
2694 assert_eq!(metadata[0].value.as_ref().unwrap(), "Chatmail server");
2695 assert_eq!(metadata[1].entry, "/shared/admin");
2696 assert_eq!(
2697 metadata[1].value.as_ref().unwrap(),
2698 "mailto:root@nine.testrun.org"
2699 );
2700 }
2701
2702 {
2703 let response = b"* METADATA \"\" (/shared/comment NIL /shared/admin NIL)\r\n\
2704 A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\r\n"
2705 .to_vec();
2706
2707 let mock_stream = MockStream::new(response);
2708 let mut session = mock_session!(mock_stream);
2709 let metadata = session
2710 .get_metadata("", "", "(/shared/comment /shared/admin)")
2711 .await
2712 .unwrap();
2713 assert_eq!(
2714 session.stream.inner.written_buf,
2715 b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2716 );
2717 assert_eq!(metadata.len(), 2);
2718 assert_eq!(metadata[0].entry, "/shared/comment");
2719 assert_eq!(metadata[0].value, None);
2720 assert_eq!(metadata[1].entry, "/shared/admin");
2721 assert_eq!(metadata[1].value, None);
2722 }
2723 }
2724
2725 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2726 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2727 async fn test_get_quota_root() {
2728 {
2729 let response = b"* QUOTAROOT Sent Userquota\r\n\
2730 * QUOTA Userquota (STORAGE 4855 48576)\r\n\
2731 A0001 OK Getquotaroot completed (0.004 + 0.000 + 0.004 secs).\r\n"
2732 .to_vec();
2733
2734 let mock_stream = MockStream::new(response);
2735 let mut session = mock_session!(mock_stream);
2736 let (quotaroots, quota) = dbg!(session.get_quota_root("Sent").await.unwrap());
2737 assert_eq!(
2738 str::from_utf8(&session.stream.inner.written_buf).unwrap(),
2739 "A0001 GETQUOTAROOT \"Sent\"\r\n"
2740 );
2741 assert_eq!(
2742 quotaroots,
2743 vec![QuotaRoot {
2744 mailbox_name: "Sent".to_string(),
2745 quota_root_names: vec!["Userquota".to_string(),],
2746 },],
2747 );
2748 assert_eq!(
2749 quota,
2750 vec![Quota {
2751 root_name: "Userquota".to_string(),
2752 resources: vec![QuotaResource {
2753 name: QuotaResourceName::Storage,
2754 usage: 4855,
2755 limit: 48576,
2756 }],
2757 }]
2758 );
2759 assert_eq!(quota[0].resources[0].get_usage_percentage(), 9);
2760 }
2761
2762 {
2763 let response = b"* QUOTAROOT \"INBOX\" \"#19\"\r\n\
2764 * QUOTA \"#19\" (STORAGE 0 0)\r\n\
2765 A0001 OK GETQUOTAROOT successful.\r\n"
2766 .to_vec();
2767
2768 let mock_stream = MockStream::new(response);
2769 let mut session = mock_session!(mock_stream);
2770 let (quotaroots, quota) = session.get_quota_root("INBOX").await.unwrap();
2771 assert_eq!(
2772 str::from_utf8(&session.stream.inner.written_buf).unwrap(),
2773 "A0001 GETQUOTAROOT \"INBOX\"\r\n"
2774 );
2775 assert_eq!(
2776 quotaroots,
2777 vec![QuotaRoot {
2778 mailbox_name: "INBOX".to_string(),
2779 quota_root_names: vec!["#19".to_string(),],
2780 },],
2781 );
2782 assert_eq!(
2783 quota,
2784 vec![Quota {
2785 root_name: "#19".to_string(),
2786 resources: vec![QuotaResource {
2787 name: QuotaResourceName::Storage,
2788 usage: 0,
2789 limit: 0,
2790 }],
2791 }]
2792 );
2793 assert_eq!(quota[0].resources[0].get_usage_percentage(), 0);
2794 }
2795 }
2796
2797 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2798 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2799 async fn test_parsing_error() {
2800 let response = b"220 mail.example.org ESMTP Postcow\r\n".to_vec();
2802 let command = "A0001 NOOP\r\n";
2803 let mock_stream = MockStream::new(response);
2804 let mut session = mock_session!(mock_stream);
2805 assert!(session
2806 .noop()
2807 .await
2808 .unwrap_err()
2809 .to_string()
2810 .contains("220 mail.example.org ESMTP Postcow"));
2811 assert!(
2812 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2813 "Invalid NOOP command"
2814 );
2815 }
2816}