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