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
Manos Pitsidianakis 2020-09-16 13:07:26 +03:00
parent 366e557e1c
commit 3618bdcffb
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
8 changed files with 366 additions and 332 deletions

View File

@ -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());

View File

@ -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(

View File

@ -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(),
); );

View File

@ -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 {

View File

@ -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

View File

@ -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);
} }

View File

@ -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();

View File

@ -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;
} }