Skip to content

Commit db40b42

Browse files
docs: improve docs + logging
1 parent 8cbeb6e commit db40b42

File tree

4 files changed

+127
-33
lines changed

4 files changed

+127
-33
lines changed

src/command.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,22 @@ pub enum Command {
2222
Quit,
2323
Capa,
2424
Greet,
25+
Other(String),
2526
}
2627

2728
impl Display for Command {
2829
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29-
for (key, value) in Self::definitions().into_iter() {
30-
if &value == self {
31-
write!(f, "{}", key.to_ascii_uppercase())?;
32-
return Ok(());
30+
match self {
31+
Self::Other(other) => {
32+
write!(f, "{}", other)?;
33+
}
34+
_ => {
35+
for (key, value) in Self::definitions().into_iter() {
36+
if &value == self {
37+
write!(f, "{}", key.to_ascii_uppercase())?;
38+
return Ok(());
39+
}
40+
}
3341
}
3442
}
3543

src/lib.rs

Lines changed: 98 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ async fn create_client_from_socket<S: Read + Write + Unpin>(
107107

108108
client.greeting = Some(client.read_greeting().await?);
109109

110-
client.capabilities = client.capa().await?;
110+
client.update_capabilities().await;
111111

112112
Ok(client)
113113
}
@@ -275,6 +275,7 @@ impl<S: Read + Write + Unpin> Client<S> {
275275
}
276276

277277
/// When the last communication with the server happened.
278+
///
278279
/// Returns [None] if there is no connection or the connection is not in the right state.
279280
pub fn last_activity(&self) -> Option<Instant> {
280281
Some(self.inner.as_ref()?.last_activity())
@@ -372,14 +373,13 @@ impl<S: Read + Write + Unpin> Client<S> {
372373
/// - May only be given in the TRANSACTION state
373374
/// ### Possible Responses:
374375
/// - +OK
375-
/// # Examples:
376-
/// ```rust,ignore
377-
/// client.rset().unwrap();
378-
/// ```
376+
///
379377
/// https://www.rfc-editor.org/rfc/rfc1939#page-9
380378
pub async fn rset(&mut self) -> Result<Text> {
381379
let response = self.send_request(Rset).await?;
382380

381+
self.marked_as_del = Vec::new();
382+
383383
match response {
384384
Response::Message(resp) => Ok(resp),
385385
_ => err!(
@@ -430,14 +430,24 @@ impl<S: Read + Write + Unpin> Client<S> {
430430
}
431431
}
432432

433+
/// ## LIST
434+
///
435+
/// 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.
436+
///
437+
/// 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.
438+
///
439+
/// ### Arguments:
440+
/// - a message-number (optional), which, if present, may NOT refer to a message marked as deleted
441+
/// ### Restrictions:
442+
/// - may only be given in the TRANSACTION state
443+
/// ### Possible responses:
444+
/// - +OK scan listing follows
445+
/// - -ERR no such message
433446
pub async fn list(&mut self, msg_number: Option<usize>) -> Result<ListResponse> {
434-
if let Some(msg_number) = msg_number.as_ref() {
435-
self.check_deleted(msg_number)?;
436-
}
437-
438447
let mut request: Request = List.into();
439448

440449
if let Some(msg_number) = msg_number {
450+
self.check_deleted(&msg_number)?;
441451
request.add_arg(msg_number)
442452
}
443453

@@ -453,6 +463,13 @@ impl<S: Read + Write + Unpin> Client<S> {
453463
}
454464
}
455465

466+
/// ## STAT
467+
/// 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.
468+
/// ### Arguments: none
469+
/// ### Restrictions:
470+
/// - may only be given in the TRANSACTION state
471+
/// ### Possible responses:
472+
/// - +OK nn mm
456473
pub async fn stat(&mut self) -> Result<Stat> {
457474
let response = self.send_request(Stat).await?;
458475

@@ -465,6 +482,28 @@ impl<S: Read + Write + Unpin> Client<S> {
465482
}
466483
}
467484

485+
/// ## APOP
486+
/// 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.
487+
///
488+
/// 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.
489+
///
490+
/// 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:
491+
///
492+
/// `<process-ID.clock@hostname>`
493+
///
494+
/// 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.
495+
///
496+
/// 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
497+
///
498+
/// ### Arguments:
499+
/// a string identifying a mailbox and a MD5 digest string (both required)
500+
///
501+
/// ### Restrictions:
502+
/// may only be given in the AUTHORIZATION state after the POP3 greeting or after an unsuccessful USER or PASS command
503+
///
504+
/// ### Possible responses:
505+
/// - +OK maildrop locked and ready
506+
/// - -ERR permission denied
468507
pub async fn apop<N: AsRef<str>, D: AsRef<str>>(&mut self, name: N, digest: D) -> Result<Text> {
469508
self.check_client_state(ClientState::Authentication)?;
470509

@@ -477,6 +516,8 @@ impl<S: Read + Write + Unpin> Client<S> {
477516

478517
let response = self.send_request(request).await?;
479518

519+
self.update_capabilities().await;
520+
480521
self.state = ClientState::Transaction;
481522

482523
match response {
@@ -488,30 +529,54 @@ impl<S: Read + Write + Unpin> Client<S> {
488529
}
489530
}
490531

491-
pub async fn auth<U: AsRef<str>>(&mut self, token: U) -> Result<Text> {
492-
self.check_client_state(ClientState::Authentication)?;
532+
// pub async fn auth<A: AsRef<str>, U: AsRef<str>>(
533+
// &mut self,
534+
// auth_type: A,
535+
// token: U,
536+
// ) -> Result<Text> {
537+
// self.check_client_state(ClientState::Authentication)?;
493538

494-
self.check_capability(vec![Capability::Sasl(vec!["XOAUTH2".into()])])?;
539+
// self.has_read_greeting()?;
495540

496-
self.has_read_greeting()?;
497-
498-
let mut request: Request = Auth.into();
541+
// let mut request: Request = Auth.into();
499542

500-
request.add_arg(token.as_ref());
543+
// request.add_arg(auth_type.as_ref());
501544

502-
let response = self.send_request(request).await?;
545+
// let response = self.send_request(request).await?;
503546

504-
self.state = ClientState::Transaction;
547+
// self.state = ClientState::Transaction;
505548

506-
match response {
507-
Response::Message(resp) => Ok(resp),
508-
_ => err!(
509-
ErrorKind::UnexpectedResponse,
510-
"Did not received the expected auth response"
511-
),
512-
}
513-
}
549+
// match response {
550+
// Response::Message(resp) => Ok(resp),
551+
// _ => err!(
552+
// ErrorKind::UnexpectedResponse,
553+
// "Did not received the expected auth response"
554+
// ),
555+
// }
556+
// }
514557

558+
/// ## USER & PASS
559+
///
560+
/// 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.
561+
///
562+
/// 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.
563+
///
564+
/// 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.
565+
///
566+
/// 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.
567+
///
568+
/// ### Arguments:
569+
/// - a string identifying a mailbox (required), which is of significance ONLY to the server
570+
/// - a server/mailbox-specific password (required)
571+
///
572+
/// ### Restrictions:
573+
/// may only be given in the AUTHORIZATION state after the POP3 greeting or after an unsuccessful USER or PASS command
574+
///
575+
/// ### Possible responses:
576+
/// - +OK maildrop locked and ready
577+
/// - -ERR invalid password
578+
/// - -ERR unable to lock maildrop
579+
/// - -ERR never heard of mailbox name
515580
pub async fn login<U: AsRef<str>, P: AsRef<str>>(
516581
&mut self,
517582
user: U,
@@ -538,7 +603,7 @@ impl<S: Read + Write + Unpin> Client<S> {
538603

539604
let pass_response = self.send_request(request).await?;
540605

541-
self.capabilities = self.capa().await?;
606+
self.update_capabilities().await;
542607

543608
self.state = ClientState::Transaction;
544609

@@ -632,6 +697,12 @@ impl<S: Read + Write + Unpin> Client<S> {
632697
}
633698
}
634699

700+
async fn update_capabilities(&mut self) {
701+
if let Ok(capabilities) = self.capa().await {
702+
self.capabilities = capabilities
703+
}
704+
}
705+
635706
/// Sends a valid Pop3 command and returns the response sent by the server.
636707
pub async fn send_request<R: Into<Request>>(&mut self, request: R) -> Result<Response> {
637708
let request = request.into();

src/macros.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,11 @@ macro_rules! collection {
1010
}
1111

1212
pub(crate) use collection;
13+
14+
macro_rules! escape_newlines {
15+
($input:expr) => {
16+
$input.replace("\n", "\\n").replace("\r", "\\r")
17+
};
18+
}
19+
20+
pub(crate) use escape_newlines;

src/stream.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::{
1111
use crate::{
1212
command::Command,
1313
error::{err, ErrorKind},
14+
macros::escape_newlines,
1415
request::Request,
1516
response::Response,
1617
runtime::{
@@ -39,7 +40,10 @@ impl<S: Read + Write + Unpin> PopStream<S> {
3940

4041
/// Send some bytes to the server
4142
async fn send_bytes<B: AsRef<[u8]>>(&mut self, buf: B) -> Result<()> {
42-
trace!("C: {}", str::from_utf8(buf.as_ref()).unwrap());
43+
trace!(
44+
"C: {}",
45+
escape_newlines!(str::from_utf8(buf.as_ref()).unwrap())
46+
);
4347

4448
self.last_activity = Instant::now();
4549

@@ -67,7 +71,10 @@ impl<S: Read + Write + Unpin> PopStream<S> {
6771
Some(command) => {
6872
match Response::from_bytes(&used[..self.buffer.cursor()], command) {
6973
Ok((remaining, response)) => {
70-
trace!("S: {}", str::from_utf8(used.as_ref()).unwrap());
74+
trace!(
75+
"S: {}",
76+
escape_newlines!(str::from_utf8(used.as_ref()).unwrap())
77+
);
7178

7279
self.queue.mark_current_as_done();
7380

0 commit comments

Comments
 (0)