melib/imap: don't discard pre-auth capabilities

The library user can retrieve capabilities from a mail backend, and they
might want to see pre-auth capabilities as well.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/425/head
Manos Pitsidianakis 4 months ago
parent e3c1656e05
commit e96e9789db
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -19,28 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::{
backends::{BackendEvent, MailboxHash, RefreshEvent},
email::parser::BytesExt,
error::*,
imap::{
protocol_parser::{self, ImapLineSplit, ImapResponse, RequiredResponses, SelectResponse},
Capabilities, ImapServerConf, UIDStore,
},
text::Truncate,
utils::{
connections::{std_net::connect as tcp_stream_connect, Connection},
futures::timeout,
},
LogLevel,
};
extern crate native_tls;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::HashSet,
convert::TryFrom, convert::TryFrom,
future::Future, future::Future,
iter::FromIterator,
pin::Pin, pin::Pin,
sync::Arc, sync::Arc,
time::{Duration, Instant, SystemTime}, time::{Duration, Instant, SystemTime},
@ -62,9 +44,26 @@ use imap_codec::{
}, },
CommandCodec, CommandCodec,
}; };
use indexmap::IndexSet;
use native_tls::TlsConnector; use native_tls::TlsConnector;
pub use smol::Async as AsyncWrapper; pub use smol::Async as AsyncWrapper;
use crate::{
backends::{BackendEvent, MailboxHash, RefreshEvent},
email::parser::BytesExt,
error::*,
imap::{
protocol_parser::{self, ImapLineSplit, ImapResponse, RequiredResponses, SelectResponse},
Capabilities, ImapServerConf, UIDStore,
},
text::Truncate,
utils::{
connections::{std_net::connect as tcp_stream_connect, Connection},
futures::timeout,
},
LogLevel,
};
const IMAP_PROTOCOL_TIMEOUT: Duration = Duration::from_secs(60 * 28); const IMAP_PROTOCOL_TIMEOUT: Duration = Duration::from_secs(60 * 28);
macro_rules! imap_log { macro_rules! imap_log {
@ -343,32 +342,47 @@ impl ImapStream {
); );
ret.send_command(CommandBody::Capability).await?; ret.send_command(CommandBody::Capability).await?;
ret.read_response(&mut res).await?; ret.read_response(&mut res).await?;
let capabilities: std::result::Result<Vec<&[u8]>, _> = res
fn parse_capabilities(bytes: &[u8], hostname: &str) -> Result<IndexSet<Box<[u8]>>> {
protocol_parser::capabilities(bytes)
.map(|(_, v)| {
v.into_iter()
.map(|v| v.to_vec().into_boxed_slice())
.collect()
})
.map_err(|err| {
Error::new(format!(
"Could not connect to {}: could not parse CAPABILITY response: `{}`",
hostname,
String::from_utf8_lossy(bytes).as_ref().trim_at_boundary(40)
))
.set_source(Some(Arc::new(Error::from(err))))
.set_kind(ErrorKind::ProtocolError)
})
}
let mut capabilities: IndexSet<Box<[u8]>> = {
let capabilities = res
.split_rn() .split_rn()
.find(|l| l.starts_with(b"* CAPABILITY")) .find(|l| l.starts_with(b"* CAPABILITY"))
.ok_or_else(|| Error::new("")) .ok_or_else(|| {
.and_then(|res| { Error::new(format!(
protocol_parser::capabilities(res) "Could not connect to {}: could not parse CAPABILITY response: `{}`",
.map_err(|_| Error::new("")) server_conf.server_hostname,
.map(|(_, v)| v)
});
let capabilities = match capabilities {
Err(_err) => {
log::debug!(
"Could not connect to {}: expected CAPABILITY response but got: {} `{}`",
&server_conf.server_hostname,
_err,
String::from_utf8_lossy(&res)
);
return Err(Error::new(format!(
"Could not connect to {}: expected CAPABILITY response but got: `{}`",
&server_conf.server_hostname,
String::from_utf8_lossy(&res).as_ref().trim_at_boundary(40) String::from_utf8_lossy(&res).as_ref().trim_at_boundary(40)
)) ))
.set_kind(ErrorKind::ProtocolError)); .set_details("Response does not start with `* CAPABILITY`.")
.set_kind(ErrorKind::ProtocolError)
})
.and_then(|res| parse_capabilities(res, &server_conf.server_hostname));
match capabilities {
Err(err) => {
log::debug!("{}: {}", uid_store.account_name, err);
return Err(err);
} }
Ok(v) => v, Ok(v) => v,
}
}; };
if !capabilities if !capabilities
@ -392,6 +406,7 @@ impl ImapStream {
ImapProtocol::IMAP { ImapProtocol::IMAP {
extension_use: ImapExtensionUse { oauth2, .. }, extension_use: ImapExtensionUse { oauth2, .. },
} if oauth2 => { } if oauth2 => {
// [ref:FIXME]: differentiate between OAUTH2 and XOAUTH2
if !capabilities if !capabilities
.iter() .iter()
.any(|cap| cap.eq_ignore_ascii_case(b"AUTH=XOAUTH2")) .any(|cap| cap.eq_ignore_ascii_case(b"AUTH=XOAUTH2"))
@ -467,18 +482,16 @@ impl ImapStream {
} }
} }
let tag_start = format!("M{} ", (ret.cmd_id - 1)); let tag_start = format!("M{} ", (ret.cmd_id - 1));
let mut capabilities = None; let mut got_new_capabilities = false;
loop { loop {
ret.read_lines(&mut res, None, false).await?; ret.read_lines(&mut res, None, false).await?;
let mut should_break = false; let mut should_break = false;
for l in res.split_rn() { for l in res.split_rn() {
if l.starts_with(b"* CAPABILITY") { if l.starts_with(b"* CAPABILITY") {
capabilities = protocol_parser::capabilities(l) got_new_capabilities = true;
.map(|(_, capabilities)| { capabilities
HashSet::from_iter(capabilities.into_iter().map(|s: &[u8]| s.to_vec())) .extend(parse_capabilities(l, &server_conf.server_hostname)?.into_iter());
})
.ok();
} }
if l.starts_with(tag_start.as_bytes()) { if l.starts_with(tag_start.as_bytes()) {
@ -497,18 +510,17 @@ impl ImapStream {
} }
} }
if let Some(capabilities) = capabilities { if got_new_capabilities {
Ok((capabilities, ret)) return Ok((capabilities, ret));
} else { }
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
* check for lazy servers */ // sending CAPABILITY after LOGIN automatically is an RFC recommendation, so
// check for lazy servers.
ret.send_command(CommandBody::Capability).await?; ret.send_command(CommandBody::Capability).await?;
ret.read_response(&mut res).await?; ret.read_response(&mut res).await?;
let capabilities = protocol_parser::capabilities(&res)?.1; capabilities.extend(parse_capabilities(&res, &server_conf.server_hostname)?.into_iter());
let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
Ok((capabilities, ret)) Ok((capabilities, ret))
} }
}
pub async fn read_response(&mut self, ret: &mut Vec<u8>) -> Result<()> { pub async fn read_response(&mut self, ret: &mut Vec<u8>) -> Result<()> {
let id = match self.protocol { let id = match self.protocol {

@ -80,7 +80,7 @@ pub type MessageSequenceNumber = ImapNum;
pub static SUPPORTED_CAPABILITIES: &[&str] = &[ pub static SUPPORTED_CAPABILITIES: &[&str] = &[
"AUTH=ANONYMOUS", "AUTH=ANONYMOUS",
"AUTH=OAUTH2", "AUTH=XOAUTH2",
"COMPRESS=DEFLATE", "COMPRESS=DEFLATE",
"CONDSTORE", "CONDSTORE",
"ENABLE", "ENABLE",
@ -115,7 +115,7 @@ pub struct ImapServerConf {
pub timeout: Option<Duration>, pub timeout: Option<Duration>,
} }
type Capabilities = HashSet<Vec<u8>>; type Capabilities = indexmap::IndexSet<Box<[u8]>>;
#[macro_export] #[macro_export]
macro_rules! get_conf_val { macro_rules! get_conf_val {

Loading…
Cancel
Save