diff --git a/melib/src/backends/imap/connection.rs b/melib/src/backends/imap/connection.rs
index 45abddffd..5a3cfe617 100644
--- a/melib/src/backends/imap/connection.rs
+++ b/melib/src/backends/imap/connection.rs
@@ -19,6 +19,7 @@
* along with meli. If not, see .
*/
+use super::protocol_parser::ImapLineSplit;
use crate::email::parser::BytesExt;
use crate::error::*;
use std::io::Read;
@@ -231,7 +232,47 @@ impl ImapStream {
}
conn_result?
};
+ let mut res = String::with_capacity(8 * 1024);
let mut ret = ImapStream { cmd_id, stream };
+ ret.send_command(b"CAPABILITY")?;
+ ret.read_response(&mut res)?;
+ let capabilities: std::result::Result, _> = res
+ .split_rn()
+ .find(|l| l.starts_with("* CAPABILITY"))
+ .ok_or_else(|| MeliError::new(""))
+ .and_then(|res| {
+ protocol_parser::capabilities(res.as_bytes())
+ .to_full_result()
+ .map_err(|_| MeliError::new(""))
+ });
+
+ if capabilities.is_err() {
+ return Err(MeliError::new(format!(
+ "Could not connect to {}: expected CAPABILITY response but got:{}",
+ &server_conf.server_hostname, res
+ )));
+ }
+
+ let capabilities = capabilities.unwrap();
+ if !capabilities
+ .iter()
+ .any(|cap| cap.eq_ignore_ascii_case(b"IMAP4rev1"))
+ {
+ return Err(MeliError::new(format!(
+ "Could not connect to {}: server is not IMAP4rev1 compliant",
+ &server_conf.server_hostname
+ )));
+ } else if capabilities
+ .iter()
+ .any(|cap| cap.eq_ignore_ascii_case(b"LOGINDISABLED"))
+ {
+ return Err(MeliError::new(format!(
+ "Could not connect to {}: server does not accept logins [LOGINDISABLED]",
+ &server_conf.server_hostname
+ )));
+ }
+
+ let mut capabilities = None;
ret.send_command(
format!(
"LOGIN \"{}\" \"{}\"",
@@ -239,32 +280,39 @@ impl ImapStream {
)
.as_bytes(),
)?;
- let mut res = String::with_capacity(8 * 1024);
let tag_start = format!("M{} ", (ret.cmd_id - 1));
+
loop {
ret.read_lines(&mut res, &String::new())?;
- if res.starts_with("* OK") {
- if let Some(pos) = res.as_bytes().find(b"\r\n") {
- let pos = pos + "\r\n".len();
- res.replace_range(..pos, "");
+ let mut should_break = false;
+ for l in res.split_rn() {
+ if l.starts_with("* CAPABILITY") {
+ capabilities = protocol_parser::capabilities(l.as_bytes())
+ .to_full_result()
+ .map(|capabilities| {
+ FnvHashSet::from_iter(
+ capabilities.into_iter().map(|s: &[u8]| s.to_vec()),
+ )
+ })
+ .ok();
+ }
+
+ if l.starts_with(tag_start.as_str()) {
+ if !l[tag_start.len()..].trim().starts_with("OK ") {
+ return Err(MeliError::new(format!(
+ "Could not connect. Server replied with '{}'",
+ l[tag_start.len()..].trim()
+ )));
+ }
+ should_break = true;
}
}
-
- if res.starts_with(tag_start.as_str()) {
- if !res[tag_start.len()..].trim().starts_with("OK ") {
- return Err(MeliError::new(format!(
- "Could not connect. Server replied with '{}'",
- res[tag_start.len()..].trim()
- )));
- }
+ if should_break {
break;
}
}
- let capabilities: std::result::Result, _> =
- protocol_parser::capabilities(res.as_bytes()).to_full_result();
-
- if capabilities.is_err() {
+ if capabilities.is_none() {
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so check
* for lazy servers */
drop(capabilities);
@@ -274,9 +322,7 @@ impl ImapStream {
let capabilities = FnvHashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
Ok((capabilities, ret))
} else {
- let capabilities = capabilities?;
- let capabilities = FnvHashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
-
+ let capabilities = capabilities.unwrap();
Ok((capabilities, ret))
}
}
diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs
index 0094822a8..77a9bac4e 100644
--- a/melib/src/backends/imap/protocol_parser.rs
+++ b/melib/src/backends/imap/protocol_parser.rs
@@ -5,6 +5,38 @@ use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
+pub struct ImapLineIterator<'a> {
+ slice: &'a str,
+}
+
+impl<'a> Iterator for ImapLineIterator<'a> {
+ type Item = &'a str;
+
+ fn next(&mut self) -> Option<&'a str> {
+ if self.slice.is_empty() {
+ None
+ } else if let Some(pos) = self.slice.find("\r\n") {
+ let ret = &self.slice[..pos + 2];
+ self.slice = &self.slice[pos + 2..];
+ Some(ret)
+ } else {
+ let ret = self.slice;
+ self.slice = &self.slice[ret.len()..];
+ Some(ret)
+ }
+ }
+}
+
+pub trait ImapLineSplit {
+ fn split_rn(&self) -> ImapLineIterator;
+}
+
+impl ImapLineSplit for str {
+ fn split_rn(&self) -> ImapLineIterator {
+ ImapLineIterator { slice: self }
+ }
+}
+
macro_rules! to_str (
($v:expr) => (unsafe{ std::str::from_utf8_unchecked($v) })
);
@@ -181,14 +213,16 @@ macro_rules! flags_to_imap_list {
* ==============
*
* "M0 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE SPECIAL-USE] Logged in\r\n"
+* "* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA ID XLIST CHILDREN X-GM-EXT-1 XYZZY SASL-IR AUTH=XOAUTH2 AUTH=PLAIN AUTH=PLAIN-CLIENT TOKEN AUTH=OAUTHBEARER AUTH=XOAUTH\r\n"
+* "* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN\r\n"
*/
named!(
pub capabilities>,
do_parse!(
- take_until!("[CAPABILITY ")
- >> tag!("[CAPABILITY ")
- >> ret: terminated!(separated_nonempty_list_complete!(tag!(" "), is_not!(" ]")), tag!("]"))
+ take_until!("CAPABILITY ")
+ >> tag!("CAPABILITY ")
+ >> ret: separated_nonempty_list_complete!(tag!(" "), is_not!(" ]\r\n"))
>> take_until!("\r\n")
>> tag!("\r\n")
>> ({ ret })