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, TryStreamExt};
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.into(), $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 async fn login<U: AsRef<str>, P: AsRef<str>>(
184 self,
185 username: U,
186 password: P,
187 ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
188 let (session, _capabilities) = self.login_with_capabilities(username, password).await?;
189 Ok(session)
190 }
191
192 pub async fn login_with_capabilities<U: AsRef<str>, P: AsRef<str>>(
198 mut self,
199 username: U,
200 password: P,
201 ) -> ::std::result::Result<(Session<T>, Option<Capabilities>), (Error, Client<T>)> {
202 let u = ok_or_unauth_client_err!(validate_str(username.as_ref()), self);
203 let p = ok_or_unauth_client_err!(validate_str(password.as_ref()), self);
204
205 let id = ok_or_unauth_client_err!(self.run_command(&format!("LOGIN {u} {p}")).await, self);
206 loop {
207 let Some(res) = ok_or_unauth_client_err!(self.stream.try_next().await, self) else {
208 return Err((Error::ConnectionLost, self));
209 };
210
211 if let Response::Done {
212 status,
213 code,
214 information,
215 tag,
216 } = res.parsed()
217 {
218 ok_or_unauth_client_err!(
219 self.check_status_ok(status, code.as_ref(), information.as_deref()),
220 self
221 );
222
223 if *tag == id {
224 let capabilities =
225 if let Some(imap_proto::types::ResponseCode::Capabilities(capabilities)) =
226 code
227 {
228 use crate::types::{Capabilities, Capability};
229 let capability_set: HashSet<Capability> =
230 capabilities.iter().map(Capability::from).collect();
231 Some(Capabilities(capability_set))
232 } else {
233 None
234 };
235 return Ok((Session::new(self.conn), capabilities));
236 }
237 }
238 }
239 }
240
241 pub async fn authenticate<A: Authenticator, S: AsRef<str>>(
285 mut self,
286 auth_type: S,
287 authenticator: A,
288 ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
289 let id = ok_or_unauth_client_err!(
290 self.run_command(&format!("AUTHENTICATE {}", auth_type.as_ref()))
291 .await,
292 self
293 );
294 let session = self.do_auth_handshake(id, authenticator).await?;
295 Ok(session)
296 }
297
298 async fn do_auth_handshake<A: Authenticator>(
300 mut self,
301 id: RequestId,
302 mut authenticator: A,
303 ) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
304 loop {
307 let Some(res) = ok_or_unauth_client_err!(self.read_response().await, self) else {
308 return Err((Error::ConnectionLost, self));
309 };
310 match res.parsed() {
311 Response::Continue { information, .. } => {
312 let challenge = if let Some(text) = information {
313 ok_or_unauth_client_err!(
314 base64::engine::general_purpose::STANDARD
315 .decode(text.as_ref())
316 .map_err(|e| Error::Parse(ParseError::Authentication(
317 (*text).to_string(),
318 Some(e)
319 ))),
320 self
321 )
322 } else {
323 Vec::new()
324 };
325 let raw_response = &mut authenticator.process(&challenge);
326 let auth_response =
327 base64::engine::general_purpose::STANDARD.encode(raw_response);
328
329 ok_or_unauth_client_err!(
330 self.conn.run_command_untagged(&auth_response).await,
331 self
332 );
333 }
334 _ => {
335 ok_or_unauth_client_err!(self.check_done_ok_from(&id, None, res).await, self);
336 return Ok(Session::new(self.conn));
337 }
338 }
339 }
340 }
341}
342
343impl<T: Read + Write + Unpin + fmt::Debug + Send> Session<T> {
344 unsafe_pinned!(conn: Connection<T>);
345
346 pub(crate) fn get_stream(self: Pin<&mut Self>) -> Pin<&mut ImapStream<T>> {
347 self.conn().stream()
348 }
349
350 fn new(conn: Connection<T>) -> Self {
352 let (tx, rx) = bounded(100);
353 Session {
354 conn,
355 unsolicited_responses: rx,
356 unsolicited_responses_tx: tx,
357 }
358 }
359
360 pub async fn select<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
378 let id = self
380 .run_command(&format!("SELECT {}", validate_str(mailbox_name.as_ref())?))
381 .await?;
382 let mbox = parse_mailbox(
383 &mut self.conn.stream,
384 self.unsolicited_responses_tx.clone(),
385 id,
386 )
387 .await?;
388
389 Ok(mbox)
390 }
391
392 pub async fn select_condstore<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
395 let id = self
396 .run_command(&format!(
397 "SELECT {} (CONDSTORE)",
398 validate_str(mailbox_name.as_ref())?
399 ))
400 .await?;
401 let mbox = parse_mailbox(
402 &mut self.conn.stream,
403 self.unsolicited_responses_tx.clone(),
404 id,
405 )
406 .await?;
407
408 Ok(mbox)
409 }
410
411 pub async fn examine<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<Mailbox> {
416 let id = self
417 .run_command(&format!("EXAMINE {}", validate_str(mailbox_name.as_ref())?))
418 .await?;
419 let mbox = parse_mailbox(
420 &mut self.conn.stream,
421 self.unsolicited_responses_tx.clone(),
422 id,
423 )
424 .await?;
425
426 Ok(mbox)
427 }
428
429 pub async fn fetch<S1, S2>(
488 &mut self,
489 sequence_set: S1,
490 query: S2,
491 ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
492 where
493 S1: AsRef<str>,
494 S2: AsRef<str>,
495 {
496 let id = self
497 .run_command(&format!(
498 "FETCH {} {}",
499 sequence_set.as_ref(),
500 query.as_ref()
501 ))
502 .await?;
503 let res = parse_fetches(
504 &mut self.conn.stream,
505 self.unsolicited_responses_tx.clone(),
506 id,
507 );
508
509 Ok(res)
510 }
511
512 pub async fn uid_fetch<S1, S2>(
515 &mut self,
516 uid_set: S1,
517 query: S2,
518 ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send + Unpin>
519 where
520 S1: AsRef<str>,
521 S2: AsRef<str>,
522 {
523 let id = self
524 .run_command(&format!(
525 "UID FETCH {} {}",
526 uid_set.as_ref(),
527 query.as_ref()
528 ))
529 .await?;
530 let res = parse_fetches(
531 &mut self.conn.stream,
532 self.unsolicited_responses_tx.clone(),
533 id,
534 );
535 Ok(res)
536 }
537
538 pub async fn noop(&mut self) -> Result<()> {
540 let id = self.run_command("NOOP").await?;
541 parse_noop(
542 &mut self.conn.stream,
543 self.unsolicited_responses_tx.clone(),
544 id,
545 )
546 .await?;
547 Ok(())
548 }
549
550 pub async fn logout(&mut self) -> Result<()> {
552 self.run_command_and_check_ok("LOGOUT").await?;
553 Ok(())
554 }
555
556 pub async fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<()> {
579 self.run_command_and_check_ok(&format!("CREATE {}", validate_str(mailbox_name.as_ref())?))
580 .await?;
581
582 Ok(())
583 }
584
585 pub async fn delete<S: AsRef<str>>(&mut self, mailbox_name: S) -> Result<()> {
605 self.run_command_and_check_ok(&format!("DELETE {}", validate_str(mailbox_name.as_ref())?))
606 .await?;
607
608 Ok(())
609 }
610
611 pub async fn rename<S1: AsRef<str>, S2: AsRef<str>>(&mut self, from: S1, to: S2) -> Result<()> {
637 self.run_command_and_check_ok(&format!(
638 "RENAME {} {}",
639 validate_str(from.as_ref())?,
640 validate_str(to.as_ref())?
641 ))
642 .await?;
643
644 Ok(())
645 }
646
647 pub async fn subscribe<S: AsRef<str>>(&mut self, mailbox: S) -> Result<()> {
656 self.run_command_and_check_ok(&format!("SUBSCRIBE {}", validate_str(mailbox.as_ref())?))
657 .await?;
658 Ok(())
659 }
660
661 pub async fn unsubscribe<S: AsRef<str>>(&mut self, mailbox: S) -> Result<()> {
666 self.run_command_and_check_ok(&format!("UNSUBSCRIBE {}", validate_str(mailbox.as_ref())?))
667 .await?;
668 Ok(())
669 }
670
671 pub async fn capabilities(&mut self) -> Result<Capabilities> {
675 let id = self.run_command("CAPABILITY").await?;
676 let c = parse_capabilities(
677 &mut self.conn.stream,
678 self.unsolicited_responses_tx.clone(),
679 id,
680 )
681 .await?;
682 Ok(c)
683 }
684
685 pub async fn expunge(&mut self) -> Result<impl Stream<Item = Result<Seq>> + '_ + Send> {
689 let id = self.run_command("EXPUNGE").await?;
690 let res = parse_expunge(
691 &mut self.conn.stream,
692 self.unsolicited_responses_tx.clone(),
693 id,
694 );
695 Ok(res)
696 }
697
698 pub async fn uid_expunge<S: AsRef<str>>(
721 &mut self,
722 uid_set: S,
723 ) -> Result<impl Stream<Item = Result<Uid>> + '_ + Send> {
724 let id = self
725 .run_command(&format!("UID EXPUNGE {}", uid_set.as_ref()))
726 .await?;
727 let res = parse_expunge(
728 &mut self.conn.stream,
729 self.unsolicited_responses_tx.clone(),
730 id,
731 );
732 Ok(res)
733 }
734
735 pub async fn check(&mut self) -> Result<()> {
746 self.run_command_and_check_ok("CHECK").await?;
747 Ok(())
748 }
749
750 pub async fn close(&mut self) -> Result<()> {
766 self.run_command_and_check_ok("CLOSE").await?;
767 Ok(())
768 }
769
770 pub async fn store<S1, S2>(
820 &mut self,
821 sequence_set: S1,
822 query: S2,
823 ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
824 where
825 S1: AsRef<str>,
826 S2: AsRef<str>,
827 {
828 let id = self
829 .run_command(&format!(
830 "STORE {} {}",
831 sequence_set.as_ref(),
832 query.as_ref()
833 ))
834 .await?;
835 let res = parse_fetches(
836 &mut self.conn.stream,
837 self.unsolicited_responses_tx.clone(),
838 id,
839 );
840 Ok(res)
841 }
842
843 pub async fn uid_store<S1, S2>(
846 &mut self,
847 uid_set: S1,
848 query: S2,
849 ) -> Result<impl Stream<Item = Result<Fetch>> + '_ + Send>
850 where
851 S1: AsRef<str>,
852 S2: AsRef<str>,
853 {
854 let id = self
855 .run_command(&format!(
856 "UID STORE {} {}",
857 uid_set.as_ref(),
858 query.as_ref()
859 ))
860 .await?;
861 let res = parse_fetches(
862 &mut self.conn.stream,
863 self.unsolicited_responses_tx.clone(),
864 id,
865 );
866 Ok(res)
867 }
868
869 pub async fn copy<S1: AsRef<str>, S2: AsRef<str>>(
877 &mut self,
878 sequence_set: S1,
879 mailbox_name: S2,
880 ) -> Result<()> {
881 self.run_command_and_check_ok(&format!(
882 "COPY {} {}",
883 sequence_set.as_ref(),
884 validate_str(mailbox_name.as_ref())?
885 ))
886 .await?;
887
888 Ok(())
889 }
890
891 pub async fn uid_copy<S1: AsRef<str>, S2: AsRef<str>>(
894 &mut self,
895 uid_set: S1,
896 mailbox_name: S2,
897 ) -> Result<()> {
898 self.run_command_and_check_ok(&format!(
899 "UID COPY {} {}",
900 uid_set.as_ref(),
901 validate_str(mailbox_name.as_ref())?
902 ))
903 .await?;
904
905 Ok(())
906 }
907
908 pub async fn mv<S1: AsRef<str>, S2: AsRef<str>>(
939 &mut self,
940 sequence_set: S1,
941 mailbox_name: S2,
942 ) -> Result<()> {
943 self.run_command_and_check_ok(&format!(
944 "MOVE {} {}",
945 sequence_set.as_ref(),
946 validate_str(mailbox_name.as_ref())?
947 ))
948 .await?;
949
950 Ok(())
951 }
952
953 pub async fn uid_mv<S1: AsRef<str>, S2: AsRef<str>>(
958 &mut self,
959 uid_set: S1,
960 mailbox_name: S2,
961 ) -> Result<()> {
962 self.run_command_and_check_ok(&format!(
963 "UID MOVE {} {}",
964 uid_set.as_ref(),
965 validate_str(mailbox_name.as_ref())?
966 ))
967 .await?;
968
969 Ok(())
970 }
971
972 pub async fn list(
1004 &mut self,
1005 reference_name: Option<&str>,
1006 mailbox_pattern: Option<&str>,
1007 ) -> Result<impl Stream<Item = Result<Name>> + '_ + Send> {
1008 let id = self
1009 .run_command(&format!(
1010 "LIST {} {}",
1011 validate_str(reference_name.unwrap_or(""))?,
1012 mailbox_pattern.unwrap_or("\"\"")
1013 ))
1014 .await?;
1015 let names = parse_names(
1016 &mut self.conn.stream,
1017 self.unsolicited_responses_tx.clone(),
1018 id,
1019 );
1020
1021 Ok(names)
1022 }
1023
1024 pub async fn lsub(
1040 &mut self,
1041 reference_name: Option<&str>,
1042 mailbox_pattern: Option<&str>,
1043 ) -> Result<impl Stream<Item = Result<Name>> + '_ + Send> {
1044 let id = self
1045 .run_command(&format!(
1046 "LSUB {} {}",
1047 validate_str(reference_name.unwrap_or(""))?,
1048 validate_str(mailbox_pattern.unwrap_or(""))?
1049 ))
1050 .await?;
1051 let names = parse_names(
1052 &mut self.conn.stream,
1053 self.unsolicited_responses_tx.clone(),
1054 id,
1055 );
1056
1057 Ok(names)
1058 }
1059
1060 pub async fn status<S1: AsRef<str>, S2: AsRef<str>>(
1095 &mut self,
1096 mailbox_name: S1,
1097 data_items: S2,
1098 ) -> Result<Mailbox> {
1099 let id = self
1100 .run_command(&format!(
1101 "STATUS {} {}",
1102 validate_str(mailbox_name.as_ref())?,
1103 data_items.as_ref()
1104 ))
1105 .await?;
1106 let mbox = parse_status(
1107 &mut self.conn.stream,
1108 mailbox_name.as_ref(),
1109 self.unsolicited_responses_tx.clone(),
1110 id,
1111 )
1112 .await?;
1113 Ok(mbox)
1114 }
1115
1116 pub fn idle(self) -> extensions::idle::Handle<T> {
1135 extensions::idle::Handle::new(self)
1136 }
1137
1138 pub async fn append(
1158 &mut self,
1159 mailbox: impl AsRef<str>,
1160 flags: Option<&str>,
1161 internaldate: Option<&str>,
1162 content: impl AsRef<[u8]>,
1163 ) -> Result<()> {
1164 let content = content.as_ref();
1165 let id = self
1166 .run_command(&format!(
1167 "APPEND {}{}{}{}{} {{{}}}",
1168 validate_str(mailbox.as_ref())?,
1169 if flags.is_some() { " " } else { "" },
1170 flags.unwrap_or(""),
1171 if internaldate.is_some() { " " } else { "" },
1172 internaldate.unwrap_or(""),
1173 content.len()
1174 ))
1175 .await?;
1176
1177 let Some(res) = self.read_response().await? else {
1178 return Err(Error::Append);
1179 };
1180 let Response::Continue { .. } = res.parsed() else {
1181 return Err(Error::Append);
1182 };
1183
1184 self.stream.as_mut().write_all(content).await?;
1185 self.stream.as_mut().write_all(b"\r\n").await?;
1186 self.stream.flush().await?;
1187 self.conn
1188 .check_done_ok(&id, Some(self.unsolicited_responses_tx.clone()))
1189 .await?;
1190 Ok(())
1191 }
1192
1193 pub async fn search<S: AsRef<str>>(&mut self, query: S) -> Result<HashSet<Seq>> {
1238 let id = self
1239 .run_command(&format!("SEARCH {}", query.as_ref()))
1240 .await?;
1241 let seqs = parse_ids(
1242 &mut self.conn.stream,
1243 self.unsolicited_responses_tx.clone(),
1244 id,
1245 )
1246 .await?;
1247
1248 Ok(seqs)
1249 }
1250
1251 pub async fn uid_search<S: AsRef<str>>(&mut self, query: S) -> Result<HashSet<Uid>> {
1255 let id = self
1256 .run_command(&format!("UID SEARCH {}", query.as_ref()))
1257 .await?;
1258 let uids = parse_ids(
1259 &mut self.conn.stream,
1260 self.unsolicited_responses_tx.clone(),
1261 id,
1262 )
1263 .await?;
1264
1265 Ok(uids)
1266 }
1267
1268 pub async fn get_quota(&mut self, quota_root: &str) -> Result<Quota> {
1270 let id = self
1271 .run_command(format!("GETQUOTA {}", validate_str(quota_root)?))
1272 .await?;
1273 let c = parse_get_quota(
1274 &mut self.conn.stream,
1275 self.unsolicited_responses_tx.clone(),
1276 id,
1277 )
1278 .await?;
1279 Ok(c)
1280 }
1281
1282 pub async fn get_quota_root(
1284 &mut self,
1285 mailbox_name: &str,
1286 ) -> Result<(Vec<QuotaRoot>, Vec<Quota>)> {
1287 let id = self
1288 .run_command(format!("GETQUOTAROOT {}", validate_str(mailbox_name)?))
1289 .await?;
1290 let c = parse_get_quota_root(
1291 &mut self.conn.stream,
1292 self.unsolicited_responses_tx.clone(),
1293 id,
1294 )
1295 .await?;
1296 Ok(c)
1297 }
1298
1299 pub async fn get_metadata(
1301 &mut self,
1302 mailbox_name: &str,
1303 options: &str,
1304 entry_specifier: &str,
1305 ) -> Result<Vec<Metadata>> {
1306 let options = if options.is_empty() {
1307 String::new()
1308 } else {
1309 format!(" {options}")
1310 };
1311 let id = self
1312 .run_command(format!(
1313 "GETMETADATA {} {}{}",
1314 validate_str(mailbox_name)?,
1315 options,
1316 entry_specifier
1317 ))
1318 .await?;
1319 let metadata = parse_metadata(
1320 &mut self.conn.stream,
1321 mailbox_name,
1322 self.unsolicited_responses_tx.clone(),
1323 id,
1324 )
1325 .await?;
1326 Ok(metadata)
1327 }
1328
1329 pub async fn id(
1333 &mut self,
1334 identification: impl IntoIterator<Item = (&str, Option<&str>)>,
1335 ) -> Result<Option<HashMap<String, String>>> {
1336 let id = self
1337 .run_command(format!("ID ({})", format_identification(identification)))
1338 .await?;
1339 let server_identification = parse_id(
1340 &mut self.conn.stream,
1341 self.unsolicited_responses_tx.clone(),
1342 id,
1343 )
1344 .await?;
1345 Ok(server_identification)
1346 }
1347
1348 pub async fn id_nil(&mut self) -> Result<Option<HashMap<String, String>>> {
1352 let id = self.run_command("ID NIL").await?;
1353 let server_identification = parse_id(
1354 &mut self.conn.stream,
1355 self.unsolicited_responses_tx.clone(),
1356 id,
1357 )
1358 .await?;
1359 Ok(server_identification)
1360 }
1361
1362 pub async fn run_command_and_check_ok<S: AsRef<str>>(&mut self, command: S) -> Result<()> {
1365 self.conn
1366 .run_command_and_check_ok(
1367 command.as_ref(),
1368 Some(self.unsolicited_responses_tx.clone()),
1369 )
1370 .await?;
1371
1372 Ok(())
1373 }
1374
1375 pub async fn run_command<S: AsRef<str>>(&mut self, command: S) -> Result<RequestId> {
1377 let id = self.conn.run_command(command.as_ref()).await?;
1378
1379 Ok(id)
1380 }
1381
1382 pub async fn run_command_untagged<S: AsRef<str>>(&mut self, command: S) -> Result<()> {
1384 self.conn.run_command_untagged(command.as_ref()).await?;
1385
1386 Ok(())
1387 }
1388
1389 pub async fn read_response(&mut self) -> io::Result<Option<ResponseData>> {
1391 self.conn.read_response().await
1392 }
1393}
1394
1395impl<T: Read + Write + Unpin + fmt::Debug> Connection<T> {
1396 unsafe_pinned!(stream: ImapStream<T>);
1397
1398 pub fn get_ref(&self) -> &T {
1400 self.stream.get_ref()
1401 }
1402
1403 pub fn get_mut(&mut self) -> &mut T {
1405 self.stream.get_mut()
1406 }
1407
1408 pub fn into_inner(self) -> T {
1410 let Self { stream, .. } = self;
1411 stream.into_inner()
1412 }
1413
1414 pub async fn read_response(&mut self) -> io::Result<Option<ResponseData>> {
1416 self.stream.try_next().await
1417 }
1418
1419 pub(crate) async fn run_command_untagged(&mut self, command: &str) -> Result<()> {
1420 self.stream
1421 .encode(Request(None, command.as_bytes().into()))
1422 .await?;
1423 self.stream.flush().await?;
1424 Ok(())
1425 }
1426
1427 pub(crate) async fn run_command(&mut self, command: &str) -> Result<RequestId> {
1428 let request_id = self.request_ids.next().unwrap(); self.stream
1430 .encode(Request(Some(request_id.clone()), command.as_bytes().into()))
1431 .await?;
1432 self.stream.flush().await?;
1433 Ok(request_id)
1434 }
1435
1436 pub async fn run_command_and_check_ok(
1438 &mut self,
1439 command: &str,
1440 unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1441 ) -> Result<()> {
1442 let id = self.run_command(command).await?;
1443 self.check_done_ok(&id, unsolicited).await?;
1444
1445 Ok(())
1446 }
1447
1448 pub(crate) async fn check_done_ok(
1449 &mut self,
1450 id: &RequestId,
1451 unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1452 ) -> Result<()> {
1453 if let Some(first_res) = self.stream.try_next().await? {
1454 self.check_done_ok_from(id, unsolicited, first_res).await
1455 } else {
1456 Err(Error::ConnectionLost)
1457 }
1458 }
1459
1460 pub(crate) async fn check_done_ok_from(
1461 &mut self,
1462 id: &RequestId,
1463 unsolicited: Option<channel::Sender<UnsolicitedResponse>>,
1464 mut response: ResponseData,
1465 ) -> Result<()> {
1466 loop {
1467 if let Response::Done {
1468 status,
1469 code,
1470 information,
1471 tag,
1472 } = response.parsed()
1473 {
1474 self.check_status_ok(status, code.as_ref(), information.as_deref())?;
1475
1476 if tag == id {
1477 return Ok(());
1478 }
1479 }
1480
1481 if let Some(unsolicited) = unsolicited.clone() {
1482 handle_unilateral(response, unsolicited);
1483 }
1484
1485 let Some(res) = self.stream.try_next().await? else {
1486 return Err(Error::ConnectionLost);
1487 };
1488 response = res;
1489 }
1490 }
1491
1492 pub(crate) fn check_status_ok(
1493 &self,
1494 status: &imap_proto::Status,
1495 code: Option<&imap_proto::ResponseCode<'_>>,
1496 information: Option<&str>,
1497 ) -> Result<()> {
1498 use imap_proto::Status;
1499 match status {
1500 Status::Ok => Ok(()),
1501 Status::Bad => Err(Error::Bad(format!("code: {code:?}, info: {information:?}"))),
1502 Status::No => Err(Error::No(format!("code: {code:?}, info: {information:?}"))),
1503 _ => Err(Error::Io(io::Error::other(format!(
1504 "status: {status:?}, code: {code:?}, information: {information:?}"
1505 )))),
1506 }
1507 }
1508}
1509
1510fn validate_str(value: &str) -> Result<String> {
1511 let quoted = quote!(value);
1512 if quoted.find('\n').is_some() {
1513 return Err(Error::Validate(ValidateError('\n')));
1514 }
1515 if quoted.find('\r').is_some() {
1516 return Err(Error::Validate(ValidateError('\r')));
1517 }
1518 Ok(quoted)
1519}
1520
1521#[cfg(test)]
1522mod tests {
1523 use pretty_assertions::assert_eq;
1524
1525 use super::super::error::Result;
1526 use super::super::mock_stream::MockStream;
1527 use super::*;
1528 use std::borrow::Cow;
1529 use std::future::Future;
1530
1531 use async_std::sync::{Arc, Mutex};
1532 use futures::StreamExt;
1533 use imap_proto::Status;
1534
1535 macro_rules! mock_client {
1536 ($s:expr) => {
1537 Client::new($s)
1538 };
1539 }
1540
1541 macro_rules! mock_session {
1542 ($s:expr) => {
1543 Session::new(mock_client!($s).conn)
1544 };
1545 }
1546
1547 macro_rules! assert_eq_bytes {
1548 ($a:expr, $b:expr, $c:expr) => {
1549 assert_eq!(
1550 std::str::from_utf8($a).unwrap(),
1551 std::str::from_utf8($b).unwrap(),
1552 $c
1553 )
1554 };
1555 }
1556
1557 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1558 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1559 async fn fetch_body() {
1560 let response = "a0 OK Logged in.\r\n\
1561 * 2 FETCH (BODY[TEXT] {3}\r\nfoo)\r\n\
1562 a0 OK FETCH completed\r\n";
1563 let mut session = mock_session!(MockStream::new(response.as_bytes().to_vec()));
1564 session.read_response().await.unwrap().unwrap();
1565 session.read_response().await.unwrap().unwrap();
1566 }
1567
1568 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1569 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1570 async fn readline_delay_read() {
1571 let greeting = "* OK Dovecot ready.\r\n";
1572 let mock_stream = MockStream::default()
1573 .with_buf(greeting.as_bytes().to_vec())
1574 .with_delay();
1575
1576 let mut client = mock_client!(mock_stream);
1577 let actual_response = client.read_response().await.unwrap().unwrap();
1578 assert_eq!(
1579 actual_response.parsed(),
1580 &Response::Data {
1581 status: Status::Ok,
1582 code: None,
1583 information: Some(Cow::Borrowed("Dovecot ready.")),
1584 }
1585 );
1586 }
1587
1588 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1589 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1590 async fn readline_eof() {
1591 let mock_stream = MockStream::default().with_eof();
1592 let mut client = mock_client!(mock_stream);
1593 let res = client.read_response().await.unwrap();
1594 assert!(res.is_none());
1595 }
1596
1597 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1598 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1599 #[should_panic]
1600 async fn readline_err() {
1601 let mock_stream = MockStream::default().with_err();
1603 let mut client = mock_client!(mock_stream);
1604 client.read_response().await.unwrap().unwrap();
1605 }
1606
1607 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1608 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1609 async fn authenticate() {
1610 let response = b"+ YmFy\r\n\
1611 A0001 OK Logged in\r\n"
1612 .to_vec();
1613 let command = "A0001 AUTHENTICATE PLAIN\r\n\
1614 Zm9v\r\n";
1615 let mock_stream = MockStream::new(response);
1616 let client = mock_client!(mock_stream);
1617 enum Authenticate {
1618 Auth,
1619 }
1620 impl Authenticator for &Authenticate {
1621 type Response = Vec<u8>;
1622 fn process(&mut self, challenge: &[u8]) -> Self::Response {
1623 assert!(challenge == b"bar", "Invalid authenticate challenge");
1624 b"foo".to_vec()
1625 }
1626 }
1627 let session = client
1628 .authenticate("PLAIN", &Authenticate::Auth)
1629 .await
1630 .ok()
1631 .unwrap();
1632 assert_eq_bytes!(
1633 &session.stream.inner.written_buf,
1634 command.as_bytes(),
1635 "Invalid authenticate command"
1636 );
1637 }
1638
1639 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1640 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1641 async fn login() {
1642 let response = b"A0001 OK Logged in\r\n".to_vec();
1643 let username = "username";
1644 let password = "password";
1645 let command = format!("A0001 LOGIN {} {}\r\n", quote!(username), quote!(password));
1646 let mock_stream = MockStream::new(response);
1647 let client = mock_client!(mock_stream);
1648 if let Ok(session) = client.login(username, password).await {
1649 assert_eq!(
1650 session.stream.inner.written_buf,
1651 command.as_bytes().to_vec(),
1652 "Invalid login command"
1653 );
1654 } else {
1655 unreachable!("invalid login");
1656 }
1657 }
1658
1659 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1660 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1661 async fn login_with_capabilities() {
1662 let response = b"A0001 OK [CAPABILITY IMAP4rev1 IDLE MOVE] Logged in\r\n".to_vec();
1663 let username = "username";
1664 let password = "password";
1665 let command = format!("A0001 LOGIN {} {}\r\n", quote!(username), quote!(password));
1666 let mock_stream = MockStream::new(response);
1667 let client = mock_client!(mock_stream);
1668 if let Ok((session, capabilities)) =
1669 client.login_with_capabilities(username, password).await
1670 {
1671 assert_eq!(
1672 session.stream.inner.written_buf,
1673 command.as_bytes().to_vec(),
1674 "Invalid login command"
1675 );
1676 let capabilities = capabilities.expect("Capabilities should not be None");
1677 assert_eq!(capabilities.len(), 3);
1678 assert!(capabilities.has(&Capability::Imap4rev1));
1679 assert!(capabilities.has(&Capability::Atom("IDLE".to_string())));
1680 assert!(capabilities.has(&Capability::Atom("MOVE".to_string())));
1681 assert!(!capabilities.has(&Capability::Atom("ID".to_string())));
1682 } else {
1683 unreachable!("invalid login");
1684 }
1685 }
1686
1687 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1690 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1691 async fn login_without_capabilities() {
1692 let response = b"A0001 OK Logged in\r\n".to_vec();
1693 let username = "username";
1694 let password = "password";
1695 let command = format!("A0001 LOGIN {} {}\r\n", quote!(username), quote!(password));
1696 let mock_stream = MockStream::new(response);
1697 let client = mock_client!(mock_stream);
1698 if let Ok((session, capabilities)) =
1699 client.login_with_capabilities(username, password).await
1700 {
1701 assert_eq!(
1702 session.stream.inner.written_buf,
1703 command.as_bytes().to_vec(),
1704 "Invalid login command"
1705 );
1706 assert!(capabilities.is_none());
1707 } else {
1708 unreachable!("invalid login");
1709 }
1710 }
1711
1712 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1713 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1714 async fn logout() {
1715 let response = b"A0001 OK Logout completed.\r\n".to_vec();
1716 let command = "A0001 LOGOUT\r\n";
1717 let mock_stream = MockStream::new(response);
1718 let mut session = mock_session!(mock_stream);
1719 session.logout().await.unwrap();
1720 assert!(
1721 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1722 "Invalid logout command"
1723 );
1724 }
1725
1726 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1727 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1728 async fn rename() {
1729 let response = b"A0001 OK RENAME completed\r\n".to_vec();
1730 let current_mailbox_name = "INBOX";
1731 let new_mailbox_name = "NEWINBOX";
1732 let command = format!(
1733 "A0001 RENAME {} {}\r\n",
1734 quote!(current_mailbox_name),
1735 quote!(new_mailbox_name)
1736 );
1737 let mock_stream = MockStream::new(response);
1738 let mut session = mock_session!(mock_stream);
1739 session
1740 .rename(current_mailbox_name, new_mailbox_name)
1741 .await
1742 .unwrap();
1743 assert!(
1744 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1745 "Invalid rename command"
1746 );
1747 }
1748
1749 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1750 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1751 async fn subscribe() {
1752 let response = b"A0001 OK SUBSCRIBE completed\r\n".to_vec();
1753 let mailbox = "INBOX";
1754 let command = format!("A0001 SUBSCRIBE {}\r\n", quote!(mailbox));
1755 let mock_stream = MockStream::new(response);
1756 let mut session = mock_session!(mock_stream);
1757 session.subscribe(mailbox).await.unwrap();
1758 assert!(
1759 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1760 "Invalid subscribe command"
1761 );
1762 }
1763
1764 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1765 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1766 async fn unsubscribe() {
1767 let response = b"A0001 OK UNSUBSCRIBE completed\r\n".to_vec();
1768 let mailbox = "INBOX";
1769 let command = format!("A0001 UNSUBSCRIBE {}\r\n", quote!(mailbox));
1770 let mock_stream = MockStream::new(response);
1771 let mut session = mock_session!(mock_stream);
1772 session.unsubscribe(mailbox).await.unwrap();
1773 assert!(
1774 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1775 "Invalid unsubscribe command"
1776 );
1777 }
1778
1779 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1780 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1781 async fn expunge() {
1782 let response = b"A0001 OK EXPUNGE completed\r\n".to_vec();
1783 let mock_stream = MockStream::new(response);
1784 let mut session = mock_session!(mock_stream);
1785 session.expunge().await.unwrap().collect::<Vec<_>>().await;
1786 assert!(
1787 session.stream.inner.written_buf == b"A0001 EXPUNGE\r\n".to_vec(),
1788 "Invalid expunge command"
1789 );
1790 }
1791
1792 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1793 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1794 async fn uid_expunge() {
1795 let response = b"* 2 EXPUNGE\r\n\
1796 * 3 EXPUNGE\r\n\
1797 * 4 EXPUNGE\r\n\
1798 A0001 OK UID EXPUNGE completed\r\n"
1799 .to_vec();
1800 let mock_stream = MockStream::new(response);
1801 let mut session = mock_session!(mock_stream);
1802 session
1803 .uid_expunge("2:4")
1804 .await
1805 .unwrap()
1806 .collect::<Vec<_>>()
1807 .await;
1808 assert!(
1809 session.stream.inner.written_buf == b"A0001 UID EXPUNGE 2:4\r\n".to_vec(),
1810 "Invalid expunge command"
1811 );
1812 }
1813
1814 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1815 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1816 async fn check() {
1817 let response = b"A0001 OK CHECK completed\r\n".to_vec();
1818 let mock_stream = MockStream::new(response);
1819 let mut session = mock_session!(mock_stream);
1820 session.check().await.unwrap();
1821 assert!(
1822 session.stream.inner.written_buf == b"A0001 CHECK\r\n".to_vec(),
1823 "Invalid check command"
1824 );
1825 }
1826
1827 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1828 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1829 async fn examine() {
1830 let response = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n\
1831 * OK [PERMANENTFLAGS ()] Read-only mailbox.\r\n\
1832 * 1 EXISTS\r\n\
1833 * 1 RECENT\r\n\
1834 * OK [UNSEEN 1] First unseen.\r\n\
1835 * OK [UIDVALIDITY 1257842737] UIDs valid\r\n\
1836 * OK [UIDNEXT 2] Predicted next UID\r\n\
1837 A0001 OK [READ-ONLY] Select completed.\r\n"
1838 .to_vec();
1839 let expected_mailbox = Mailbox {
1840 flags: vec![
1841 Flag::Answered,
1842 Flag::Flagged,
1843 Flag::Deleted,
1844 Flag::Seen,
1845 Flag::Draft,
1846 ],
1847 exists: 1,
1848 recent: 1,
1849 unseen: Some(1),
1850 permanent_flags: vec![],
1851 uid_next: Some(2),
1852 uid_validity: Some(1257842737),
1853 highest_modseq: None,
1854 };
1855 let mailbox_name = "INBOX";
1856 let command = format!("A0001 EXAMINE {}\r\n", quote!(mailbox_name));
1857 let mock_stream = MockStream::new(response);
1858 let mut session = mock_session!(mock_stream);
1859 let mailbox = session.examine(mailbox_name).await.unwrap();
1860 assert!(
1861 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1862 "Invalid examine command"
1863 );
1864 assert_eq!(mailbox, expected_mailbox);
1865 }
1866
1867 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1868 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1869 async fn select() {
1870 let response = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n\
1871 * OK [PERMANENTFLAGS (\\* \\Answered \\Flagged \\Deleted \\Draft \\Seen)] \
1872 Read-only mailbox.\r\n\
1873 * 1 EXISTS\r\n\
1874 * 1 RECENT\r\n\
1875 * OK [UNSEEN 1] First unseen.\r\n\
1876 * OK [UIDVALIDITY 1257842737] UIDs valid\r\n\
1877 * OK [UIDNEXT 2] Predicted next UID\r\n\
1878 * OK [HIGHESTMODSEQ 90060115205545359] Highest mailbox modsequence\r\n\
1879 A0001 OK [READ-ONLY] Select completed.\r\n"
1880 .to_vec();
1881 let expected_mailbox = Mailbox {
1882 flags: vec![
1883 Flag::Answered,
1884 Flag::Flagged,
1885 Flag::Deleted,
1886 Flag::Seen,
1887 Flag::Draft,
1888 ],
1889 exists: 1,
1890 recent: 1,
1891 unseen: Some(1),
1892 permanent_flags: vec![
1893 Flag::MayCreate,
1894 Flag::Answered,
1895 Flag::Flagged,
1896 Flag::Deleted,
1897 Flag::Draft,
1898 Flag::Seen,
1899 ],
1900 uid_next: Some(2),
1901 uid_validity: Some(1257842737),
1902 highest_modseq: Some(90060115205545359),
1903 };
1904 let mailbox_name = "INBOX";
1905 let command = format!("A0001 SELECT {}\r\n", quote!(mailbox_name));
1906 let mock_stream = MockStream::new(response);
1907 let mut session = mock_session!(mock_stream);
1908 let mailbox = session.select(mailbox_name).await.unwrap();
1909 assert!(
1910 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1911 "Invalid select command"
1912 );
1913 assert_eq!(mailbox, expected_mailbox);
1914 }
1915
1916 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1917 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1918 async fn search() {
1919 let response = b"* SEARCH 1 2 3 4 5\r\n\
1920 A0001 OK Search completed\r\n"
1921 .to_vec();
1922 let mock_stream = MockStream::new(response);
1923 let mut session = mock_session!(mock_stream);
1924 let ids = session.search("Unseen").await.unwrap();
1925 let ids: HashSet<u32> = ids.iter().cloned().collect();
1926 assert!(
1927 session.stream.inner.written_buf == b"A0001 SEARCH Unseen\r\n".to_vec(),
1928 "Invalid search command"
1929 );
1930 assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
1931 }
1932
1933 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1934 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1935 async fn uid_search() {
1936 let response = b"* SEARCH 1 2 3 4 5\r\n\
1937 A0001 OK Search completed\r\n"
1938 .to_vec();
1939 let mock_stream = MockStream::new(response);
1940 let mut session = mock_session!(mock_stream);
1941 let ids = session.uid_search("Unseen").await.unwrap();
1942 let ids: HashSet<Uid> = ids.iter().cloned().collect();
1943 assert!(
1944 session.stream.inner.written_buf == b"A0001 UID SEARCH Unseen\r\n".to_vec(),
1945 "Invalid search command"
1946 );
1947 assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
1948 }
1949
1950 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1951 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1952 async fn uid_search_unordered() {
1953 let response = b"* SEARCH 1 2 3 4 5\r\n\
1954 A0002 OK CAPABILITY completed\r\n\
1955 A0001 OK Search completed\r\n"
1956 .to_vec();
1957 let mock_stream = MockStream::new(response);
1958 let mut session = mock_session!(mock_stream);
1959 let ids = session.uid_search("Unseen").await.unwrap();
1960 let ids: HashSet<Uid> = ids.iter().cloned().collect();
1961 assert!(
1962 session.stream.inner.written_buf == b"A0001 UID SEARCH Unseen\r\n".to_vec(),
1963 "Invalid search command"
1964 );
1965 assert_eq!(ids, [1, 2, 3, 4, 5].iter().cloned().collect());
1966 }
1967
1968 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1969 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1970 async fn capability() {
1971 let response = b"* CAPABILITY IMAP4rev1 STARTTLS AUTH=GSSAPI LOGINDISABLED\r\n\
1972 A0001 OK CAPABILITY completed\r\n"
1973 .to_vec();
1974 let expected_capabilities = vec!["IMAP4rev1", "STARTTLS", "AUTH=GSSAPI", "LOGINDISABLED"];
1975 let mock_stream = MockStream::new(response);
1976 let mut session = mock_session!(mock_stream);
1977 let capabilities = session.capabilities().await.unwrap();
1978 assert!(
1979 session.stream.inner.written_buf == b"A0001 CAPABILITY\r\n".to_vec(),
1980 "Invalid capability command"
1981 );
1982 assert_eq!(capabilities.len(), 4);
1983 for e in expected_capabilities {
1984 assert!(capabilities.has_str(e));
1985 }
1986 }
1987
1988 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
1989 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
1990 async fn create() {
1991 let response = b"A0001 OK CREATE completed\r\n".to_vec();
1992 let mailbox_name = "INBOX";
1993 let command = format!("A0001 CREATE {}\r\n", quote!(mailbox_name));
1994 let mock_stream = MockStream::new(response);
1995 let mut session = mock_session!(mock_stream);
1996 session.create(mailbox_name).await.unwrap();
1997 assert!(
1998 session.stream.inner.written_buf == command.as_bytes().to_vec(),
1999 "Invalid create command"
2000 );
2001 }
2002
2003 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2004 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2005 async fn delete() {
2006 let response = b"A0001 OK DELETE completed\r\n".to_vec();
2007 let mailbox_name = "INBOX";
2008 let command = format!("A0001 DELETE {}\r\n", quote!(mailbox_name));
2009 let mock_stream = MockStream::new(response);
2010 let mut session = mock_session!(mock_stream);
2011 session.delete(mailbox_name).await.unwrap();
2012 assert!(
2013 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2014 "Invalid delete command"
2015 );
2016 }
2017
2018 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2019 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2020 async fn noop() {
2021 let response = b"A0001 OK NOOP completed\r\n".to_vec();
2022 let mock_stream = MockStream::new(response);
2023 let mut session = mock_session!(mock_stream);
2024 session.noop().await.unwrap();
2025 assert!(
2026 session.stream.inner.written_buf == b"A0001 NOOP\r\n".to_vec(),
2027 "Invalid noop command"
2028 );
2029 }
2030
2031 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2032 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2033 async fn close() {
2034 let response = b"A0001 OK CLOSE completed\r\n".to_vec();
2035 let mock_stream = MockStream::new(response);
2036 let mut session = mock_session!(mock_stream);
2037 session.close().await.unwrap();
2038 assert!(
2039 session.stream.inner.written_buf == b"A0001 CLOSE\r\n".to_vec(),
2040 "Invalid close command"
2041 );
2042 }
2043
2044 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2045 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2046 async fn store() {
2047 generic_store(" ", |c, set, query| async move {
2048 c.lock()
2049 .await
2050 .store(set, query)
2051 .await?
2052 .collect::<Vec<_>>()
2053 .await;
2054 Ok(())
2055 })
2056 .await;
2057 }
2058
2059 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2060 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2061 async fn uid_store() {
2062 generic_store(" UID ", |c, set, query| async move {
2063 c.lock()
2064 .await
2065 .uid_store(set, query)
2066 .await?
2067 .collect::<Vec<_>>()
2068 .await;
2069 Ok(())
2070 })
2071 .await;
2072 }
2073
2074 async fn generic_store<'a, F, T, K>(prefix: &'a str, op: F)
2075 where
2076 F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2077 K: 'a + Future<Output = Result<T>>,
2078 {
2079 let res = "* 2 FETCH (FLAGS (\\Deleted \\Seen))\r\n\
2080 * 3 FETCH (FLAGS (\\Deleted))\r\n\
2081 * 4 FETCH (FLAGS (\\Deleted \\Flagged \\Seen))\r\n\
2082 A0001 OK STORE completed\r\n";
2083
2084 generic_with_uid(res, "STORE", "2.4", "+FLAGS (\\Deleted)", prefix, op).await;
2085 }
2086
2087 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2088 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2089 async fn copy() {
2090 generic_copy(" ", |c, set, query| async move {
2091 c.lock().await.copy(set, query).await?;
2092 Ok(())
2093 })
2094 .await;
2095 }
2096
2097 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2098 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2099 async fn uid_copy() {
2100 generic_copy(" UID ", |c, set, query| async move {
2101 c.lock().await.uid_copy(set, query).await?;
2102 Ok(())
2103 })
2104 .await;
2105 }
2106
2107 async fn generic_copy<'a, F, T, K>(prefix: &'a str, op: F)
2108 where
2109 F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2110 K: 'a + Future<Output = Result<T>>,
2111 {
2112 let resp = "A0001 OK COPY completed\r\n".as_bytes().to_vec();
2113 let seq = "2:4";
2114 let query = "MEETING";
2115 let line = format!("A0001{prefix}COPY {seq} {}\r\n", quote!(query));
2116 let session = Arc::new(Mutex::new(mock_session!(MockStream::new(resp))));
2117
2118 {
2119 let _ = op(session.clone(), seq, query).await.unwrap();
2120 }
2121 assert!(
2122 session.lock().await.stream.inner.written_buf == line.as_bytes().to_vec(),
2123 "Invalid command"
2124 );
2125 }
2126
2127 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2128 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2129 async fn mv() {
2130 let response = b"* OK [COPYUID 1511554416 142,399 41:42] Moved UIDs.\r\n\
2131 * 2 EXPUNGE\r\n\
2132 * 1 EXPUNGE\r\n\
2133 A0001 OK Move completed\r\n"
2134 .to_vec();
2135 let mailbox_name = "MEETING";
2136 let command = format!("A0001 MOVE 1:2 {}\r\n", quote!(mailbox_name));
2137 let mock_stream = MockStream::new(response);
2138 let mut session = mock_session!(mock_stream);
2139 session.mv("1:2", mailbox_name).await.unwrap();
2140 assert!(
2141 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2142 "Invalid move command"
2143 );
2144 }
2145
2146 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2147 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2148 async fn uid_mv() {
2149 let response = b"* OK [COPYUID 1511554416 142,399 41:42] Moved UIDs.\r\n\
2150 * 2 EXPUNGE\r\n\
2151 * 1 EXPUNGE\r\n\
2152 A0001 OK Move completed\r\n"
2153 .to_vec();
2154 let mailbox_name = "MEETING";
2155 let command = format!("A0001 UID MOVE 41:42 {}\r\n", quote!(mailbox_name));
2156 let mock_stream = MockStream::new(response);
2157 let mut session = mock_session!(mock_stream);
2158 session.uid_mv("41:42", mailbox_name).await.unwrap();
2159 assert!(
2160 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2161 "Invalid uid move command"
2162 );
2163 }
2164
2165 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2166 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2167 async fn fetch() {
2168 generic_fetch(" ", |c, seq, query| async move {
2169 c.lock()
2170 .await
2171 .fetch(seq, query)
2172 .await?
2173 .collect::<Vec<_>>()
2174 .await;
2175
2176 Ok(())
2177 })
2178 .await;
2179 }
2180
2181 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2182 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2183 async fn uid_fetch() {
2184 generic_fetch(" UID ", |c, seq, query| async move {
2185 c.lock()
2186 .await
2187 .uid_fetch(seq, query)
2188 .await?
2189 .collect::<Vec<_>>()
2190 .await;
2191 Ok(())
2192 })
2193 .await;
2194 }
2195
2196 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2197 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2198 async fn fetch_unexpected_eof() {
2199 let response = b"".to_vec();
2201
2202 let mock_stream = MockStream::new(response);
2203 let mut session = mock_session!(mock_stream);
2204
2205 {
2206 let mut fetch_result = session
2207 .uid_fetch("1:*", "(FLAGS BODY.PEEK[])")
2208 .await
2209 .unwrap();
2210
2211 let err = fetch_result.try_next().await.unwrap_err();
2213 let Error::Io(io_err) = err else {
2214 panic!("Unexpected error type: {err}")
2215 };
2216 assert_eq!(io_err.kind(), io::ErrorKind::UnexpectedEof);
2217 }
2218
2219 assert_eq!(
2220 session.stream.inner.written_buf,
2221 b"A0001 UID FETCH 1:* (FLAGS BODY.PEEK[])\r\n".to_vec()
2222 );
2223 }
2224
2225 async fn generic_fetch<'a, F, T, K>(prefix: &'a str, op: F)
2226 where
2227 F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2228 K: 'a + Future<Output = Result<T>>,
2229 {
2230 generic_with_uid(
2231 "A0001 OK FETCH completed\r\n",
2232 "FETCH",
2233 "1",
2234 "BODY[]",
2235 prefix,
2236 op,
2237 )
2238 .await;
2239 }
2240
2241 async fn generic_with_uid<'a, F, T, K>(
2242 res: &'a str,
2243 cmd: &'a str,
2244 seq: &'a str,
2245 query: &'a str,
2246 prefix: &'a str,
2247 op: F,
2248 ) where
2249 F: 'a + FnOnce(Arc<Mutex<Session<MockStream>>>, &'a str, &'a str) -> K,
2250 K: 'a + Future<Output = Result<T>>,
2251 {
2252 let resp = res.as_bytes().to_vec();
2253 let line = format!("A0001{prefix}{cmd} {seq} {query}\r\n");
2254 let session = Arc::new(Mutex::new(mock_session!(MockStream::new(resp))));
2255
2256 {
2257 let _ = op(session.clone(), seq, query).await.unwrap();
2258 }
2259 assert!(
2260 session.lock().await.stream.inner.written_buf == line.as_bytes().to_vec(),
2261 "Invalid command"
2262 );
2263 }
2264
2265 #[test]
2266 fn quote_backslash() {
2267 assert_eq!("\"test\\\\text\"", quote!(r"test\text"));
2268 }
2269
2270 #[test]
2271 fn quote_dquote() {
2272 assert_eq!("\"test\\\"text\"", quote!("test\"text"));
2273 }
2274
2275 #[test]
2276 fn validate_random() {
2277 assert_eq!(
2278 "\"~iCQ_k;>[&\\\"sVCvUW`e<<P!wJ\"",
2279 &validate_str("~iCQ_k;>[&\"sVCvUW`e<<P!wJ").unwrap()
2280 );
2281 }
2282
2283 #[test]
2284 fn validate_newline() {
2285 if let Err(ref e) = validate_str("test\nstring") {
2286 if let Error::Validate(ref ve) = e {
2287 if ve.0 == '\n' {
2288 return;
2289 }
2290 }
2291 panic!("Wrong error: {e:?}");
2292 }
2293 panic!("No error");
2294 }
2295
2296 #[test]
2297 #[allow(unreachable_patterns)]
2298 fn validate_carriage_return() {
2299 if let Err(ref e) = validate_str("test\rstring") {
2300 if let Error::Validate(ref ve) = e {
2301 if ve.0 == '\r' {
2302 return;
2303 }
2304 }
2305 panic!("Wrong error: {e:?}");
2306 }
2307 panic!("No error");
2308 }
2309
2310 #[cfg(feature = "runtime-tokio")]
2314 async fn handle_client(stream: tokio::io::DuplexStream) -> Result<()> {
2315 use tokio::io::AsyncBufReadExt;
2316
2317 let (reader, mut writer) = tokio::io::split(stream);
2318 let reader = tokio::io::BufReader::new(reader);
2319
2320 let mut lines = reader.lines();
2321 while let Some(line) = lines.next_line().await? {
2322 let (request_id, request) = line.split_once(' ').unwrap();
2323 eprintln!("Received request {request_id}.");
2324
2325 let (id, _) = request
2326 .strip_prefix("FETCH ")
2327 .unwrap()
2328 .split_once(' ')
2329 .unwrap();
2330 let id = id.parse().unwrap();
2331
2332 let mut body = concat!(
2333 "From: Bob <bob@example.com>\r\n",
2334 "To: Alice <alice@example.org>\r\n",
2335 "Subject: Test\r\n",
2336 "Message-Id: <foobar@example.com>\r\n",
2337 "Date: Sun, 22 Mar 2020 00:00:00 +0100\r\n",
2338 "\r\n",
2339 )
2340 .to_string();
2341 for _ in 1..id {
2342 body +=
2343 "012345678901234567890123456789012345678901234567890123456789012345678901\r\n";
2344 }
2345 let body_len = body.len();
2346
2347 let response = format!("* {id} FETCH (RFC822.SIZE {body_len} BODY[] {{{body_len}}}\r\n{body} FLAGS (\\Seen))\r\n");
2348 writer.write_all(response.as_bytes()).await?;
2349 writer
2350 .write_all(format!("{request_id} OK FETCH completed\r\n").as_bytes())
2351 .await?;
2352 writer.flush().await?;
2353 }
2354
2355 Ok(())
2356 }
2357
2358 #[cfg(feature = "runtime-tokio")]
2365 #[cfg_attr(
2366 feature = "runtime-tokio",
2367 tokio::test(flavor = "multi_thread", worker_threads = 2)
2368 )]
2369 async fn large_fetch() -> Result<()> {
2370 use futures::TryStreamExt;
2371
2372 let (client, server) = tokio::io::duplex(4096);
2373 tokio::spawn(handle_client(server));
2374
2375 let client = crate::Client::new(client);
2376 let mut imap_session = Session::new(client.conn);
2377
2378 for i in 200..300 {
2379 eprintln!("Fetching {i}.");
2380 let mut messages_stream = imap_session
2381 .fetch(format!("{i}"), "(RFC822.SIZE BODY.PEEK[] FLAGS)")
2382 .await?;
2383 let fetch = messages_stream
2384 .try_next()
2385 .await?
2386 .expect("no FETCH returned");
2387 let body = fetch.body().expect("message did not have a body!");
2388 assert_eq!(body.len(), 76 + 74 * i);
2389
2390 let no_fetch = messages_stream.try_next().await?;
2391 assert!(no_fetch.is_none());
2392 drop(messages_stream);
2393 }
2394
2395 Ok(())
2396 }
2397
2398 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2399 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2400 async fn status() {
2401 {
2402 let response = b"* STATUS INBOX (UIDNEXT 25)\r\n\
2403 A0001 OK [CLIENTBUG] Status on selected mailbox completed (0.001 + 0.000 secs).\r\n"
2404 .to_vec();
2405
2406 let mock_stream = MockStream::new(response);
2407 let mut session = mock_session!(mock_stream);
2408 let status = session.status("INBOX", "(UIDNEXT)").await.unwrap();
2409 assert_eq!(
2410 session.stream.inner.written_buf,
2411 b"A0001 STATUS \"INBOX\" (UIDNEXT)\r\n".to_vec()
2412 );
2413 assert_eq!(status.uid_next, Some(25));
2414 }
2415
2416 {
2417 let response = b"* STATUS INBOX (RECENT 15)\r\n\
2418 A0001 OK STATUS completed\r\n"
2419 .to_vec();
2420
2421 let mock_stream = MockStream::new(response);
2422 let mut session = mock_session!(mock_stream);
2423 let status = session.status("INBOX", "(RECENT)").await.unwrap();
2424 assert_eq!(
2425 session.stream.inner.written_buf,
2426 b"A0001 STATUS \"INBOX\" (RECENT)\r\n".to_vec()
2427 );
2428 assert_eq!(status.recent, 15);
2429 }
2430
2431 {
2432 let response = b"* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)\r\n\
2434 A0001 OK STATUS completed\r\n"
2435 .to_vec();
2436
2437 let mock_stream = MockStream::new(response);
2438 let mut session = mock_session!(mock_stream);
2439 let status = session
2440 .status("blurdybloop", "(UIDNEXT MESSAGES)")
2441 .await
2442 .unwrap();
2443 assert_eq!(
2444 session.stream.inner.written_buf,
2445 b"A0001 STATUS \"blurdybloop\" (UIDNEXT MESSAGES)\r\n".to_vec()
2446 );
2447 assert_eq!(status.uid_next, Some(44292));
2448 assert_eq!(status.exists, 231);
2449 }
2450 }
2451
2452 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2453 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2454 async fn append() {
2455 {
2456 let response = b"+ OK\r\nA0001 OK [APPENDUID 1725735035 2] Append completed (0.052 + 12.097 + 0.049 secs).\r\n".to_vec();
2460
2461 let mock_stream = MockStream::new(response);
2462 let mut session = mock_session!(mock_stream);
2463 session
2464 .append("INBOX", Some(r"(\Seen)"), None, "foobarbaz")
2465 .await
2466 .unwrap();
2467 assert_eq!(
2468 session.stream.inner.written_buf,
2469 b"A0001 APPEND \"INBOX\" (\\Seen) {9}\r\nfoobarbaz\r\n".to_vec()
2470 );
2471 }
2472
2473 {
2474 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();
2478
2479 let mock_stream = MockStream::new(response);
2480 let mut session = mock_session!(mock_stream);
2481 session
2482 .append("INBOX", Some(r"(\Seen)"), None, "foobarbaz")
2483 .await
2484 .unwrap();
2485 assert_eq!(
2486 session.stream.inner.written_buf,
2487 b"A0001 APPEND \"INBOX\" (\\Seen) {9}\r\nfoobarbaz\r\n".to_vec()
2488 );
2489 let exists_response = session.unsolicited_responses.recv().await.unwrap();
2490 assert_eq!(exists_response, UnsolicitedResponse::Exists(3));
2491 let recent_response = session.unsolicited_responses.recv().await.unwrap();
2492 assert_eq!(recent_response, UnsolicitedResponse::Recent(2));
2493 }
2494
2495 {
2496 let response =
2498 b"A0001 NO [TRYCREATE] Mailbox doesn't exist: foobar (0.001 + 0.000 secs)."
2499 .to_vec();
2500 let mock_stream = MockStream::new(response);
2501 let mut session = mock_session!(mock_stream);
2502 session
2503 .append("foobar", None, None, "foobarbaz")
2504 .await
2505 .unwrap_err();
2506 assert_eq!(
2507 session.stream.inner.written_buf,
2508 b"A0001 APPEND \"foobar\" {9}\r\n".to_vec()
2509 );
2510 }
2511 }
2512
2513 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2514 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2515 async fn get_metadata() {
2516 {
2517 let response = b"* METADATA \"INBOX\" (/private/comment \"My own comment\")\r\n\
2518 A0001 OK GETMETADATA complete\r\n"
2519 .to_vec();
2520
2521 let mock_stream = MockStream::new(response);
2522 let mut session = mock_session!(mock_stream);
2523 let metadata = session
2524 .get_metadata("INBOX", "", "/private/comment")
2525 .await
2526 .unwrap();
2527 assert_eq!(
2528 session.stream.inner.written_buf,
2529 b"A0001 GETMETADATA \"INBOX\" /private/comment\r\n".to_vec()
2530 );
2531 assert_eq!(metadata.len(), 1);
2532 assert_eq!(metadata[0].entry, "/private/comment");
2533 assert_eq!(metadata[0].value.as_ref().unwrap(), "My own comment");
2534 }
2535
2536 {
2537 let response = b"* METADATA \"INBOX\" (/shared/comment \"Shared comment\" /private/comment \"My own comment\")\r\n\
2538 A0001 OK GETMETADATA complete\r\n"
2539 .to_vec();
2540
2541 let mock_stream = MockStream::new(response);
2542 let mut session = mock_session!(mock_stream);
2543 let metadata = session
2544 .get_metadata("INBOX", "", "(/shared/comment /private/comment)")
2545 .await
2546 .unwrap();
2547 assert_eq!(
2548 session.stream.inner.written_buf,
2549 b"A0001 GETMETADATA \"INBOX\" (/shared/comment /private/comment)\r\n".to_vec()
2550 );
2551 assert_eq!(metadata.len(), 2);
2552 assert_eq!(metadata[0].entry, "/shared/comment");
2553 assert_eq!(metadata[0].value.as_ref().unwrap(), "Shared comment");
2554 assert_eq!(metadata[1].entry, "/private/comment");
2555 assert_eq!(metadata[1].value.as_ref().unwrap(), "My own comment");
2556 }
2557
2558 {
2559 let response = b"* METADATA \"\" (/shared/comment {15}\r\nChatmail server /shared/admin {28}\r\nmailto:root@nine.testrun.org)\r\n\
2560 A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\r\n"
2561 .to_vec();
2562
2563 let mock_stream = MockStream::new(response);
2564 let mut session = mock_session!(mock_stream);
2565 let metadata = session
2566 .get_metadata("", "", "(/shared/comment /shared/admin)")
2567 .await
2568 .unwrap();
2569 assert_eq!(
2570 session.stream.inner.written_buf,
2571 b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2572 );
2573 assert_eq!(metadata.len(), 2);
2574 assert_eq!(metadata[0].entry, "/shared/comment");
2575 assert_eq!(metadata[0].value.as_ref().unwrap(), "Chatmail server");
2576 assert_eq!(metadata[1].entry, "/shared/admin");
2577 assert_eq!(
2578 metadata[1].value.as_ref().unwrap(),
2579 "mailto:root@nine.testrun.org"
2580 );
2581 }
2582
2583 {
2584 let response = b"* METADATA \"\" (/shared/comment \"Chatmail server\")\r\n\
2585 * METADATA \"\" (/shared/admin \"mailto:root@nine.testrun.org\")\r\n\
2586 A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\r\n"
2587 .to_vec();
2588
2589 let mock_stream = MockStream::new(response);
2590 let mut session = mock_session!(mock_stream);
2591 let metadata = session
2592 .get_metadata("", "", "(/shared/comment /shared/admin)")
2593 .await
2594 .unwrap();
2595 assert_eq!(
2596 session.stream.inner.written_buf,
2597 b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2598 );
2599 assert_eq!(metadata.len(), 2);
2600 assert_eq!(metadata[0].entry, "/shared/comment");
2601 assert_eq!(metadata[0].value.as_ref().unwrap(), "Chatmail server");
2602 assert_eq!(metadata[1].entry, "/shared/admin");
2603 assert_eq!(
2604 metadata[1].value.as_ref().unwrap(),
2605 "mailto:root@nine.testrun.org"
2606 );
2607 }
2608
2609 {
2610 let response = b"* METADATA \"\" (/shared/comment NIL /shared/admin NIL)\r\n\
2611 A0001 OK OK Getmetadata completed (0.001 + 0.000 secs).\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("", "", "(/shared/comment /shared/admin)")
2618 .await
2619 .unwrap();
2620 assert_eq!(
2621 session.stream.inner.written_buf,
2622 b"A0001 GETMETADATA \"\" (/shared/comment /shared/admin)\r\n".to_vec()
2623 );
2624 assert_eq!(metadata.len(), 2);
2625 assert_eq!(metadata[0].entry, "/shared/comment");
2626 assert_eq!(metadata[0].value, None);
2627 assert_eq!(metadata[1].entry, "/shared/admin");
2628 assert_eq!(metadata[1].value, None);
2629 }
2630 }
2631
2632 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2633 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2634 async fn test_get_quota_root() {
2635 {
2636 let response = b"* QUOTAROOT Sent Userquota\r\n\
2637 * QUOTA Userquota (STORAGE 4855 48576)\r\n\
2638 A0001 OK Getquotaroot completed (0.004 + 0.000 + 0.004 secs).\r\n"
2639 .to_vec();
2640
2641 let mock_stream = MockStream::new(response);
2642 let mut session = mock_session!(mock_stream);
2643 let (quotaroots, quota) = dbg!(session.get_quota_root("Sent").await.unwrap());
2644 assert_eq!(
2645 str::from_utf8(&session.stream.inner.written_buf).unwrap(),
2646 "A0001 GETQUOTAROOT \"Sent\"\r\n"
2647 );
2648 assert_eq!(
2649 quotaroots,
2650 vec![QuotaRoot {
2651 mailbox_name: "Sent".to_string(),
2652 quota_root_names: vec!["Userquota".to_string(),],
2653 },],
2654 );
2655 assert_eq!(
2656 quota,
2657 vec![Quota {
2658 root_name: "Userquota".to_string(),
2659 resources: vec![QuotaResource {
2660 name: QuotaResourceName::Storage,
2661 usage: 4855,
2662 limit: 48576,
2663 }],
2664 }]
2665 );
2666 assert_eq!(quota[0].resources[0].get_usage_percentage(), 9);
2667 }
2668
2669 {
2670 let response = b"* QUOTAROOT \"INBOX\" \"#19\"\r\n\
2671 * QUOTA \"#19\" (STORAGE 0 0)\r\n\
2672 A0001 OK GETQUOTAROOT successful.\r\n"
2673 .to_vec();
2674
2675 let mock_stream = MockStream::new(response);
2676 let mut session = mock_session!(mock_stream);
2677 let (quotaroots, quota) = session.get_quota_root("INBOX").await.unwrap();
2678 assert_eq!(
2679 str::from_utf8(&session.stream.inner.written_buf).unwrap(),
2680 "A0001 GETQUOTAROOT \"INBOX\"\r\n"
2681 );
2682 assert_eq!(
2683 quotaroots,
2684 vec![QuotaRoot {
2685 mailbox_name: "INBOX".to_string(),
2686 quota_root_names: vec!["#19".to_string(),],
2687 },],
2688 );
2689 assert_eq!(
2690 quota,
2691 vec![Quota {
2692 root_name: "#19".to_string(),
2693 resources: vec![QuotaResource {
2694 name: QuotaResourceName::Storage,
2695 usage: 0,
2696 limit: 0,
2697 }],
2698 }]
2699 );
2700 assert_eq!(quota[0].resources[0].get_usage_percentage(), 0);
2701 }
2702 }
2703
2704 #[cfg_attr(feature = "runtime-tokio", tokio::test)]
2705 #[cfg_attr(feature = "runtime-async-std", async_std::test)]
2706 async fn test_parsing_error() {
2707 let response = b"220 mail.example.org ESMTP Postcow\r\n".to_vec();
2709 let command = "A0001 NOOP\r\n";
2710 let mock_stream = MockStream::new(response);
2711 let mut session = mock_session!(mock_stream);
2712 assert!(session
2713 .noop()
2714 .await
2715 .unwrap_err()
2716 .to_string()
2717 .contains("220 mail.example.org ESMTP Postcow"));
2718 assert!(
2719 session.stream.inner.written_buf == command.as_bytes().to_vec(),
2720 "Invalid NOOP command"
2721 );
2722 }
2723}