async_pop/
lib.rs

1//!
2//! # Pop3 client
3//!
4//! This is a simple Pop3 client that implements all of the features according to [RFC 1939](https://www.rfc-editor.org/rfc/rfc1939), written in Rust.
5//!
6//! ## Usage
7//!
8//! You can create a new session using the `connect` function or the `connect_plain` function.
9//!
10//! `connect` expects a tls connector from the `async-native-tls` crate. In the future more tls options will be supported.
11//!
12//! If you already have a connected socket, you can also create a new session using the `new` function.
13//!
14//! ## Example
15//!
16//! ```rust,ignore
17//! extern crate async_pop;
18//! extern crate async_native_tls;
19//! extern crate mailparse;
20//!
21//! use async_native_tls::TlsConnector;
22//! use mailparse::parse_mail;
23//!
24//! #[tokio::main]
25//! async fn main() {
26//!     let tls = TlsConnector::new();
27//!
28//!     let mut client = async_pop::connect(("pop.gmail.com", 995), "pop.gmail.com", &tls, None).await.unwrap();
29//!
30//!     client.login("example@gmail.com", "password").await.unwrap();
31//!
32//!     let bytes = client.retr(1).await.unwrap();
33//!
34//!     let message = parse_mail(&bytes).unwrap();
35//!
36//!     let subject = message.headers.get_first_value("Subject").unwrap();
37//!
38//!     println!("{}", subject);
39//!
40//! }
41//! ```
42
43mod command;
44mod constants;
45pub mod error;
46mod macros;
47pub mod request;
48pub mod response;
49mod runtime;
50mod stream;
51
52#[cfg(feature = "sasl")]
53mod base64;
54#[cfg(feature = "sasl")]
55pub mod sasl;
56
57use std::collections::HashSet;
58
59use async_native_tls::{TlsConnector, TlsStream};
60use bytes::Bytes;
61use command::Command::*;
62use error::{ErrorKind, Result};
63use request::Request;
64use response::{
65    capability::{Capabilities, Capability},
66    list::ListResponse,
67    stat::Stat,
68    types::message::Text,
69    uidl::UidlResponse,
70    Response,
71};
72use stream::PopStream;
73
74use crate::{
75    error::err,
76    runtime::{
77        io::{Read, Write},
78        net::{TcpStream, ToSocketAddrs},
79        Instant,
80    },
81};
82
83#[derive(Eq, PartialEq, Debug)]
84pub enum ClientState {
85    Authentication,
86    Transaction,
87    Update,
88    None,
89}
90
91pub struct Client<S: Write + Read + Unpin + Send> {
92    inner: Option<PopStream<S>>,
93    capabilities: Capabilities,
94    marked_as_del: Vec<usize>,
95    greeting: Option<Text>,
96    read_greeting: bool,
97    state: ClientState,
98}
99
100/// Creates a client from a given socket connection.
101async fn create_client_from_socket<S: Read + Write + Unpin + Send>(
102    socket: PopStream<S>,
103) -> Result<Client<S>> {
104    let mut client = Client {
105        marked_as_del: Vec::new(),
106        capabilities: Vec::new(),
107        greeting: None,
108        read_greeting: false,
109        inner: Some(socket),
110        state: ClientState::Authentication,
111    };
112
113    client.greeting = Some(client.read_greeting().await?);
114
115    client.update_capabilities().await;
116
117    Ok(client)
118}
119
120/// Creates a new pop3 client from an existing stream.
121/// # Examples
122/// ```rust,ignore
123/// extern crate pop3;
124/// use std::net::TcpStream;
125///
126/// fn main() {
127///     // Not recommended to use plaintext, just an example.
128///     let stream = TcpStream::connect(("outlook.office365.com", 110)).unwrap();
129///
130///     let mut client = pop3::new(stream).unwrap();
131///
132///     client.quit().unwrap();
133/// }
134/// ```
135pub async fn new<S: Read + Write + Unpin + Send>(stream: S) -> Result<Client<S>> {
136    let socket = PopStream::new(stream);
137
138    create_client_from_socket(socket).await
139}
140
141/// Create a new pop3 client with a tls connection.
142pub async fn connect<A: ToSocketAddrs, D: AsRef<str>>(
143    addr: A,
144    domain: D,
145    tls_connector: &TlsConnector,
146) -> Result<Client<TlsStream<TcpStream>>> {
147    let tcp_stream = TcpStream::connect(addr).await?;
148
149    let tls_stream = tls_connector.connect(domain.as_ref(), tcp_stream).await?;
150
151    let socket = PopStream::new(tls_stream);
152
153    create_client_from_socket(socket).await
154}
155
156/// Creates a new pop3 client using a plain connection.
157///
158/// DO NOT USE in a production environment. Your password will be sent over a plain tcp stream which hackers could intercept.
159pub async fn connect_plain<A: ToSocketAddrs>(addr: A) -> Result<Client<TcpStream>> {
160    let tcp_stream = TcpStream::connect(addr).await?;
161
162    let socket = PopStream::new(tcp_stream);
163
164    create_client_from_socket(socket).await
165}
166
167impl<S: Read + Write + Unpin + Send> Client<S> {
168    /// Check if the client is in the correct state and return a mutable reference to the tcp connection.
169    fn inner_mut(&mut self) -> Result<&mut PopStream<S>> {
170        match self.inner.as_mut() {
171            Some(socket) => {
172                if self.state == ClientState::Transaction
173                    || self.state == ClientState::Authentication
174                {
175                    Ok(socket)
176                } else {
177                    err!(
178                        ErrorKind::ShouldNotBeConnected,
179                        "There is a connection, but our state indicates that we should not be connected",
180                    )
181                }
182            }
183            None => err!(ErrorKind::NotConnected, "Not connected to any server",),
184        }
185    }
186
187    pub fn inner(&self) -> &Option<PopStream<S>> {
188        &self.inner
189    }
190
191    pub fn into_inner(self) -> Option<PopStream<S>> {
192        self.inner
193    }
194
195    /// Check if the client is in the correct state.
196    fn check_client_state(&self, state: ClientState) -> Result<()> {
197        if self.state != state {
198            err!(
199                ErrorKind::IncorrectStateForCommand,
200                "The connection is not the right state to use this command",
201            )
202        } else {
203            Ok(())
204        }
205    }
206
207    /// ## Current client state
208    ///
209    /// Indicates what state the client is currently in, can be either
210    /// Authentication, Transaction, Update or None.
211    ///
212    /// Some methods are only available in some specified states and will error if run in an incorrect state.
213    ///
214    /// https://www.rfc-editor.org/rfc/rfc1939#section-3
215    pub fn get_state(&self) -> &ClientState {
216        &self.state
217    }
218
219    /// ## NOOP
220    /// The POP3 server does nothing, it merely replies with a positive response.
221    /// ### Arguments: none
222    /// ### Restrictions:
223    /// - May only be given in the TRANSACTION state
224    /// ### Possible Responses:
225    /// - OK
226    /// # Examples:
227    /// ```rust,ignore
228    /// client.noop()?;
229    /// ```
230    /// https://www.rfc-editor.org/rfc/rfc1939#page-9
231    pub async fn noop(&mut self) -> Result<()> {
232        self.send_request(Noop).await?;
233
234        Ok(())
235    }
236
237    /// ## UIDL
238    /// If an argument was given and the POP3 server issues a positive response with a line containing information for that message.
239    /// This line is called a "unique-id listing" for that message.
240    ///
241    /// If no argument was given and the POP3 server issues a positive response, then the response given is multi-line.
242    /// After the initial +OK, for each message in the maildrop, the POP3 server responds with a line containing information for that message.          This line is called a "unique-id listing" for that message.
243    ///
244    /// ### Arguments:
245    /// - a message-number (optional), which, if present, may NOT refer to a message marked as deleted.
246    ///
247    /// ### Restrictions:
248    /// - May only be given in the TRANSACTION state.
249    ///
250    /// ### Possible responses:
251    /// - +OK unique-id listing follows
252    /// - -ERR no such message
253    ///
254    /// https://www.rfc-editor.org/rfc/rfc1939#page-12
255    pub async fn uidl(&mut self, msg_number: Option<usize>) -> Result<UidlResponse> {
256        self.check_capability(vec![Capability::Uidl])?;
257
258        match msg_number.as_ref() {
259            Some(msg_number) => self.check_deleted(msg_number)?,
260            None => {}
261        };
262
263        let mut request: Request = Uidl.into();
264
265        if let Some(number) = msg_number {
266            request.add_arg(number)
267        }
268
269        let response = self.send_request(request).await?;
270
271        match response {
272            Response::Uidl(resp) => Ok(resp),
273            _ => {
274                err!(
275                    ErrorKind::UnexpectedResponse,
276                    "Did not received the expected uidl response"
277                )
278            }
279        }
280    }
281
282    /// When the last communication with the server happened.
283    ///
284    /// Returns [None] if there is no connection or the connection is not in the right state.
285    pub fn last_activity(&self) -> Option<Instant> {
286        Some(self.inner.as_ref()?.last_activity())
287    }
288
289    pub async fn top(&mut self, msg_number: usize, lines: usize) -> Result<Bytes> {
290        self.check_deleted(&msg_number)?;
291
292        self.check_capability(vec![Capability::Top])?;
293
294        let mut request: Request = Top.into();
295
296        request.add_arg(msg_number);
297        request.add_arg(lines);
298
299        let response = self.send_request(request).await?;
300
301        match response {
302            Response::Bytes(resp) => Ok(resp),
303            _ => err!(
304                ErrorKind::UnexpectedResponse,
305                "Did not received the expected top response"
306            ),
307        }
308    }
309
310    /// Check whether a given message is marked as deleted by the server.
311    ///
312    /// If this function returns true then the message may still not exist.
313    /// # Examples:
314    /// ```rust,ignore
315    /// let msg_number: u32 = 8;
316    /// let is_deleted = client.is_deleted(msg_number);
317    /// assert_eq!(is_deleted, false);
318    /// ```
319    pub fn is_deleted(&mut self, msg_number: &usize) -> bool {
320        self.marked_as_del.sort();
321
322        match self.marked_as_del.binary_search(msg_number) {
323            Ok(_) => true,
324            Err(_) => false,
325        }
326    }
327
328    fn check_deleted(&mut self, msg_number: &usize) -> Result<()> {
329        if self.is_deleted(msg_number) {
330            err!(
331                ErrorKind::MessageIsDeleted,
332                "This message has been marked as deleted and cannot be refenced anymore",
333            )
334        } else {
335            Ok(())
336        }
337    }
338
339    /// ## DELE
340    /// The POP3 server marks the message as deleted.  Any future reference to the message-number associated with the message in a POP3 command generates an error.  The POP3 server does not actually delete the message until the POP3 session enters the UPDATE state.
341    /// ### Arguments:
342    /// - a message-number (required) which may NOT refer to a message marked as deleted.
343    /// ### Restrictions:
344    /// - may only be given in the TRANSACTION state
345    /// ### Possible Responses:
346    /// - OK: message deleted
347    /// - ERR: no such message
348    /// # Examples
349    /// ```rust,ignore
350    /// let msg_number: u32 = 8;
351    /// let is_deleted = client.is_deleted(msg_number);
352    ///
353    /// println!("{}", is_deleted);
354    /// ```
355    pub async fn dele(&mut self, msg_number: usize) -> Result<Text> {
356        self.check_deleted(&msg_number)?;
357
358        let mut request: Request = Dele.into();
359
360        request.add_arg(msg_number);
361
362        let response = self.send_request(request).await?;
363
364        match response {
365            Response::Message(resp) => Ok(resp),
366            _ => err!(
367                ErrorKind::UnexpectedResponse,
368                "Did not received the expected dele response"
369            ),
370        }
371    }
372
373    /// ## RSET
374    /// If any messages have been marked as deleted by the POP3
375    /// server, they are unmarked.
376    /// ### Arguments: none
377    /// ### Restrictions:
378    /// - May only be given in the TRANSACTION state
379    /// ### Possible Responses:
380    /// - +OK
381    ///
382    /// https://www.rfc-editor.org/rfc/rfc1939#page-9
383    pub async fn rset(&mut self) -> Result<Text> {
384        let response = self.send_request(Rset).await?;
385
386        self.marked_as_del = Vec::new();
387
388        match response {
389            Response::Message(resp) => Ok(resp),
390            _ => err!(
391                ErrorKind::UnexpectedResponse,
392                "Did not received the expected rset response"
393            ),
394        }
395    }
396
397    /// ## RETR
398    /// Retrieves the full RFC822 compliant message from the server and returns it as a byte vector
399    /// ### Arguments:
400    /// - A message-number (required) which may NOT refer to a message marked as deleted
401    /// ### Restrictions:
402    /// - May only be given in the TRANSACTION state
403    /// ### Possible Responses:
404    /// - OK: message follows
405    /// - ERR: no such message
406    /// # Examples
407    /// ```rust,ignore
408    /// extern crate mailparse;
409    /// use mailparse::parse_mail;
410    ///
411    /// let response = client.retr(1).unwrap();
412    ///
413    /// let parsed = parse_mail(&response);
414    ///
415    /// let subject = parsed.headers.get_first_value("Subject").unwrap();
416    ///
417    /// println!("{}", subject);
418    /// ```
419    /// https://www.rfc-editor.org/rfc/rfc1939#page-8
420    pub async fn retr(&mut self, msg_number: usize) -> Result<Bytes> {
421        self.check_deleted(&msg_number)?;
422
423        let mut request: Request = Retr.into();
424
425        request.add_arg(msg_number);
426
427        let response = self.send_request(request).await?;
428
429        match response {
430            Response::Bytes(resp) => Ok(resp),
431            _ => err!(
432                ErrorKind::UnexpectedResponse,
433                "Did not received the expected retr response"
434            ),
435        }
436    }
437
438    /// ## LIST
439    ///
440    /// If an argument was given and the POP3 server issues a positive response with a line containing information for that message.  This line is called a "scan listing" for that message.
441    ///
442    /// If no argument was given and the POP3 server issues a positive response, then the response given is multi-line. After the initial +OK, for each message in the maildrop, the POP3 server responds with a line containing information for that message. This line is also called a "scan listing" for that message.  If there are no messages in the maildrop, then the POP3 server responds with no scan listings--it issues a positive response followed by a line containing a termination octet and a CRLF pair.
443    ///
444    /// ### Arguments:
445    /// - a message-number (optional), which, if present, may NOT refer to a message marked as deleted
446    /// ### Restrictions:
447    /// - may only be given in the TRANSACTION state
448    /// ### Possible responses:
449    /// - +OK scan listing follows
450    /// - -ERR no such message
451    pub async fn list(&mut self, msg_number: Option<usize>) -> Result<ListResponse> {
452        let mut request: Request = List.into();
453
454        if let Some(msg_number) = msg_number {
455            self.check_deleted(&msg_number)?;
456            request.add_arg(msg_number)
457        }
458
459        let response = self.send_request(request).await?;
460
461        match response {
462            Response::List(list) => Ok(list.into()),
463            Response::Stat(stat) => Ok(stat.into()),
464            _ => err!(
465                ErrorKind::UnexpectedResponse,
466                "Did not received the expected list response"
467            ),
468        }
469    }
470
471    /// ## STAT
472    /// The POP3 server issues a positive response with a line containing information for the maildrop. This line is called a "drop listing" for that maildrop.
473    /// ### Arguments: none
474    /// ### Restrictions:
475    /// - may only be given in the TRANSACTION state
476    /// ### Possible responses:
477    /// - +OK nn mm
478    pub async fn stat(&mut self) -> Result<Stat> {
479        let response = self.send_request(Stat).await?;
480
481        match response.into() {
482            Response::Stat(resp) => Ok(resp),
483            _ => err!(
484                ErrorKind::UnexpectedResponse,
485                "Did not received the expected stat response"
486            ),
487        }
488    }
489
490    /// ## APOP
491    /// Normally, each POP3 session starts with a USER/PASS exchange.  This results in a server/user-id specific password being sent in the clear on the network.  For intermittent use of POP3, this may not introduce a sizable risk.  However, many POP3 client implementations connect to the POP3 server on a regular basis -- to check for new mail.  Further the interval of session initiation may be on the order of five minutes.  Hence, the risk of password capture is greatly enhanced.
492    ///
493    /// An alternate method of authentication is required which provides for both origin authentication and replay protection, but which does not involve sending a password in the clear over the network.  The APOP command provides this functionality.
494    ///
495    /// A POP3 server which implements the APOP command will include a timestamp in its banner greeting.  The syntax of the timestamp corresponds to the `msg-id' in [RFC822], and MUST be different each time the POP3 server issues a banner greeting.  For example, on a UNIX implementation in which a separate UNIX process is used for each instance of a POP3 server, the syntax of the timestamp might be:
496    ///
497    /// `<process-ID.clock@hostname>`
498    ///
499    /// where `process-ID' is the decimal value of the process's PID, clock is the decimal value of the system clock, and hostname is the fully-qualified domain-name corresponding to the host where the POP3 server is running.
500    ///
501    /// The POP3 client makes note of this timestamp, and then issues the APOP command.  The `name` parameter has identical semantics to the `name` parameter of the USER command. The `digest` parameter is calculated by applying the MD5 algorithm [RFC1321] to a string consisting of the timestamp (including angle-brackets) followed by a shared
502    ///
503    /// ### Arguments:
504    /// a string identifying a mailbox and a MD5 digest string (both required)
505    ///
506    /// ### Restrictions:
507    /// may only be given in the AUTHORIZATION state after the POP3 greeting or after an unsuccessful USER or PASS command
508    ///
509    /// ### Possible responses:
510    /// - +OK maildrop locked and ready
511    /// - -ERR permission denied
512    pub async fn apop<N: AsRef<str>, D: AsRef<str>>(&mut self, name: N, digest: D) -> Result<Text> {
513        self.check_client_state(ClientState::Authentication)?;
514
515        self.has_read_greeting()?;
516
517        let mut request: Request = Apop.into();
518
519        request.add_arg(name.as_ref());
520        request.add_arg(digest.as_ref());
521
522        let response = self.send_request(request).await?;
523
524        self.update_capabilities().await;
525
526        self.state = ClientState::Transaction;
527
528        match response {
529            Response::Message(resp) => Ok(resp),
530            _ => err!(
531                ErrorKind::UnexpectedResponse,
532                "Did not received the expected apop response"
533            ),
534        }
535    }
536
537    /// ### AUTH
538    ///
539    /// Requires an [sasl::Authenticator] to work. One could implement this themeselves for any given mechanism, look at the documentation for this trait.
540    ///
541    /// If a common mechanism is needed, it can probably be found in the [sasl] module.
542    ///
543    /// The AUTH command indicates an authentication mechanism to the server.  If the server supports the requested authentication mechanism, it performs an authentication protocol exchange to authenticate and identify the user. Optionally, it also negotiates a protection mechanism for subsequent protocol interactions.  If the requested authentication mechanism is not supported, the server should reject the AUTH command by sending a negative response.
544    ///
545    /// The authentication protocol exchange consists of a series of server challenges and client answers that are specific to the authentication mechanism.  A server challenge, otherwise known as a ready response, is a line consisting of a "+" character followed by a single space and a BASE64 encoded string.  The client answer consists of a line containing a BASE64 encoded string.  If the client wishes to cancel an authentication exchange, it should issue a line with a single "*".  If the server receives such an answer, it must reject the AUTH command by sending a negative response.
546    ///
547    /// A protection mechanism provides integrity and privacy protection to the protocol session.  If a protection mechanism is negotiated, it is applied to all subsequent data sent over the connection.  The protection mechanism takes effect immediately following the CRLF that concludes the authentication exchange for the client, and the CRLF of the positive response for the server.  Once the protection mechanism is in effect, the stream of command and response octets is processed into buffers of ciphertext.  Each buffer is transferred over the connection as a stream of octets prepended with a four octet field in network byte order that represents the length of the following data. The maximum ciphertext buffer length is defined by the protection mechanism.
548    ///
549    /// The server is not required to support any particular authentication mechanism, nor are authentication mechanisms required to support any protection mechanisms.  If an AUTH command fails with a negative response, the session remains in the AUTHORIZATION state and client may try another authentication mechanism by issuing another AUTH command, or may attempt to authenticate by using the USER/PASS or APOP commands.  In other words, the client may request authentication types in decreasing order of preference, with the USER/PASS or APOP command as a last resort.
550    #[cfg(feature = "sasl")]
551    pub async fn auth<A: sasl::Authenticator + Sync>(&mut self, authenticator: A) -> Result<Text> {
552        self.check_client_state(ClientState::Authentication)?;
553
554        self.has_read_greeting()?;
555
556        let mut request: Request = Auth.into();
557
558        let mechanism = authenticator.mechanism();
559
560        request.add_arg(mechanism);
561
562        if let Some(arg) = authenticator.auth() {
563            request.add_arg(crate::base64::encode(arg))
564        }
565
566        let stream = self.inner_mut()?;
567
568        stream.encode(&request).await?;
569
570        let communicator = sasl::Communicator::new(stream);
571
572        authenticator.handle(communicator).await?;
573
574        let message = match stream.read_response(request).await? {
575            Response::Message(message) => message,
576            _ => err!(
577                ErrorKind::UnexpectedResponse,
578                "Did not received the expected auith response"
579            ),
580        };
581
582        self.update_capabilities().await;
583
584        self.state = ClientState::Transaction;
585
586        Ok(message)
587    }
588
589    /// ## USER & PASS
590    ///
591    /// To authenticate using the USER and PASS command combination, the client must first issue the USER command. If the POP3 server responds with a positive status indicator ("+OK"), then the client may issue either the PASS command to complete the authentication, or the QUIT command to terminate the POP3 session.  If the POP3 server responds with a negative status indicator ("-ERR") to the USER command, then the client may either issue a new authentication command or may issue the QUIT command.
592    ///
593    /// The server may return a positive response even though no such mailbox exists. The server may return a negative response if mailbox exists, but does not permit plaintext password authentication.
594    ///
595    /// When the client issues the PASS command, the POP3 server uses the argument pair from the USER and PASS commands to determine if the client should be given access to the appropriate maildrop.
596    ///
597    /// Since the PASS command has exactly one argument, a POP3 server may treat spaces in the argument as part of the password, instead of as argument separators.
598    ///
599    /// ### Arguments:
600    /// -  a string identifying a mailbox (required), which is of significance ONLY to the server
601    /// -  a server/mailbox-specific password (required)
602    ///
603    /// ### Restrictions:
604    /// may only be given in the AUTHORIZATION state after the POP3 greeting or after an unsuccessful USER or PASS command
605    ///
606    /// ### Possible responses:
607    /// - +OK maildrop locked and ready
608    /// - -ERR invalid password
609    /// - -ERR unable to lock maildrop
610    /// - -ERR never heard of mailbox name
611    pub async fn login<U: AsRef<str>, P: AsRef<str>>(
612        &mut self,
613        user: U,
614        password: P,
615    ) -> Result<(Text, Text)> {
616        self.check_client_state(ClientState::Authentication)?;
617
618        // self.check_capability(vec![
619        //     Capability::User,
620        //     Capability::Sasl(vec![String::from("PLAIN")]),
621        // ])?;
622
623        self.has_read_greeting()?;
624
625        let mut request: Request = User.into();
626
627        request.add_arg(user.as_ref());
628
629        let user_response = self.send_request(request).await?;
630
631        let mut request: Request = Pass.into();
632
633        request.add_arg(password.as_ref());
634
635        let pass_response = self.send_request(request).await?;
636
637        self.update_capabilities().await;
638
639        self.state = ClientState::Transaction;
640
641        let user_response_str = match user_response {
642            Response::Message(resp) => resp,
643            _ => err!(
644                ErrorKind::UnexpectedResponse,
645                "Did not received the expected user response"
646            ),
647        };
648
649        let pass_response_str = match pass_response {
650            Response::Message(resp) => resp,
651            _ => err!(
652                ErrorKind::UnexpectedResponse,
653                "Did not received the expected pass response"
654            ),
655        };
656
657        Ok((user_response_str, pass_response_str))
658    }
659
660    /// ## QUIT
661    /// Quits the session
662    ///
663    /// ### Arguments: none
664    ///
665    /// ### Restrictions: none
666    ///
667    /// ### Possible Responses:
668    /// - +OK
669    ///
670    /// https://www.rfc-editor.org/rfc/rfc1939#page-5
671    pub async fn quit(&mut self) -> Result<Text> {
672        let response = self.send_request(Quit).await?;
673
674        self.state = ClientState::Update;
675        self.inner = None;
676        self.state = ClientState::None;
677        self.read_greeting = false;
678
679        self.marked_as_del.clear();
680        self.capabilities.clear();
681
682        match response {
683            Response::Message(resp) => Ok(resp),
684            _ => err!(
685                ErrorKind::UnexpectedResponse,
686                "Did not received the expected quit response"
687            ),
688        }
689    }
690
691    /// Check whether the server supports one of the given capabilities.
692    pub fn has_capability<C: AsRef<[Capability]>>(&mut self, capabilities: C) -> bool {
693        let to_find: HashSet<_> = capabilities.as_ref().iter().collect();
694        let server_has: HashSet<_> = self.capabilities.iter().collect();
695
696        let intersect: Vec<_> = server_has.intersection(&to_find).collect();
697
698        intersect.len() == capabilities.as_ref().len()
699    }
700
701    /// Make sure the given capabilities are present
702    fn check_capability<C: AsRef<[Capability]>>(&mut self, capability: C) -> Result<()> {
703        if !self.has_capability(capability) {
704            err!(
705                ErrorKind::FeatureUnsupported,
706                "The remote pop server does not support this command/function",
707            )
708        } else {
709            Ok(())
710        }
711    }
712
713    /// Returns the current list of capabilities given by the server.
714    pub fn capabilities(&self) -> &Capabilities {
715        &self.capabilities
716    }
717
718    /// Fetches a list of capabilities for the currently connected server and returns it.
719    pub async fn capa(&mut self) -> Result<Capabilities> {
720        let response = self.send_request(Capa).await?;
721
722        match response.into() {
723            Response::Capability(resp) => Ok(resp),
724            _ => err!(
725                ErrorKind::UnexpectedResponse,
726                "Did not received the expected capa response"
727            ),
728        }
729    }
730
731    async fn update_capabilities(&mut self) {
732        if let Ok(capabilities) = self.capa().await {
733            self.capabilities = capabilities
734        }
735    }
736
737    /// Sends a valid Pop3 command and returns the response sent by the server.
738    pub async fn send_request<R: Into<Request>>(&mut self, request: R) -> Result<Response> {
739        let request = request.into();
740
741        let stream = self.inner_mut()?;
742
743        stream.encode(&request).await?;
744
745        let response = stream.read_response(request).await?;
746
747        Ok(response)
748    }
749
750    fn has_read_greeting(&self) -> Result<()> {
751        if !self.read_greeting {
752            err!(
753                ErrorKind::ServerFailedToGreet,
754                "Did not connect to the server correctly, as we did not get a greeting yet",
755            )
756        } else {
757            Ok(())
758        }
759    }
760
761    async fn read_greeting(&mut self) -> Result<Text> {
762        assert!(!self.read_greeting, "Cannot read greeting twice");
763
764        let socket = self.inner_mut()?;
765
766        let response = socket.read_response(Greet).await?;
767
768        match response {
769            Response::Message(resp) => {
770                self.greeting = Some(resp.clone());
771                self.read_greeting = true;
772
773                Ok(resp)
774            }
775            _ => err!(
776                ErrorKind::UnexpectedResponse,
777                "Did not received the expected greeting"
778            ),
779        }
780    }
781
782    /// The greeting that the POP server sent when the connection opened.
783    pub fn greeting(&self) -> Option<&Text> {
784        self.greeting.as_ref()
785    }
786}
787
788#[cfg(test)]
789mod test;