melib/imap: treat server input as bytes
Server input was assumed valid ascii and converted haphazardly to &str. Don't do that, since it might not be valid UTF8.memfd
parent
366e557e1c
commit
3618bdcffb
|
@ -44,7 +44,7 @@ use crate::backends::{
|
||||||
|
|
||||||
use crate::conf::AccountSettings;
|
use crate::conf::AccountSettings;
|
||||||
use crate::connections::timeout;
|
use crate::connections::timeout;
|
||||||
use crate::email::*;
|
use crate::email::{parser::BytesExt, *};
|
||||||
use crate::error::{MeliError, Result, ResultIntoMeliError};
|
use crate::error::{MeliError, Result, ResultIntoMeliError};
|
||||||
use futures::lock::Mutex as FutureMutex;
|
use futures::lock::Mutex as FutureMutex;
|
||||||
use futures::stream::Stream;
|
use futures::stream::Stream;
|
||||||
|
@ -534,7 +534,7 @@ impl MailBackend for ImapType {
|
||||||
let uid_store = self.uid_store.clone();
|
let uid_store = self.uid_store.clone();
|
||||||
let connection = self.connection.clone();
|
let connection = self.connection.clone();
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let mut conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
conn.select_mailbox(mailbox_hash, &mut response, true)
|
conn.select_mailbox(mailbox_hash, &mut response, true)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -645,7 +645,7 @@ impl MailBackend for ImapType {
|
||||||
|
|
||||||
mailbox.imap_path().to_string()
|
mailbox.imap_path().to_string()
|
||||||
};
|
};
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let mut conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
conn.select_mailbox(source_mailbox_hash, &mut response, false)
|
conn.select_mailbox(source_mailbox_hash, &mut response, false)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -711,7 +711,7 @@ impl MailBackend for ImapType {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let mut conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
conn.select_mailbox(mailbox_hash, &mut response, false)
|
conn.select_mailbox(mailbox_hash, &mut response, false)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -883,7 +883,7 @@ impl MailBackend for ImapType {
|
||||||
* flag set. */
|
* flag set. */
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
{
|
{
|
||||||
let mut conn_lck = connection.lock().await;
|
let mut conn_lck = connection.lock().await;
|
||||||
conn_lck.unselect().await?;
|
conn_lck.unselect().await?;
|
||||||
|
@ -901,11 +901,11 @@ impl MailBackend for ImapType {
|
||||||
.read_response(&mut response, RequiredResponses::empty())
|
.read_response(&mut response, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
let ret: Result<()> = ImapResponse::try_from(response.as_str())?.into();
|
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
|
||||||
ret?;
|
ret?;
|
||||||
let new_hash = get_path_hash!(path.as_str());
|
let new_hash = get_path_hash!(path.as_str());
|
||||||
uid_store.mailboxes.lock().await.clear();
|
uid_store.mailboxes.lock().await.clear();
|
||||||
Ok((new_hash, new_mailbox_fut?.await.map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err)))?))
|
Ok((new_hash, new_mailbox_fut?.await.map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err)))?))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -928,7 +928,7 @@ impl MailBackend for ImapType {
|
||||||
return Err(MeliError::new(format!("You do not have permission to delete `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
|
return Err(MeliError::new(format!("You do not have permission to delete `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
{
|
{
|
||||||
let mut conn_lck = connection.lock().await;
|
let mut conn_lck = connection.lock().await;
|
||||||
/* make sure mailbox is not selected before it gets deleted, otherwise
|
/* make sure mailbox is not selected before it gets deleted, otherwise
|
||||||
|
@ -950,10 +950,10 @@ impl MailBackend for ImapType {
|
||||||
.read_response(&mut response, RequiredResponses::empty())
|
.read_response(&mut response, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
let ret: Result<()> = ImapResponse::try_from(response.as_str())?.into();
|
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
|
||||||
ret?;
|
ret?;
|
||||||
uid_store.mailboxes.lock().await.clear();
|
uid_store.mailboxes.lock().await.clear();
|
||||||
new_mailbox_fut?.await.map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err).into())
|
new_mailbox_fut?.await.map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err).into())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -974,7 +974,7 @@ impl MailBackend for ImapType {
|
||||||
command = format!("SUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path());
|
command = format!("SUBSCRIBE \"{}\"", mailboxes[&mailbox_hash].imap_path());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
{
|
{
|
||||||
let mut conn_lck = connection.lock().await;
|
let mut conn_lck = connection.lock().await;
|
||||||
if new_val {
|
if new_val {
|
||||||
|
@ -989,7 +989,7 @@ impl MailBackend for ImapType {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret: Result<()> = ImapResponse::try_from(response.as_str())?.into();
|
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
|
||||||
if ret.is_ok() {
|
if ret.is_ok() {
|
||||||
uid_store
|
uid_store
|
||||||
.mailboxes
|
.mailboxes
|
||||||
|
@ -1014,7 +1014,7 @@ impl MailBackend for ImapType {
|
||||||
let new_mailbox_fut = self.mailboxes();
|
let new_mailbox_fut = self.mailboxes();
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
let command: String;
|
let command: String;
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
{
|
{
|
||||||
let mailboxes = uid_store.mailboxes.lock().await;
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
let permissions = mailboxes[&mailbox_hash].permissions();
|
let permissions = mailboxes[&mailbox_hash].permissions();
|
||||||
|
@ -1041,10 +1041,10 @@ impl MailBackend for ImapType {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
let new_hash = get_path_hash!(new_path.as_str());
|
let new_hash = get_path_hash!(new_path.as_str());
|
||||||
let ret: Result<()> = ImapResponse::try_from(response.as_str())?.into();
|
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
|
||||||
ret?;
|
ret?;
|
||||||
uid_store.mailboxes.lock().await.clear();
|
uid_store.mailboxes.lock().await.clear();
|
||||||
new_mailbox_fut?.await.map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
|
new_mailbox_fut?.await.map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", String::from_utf8_lossy(&response), err))?;
|
||||||
Ok(BackendMailbox::clone(
|
Ok(BackendMailbox::clone(
|
||||||
&uid_store.mailboxes.lock().await[&new_hash],
|
&uid_store.mailboxes.lock().await[&new_hash],
|
||||||
))
|
))
|
||||||
|
@ -1172,7 +1172,7 @@ impl MailBackend for ImapType {
|
||||||
let uid_store = self.uid_store.clone();
|
let uid_store = self.uid_store.clone();
|
||||||
|
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let mut conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
conn.examine_mailbox(mailbox_hash, &mut response, false)
|
conn.examine_mailbox(mailbox_hash, &mut response, false)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1180,16 +1180,18 @@ impl MailBackend for ImapType {
|
||||||
.await?;
|
.await?;
|
||||||
conn.read_response(&mut response, RequiredResponses::SEARCH)
|
conn.read_response(&mut response, RequiredResponses::SEARCH)
|
||||||
.await?;
|
.await?;
|
||||||
debug!(&response);
|
debug!(
|
||||||
|
"searching for {} returned: {}",
|
||||||
|
query_str,
|
||||||
|
String::from_utf8_lossy(&response)
|
||||||
|
);
|
||||||
|
|
||||||
let mut lines = response.lines();
|
for l in response.split_rn() {
|
||||||
for l in lines.by_ref() {
|
if l.starts_with(b"* SEARCH") {
|
||||||
if l.starts_with("* SEARCH") {
|
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
let uid_index = uid_store.uid_index.lock()?;
|
let uid_index = uid_store.uid_index.lock()?;
|
||||||
return Ok(SmallVec::from_iter(
|
return Ok(SmallVec::from_iter(
|
||||||
l["* SEARCH".len()..]
|
String::from_utf8_lossy(l[b"* SEARCH".len()..].trim())
|
||||||
.trim()
|
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.map(UID::from_str)
|
.map(UID::from_str)
|
||||||
.filter_map(std::result::Result::ok)
|
.filter_map(std::result::Result::ok)
|
||||||
|
@ -1198,7 +1200,9 @@ impl MailBackend for ImapType {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(MeliError::new(response))
|
Err(MeliError::new(
|
||||||
|
String::from_utf8_lossy(&response).to_string(),
|
||||||
|
))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1303,7 +1307,7 @@ impl ImapType {
|
||||||
futures::executor::block_on(timeout(self.server_conf.timeout, conn.connect()))
|
futures::executor::block_on(timeout(self.server_conf.timeout, conn.connect()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut res = String::with_capacity(8 * 1024);
|
let mut res = Vec::with_capacity(8 * 1024);
|
||||||
futures::executor::block_on(timeout(
|
futures::executor::block_on(timeout(
|
||||||
self.server_conf.timeout,
|
self.server_conf.timeout,
|
||||||
conn.send_command(b"NOOP"),
|
conn.send_command(b"NOOP"),
|
||||||
|
@ -1332,7 +1336,7 @@ impl ImapType {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
futures::executor::block_on(timeout(
|
futures::executor::block_on(timeout(
|
||||||
self.server_conf.timeout,
|
self.server_conf.timeout,
|
||||||
conn.read_lines(&mut res, String::new()),
|
conn.read_lines(&mut res, Vec::new()),
|
||||||
))
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1348,7 +1352,7 @@ impl ImapType {
|
||||||
conn = iter.into_conn();
|
conn = iter.into_conn();
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
println!("S: {}", &res);
|
println!("S: {}", String::from_utf8_lossy(&res));
|
||||||
}
|
}
|
||||||
Err(error) => println!("error: {}", error),
|
Err(error) => println!("error: {}", error),
|
||||||
}
|
}
|
||||||
|
@ -1359,7 +1363,7 @@ impl ImapType {
|
||||||
connection: &Arc<FutureMutex<ImapConnection>>,
|
connection: &Arc<FutureMutex<ImapConnection>>,
|
||||||
) -> Result<HashMap<MailboxHash, ImapMailbox>> {
|
) -> Result<HashMap<MailboxHash, ImapMailbox>> {
|
||||||
let mut mailboxes: HashMap<MailboxHash, ImapMailbox> = Default::default();
|
let mut mailboxes: HashMap<MailboxHash, ImapMailbox> = Default::default();
|
||||||
let mut res = String::with_capacity(8 * 1024);
|
let mut res = Vec::with_capacity(8 * 1024);
|
||||||
let mut conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
let has_list_status: bool = conn
|
let has_list_status: bool = conn
|
||||||
.uid_store
|
.uid_store
|
||||||
|
@ -1381,14 +1385,12 @@ impl ImapType {
|
||||||
conn.read_response(&mut res, RequiredResponses::LIST_REQUIRED)
|
conn.read_response(&mut res, RequiredResponses::LIST_REQUIRED)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
debug!("out: {}", &res);
|
debug!("out: {}", String::from_utf8_lossy(&res));
|
||||||
let mut lines = res.split_rn();
|
let mut lines = res.split_rn();
|
||||||
/* Remove "M__ OK .." line */
|
/* Remove "M__ OK .." line */
|
||||||
lines.next_back();
|
lines.next_back();
|
||||||
for l in lines {
|
for l in lines {
|
||||||
if let Ok(mut mailbox) =
|
if let Ok(mut mailbox) = protocol_parser::list_mailbox_result(&l).map(|(_, v)| v) {
|
||||||
protocol_parser::list_mailbox_result(l.as_bytes()).map(|(_, v)| v)
|
|
||||||
{
|
|
||||||
if let Some(parent) = mailbox.parent {
|
if let Some(parent) = mailbox.parent {
|
||||||
if mailboxes.contains_key(&parent) {
|
if mailboxes.contains_key(&parent) {
|
||||||
mailboxes
|
mailboxes
|
||||||
|
@ -1414,9 +1416,7 @@ impl ImapType {
|
||||||
} else {
|
} else {
|
||||||
mailboxes.insert(mailbox.hash, mailbox);
|
mailboxes.insert(mailbox.hash, mailbox);
|
||||||
}
|
}
|
||||||
} else if let Ok(status) =
|
} else if let Ok(status) = protocol_parser::status_response(&l).map(|(_, v)| v) {
|
||||||
protocol_parser::status_response(l.as_bytes()).map(|(_, v)| v)
|
|
||||||
{
|
|
||||||
if let Some(mailbox_hash) = status.mailbox {
|
if let Some(mailbox_hash) = status.mailbox {
|
||||||
if mailboxes.contains_key(&mailbox_hash) {
|
if mailboxes.contains_key(&mailbox_hash) {
|
||||||
let entry = mailboxes.entry(mailbox_hash).or_default();
|
let entry = mailboxes.entry(mailbox_hash).or_default();
|
||||||
|
@ -1437,13 +1437,11 @@ impl ImapType {
|
||||||
conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED)
|
conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED)
|
||||||
.await?;
|
.await?;
|
||||||
let mut lines = res.split_rn();
|
let mut lines = res.split_rn();
|
||||||
debug!("out: {}", &res);
|
debug!("out: {}", String::from_utf8_lossy(&res));
|
||||||
/* Remove "M__ OK .." line */
|
/* Remove "M__ OK .." line */
|
||||||
lines.next_back();
|
lines.next_back();
|
||||||
for l in lines {
|
for l in lines {
|
||||||
if let Ok(subscription) =
|
if let Ok(subscription) = protocol_parser::list_mailbox_result(&l).map(|(_, v)| v) {
|
||||||
protocol_parser::list_mailbox_result(l.as_bytes()).map(|(_, v)| v)
|
|
||||||
{
|
|
||||||
if let Some(f) = mailboxes.get_mut(&subscription.hash()) {
|
if let Some(f) = mailboxes.get_mut(&subscription.hash()) {
|
||||||
f.is_subscribed = true;
|
f.is_subscribed = true;
|
||||||
}
|
}
|
||||||
|
@ -1651,7 +1649,7 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
|
||||||
}
|
}
|
||||||
let mut conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
debug!("locked for fetch {}", mailbox_path);
|
debug!("locked for fetch {}", mailbox_path);
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let max_uid_left = max_uid;
|
let max_uid_left = max_uid;
|
||||||
let chunk_size = 250;
|
let chunk_size = 250;
|
||||||
|
|
||||||
|
@ -1686,7 +1684,7 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
|
||||||
debug!(
|
debug!(
|
||||||
"fetch response is {} bytes and {} lines",
|
"fetch response is {} bytes and {} lines",
|
||||||
response.len(),
|
response.len(),
|
||||||
response.lines().count()
|
String::from_utf8_lossy(&response).lines().count()
|
||||||
);
|
);
|
||||||
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
|
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
|
||||||
debug!("responses len is {}", v.len());
|
debug!("responses len is {}", v.len());
|
||||||
|
|
|
@ -83,7 +83,7 @@ impl ImapConnection {
|
||||||
mailbox_hash: MailboxHash,
|
mailbox_hash: MailboxHash,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
debug!("build_cache {}", mailbox_hash);
|
debug!("build_cache {}", mailbox_hash);
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
// 1 get uidvalidity, highestmodseq
|
// 1 get uidvalidity, highestmodseq
|
||||||
let select_response = self
|
let select_response = self
|
||||||
.select_mailbox(mailbox_hash, &mut response, true)
|
.select_mailbox(mailbox_hash, &mut response, true)
|
||||||
|
@ -119,7 +119,7 @@ impl ImapConnection {
|
||||||
) -> Result<Option<Vec<Envelope>>> {
|
) -> Result<Option<Vec<Envelope>>> {
|
||||||
let mut payload = vec![];
|
let mut payload = vec![];
|
||||||
debug!("resync_basic");
|
debug!("resync_basic");
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let cached_uidvalidity = self
|
let cached_uidvalidity = self
|
||||||
.uid_store
|
.uid_store
|
||||||
.uidvalidity
|
.uidvalidity
|
||||||
|
@ -181,7 +181,7 @@ impl ImapConnection {
|
||||||
debug!(
|
debug!(
|
||||||
"fetch response is {} bytes and {} lines",
|
"fetch response is {} bytes and {} lines",
|
||||||
response.len(),
|
response.len(),
|
||||||
response.lines().count()
|
String::from_utf8_lossy(&response).lines().count()
|
||||||
);
|
);
|
||||||
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
|
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
|
||||||
debug!("responses len is {}", v.len());
|
debug!("responses len is {}", v.len());
|
||||||
|
@ -344,7 +344,7 @@ impl ImapConnection {
|
||||||
) -> Result<Option<Vec<Envelope>>> {
|
) -> Result<Option<Vec<Envelope>>> {
|
||||||
let mut payload = vec![];
|
let mut payload = vec![];
|
||||||
debug!("resync_condstore");
|
debug!("resync_condstore");
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let cached_uidvalidity = self
|
let cached_uidvalidity = self
|
||||||
.uid_store
|
.uid_store
|
||||||
.uidvalidity
|
.uidvalidity
|
||||||
|
@ -465,7 +465,7 @@ impl ImapConnection {
|
||||||
debug!(
|
debug!(
|
||||||
"fetch response is {} bytes and {} lines",
|
"fetch response is {} bytes and {} lines",
|
||||||
response.len(),
|
response.len(),
|
||||||
response.lines().count()
|
String::from_utf8_lossy(&response).lines().count()
|
||||||
);
|
);
|
||||||
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
|
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
|
||||||
debug!("responses len is {}", v.len());
|
debug!("responses len is {}", v.len());
|
||||||
|
@ -597,7 +597,7 @@ impl ImapConnection {
|
||||||
self.read_response(&mut response, RequiredResponses::SEARCH)
|
self.read_response(&mut response, RequiredResponses::SEARCH)
|
||||||
.await?;
|
.await?;
|
||||||
//1) update cached flags for old messages;
|
//1) update cached flags for old messages;
|
||||||
let (_, v) = protocol_parser::search_results(response.as_bytes())?;
|
let (_, v) = protocol_parser::search_results(response.as_slice())?;
|
||||||
for uid in v {
|
for uid in v {
|
||||||
valid_envs.insert(generate_envelope_hash(&mailbox_path, &uid));
|
valid_envs.insert(generate_envelope_hash(&mailbox_path, &uid));
|
||||||
}
|
}
|
||||||
|
@ -644,7 +644,7 @@ impl ImapConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_mailbox(&mut self, mailbox_hash: MailboxHash) -> Result<SelectResponse> {
|
pub async fn init_mailbox(&mut self, mailbox_hash: MailboxHash) -> Result<SelectResponse> {
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let (mailbox_path, mailbox_exists, unseen, permissions) = {
|
let (mailbox_path, mailbox_exists, unseen, permissions) = {
|
||||||
let f = &self.uid_store.mailboxes.lock().await[&mailbox_hash];
|
let f = &self.uid_store.mailboxes.lock().await[&mailbox_hash];
|
||||||
(
|
(
|
||||||
|
@ -708,7 +708,7 @@ impl ImapConnection {
|
||||||
.await?;
|
.await?;
|
||||||
self.read_response(&mut response, RequiredResponses::STATUS)
|
self.read_response(&mut response, RequiredResponses::STATUS)
|
||||||
.await?;
|
.await?;
|
||||||
let (_, status) = protocol_parser::status_response(response.as_bytes())?;
|
let (_, status) = protocol_parser::status_response(response.as_slice())?;
|
||||||
if let Some(uidnext) = status.uidnext {
|
if let Some(uidnext) = status.uidnext {
|
||||||
if uidnext == 0 {
|
if uidnext == 0 {
|
||||||
return Err(MeliError::new(
|
return Err(MeliError::new(
|
||||||
|
|
|
@ -165,7 +165,7 @@ impl ImapStream {
|
||||||
.flush()
|
.flush()
|
||||||
.await
|
.await
|
||||||
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
||||||
let mut response = String::with_capacity(1024);
|
let mut response = Vec::with_capacity(1024);
|
||||||
let mut broken = false;
|
let mut broken = false;
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
|
@ -174,24 +174,24 @@ impl ImapStream {
|
||||||
.read(&mut buf)
|
.read(&mut buf)
|
||||||
.await
|
.await
|
||||||
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
.chain_err_kind(crate::error::ErrorKind::Network)?;
|
||||||
response.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..len]) });
|
response.extend_from_slice(&buf[0..len]);
|
||||||
match server_conf.protocol {
|
match server_conf.protocol {
|
||||||
ImapProtocol::IMAP { .. } => {
|
ImapProtocol::IMAP { .. } => {
|
||||||
if response.starts_with("* OK ") && response.find("\r\n").is_some() {
|
if response.starts_with(b"* OK ") && response.find(b"\r\n").is_some() {
|
||||||
if let Some(pos) = response.as_bytes().find(b"\r\n") {
|
if let Some(pos) = response.find(b"\r\n") {
|
||||||
response.drain(0..pos + 2);
|
response.drain(0..pos + 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImapProtocol::ManageSieve => {
|
ImapProtocol::ManageSieve => {
|
||||||
if response.starts_with("OK ") && response.find("\r\n").is_some() {
|
if response.starts_with(b"OK ") && response.find(b"\r\n").is_some() {
|
||||||
response.clear();
|
response.clear();
|
||||||
broken = true;
|
broken = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if response.starts_with("M1 OK") {
|
if response.starts_with(b"M1 OK") {
|
||||||
broken = true;
|
broken = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ impl ImapStream {
|
||||||
crate::LoggingLevel::WARN,
|
crate::LoggingLevel::WARN,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mut res = String::with_capacity(8 * 1024);
|
let mut res = Vec::with_capacity(8 * 1024);
|
||||||
let mut ret = ImapStream {
|
let mut ret = ImapStream {
|
||||||
cmd_id,
|
cmd_id,
|
||||||
stream,
|
stream,
|
||||||
|
@ -295,10 +295,10 @@ impl ImapStream {
|
||||||
ret.read_response(&mut res).await?;
|
ret.read_response(&mut res).await?;
|
||||||
let capabilities: std::result::Result<Vec<&[u8]>, _> = res
|
let capabilities: std::result::Result<Vec<&[u8]>, _> = res
|
||||||
.split_rn()
|
.split_rn()
|
||||||
.find(|l| l.starts_with("* CAPABILITY"))
|
.find(|l| l.starts_with(b"* CAPABILITY"))
|
||||||
.ok_or_else(|| MeliError::new(""))
|
.ok_or_else(|| MeliError::new(""))
|
||||||
.and_then(|res| {
|
.and_then(|res| {
|
||||||
protocol_parser::capabilities(res.as_bytes())
|
protocol_parser::capabilities(&res)
|
||||||
.map_err(|_| MeliError::new(""))
|
.map_err(|_| MeliError::new(""))
|
||||||
.map(|(_, v)| v)
|
.map(|(_, v)| v)
|
||||||
});
|
});
|
||||||
|
@ -306,8 +306,10 @@ impl ImapStream {
|
||||||
if capabilities.is_err() {
|
if capabilities.is_err() {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Could not connect to {}: expected CAPABILITY response but got:{}",
|
"Could not connect to {}: expected CAPABILITY response but got:{}",
|
||||||
&server_conf.server_hostname, res
|
&server_conf.server_hostname,
|
||||||
)));
|
String::from_utf8_lossy(&res)
|
||||||
|
))
|
||||||
|
.set_kind(ErrorKind::Bug));
|
||||||
}
|
}
|
||||||
|
|
||||||
let capabilities = capabilities.unwrap();
|
let capabilities = capabilities.unwrap();
|
||||||
|
@ -342,22 +344,22 @@ impl ImapStream {
|
||||||
let tag_start = format!("M{} ", (ret.cmd_id - 1));
|
let tag_start = format!("M{} ", (ret.cmd_id - 1));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
ret.read_lines(&mut res, &String::new(), false).await?;
|
ret.read_lines(&mut res, &[], false).await?;
|
||||||
let mut should_break = false;
|
let mut should_break = false;
|
||||||
for l in res.split_rn() {
|
for l in res.split_rn() {
|
||||||
if l.starts_with("* CAPABILITY") {
|
if l.starts_with(b"* CAPABILITY") {
|
||||||
capabilities = protocol_parser::capabilities(l.as_bytes())
|
capabilities = protocol_parser::capabilities(&l)
|
||||||
.map(|(_, capabilities)| {
|
.map(|(_, capabilities)| {
|
||||||
HashSet::from_iter(capabilities.into_iter().map(|s: &[u8]| s.to_vec()))
|
HashSet::from_iter(capabilities.into_iter().map(|s: &[u8]| s.to_vec()))
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.starts_with(tag_start.as_str()) {
|
if l.starts_with(tag_start.as_bytes()) {
|
||||||
if !l[tag_start.len()..].trim().starts_with("OK ") {
|
if !l[tag_start.len()..].trim().starts_with(b"OK ") {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Could not connect. Server replied with '{}'",
|
"Could not connect. Server replied with '{}'",
|
||||||
l[tag_start.len()..].trim()
|
String::from_utf8_lossy(l[tag_start.len()..].trim())
|
||||||
))
|
))
|
||||||
.set_err_kind(crate::error::ErrorKind::Authentication));
|
.set_err_kind(crate::error::ErrorKind::Authentication));
|
||||||
}
|
}
|
||||||
|
@ -375,7 +377,7 @@ impl ImapStream {
|
||||||
drop(capabilities);
|
drop(capabilities);
|
||||||
ret.send_command(b"CAPABILITY").await?;
|
ret.send_command(b"CAPABILITY").await?;
|
||||||
ret.read_response(&mut res).await.unwrap();
|
ret.read_response(&mut res).await.unwrap();
|
||||||
let capabilities = protocol_parser::capabilities(res.as_bytes())?.1;
|
let capabilities = protocol_parser::capabilities(&res)?.1;
|
||||||
let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
|
let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
|
||||||
Ok((capabilities, ret))
|
Ok((capabilities, ret))
|
||||||
} else {
|
} else {
|
||||||
|
@ -384,10 +386,10 @@ impl ImapStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_response(&mut self, ret: &mut String) -> Result<()> {
|
pub async fn read_response(&mut self, ret: &mut Vec<u8>) -> Result<()> {
|
||||||
let id = match self.protocol {
|
let id = match self.protocol {
|
||||||
ImapProtocol::IMAP { .. } => format!("M{} ", self.cmd_id - 1),
|
ImapProtocol::IMAP { .. } => format!("M{} ", self.cmd_id - 1).into_bytes(),
|
||||||
ImapProtocol::ManageSieve => String::new(),
|
ImapProtocol::ManageSieve => Vec::new(),
|
||||||
};
|
};
|
||||||
self.read_lines(ret, &id, true).await?;
|
self.read_lines(ret, &id, true).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -395,8 +397,8 @@ impl ImapStream {
|
||||||
|
|
||||||
pub async fn read_lines(
|
pub async fn read_lines(
|
||||||
&mut self,
|
&mut self,
|
||||||
ret: &mut String,
|
ret: &mut Vec<u8>,
|
||||||
termination_string: &str,
|
termination_string: &[u8],
|
||||||
keep_termination_string: bool,
|
keep_termination_string: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut buf: Vec<u8> = vec![0; Connection::IO_BUF_SIZE];
|
let mut buf: Vec<u8> = vec![0; Connection::IO_BUF_SIZE];
|
||||||
|
@ -406,31 +408,31 @@ impl ImapStream {
|
||||||
match timeout(self.timeout, self.stream.read(&mut buf)).await? {
|
match timeout(self.timeout, self.stream.read(&mut buf)).await? {
|
||||||
Ok(0) => break,
|
Ok(0) => break,
|
||||||
Ok(b) => {
|
Ok(b) => {
|
||||||
ret.push_str(unsafe { std::str::from_utf8_unchecked(&buf[0..b]) });
|
ret.extend_from_slice(&buf[0..b]);
|
||||||
if let Some(mut pos) = ret[last_line_idx..].rfind("\r\n") {
|
if let Some(mut pos) = ret[last_line_idx..].rfind("\r\n") {
|
||||||
if ret[last_line_idx..].starts_with("* BYE") {
|
if ret[last_line_idx..].starts_with(b"* BYE") {
|
||||||
return Err(MeliError::new("Disconnected"));
|
return Err(MeliError::new("Disconnected"));
|
||||||
}
|
}
|
||||||
if let Some(prev_line) =
|
if let Some(prev_line) =
|
||||||
ret[last_line_idx..pos + last_line_idx].rfind("\r\n")
|
ret[last_line_idx..pos + last_line_idx].rfind(b"\r\n")
|
||||||
{
|
{
|
||||||
last_line_idx += prev_line + "\r\n".len();
|
last_line_idx += prev_line + b"\r\n".len();
|
||||||
pos -= prev_line + "\r\n".len();
|
pos -= prev_line + b"\r\n".len();
|
||||||
}
|
}
|
||||||
if Some(pos + "\r\n".len()) == ret.get(last_line_idx..).map(|r| r.len()) {
|
if Some(pos + b"\r\n".len()) == ret.get(last_line_idx..).map(|r| r.len()) {
|
||||||
if !termination_string.is_empty()
|
if !termination_string.is_empty()
|
||||||
&& ret[last_line_idx..].starts_with(termination_string)
|
&& ret[last_line_idx..].starts_with(termination_string)
|
||||||
{
|
{
|
||||||
debug!(&ret[last_line_idx..]);
|
debug!(&ret[last_line_idx..]);
|
||||||
if !keep_termination_string {
|
if !keep_termination_string {
|
||||||
ret.replace_range(last_line_idx.., "");
|
ret.splice(last_line_idx.., std::iter::empty::<u8>());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else if termination_string.is_empty() {
|
} else if termination_string.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_line_idx += pos + "\r\n".len();
|
last_line_idx += pos + b"\r\n".len();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -443,9 +445,9 @@ impl ImapStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_for_continuation_request(&mut self) -> Result<()> {
|
pub async fn wait_for_continuation_request(&mut self) -> Result<()> {
|
||||||
let term = "+ ".to_string();
|
let term = b"+ ";
|
||||||
let mut ret = String::new();
|
let mut ret = Vec::new();
|
||||||
self.read_lines(&mut ret, &term, false).await?;
|
self.read_lines(&mut ret, &term[..], false).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,7 +548,7 @@ impl ImapConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if debug!(self.stream.is_ok()) {
|
if debug!(self.stream.is_ok()) {
|
||||||
let mut ret = String::new();
|
let mut ret = Vec::new();
|
||||||
if let Err(err) = try_await(async {
|
if let Err(err) = try_await(async {
|
||||||
self.send_command(b"NOOP").await?;
|
self.send_command(b"NOOP").await?;
|
||||||
self.read_response(&mut ret, RequiredResponses::empty())
|
self.read_response(&mut ret, RequiredResponses::empty())
|
||||||
|
@ -586,7 +588,7 @@ impl ImapConnection {
|
||||||
SyncPolicy::None => { /* do nothing, sync is disabled */ }
|
SyncPolicy::None => { /* do nothing, sync is disabled */ }
|
||||||
_ => {
|
_ => {
|
||||||
/* Upgrade to Condstore */
|
/* Upgrade to Condstore */
|
||||||
let mut ret = String::new();
|
let mut ret = Vec::new();
|
||||||
if capabilities.contains(&b"ENABLE"[..]) {
|
if capabilities.contains(&b"ENABLE"[..]) {
|
||||||
self.send_command(b"ENABLE CONDSTORE").await?;
|
self.send_command(b"ENABLE CONDSTORE").await?;
|
||||||
self.read_response(&mut ret, RequiredResponses::empty())
|
self.read_response(&mut ret, RequiredResponses::empty())
|
||||||
|
@ -605,11 +607,11 @@ impl ImapConnection {
|
||||||
}
|
}
|
||||||
#[cfg(feature = "deflate_compression")]
|
#[cfg(feature = "deflate_compression")]
|
||||||
if capabilities.contains(&b"COMPRESS=DEFLATE"[..]) && deflate {
|
if capabilities.contains(&b"COMPRESS=DEFLATE"[..]) && deflate {
|
||||||
let mut ret = String::new();
|
let mut ret = Vec::new();
|
||||||
self.send_command(b"COMPRESS DEFLATE").await?;
|
self.send_command(b"COMPRESS DEFLATE").await?;
|
||||||
self.read_response(&mut ret, RequiredResponses::empty())
|
self.read_response(&mut ret, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
match ImapResponse::try_from(ret.as_str())? {
|
match ImapResponse::try_from(ret.as_slice())? {
|
||||||
ImapResponse::No(code)
|
ImapResponse::No(code)
|
||||||
| ImapResponse::Bad(code)
|
| ImapResponse::Bad(code)
|
||||||
| ImapResponse::Preauth(code)
|
| ImapResponse::Preauth(code)
|
||||||
|
@ -645,25 +647,25 @@ impl ImapConnection {
|
||||||
|
|
||||||
pub fn read_response<'a>(
|
pub fn read_response<'a>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
ret: &'a mut String,
|
ret: &'a mut Vec<u8>,
|
||||||
required_responses: RequiredResponses,
|
required_responses: RequiredResponses,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
|
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut response = String::new();
|
let mut response = Vec::new();
|
||||||
ret.clear();
|
ret.clear();
|
||||||
self.stream.as_mut()?.read_response(&mut response).await?;
|
self.stream.as_mut()?.read_response(&mut response).await?;
|
||||||
*self.uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(()));
|
*self.uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(()));
|
||||||
|
|
||||||
match self.server_conf.protocol {
|
match self.server_conf.protocol {
|
||||||
ImapProtocol::IMAP { .. } => {
|
ImapProtocol::IMAP { .. } => {
|
||||||
let r: ImapResponse = ImapResponse::try_from(response.as_str())?;
|
let r: ImapResponse = ImapResponse::try_from(response.as_slice())?;
|
||||||
match r {
|
match r {
|
||||||
ImapResponse::Bye(ref response_code) => {
|
ImapResponse::Bye(ref response_code) => {
|
||||||
self.stream = Err(MeliError::new(format!(
|
self.stream = Err(MeliError::new(format!(
|
||||||
"Offline: received BYE: {:?}",
|
"Offline: received BYE: {:?}",
|
||||||
response_code
|
response_code
|
||||||
)));
|
)));
|
||||||
ret.push_str(&response);
|
ret.extend_from_slice(&response);
|
||||||
return r.into();
|
return r.into();
|
||||||
}
|
}
|
||||||
ImapResponse::No(ref response_code)
|
ImapResponse::No(ref response_code)
|
||||||
|
@ -685,7 +687,7 @@ impl ImapConnection {
|
||||||
level: crate::logging::LoggingLevel::ERROR,
|
level: crate::logging::LoggingLevel::ERROR,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ret.push_str(&response);
|
ret.extend_from_slice(&response);
|
||||||
return r.into();
|
return r.into();
|
||||||
}
|
}
|
||||||
ImapResponse::Bad(ref response_code) => {
|
ImapResponse::Bad(ref response_code) => {
|
||||||
|
@ -699,7 +701,7 @@ impl ImapConnection {
|
||||||
level: crate::logging::LoggingLevel::ERROR,
|
level: crate::logging::LoggingLevel::ERROR,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ret.push_str(&response);
|
ret.extend_from_slice(&response);
|
||||||
return r.into();
|
return r.into();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -711,20 +713,24 @@ impl ImapConnection {
|
||||||
for l in response.split_rn() {
|
for l in response.split_rn() {
|
||||||
/*debug!("check line: {}", &l);*/
|
/*debug!("check line: {}", &l);*/
|
||||||
if required_responses.check(l) || !self.process_untagged(l).await? {
|
if required_responses.check(l) || !self.process_untagged(l).await? {
|
||||||
ret.push_str(l);
|
ret.extend_from_slice(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
ImapProtocol::ManageSieve => {
|
ImapProtocol::ManageSieve => {
|
||||||
ret.push_str(&response);
|
ret.extend_from_slice(&response);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> {
|
pub async fn read_lines(
|
||||||
|
&mut self,
|
||||||
|
ret: &mut Vec<u8>,
|
||||||
|
termination_string: Vec<u8>,
|
||||||
|
) -> Result<()> {
|
||||||
self.stream
|
self.stream
|
||||||
.as_mut()?
|
.as_mut()?
|
||||||
.read_lines(ret, &termination_string, false)
|
.read_lines(ret, &termination_string, false)
|
||||||
|
@ -783,7 +789,7 @@ impl ImapConnection {
|
||||||
pub async fn select_mailbox(
|
pub async fn select_mailbox(
|
||||||
&mut self,
|
&mut self,
|
||||||
mailbox_hash: MailboxHash,
|
mailbox_hash: MailboxHash,
|
||||||
ret: &mut String,
|
ret: &mut Vec<u8>,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<Option<SelectResponse>> {
|
) -> Result<Option<SelectResponse>> {
|
||||||
if !force && self.stream.as_ref()?.current_mailbox == MailboxSelection::Select(mailbox_hash)
|
if !force && self.stream.as_ref()?.current_mailbox == MailboxSelection::Select(mailbox_hash)
|
||||||
|
@ -809,7 +815,11 @@ impl ImapConnection {
|
||||||
.await?;
|
.await?;
|
||||||
self.read_response(ret, RequiredResponses::SELECT_REQUIRED)
|
self.read_response(ret, RequiredResponses::SELECT_REQUIRED)
|
||||||
.await?;
|
.await?;
|
||||||
debug!("select response {}", ret);
|
debug!(
|
||||||
|
"{} select response {}",
|
||||||
|
imap_path,
|
||||||
|
String::from_utf8_lossy(&ret)
|
||||||
|
);
|
||||||
let select_response = protocol_parser::select_response(&ret).chain_err_summary(|| {
|
let select_response = protocol_parser::select_response(&ret).chain_err_summary(|| {
|
||||||
format!("Could not parse select response for mailbox {}", imap_path)
|
format!("Could not parse select response for mailbox {}", imap_path)
|
||||||
})?;
|
})?;
|
||||||
|
@ -868,7 +878,7 @@ impl ImapConnection {
|
||||||
pub async fn examine_mailbox(
|
pub async fn examine_mailbox(
|
||||||
&mut self,
|
&mut self,
|
||||||
mailbox_hash: MailboxHash,
|
mailbox_hash: MailboxHash,
|
||||||
ret: &mut String,
|
ret: &mut Vec<u8>,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<Option<SelectResponse>> {
|
) -> Result<Option<SelectResponse>> {
|
||||||
if !force
|
if !force
|
||||||
|
@ -891,7 +901,7 @@ impl ImapConnection {
|
||||||
.await?;
|
.await?;
|
||||||
self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED)
|
self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED)
|
||||||
.await?;
|
.await?;
|
||||||
debug!("examine response {}", ret);
|
debug!("examine response {}", String::from_utf8_lossy(&ret));
|
||||||
let select_response = protocol_parser::select_response(&ret).chain_err_summary(|| {
|
let select_response = protocol_parser::select_response(&ret).chain_err_summary(|| {
|
||||||
format!("Could not parse select response for mailbox {}", imap_path)
|
format!("Could not parse select response for mailbox {}", imap_path)
|
||||||
})?;
|
})?;
|
||||||
|
@ -915,7 +925,7 @@ impl ImapConnection {
|
||||||
match self.stream.as_mut()?.current_mailbox.take() {
|
match self.stream.as_mut()?.current_mailbox.take() {
|
||||||
MailboxSelection::Examine(_) |
|
MailboxSelection::Examine(_) |
|
||||||
MailboxSelection::Select(_) => {
|
MailboxSelection::Select(_) => {
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
if self
|
if self
|
||||||
.uid_store
|
.uid_store
|
||||||
.capabilities
|
.capabilities
|
||||||
|
@ -970,7 +980,7 @@ impl ImapConnection {
|
||||||
_select_response: &SelectResponse,
|
_select_response: &SelectResponse,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
debug_assert!(low > 0);
|
debug_assert!(low > 0);
|
||||||
let mut response = String::new();
|
let mut response = Vec::new();
|
||||||
self.send_command(format!("UID SEARCH {}:*", low).as_bytes())
|
self.send_command(format!("UID SEARCH {}:*", low).as_bytes())
|
||||||
.await?;
|
.await?;
|
||||||
self.read_response(&mut response, RequiredResponses::SEARCH)
|
self.read_response(&mut response, RequiredResponses::SEARCH)
|
||||||
|
@ -980,7 +990,7 @@ impl ImapConnection {
|
||||||
let msn_index = msn_index_lck.entry(mailbox_hash).or_default();
|
let msn_index = msn_index_lck.entry(mailbox_hash).or_default();
|
||||||
let _ = msn_index.drain(low - 1..);
|
let _ = msn_index.drain(low - 1..);
|
||||||
msn_index.extend(
|
msn_index.extend(
|
||||||
debug!(protocol_parser::search_results(response.as_bytes()))?
|
debug!(protocol_parser::search_results(&response))?
|
||||||
.1
|
.1
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -64,7 +64,7 @@ impl BackendOp for ImapOp {
|
||||||
cache.bytes.is_some()
|
cache.bytes.is_some()
|
||||||
};
|
};
|
||||||
if !exists_in_cache {
|
if !exists_in_cache {
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
{
|
{
|
||||||
let mut conn = timeout(uid_store.timeout, connection.lock()).await?;
|
let mut conn = timeout(uid_store.timeout, connection.lock()).await?;
|
||||||
conn.connect().await?;
|
conn.connect().await?;
|
||||||
|
@ -78,7 +78,7 @@ impl BackendOp for ImapOp {
|
||||||
debug!(
|
debug!(
|
||||||
"fetch response is {} bytes and {} lines",
|
"fetch response is {} bytes and {} lines",
|
||||||
response.len(),
|
response.len(),
|
||||||
response.lines().count()
|
String::from_utf8_lossy(&response).lines().count()
|
||||||
);
|
);
|
||||||
let mut results = protocol_parser::fetch_responses(&response)?.1;
|
let mut results = protocol_parser::fetch_responses(&response)?.1;
|
||||||
if results.len() != 1 {
|
if results.len() != 1 {
|
||||||
|
@ -114,7 +114,7 @@ impl BackendOp for ImapOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let connection = self.connection.clone();
|
let connection = self.connection.clone();
|
||||||
let mailbox_hash = self.mailbox_hash;
|
let mailbox_hash = self.mailbox_hash;
|
||||||
let uid = self.uid;
|
let uid = self.uid;
|
||||||
|
@ -138,9 +138,9 @@ impl BackendOp for ImapOp {
|
||||||
debug!(
|
debug!(
|
||||||
"fetch response is {} bytes and {} lines",
|
"fetch response is {} bytes and {} lines",
|
||||||
response.len(),
|
response.len(),
|
||||||
response.lines().count()
|
String::from_utf8_lossy(&response).lines().count()
|
||||||
);
|
);
|
||||||
let v = protocol_parser::uid_fetch_flags_responses(response.as_bytes())
|
let v = protocol_parser::uid_fetch_flags_responses(&response)
|
||||||
.map(|(_, v)| v)
|
.map(|(_, v)| v)
|
||||||
.map_err(MeliError::from)?;
|
.map_err(MeliError::from)?;
|
||||||
if v.len() != 1 {
|
if v.len() != 1 {
|
||||||
|
|
|
@ -66,63 +66,63 @@ bitflags! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequiredResponses {
|
impl RequiredResponses {
|
||||||
pub fn check(&self, line: &str) -> bool {
|
pub fn check(&self, line: &[u8]) -> bool {
|
||||||
if !line.starts_with("* ") {
|
if !line.starts_with(b"* ") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let line = &line["* ".len()..];
|
let line = &line[b"* ".len()..];
|
||||||
let mut ret = false;
|
let mut ret = false;
|
||||||
if self.intersects(RequiredResponses::CAPABILITY) {
|
if self.intersects(RequiredResponses::CAPABILITY) {
|
||||||
ret |= line.starts_with("CAPABILITY");
|
ret |= line.starts_with(b"CAPABILITY");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::BYE) {
|
if self.intersects(RequiredResponses::BYE) {
|
||||||
ret |= line.starts_with("BYE");
|
ret |= line.starts_with(b"BYE");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::FLAGS) {
|
if self.intersects(RequiredResponses::FLAGS) {
|
||||||
ret |= line.starts_with("FLAGS");
|
ret |= line.starts_with(b"FLAGS");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::EXISTS) {
|
if self.intersects(RequiredResponses::EXISTS) {
|
||||||
ret |= line.ends_with("EXISTS\r\n");
|
ret |= line.ends_with(b"EXISTS\r\n");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::RECENT) {
|
if self.intersects(RequiredResponses::RECENT) {
|
||||||
ret |= line.ends_with("RECENT\r\n");
|
ret |= line.ends_with(b"RECENT\r\n");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::UNSEEN) {
|
if self.intersects(RequiredResponses::UNSEEN) {
|
||||||
ret |= line.starts_with("UNSEEN");
|
ret |= line.starts_with(b"UNSEEN");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::PERMANENTFLAGS) {
|
if self.intersects(RequiredResponses::PERMANENTFLAGS) {
|
||||||
ret |= line.starts_with("PERMANENTFLAGS");
|
ret |= line.starts_with(b"PERMANENTFLAGS");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::UIDNEXT) {
|
if self.intersects(RequiredResponses::UIDNEXT) {
|
||||||
ret |= line.starts_with("UIDNEXT");
|
ret |= line.starts_with(b"UIDNEXT");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::UIDVALIDITY) {
|
if self.intersects(RequiredResponses::UIDVALIDITY) {
|
||||||
ret |= line.starts_with("UIDVALIDITY");
|
ret |= line.starts_with(b"UIDVALIDITY");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::LIST) {
|
if self.intersects(RequiredResponses::LIST) {
|
||||||
ret |= line.starts_with("LIST");
|
ret |= line.starts_with(b"LIST");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::LSUB) {
|
if self.intersects(RequiredResponses::LSUB) {
|
||||||
ret |= line.starts_with("LSUB");
|
ret |= line.starts_with(b"LSUB");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::STATUS) {
|
if self.intersects(RequiredResponses::STATUS) {
|
||||||
ret |= line.starts_with("STATUS");
|
ret |= line.starts_with(b"STATUS");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::EXPUNGE) {
|
if self.intersects(RequiredResponses::EXPUNGE) {
|
||||||
ret |= line.ends_with("EXPUNGE\r\n");
|
ret |= line.ends_with(b"EXPUNGE\r\n");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::SEARCH) {
|
if self.intersects(RequiredResponses::SEARCH) {
|
||||||
ret |= line.starts_with("SEARCH");
|
ret |= line.starts_with(b"SEARCH");
|
||||||
}
|
}
|
||||||
if self.intersects(RequiredResponses::FETCH) {
|
if self.intersects(RequiredResponses::FETCH) {
|
||||||
let mut ptr = 0;
|
let mut ptr = 0;
|
||||||
for i in 0..line.len() {
|
for i in 0..line.len() {
|
||||||
if !line.as_bytes()[i].is_ascii_digit() {
|
if !line[i].is_ascii_digit() {
|
||||||
ptr = i;
|
ptr = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret |= line[ptr..].trim_start().starts_with("FETCH");
|
ret |= line[ptr..].trim_start().starts_with(b"FETCH");
|
||||||
}
|
}
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
@ -130,19 +130,18 @@ impl RequiredResponses {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_imap_required_responses() {
|
fn test_imap_required_responses() {
|
||||||
let mut ret = String::new();
|
let mut ret = Vec::new();
|
||||||
let required_responses = RequiredResponses::FETCH_REQUIRED;
|
let required_responses = RequiredResponses::FETCH_REQUIRED;
|
||||||
let response =
|
let response =
|
||||||
&"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n"
|
&b"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n"[..];
|
||||||
[0..];
|
|
||||||
for l in response.split_rn() {
|
for l in response.split_rn() {
|
||||||
/*debug!("check line: {}", &l);*/
|
/*debug!("check line: {}", &l);*/
|
||||||
if required_responses.check(l) {
|
if required_responses.check(l) {
|
||||||
ret.push_str(l);
|
ret.extend_from_slice(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert_eq!(&ret, "* 1040 FETCH (UID 1064 FLAGS ())\r\n");
|
assert_eq!(ret.as_slice(), &b"* 1040 FETCH (UID 1064 FLAGS ())\r\n"[..]);
|
||||||
let v = protocol_parser::uid_fetch_flags_responses(response.as_bytes())
|
let v = protocol_parser::uid_fetch_flags_responses(response)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.1;
|
.1;
|
||||||
assert_eq!(v.len(), 1);
|
assert_eq!(v.len(), 1);
|
||||||
|
@ -150,9 +149,9 @@ fn test_imap_required_responses() {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Alert(String);
|
pub struct Alert(String);
|
||||||
pub type ImapParseResult<'a, T> = Result<(&'a str, T, Option<Alert>)>;
|
pub type ImapParseResult<'a, T> = Result<(&'a [u8], T, Option<Alert>)>;
|
||||||
pub struct ImapLineIterator<'a> {
|
pub struct ImapLineIterator<'a> {
|
||||||
slice: &'a str,
|
slice: &'a [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -210,38 +209,35 @@ impl std::fmt::Display for ResponseCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseCode {
|
impl ResponseCode {
|
||||||
fn from(val: &str) -> ResponseCode {
|
fn from(val: &[u8]) -> ResponseCode {
|
||||||
use ResponseCode::*;
|
use ResponseCode::*;
|
||||||
if !val.starts_with('[') {
|
if !val.starts_with(b"[") {
|
||||||
let msg = val.trim();
|
let msg = val.trim();
|
||||||
return Alert(msg.to_string());
|
return Alert(String::from_utf8_lossy(msg).to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let val = &val[1..];
|
let val = &val[1..];
|
||||||
if val.starts_with("BADCHARSET") {
|
if val.starts_with(b"BADCHARSET") {
|
||||||
let charsets = val
|
let charsets = val.find(b"(").map(|pos| val[pos + 1..].trim());
|
||||||
.as_bytes()
|
Badcharset(charsets.map(|charsets| String::from_utf8_lossy(charsets).to_string()))
|
||||||
.find(b"(")
|
} else if val.starts_with(b"READONLY") {
|
||||||
.map(|pos| val[pos + 1..].trim().to_string());
|
|
||||||
Badcharset(charsets)
|
|
||||||
} else if val.starts_with("READONLY") {
|
|
||||||
ReadOnly
|
ReadOnly
|
||||||
} else if val.starts_with("READWRITE") {
|
} else if val.starts_with(b"READWRITE") {
|
||||||
ReadWrite
|
ReadWrite
|
||||||
} else if val.starts_with("TRYCREATE") {
|
} else if val.starts_with(b"TRYCREATE") {
|
||||||
Trycreate
|
Trycreate
|
||||||
} else if val.starts_with("UIDNEXT") {
|
} else if val.starts_with(b"UIDNEXT") {
|
||||||
//FIXME
|
//FIXME
|
||||||
Uidnext(0)
|
Uidnext(0)
|
||||||
} else if val.starts_with("UIDVALIDITY") {
|
} else if val.starts_with(b"UIDVALIDITY") {
|
||||||
//FIXME
|
//FIXME
|
||||||
Uidvalidity(0)
|
Uidvalidity(0)
|
||||||
} else if val.starts_with("UNSEEN") {
|
} else if val.starts_with(b"UNSEEN") {
|
||||||
//FIXME
|
//FIXME
|
||||||
Unseen(0)
|
Unseen(0)
|
||||||
} else {
|
} else {
|
||||||
let msg = &val[val.as_bytes().find(b"] ").unwrap() + 1..].trim();
|
let msg = &val[val.find(b"] ").unwrap() + 1..].trim();
|
||||||
Alert(msg.to_string())
|
Alert(String::from_utf8_lossy(msg).to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,12 +251,11 @@ pub enum ImapResponse {
|
||||||
Bye(ResponseCode),
|
Bye(ResponseCode),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&'_ str> for ImapResponse {
|
impl TryFrom<&'_ [u8]> for ImapResponse {
|
||||||
type Error = MeliError;
|
type Error = MeliError;
|
||||||
fn try_from(val: &'_ str) -> Result<ImapResponse> {
|
fn try_from(val: &'_ [u8]) -> Result<ImapResponse> {
|
||||||
let val: &str = val.split_rn().last().unwrap_or(val.as_ref());
|
let val: &[u8] = val.split_rn().last().unwrap_or(val.as_ref());
|
||||||
debug!(&val);
|
let mut val = val[val.find(b" ").ok_or_else(|| {
|
||||||
let mut val = val[val.as_bytes().find(b" ").ok_or_else(|| {
|
|
||||||
MeliError::new(format!(
|
MeliError::new(format!(
|
||||||
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
|
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
|
||||||
val
|
val
|
||||||
|
@ -268,8 +263,8 @@ impl TryFrom<&'_ str> for ImapResponse {
|
||||||
})? + 1..]
|
})? + 1..]
|
||||||
.trim();
|
.trim();
|
||||||
// M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n
|
// M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n
|
||||||
if val.ends_with(" secs).") {
|
if val.ends_with(b" secs).") {
|
||||||
val = &val[..val.as_bytes().rfind(b"(").ok_or_else(|| {
|
val = &val[..val.rfind(b"(").ok_or_else(|| {
|
||||||
MeliError::new(format!(
|
MeliError::new(format!(
|
||||||
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
|
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
|
||||||
val
|
val
|
||||||
|
@ -277,16 +272,16 @@ impl TryFrom<&'_ str> for ImapResponse {
|
||||||
})?];
|
})?];
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(if val.starts_with("OK") {
|
Ok(if val.starts_with(b"OK") {
|
||||||
Self::Ok(ResponseCode::from(&val["OK ".len()..]))
|
Self::Ok(ResponseCode::from(&val[b"OK ".len()..]))
|
||||||
} else if val.starts_with("NO") {
|
} else if val.starts_with(b"NO") {
|
||||||
Self::No(ResponseCode::from(&val["NO ".len()..]))
|
Self::No(ResponseCode::from(&val[b"NO ".len()..]))
|
||||||
} else if val.starts_with("BAD") {
|
} else if val.starts_with(b"BAD") {
|
||||||
Self::Bad(ResponseCode::from(&val["BAD ".len()..]))
|
Self::Bad(ResponseCode::from(&val[b"BAD ".len()..]))
|
||||||
} else if val.starts_with("PREAUTH") {
|
} else if val.starts_with(b"PREAUTH") {
|
||||||
Self::Preauth(ResponseCode::from(&val["PREAUTH ".len()..]))
|
Self::Preauth(ResponseCode::from(&val[b"PREAUTH ".len()..]))
|
||||||
} else if val.starts_with("BYE") {
|
} else if val.starts_with(b"BYE") {
|
||||||
Self::Bye(ResponseCode::from(&val["BYE ".len()..]))
|
Self::Bye(ResponseCode::from(&val[b"BYE ".len()..]))
|
||||||
} else {
|
} else {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
|
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
|
||||||
|
@ -313,47 +308,48 @@ impl Into<Result<()>> for ImapResponse {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_imap_response() {
|
fn test_imap_response() {
|
||||||
assert_eq!(ImapResponse::try_from("M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n").unwrap(), ImapResponse::No(ResponseCode::Alert("Invalid mailbox name: Name must not have '/' characters".to_string())));
|
assert_eq!(ImapResponse::try_from(&b"M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n"[..]).unwrap(), ImapResponse::No(ResponseCode::Alert("Invalid mailbox name: Name must not have '/' characters".to_string())));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::iter::DoubleEndedIterator for ImapLineIterator<'a> {
|
impl<'a> std::iter::DoubleEndedIterator for ImapLineIterator<'a> {
|
||||||
fn next_back(&mut self) -> Option<Self::Item> {
|
fn next_back(&mut self) -> Option<Self::Item> {
|
||||||
if self.slice.is_empty() {
|
if self.slice.is_empty() {
|
||||||
None
|
None
|
||||||
} else if let Some(pos) = self.slice.rfind("\r\n") {
|
} else if let Some(pos) = self.slice.rfind(b"\r\n") {
|
||||||
if self.slice[..pos].is_empty() {
|
if self.slice.get(..pos).unwrap_or_default().is_empty() {
|
||||||
self.slice = &self.slice[..pos];
|
self.slice = self.slice.get(..pos).unwrap_or_default();
|
||||||
None
|
None
|
||||||
} else if let Some(prev_pos) = self.slice[..pos].rfind("\r\n") {
|
} else if let Some(prev_pos) = self.slice.get(..pos).unwrap_or_default().rfind(b"\r\n")
|
||||||
let ret = &self.slice[prev_pos + 2..pos + 2];
|
{
|
||||||
self.slice = &self.slice[..prev_pos + 2];
|
let ret = self.slice.get(prev_pos + 2..pos + 2).unwrap_or_default();
|
||||||
|
self.slice = self.slice.get(..prev_pos + 2).unwrap_or_default();
|
||||||
Some(ret)
|
Some(ret)
|
||||||
} else {
|
} else {
|
||||||
let ret = self.slice;
|
let ret = self.slice;
|
||||||
self.slice = &self.slice[ret.len()..];
|
self.slice = self.slice.get(ret.len()..).unwrap_or_default();
|
||||||
Some(ret)
|
Some(ret)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let ret = self.slice;
|
let ret = self.slice;
|
||||||
self.slice = &self.slice[ret.len()..];
|
self.slice = self.slice.get(ret.len()..).unwrap_or_default();
|
||||||
Some(ret)
|
Some(ret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for ImapLineIterator<'a> {
|
impl<'a> Iterator for ImapLineIterator<'a> {
|
||||||
type Item = &'a str;
|
type Item = &'a [u8];
|
||||||
|
|
||||||
fn next(&mut self) -> Option<&'a str> {
|
fn next(&mut self) -> Option<&'a [u8]> {
|
||||||
if self.slice.is_empty() {
|
if self.slice.is_empty() {
|
||||||
None
|
None
|
||||||
} else if let Some(pos) = self.slice.find("\r\n") {
|
} else if let Some(pos) = self.slice.find(b"\r\n") {
|
||||||
let ret = &self.slice[..pos + 2];
|
let ret = self.slice.get(..pos + 2).unwrap_or_default();
|
||||||
self.slice = &self.slice[pos + 2..];
|
self.slice = self.slice.get(pos + 2..).unwrap_or_default();
|
||||||
Some(ret)
|
Some(ret)
|
||||||
} else {
|
} else {
|
||||||
let ret = self.slice;
|
let ret = self.slice;
|
||||||
self.slice = &self.slice[ret.len()..];
|
self.slice = self.slice.get(ret.len()..).unwrap_or_default();
|
||||||
Some(ret)
|
Some(ret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -363,7 +359,7 @@ pub trait ImapLineSplit {
|
||||||
fn split_rn(&self) -> ImapLineIterator;
|
fn split_rn(&self) -> ImapLineIterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImapLineSplit for str {
|
impl ImapLineSplit for [u8] {
|
||||||
fn split_rn(&self) -> ImapLineIterator {
|
fn split_rn(&self) -> ImapLineIterator {
|
||||||
ImapLineIterator { slice: self }
|
ImapLineIterator { slice: self }
|
||||||
}
|
}
|
||||||
|
@ -416,7 +412,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
|
||||||
let separator: u8 = separator[0];
|
let separator: u8 = separator[0];
|
||||||
let mut f = ImapMailbox::default();
|
let mut f = ImapMailbox::default();
|
||||||
f.no_select = false;
|
f.no_select = false;
|
||||||
f.is_subscribed = path == "INBOX";
|
f.is_subscribed = path.eq_ignore_ascii_case("INBOX");
|
||||||
for p in properties.split(|&b| b == b' ') {
|
for p in properties.split(|&b| b == b' ') {
|
||||||
if p.eq_ignore_ascii_case(b"\\NoSelect") || p.eq_ignore_ascii_case(b"\\NonExistent")
|
if p.eq_ignore_ascii_case(b"\\NoSelect") || p.eq_ignore_ascii_case(b"\\NonExistent")
|
||||||
{
|
{
|
||||||
|
@ -431,7 +427,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
|
||||||
let _ = f.set_special_usage(SpecialUsageMailbox::Drafts);
|
let _ = f.set_special_usage(SpecialUsageMailbox::Drafts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f.imap_path = path.into();
|
f.imap_path = path.to_string();
|
||||||
f.hash = get_path_hash!(&f.imap_path);
|
f.hash = get_path_hash!(&f.imap_path);
|
||||||
f.path = if separator == b'/' {
|
f.path = if separator == b'/' {
|
||||||
f.imap_path.clone()
|
f.imap_path.clone()
|
||||||
|
@ -462,26 +458,27 @@ pub struct FetchResponse<'a> {
|
||||||
pub envelope: Option<Envelope>,
|
pub envelope: Option<Envelope>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
pub fn fetch_response(input: &[u8]) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
macro_rules! should_start_with {
|
macro_rules! should_start_with {
|
||||||
($input:expr, $tag:literal) => {
|
($input:expr, $tag:literal) => {
|
||||||
if !$input.starts_with($tag) {
|
if !$input.starts_with($tag) {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Expected `{}` but got `{:.50}`",
|
"Expected `{}` but got `{:.50}`",
|
||||||
$tag, &$input
|
String::from_utf8_lossy($tag),
|
||||||
|
String::from_utf8_lossy(&$input)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
should_start_with!(input, "* ");
|
should_start_with!(input, b"* ");
|
||||||
|
|
||||||
let mut i = "* ".len();
|
let mut i = b"* ".len();
|
||||||
macro_rules! bounds {
|
macro_rules! bounds {
|
||||||
() => {
|
() => {
|
||||||
if i == input.len() {
|
if i == input.len() {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Expected more input. Got: `{:.50}`",
|
"Expected more input. Got: `{:.50}`",
|
||||||
input
|
String::from_utf8_lossy(&input)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -494,13 +491,13 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
|
|
||||||
macro_rules! eat_whitespace {
|
macro_rules! eat_whitespace {
|
||||||
() => {
|
() => {
|
||||||
while (input.as_bytes()[i] as char).is_whitespace() {
|
while (input[i] as char).is_whitespace() {
|
||||||
i += 1;
|
i += 1;
|
||||||
bounds!();
|
bounds!();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(break) => {
|
(break) => {
|
||||||
while (input.as_bytes()[i] as char).is_whitespace() {
|
while (input[i] as char).is_whitespace() {
|
||||||
i += 1;
|
i += 1;
|
||||||
bounds!(break);
|
bounds!(break);
|
||||||
}
|
}
|
||||||
|
@ -516,8 +513,8 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
envelope: None,
|
envelope: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
while input.as_bytes()[i].is_ascii_digit() {
|
while input[i].is_ascii_digit() {
|
||||||
let b: u8 = input.as_bytes()[i] - 0x30;
|
let b: u8 = input[i] - 0x30;
|
||||||
ret.message_sequence_number *= 10;
|
ret.message_sequence_number *= 10;
|
||||||
ret.message_sequence_number += b as MessageSequenceNumber;
|
ret.message_sequence_number += b as MessageSequenceNumber;
|
||||||
i += 1;
|
i += 1;
|
||||||
|
@ -525,18 +522,17 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
eat_whitespace!();
|
eat_whitespace!();
|
||||||
should_start_with!(input[i..], "FETCH (");
|
should_start_with!(&input[i..], b"FETCH (");
|
||||||
i += "FETCH (".len();
|
i += b"FETCH (".len();
|
||||||
let mut has_attachments = false;
|
let mut has_attachments = false;
|
||||||
while i < input.len() {
|
while i < input.len() {
|
||||||
eat_whitespace!(break);
|
eat_whitespace!(break);
|
||||||
bounds!(break);
|
bounds!(break);
|
||||||
|
|
||||||
if input[i..].starts_with("UID ") {
|
if input[i..].starts_with(b"UID ") {
|
||||||
i += "UID ".len();
|
i += b"UID ".len();
|
||||||
if let Ok((rest, uid)) = take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(
|
if let Ok((rest, uid)) =
|
||||||
is_digit,
|
take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(is_digit)(&input[i..])
|
||||||
)(input[i..].as_bytes())
|
|
||||||
{
|
{
|
||||||
i += input.len() - i - rest.len();
|
i += input.len() - i - rest.len();
|
||||||
ret.uid =
|
ret.uid =
|
||||||
|
@ -544,25 +540,24 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
} else {
|
} else {
|
||||||
return debug!(Err(MeliError::new(format!(
|
return debug!(Err(MeliError::new(format!(
|
||||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||||
input
|
String::from_utf8_lossy(&input)
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
} else if input[i..].starts_with("FLAGS (") {
|
} else if input[i..].starts_with(b"FLAGS (") {
|
||||||
i += "FLAGS (".len();
|
i += b"FLAGS (".len();
|
||||||
if let Ok((rest, flags)) = flags(&input[i..]) {
|
if let Ok((rest, flags)) = flags(&input[i..]) {
|
||||||
ret.flags = Some(flags);
|
ret.flags = Some(flags);
|
||||||
i += (input.len() - i - rest.len()) + 1;
|
i += (input.len() - i - rest.len()) + 1;
|
||||||
} else {
|
} else {
|
||||||
return debug!(Err(MeliError::new(format!(
|
return debug!(Err(MeliError::new(format!(
|
||||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||||
input
|
String::from_utf8_lossy(&input)
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
} else if input[i..].starts_with("MODSEQ (") {
|
} else if input[i..].starts_with(b"MODSEQ (") {
|
||||||
i += "MODSEQ (".len();
|
i += b"MODSEQ (".len();
|
||||||
if let Ok((rest, modseq)) = take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(
|
if let Ok((rest, modseq)) =
|
||||||
is_digit,
|
take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(is_digit)(&input[i..])
|
||||||
)(input[i..].as_bytes())
|
|
||||||
{
|
{
|
||||||
i += (input.len() - i - rest.len()) + 1;
|
i += (input.len() - i - rest.len()) + 1;
|
||||||
ret.modseq = u64::from_str(to_str!(modseq))
|
ret.modseq = u64::from_str(to_str!(modseq))
|
||||||
|
@ -572,11 +567,11 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
} else {
|
} else {
|
||||||
return debug!(Err(MeliError::new(format!(
|
return debug!(Err(MeliError::new(format!(
|
||||||
"Unexpected input while parsing MODSEQ in UID FETCH response. Got: `{:.40}`",
|
"Unexpected input while parsing MODSEQ in UID FETCH response. Got: `{:.40}`",
|
||||||
input
|
String::from_utf8_lossy(&input)
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
} else if input[i..].starts_with("RFC822 {") {
|
} else if input[i..].starts_with(b"RFC822 {") {
|
||||||
i += "RFC822 ".len();
|
i += b"RFC822 ".len();
|
||||||
if let Ok((rest, body)) =
|
if let Ok((rest, body)) =
|
||||||
length_data::<_, _, (&[u8], nom::error::ErrorKind), _>(delimited(
|
length_data::<_, _, (&[u8], nom::error::ErrorKind), _>(delimited(
|
||||||
tag("{"),
|
tag("{"),
|
||||||
|
@ -584,41 +579,41 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
tag("}\r\n"),
|
tag("}\r\n"),
|
||||||
))(input[i..].as_bytes())
|
))(&input[i..])
|
||||||
{
|
{
|
||||||
ret.body = Some(body);
|
ret.body = Some(body);
|
||||||
i += input.len() - i - rest.len();
|
i += input.len() - i - rest.len();
|
||||||
} else {
|
} else {
|
||||||
return debug!(Err(MeliError::new(format!(
|
return debug!(Err(MeliError::new(format!(
|
||||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||||
input
|
String::from_utf8_lossy(&input)
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
} else if input[i..].starts_with("ENVELOPE (") {
|
} else if input[i..].starts_with(b"ENVELOPE (") {
|
||||||
i += "ENVELOPE ".len();
|
i += b"ENVELOPE ".len();
|
||||||
if let Ok((rest, envelope)) = envelope(input[i..].as_bytes()) {
|
if let Ok((rest, envelope)) = envelope(&input[i..]) {
|
||||||
ret.envelope = Some(envelope);
|
ret.envelope = Some(envelope);
|
||||||
i += input.len() - i - rest.len();
|
i += input.len() - i - rest.len();
|
||||||
} else {
|
} else {
|
||||||
return debug!(Err(MeliError::new(format!(
|
return debug!(Err(MeliError::new(format!(
|
||||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||||
&input[i..]
|
String::from_utf8_lossy(&input[i..])
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
} else if input[i..].starts_with("BODYSTRUCTURE ") {
|
} else if input[i..].starts_with(b"BODYSTRUCTURE ") {
|
||||||
i += "BODYSTRUCTURE ".len();
|
i += b"BODYSTRUCTURE ".len();
|
||||||
let mut struct_ptr = i;
|
let mut struct_ptr = i;
|
||||||
let mut parenth_level = 0;
|
let mut parenth_level = 0;
|
||||||
let mut inside_quote = false;
|
let mut inside_quote = false;
|
||||||
while struct_ptr != input.len() {
|
while struct_ptr != input.len() {
|
||||||
if !inside_quote {
|
if !inside_quote {
|
||||||
if input.as_bytes()[struct_ptr] == b'(' {
|
if input[struct_ptr] == b'(' {
|
||||||
parenth_level += 1;
|
parenth_level += 1;
|
||||||
} else if input.as_bytes()[struct_ptr] == b')' {
|
} else if input[struct_ptr] == b')' {
|
||||||
if parenth_level == 0 {
|
if parenth_level == 0 {
|
||||||
return debug!(Err(MeliError::new(format!(
|
return debug!(Err(MeliError::new(format!(
|
||||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||||
&input[struct_ptr..]
|
String::from_utf8_lossy(&input[struct_ptr..])
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
parenth_level -= 1;
|
parenth_level -= 1;
|
||||||
|
@ -626,30 +621,30 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
struct_ptr += 1;
|
struct_ptr += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if input.as_bytes()[struct_ptr] == b'"' {
|
} else if input[struct_ptr] == b'"' {
|
||||||
inside_quote = true;
|
inside_quote = true;
|
||||||
}
|
}
|
||||||
} else if input.as_bytes()[struct_ptr] == b'\"'
|
} else if input[struct_ptr] == b'\"'
|
||||||
&& (struct_ptr == 0 || (input.as_bytes()[struct_ptr - 1] != b'\\'))
|
&& (struct_ptr == 0 || (input[struct_ptr - 1] != b'\\'))
|
||||||
{
|
{
|
||||||
inside_quote = false;
|
inside_quote = false;
|
||||||
}
|
}
|
||||||
struct_ptr += 1;
|
struct_ptr += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
has_attachments = bodystructure_has_attachments(&input.as_bytes()[i..struct_ptr]);
|
has_attachments = bodystructure_has_attachments(&input[i..struct_ptr]);
|
||||||
i = struct_ptr;
|
i = struct_ptr;
|
||||||
} else if input[i..].starts_with(")\r\n") {
|
} else if input[i..].starts_with(b")\r\n") {
|
||||||
i += ")\r\n".len();
|
i += b")\r\n".len();
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
"Got unexpected token while parsing UID FETCH response:\n`{}`\n",
|
"Got unexpected token while parsing UID FETCH response:\n`{}`\n",
|
||||||
input
|
String::from_utf8_lossy(&input)
|
||||||
);
|
);
|
||||||
return debug!(Err(MeliError::new(format!(
|
return debug!(Err(MeliError::new(format!(
|
||||||
"Got unexpected token while parsing UID FETCH response: `{:.40}`",
|
"Got unexpected token while parsing UID FETCH response: `{:.40}`",
|
||||||
&input[i..]
|
String::from_utf8_lossy(&input[i..])
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -661,11 +656,11 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
||||||
Ok((&input[i..], ret, None))
|
Ok((&input[i..], ret, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_responses(mut input: &str) -> ImapParseResult<Vec<FetchResponse<'_>>> {
|
pub fn fetch_responses(mut input: &[u8]) -> ImapParseResult<Vec<FetchResponse<'_>>> {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
let mut alert: Option<Alert> = None;
|
let mut alert: Option<Alert> = None;
|
||||||
|
|
||||||
while input.starts_with("* ") {
|
while input.starts_with(b"* ") {
|
||||||
let next_response = fetch_response(input);
|
let next_response = fetch_response(input);
|
||||||
match next_response {
|
match next_response {
|
||||||
Ok((rest, el, el_alert)) => {
|
Ok((rest, el, el_alert)) => {
|
||||||
|
@ -683,7 +678,8 @@ pub fn fetch_responses(mut input: &str) -> ImapParseResult<Vec<FetchResponse<'_>
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Unexpected input while parsing UID FETCH responses: `{:.40}`, {}",
|
"Unexpected input while parsing UID FETCH responses: `{:.40}`, {}",
|
||||||
input, err
|
String::from_utf8_lossy(&input),
|
||||||
|
err
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -694,7 +690,7 @@ pub fn fetch_responses(mut input: &str) -> ImapParseResult<Vec<FetchResponse<'_>
|
||||||
} else {
|
} else {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"310Unexpected input while parsing UID FETCH responses: `{:.40}`",
|
"310Unexpected input while parsing UID FETCH responses: `{:.40}`",
|
||||||
input
|
String::from_utf8_lossy(&input)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -842,27 +838,31 @@ pub enum UntaggedResponse<'s> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn untagged_responses(input: &str) -> ImapParseResult<Option<UntaggedResponse<'_>>> {
|
pub fn untagged_responses(input: &[u8]) -> ImapParseResult<Option<UntaggedResponse<'_>>> {
|
||||||
let orig_input = input;
|
let orig_input = input;
|
||||||
let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>("* ")(input)?;
|
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(b"* ")(input)?;
|
||||||
let (input, num) = map_res::<_, _, _, (&str, nom::error::ErrorKind), _, _, _>(digit1, |s| {
|
let (input, num) = map_res::<_, _, _, (&[u8], nom::error::ErrorKind), _, _, _>(digit1, |s| {
|
||||||
ImapNum::from_str(s)
|
ImapNum::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
})(input)?;
|
})(input)?;
|
||||||
let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>(" ")(input)?;
|
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(b" ")(input)?;
|
||||||
let (input, _tag) = take_until::<_, &str, (&str, nom::error::ErrorKind)>("\r\n")(input)?;
|
let (input, _tag) =
|
||||||
let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>("\r\n")(input)?;
|
take_until::<_, &[u8], (&[u8], nom::error::ErrorKind)>(&b"\r\n"[..])(input)?;
|
||||||
|
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(b"\r\n")(input)?;
|
||||||
debug!("Parse untagged response from {:?}", orig_input);
|
debug!("Parse untagged response from {:?}", orig_input);
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
{
|
{
|
||||||
use UntaggedResponse::*;
|
use UntaggedResponse::*;
|
||||||
match _tag {
|
match _tag {
|
||||||
"EXPUNGE" => Some(Expunge(num)),
|
b"EXPUNGE" => Some(Expunge(num)),
|
||||||
"EXISTS" => Some(Exists(num)),
|
b"EXISTS" => Some(Exists(num)),
|
||||||
"RECENT" => Some(Recent(num)),
|
b"RECENT" => Some(Recent(num)),
|
||||||
_ if _tag.starts_with("FETCH ") => Some(Fetch(fetch_response(orig_input)?.1)),
|
_ if _tag.starts_with(b"FETCH ") => Some(Fetch(fetch_response(orig_input)?.1)),
|
||||||
_ => {
|
_ => {
|
||||||
debug!("unknown untagged_response: {}", _tag);
|
debug!(
|
||||||
|
"unknown untagged_response: {}",
|
||||||
|
String::from_utf8_lossy(&_tag)
|
||||||
|
);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -876,14 +876,14 @@ fn test_untagged_responses() {
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use UntaggedResponse::*;
|
use UntaggedResponse::*;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
untagged_responses("* 2 EXISTS\r\n")
|
untagged_responses(b"* 2 EXISTS\r\n")
|
||||||
.map(|(_, v, _)| v)
|
.map(|(_, v, _)| v)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Exists(2)
|
Exists(2)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
untagged_responses("* 1079 FETCH (UID 1103 MODSEQ (1365) FLAGS (\\Seen))\r\n")
|
untagged_responses(b"* 1079 FETCH (UID 1103 MODSEQ (1365) FLAGS (\\Seen))\r\n")
|
||||||
.map(|(_, v, _)| v)
|
.map(|(_, v, _)| v)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -897,7 +897,7 @@ fn test_untagged_responses() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
untagged_responses("* 1 FETCH (FLAGS (\\Seen))\r\n")
|
untagged_responses(b"* 1 FETCH (FLAGS (\\Seen))\r\n")
|
||||||
.map(|(_, v, _)| v)
|
.map(|(_, v, _)| v)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
@ -1001,59 +1001,70 @@ pub struct SelectResponse {
|
||||||
* * OK [UIDVALIDITY 1554422056] UIDs valid
|
* * OK [UIDVALIDITY 1554422056] UIDs valid
|
||||||
* * OK [UIDNEXT 50] Predicted next UID
|
* * OK [UIDNEXT 50] Predicted next UID
|
||||||
*/
|
*/
|
||||||
pub fn select_response(input: &str) -> Result<SelectResponse> {
|
pub fn select_response(input: &[u8]) -> Result<SelectResponse> {
|
||||||
if input.contains("* OK") {
|
if input.contains_subsequence(b"* OK") {
|
||||||
let mut ret = SelectResponse::default();
|
let mut ret = SelectResponse::default();
|
||||||
for l in input.split_rn() {
|
for l in input.split_rn() {
|
||||||
if l.starts_with("* ") && l.ends_with(" EXISTS\r\n") {
|
if l.starts_with(b"* ") && l.ends_with(b" EXISTS\r\n") {
|
||||||
ret.exists = ImapNum::from_str(&l["* ".len()..l.len() - " EXISTS\r\n".len()])?;
|
ret.exists = ImapNum::from_str(&String::from_utf8_lossy(
|
||||||
} else if l.starts_with("* ") && l.ends_with(" RECENT\r\n") {
|
&l[b"* ".len()..l.len() - b" EXISTS\r\n".len()],
|
||||||
ret.recent = ImapNum::from_str(&l["* ".len()..l.len() - " RECENT\r\n".len()])?;
|
))?;
|
||||||
} else if l.starts_with("* FLAGS (") {
|
} else if l.starts_with(b"* ") && l.ends_with(b" RECENT\r\n") {
|
||||||
ret.flags = flags(&l["* FLAGS (".len()..l.len() - ")".len()]).map(|(_, v)| v)?;
|
ret.recent = ImapNum::from_str(&String::from_utf8_lossy(
|
||||||
} else if l.starts_with("* OK [UNSEEN ") {
|
&l[b"* ".len()..l.len() - b" RECENT\r\n".len()],
|
||||||
ret.unseen = MessageSequenceNumber::from_str(
|
))?;
|
||||||
&l["* OK [UNSEEN ".len()..l.find(']').unwrap()],
|
} else if l.starts_with(b"* FLAGS (") {
|
||||||
)?;
|
ret.flags = flags(&l[b"* FLAGS (".len()..l.len() - b")".len()]).map(|(_, v)| v)?;
|
||||||
} else if l.starts_with("* OK [UIDVALIDITY ") {
|
} else if l.starts_with(b"* OK [UNSEEN ") {
|
||||||
ret.uidvalidity =
|
ret.unseen = MessageSequenceNumber::from_str(&String::from_utf8_lossy(
|
||||||
UIDVALIDITY::from_str(&l["* OK [UIDVALIDITY ".len()..l.find(']').unwrap()])?;
|
&l[b"* OK [UNSEEN ".len()..l.find(b"]").unwrap()],
|
||||||
} else if l.starts_with("* OK [UIDNEXT ") {
|
))?;
|
||||||
ret.uidnext = UID::from_str(&l["* OK [UIDNEXT ".len()..l.find(']').unwrap()])?;
|
} else if l.starts_with(b"* OK [UIDVALIDITY ") {
|
||||||
} else if l.starts_with("* OK [PERMANENTFLAGS (") {
|
ret.uidvalidity = UIDVALIDITY::from_str(&String::from_utf8_lossy(
|
||||||
|
&l[b"* OK [UIDVALIDITY ".len()..l.find(b"]").unwrap()],
|
||||||
|
))?;
|
||||||
|
} else if l.starts_with(b"* OK [UIDNEXT ") {
|
||||||
|
ret.uidnext = UID::from_str(&String::from_utf8_lossy(
|
||||||
|
&l[b"* OK [UIDNEXT ".len()..l.find(b"]").unwrap()],
|
||||||
|
))?;
|
||||||
|
} else if l.starts_with(b"* OK [PERMANENTFLAGS (") {
|
||||||
ret.permanentflags =
|
ret.permanentflags =
|
||||||
flags(&l["* OK [PERMANENTFLAGS (".len()..l.find(')').unwrap()])
|
flags(&l[b"* OK [PERMANENTFLAGS (".len()..l.find(b")").unwrap()])
|
||||||
.map(|(_, v)| v)?;
|
.map(|(_, v)| v)?;
|
||||||
ret.can_create_flags = l.contains("\\*");
|
ret.can_create_flags = l.contains_subsequence(b"\\*");
|
||||||
} else if l.contains("OK [READ-WRITE]") {
|
} else if l.contains_subsequence(b"OK [READ-WRITE]" as &[u8]) {
|
||||||
ret.read_only = false;
|
ret.read_only = false;
|
||||||
} else if l.contains("OK [READ-ONLY]") {
|
} else if l.contains_subsequence(b"OK [READ-ONLY]") {
|
||||||
ret.read_only = true;
|
ret.read_only = true;
|
||||||
} else if l.starts_with("* OK [HIGHESTMODSEQ ") {
|
} else if l.starts_with(b"* OK [HIGHESTMODSEQ ") {
|
||||||
let res: IResult<&str, &str> = take_until("]")(&l["* OK [HIGHESTMODSEQ ".len()..]);
|
let res: IResult<&[u8], &[u8]> =
|
||||||
|
take_until(&b"]"[..])(&l[b"* OK [HIGHESTMODSEQ ".len()..]);
|
||||||
let (_, highestmodseq) = res?;
|
let (_, highestmodseq) = res?;
|
||||||
ret.highestmodseq = Some(
|
ret.highestmodseq = Some(
|
||||||
std::num::NonZeroU64::new(u64::from_str(&highestmodseq)?)
|
std::num::NonZeroU64::new(u64::from_str(&String::from_utf8_lossy(
|
||||||
.map(|u| Ok(ModSequence(u)))
|
&highestmodseq,
|
||||||
.unwrap_or(Err(())),
|
))?)
|
||||||
|
.map(|u| Ok(ModSequence(u)))
|
||||||
|
.unwrap_or(Err(())),
|
||||||
);
|
);
|
||||||
} else if l.starts_with("* OK [NOMODSEQ") {
|
} else if l.starts_with(b"* OK [NOMODSEQ") {
|
||||||
ret.highestmodseq = Some(Err(()));
|
ret.highestmodseq = Some(Err(()));
|
||||||
} else if !l.is_empty() {
|
} else if !l.is_empty() {
|
||||||
debug!("select response: {}", l);
|
debug!("select response: {}", String::from_utf8_lossy(&l));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
} else {
|
} else {
|
||||||
debug!("BAD/NO response in select: {}", input);
|
let ret = String::from_utf8_lossy(&input).to_string();
|
||||||
Err(MeliError::new(input.to_string()))
|
debug!("BAD/NO response in select: {}", &ret);
|
||||||
|
Err(MeliError::new(ret))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_select_response() {
|
fn test_select_response() {
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
let r = "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\*)] Flags permitted.\r\n* 45 EXISTS\r\n* 0 RECENT\r\n* OK [UNSEEN 16] First unseen.\r\n* OK [UIDVALIDITY 1554422056] UIDs valid\r\n* OK [UIDNEXT 50] Predicted next UID\r\n";
|
let r = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\*)] Flags permitted.\r\n* 45 EXISTS\r\n* 0 RECENT\r\n* OK [UNSEEN 16] First unseen.\r\n* OK [UIDVALIDITY 1554422056] UIDs valid\r\n* OK [UIDNEXT 50] Predicted next UID\r\n";
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
select_response(r).expect("Could not parse IMAP select response"),
|
select_response(r).expect("Could not parse IMAP select response"),
|
||||||
|
@ -1076,7 +1087,7 @@ fn test_select_response() {
|
||||||
highestmodseq: None
|
highestmodseq: None
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let r = "* 172 EXISTS\r\n* 1 RECENT\r\n* OK [UNSEEN 12] Message 12 is first unseen\r\n* OK [UIDVALIDITY 3857529045] UIDs valid\r\n* OK [UIDNEXT 4392] Predicted next UID\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited\r\n* OK [HIGHESTMODSEQ 715194045007]\r\n* A142 OK [READ-WRITE] SELECT completed\r\n";
|
let r = b"* 172 EXISTS\r\n* 1 RECENT\r\n* OK [UNSEEN 12] Message 12 is first unseen\r\n* OK [UIDVALIDITY 3857529045] UIDs valid\r\n* OK [UIDNEXT 4392] Predicted next UID\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited\r\n* OK [HIGHESTMODSEQ 715194045007]\r\n* A142 OK [READ-WRITE] SELECT completed\r\n";
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
select_response(r).expect("Could not parse IMAP select response"),
|
select_response(r).expect("Could not parse IMAP select response"),
|
||||||
|
@ -1098,7 +1109,7 @@ fn test_select_response() {
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let r = "* 172 EXISTS\r\n* 1 RECENT\r\n* OK [UNSEEN 12] Message 12 is first unseen\r\n* OK [UIDVALIDITY 3857529045] UIDs valid\r\n* OK [UIDNEXT 4392] Predicted next UID\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited\r\n* OK [NOMODSEQ] Sorry, this mailbox format doesn't support modsequences\r\n* A142 OK [READ-WRITE] SELECT completed\r\n";
|
let r = b"* 172 EXISTS\r\n* 1 RECENT\r\n* OK [UNSEEN 12] Message 12 is first unseen\r\n* OK [UIDVALIDITY 3857529045] UIDs valid\r\n* OK [UIDNEXT 4392] Predicted next UID\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited\r\n* OK [NOMODSEQ] Sorry, this mailbox format doesn't support modsequences\r\n* A142 OK [READ-WRITE] SELECT completed\r\n";
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
select_response(r).expect("Could not parse IMAP select response"),
|
select_response(r).expect("Could not parse IMAP select response"),
|
||||||
|
@ -1120,41 +1131,41 @@ fn test_select_response() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flags(input: &str) -> IResult<&str, (Flag, Vec<String>)> {
|
pub fn flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec<String>)> {
|
||||||
let mut ret = Flag::default();
|
let mut ret = Flag::default();
|
||||||
let mut keywords = Vec::new();
|
let mut keywords = Vec::new();
|
||||||
|
|
||||||
let mut input = input;
|
let mut input = input;
|
||||||
while !input.starts_with(')') && !input.is_empty() {
|
while !input.starts_with(b")") && !input.is_empty() {
|
||||||
if input.starts_with('\\') {
|
if input.starts_with(b"\\") {
|
||||||
input = &input[1..];
|
input = &input[1..];
|
||||||
}
|
}
|
||||||
let mut match_end = 0;
|
let mut match_end = 0;
|
||||||
while match_end < input.len() {
|
while match_end < input.len() {
|
||||||
if input[match_end..].starts_with(' ') || input[match_end..].starts_with(')') {
|
if input[match_end..].starts_with(b" ") || input[match_end..].starts_with(b")") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match_end += 1;
|
match_end += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
match &input[..match_end] {
|
match &input[..match_end] {
|
||||||
"Answered" => {
|
b"Answered" => {
|
||||||
ret.set(Flag::REPLIED, true);
|
ret.set(Flag::REPLIED, true);
|
||||||
}
|
}
|
||||||
"Flagged" => {
|
b"Flagged" => {
|
||||||
ret.set(Flag::FLAGGED, true);
|
ret.set(Flag::FLAGGED, true);
|
||||||
}
|
}
|
||||||
"Deleted" => {
|
b"Deleted" => {
|
||||||
ret.set(Flag::TRASHED, true);
|
ret.set(Flag::TRASHED, true);
|
||||||
}
|
}
|
||||||
"Seen" => {
|
b"Seen" => {
|
||||||
ret.set(Flag::SEEN, true);
|
ret.set(Flag::SEEN, true);
|
||||||
}
|
}
|
||||||
"Draft" => {
|
b"Draft" => {
|
||||||
ret.set(Flag::DRAFT, true);
|
ret.set(Flag::DRAFT, true);
|
||||||
}
|
}
|
||||||
f => {
|
f => {
|
||||||
keywords.push(f.to_string());
|
keywords.push(String::from_utf8_lossy(&f).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input = &input[match_end..];
|
input = &input[match_end..];
|
||||||
|
@ -1164,11 +1175,10 @@ pub fn flags(input: &str) -> IResult<&str, (Flag, Vec<String>)> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec<String>)> {
|
pub fn byte_flags(input: &[u8]) -> IResult<&[u8], (Flag, Vec<String>)> {
|
||||||
let i = unsafe { std::str::from_utf8_unchecked(input) };
|
match flags(input) {
|
||||||
match flags(i) {
|
Ok((rest, ret)) => Ok((rest, ret)),
|
||||||
Ok((rest, ret)) => Ok((rest.as_bytes(), ret)),
|
Err(nom::Err::Error(err)) => Err(nom::Err::Error(err)),
|
||||||
Err(nom::Err::Error(err)) => Err(nom::Err::Error(err.as_bytes())),
|
Err(nom::Err::Failure(err)) => Err(nom::Err::Error(err)),
|
||||||
Err(nom::Err::Failure(err)) => Err(nom::Err::Error(err.as_bytes())),
|
|
||||||
Err(nom::Err::Incomplete(_)) => {
|
Err(nom::Err::Incomplete(_)) => {
|
||||||
Err(nom::Err::Error((input, "byte_flags(): incomplete").into()))
|
Err(nom::Err::Error((input, "byte_flags(): incomplete").into()))
|
||||||
}
|
}
|
||||||
|
@ -1505,12 +1515,12 @@ pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> {
|
||||||
// ; is considered to be INBOX and not an astring.
|
// ; is considered to be INBOX and not an astring.
|
||||||
// ; Refer to section 5.1 for further
|
// ; Refer to section 5.1 for further
|
||||||
// ; semantic details of mailbox names.
|
// ; semantic details of mailbox names.
|
||||||
pub fn mailbox_token<'i>(input: &'i [u8]) -> IResult<&'i [u8], &'i str> {
|
pub fn mailbox_token<'i>(input: &'i [u8]) -> IResult<&'i [u8], std::borrow::Cow<'i, str>> {
|
||||||
let (input, astring) = astring_token(input)?;
|
let (input, astring) = astring_token(input)?;
|
||||||
if astring.eq_ignore_ascii_case(b"INBOX") {
|
if astring.eq_ignore_ascii_case(b"INBOX") {
|
||||||
return Ok((input, "INBOX"));
|
return Ok((input, "INBOX".into()));
|
||||||
}
|
}
|
||||||
Ok((input, to_str!(astring)))
|
Ok((input, String::from_utf8_lossy(astring)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// astring = 1*ASTRING-CHAR / string
|
// astring = 1*ASTRING-CHAR / string
|
||||||
|
|
|
@ -33,7 +33,7 @@ use crate::error::*;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
impl ImapConnection {
|
impl ImapConnection {
|
||||||
pub async fn process_untagged(&mut self, line: &str) -> Result<bool> {
|
pub async fn process_untagged(&mut self, line: &[u8]) -> Result<bool> {
|
||||||
macro_rules! try_fail {
|
macro_rules! try_fail {
|
||||||
($mailbox_hash: expr, $($result:expr)+) => {
|
($mailbox_hash: expr, $($result:expr)+) => {
|
||||||
$(if let Err(err) = $result {
|
$(if let Err(err) = $result {
|
||||||
|
@ -59,7 +59,7 @@ impl ImapConnection {
|
||||||
let mut cache_handle = super::cache::DefaultCache::get(self.uid_store.clone())?;
|
let mut cache_handle = super::cache::DefaultCache::get(self.uid_store.clone())?;
|
||||||
#[cfg(feature = "sqlite3")]
|
#[cfg(feature = "sqlite3")]
|
||||||
let mut cache_handle = super::cache::Sqlite3Cache::get(self.uid_store.clone())?;
|
let mut cache_handle = super::cache::Sqlite3Cache::get(self.uid_store.clone())?;
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let untagged_response =
|
let untagged_response =
|
||||||
match super::protocol_parser::untagged_responses(line).map(|(_, v, _)| v) {
|
match super::protocol_parser::untagged_responses(line).map(|(_, v, _)| v) {
|
||||||
Ok(None) | Err(_) => {
|
Ok(None) | Err(_) => {
|
||||||
|
@ -233,7 +233,7 @@ impl ImapConnection {
|
||||||
self.send_command(b"UID SEARCH RECENT").await
|
self.send_command(b"UID SEARCH RECENT").await
|
||||||
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
||||||
);
|
);
|
||||||
match super::protocol_parser::search_results_raw(response.as_bytes())
|
match super::protocol_parser::search_results_raw(&response)
|
||||||
.map(|(_, v)| v)
|
.map(|(_, v)| v)
|
||||||
.map_err(MeliError::from)
|
.map_err(MeliError::from)
|
||||||
{
|
{
|
||||||
|
@ -339,7 +339,7 @@ impl ImapConnection {
|
||||||
debug!(
|
debug!(
|
||||||
"UID SEARCH RECENT err: {}\nresp: {}",
|
"UID SEARCH RECENT err: {}\nresp: {}",
|
||||||
e.to_string(),
|
e.to_string(),
|
||||||
&response
|
to_str!(&response)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,9 +381,9 @@ impl ImapConnection {
|
||||||
).await
|
).await
|
||||||
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
||||||
);
|
);
|
||||||
debug!(&response);
|
debug!(to_str!(&response));
|
||||||
match super::protocol_parser::search_results(
|
match super::protocol_parser::search_results(
|
||||||
response.split_rn().next().unwrap_or("").as_bytes(),
|
response.split_rn().next().unwrap_or(b""),
|
||||||
)
|
)
|
||||||
.map(|(_, v)| v)
|
.map(|(_, v)| v)
|
||||||
{
|
{
|
||||||
|
@ -392,7 +392,7 @@ impl ImapConnection {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(&response);
|
debug!(to_str!(&response));
|
||||||
debug!(e);
|
debug!(e);
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,12 +74,12 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mailbox_hash = mailbox.hash();
|
let mailbox_hash = mailbox.hash();
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let select_response = conn
|
let select_response = conn
|
||||||
.select_mailbox(mailbox_hash, &mut response, true)
|
.select_mailbox(mailbox_hash, &mut response, true)
|
||||||
.await?
|
.await?
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!("select response {}", &response);
|
debug!("select response {}", String::from_utf8_lossy(&response));
|
||||||
{
|
{
|
||||||
let mut uidvalidities = uid_store.uidvalidity.lock().unwrap();
|
let mut uidvalidities = uid_store.uidvalidity.lock().unwrap();
|
||||||
|
|
||||||
|
@ -153,14 +153,14 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
}
|
}
|
||||||
watch = now;
|
watch = now;
|
||||||
}
|
}
|
||||||
if to_str!(&line)
|
if line
|
||||||
.split_rn()
|
.split_rn()
|
||||||
.filter(|l| {
|
.filter(|l| {
|
||||||
!l.starts_with("+ ")
|
!l.starts_with(b"+ ")
|
||||||
&& !l.starts_with("* ok")
|
&& !l.starts_with(b"* ok")
|
||||||
&& !l.starts_with("* ok")
|
&& !l.starts_with(b"* ok")
|
||||||
&& !l.starts_with("* Ok")
|
&& !l.starts_with(b"* Ok")
|
||||||
&& !l.starts_with("* OK")
|
&& !l.starts_with(b"* OK")
|
||||||
})
|
})
|
||||||
.count()
|
.count()
|
||||||
== 0
|
== 0
|
||||||
|
@ -173,13 +173,13 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
.conn
|
.conn
|
||||||
.read_response(&mut response, RequiredResponses::empty())
|
.read_response(&mut response, RequiredResponses::empty())
|
||||||
.await?;
|
.await?;
|
||||||
for l in to_str!(&line).split_rn() {
|
for l in line.split_rn() {
|
||||||
debug!("process_untagged {:?}", &l);
|
debug!("process_untagged {:?}", &l);
|
||||||
if l.starts_with("+ ")
|
if l.starts_with(b"+ ")
|
||||||
|| l.starts_with("* ok")
|
|| l.starts_with(b"* ok")
|
||||||
|| l.starts_with("* ok")
|
|| l.starts_with(b"* ok")
|
||||||
|| l.starts_with("* Ok")
|
|| l.starts_with(b"* Ok")
|
||||||
|| l.starts_with("* OK")
|
|| l.starts_with(b"* OK")
|
||||||
{
|
{
|
||||||
debug!("ignore continuation mark");
|
debug!("ignore continuation mark");
|
||||||
continue;
|
continue;
|
||||||
|
@ -214,7 +214,7 @@ pub async fn examine_updates(
|
||||||
let mut cache_handle = super::cache::DefaultCache::get(uid_store.clone())?;
|
let mut cache_handle = super::cache::DefaultCache::get(uid_store.clone())?;
|
||||||
#[cfg(feature = "sqlite3")]
|
#[cfg(feature = "sqlite3")]
|
||||||
let mut cache_handle = super::cache::Sqlite3Cache::get(uid_store.clone())?;
|
let mut cache_handle = super::cache::Sqlite3Cache::get(uid_store.clone())?;
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = Vec::with_capacity(8 * 1024);
|
||||||
let select_response = conn
|
let select_response = conn
|
||||||
.examine_mailbox(mailbox_hash, &mut response, true)
|
.examine_mailbox(mailbox_hash, &mut response, true)
|
||||||
.await?
|
.await?
|
||||||
|
@ -249,9 +249,12 @@ pub async fn examine_updates(
|
||||||
conn.send_command(b"UID SEARCH RECENT").await?;
|
conn.send_command(b"UID SEARCH RECENT").await?;
|
||||||
conn.read_response(&mut response, RequiredResponses::SEARCH)
|
conn.read_response(&mut response, RequiredResponses::SEARCH)
|
||||||
.await?;
|
.await?;
|
||||||
let v = protocol_parser::search_results(response.as_bytes()).map(|(_, v)| v)?;
|
let v = protocol_parser::search_results(response.as_slice()).map(|(_, v)| v)?;
|
||||||
if v.is_empty() {
|
if v.is_empty() {
|
||||||
debug!("search response was empty: {}", response);
|
debug!(
|
||||||
|
"search response was empty: {}",
|
||||||
|
String::from_utf8_lossy(&response)
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let mut cmd = "UID FETCH ".to_string();
|
let mut cmd = "UID FETCH ".to_string();
|
||||||
|
|
|
@ -177,9 +177,19 @@ macro_rules! is_whitespace {
|
||||||
pub trait BytesExt {
|
pub trait BytesExt {
|
||||||
fn rtrim(&self) -> &Self;
|
fn rtrim(&self) -> &Self;
|
||||||
fn ltrim(&self) -> &Self;
|
fn ltrim(&self) -> &Self;
|
||||||
|
fn trim_start(&self) -> &Self {
|
||||||
|
self.ltrim()
|
||||||
|
}
|
||||||
|
fn trim_end(&self) -> &Self {
|
||||||
|
self.rtrim()
|
||||||
|
}
|
||||||
fn trim(&self) -> &Self;
|
fn trim(&self) -> &Self;
|
||||||
fn find(&self, needle: &[u8]) -> Option<usize>;
|
fn find<T: AsRef<[u8]>>(&self, needle: T) -> Option<usize>;
|
||||||
fn rfind(&self, needle: &[u8]) -> Option<usize>;
|
fn contains_subsequence<T: AsRef<[u8]>>(&self, needle: T) -> bool {
|
||||||
|
self.find(needle.as_ref()).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rfind<T: AsRef<[u8]>>(&self, needle: T) -> Option<usize>;
|
||||||
fn replace(&self, from: &[u8], to: &[u8]) -> Vec<u8>;
|
fn replace(&self, from: &[u8], to: &[u8]) -> Vec<u8>;
|
||||||
fn is_quoted(&self) -> bool;
|
fn is_quoted(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
@ -202,8 +212,10 @@ impl BytesExt for [u8] {
|
||||||
fn trim(&self) -> &[u8] {
|
fn trim(&self) -> &[u8] {
|
||||||
self.rtrim().ltrim()
|
self.rtrim().ltrim()
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/35907071
|
// https://stackoverflow.com/a/35907071
|
||||||
fn find(&self, needle: &[u8]) -> Option<usize> {
|
fn find<T: AsRef<[u8]>>(&self, needle: T) -> Option<usize> {
|
||||||
|
let needle = needle.as_ref();
|
||||||
if needle.is_empty() {
|
if needle.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -211,7 +223,8 @@ impl BytesExt for [u8] {
|
||||||
.position(|window| window == needle)
|
.position(|window| window == needle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rfind(&self, needle: &[u8]) -> Option<usize> {
|
fn rfind<T: AsRef<[u8]>>(&self, needle: T) -> Option<usize> {
|
||||||
|
let needle = needle.as_ref();
|
||||||
if needle.is_empty() {
|
if needle.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue