some more commands

This commit is contained in:
Michael Zhang 2021-02-24 04:46:21 -06:00
parent 508429ff5d
commit 5438bb170d
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
8 changed files with 994 additions and 27 deletions

742
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@ inotify = { version = "0.9.2", features = ["stream"] }
log = "0.4.14" log = "0.4.14"
panorama-imap = { path = "imap", version = "0" } panorama-imap = { path = "imap", version = "0" }
parking_lot = "0.11.1" parking_lot = "0.11.1"
pgp = "0.7.1"
pin-project = "1.0.5" pin-project = "1.0.5"
rustls-connector = "0.13.1" rustls-connector = "0.13.1"
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }

View file

@ -146,6 +146,7 @@ impl ClientAuthenticated {
} }
} }
/// Runs the LIST command
pub async fn list(&mut self) -> Result<()> { pub async fn list(&mut self) -> Result<()> {
let cmd = Command::List { let cmd = Command::List {
reference: "".to_owned(), reference: "".to_owned(),
@ -155,4 +156,14 @@ impl ClientAuthenticated {
debug!("list response: {:?}", resp); debug!("list response: {:?}", resp);
Ok(()) Ok(())
} }
/// Runs the SELECT command
pub async fn select(&mut self, mailbox: impl AsRef<str>) -> Result<()> {
let cmd = Command::Select {
mailbox: mailbox.as_ref().to_owned(),
};
let resp = self.execute(cmd).await?;
debug!("select response: {:?}", resp);
Ok(())
}
} }

View file

@ -171,7 +171,22 @@ fn build_resp_code(pair: Pair<Rule>) -> Option<ResponseCode> {
Rule::capability_data => ResponseCode::Capabilities(build_capabilities(pair)), Rule::capability_data => ResponseCode::Capabilities(build_capabilities(pair)),
Rule::resp_text_code_readwrite => ResponseCode::ReadWrite, Rule::resp_text_code_readwrite => ResponseCode::ReadWrite,
Rule::resp_text_code_uidvalidity => ResponseCode::UidValidity(build_number(unwrap1(pair))), Rule::resp_text_code_uidvalidity => ResponseCode::UidValidity(build_number(unwrap1(pair))),
Rule::resp_text_code_uidnext => ResponseCode::UidNext(build_number(unwrap1(pair))),
Rule::resp_text_code_unseen => ResponseCode::Unseen(build_number(unwrap1(pair))), Rule::resp_text_code_unseen => ResponseCode::Unseen(build_number(unwrap1(pair))),
// TODO: maybe have an actual type for these flags instead of just string
Rule::resp_text_code_permanentflags => {
ResponseCode::PermanentFlags(pair.into_inner().map(|p| p.as_str().to_owned()).collect())
}
Rule::resp_text_code_other => {
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
let a = pair.as_str().to_owned();
let mut b = None;
if let Some(pair) = pairs.next() {
b = Some(pair.as_str().to_owned());
}
ResponseCode::Other(a, b)
}
_ => unreachable!("{:#?}", pair), _ => unreachable!("{:#?}", pair),
}) })
} }
@ -241,8 +256,9 @@ fn build_flag(mut pair: Pair<Rule>) -> MailboxFlag {
"\\Deleted" => MailboxFlag::Deleted, "\\Deleted" => MailboxFlag::Deleted,
"\\Seen" => MailboxFlag::Seen, "\\Seen" => MailboxFlag::Seen,
"\\Draft" => MailboxFlag::Draft, "\\Draft" => MailboxFlag::Draft,
s if s.starts_with("\\") => MailboxFlag::Ext(s.to_owned()), // s if s.starts_with("\\") => MailboxFlag::Ext(s.to_owned()),
_ => unreachable!("{:#?}", pair.as_str()), // TODO: what??
s => MailboxFlag::Ext(s.to_owned()),
} }
} }
@ -309,7 +325,9 @@ fn build_mailbox_list(pair: Pair<Rule>) -> (Vec<String>, Option<String>, String)
fn build_mbx_list_flags(pair: Pair<Rule>) -> Vec<String> { fn build_mbx_list_flags(pair: Pair<Rule>) -> Vec<String> {
assert!(matches!(pair.as_rule(), Rule::mbx_list_flags)); assert!(matches!(pair.as_rule(), Rule::mbx_list_flags));
pair.into_inner().map(|pair| pair.as_str().to_owned()).collect() pair.into_inner()
.map(|pair| pair.as_str().to_owned())
.collect()
} }
/// Unwraps a singleton pair (a pair that only has one element in its `inner` list) /// Unwraps a singleton pair (a pair that only has one element in its `inner` list)
@ -332,7 +350,7 @@ where
} }
/// Wrapper around [build_string][1], except return None for the `nil` case /// Wrapper around [build_string][1], except return None for the `nil` case
/// ///
/// [1]: self::build_string /// [1]: self::build_string
fn build_nstring(pair: Pair<Rule>) -> Option<String> { fn build_nstring(pair: Pair<Rule>) -> Option<String> {
if matches!(pair.as_rule(), Rule::nil) { if matches!(pair.as_rule(), Rule::nil) {

View file

@ -68,8 +68,8 @@ mailbox = { ^"INBOX" | astring }
mailbox_data = { mailbox_data_flags | mailbox_data_list | (^"LSUB" ~ sp ~ mailbox_list) | (^"SEARCH" ~ (sp ~ nz_number)*) | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | mailbox_data_exists | mailbox_data_recent } mailbox_data = { mailbox_data_flags | mailbox_data_list | (^"LSUB" ~ sp ~ mailbox_list) | (^"SEARCH" ~ (sp ~ nz_number)*) | (^"STATUS" ~ sp ~ mailbox ~ sp ~ ^"(" ~ status_att_list? ~ ^")") | mailbox_data_exists | mailbox_data_recent }
mailbox_data_exists = { number ~ sp ~ ^"EXISTS" } mailbox_data_exists = { number ~ sp ~ ^"EXISTS" }
mailbox_data_flags = { ^"FLAGS" ~ sp ~ flag_list } mailbox_data_flags = { ^"FLAGS" ~ sp ~ flag_list }
mailbox_data_recent = { number ~ sp ~ ^"RECENT" }
mailbox_data_list = { ^"LIST" ~ sp ~ mailbox_list } mailbox_data_list = { ^"LIST" ~ sp ~ mailbox_list }
mailbox_data_recent = { number ~ sp ~ ^"RECENT" }
mailbox_list = { mailbox_list_flags ~ sp ~ mailbox_list_string ~ sp ~ mailbox } mailbox_list = { mailbox_list_flags ~ sp ~ mailbox_list_string ~ sp ~ mailbox }
mailbox_list_flags = { "(" ~ mbx_list_flags* ~ ")" } mailbox_list_flags = { "(" ~ mbx_list_flags* ~ ")" }
mailbox_list_string = { dquote ~ quoted_char ~ dquote | nil } mailbox_list_string = { dquote ~ quoted_char ~ dquote | nil }
@ -87,10 +87,10 @@ msg_att = { "(" ~ msg_att_dyn_or_stat ~ (sp ~ msg_att_dyn_or_stat)* ~ ")" }
msg_att_dyn_or_stat = { msg_att_dynamic | msg_att_static } msg_att_dyn_or_stat = { msg_att_dynamic | msg_att_static }
msg_att_dynamic = { ^"FLAGS" ~ sp ~ "(" ~ (flag_fetch ~ (sp ~ flag_fetch)*)? ~ ")" } msg_att_dynamic = { ^"FLAGS" ~ sp ~ "(" ~ (flag_fetch ~ (sp ~ flag_fetch)*)? ~ ")" }
msg_att_static = { msg_att_static_envelope | msg_att_static_internaldate | (^"RFC822" ~ (^".HEADER" | ^".TEXT") ~ sp ~ nstring) | msg_att_static_rfc822_size | msg_att_static_body | (^"BODY" ~ section ~ ("<" ~ number ~ ">")? ~ sp ~ nstring) | (^"UID" ~ sp ~ uniqueid) } msg_att_static = { msg_att_static_envelope | msg_att_static_internaldate | (^"RFC822" ~ (^".HEADER" | ^".TEXT") ~ sp ~ nstring) | msg_att_static_rfc822_size | msg_att_static_body | (^"BODY" ~ section ~ ("<" ~ number ~ ">")? ~ sp ~ nstring) | (^"UID" ~ sp ~ uniqueid) }
msg_att_static_body = { ^"BODY" ~ ^"STRUCTURE"? ~ sp ~ body }
msg_att_static_envelope = { ^"ENVELOPE" ~ sp ~ envelope } msg_att_static_envelope = { ^"ENVELOPE" ~ sp ~ envelope }
msg_att_static_internaldate = { ^"INTERNALDATE" ~ sp ~ date_time } msg_att_static_internaldate = { ^"INTERNALDATE" ~ sp ~ date_time }
msg_att_static_rfc822_size = { ^"RFC822.SIZE" ~ sp ~ number } msg_att_static_rfc822_size = { ^"RFC822.SIZE" ~ sp ~ number }
msg_att_static_body = { ^"BODY" ~ ^"STRUCTURE"? ~ sp ~ body }
nil = { ^"NIL" } nil = { ^"NIL" }
nstring = { string | nil } nstring = { string | nil }
number = @{ digit{1,} } number = @{ digit{1,} }
@ -103,11 +103,14 @@ resp_cond_state = { resp_status ~ sp ~ resp_text }
resp_specials = @{ "]" } resp_specials = @{ "]" }
resp_status = { (^"OK" | ^"NO" | ^"BAD") } resp_status = { (^"OK" | ^"NO" | ^"BAD") }
resp_text = { ("[" ~ resp_text_code ~ "]" ~ sp)? ~ text } resp_text = { ("[" ~ resp_text_code ~ "]" ~ sp)? ~ text }
resp_text_code = { ^"ALERT" | (^"BADCHARSET" ~ (sp ~ "(" ~ astring ~ (sp ~ astring)* ~ ")")?) | capability_data | ^"PARSE" | (^"PERMANENTFLAGS" ~ sp ~ "(" ~ (flag_perm ~ (sp ~ flag_perm)*)? ~ ")") | ^"READ-ONLY" | resp_text_code_readwrite | ^"TRYCREATE" | (^"UIDNEXT" ~ sp ~ nz_number) | resp_text_code_uidvalidity | resp_text_code_unseen | (atom ~ (sp ~ resp_text_code_atom)?) } resp_text_code = { ^"ALERT" | (^"BADCHARSET" ~ (sp ~ "(" ~ astring ~ (sp ~ astring)* ~ ")")?) | capability_data | ^"PARSE" | resp_text_code_permanentflags | ^"READ-ONLY" | resp_text_code_readwrite | ^"TRYCREATE" | resp_text_code_uidnext | resp_text_code_uidvalidity | resp_text_code_unseen | resp_text_code_other }
resp_text_code_atom = @{ (!"]" ~ text_char){1,} } resp_text_code_atom = @{ (!"]" ~ text_char){1,} }
resp_text_code_permanentflags = { ^"PERMANENTFLAGS" ~ sp ~ "(" ~ (flag_perm ~ (sp ~ flag_perm)*)? ~ ")" }
resp_text_code_readwrite = { ^"READ-WRITE" } resp_text_code_readwrite = { ^"READ-WRITE" }
resp_text_code_uidvalidity = { ^"UIDVALIDITY" ~ sp ~ nz_number } resp_text_code_uidvalidity = { ^"UIDVALIDITY" ~ sp ~ nz_number }
resp_text_code_uidnext = { ^"UIDNEXT" ~ sp ~ nz_number }
resp_text_code_unseen = { ^"UNSEEN" ~ sp ~ nz_number } resp_text_code_unseen = { ^"UNSEEN" ~ sp ~ nz_number }
resp_text_code_other = { (atom ~ (sp ~ resp_text_code_atom)?) }
response = { continue_req | response_data | response_done } response = { continue_req | response_data | response_done }
response_data = { "*" ~ sp ~ (resp_cond_state | resp_cond_bye | mailbox_data | message_data | capability_data) ~ crlf } response_data = { "*" ~ sp ~ (resp_cond_state | resp_cond_bye | mailbox_data | message_data | capability_data) ~ crlf }
response_done = { response_tagged | response_fatal } response_done = { response_tagged | response_fatal }

View file

@ -51,6 +51,7 @@ pub enum ResponseCode {
AppendUid(u32, Vec<UidSetMember>), AppendUid(u32, Vec<UidSetMember>),
CopyUid(u32, Vec<UidSetMember>, Vec<UidSetMember>), CopyUid(u32, Vec<UidSetMember>, Vec<UidSetMember>),
UidNotSticky, UidNotSticky,
Other(String, Option<String>),
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]

227
rfc/rfc2177.txt Normal file
View file

@ -0,0 +1,227 @@
Network Working Group B. Leiba
Request for Comments: 2177 IBM T.J. Watson Research Center
Category: Standards Track June 1997
IMAP4 IDLE command
Status of this Memo
This document specifies an Internet standards track protocol for the
Internet community, and requests discussion and suggestions for
improvements. Please refer to the current edition of the "Internet
Official Protocol Standards" (STD 1) for the standardization state
and status of this protocol. Distribution of this memo is unlimited.
1. Abstract
The Internet Message Access Protocol [IMAP4] requires a client to
poll the server for changes to the selected mailbox (new mail,
deletions). It's often more desirable to have the server transmit
updates to the client in real time. This allows a user to see new
mail immediately. It also helps some real-time applications based on
IMAP, which might otherwise need to poll extremely often (such as
every few seconds). (While the spec actually does allow a server to
push EXISTS responses aysynchronously, a client can't expect this
behaviour and must poll.)
This document specifies the syntax of an IDLE command, which will
allow a client to tell the server that it's ready to accept such
real-time updates.
2. Conventions Used in this Document
In examples, "C:" and "S:" indicate lines sent by the client and
server respectively.
The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY"
in this document are to be interpreted as described in RFC 2060
[IMAP4].
3. Specification
IDLE Command
Arguments: none
Responses: continuation data will be requested; the client sends
the continuation data "DONE" to end the command
Leiba Standards Track [Page 1]
RFC 2177 IMAP4 IDLE command June 1997
Result: OK - IDLE completed after client sent "DONE"
NO - failure: the server will not allow the IDLE
command at this time
BAD - command unknown or arguments invalid
The IDLE command may be used with any IMAP4 server implementation
that returns "IDLE" as one of the supported capabilities to the
CAPABILITY command. If the server does not advertise the IDLE
capability, the client MUST NOT use the IDLE command and must poll
for mailbox updates. In particular, the client MUST continue to be
able to accept unsolicited untagged responses to ANY command, as
specified in the base IMAP specification.
The IDLE command is sent from the client to the server when the
client is ready to accept unsolicited mailbox update messages. The
server requests a response to the IDLE command using the continuation
("+") response. The IDLE command remains active until the client
responds to the continuation, and as long as an IDLE command is
active, the server is now free to send untagged EXISTS, EXPUNGE, and
other messages at any time.
The IDLE command is terminated by the receipt of a "DONE"
continuation from the client; such response satisfies the server's
continuation request. At that point, the server MAY send any
remaining queued untagged responses and then MUST immediately send
the tagged response to the IDLE command and prepare to process other
commands. As in the base specification, the processing of any new
command may cause the sending of unsolicited untagged responses,
subject to the ambiguity limitations. The client MUST NOT send a
command while the server is waiting for the DONE, since the server
will not be able to distinguish a command from a continuation.
The server MAY consider a client inactive if it has an IDLE command
running, and if such a server has an inactivity timeout it MAY log
the client off implicitly at the end of its timeout period. Because
of that, clients using IDLE are advised to terminate the IDLE and
re-issue it at least every 29 minutes to avoid being logged off.
This still allows a client to receive immediate mailbox updates even
though it need only "poll" at half hour intervals.
Leiba Standards Track [Page 2]
RFC 2177 IMAP4 IDLE command June 1997
Example: C: A001 SELECT INBOX
S: * FLAGS (Deleted Seen)
S: * 3 EXISTS
S: * 0 RECENT
S: * OK [UIDVALIDITY 1]
S: A001 OK SELECT completed
C: A002 IDLE
S: + idling
...time passes; new mail arrives...
S: * 4 EXISTS
C: DONE
S: A002 OK IDLE terminated
...another client expunges message 2 now...
C: A003 FETCH 4 ALL
S: * 4 FETCH (...)
S: A003 OK FETCH completed
C: A004 IDLE
S: * 2 EXPUNGE
S: * 3 EXISTS
S: + idling
...time passes; another client expunges message 3...
S: * 3 EXPUNGE
S: * 2 EXISTS
...time passes; new mail arrives...
S: * 3 EXISTS
C: DONE
S: A004 OK IDLE terminated
C: A005 FETCH 3 ALL
S: * 3 FETCH (...)
S: A005 OK FETCH completed
C: A006 IDLE
4. Formal Syntax
The following syntax specification uses the augmented Backus-Naur
Form (BNF) notation as specified in [RFC-822] as modified by [IMAP4].
Non-terminals referenced but not defined below are as defined by
[IMAP4].
command_auth ::= append / create / delete / examine / list / lsub /
rename / select / status / subscribe / unsubscribe
/ idle
;; Valid only in Authenticated or Selected state
idle ::= "IDLE" CRLF "DONE"
Leiba Standards Track [Page 3]
RFC 2177 IMAP4 IDLE command June 1997
5. References
[IMAP4] Crispin, M., "Internet Message Access Protocol - Version
4rev1", RFC 2060, December 1996.
6. Security Considerations
There are no known security issues with this extension.
7. Author's Address
Barry Leiba
IBM T.J. Watson Research Center
30 Saw Mill River Road
Hawthorne, NY 10532
Email: leiba@watson.ibm.com
Leiba Standards Track [Page 4]

View file

@ -102,6 +102,10 @@ async fn imap_main(acct: MailAccountConfig) -> Result<()> {
debug!("authentication successful!"); debug!("authentication successful!");
// let's just select INBOX for now, maybe have a config for default mailbox later?
debug!("selecting the INBOX mailbox");
authed.select("INBOX").await?;
loop { loop {
debug!("listing all emails..."); debug!("listing all emails...");
let folder_tree = authed.list().await?; let folder_tree = authed.list().await?;