mirror of https://git.meli.delivery/meli/meli
imap: remove blocking imap backend, replace with async
parent
89dedbedb7
commit
931863436d
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,174 +0,0 @@
|
|||||||
/*
|
|
||||||
* meli - imap melib
|
|
||||||
*
|
|
||||||
* Copyright 2020 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use super::UID;
|
|
||||||
use crate::{
|
|
||||||
backends::{AccountHash, MailboxHash},
|
|
||||||
email::Envelope,
|
|
||||||
error::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type MaxUID = UID;
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlite3")]
|
|
||||||
pub use sqlite3_m::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "sqlite3")]
|
|
||||||
mod sqlite3_m {
|
|
||||||
use super::*;
|
|
||||||
use crate::sqlite3;
|
|
||||||
const DB_NAME: &str = "header_cache.db";
|
|
||||||
const INIT_SCRIPT: &str = "PRAGMA foreign_keys = true;
|
|
||||||
PRAGMA encoding = 'UTF-8';
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS envelopes (
|
|
||||||
mailbox_hash INTEGER,
|
|
||||||
uid INTEGER,
|
|
||||||
validity INTEGER,
|
|
||||||
envelope BLOB NOT NULL UNIQUE,
|
|
||||||
PRIMARY KEY (mailbox_hash, uid, validity),
|
|
||||||
FOREIGN KEY (mailbox_hash, validity) REFERENCES uidvalidity(mailbox_hash, uid) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS uidvalidity (
|
|
||||||
uid INTEGER UNIQUE,
|
|
||||||
mailbox_hash INTEGER UNIQUE,
|
|
||||||
PRIMARY KEY (mailbox_hash, uid)
|
|
||||||
);
|
|
||||||
CREATE INDEX IF NOT EXISTS envelope_idx ON envelopes(mailbox_hash, uid, validity);
|
|
||||||
CREATE INDEX IF NOT EXISTS uidvalidity_idx ON uidvalidity(mailbox_hash);";
|
|
||||||
|
|
||||||
pub fn get_envelopes(
|
|
||||||
account_hash: AccountHash,
|
|
||||||
mailbox_hash: MailboxHash,
|
|
||||||
uidvalidity: usize,
|
|
||||||
) -> Result<(MaxUID, Vec<(UID, Envelope)>)> {
|
|
||||||
let conn = sqlite3::open_or_create_db(
|
|
||||||
&format!("{}_{}", account_hash, DB_NAME),
|
|
||||||
Some(INIT_SCRIPT),
|
|
||||||
)?;
|
|
||||||
let mut stmt = conn
|
|
||||||
.prepare("SELECT MAX(uid) FROM envelopes WHERE mailbox_hash = ? AND validity = ?")
|
|
||||||
.unwrap();
|
|
||||||
let max_uid: usize = stmt
|
|
||||||
.query_map(
|
|
||||||
sqlite3::params![mailbox_hash as i64, uidvalidity as i64],
|
|
||||||
|row| row.get(0).map(|u: i64| u as usize),
|
|
||||||
)
|
|
||||||
.chain_err_summary(|| {
|
|
||||||
format!(
|
|
||||||
"Error while performing query {:?}",
|
|
||||||
"SELECT MAX(uid) FROM envelopes WHERE mailbox_hash = ? AND validity = ?"
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.unwrap_or(0);
|
|
||||||
let mut stmt = conn
|
|
||||||
.prepare("SELECT uid, envelope FROM envelopes WHERE mailbox_hash = ? AND validity = ?")
|
|
||||||
.unwrap();
|
|
||||||
let results: Vec<(UID, Vec<u8>)> = stmt
|
|
||||||
.query_map(
|
|
||||||
sqlite3::params![mailbox_hash as i64, uidvalidity as i64],
|
|
||||||
|row| Ok((row.get::<_, i64>(0)? as usize, row.get(1)?)),
|
|
||||||
)
|
|
||||||
.chain_err_summary(|| {
|
|
||||||
format!(
|
|
||||||
"Error while performing query {:?}",
|
|
||||||
"SELECT uid, envelope FROM envelopes WHERE mailbox_hash = ? AND validity = ?",
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.collect::<std::result::Result<_, _>>()?;
|
|
||||||
debug!(
|
|
||||||
"imap cache max_uid: {} results len: {}",
|
|
||||||
max_uid,
|
|
||||||
results.len()
|
|
||||||
);
|
|
||||||
Ok((
|
|
||||||
max_uid,
|
|
||||||
results
|
|
||||||
.into_iter()
|
|
||||||
.map(|(uid, env)| {
|
|
||||||
Ok((
|
|
||||||
uid,
|
|
||||||
bincode::deserialize(&env).map_err(|e| MeliError::new(e.to_string()))?,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<(UID, Envelope)>>>()?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_envelopes(
|
|
||||||
account_hash: AccountHash,
|
|
||||||
mailbox_hash: MailboxHash,
|
|
||||||
uidvalidity: usize,
|
|
||||||
envs: &[(UID, &Envelope)],
|
|
||||||
) -> Result<()> {
|
|
||||||
let conn =
|
|
||||||
sqlite3::open_or_create_db(&format!("{}_{}", account_hash, DB_NAME), Some(INIT_SCRIPT))
|
|
||||||
.chain_err_summary(|| {
|
|
||||||
format!(
|
|
||||||
"Could not create header_cache.db for account {}",
|
|
||||||
account_hash
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
conn.execute(
|
|
||||||
"INSERT OR REPLACE INTO uidvalidity (uid, mailbox_hash) VALUES (?1, ?2)",
|
|
||||||
sqlite3::params![uidvalidity as i64, mailbox_hash as i64],
|
|
||||||
)
|
|
||||||
.chain_err_summary(|| {
|
|
||||||
format!(
|
|
||||||
"Could not insert uidvalidity {} in header_cache of account {}",
|
|
||||||
uidvalidity, account_hash
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
for (uid, env) in envs {
|
|
||||||
conn.execute(
|
|
||||||
"INSERT OR REPLACE INTO envelopes (uid, mailbox_hash, validity, envelope) VALUES (?1, ?2, ?3, ?4)",
|
|
||||||
sqlite3::params![*uid as i64, mailbox_hash as i64, uidvalidity as i64, bincode::serialize(env).map_err(|e| MeliError::new(e.to_string()))?],
|
|
||||||
).chain_err_summary(|| format!("Could not insert envelope with hash {} in header_cache of account {}", env.hash(), account_hash))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "sqlite3"))]
|
|
||||||
pub use filesystem_m::*;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "sqlite3"))]
|
|
||||||
mod filesystem_m {
|
|
||||||
use super::*;
|
|
||||||
pub fn get_envelopes(
|
|
||||||
_account_hash: AccountHash,
|
|
||||||
_mailbox_hash: MailboxHash,
|
|
||||||
_uidvalidity: usize,
|
|
||||||
) -> Result<(MaxUID, Vec<(UID, Envelope)>)> {
|
|
||||||
Ok((0, vec![]))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_envelopes(
|
|
||||||
_account_hash: AccountHash,
|
|
||||||
_mailbox_hash: MailboxHash,
|
|
||||||
_uidvalidity: usize,
|
|
||||||
_envs: &[(UID, &Envelope)],
|
|
||||||
) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,777 +0,0 @@
|
|||||||
/*
|
|
||||||
* meli - imap module.
|
|
||||||
*
|
|
||||||
* Copyright 2017 - 2019 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use super::protocol_parser::{ImapLineSplit, ImapResponse, RequiredResponses};
|
|
||||||
use crate::backends::MailboxHash;
|
|
||||||
use crate::connections::Connection;
|
|
||||||
use crate::email::parser::BytesExt;
|
|
||||||
use crate::error::*;
|
|
||||||
extern crate native_tls;
|
|
||||||
use futures::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use native_tls::TlsConnector;
|
|
||||||
pub use smol::Async as AsyncWrapper;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::iter::FromIterator;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use super::protocol_parser;
|
|
||||||
use super::{Capabilities, ImapServerConf, UIDStore};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum ImapProtocol {
|
|
||||||
IMAP,
|
|
||||||
ManageSieve,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ImapStream {
|
|
||||||
cmd_id: usize,
|
|
||||||
stream: AsyncWrapper<Connection>,
|
|
||||||
protocol: ImapProtocol,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum MailboxSelection {
|
|
||||||
None,
|
|
||||||
Select(MailboxHash),
|
|
||||||
Examine(MailboxHash),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MailboxSelection {
|
|
||||||
pub fn take(&mut self) -> Self {
|
|
||||||
std::mem::replace(self, MailboxSelection::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ImapConnection {
|
|
||||||
pub stream: Result<ImapStream>,
|
|
||||||
pub server_conf: ImapServerConf,
|
|
||||||
pub capabilities: Capabilities,
|
|
||||||
pub uid_store: Arc<UIDStore>,
|
|
||||||
pub current_mailbox: MailboxSelection,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ImapStream {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
//self.send_command(b"LOGOUT").ok().take();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImapStream {
|
|
||||||
pub async fn new_connection(
|
|
||||||
server_conf: &ImapServerConf,
|
|
||||||
) -> Result<(Capabilities, ImapStream)> {
|
|
||||||
use std::net::TcpStream;
|
|
||||||
let path = &server_conf.server_hostname;
|
|
||||||
debug!("ImapStream::new_connection");
|
|
||||||
|
|
||||||
let cmd_id = 1;
|
|
||||||
let stream = if debug!(server_conf.use_tls) {
|
|
||||||
let mut connector = TlsConnector::builder();
|
|
||||||
if server_conf.danger_accept_invalid_certs {
|
|
||||||
connector.danger_accept_invalid_certs(true);
|
|
||||||
}
|
|
||||||
let connector = connector.build()?;
|
|
||||||
|
|
||||||
let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) {
|
|
||||||
a
|
|
||||||
} else {
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"Could not lookup address {}",
|
|
||||||
&path
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
debug!("addr {:?}", addr);
|
|
||||||
|
|
||||||
let mut socket = AsyncWrapper::new(Connection::Tcp(debug!(
|
|
||||||
TcpStream::connect_timeout(&addr, std::time::Duration::new(4, 0),)
|
|
||||||
)?))?;
|
|
||||||
if debug!(server_conf.use_starttls) {
|
|
||||||
let mut buf = vec![0; 1024];
|
|
||||||
match server_conf.protocol {
|
|
||||||
ImapProtocol::IMAP => {
|
|
||||||
socket
|
|
||||||
.write_all(format!("M{} STARTTLS\r\n", cmd_id).as_bytes())
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
ImapProtocol::ManageSieve => {
|
|
||||||
let len = socket.read(&mut buf).await?;
|
|
||||||
debug!(unsafe { std::str::from_utf8_unchecked(&buf[0..len]) });
|
|
||||||
debug!(socket.write_all(b"STARTTLS\r\n").await?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut response = String::with_capacity(1024);
|
|
||||||
let mut broken = false;
|
|
||||||
let now = std::time::Instant::now();
|
|
||||||
|
|
||||||
while now.elapsed().as_secs() < 3 {
|
|
||||||
let len = socket.read(&mut buf).await?;
|
|
||||||
response.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..len]) });
|
|
||||||
match server_conf.protocol {
|
|
||||||
ImapProtocol::IMAP => {
|
|
||||||
if response.starts_with("* OK ") && response.find("\r\n").is_some() {
|
|
||||||
if let Some(pos) = response.as_bytes().find(b"\r\n") {
|
|
||||||
response.drain(0..pos + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImapProtocol::ManageSieve => {
|
|
||||||
if response.starts_with("OK ") && response.find("\r\n").is_some() {
|
|
||||||
response.clear();
|
|
||||||
broken = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if response.starts_with("M1 OK") {
|
|
||||||
broken = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !broken {
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"Could not initiate TLS negotiation to {}.",
|
|
||||||
path
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
// FIXME: This is blocking
|
|
||||||
let socket = socket.into_inner()?;
|
|
||||||
let mut conn_result = connector.connect(path, socket);
|
|
||||||
if let Err(native_tls::HandshakeError::WouldBlock(midhandshake_stream)) =
|
|
||||||
conn_result
|
|
||||||
{
|
|
||||||
let mut midhandshake_stream = Some(midhandshake_stream);
|
|
||||||
loop {
|
|
||||||
match midhandshake_stream.take().unwrap().handshake() {
|
|
||||||
Ok(r) => {
|
|
||||||
conn_result = Ok(r);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(native_tls::HandshakeError::WouldBlock(stream)) => {
|
|
||||||
midhandshake_stream = Some(stream);
|
|
||||||
}
|
|
||||||
p => {
|
|
||||||
p?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AsyncWrapper::new(Connection::Tls(conn_result?))?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let addr = if let Ok(a) = lookup_ipv4(path, server_conf.server_port) {
|
|
||||||
a
|
|
||||||
} else {
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"Could not lookup address {}",
|
|
||||||
&path
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
AsyncWrapper::new(Connection::Tcp(TcpStream::connect_timeout(
|
|
||||||
&addr,
|
|
||||||
std::time::Duration::new(4, 0),
|
|
||||||
)?))?
|
|
||||||
};
|
|
||||||
let mut res = String::with_capacity(8 * 1024);
|
|
||||||
let mut ret = ImapStream {
|
|
||||||
cmd_id,
|
|
||||||
stream,
|
|
||||||
protocol: server_conf.protocol,
|
|
||||||
};
|
|
||||||
if let ImapProtocol::ManageSieve = server_conf.protocol {
|
|
||||||
use data_encoding::BASE64;
|
|
||||||
ret.read_response(&mut res).await?;
|
|
||||||
ret.send_command(
|
|
||||||
format!(
|
|
||||||
"AUTHENTICATE \"PLAIN\" \"{}\"",
|
|
||||||
BASE64.encode(
|
|
||||||
format!(
|
|
||||||
"\0{}\0{}",
|
|
||||||
&server_conf.server_username, &server_conf.server_password
|
|
||||||
)
|
|
||||||
.as_bytes()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
ret.read_response(&mut res).await?;
|
|
||||||
return Ok((Default::default(), ret));
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.send_command(b"CAPABILITY").await?;
|
|
||||||
ret.read_response(&mut res).await?;
|
|
||||||
let capabilities: std::result::Result<Vec<&[u8]>, _> = res
|
|
||||||
.split_rn()
|
|
||||||
.find(|l| l.starts_with("* CAPABILITY"))
|
|
||||||
.ok_or_else(|| MeliError::new(""))
|
|
||||||
.and_then(|res| {
|
|
||||||
protocol_parser::capabilities(res.as_bytes())
|
|
||||||
.map_err(|_| MeliError::new(""))
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
});
|
|
||||||
|
|
||||||
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 \"{}\" \"{}\"",
|
|
||||||
&server_conf.server_username, &server_conf.server_password
|
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let tag_start = format!("M{} ", (ret.cmd_id - 1));
|
|
||||||
|
|
||||||
loop {
|
|
||||||
ret.read_lines(&mut res, &String::new(), false).await?;
|
|
||||||
let mut should_break = false;
|
|
||||||
for l in res.split_rn() {
|
|
||||||
if l.starts_with("* CAPABILITY") {
|
|
||||||
capabilities = protocol_parser::capabilities(l.as_bytes())
|
|
||||||
.map(|(_, capabilities)| {
|
|
||||||
HashSet::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 should_break {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if capabilities.is_none() {
|
|
||||||
/* sending CAPABILITY after LOGIN automatically is an RFC recommendation, so check
|
|
||||||
* for lazy servers */
|
|
||||||
drop(capabilities);
|
|
||||||
ret.send_command(b"CAPABILITY").await?;
|
|
||||||
ret.read_response(&mut res).await.unwrap();
|
|
||||||
let capabilities = protocol_parser::capabilities(res.as_bytes())?.1;
|
|
||||||
let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
|
|
||||||
Ok((capabilities, ret))
|
|
||||||
} else {
|
|
||||||
let capabilities = capabilities.unwrap();
|
|
||||||
Ok((capabilities, ret))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read_response(&mut self, ret: &mut String) -> Result<()> {
|
|
||||||
let id = match self.protocol {
|
|
||||||
ImapProtocol::IMAP => format!("M{} ", self.cmd_id - 1),
|
|
||||||
ImapProtocol::ManageSieve => String::new(),
|
|
||||||
};
|
|
||||||
self.read_lines(ret, &id, true).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read_lines(
|
|
||||||
&mut self,
|
|
||||||
ret: &mut String,
|
|
||||||
termination_string: &str,
|
|
||||||
keep_termination_string: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut buf: [u8; 1024] = [0; 1024];
|
|
||||||
ret.clear();
|
|
||||||
let mut last_line_idx: usize = 0;
|
|
||||||
loop {
|
|
||||||
match self.stream.read(&mut buf).await {
|
|
||||||
Ok(0) => break,
|
|
||||||
Ok(b) => {
|
|
||||||
ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) });
|
|
||||||
if let Some(mut pos) = ret[last_line_idx..].rfind("\r\n") {
|
|
||||||
if ret[last_line_idx..].starts_with("* BYE") {
|
|
||||||
return Err(MeliError::new("Disconnected"));
|
|
||||||
}
|
|
||||||
if let Some(prev_line) =
|
|
||||||
ret[last_line_idx..pos + last_line_idx].rfind("\r\n")
|
|
||||||
{
|
|
||||||
last_line_idx += prev_line + "\r\n".len();
|
|
||||||
pos -= prev_line + "\r\n".len();
|
|
||||||
}
|
|
||||||
if Some(pos + "\r\n".len()) == ret.get(last_line_idx..).map(|r| r.len()) {
|
|
||||||
if !termination_string.is_empty()
|
|
||||||
&& ret[last_line_idx..].starts_with(termination_string)
|
|
||||||
{
|
|
||||||
debug!(&ret[last_line_idx..]);
|
|
||||||
if !keep_termination_string {
|
|
||||||
ret.replace_range(last_line_idx.., "");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else if termination_string.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
last_line_idx += pos + "\r\n".len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Err(MeliError::from(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//debug!("returning IMAP response:\n{:?}", &ret);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn wait_for_continuation_request(&mut self) -> Result<()> {
|
|
||||||
let term = "+ ".to_string();
|
|
||||||
let mut ret = String::new();
|
|
||||||
self.read_lines(&mut ret, &term, false).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_command(&mut self, command: &[u8]) -> Result<()> {
|
|
||||||
let command = command.trim();
|
|
||||||
match self.protocol {
|
|
||||||
ImapProtocol::IMAP => {
|
|
||||||
self.stream.write_all(b"M").await?;
|
|
||||||
self.stream
|
|
||||||
.write_all(self.cmd_id.to_string().as_bytes())
|
|
||||||
.await?;
|
|
||||||
self.stream.write_all(b" ").await?;
|
|
||||||
self.cmd_id += 1;
|
|
||||||
}
|
|
||||||
ImapProtocol::ManageSieve => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.stream.write_all(command).await?;
|
|
||||||
self.stream.write_all(b"\r\n").await?;
|
|
||||||
match self.protocol {
|
|
||||||
ImapProtocol::IMAP => {
|
|
||||||
debug!("sent: M{} {}", self.cmd_id - 1, unsafe {
|
|
||||||
std::str::from_utf8_unchecked(command)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ImapProtocol::ManageSieve => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_literal(&mut self, data: &[u8]) -> Result<()> {
|
|
||||||
self.stream.write_all(data).await?;
|
|
||||||
self.stream.write_all(b"\r\n").await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_raw(&mut self, raw: &[u8]) -> Result<()> {
|
|
||||||
self.stream.write_all(raw).await?;
|
|
||||||
self.stream.write_all(b"\r\n").await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImapConnection {
|
|
||||||
pub fn new_connection(
|
|
||||||
server_conf: &ImapServerConf,
|
|
||||||
uid_store: Arc<UIDStore>,
|
|
||||||
) -> ImapConnection {
|
|
||||||
ImapConnection {
|
|
||||||
stream: Err(MeliError::new("Offline".to_string())),
|
|
||||||
server_conf: server_conf.clone(),
|
|
||||||
capabilities: Capabilities::default(),
|
|
||||||
uid_store,
|
|
||||||
current_mailbox: MailboxSelection::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect(&mut self) -> Result<()> {
|
|
||||||
if let (instant, ref mut status @ Ok(())) = *self.uid_store.is_online.lock().unwrap() {
|
|
||||||
if Instant::now().duration_since(instant) >= std::time::Duration::new(60 * 30, 0) {
|
|
||||||
*status = Err(MeliError::new("Connection timed out"));
|
|
||||||
self.stream = Err(MeliError::new("Connection timed out"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.stream.is_ok() {
|
|
||||||
self.uid_store.is_online.lock().unwrap().0 = Instant::now();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let new_stream = debug!(ImapStream::new_connection(&self.server_conf).await);
|
|
||||||
if new_stream.is_err() {
|
|
||||||
*self.uid_store.is_online.lock().unwrap() = (
|
|
||||||
Instant::now(),
|
|
||||||
Err(new_stream.as_ref().unwrap_err().clone()),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
*self.uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(()));
|
|
||||||
}
|
|
||||||
let (capabilities, stream) = new_stream?;
|
|
||||||
self.stream = Ok(stream);
|
|
||||||
self.capabilities = capabilities;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_response<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
ret: &'a mut String,
|
|
||||||
required_responses: RequiredResponses,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let mut response = String::new();
|
|
||||||
ret.clear();
|
|
||||||
self.stream.as_mut()?.read_response(&mut response).await?;
|
|
||||||
|
|
||||||
match self.server_conf.protocol {
|
|
||||||
ImapProtocol::IMAP => {
|
|
||||||
let r: ImapResponse = ImapResponse::from(&response);
|
|
||||||
match r {
|
|
||||||
ImapResponse::Bye(ref response_code) => {
|
|
||||||
self.stream = Err(MeliError::new(format!(
|
|
||||||
"Offline: received BYE: {:?}",
|
|
||||||
response_code
|
|
||||||
)));
|
|
||||||
ret.push_str(&response);
|
|
||||||
}
|
|
||||||
ImapResponse::No(ref response_code) => {
|
|
||||||
debug!("Received NO response: {:?} {:?}", response_code, response);
|
|
||||||
ret.push_str(&response);
|
|
||||||
}
|
|
||||||
ImapResponse::Bad(ref response_code) => {
|
|
||||||
debug!("Received BAD response: {:?} {:?}", response_code, response);
|
|
||||||
ret.push_str(&response);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
/*debug!(
|
|
||||||
"check every line for required_responses: {:#?}",
|
|
||||||
&required_responses
|
|
||||||
);*/
|
|
||||||
for l in response.split_rn() {
|
|
||||||
/*debug!("check line: {}", &l);*/
|
|
||||||
if required_responses.check(l) || !self.process_untagged(l).await? {
|
|
||||||
ret.push_str(l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.into()
|
|
||||||
}
|
|
||||||
ImapProtocol::ManageSieve => {
|
|
||||||
ret.push_str(&response);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> {
|
|
||||||
self.stream
|
|
||||||
.as_mut()?
|
|
||||||
.read_lines(ret, &termination_string, false)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn wait_for_continuation_request(&mut self) -> Result<()> {
|
|
||||||
self.stream
|
|
||||||
.as_mut()?
|
|
||||||
.wait_for_continuation_request()
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_command(&mut self, command: &[u8]) -> Result<()> {
|
|
||||||
self.stream.as_mut()?.send_command(command).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_literal(&mut self, data: &[u8]) -> Result<()> {
|
|
||||||
self.stream.as_mut()?.send_literal(data).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_raw(&mut self, raw: &[u8]) -> Result<()> {
|
|
||||||
self.stream.as_mut()?.send_raw(raw).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn select_mailbox(
|
|
||||||
&mut self,
|
|
||||||
mailbox_hash: MailboxHash,
|
|
||||||
ret: &mut String,
|
|
||||||
force: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
if !force && self.current_mailbox == MailboxSelection::Select(mailbox_hash) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.send_command(
|
|
||||||
format!(
|
|
||||||
"SELECT \"{}\"",
|
|
||||||
self.uid_store.mailboxes.lock().await[&mailbox_hash].imap_path()
|
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
self.read_response(ret, RequiredResponses::SELECT_REQUIRED)
|
|
||||||
.await?;
|
|
||||||
debug!("select response {}", ret);
|
|
||||||
self.current_mailbox = MailboxSelection::Select(mailbox_hash);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn examine_mailbox(
|
|
||||||
&mut self,
|
|
||||||
mailbox_hash: MailboxHash,
|
|
||||||
ret: &mut String,
|
|
||||||
force: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
if !force && self.current_mailbox == MailboxSelection::Examine(mailbox_hash) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.send_command(
|
|
||||||
format!(
|
|
||||||
"EXAMINE \"{}\"",
|
|
||||||
self.uid_store.mailboxes.lock().await[&mailbox_hash].imap_path()
|
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED)
|
|
||||||
.await?;
|
|
||||||
debug!("examine response {}", ret);
|
|
||||||
self.current_mailbox = MailboxSelection::Examine(mailbox_hash);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn unselect(&mut self) -> Result<()> {
|
|
||||||
match self.current_mailbox.take() {
|
|
||||||
MailboxSelection::Examine(mailbox_hash) |
|
|
||||||
MailboxSelection::Select(mailbox_hash) =>{
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
if self
|
|
||||||
.capabilities
|
|
||||||
.iter()
|
|
||||||
.any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT"))
|
|
||||||
{
|
|
||||||
self.send_command(b"UNSELECT").await?;
|
|
||||||
self.read_response(&mut response, RequiredResponses::empty())
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
/* `RFC3691 - UNSELECT Command` states: "[..] IMAP4 provides this
|
|
||||||
* functionality (via a SELECT command with a nonexistent mailbox name or
|
|
||||||
* reselecting the same mailbox with EXAMINE command)[..]
|
|
||||||
*/
|
|
||||||
|
|
||||||
self.select_mailbox(mailbox_hash, &mut response, true).await?;
|
|
||||||
self.examine_mailbox(mailbox_hash, &mut response, true).await?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MailboxSelection::None => {},
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_refresh_event(&mut self, ev: crate::backends::RefreshEvent) {
|
|
||||||
if let Some(ref sender) = self.uid_store.sender.read().unwrap().as_ref() {
|
|
||||||
sender.send(ev);
|
|
||||||
for ev in self.uid_store.refresh_events.lock().unwrap().drain(..) {
|
|
||||||
sender.send(ev);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.uid_store.refresh_events.lock().unwrap().push(ev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_uid_msn_cache(
|
|
||||||
&mut self,
|
|
||||||
mailbox_hash: MailboxHash,
|
|
||||||
low: usize,
|
|
||||||
) -> Result<()> {
|
|
||||||
debug_assert!(low > 0);
|
|
||||||
let mut response = String::new();
|
|
||||||
self.examine_mailbox(mailbox_hash, &mut response, false)
|
|
||||||
.await?;
|
|
||||||
self.send_command(format!("UID SEARCH {}:*", low).as_bytes())
|
|
||||||
.await?;
|
|
||||||
self.read_response(&mut response, RequiredResponses::SEARCH)
|
|
||||||
.await?;
|
|
||||||
debug!("uid search response {:?}", &response);
|
|
||||||
let mut msn_index_lck = self.uid_store.msn_index.lock().unwrap();
|
|
||||||
let msn_index = msn_index_lck.entry(mailbox_hash).or_default();
|
|
||||||
let _ = msn_index.drain(low - 1..);
|
|
||||||
msn_index.extend(
|
|
||||||
debug!(protocol_parser::search_results(response.as_bytes()))?
|
|
||||||
.1
|
|
||||||
.into_iter(),
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ImapBlockingConnection {
|
|
||||||
buf: [u8; 1024],
|
|
||||||
result: Vec<u8>,
|
|
||||||
prev_res_length: usize,
|
|
||||||
pub conn: ImapConnection,
|
|
||||||
err: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ImapConnection> for ImapBlockingConnection {
|
|
||||||
fn from(conn: ImapConnection) -> Self {
|
|
||||||
ImapBlockingConnection {
|
|
||||||
buf: [0; 1024],
|
|
||||||
conn,
|
|
||||||
prev_res_length: 0,
|
|
||||||
result: Vec::with_capacity(8 * 1024),
|
|
||||||
err: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImapBlockingConnection {
|
|
||||||
pub fn into_conn(self) -> ImapConnection {
|
|
||||||
self.conn
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn err(&self) -> Option<&str> {
|
|
||||||
self.err.as_ref().map(String::as_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_stream<'a>(&'a mut self) -> impl Future<Output = Option<Vec<u8>>> + 'a {
|
|
||||||
self.result.drain(0..self.prev_res_length);
|
|
||||||
self.prev_res_length = 0;
|
|
||||||
let mut break_flag = false;
|
|
||||||
let mut prev_failure = None;
|
|
||||||
async move {
|
|
||||||
if self.conn.stream.is_err() {
|
|
||||||
debug!(&self.conn.stream);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
loop {
|
|
||||||
if let Some(y) = read(self, &mut break_flag, &mut prev_failure).await {
|
|
||||||
return Some(y);
|
|
||||||
}
|
|
||||||
if break_flag {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read(
|
|
||||||
conn: &mut ImapBlockingConnection,
|
|
||||||
break_flag: &mut bool,
|
|
||||||
prev_failure: &mut Option<std::time::Instant>,
|
|
||||||
) -> Option<Vec<u8>> {
|
|
||||||
let ImapBlockingConnection {
|
|
||||||
ref mut prev_res_length,
|
|
||||||
ref mut result,
|
|
||||||
ref mut conn,
|
|
||||||
ref mut buf,
|
|
||||||
ref mut err,
|
|
||||||
} = conn;
|
|
||||||
|
|
||||||
match conn.stream.as_mut().unwrap().stream.read(buf).await {
|
|
||||||
Ok(0) => {
|
|
||||||
*break_flag = true;
|
|
||||||
}
|
|
||||||
Ok(b) => {
|
|
||||||
result.extend_from_slice(&buf[0..b]);
|
|
||||||
debug!(unsafe { std::str::from_utf8_unchecked(result) });
|
|
||||||
if let Some(pos) = result.find(b"\r\n") {
|
|
||||||
*prev_res_length = pos + b"\r\n".len();
|
|
||||||
return Some(result[0..*prev_res_length].to_vec());
|
|
||||||
}
|
|
||||||
*prev_failure = None;
|
|
||||||
}
|
|
||||||
Err(e)
|
|
||||||
if e.kind() == std::io::ErrorKind::WouldBlock
|
|
||||||
|| e.kind() == std::io::ErrorKind::Interrupted =>
|
|
||||||
{
|
|
||||||
debug!(&e);
|
|
||||||
if let Some(prev_failure) = prev_failure.as_ref() {
|
|
||||||
if Instant::now().duration_since(*prev_failure)
|
|
||||||
>= std::time::Duration::new(60 * 5, 0)
|
|
||||||
{
|
|
||||||
*err = Some(e.to_string());
|
|
||||||
*break_flag = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*prev_failure = Some(Instant::now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(&conn.stream);
|
|
||||||
debug!(&e);
|
|
||||||
*err = Some(e.to_string());
|
|
||||||
*break_flag = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup_ipv4(host: &str, port: u16) -> Result<SocketAddr> {
|
|
||||||
use std::net::ToSocketAddrs;
|
|
||||||
|
|
||||||
let addrs = (host, port).to_socket_addrs()?;
|
|
||||||
for addr in addrs {
|
|
||||||
if let SocketAddr::V4(_) = addr {
|
|
||||||
return Ok(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(MeliError::new("Cannot lookup address"))
|
|
||||||
}
|
|
@ -1,172 +0,0 @@
|
|||||||
/*
|
|
||||||
* meli - imap module.
|
|
||||||
*
|
|
||||||
* Copyright 2019 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
use crate::backends::{
|
|
||||||
BackendMailbox, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
|
|
||||||
};
|
|
||||||
use crate::email::EnvelopeHash;
|
|
||||||
use crate::error::*;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct LazyCountSet {
|
|
||||||
not_yet_seen: usize,
|
|
||||||
set: BTreeSet<EnvelopeHash>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LazyCountSet {
|
|
||||||
pub fn set_not_yet_seen(&mut self, new_val: usize) {
|
|
||||||
self.not_yet_seen = new_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_existing(&mut self, new_val: EnvelopeHash) -> bool {
|
|
||||||
if self.not_yet_seen == 0 {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
self.not_yet_seen -= 1;
|
|
||||||
self.set.insert(new_val);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_existing_set(&mut self, set: BTreeSet<EnvelopeHash>) -> bool {
|
|
||||||
debug!("insert_existing_set {:?}", &set);
|
|
||||||
if self.not_yet_seen < set.len() {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
self.not_yet_seen -= set.len();
|
|
||||||
self.set.extend(set.into_iter());
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.set.len() + self.not_yet_seen
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.set.clear();
|
|
||||||
self.not_yet_seen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_new(&mut self, new_val: EnvelopeHash) {
|
|
||||||
self.set.insert(new_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_set(&mut self, set: BTreeSet<EnvelopeHash>) {
|
|
||||||
debug!("insert__set {:?}", &set);
|
|
||||||
self.set.extend(set.into_iter());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, new_val: EnvelopeHash) -> bool {
|
|
||||||
self.set.remove(&new_val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_lazy_count_set() {
|
|
||||||
let mut new = LazyCountSet::default();
|
|
||||||
new.set_not_yet_seen(10);
|
|
||||||
for i in 0..10 {
|
|
||||||
assert!(new.insert_existing(i));
|
|
||||||
}
|
|
||||||
assert!(!new.insert_existing(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct ImapMailbox {
|
|
||||||
pub(super) hash: MailboxHash,
|
|
||||||
pub(super) imap_path: String,
|
|
||||||
pub(super) path: String,
|
|
||||||
pub(super) name: String,
|
|
||||||
pub(super) parent: Option<MailboxHash>,
|
|
||||||
pub(super) children: Vec<MailboxHash>,
|
|
||||||
pub separator: u8,
|
|
||||||
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
|
|
||||||
pub no_select: bool,
|
|
||||||
pub is_subscribed: bool,
|
|
||||||
|
|
||||||
pub permissions: Arc<Mutex<MailboxPermissions>>,
|
|
||||||
pub exists: Arc<Mutex<LazyCountSet>>,
|
|
||||||
pub unseen: Arc<Mutex<LazyCountSet>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImapMailbox {
|
|
||||||
pub fn imap_path(&self) -> &str {
|
|
||||||
&self.imap_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BackendMailbox for ImapMailbox {
|
|
||||||
fn hash(&self) -> MailboxHash {
|
|
||||||
self.hash
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
&self.name
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path(&self) -> &str {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change_name(&mut self, s: &str) {
|
|
||||||
self.name = s.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn children(&self) -> &[MailboxHash] {
|
|
||||||
&self.children
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone(&self) -> Mailbox {
|
|
||||||
Box::new(std::clone::Clone::clone(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn special_usage(&self) -> SpecialUsageMailbox {
|
|
||||||
*self.usage.read().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parent(&self) -> Option<MailboxHash> {
|
|
||||||
self.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
fn permissions(&self) -> MailboxPermissions {
|
|
||||||
*self.permissions.lock().unwrap()
|
|
||||||
}
|
|
||||||
fn is_subscribed(&self) -> bool {
|
|
||||||
self.is_subscribed
|
|
||||||
}
|
|
||||||
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
|
|
||||||
self.is_subscribed = new_val;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
|
|
||||||
*self.usage.write()? = new_val;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn count(&self) -> Result<(usize, usize)> {
|
|
||||||
Ok((self.unseen.lock()?.len(), self.exists.lock()?.len()))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
/*
|
|
||||||
* meli - managesieve
|
|
||||||
*
|
|
||||||
* Copyright 2020 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use super::{ImapConnection, ImapProtocol, ImapServerConf, UIDStore};
|
|
||||||
use crate::conf::AccountSettings;
|
|
||||||
use crate::error::{MeliError, Result};
|
|
||||||
use crate::get_conf_val;
|
|
||||||
use nom::{
|
|
||||||
branch::alt, bytes::complete::tag, combinator::map, error::ErrorKind,
|
|
||||||
multi::separated_nonempty_list, sequence::separated_pair, IResult,
|
|
||||||
};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
pub fn managesieve_capabilities(input: &[u8]) -> Result<Vec<(&[u8], &[u8])>> {
|
|
||||||
let (_, ret) = separated_nonempty_list(
|
|
||||||
tag(b"\r\n"),
|
|
||||||
alt((
|
|
||||||
separated_pair(quoted_raw, tag(b" "), quoted_raw),
|
|
||||||
map(quoted_raw, |q| (q, &b""[..])),
|
|
||||||
)),
|
|
||||||
)(input)?;
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_managesieve_capabilities() {
|
|
||||||
assert_eq!(managesieve_capabilities(b"\"IMPLEMENTATION\" \"Dovecot Pigeonhole\"\r\n\"SIEVE\" \"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext\"\r\n\"NOTIFY\" \"mailto\"\r\n\"SASL\" \"PLAIN\"\r\n\"STARTTLS\"\r\n\"VERSION\" \"1.0\"\r\n").unwrap(), vec![
|
|
||||||
(&b"IMPLEMENTATION"[..],&b"Dovecot Pigeonhole"[..]),
|
|
||||||
(&b"SIEVE"[..],&b"fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"[..]),
|
|
||||||
(&b"NOTIFY"[..],&b"mailto"[..]),
|
|
||||||
(&b"SASL"[..],&b"PLAIN"[..]),
|
|
||||||
(&b"STARTTLS"[..], &b""[..]),
|
|
||||||
(&b"VERSION"[..],&b"1.0"[..])]
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a byte sequence surrounded by "s and decoded if necessary
|
|
||||||
pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
|
||||||
if input.is_empty() || input[0] != b'"' {
|
|
||||||
return Err(nom::Err::Error((input, ErrorKind::Tag)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut i = 1;
|
|
||||||
while i < input.len() {
|
|
||||||
if input[i] == b'\"' && input[i - 1] != b'\\' {
|
|
||||||
return Ok((&input[i + 1..], &input[1..i]));
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(nom::Err::Error((input, ErrorKind::Tag)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ManageSieve {
|
|
||||||
fn havespace(&mut self) -> Result<()>;
|
|
||||||
fn putscript(&mut self) -> Result<()>;
|
|
||||||
|
|
||||||
fn listscripts(&mut self) -> Result<()>;
|
|
||||||
fn setactive(&mut self) -> Result<()>;
|
|
||||||
|
|
||||||
fn getscript(&mut self) -> Result<()>;
|
|
||||||
|
|
||||||
fn deletescript(&mut self) -> Result<()>;
|
|
||||||
fn renamescript(&mut self) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_managesieve_connection(s: &AccountSettings) -> Result<ImapConnection> {
|
|
||||||
let server_hostname = get_conf_val!(s["server_hostname"])?;
|
|
||||||
let server_username = get_conf_val!(s["server_username"])?;
|
|
||||||
let server_password = get_conf_val!(s["server_password"])?;
|
|
||||||
let server_port = get_conf_val!(s["server_port"], 4190)?;
|
|
||||||
let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
|
||||||
let server_conf = ImapServerConf {
|
|
||||||
server_hostname: server_hostname.to_string(),
|
|
||||||
server_username: server_username.to_string(),
|
|
||||||
server_password: server_password.to_string(),
|
|
||||||
server_port,
|
|
||||||
use_starttls: true,
|
|
||||||
use_tls: true,
|
|
||||||
danger_accept_invalid_certs,
|
|
||||||
protocol: ImapProtocol::ManageSieve,
|
|
||||||
};
|
|
||||||
let uid_store = Arc::new(UIDStore {
|
|
||||||
is_online: Arc::new(Mutex::new((
|
|
||||||
Instant::now(),
|
|
||||||
Err(MeliError::new("Account is uninitialised.")),
|
|
||||||
))),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
Ok(ImapConnection::new_connection(&server_conf, uid_store))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ManageSieve for ImapConnection {
|
|
||||||
fn havespace(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn putscript(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn listscripts(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn setactive(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getscript(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deletescript(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn renamescript(&mut self) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,252 +0,0 @@
|
|||||||
/*
|
|
||||||
* meli - imap module.
|
|
||||||
*
|
|
||||||
* Copyright 2017 - 2019 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use crate::backends::*;
|
|
||||||
use crate::email::*;
|
|
||||||
use crate::error::{MeliError, Result};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// `BackendOp` implementor for Imap
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ImapOp {
|
|
||||||
uid: usize,
|
|
||||||
mailbox_hash: MailboxHash,
|
|
||||||
connection: Arc<FutureMutex<ImapConnection>>,
|
|
||||||
uid_store: Arc<UIDStore>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImapOp {
|
|
||||||
pub fn new(
|
|
||||||
uid: usize,
|
|
||||||
mailbox_hash: MailboxHash,
|
|
||||||
connection: Arc<FutureMutex<ImapConnection>>,
|
|
||||||
uid_store: Arc<UIDStore>,
|
|
||||||
) -> Self {
|
|
||||||
ImapOp {
|
|
||||||
uid,
|
|
||||||
connection,
|
|
||||||
mailbox_hash,
|
|
||||||
uid_store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BackendOp for ImapOp {
|
|
||||||
fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
|
|
||||||
let connection = self.connection.clone();
|
|
||||||
let mailbox_hash = self.mailbox_hash;
|
|
||||||
let uid = self.uid;
|
|
||||||
let uid_store = self.uid_store.clone();
|
|
||||||
Ok(Box::pin(async move {
|
|
||||||
let exists_in_cache = {
|
|
||||||
let mut bytes_cache = uid_store.byte_cache.lock()?;
|
|
||||||
let cache = bytes_cache.entry(uid).or_default();
|
|
||||||
cache.bytes.is_some()
|
|
||||||
};
|
|
||||||
if !exists_in_cache {
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
{
|
|
||||||
let mut conn = connection.lock().await;
|
|
||||||
conn.examine_mailbox(mailbox_hash, &mut response, false)
|
|
||||||
.await?;
|
|
||||||
conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", uid).as_bytes())
|
|
||||||
.await?;
|
|
||||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
debug!(
|
|
||||||
"fetch response is {} bytes and {} lines",
|
|
||||||
response.len(),
|
|
||||||
response.lines().collect::<Vec<&str>>().len()
|
|
||||||
);
|
|
||||||
let UidFetchResponse {
|
|
||||||
uid: _uid,
|
|
||||||
flags: _flags,
|
|
||||||
body,
|
|
||||||
..
|
|
||||||
} = protocol_parser::uid_fetch_response(&response)?.1;
|
|
||||||
assert_eq!(_uid, uid);
|
|
||||||
assert!(body.is_some());
|
|
||||||
let mut bytes_cache = uid_store.byte_cache.lock()?;
|
|
||||||
let cache = bytes_cache.entry(uid).or_default();
|
|
||||||
if let Some((_flags, _)) = _flags {
|
|
||||||
//flags.lock().await.set(Some(_flags));
|
|
||||||
cache.flags = Some(_flags);
|
|
||||||
}
|
|
||||||
cache.bytes =
|
|
||||||
Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() });
|
|
||||||
}
|
|
||||||
let mut bytes_cache = uid_store.byte_cache.lock()?;
|
|
||||||
let cache = bytes_cache.entry(uid).or_default();
|
|
||||||
let ret = cache.bytes.clone().unwrap().into_bytes();
|
|
||||||
Ok(ret)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
let connection = self.connection.clone();
|
|
||||||
let mailbox_hash = self.mailbox_hash;
|
|
||||||
let uid = self.uid;
|
|
||||||
let uid_store = self.uid_store.clone();
|
|
||||||
|
|
||||||
Ok(Box::pin(async move {
|
|
||||||
let exists_in_cache = {
|
|
||||||
let mut bytes_cache = uid_store.byte_cache.lock()?;
|
|
||||||
let cache = bytes_cache.entry(uid).or_default();
|
|
||||||
cache.flags.is_some()
|
|
||||||
};
|
|
||||||
if !exists_in_cache {
|
|
||||||
let mut conn = connection.lock().await;
|
|
||||||
conn.examine_mailbox(mailbox_hash, &mut response, false)
|
|
||||||
.await?;
|
|
||||||
conn.send_command(format!("UID FETCH {} FLAGS", uid).as_bytes())
|
|
||||||
.await?;
|
|
||||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
|
|
||||||
.await?;
|
|
||||||
debug!(
|
|
||||||
"fetch response is {} bytes and {} lines",
|
|
||||||
response.len(),
|
|
||||||
response.lines().collect::<Vec<&str>>().len()
|
|
||||||
);
|
|
||||||
let v = protocol_parser::uid_fetch_flags_response(response.as_bytes())
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
.map_err(MeliError::from)?;
|
|
||||||
if v.len() != 1 {
|
|
||||||
debug!("responses len is {}", v.len());
|
|
||||||
debug!(&response);
|
|
||||||
/* TODO: Trigger cache invalidation here. */
|
|
||||||
debug!(format!("message with UID {} was not found", uid));
|
|
||||||
return Err(MeliError::new(format!(
|
|
||||||
"Invalid/unexpected response: {:?}",
|
|
||||||
response
|
|
||||||
))
|
|
||||||
.set_summary(format!("message with UID {} was not found?", uid)));
|
|
||||||
}
|
|
||||||
let (_uid, (_flags, _)) = v[0];
|
|
||||||
assert_eq!(uid, uid);
|
|
||||||
let mut bytes_cache = uid_store.byte_cache.lock()?;
|
|
||||||
let cache = bytes_cache.entry(uid).or_default();
|
|
||||||
cache.flags = Some(_flags);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let val = {
|
|
||||||
let mut bytes_cache = uid_store.byte_cache.lock()?;
|
|
||||||
let cache = bytes_cache.entry(uid).or_default();
|
|
||||||
cache.flags
|
|
||||||
};
|
|
||||||
Ok(val.unwrap())
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_flag(
|
|
||||||
&mut self,
|
|
||||||
flag: Flag,
|
|
||||||
value: bool,
|
|
||||||
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
|
|
||||||
let flags = self.fetch_flags()?;
|
|
||||||
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
let connection = self.connection.clone();
|
|
||||||
let mailbox_hash = self.mailbox_hash;
|
|
||||||
let uid = self.uid;
|
|
||||||
let uid_store = self.uid_store.clone();
|
|
||||||
Ok(Box::pin(async move {
|
|
||||||
let mut flags = flags.await?;
|
|
||||||
flags.set(flag, value);
|
|
||||||
let mut conn = connection.lock().await;
|
|
||||||
conn.select_mailbox(mailbox_hash, &mut response, false)
|
|
||||||
.await?;
|
|
||||||
debug!(&response);
|
|
||||||
conn.send_command(
|
|
||||||
format!(
|
|
||||||
"UID STORE {} FLAGS.SILENT ({})",
|
|
||||||
uid,
|
|
||||||
flags_to_imap_list!(flags)
|
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)
|
|
||||||
.await?;
|
|
||||||
debug!(&response);
|
|
||||||
match protocol_parser::uid_fetch_flags_response(response.as_bytes())
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(v) => {
|
|
||||||
if v.len() == 1 {
|
|
||||||
debug!("responses len is {}", v.len());
|
|
||||||
let (_uid, (_flags, _)) = v[0];
|
|
||||||
assert_eq!(_uid, uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => Err(e)?,
|
|
||||||
}
|
|
||||||
let mut bytes_cache = uid_store.byte_cache.lock()?;
|
|
||||||
let cache = bytes_cache.entry(uid).or_default();
|
|
||||||
cache.flags = Some(flags);
|
|
||||||
Ok(())
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_tag(
|
|
||||||
&mut self,
|
|
||||||
tag: String,
|
|
||||||
value: bool,
|
|
||||||
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
let connection = self.connection.clone();
|
|
||||||
let mailbox_hash = self.mailbox_hash;
|
|
||||||
let uid = self.uid;
|
|
||||||
let uid_store = self.uid_store.clone();
|
|
||||||
Ok(Box::pin(async move {
|
|
||||||
let mut conn = connection.lock().await;
|
|
||||||
conn.select_mailbox(mailbox_hash, &mut response, false)
|
|
||||||
.await?;
|
|
||||||
conn.send_command(
|
|
||||||
format!(
|
|
||||||
"UID STORE {} {}FLAGS.SILENT ({})",
|
|
||||||
uid,
|
|
||||||
if value { "+" } else { "-" },
|
|
||||||
&tag
|
|
||||||
)
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)
|
|
||||||
.await?;
|
|
||||||
protocol_parser::uid_fetch_flags_response(response.as_bytes())
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
.map_err(MeliError::from)?;
|
|
||||||
let hash = tag_hash!(tag);
|
|
||||||
if value {
|
|
||||||
uid_store.tag_index.write().unwrap().insert(hash, tag);
|
|
||||||
} else {
|
|
||||||
uid_store.tag_index.write().unwrap().remove(&hash);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,332 +0,0 @@
|
|||||||
/*
|
|
||||||
* meli - imap
|
|
||||||
*
|
|
||||||
* Copyright 2020 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use super::{ImapConnection, MailboxSelection};
|
|
||||||
use crate::backends::imap_async::protocol_parser::{
|
|
||||||
ImapLineSplit, RequiredResponses, UidFetchResponse, UntaggedResponse,
|
|
||||||
};
|
|
||||||
use crate::backends::BackendMailbox;
|
|
||||||
use crate::backends::{
|
|
||||||
RefreshEvent,
|
|
||||||
RefreshEventKind::{self, *},
|
|
||||||
};
|
|
||||||
use crate::email::Envelope;
|
|
||||||
use crate::error::*;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
impl ImapConnection {
|
|
||||||
pub async fn process_untagged(&mut self, line: &str) -> Result<bool> {
|
|
||||||
macro_rules! try_fail {
|
|
||||||
($mailbox_hash: expr, $($result:expr)+) => {
|
|
||||||
$(if let Err(err) = $result {
|
|
||||||
*self.uid_store.is_online.lock().unwrap() = (
|
|
||||||
Instant::now(),
|
|
||||||
Err(err.clone()),
|
|
||||||
);
|
|
||||||
debug!("failure: {}", err.to_string());
|
|
||||||
self.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: self.uid_store.account_hash,
|
|
||||||
mailbox_hash: $mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Failure(err.clone()),
|
|
||||||
});
|
|
||||||
Err(err)
|
|
||||||
} else { Ok(()) }?;)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//FIXME
|
|
||||||
let mailbox_hash = match self.current_mailbox {
|
|
||||||
MailboxSelection::Select(h) | MailboxSelection::Examine(h) => h,
|
|
||||||
MailboxSelection::None => return Ok(false),
|
|
||||||
};
|
|
||||||
let mailbox =
|
|
||||||
std::clone::Clone::clone(&self.uid_store.mailboxes.lock().await[&mailbox_hash]);
|
|
||||||
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
let untagged_response =
|
|
||||||
match super::protocol_parser::untagged_responses(line.as_bytes()).map(|(_, v)| v) {
|
|
||||||
Ok(None) | Err(_) => {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
Ok(Some(r)) => r,
|
|
||||||
};
|
|
||||||
match untagged_response {
|
|
||||||
UntaggedResponse::Bye { reason } => {
|
|
||||||
*self.uid_store.is_online.lock().unwrap() =
|
|
||||||
(std::time::Instant::now(), Err(reason.into()));
|
|
||||||
}
|
|
||||||
UntaggedResponse::Expunge(n) => {
|
|
||||||
let deleted_uid = self
|
|
||||||
.uid_store
|
|
||||||
.msn_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.entry(mailbox_hash)
|
|
||||||
.or_default()
|
|
||||||
.remove(n);
|
|
||||||
debug!("expunge {}, UID = {}", n, deleted_uid);
|
|
||||||
let deleted_hash: crate::email::EnvelopeHash = self
|
|
||||||
.uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.remove(&(mailbox_hash, deleted_uid))
|
|
||||||
.unwrap();
|
|
||||||
self.uid_store
|
|
||||||
.hash_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.remove(&deleted_hash);
|
|
||||||
self.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: self.uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: Remove(deleted_hash),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
UntaggedResponse::Exists(n) => {
|
|
||||||
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
|
||||||
* */
|
|
||||||
debug!("exists {}", n);
|
|
||||||
if n > mailbox.exists.lock().unwrap().len() {
|
|
||||||
try_fail!(
|
|
||||||
mailbox_hash,
|
|
||||||
self.send_command(
|
|
||||||
&[
|
|
||||||
b"FETCH",
|
|
||||||
format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(),
|
|
||||||
b"(UID FLAGS RFC822)",
|
|
||||||
]
|
|
||||||
.join(&b' '),
|
|
||||||
).await
|
|
||||||
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
||||||
);
|
|
||||||
match super::protocol_parser::uid_fetch_responses(&response) {
|
|
||||||
Ok((_, v, _)) => {
|
|
||||||
'fetch_responses: for UidFetchResponse {
|
|
||||||
uid, flags, body, ..
|
|
||||||
} in v
|
|
||||||
{
|
|
||||||
if self
|
|
||||||
.uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.contains_key(&(mailbox_hash, uid))
|
|
||||||
{
|
|
||||||
continue 'fetch_responses;
|
|
||||||
}
|
|
||||||
if let Ok(mut env) = Envelope::from_bytes(
|
|
||||||
body.unwrap(),
|
|
||||||
flags.as_ref().map(|&(f, _)| f),
|
|
||||||
) {
|
|
||||||
self.uid_store
|
|
||||||
.hash_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(env.hash(), (uid, mailbox_hash));
|
|
||||||
self.uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert((mailbox_hash, uid), env.hash());
|
|
||||||
if let Some((_, keywords)) = flags {
|
|
||||||
let mut tag_lck = self.uid_store.tag_index.write().unwrap();
|
|
||||||
for f in keywords {
|
|
||||||
let hash = tag_hash!(f);
|
|
||||||
if !tag_lck.contains_key(&hash) {
|
|
||||||
tag_lck.insert(hash, f);
|
|
||||||
}
|
|
||||||
env.labels_mut().push(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!(
|
|
||||||
"Create event {} {} {}",
|
|
||||||
env.hash(),
|
|
||||||
env.subject(),
|
|
||||||
mailbox.path(),
|
|
||||||
);
|
|
||||||
if !env.is_seen() {
|
|
||||||
mailbox.unseen.lock().unwrap().insert_new(env.hash());
|
|
||||||
}
|
|
||||||
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
|
||||||
self.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: self.uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: Create(Box::new(env)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UntaggedResponse::Recent(_) => {
|
|
||||||
try_fail!(
|
|
||||||
mailbox_hash,
|
|
||||||
self.send_command(b"UID SEARCH RECENT").await
|
|
||||||
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
|
||||||
);
|
|
||||||
match super::protocol_parser::search_results_raw(response.as_bytes())
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(&[]) => {
|
|
||||||
debug!("UID SEARCH RECENT returned no results");
|
|
||||||
}
|
|
||||||
Ok(v) => {
|
|
||||||
try_fail!(
|
|
||||||
mailbox_hash,
|
|
||||||
self.send_command(
|
|
||||||
&[b"UID FETCH", v, b"(FLAGS RFC822)"]
|
|
||||||
.join(&b' '),
|
|
||||||
).await
|
|
||||||
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
||||||
);
|
|
||||||
debug!(&response);
|
|
||||||
match super::protocol_parser::uid_fetch_responses(&response) {
|
|
||||||
Ok((_, v, _)) => {
|
|
||||||
for UidFetchResponse {
|
|
||||||
uid, flags, body, ..
|
|
||||||
} in v
|
|
||||||
{
|
|
||||||
if !self
|
|
||||||
.uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.contains_key(&(mailbox_hash, uid))
|
|
||||||
{
|
|
||||||
if let Ok(mut env) = Envelope::from_bytes(
|
|
||||||
body.unwrap(),
|
|
||||||
flags.as_ref().map(|&(f, _)| f),
|
|
||||||
) {
|
|
||||||
self.uid_store
|
|
||||||
.hash_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(env.hash(), (uid, mailbox_hash));
|
|
||||||
self.uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert((mailbox_hash, uid), env.hash());
|
|
||||||
debug!(
|
|
||||||
"Create event {} {} {}",
|
|
||||||
env.hash(),
|
|
||||||
env.subject(),
|
|
||||||
mailbox.path(),
|
|
||||||
);
|
|
||||||
if let Some((_, keywords)) = flags {
|
|
||||||
let mut tag_lck =
|
|
||||||
self.uid_store.tag_index.write().unwrap();
|
|
||||||
for f in keywords {
|
|
||||||
let hash = tag_hash!(f);
|
|
||||||
if !tag_lck.contains_key(&hash) {
|
|
||||||
tag_lck.insert(hash, f);
|
|
||||||
}
|
|
||||||
env.labels_mut().push(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !env.is_seen() {
|
|
||||||
mailbox
|
|
||||||
.unseen
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert_new(env.hash());
|
|
||||||
}
|
|
||||||
|
|
||||||
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
|
||||||
self.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: self.uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: Create(Box::new(env)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(
|
|
||||||
"UID SEARCH RECENT err: {}\nresp: {}",
|
|
||||||
e.to_string(),
|
|
||||||
&response
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UntaggedResponse::Fetch(msg_seq, flags) => {
|
|
||||||
/* a * {msg_seq} FETCH (FLAGS ({flags})) was received, so find out UID from msg_seq
|
|
||||||
* and send update
|
|
||||||
*/
|
|
||||||
debug!("fetch {} {:?}", msg_seq, flags);
|
|
||||||
try_fail!(
|
|
||||||
mailbox_hash,
|
|
||||||
self.send_command(
|
|
||||||
&[
|
|
||||||
b"UID SEARCH ",
|
|
||||||
format!("{}", msg_seq).as_bytes(),
|
|
||||||
]
|
|
||||||
.join(&b' '),
|
|
||||||
).await
|
|
||||||
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
|
||||||
);
|
|
||||||
debug!(&response);
|
|
||||||
match super::protocol_parser::search_results(
|
|
||||||
response.split_rn().next().unwrap_or("").as_bytes(),
|
|
||||||
)
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
{
|
|
||||||
Ok(mut v) => {
|
|
||||||
if let Some(uid) = v.pop() {
|
|
||||||
let lck = self.uid_store.uid_index.lock().unwrap();
|
|
||||||
let env_hash = lck.get(&(mailbox_hash, uid)).copied();
|
|
||||||
drop(lck);
|
|
||||||
if let Some(env_hash) = env_hash {
|
|
||||||
if !flags.0.intersects(crate::email::Flag::SEEN) {
|
|
||||||
mailbox.unseen.lock().unwrap().insert_new(env_hash);
|
|
||||||
} else {
|
|
||||||
mailbox.unseen.lock().unwrap().remove(env_hash);
|
|
||||||
}
|
|
||||||
self.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: self.uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: NewFlags(env_hash, flags),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(&response);
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,770 +0,0 @@
|
|||||||
/*
|
|
||||||
* meli - imap module.
|
|
||||||
*
|
|
||||||
* Copyright 2019 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
use super::*;
|
|
||||||
use crate::backends::SpecialUsageMailbox;
|
|
||||||
use crate::email::parser::BytesExt;
|
|
||||||
use crate::email::parser::BytesIterExt;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Arguments for IMAP watching functions
|
|
||||||
pub struct ImapWatchKit {
|
|
||||||
pub conn: ImapConnection,
|
|
||||||
pub main_conn: Arc<FutureMutex<ImapConnection>>,
|
|
||||||
pub uid_store: Arc<UIDStore>,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! exit_on_error {
|
|
||||||
($conn:expr, $mailbox_hash:ident, $($result:expr)+) => {
|
|
||||||
$(if let Err(e) = $result {
|
|
||||||
*$conn.uid_store.is_online.lock().unwrap() = (
|
|
||||||
Instant::now(),
|
|
||||||
Err(e.clone()),
|
|
||||||
);
|
|
||||||
debug!("failure: {}", e.to_string());
|
|
||||||
let account_hash = $conn.uid_store.account_hash;
|
|
||||||
$conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash,
|
|
||||||
mailbox_hash: $mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Failure(e.clone()),
|
|
||||||
});
|
|
||||||
Err(e)
|
|
||||||
} else { Ok(()) }?;)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
|
|
||||||
debug!("poll with examine");
|
|
||||||
let ImapWatchKit {
|
|
||||||
mut conn,
|
|
||||||
main_conn,
|
|
||||||
uid_store,
|
|
||||||
} = kit;
|
|
||||||
conn.connect().await?;
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
loop {
|
|
||||||
let mailboxes = uid_store.mailboxes.lock().await;
|
|
||||||
for mailbox in mailboxes.values() {
|
|
||||||
examine_updates(mailbox, &mut conn, &uid_store).await?;
|
|
||||||
}
|
|
||||||
let mut main_conn = main_conn.lock().await;
|
|
||||||
main_conn.send_command(b"NOOP").await?;
|
|
||||||
main_conn
|
|
||||||
.read_response(&mut response, RequiredResponses::empty())
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|
||||||
debug!("IDLE");
|
|
||||||
/* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX and every ~5
|
|
||||||
* minutes wake up and poll the others */
|
|
||||||
let ImapWatchKit {
|
|
||||||
mut conn,
|
|
||||||
main_conn,
|
|
||||||
uid_store,
|
|
||||||
} = kit;
|
|
||||||
conn.connect().await?;
|
|
||||||
let mailbox: ImapMailbox = match uid_store
|
|
||||||
.mailboxes
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.values()
|
|
||||||
.find(|f| f.parent.is_none() && (f.special_usage() == SpecialUsageMailbox::Inbox))
|
|
||||||
.map(std::clone::Clone::clone)
|
|
||||||
{
|
|
||||||
Some(mailbox) => mailbox,
|
|
||||||
None => {
|
|
||||||
let err = MeliError::new("INBOX mailbox not found in local mailbox index. meli may have not parsed the IMAP mailboxes correctly");
|
|
||||||
debug!("failure: {}", err.to_string());
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash: 0,
|
|
||||||
kind: RefreshEventKind::Failure(err.clone()),
|
|
||||||
});
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mailbox_hash = mailbox.hash();
|
|
||||||
let uidvalidity;
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.send_command(format!("SELECT \"{}\"", mailbox.imap_path()).as_bytes())
|
|
||||||
.await
|
|
||||||
conn.read_response(&mut response, RequiredResponses::SELECT_REQUIRED)
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
debug!("select response {}", &response);
|
|
||||||
{
|
|
||||||
let mut prev_exists = mailbox.exists.lock().unwrap();
|
|
||||||
match protocol_parser::select_response(&response) {
|
|
||||||
Ok(ok) => {
|
|
||||||
{
|
|
||||||
uidvalidity = ok.uidvalidity;
|
|
||||||
let mut uidvalidities = uid_store.uidvalidity.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(v) = uidvalidities.get_mut(&mailbox_hash) {
|
|
||||||
if *v != ok.uidvalidity {
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Rescan,
|
|
||||||
});
|
|
||||||
prev_exists.clear();
|
|
||||||
/*
|
|
||||||
uid_store.uid_index.lock().unwrap().clear();
|
|
||||||
uid_store.hash_index.lock().unwrap().clear();
|
|
||||||
uid_store.byte_cache.lock().unwrap().clear();
|
|
||||||
*/
|
|
||||||
*v = ok.uidvalidity;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Rescan,
|
|
||||||
});
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Failure(MeliError::new(format!(
|
|
||||||
"Unknown mailbox: {} {}",
|
|
||||||
mailbox.path(),
|
|
||||||
mailbox_hash
|
|
||||||
))),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!(&ok);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("{:?}", e);
|
|
||||||
return Err(e).chain_err_summary(|| "could not select mailbox");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
exit_on_error!(conn, mailbox_hash, conn.send_command(b"IDLE").await);
|
|
||||||
let mut blockn = ImapBlockingConnection::from(conn);
|
|
||||||
let mut beat = std::time::Instant::now();
|
|
||||||
let mut watch = std::time::Instant::now();
|
|
||||||
/* duration interval to send heartbeat */
|
|
||||||
const _26_MINS: std::time::Duration = std::time::Duration::from_secs(26 * 60);
|
|
||||||
/* duration interval to check other mailboxes for changes */
|
|
||||||
const _5_MINS: std::time::Duration = std::time::Duration::from_secs(5 * 60);
|
|
||||||
while let Some(line) = blockn.as_stream().await {
|
|
||||||
let now = std::time::Instant::now();
|
|
||||||
if now.duration_since(beat) >= _26_MINS {
|
|
||||||
let mut main_conn_lck = main_conn.lock().await;
|
|
||||||
exit_on_error!(
|
|
||||||
blockn.conn,
|
|
||||||
mailbox_hash,
|
|
||||||
blockn.conn.send_raw(b"DONE").await
|
|
||||||
blockn.conn.read_response(&mut response, RequiredResponses::empty()).await
|
|
||||||
blockn.conn.send_command(b"IDLE").await
|
|
||||||
main_conn_lck.send_command(b"NOOP").await
|
|
||||||
main_conn_lck.read_response(&mut response, RequiredResponses::empty()).await
|
|
||||||
);
|
|
||||||
beat = now;
|
|
||||||
}
|
|
||||||
if now.duration_since(watch) >= _5_MINS {
|
|
||||||
/* Time to poll all inboxes */
|
|
||||||
let mut conn = main_conn.lock().await;
|
|
||||||
let mailboxes = uid_store.mailboxes.lock().await;
|
|
||||||
for mailbox in mailboxes.values() {
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
examine_updates(mailbox, &mut conn, &uid_store).await
|
|
||||||
);
|
|
||||||
}
|
|
||||||
watch = now;
|
|
||||||
}
|
|
||||||
*uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(()));
|
|
||||||
match protocol_parser::untagged_responses(line.as_slice())
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(Some(Recent(_r))) => {
|
|
||||||
let mut conn = main_conn.lock().await;
|
|
||||||
/* UID SEARCH RECENT */
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.examine_mailbox(mailbox_hash, &mut response, false).await
|
|
||||||
conn.send_command(b"UID SEARCH RECENT").await
|
|
||||||
conn.read_response(&mut response, RequiredResponses::SEARCH).await
|
|
||||||
);
|
|
||||||
match protocol_parser::search_results_raw(response.as_bytes())
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(&[]) => {
|
|
||||||
debug!("UID SEARCH RECENT returned no results");
|
|
||||||
}
|
|
||||||
Ok(v) => {
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.send_command(
|
|
||||||
&[&b"UID FETCH"[..], &v.trim().split(|b| b == &b' ').join(b','), &b"(FLAGS RFC822)"[..]]
|
|
||||||
.join(&b' '),
|
|
||||||
).await
|
|
||||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
||||||
);
|
|
||||||
debug!(&response);
|
|
||||||
match protocol_parser::uid_fetch_responses(&response) {
|
|
||||||
Ok((_, v, _)) => {
|
|
||||||
for UidFetchResponse {
|
|
||||||
uid, flags, body, ..
|
|
||||||
} in v
|
|
||||||
{
|
|
||||||
if !uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.contains_key(&(mailbox_hash, uid))
|
|
||||||
{
|
|
||||||
if let Ok(mut env) = Envelope::from_bytes(
|
|
||||||
/* unwrap() is safe since we ask for RFC822 in the
|
|
||||||
* above FETCH, thus uid_fetch_responses() if
|
|
||||||
* returns a successful parse, it will include the
|
|
||||||
* RFC822 response */
|
|
||||||
body.unwrap(),
|
|
||||||
flags.as_ref().map(|&(f, _)| f),
|
|
||||||
) {
|
|
||||||
uid_store
|
|
||||||
.hash_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(env.hash(), (uid, mailbox_hash));
|
|
||||||
uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert((mailbox_hash, uid), env.hash());
|
|
||||||
debug!(
|
|
||||||
"Create event {} {} {}",
|
|
||||||
env.hash(),
|
|
||||||
env.subject(),
|
|
||||||
mailbox.path(),
|
|
||||||
);
|
|
||||||
if let Some((_, keywords)) = flags {
|
|
||||||
let mut tag_lck =
|
|
||||||
uid_store.tag_index.write().unwrap();
|
|
||||||
for f in keywords {
|
|
||||||
let hash = tag_hash!(f);
|
|
||||||
if !tag_lck.contains_key(&hash) {
|
|
||||||
tag_lck.insert(hash, f);
|
|
||||||
}
|
|
||||||
env.labels_mut().push(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !env.is_seen() {
|
|
||||||
mailbox
|
|
||||||
.unseen
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert_new(env.hash());
|
|
||||||
}
|
|
||||||
if uid_store.cache_headers {
|
|
||||||
cache::save_envelopes(
|
|
||||||
uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
uidvalidity,
|
|
||||||
&[(uid, &env)],
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
|
||||||
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: Create(Box::new(env)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(
|
|
||||||
"UID SEARCH RECENT err: {}\nresp: {}",
|
|
||||||
e.to_string(),
|
|
||||||
&response
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(Expunge(n))) => {
|
|
||||||
// The EXPUNGE response reports that the specified message sequence
|
|
||||||
// number has been permanently removed from the mailbox. The message
|
|
||||||
// sequence number for each successive message in the mailbox is
|
|
||||||
// immediately decremented by 1, and this decrement is reflected in
|
|
||||||
// message sequence numbers in subsequent responses (including other
|
|
||||||
// untagged EXPUNGE responses).
|
|
||||||
let mut conn = main_conn.lock().await;
|
|
||||||
let deleted_uid = uid_store
|
|
||||||
.msn_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.entry(mailbox_hash)
|
|
||||||
.or_default()
|
|
||||||
.remove(n);
|
|
||||||
debug!("expunge {}, UID = {}", n, deleted_uid);
|
|
||||||
let deleted_hash: EnvelopeHash = uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.remove(&(mailbox_hash, deleted_uid))
|
|
||||||
.unwrap();
|
|
||||||
uid_store.hash_index.lock().unwrap().remove(&deleted_hash);
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: Remove(deleted_hash),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(Some(Exists(n))) => {
|
|
||||||
let mut conn = main_conn.lock().await;
|
|
||||||
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
|
||||||
* */
|
|
||||||
debug!("exists {}", n);
|
|
||||||
if n > mailbox.exists.lock().unwrap().len() {
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.examine_mailbox(mailbox_hash, &mut response, false).await
|
|
||||||
conn.send_command(
|
|
||||||
&[
|
|
||||||
b"FETCH",
|
|
||||||
format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(),
|
|
||||||
b"(UID FLAGS RFC822)",
|
|
||||||
]
|
|
||||||
.join(&b' '),
|
|
||||||
).await
|
|
||||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
||||||
);
|
|
||||||
match protocol_parser::uid_fetch_responses(&response) {
|
|
||||||
Ok((_, v, _)) => {
|
|
||||||
'fetch_responses_b: for UidFetchResponse {
|
|
||||||
uid, flags, body, ..
|
|
||||||
} in v
|
|
||||||
{
|
|
||||||
if uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.contains_key(&(mailbox_hash, uid))
|
|
||||||
{
|
|
||||||
continue 'fetch_responses_b;
|
|
||||||
}
|
|
||||||
if let Ok(mut env) = Envelope::from_bytes(
|
|
||||||
body.unwrap(),
|
|
||||||
flags.as_ref().map(|&(f, _)| f),
|
|
||||||
) {
|
|
||||||
uid_store
|
|
||||||
.hash_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(env.hash(), (uid, mailbox_hash));
|
|
||||||
uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert((mailbox_hash, uid), env.hash());
|
|
||||||
if let Some((_, keywords)) = flags {
|
|
||||||
let mut tag_lck = uid_store.tag_index.write().unwrap();
|
|
||||||
for f in keywords {
|
|
||||||
let hash = tag_hash!(f);
|
|
||||||
if !tag_lck.contains_key(&hash) {
|
|
||||||
tag_lck.insert(hash, f);
|
|
||||||
}
|
|
||||||
env.labels_mut().push(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!(
|
|
||||||
"Create event {} {} {}",
|
|
||||||
env.hash(),
|
|
||||||
env.subject(),
|
|
||||||
mailbox.path(),
|
|
||||||
);
|
|
||||||
if !env.is_seen() {
|
|
||||||
mailbox.unseen.lock().unwrap().insert_new(env.hash());
|
|
||||||
}
|
|
||||||
if uid_store.cache_headers {
|
|
||||||
cache::save_envelopes(
|
|
||||||
uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
uidvalidity,
|
|
||||||
&[(uid, &env)],
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
|
||||||
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: Create(Box::new(env)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(Fetch(msg_seq, flags))) => {
|
|
||||||
/* a * {msg_seq} FETCH (FLAGS ({flags})) was received, so find out UID from msg_seq
|
|
||||||
* and send update
|
|
||||||
*/
|
|
||||||
let mut conn = main_conn.lock().await;
|
|
||||||
debug!("fetch {} {:?}", msg_seq, flags);
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.examine_mailbox(mailbox_hash, &mut response, false).await
|
|
||||||
conn.send_command(
|
|
||||||
&[
|
|
||||||
b"UID SEARCH ",
|
|
||||||
format!("{}", msg_seq).as_bytes(),
|
|
||||||
]
|
|
||||||
.join(&b' '),
|
|
||||||
).await
|
|
||||||
conn.read_response(&mut response, RequiredResponses::SEARCH).await
|
|
||||||
);
|
|
||||||
match search_results(response.split_rn().next().unwrap_or("").as_bytes())
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
{
|
|
||||||
Ok(mut v) => {
|
|
||||||
if let Some(uid) = v.pop() {
|
|
||||||
if let Some(env_hash) = uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.get(&(mailbox_hash, uid))
|
|
||||||
{
|
|
||||||
if !flags.0.intersects(crate::email::Flag::SEEN) {
|
|
||||||
mailbox.unseen.lock().unwrap().insert_new(*env_hash);
|
|
||||||
} else {
|
|
||||||
mailbox.unseen.lock().unwrap().remove(*env_hash);
|
|
||||||
}
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: NewFlags(*env_hash, flags),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(&response);
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(Bye { .. })) => break,
|
|
||||||
Ok(None) | Err(_) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!("IDLE connection dropped");
|
|
||||||
let err: &str = blockn.err().unwrap_or("Unknown reason.");
|
|
||||||
main_conn.lock().await.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Failure(MeliError::new(format!(
|
|
||||||
"IDLE connection dropped: {}",
|
|
||||||
&err
|
|
||||||
))),
|
|
||||||
});
|
|
||||||
Err(MeliError::new(format!("IDLE connection dropped: {}", err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn examine_updates(
|
|
||||||
mailbox: &ImapMailbox,
|
|
||||||
conn: &mut ImapConnection,
|
|
||||||
uid_store: &Arc<UIDStore>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mailbox_hash = mailbox.hash();
|
|
||||||
debug!("examining mailbox {} {}", mailbox_hash, mailbox.path());
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.examine_mailbox(mailbox_hash, &mut response, true)
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
*uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(()));
|
|
||||||
let uidvalidity;
|
|
||||||
match protocol_parser::select_response(&response) {
|
|
||||||
Ok(ok) => {
|
|
||||||
uidvalidity = ok.uidvalidity;
|
|
||||||
debug!(&ok);
|
|
||||||
{
|
|
||||||
let mut uidvalidities = uid_store.uidvalidity.lock().unwrap();
|
|
||||||
|
|
||||||
if let Some(v) = uidvalidities.get_mut(&mailbox_hash) {
|
|
||||||
if *v != ok.uidvalidity {
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Rescan,
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
uid_store.uid_index.lock().unwrap().clear();
|
|
||||||
uid_store.hash_index.lock().unwrap().clear();
|
|
||||||
uid_store.byte_cache.lock().unwrap().clear();
|
|
||||||
*/
|
|
||||||
*v = ok.uidvalidity;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Rescan,
|
|
||||||
});
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: RefreshEventKind::Failure(MeliError::new(format!(
|
|
||||||
"Unknown mailbox: {} {}",
|
|
||||||
mailbox.path(),
|
|
||||||
mailbox_hash
|
|
||||||
))),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let n = ok.exists;
|
|
||||||
if ok.recent > 0 {
|
|
||||||
{
|
|
||||||
/* UID SEARCH RECENT */
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.send_command(b"UID SEARCH RECENT").await
|
|
||||||
conn.read_response(&mut response, RequiredResponses::SEARCH).await
|
|
||||||
);
|
|
||||||
match protocol_parser::search_results_raw(response.as_bytes())
|
|
||||||
.map(|(_, v)| v)
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(&[]) => {
|
|
||||||
debug!("UID SEARCH RECENT returned no results");
|
|
||||||
}
|
|
||||||
Ok(v) => {
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.send_command(
|
|
||||||
&[&b"UID FETCH"[..], &v.trim().split(|b| b == &b' ').join(b','), &b"(FLAGS RFC822)"[..]]
|
|
||||||
.join(&b' '),
|
|
||||||
).await
|
|
||||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
||||||
);
|
|
||||||
debug!(&response);
|
|
||||||
match protocol_parser::uid_fetch_responses(&response) {
|
|
||||||
Ok((_, v, _)) => {
|
|
||||||
'fetch_responses_c: for UidFetchResponse {
|
|
||||||
uid,
|
|
||||||
flags,
|
|
||||||
body,
|
|
||||||
..
|
|
||||||
} in v
|
|
||||||
{
|
|
||||||
if uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.contains_key(&(mailbox_hash, uid))
|
|
||||||
{
|
|
||||||
continue 'fetch_responses_c;
|
|
||||||
}
|
|
||||||
if let Ok(mut env) = Envelope::from_bytes(
|
|
||||||
body.unwrap(),
|
|
||||||
flags.as_ref().map(|&(f, _)| f),
|
|
||||||
) {
|
|
||||||
uid_store
|
|
||||||
.hash_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(env.hash(), (uid, mailbox_hash));
|
|
||||||
uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert((mailbox_hash, uid), env.hash());
|
|
||||||
debug!(
|
|
||||||
"Create event {} {} {}",
|
|
||||||
env.hash(),
|
|
||||||
env.subject(),
|
|
||||||
mailbox.path(),
|
|
||||||
);
|
|
||||||
if let Some((_, keywords)) = flags {
|
|
||||||
let mut tag_lck =
|
|
||||||
uid_store.tag_index.write().unwrap();
|
|
||||||
for f in keywords {
|
|
||||||
let hash = tag_hash!(f);
|
|
||||||
if !tag_lck.contains_key(&hash) {
|
|
||||||
tag_lck.insert(hash, f);
|
|
||||||
}
|
|
||||||
env.labels_mut().push(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !env.is_seen() {
|
|
||||||
mailbox
|
|
||||||
.unseen
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert_new(env.hash());
|
|
||||||
}
|
|
||||||
if uid_store.cache_headers {
|
|
||||||
cache::save_envelopes(
|
|
||||||
uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
uidvalidity,
|
|
||||||
&[(uid, &env)],
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
let mut prev_exists = mailbox.exists.lock().unwrap();
|
|
||||||
prev_exists.insert_new(env.hash());
|
|
||||||
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: Create(Box::new(env)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(
|
|
||||||
"UID SEARCH RECENT err: {}\nresp: {}",
|
|
||||||
e.to_string(),
|
|
||||||
&response
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if n > mailbox.exists.lock().unwrap().len() {
|
|
||||||
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
|
||||||
* */
|
|
||||||
debug!("exists {}", n);
|
|
||||||
exit_on_error!(
|
|
||||||
conn,
|
|
||||||
mailbox_hash,
|
|
||||||
conn.send_command(
|
|
||||||
&[
|
|
||||||
b"FETCH",
|
|
||||||
format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(),
|
|
||||||
b"(UID FLAGS RFC822)",
|
|
||||||
]
|
|
||||||
.join(&b' '),
|
|
||||||
).await
|
|
||||||
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
||||||
);
|
|
||||||
match protocol_parser::uid_fetch_responses(&response) {
|
|
||||||
Ok((_, v, _)) => {
|
|
||||||
'fetch_responses_a: for UidFetchResponse {
|
|
||||||
uid, flags, body, ..
|
|
||||||
} in v
|
|
||||||
{
|
|
||||||
if uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.contains_key(&(mailbox_hash, uid))
|
|
||||||
{
|
|
||||||
continue 'fetch_responses_a;
|
|
||||||
}
|
|
||||||
if let Ok(mut env) =
|
|
||||||
Envelope::from_bytes(body.unwrap(), flags.as_ref().map(|&(f, _)| f))
|
|
||||||
{
|
|
||||||
uid_store
|
|
||||||
.hash_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(env.hash(), (uid, mailbox_hash));
|
|
||||||
uid_store
|
|
||||||
.uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert((mailbox_hash, uid), env.hash());
|
|
||||||
if let Some((_, keywords)) = flags {
|
|
||||||
let mut tag_lck = uid_store.tag_index.write().unwrap();
|
|
||||||
for f in keywords {
|
|
||||||
let hash = tag_hash!(f);
|
|
||||||
if !tag_lck.contains_key(&hash) {
|
|
||||||
tag_lck.insert(hash, f);
|
|
||||||
}
|
|
||||||
env.labels_mut().push(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!(
|
|
||||||
"Create event {} {} {}",
|
|
||||||
env.hash(),
|
|
||||||
env.subject(),
|
|
||||||
mailbox.path(),
|
|
||||||
);
|
|
||||||
if !env.is_seen() {
|
|
||||||
mailbox.unseen.lock().unwrap().insert_new(env.hash());
|
|
||||||
}
|
|
||||||
if uid_store.cache_headers {
|
|
||||||
cache::save_envelopes(
|
|
||||||
uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
uidvalidity,
|
|
||||||
&[(uid, &env)],
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
|
||||||
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
|
||||||
account_hash: uid_store.account_hash,
|
|
||||||
mailbox_hash,
|
|
||||||
kind: Create(Box::new(env)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("{:?}", e);
|
|
||||||
return Err(e).chain_err_summary(|| "could not select mailbox");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
extern crate melib;
|
|
||||||
|
|
||||||
use melib::backends::imap::managesieve::new_managesieve_connection;
|
|
||||||
use melib::AccountSettings;
|
|
||||||
use melib::Result;
|
|
||||||
|
|
||||||
/// Opens an interactive shell on a managesieve server. Suggested use is with rlwrap(1)
|
|
||||||
///
|
|
||||||
/// # Example invocation:
|
|
||||||
/// ```sh
|
|
||||||
/// ./manage_sieve server_hostname server_username server_password server_port");
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// `danger_accept_invalid_certs` is turned on by default, so no certificate validation is performed.
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let mut args = std::env::args().skip(1).collect::<Vec<String>>();
|
|
||||||
if args.len() != 4 {
|
|
||||||
eprintln!(
|
|
||||||
"Usage: manage_sieve server_hostname server_username server_password server_port"
|
|
||||||
);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (a, b, c, d) = (
|
|
||||||
std::mem::replace(&mut args[0], String::new()),
|
|
||||||
std::mem::replace(&mut args[1], String::new()),
|
|
||||||
std::mem::replace(&mut args[2], String::new()),
|
|
||||||
std::mem::replace(&mut args[3], String::new()),
|
|
||||||
);
|
|
||||||
let set = AccountSettings {
|
|
||||||
extra: [
|
|
||||||
("server_hostname".to_string(), a),
|
|
||||||
("server_username".to_string(), b),
|
|
||||||
("server_password".to_string(), c),
|
|
||||||
("server_port".to_string(), d),
|
|
||||||
(
|
|
||||||
"danger_accept_invalid_certs".to_string(),
|
|
||||||
"true".to_string(),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let mut conn = new_managesieve_connection(&set)?;
|
|
||||||
conn.connect()?;
|
|
||||||
let mut res = String::with_capacity(8 * 1024);
|
|
||||||
|
|
||||||
let mut input = String::new();
|
|
||||||
loop {
|
|
||||||
use std::io;
|
|
||||||
input.clear();
|
|
||||||
match io::stdin().read_line(&mut input) {
|
|
||||||
Ok(_) => {
|
|
||||||
if input.trim().eq_ignore_ascii_case("logout") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
conn.send_command(input.as_bytes()).unwrap();
|
|
||||||
conn.read_lines(&mut res, String::new()).unwrap();
|
|
||||||
println!("out: {}", &res);
|
|
||||||
}
|
|
||||||
Err(error) => println!("error: {}", error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
Loading…
Reference in New Issue