Skip to main content

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 = "tls")]
53mod tls;
54
55#[cfg(feature = "sasl")]
56mod base64;
57#[cfg(feature = "sasl")]
58pub mod sasl;
59
60use std::collections::HashSet;
61
62use bytes::Bytes;
63use command::Command::*;
64use error::{ErrorKind, Result};
65use request::Request;
66use response::{
67    capability::{Capabilities, Capability},
68    list::ListResponse,
69    stat::Stat,
70    types::message::Text,
71    uidl::UidlResponse,
72    Response,
73};
74#[cfg(feature = "sasl")]
75use sasl::PlainAuthenticator;
76use stream::PopStream;
77
78use crate::{
79    error::err,
80    runtime::{
81        io::{Read, Write},
82        net::{TcpStream, ToSocketAddrs},
83        Instant,
84    },
85};
86
87#[derive(Eq, PartialEq, Debug)]
88pub enum ClientState {
89    Authentication,
90    Transaction,
91    Update,
92    None,
93}
94
95pub struct Client<S: Write + Read + Unpin + Send> {
96    inner: Option<PopStream<S>>,
97    capabilities: Capabilities,
98    marked_as_del: Vec<usize>,
99    greeting: Option<Text>,
100    read_greeting: bool,
101    state: ClientState,
102}
103
104/// Creates a client from a given socket connection.
105async fn create_client_from_socket<S: Read + Write + Unpin + Send>(
106    socket: PopStream<S>,
107) -> Result<Client<S>> {
108    let mut client = Client {
109        marked_as_del: Vec::new(),
110        capabilities: Vec::new(),
111        greeting: None,
112        read_greeting: false,
113        inner: Some(socket),
114        state: ClientState::Authentication,
115    };
116
117    client.greeting = Some(client.read_greeting().await?);
118
119    client.update_capabilities().await;
120
121    Ok(client)
122}
123
124/// Creates a new pop3 client from an existing stream.
125/// # Examples
126/// ```rust,ignore
127/// extern crate pop3;
128/// use std::net::TcpStream;
129///
130/// fn main() {
131///     // Not recommended to use plaintext, just an example.
132///     let stream = TcpStream::connect(("outlook.office365.com", 110)).unwrap();
133///
134///     let mut client = pop3::new(stream).unwrap();
135///
136///     client.quit().unwrap();
137/// }
138/// ```
139pub async fn new<S: Read + Write + Unpin + Send>(stream: S) -> Result<Client<S>> {
140    let socket = PopStream::new(stream);
141
142    create_client_from_socket(socket).await
143}
144
145/// Create a new pop3 client with a tls connection.
146#[cfg(feature = "tls")]
147pub async fn connect<'a, A: ToSocketAddrs, D: AsRef<str>, C: Into<tls::TlsConnector<'a>>>(
148    addr: A,
149    domain: D,
150    tls: C,
151) -> Result<Client<impl tls::TlsStream<TcpStream>>> {
152    let tcp_stream = TcpStream::connect(addr).await?;
153
154    let tls_connector: tls::TlsConnector<'a> = tls.into();
155
156    let tls_stream = tls_connector.connect(domain, tcp_stream).await?;
157
158    let socket = PopStream::new(tls_stream);
159
160    create_client_from_socket(socket).await
161}
162
163/// Creates a new pop3 client using a plain connection.
164///
165/// DO NOT USE in a production environment. Your password will be sent over a plain tcp stream which hackers could intercept.
166pub async fn connect_plain<A: ToSocketAddrs>(addr: A) -> Result<Client<TcpStream>> {
167    let tcp_stream = TcpStream::connect(addr).await?;
168
169    let socket = PopStream::new(tcp_stream);
170
171    create_client_from_socket(socket).await
172}
173
174impl<S: Read + Write + Unpin + Send> Client<S> {
175    /// Check if the client is in the correct state and return a mutable reference to the tcp connection.
176    fn inner_mut(&mut self) -> Result<&mut PopStream<S>> {
177        match self.inner.as_mut() {
178            Some(socket) => {
179                if self.state == ClientState::Transaction
180                    || self.state == ClientState::Authentication
181                {
182                    Ok(socket)
183                } else {
184                    err!(
185                        ErrorKind::ShouldNotBeConnected,
186                        "There is a connection, but our state indicates that we should not be connected",
187                    )
188                }
189            }
190            None => err!(ErrorKind::NotConnected, "Not connected to any server",),
191        }
192    }
193
194    pub fn inner(&self) -> &Option<PopStream<S>> {
195        &self.inner
196    }
197
198    pub fn into_inner(self) -> Option<PopStream<S>> {
199        self.inner
200    }
201
202    /// Check if the client is in the correct state.
203    fn check_client_state(&self, state: ClientState) -> Result<()> {
204        if self.state != state {
205            err!(
206                ErrorKind::IncorrectStateForCommand,
207                "The connection is not the right state to use this command",
208            )
209        } else {
210            Ok(())
211        }
212    }
213
214    /// ## Current client state
215    ///
216    /// Indicates what state the client is currently in, can be either
217    /// Authentication, Transaction, Update or None.
218    ///
219    /// Some methods are only available in some specified states and will error if run in an incorrect state.
220    ///
221    /// https://www.rfc-editor.org/rfc/rfc1939#section-3
222    pub fn get_state(&self) -> &ClientState {
223        &self.state
224    }
225
226    /// ## NOOP
227    /// The POP3 server does nothing, it merely replies with a positive response.
228    /// ### Arguments: none
229    /// ### Restrictions:
230    /// - May only be given in the TRANSACTION state
231    /// ### Possible Responses:
232    /// - OK
233    /// # Examples:
234    /// ```rust,ignore
235    /// client.noop()?;
236    /// ```
237    /// https://www.rfc-editor.org/rfc/rfc1939#page-9
238    pub async fn noop(&mut self) -> Result<()> {
239        self.send_request(Noop).await?;
240
241        Ok(())
242    }
243
244    /// ## UIDL
245    /// If an argument was given and the POP3 server issues a positive response with a line containing information for that message.
246    /// This line is called a "unique-id listing" for that message.
247    ///
248    /// If no argument was given and the POP3 server issues a positive response, then the response given is multi-line.
249    /// 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.
250    ///
251    /// ### Arguments:
252    /// - a message-number (optional), which, if present, may NOT refer to a message marked as deleted.
253    ///
254    /// ### Restrictions:
255    /// - May only be given in the TRANSACTION state.
256    ///
257    /// ### Possible responses:
258    /// - +OK unique-id listing follows
259    /// - -ERR no such message
260    ///
261    /// https://www.rfc-editor.org/rfc/rfc1939#page-12
262    pub async fn uidl(&mut self, msg_number: Option<usize>) -> Result<UidlResponse> {
263        self.check_capability(vec![Capability::Uidl])?;
264
265        if let Some(msg_number) = msg_number.as_ref() {
266            self.check_deleted(msg_number)?
267        };
268
269        let mut request: Request = Uidl.into();
270
271        if let Some(number) = msg_number {
272            request.add_arg(number)
273        }
274
275        let response = self.send_request(request).await?;
276
277        match response {
278            Response::Uidl(resp) => Ok(resp),
279            _ => {
280                err!(
281                    ErrorKind::UnexpectedResponse,
282                    "Did not received the expected uidl response"
283                )
284            }
285        }
286    }
287
288    /// When the last communication with the server happened.
289    ///
290    /// Returns [None] if there is no connection or the connection is not in the right state.
291    pub fn last_activity(&self) -> Option<Instant> {
292        Some(self.inner.as_ref()?.last_activity())
293    }
294
295    pub async fn top(&mut self, msg_number: usize, lines: usize) -> Result<Bytes> {
296        self.check_deleted(&msg_number)?;
297
298        self.check_capability(vec![Capability::Top])?;
299
300        let mut request: Request = Top.into();
301
302        request.add_arg(msg_number);
303        request.add_arg(lines);
304
305        let response = self.send_request(request).await?;
306
307        match response {
308            Response::Bytes(resp) => Ok(resp),
309            _ => err!(
310                ErrorKind::UnexpectedResponse,
311                "Did not received the expected top response"
312            ),
313        }
314    }
315
316    /// Check whether a given message is marked as deleted by the server.
317    ///
318    /// If this function returns true then the message may still not exist.
319    /// # Examples:
320    /// ```rust,ignore
321    /// let msg_number: u32 = 8;
322    /// let is_deleted = client.is_deleted(msg_number);
323    /// assert_eq!(is_deleted, false);
324    /// ```
325    pub fn is_deleted(&mut self, msg_number: &usize) -> bool {
326        self.marked_as_del.sort();
327
328        self.marked_as_del.binary_search(msg_number).is_ok()
329    }
330
331    fn check_deleted(&mut self, msg_number: &usize) -> Result<()> {
332        if self.is_deleted(msg_number) {
333            err!(
334                ErrorKind::MessageIsDeleted,
335                "This message has been marked as deleted and cannot be refenced anymore",
336            )
337        } else {
338            Ok(())
339        }
340    }
341
342    /// ## DELE
343    /// 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.
344    /// ### Arguments:
345    /// - a message-number (required) which may NOT refer to a message marked as deleted.
346    /// ### Restrictions:
347    /// - may only be given in the TRANSACTION state
348    /// ### Possible Responses:
349    /// - OK: message deleted
350    /// - ERR: no such message
351    /// # Examples
352    /// ```rust,ignore
353    /// let msg_number: u32 = 8;
354    /// let is_deleted = client.is_deleted(msg_number);
355    ///
356    /// println!("{}", is_deleted);
357    /// ```
358    pub async fn dele(&mut self, msg_number: usize) -> Result<Text> {
359        self.check_deleted(&msg_number)?;
360
361        let mut request: Request = Dele.into();
362
363        request.add_arg(msg_number);
364
365        let response = self.send_request(request).await?;
366
367        match response {
368            Response::Message(resp) => Ok(resp),
369            _ => err!(
370                ErrorKind::UnexpectedResponse,
371                "Did not received the expected dele response"
372            ),
373        }
374    }
375
376    /// ## RSET
377    /// If any messages have been marked as deleted by the POP3
378    /// server, they are unmarked.
379    /// ### Arguments: none
380    /// ### Restrictions:
381    /// - May only be given in the TRANSACTION state
382    /// ### Possible Responses:
383    /// - +OK
384    ///
385    /// https://www.rfc-editor.org/rfc/rfc1939#page-9
386    pub async fn rset(&mut self) -> Result<Text> {
387        let response = self.send_request(Rset).await?;
388
389        self.marked_as_del = Vec::new();
390
391        match response {
392            Response::Message(resp) => Ok(resp),
393            _ => err!(
394                ErrorKind::UnexpectedResponse,
395                "Did not received the expected rset response"
396            ),
397        }
398    }
399
400    /// ## RETR
401    /// Retrieves the full RFC822 compliant message from the server and returns it as a byte vector
402    /// ### Arguments:
403    /// - A message-number (required) which may NOT refer to a message marked as deleted
404    /// ### Restrictions:
405    /// - May only be given in the TRANSACTION state
406    /// ### Possible Responses:
407    /// - OK: message follows
408    /// - ERR: no such message
409    /// # Examples
410    /// ```rust,ignore
411    /// extern crate mailparse;
412    /// use mailparse::parse_mail;
413    ///
414    /// let response = client.retr(1).unwrap();
415    ///
416    /// let parsed = parse_mail(&response);
417    ///
418    /// let subject = parsed.headers.get_first_value("Subject").unwrap();
419    ///
420    /// println!("{}", subject);
421    /// ```
422    /// https://www.rfc-editor.org/rfc/rfc1939#page-8
423    pub async fn retr(&mut self, msg_number: usize) -> Result<Bytes> {
424        self.check_deleted(&msg_number)?;
425
426        let mut request: Request = Retr.into();
427
428        request.add_arg(msg_number);
429
430        let response = self.send_request(request).await?;
431
432        match response {
433            Response::Bytes(resp) => Ok(resp),
434            _ => err!(
435                ErrorKind::UnexpectedResponse,
436                "Did not received the expected retr response"
437            ),
438        }
439    }
440
441    /// ## LIST
442    ///
443    /// 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.
444    ///
445    /// 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.
446    ///
447    /// ### Arguments:
448    /// - a message-number (optional), which, if present, may NOT refer to a message marked as deleted
449    /// ### Restrictions:
450    /// - may only be given in the TRANSACTION state
451    /// ### Possible responses:
452    /// - +OK scan listing follows
453    /// - -ERR no such message
454    pub async fn list(&mut self, msg_number: Option<usize>) -> Result<ListResponse> {
455        let mut request: Request = List.into();
456
457        if let Some(msg_number) = msg_number {
458            self.check_deleted(&msg_number)?;
459            request.add_arg(msg_number)
460        }
461
462        let response = self.send_request(request).await?;
463
464        match response {
465            Response::List(list) => Ok(list.into()),
466            Response::Stat(stat) => Ok(stat.into()),
467            _ => err!(
468                ErrorKind::UnexpectedResponse,
469                "Did not received the expected list response"
470            ),
471        }
472    }
473
474    /// ## STAT
475    /// 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.
476    /// ### Arguments: none
477    /// ### Restrictions:
478    /// - may only be given in the TRANSACTION state
479    /// ### Possible responses:
480    /// - +OK nn mm
481    pub async fn stat(&mut self) -> Result<Stat> {
482        let response = self.send_request(Stat).await?;
483
484        match response {
485            Response::Stat(resp) => Ok(resp),
486            _ => err!(
487                ErrorKind::UnexpectedResponse,
488                "Did not received the expected stat response"
489            ),
490        }
491    }
492
493    /// ## APOP
494    /// 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.
495    ///
496    /// 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.
497    ///
498    /// 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:
499    ///
500    /// `<process-ID.clock@hostname>`
501    ///
502    /// 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.
503    ///
504    /// 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
505    ///
506    /// ### Arguments:
507    /// a string identifying a mailbox and a MD5 digest string (both required)
508    ///
509    /// ### Restrictions:
510    /// may only be given in the AUTHORIZATION state after the POP3 greeting or after an unsuccessful USER or PASS command
511    ///
512    /// ### Possible responses:
513    /// - +OK maildrop locked and ready
514    /// - -ERR permission denied
515    pub async fn apop<N: AsRef<str>, D: AsRef<str>>(&mut self, name: N, digest: D) -> Result<Text> {
516        self.check_client_state(ClientState::Authentication)?;
517
518        self.has_read_greeting()?;
519
520        let mut request: Request = Apop.into();
521
522        request.add_arg(name.as_ref());
523        request.add_arg(digest.as_ref());
524
525        let response = self.send_request(request).await?;
526
527        self.update_capabilities().await;
528
529        self.state = ClientState::Transaction;
530
531        match response {
532            Response::Message(resp) => Ok(resp),
533            _ => err!(
534                ErrorKind::UnexpectedResponse,
535                "Did not received the expected apop response"
536            ),
537        }
538    }
539
540    pub fn has_auth_mechanism<M: AsRef<[u8]>>(&self, mechanism: M) -> bool {
541        for capa in &self.capabilities {
542            if let Capability::Sasl(supported_mechanisms) = capa {
543                for supported_mechanism in supported_mechanisms {
544                    if supported_mechanism.to_ascii_lowercase()
545                        == mechanism.as_ref().to_ascii_lowercase()
546                    {
547                        return true;
548                    }
549                }
550            }
551        }
552
553        false
554    }
555
556    /// ### AUTH
557    ///
558    /// Requires an [sasl::Authenticator] to work. One could implement this themeselves for any given mechanism, look at the documentation for this trait.
559    ///
560    /// If a common mechanism is needed, it can probably be found in the [sasl] module.
561    ///
562    /// 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.
563    ///
564    /// 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.
565    ///
566    /// 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.
567    ///
568    /// 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.
569    #[cfg(feature = "sasl")]
570    pub async fn auth<A: sasl::Authenticator + Sync>(&mut self, authenticator: A) -> Result<Text> {
571        self.check_client_state(ClientState::Authentication)?;
572
573        self.has_read_greeting()?;
574
575        let mut request: Request = Auth.into();
576
577        let mechanism = authenticator.mechanism();
578
579        request.add_arg(mechanism);
580
581        if let Some(arg) = authenticator.auth() {
582            request.add_arg(crate::base64::encode(arg))
583        }
584
585        let stream = self.inner_mut()?;
586
587        stream.encode(&request).await?;
588
589        let communicator = sasl::Communicator::new(stream);
590
591        authenticator.handle(communicator).await?;
592
593        let message = match stream.read_response(request).await? {
594            Response::Message(message) => message,
595            _ => err!(
596                ErrorKind::UnexpectedResponse,
597                "Did not received the expected auith response"
598            ),
599        };
600
601        self.update_capabilities().await;
602
603        self.state = ClientState::Transaction;
604
605        Ok(message)
606    }
607
608    /// ## USER & PASS
609    ///
610    /// 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.
611    ///
612    /// 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.
613    ///
614    /// 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.
615    ///
616    /// 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.
617    ///
618    /// ### Arguments:
619    /// -  a string identifying a mailbox (required), which is of significance ONLY to the server
620    /// -  a server/mailbox-specific password (required)
621    ///
622    /// ### Restrictions:
623    /// may only be given in the AUTHORIZATION state after the POP3 greeting or after an unsuccessful USER or PASS command
624    ///
625    /// ### Possible responses:
626    /// - +OK maildrop locked and ready
627    /// - -ERR invalid password
628    /// - -ERR unable to lock maildrop
629    /// - -ERR never heard of mailbox name
630    pub async fn login<U: AsRef<str>, P: AsRef<str>>(
631        &mut self,
632        user: U,
633        password: P,
634    ) -> Result<(Text, Text)> {
635        self.check_client_state(ClientState::Authentication)?;
636
637        #[cfg(feature = "sasl")]
638        if self.has_auth_mechanism("PLAIN") {
639            let plain_auth = PlainAuthenticator::new(user.as_ref(), password.as_ref());
640
641            if let Ok(text) = self.auth(plain_auth).await {
642                return Ok((text, Bytes::new().into()));
643            }
644        }
645
646        self.has_read_greeting()?;
647
648        let mut request: Request = User.into();
649
650        request.add_arg(user.as_ref());
651
652        let user_response = self.send_request(request).await?;
653
654        let mut request: Request = Pass.into();
655
656        request.add_arg(password.as_ref());
657
658        let pass_response = self.send_request(request).await?;
659
660        self.update_capabilities().await;
661
662        self.state = ClientState::Transaction;
663
664        let user_response_str = match user_response {
665            Response::Message(resp) => resp,
666            _ => err!(
667                ErrorKind::UnexpectedResponse,
668                "Did not received the expected user response"
669            ),
670        };
671
672        let pass_response_str = match pass_response {
673            Response::Message(resp) => resp,
674            _ => err!(
675                ErrorKind::UnexpectedResponse,
676                "Did not received the expected pass response"
677            ),
678        };
679
680        Ok((user_response_str, pass_response_str))
681    }
682
683    /// ## QUIT
684    /// Quits the session
685    ///
686    /// ### Arguments: none
687    ///
688    /// ### Restrictions: none
689    ///
690    /// ### Possible Responses:
691    /// - +OK
692    ///
693    /// https://www.rfc-editor.org/rfc/rfc1939#page-5
694    pub async fn quit(&mut self) -> Result<Text> {
695        let response = self.send_request(Quit).await?;
696
697        self.state = ClientState::Update;
698        self.inner = None;
699        self.state = ClientState::None;
700        self.read_greeting = false;
701
702        self.marked_as_del.clear();
703        self.capabilities.clear();
704
705        match response {
706            Response::Message(resp) => Ok(resp),
707            _ => err!(
708                ErrorKind::UnexpectedResponse,
709                "Did not received the expected quit response"
710            ),
711        }
712    }
713
714    /// Check whether the server supports one of the given capabilities.
715    pub fn has_capability<C: AsRef<[Capability]>>(&mut self, capabilities: C) -> bool {
716        let to_find: HashSet<_> = capabilities.as_ref().iter().collect();
717        let server_has: HashSet<_> = self.capabilities.iter().collect();
718
719        let intersect: Vec<_> = server_has.intersection(&to_find).collect();
720
721        intersect.len() == capabilities.as_ref().len()
722    }
723
724    /// Make sure the given capabilities are present
725    fn check_capability<C: AsRef<[Capability]>>(&mut self, capability: C) -> Result<()> {
726        if !self.has_capability(capability) {
727            err!(
728                ErrorKind::FeatureUnsupported,
729                "The remote pop server does not support this command/function",
730            )
731        } else {
732            Ok(())
733        }
734    }
735
736    /// Returns the current list of capabilities given by the server.
737    pub fn capabilities(&self) -> &Capabilities {
738        &self.capabilities
739    }
740
741    /// Fetches a list of capabilities for the currently connected server and returns it.
742    pub async fn capa(&mut self) -> Result<Capabilities> {
743        let response = self.send_request(Capa).await?;
744
745        match response {
746            Response::Capability(resp) => Ok(resp),
747            _ => err!(
748                ErrorKind::UnexpectedResponse,
749                "Did not received the expected capa response"
750            ),
751        }
752    }
753
754    async fn update_capabilities(&mut self) {
755        if let Ok(capabilities) = self.capa().await {
756            self.capabilities = capabilities
757        }
758    }
759
760    /// Sends a valid Pop3 command and returns the response sent by the server.
761    pub async fn send_request<R: Into<Request>>(&mut self, request: R) -> Result<Response> {
762        let request = request.into();
763
764        let stream = self.inner_mut()?;
765
766        stream.encode(&request).await?;
767
768        let response = stream.read_response(request).await?;
769
770        Ok(response)
771    }
772
773    fn has_read_greeting(&self) -> Result<()> {
774        if !self.read_greeting {
775            err!(
776                ErrorKind::ServerFailedToGreet,
777                "Did not connect to the server correctly, as we did not get a greeting yet",
778            )
779        } else {
780            Ok(())
781        }
782    }
783
784    async fn read_greeting(&mut self) -> Result<Text> {
785        assert!(!self.read_greeting, "Cannot read greeting twice");
786
787        let socket = self.inner_mut()?;
788
789        let response = socket.read_response(Greet).await?;
790
791        match response {
792            Response::Message(resp) => {
793                self.greeting = Some(resp.clone());
794                self.read_greeting = true;
795
796                Ok(resp)
797            }
798            _ => err!(
799                ErrorKind::UnexpectedResponse,
800                "Did not received the expected greeting"
801            ),
802        }
803    }
804
805    /// The greeting that the POP server sent when the connection opened.
806    pub fn greeting(&self) -> Option<&Text> {
807        self.greeting.as_ref()
808    }
809}
810
811#[cfg(test)]
812mod test;