Browse Source

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 1 year ago
parent
commit
3618bdcffb
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 80
      melib/src/backends/imap.rs
  2. 16
      melib/src/backends/imap/cache/sync.rs
  3. 118
      melib/src/backends/imap/connection.rs
  4. 10
      melib/src/backends/imap/operations.rs
  5. 404
      melib/src/backends/imap/protocol_parser.rs
  6. 14
      melib/src/backends/imap/untagged.rs
  7. 37
      melib/src/backends/imap/watch.rs
  8. 21
      melib/src/email/parser.rs

80
melib/src/backends/imap.rs

@ -44,7 +44,7 @@ use crate::backends::{
use crate::conf::AccountSettings;
use crate::connections::timeout;
use crate::email::*;
use crate::email::{parser::BytesExt, *};
use crate::error::{MeliError, Result, ResultIntoMeliError};
use futures::lock::Mutex as FutureMutex;
use futures::stream::Stream;
@ -534,7 +534,7 @@ impl MailBackend for ImapType {
let uid_store = self.uid_store.clone();
let connection = self.connection.clone();
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;
conn.select_mailbox(mailbox_hash, &mut response, true)
.await?;
@ -645,7 +645,7 @@ impl MailBackend for ImapType {
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;
conn.select_mailbox(source_mailbox_hash, &mut response, false)
.await?;
@ -711,7 +711,7 @@ impl MailBackend for ImapType {
return Ok(());
}
let mut response = String::with_capacity(8 * 1024);
let mut response = Vec::with_capacity(8 * 1024);
let mut conn = connection.lock().await;
conn.select_mailbox(mailbox_hash, &mut response, false)
.await?;
@ -883,7 +883,7 @@ impl MailBackend for ImapType {
* 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;
conn_lck.unselect().await?;
@ -901,11 +901,11 @@ impl MailBackend for ImapType {
.read_response(&mut response, RequiredResponses::empty())
.await?;
}
let ret: Result<()> = ImapResponse::try_from(response.as_str())?.into();
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?;
let new_hash = get_path_hash!(path.as_str());
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)));
}
}
let mut response = String::with_capacity(8 * 1024);
let mut response = Vec::with_capacity(8 * 1024);
{
let mut conn_lck = connection.lock().await;
/* 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())
.await?;
}
let ret: Result<()> = ImapResponse::try_from(response.as_str())?.into();
let ret: Result<()> = ImapResponse::try_from(response.as_slice())?.into();
ret?;
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());
}
let mut response = String::with_capacity(8 * 1024);
let mut response = Vec::with_capacity(8 * 1024);
{
let mut conn_lck = connection.lock().await;
if new_val {
@ -989,7 +989,7 @@ impl MailBackend for ImapType {
.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() {
uid_store
.mailboxes
@ -1014,7 +1014,7 @@ impl MailBackend for ImapType {
let new_mailbox_fut = self.mailboxes();
Ok(Box::pin(async move {
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 permissions = mailboxes[&mailbox_hash].permissions();
@ -1041,10 +1041,10 @@ impl MailBackend for ImapType {
.await?;
}
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?;
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(
&uid_store.mailboxes.lock().await[&new_hash],
))
@ -1172,7 +1172,7 @@ impl MailBackend for ImapType {
let uid_store = self.uid_store.clone();
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;
conn.examine_mailbox(mailbox_hash, &mut response, false)
.await?;
@ -1180,16 +1180,18 @@ impl MailBackend for ImapType {
.await?;
conn.read_response(&mut response, RequiredResponses::SEARCH)
.await?;
debug!(&response);
let mut lines = response.lines();
for l in lines.by_ref() {
if l.starts_with("* SEARCH") {
debug!(
"searching for {} returned: {}",
query_str,
String::from_utf8_lossy(&response)
);
for l in response.split_rn() {
if l.starts_with(b"* SEARCH") {
use std::iter::FromIterator;
let uid_index = uid_store.uid_index.lock()?;
return Ok(SmallVec::from_iter(
l["* SEARCH".len()..]
.trim()
String::from_utf8_lossy(l[b"* SEARCH".len()..].trim())
.split_whitespace()
.map(UID::from_str)
.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()))
.unwrap()
.unwrap();
let mut res = String::with_capacity(8 * 1024);
let mut res = Vec::with_capacity(8 * 1024);
futures::executor::block_on(timeout(
self.server_conf.timeout,
conn.send_command(b"NOOP"),
@ -1332,7 +1336,7 @@ impl ImapType {
.unwrap();
futures::executor::block_on(timeout(
self.server_conf.timeout,
conn.read_lines(&mut res, String::new()),
conn.read_lines(&mut res, Vec::new()),
))
.unwrap()
.unwrap();
@ -1348,7 +1352,7 @@ impl ImapType {
conn = iter.into_conn();
}
*/
println!("S: {}", &res);
println!("S: {}", String::from_utf8_lossy(&res));
}
Err(error) => println!("error: {}", error),
}
@ -1359,7 +1363,7 @@ impl ImapType {
connection: &Arc<FutureMutex<ImapConnection>>,
) -> Result<HashMap<MailboxHash, ImapMailbox>> {
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 has_list_status: bool = conn
.uid_store
@ -1381,14 +1385,12 @@ impl ImapType {
conn.read_response(&mut res, RequiredResponses::LIST_REQUIRED)
.await?;
}
debug!("out: {}", &res);
debug!("out: {}", String::from_utf8_lossy(&res));
let mut lines = res.split_rn();
/* Remove "M__ OK .." line */
lines.next_back();
for l in lines {
if let Ok(mut mailbox) =
protocol_parser::list_mailbox_result(l.as_bytes()).map(|(_, v)| v)
{
if let Ok(mut mailbox) = protocol_parser::list_mailbox_result(&l).map(|(_, v)| v) {
if let Some(parent) = mailbox.parent {
if mailboxes.contains_key(&parent) {
mailboxes
@ -1414,9 +1416,7 @@ impl ImapType {
} else {
mailboxes.insert(mailbox.hash, mailbox);
}
} else if let Ok(status) =
protocol_parser::status_response(l.as_bytes()).map(|(_, v)| v)
{
} else if let Ok(status) = protocol_parser::status_response(&l).map(|(_, v)| v) {
if let Some(mailbox_hash) = status.mailbox {
if mailboxes.contains_key(&mailbox_hash) {
let entry = mailboxes.entry(mailbox_hash).or_default();
@ -1437,13 +1437,11 @@ impl ImapType {
conn.read_response(&mut res, RequiredResponses::LSUB_REQUIRED)
.await?;
let mut lines = res.split_rn();
debug!("out: {}", &res);
debug!("out: {}", String::from_utf8_lossy(&res));
/* Remove "M__ OK .." line */
lines.next_back();
for l in lines {
if let Ok(subscription) =
protocol_parser::list_mailbox_result(l.as_bytes()).map(|(_, v)| v)
{
if let Ok(subscription) = protocol_parser::list_mailbox_result(&l).map(|(_, v)| v) {
if let Some(f) = mailboxes.get_mut(&subscription.hash()) {
f.is_subscribed = true;
}
@ -1651,7 +1649,7 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
}
let mut conn = connection.lock().await;
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 chunk_size = 250;
@ -1686,7 +1684,7 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
debug!(
"fetch response is {} bytes and {} lines",
response.len(),
response.lines().count()
String::from_utf8_lossy(&response).lines().count()
);
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
debug!("responses len is {}", v.len());

16
melib/src/backends/imap/cache/sync.rs

@ -83,7 +83,7 @@ impl ImapConnection {
mailbox_hash: MailboxHash,
) -> Result<()> {
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
let select_response = self
.select_mailbox(mailbox_hash, &mut response, true)
@ -119,7 +119,7 @@ impl ImapConnection {
) -> Result<Option<Vec<Envelope>>> {
let mut payload = vec![];
debug!("resync_basic");
let mut response = String::with_capacity(8 * 1024);
let mut response = Vec::with_capacity(8 * 1024);
let cached_uidvalidity = self
.uid_store
.uidvalidity
@ -181,7 +181,7 @@ impl ImapConnection {
debug!(
"fetch response is {} bytes and {} lines",
response.len(),
response.lines().count()
String::from_utf8_lossy(&response).lines().count()
);
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
debug!("responses len is {}", v.len());
@ -344,7 +344,7 @@ impl ImapConnection {
) -> Result<Option<Vec<Envelope>>> {
let mut payload = vec![];
debug!("resync_condstore");
let mut response = String::with_capacity(8 * 1024);
let mut response = Vec::with_capacity(8 * 1024);
let cached_uidvalidity = self
.uid_store
.uidvalidity
@ -465,7 +465,7 @@ impl ImapConnection {
debug!(
"fetch response is {} bytes and {} lines",
response.len(),
response.lines().count()
String::from_utf8_lossy(&response).lines().count()
);
let (_, mut v, _) = protocol_parser::fetch_responses(&response)?;
debug!("responses len is {}", v.len());
@ -597,7 +597,7 @@ impl ImapConnection {
self.read_response(&mut response, RequiredResponses::SEARCH)
.await?;
//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 {
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> {
let mut response = String::with_capacity(8 * 1024);
let mut response = Vec::with_capacity(8 * 1024);
let (mailbox_path, mailbox_exists, unseen, permissions) = {
let f = &self.uid_store.mailboxes.lock().await[&mailbox_hash];
(
@ -708,7 +708,7 @@ impl ImapConnection {
.await?;
self.read_response(&mut response, RequiredResponses::STATUS)
.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 uidnext == 0 {
return Err(MeliError::new(

118
melib/src/backends/imap/connection.rs

@ -165,7 +165,7 @@ impl ImapStream {
.flush()
.await
.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 now = Instant::now();
@ -174,24 +174,24 @@ impl ImapStream {
.read(&mut buf)
.await
.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 {
ImapProtocol::IMAP { .. } => {
if response.starts_with("* OK ") && response.find("\r\n").is_some() {
if let Some(pos) = response.as_bytes().find(b"\r\n") {
if response.starts_with(b"* OK ") && response.find(b"\r\n").is_some() {
if let Some(pos) = response.find(b"\r\n") {
response.drain(0..pos + 2);
}
}
}
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();
broken = true;
break;
}
}
}
if response.starts_with("M1 OK") {
if response.starts_with(b"M1 OK") {
broken = true;
break;
}
@ -262,7 +262,7 @@ impl ImapStream {
crate::LoggingLevel::WARN,
);
}
let mut res = String::with_capacity(8 * 1024);
let mut res = Vec::with_capacity(8 * 1024);
let mut ret = ImapStream {
cmd_id,
stream,
@ -295,10 +295,10 @@ impl ImapStream {
ret.read_response(&mut res).await?;
let capabilities: std::result::Result<Vec<&[u8]>, _> = res
.split_rn()
.find(|l| l.starts_with("* CAPABILITY"))
.find(|l| l.starts_with(b"* CAPABILITY"))
.ok_or_else(|| MeliError::new(""))
.and_then(|res| {
protocol_parser::capabilities(res.as_bytes())
protocol_parser::capabilities(&res)
.map_err(|_| MeliError::new(""))
.map(|(_, v)| v)
});
@ -306,8 +306,10 @@ impl ImapStream {
if capabilities.is_err() {
return Err(MeliError::new(format!(
"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();
@ -342,22 +344,22 @@ impl ImapStream {
let tag_start = format!("M{} ", (ret.cmd_id - 1));
loop {
ret.read_lines(&mut res, &String::new(), false).await?;
ret.read_lines(&mut res, &[], false).await?;
let mut should_break = false;
for l in res.split_rn() {
if l.starts_with("* CAPABILITY") {
capabilities = protocol_parser::capabilities(l.as_bytes())
if l.starts_with(b"* CAPABILITY") {
capabilities = protocol_parser::capabilities(&l)
.map(|(_, capabilities)| {
HashSet::from_iter(capabilities.into_iter().map(|s: &[u8]| s.to_vec()))
})
.ok();
}
if l.starts_with(tag_start.as_str()) {
if !l[tag_start.len()..].trim().starts_with("OK ") {
if l.starts_with(tag_start.as_bytes()) {
if !l[tag_start.len()..].trim().starts_with(b"OK ") {
return Err(MeliError::new(format!(
"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));
}
@ -375,7 +377,7 @@ impl ImapStream {
drop(capabilities);
ret.send_command(b"CAPABILITY").await?;
ret.read_response(&mut res).await.unwrap();
let capabilities = protocol_parser::capabilities(res.as_bytes())?.1;
let capabilities = protocol_parser::capabilities(&res)?.1;
let capabilities = HashSet::from_iter(capabilities.into_iter().map(|s| s.to_vec()));
Ok((capabilities, ret))
} 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 {
ImapProtocol::IMAP { .. } => format!("M{} ", self.cmd_id - 1),
ImapProtocol::ManageSieve => String::new(),
ImapProtocol::IMAP { .. } => format!("M{} ", self.cmd_id - 1).into_bytes(),
ImapProtocol::ManageSieve => Vec::new(),
};
self.read_lines(ret, &id, true).await?;
Ok(())
@ -395,8 +397,8 @@ impl ImapStream {
pub async fn read_lines(
&mut self,
ret: &mut String,
termination_string: &str,
ret: &mut Vec<u8>,
termination_string: &[u8],
keep_termination_string: bool,
) -> Result<()> {
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? {
Ok(0) => break,
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 ret[last_line_idx..].starts_with("* BYE") {
if ret[last_line_idx..].starts_with(b"* BYE") {
return Err(MeliError::new("Disconnected"));
}
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();
pos -= prev_line + "\r\n".len();
last_line_idx += prev_line + b"\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()
&& ret[last_line_idx..].starts_with(termination_string)
{
debug!(&ret[last_line_idx..]);
if !keep_termination_string {
ret.replace_range(last_line_idx.., "");
ret.splice(last_line_idx.., std::iter::empty::<u8>());
}
break;
} else if termination_string.is_empty() {
break;
}
}
last_line_idx += pos + "\r\n".len();
last_line_idx += pos + b"\r\n".len();
}
}
Err(e) => {
@ -443,9 +445,9 @@ impl ImapStream {
}
pub async fn wait_for_continuation_request(&mut self) -> Result<()> {
let term = "+ ".to_string();
let mut ret = String::new();
self.read_lines(&mut ret, &term, false).await?;
let term = b"+ ";
let mut ret = Vec::new();
self.read_lines(&mut ret, &term[..], false).await?;
Ok(())
}
@ -546,7 +548,7 @@ impl ImapConnection {
}
}
if debug!(self.stream.is_ok()) {
let mut ret = String::new();
let mut ret = Vec::new();
if let Err(err) = try_await(async {
self.send_command(b"NOOP").await?;
self.read_response(&mut ret, RequiredResponses::empty())
@ -586,7 +588,7 @@ impl ImapConnection {
SyncPolicy::None => { /* do nothing, sync is disabled */ }
_ => {
/* Upgrade to Condstore */
let mut ret = String::new();
let mut ret = Vec::new();
if capabilities.contains(&b"ENABLE"[..]) {
self.send_command(b"ENABLE CONDSTORE").await?;
self.read_response(&mut ret, RequiredResponses::empty())
@ -605,11 +607,11 @@ impl ImapConnection {
}
#[cfg(feature = "deflate_compression")]
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.read_response(&mut ret, RequiredResponses::empty())
.await?;
match ImapResponse::try_from(ret.as_str())? {
match ImapResponse::try_from(ret.as_slice())? {
ImapResponse::No(code)
| ImapResponse::Bad(code)
| ImapResponse::Preauth(code)
@ -645,25 +647,25 @@ impl ImapConnection {
pub fn read_response<'a>(
&'a mut self,
ret: &'a mut String,
ret: &'a mut Vec<u8>,
required_responses: RequiredResponses,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> {
Box::pin(async move {
let mut response = String::new();
let mut response = Vec::new();
ret.clear();
self.stream.as_mut()?.read_response(&mut response).await?;
*self.uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(()));
match self.server_conf.protocol {
ImapProtocol::IMAP { .. } => {
let r: ImapResponse = ImapResponse::try_from(response.as_str())?;
let r: ImapResponse = ImapResponse::try_from(response.as_slice())?;
match r {
ImapResponse::Bye(ref response_code) => {
self.stream = Err(MeliError::new(format!(
"Offline: received BYE: {:?}",
response_code
)));
ret.push_str(&response);
ret.extend_from_slice(&response);
return r.into();
}
ImapResponse::No(ref response_code)
@ -685,7 +687,7 @@ impl ImapConnection {
level: crate::logging::LoggingLevel::ERROR,
},
);
ret.push_str(&response);
ret.extend_from_slice(&response);
return r.into();
}
ImapResponse::Bad(ref response_code) => {
@ -699,7 +701,7 @@ impl ImapConnection {
level: crate::logging::LoggingLevel::ERROR,
},
);
ret.push_str(&response);
ret.extend_from_slice(&response);
return r.into();
}
_ => {}
@ -711,20 +713,24 @@ impl ImapConnection {
for l in response.split_rn() {
/*debug!("check line: {}", &l);*/
if required_responses.check(l) || !self.process_untagged(l).await? {
ret.push_str(l);
ret.extend_from_slice(l);
}
}
Ok(())
}
ImapProtocol::ManageSieve => {
ret.push_str(&response);
ret.extend_from_slice(&response);
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
.as_mut()?
.read_lines(ret, &termination_string, false)
@ -783,7 +789,7 @@ impl ImapConnection {
pub async fn select_mailbox(
&mut self,
mailbox_hash: MailboxHash,
ret: &mut String,
ret: &mut Vec<u8>,
force: bool,
) -> Result<Option<SelectResponse>> {
if !force && self.stream.as_ref()?.current_mailbox == MailboxSelection::Select(mailbox_hash)
@ -809,7 +815,11 @@ impl ImapConnection {
.await?;
self.read_response(ret, RequiredResponses::SELECT_REQUIRED)
.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(|| {
format!("Could not parse select response for mailbox {}", imap_path)
})?;
@ -868,7 +878,7 @@ impl ImapConnection {
pub async fn examine_mailbox(
&mut self,
mailbox_hash: MailboxHash,
ret: &mut String,
ret: &mut Vec<u8>,
force: bool,
) -> Result<Option<SelectResponse>> {
if !force
@ -891,7 +901,7 @@ impl ImapConnection {
.await?;
self.read_response(ret, RequiredResponses::EXAMINE_REQUIRED)
.await?;
debug!("examine response {}", ret);
debug!("examine response {}", String::from_utf8_lossy(&ret));
let select_response = protocol_parser::select_response(&ret).chain_err_summary(|| {
format!("Could not parse select response for mailbox {}", imap_path)
})?;
@ -915,7 +925,7 @@ impl ImapConnection {
match self.stream.as_mut()?.current_mailbox.take() {
MailboxSelection::Examine(_) |
MailboxSelection::Select(_) => {
let mut response = String::with_capacity(8 * 1024);
let mut response = Vec::with_capacity(8 * 1024);
if self
.uid_store
.capabilities
@ -970,7 +980,7 @@ impl ImapConnection {
_select_response: &SelectResponse,
) -> Result<()> {
debug_assert!(low > 0);
let mut response = String::new();
let mut response = Vec::new();
self.send_command(format!("UID SEARCH {}:*", low).as_bytes())
.await?;
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.drain(low - 1..);
msn_index.extend(
debug!(protocol_parser::search_results(response.as_bytes()))?
debug!(protocol_parser::search_results(&response))?
.1
.into_iter(),
);

10
melib/src/backends/imap/operations.rs

@ -64,7 +64,7 @@ impl BackendOp for ImapOp {
cache.bytes.is_some()
};
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?;
conn.connect().await?;
@ -78,7 +78,7 @@ impl BackendOp for ImapOp {
debug!(
"fetch response is {} bytes and {} lines",
response.len(),
response.lines().count()
String::from_utf8_lossy(&response).lines().count()
);
let mut results = protocol_parser::fetch_responses(&response)?.1;
if results.len() != 1 {
@ -114,7 +114,7 @@ impl BackendOp for ImapOp {
}
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 mailbox_hash = self.mailbox_hash;
let uid = self.uid;
@ -138,9 +138,9 @@ impl BackendOp for ImapOp {
debug!(
"fetch response is {} bytes and {} lines",
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_err(MeliError::from)?;
if v.len() != 1 {

404
melib/src/backends/imap/protocol_parser.rs

@ -66,63 +66,63 @@ bitflags! {
}
impl RequiredResponses {
pub fn check(&self, line: &str) -> bool {
if !line.starts_with("* ") {
pub fn check(&self, line: &[u8]) -> bool {
if !line.starts_with(b"* ") {
return false;
}
let line = &line["* ".len()..];
let line = &line[b"* ".len()..];
let mut ret = false;
if self.intersects(RequiredResponses::CAPABILITY) {
ret |= line.starts_with("CAPABILITY");
ret |= line.starts_with(b"CAPABILITY");
}
if self.intersects(RequiredResponses::BYE) {
ret |= line.starts_with("BYE");
ret |= line.starts_with(b"BYE");
}
if self.intersects(RequiredResponses::FLAGS) {
ret |= line.starts_with("FLAGS");
ret |= line.starts_with(b"FLAGS");
}
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) {
ret |= line.ends_with("RECENT\r\n");
ret |= line.ends_with(b"RECENT\r\n");
}
if self.intersects(RequiredResponses::UNSEEN) {
ret |= line.starts_with("UNSEEN");
ret |= line.starts_with(b"UNSEEN");
}
if self.intersects(RequiredResponses::PERMANENTFLAGS) {
ret |= line.starts_with("PERMANENTFLAGS");
ret |= line.starts_with(b"PERMANENTFLAGS");
}
if self.intersects(RequiredResponses::UIDNEXT) {
ret |= line.starts_with("UIDNEXT");
ret |= line.starts_with(b"UIDNEXT");
}
if self.intersects(RequiredResponses::UIDVALIDITY) {
ret |= line.starts_with("UIDVALIDITY");
ret |= line.starts_with(b"UIDVALIDITY");
}
if self.intersects(RequiredResponses::LIST) {
ret |= line.starts_with("LIST");
ret |= line.starts_with(b"LIST");
}
if self.intersects(RequiredResponses::LSUB) {
ret |= line.starts_with("LSUB");
ret |= line.starts_with(b"LSUB");
}
if self.intersects(RequiredResponses::STATUS) {
ret |= line.starts_with("STATUS");
ret |= line.starts_with(b"STATUS");
}
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) {
ret |= line.starts_with("SEARCH");
ret |= line.starts_with(b"SEARCH");
}
if self.intersects(RequiredResponses::FETCH) {
let mut ptr = 0;
for i in 0..line.len() {
if !line.as_bytes()[i].is_ascii_digit() {
if !line[i].is_ascii_digit() {
ptr = i;
break;
}
}
ret |= line[ptr..].trim_start().starts_with("FETCH");
ret |= line[ptr..].trim_start().starts_with(b"FETCH");
}
ret
}
@ -130,19 +130,18 @@ impl RequiredResponses {
#[test]
fn test_imap_required_responses() {
let mut ret = String::new();
let mut ret = Vec::new();
let required_responses = RequiredResponses::FETCH_REQUIRED;
let response =
&"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n"
[0..];
&b"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n"[..];
for l in response.split_rn() {
/*debug!("check line: {}", &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");
let v = protocol_parser::uid_fetch_flags_responses(response.as_bytes())
assert_eq!(ret.as_slice(), &b"* 1040 FETCH (UID 1064 FLAGS ())\r\n"[..]);
let v = protocol_parser::uid_fetch_flags_responses(response)
.unwrap()
.1;
assert_eq!(v.len(), 1);
@ -150,9 +149,9 @@ fn test_imap_required_responses() {
#[derive(Debug)]
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> {
slice: &'a str,
slice: &'a [u8],
}
#[derive(Debug, PartialEq)]
@ -210,38 +209,35 @@ impl std::fmt::Display for ResponseCode {
}
impl ResponseCode {
fn from(val: &str) -> ResponseCode {
fn from(val: &[u8]) -> ResponseCode {
use ResponseCode::*;
if !val.starts_with('[') {
if !val.starts_with(b"[") {
let msg = val.trim();
return Alert(msg.to_string());
return Alert(String::from_utf8_lossy(msg).to_string());
}
let val = &val[1..];
if val.starts_with("BADCHARSET") {
let charsets = val
.as_bytes()
.find(b"(")
.map(|pos| val[pos + 1..].trim().to_string());
Badcharset(charsets)
} else if val.starts_with("READONLY") {
if val.starts_with(b"BADCHARSET") {
let charsets = val.find(b"(").map(|pos| val[pos + 1..].trim());
Badcharset(charsets.map(|charsets| String::from_utf8_lossy(charsets).to_string()))
} else if val.starts_with(b"READONLY") {
ReadOnly
} else if val.starts_with("READWRITE") {
} else if val.starts_with(b"READWRITE") {
ReadWrite
} else if val.starts_with("TRYCREATE") {
} else if val.starts_with(b"TRYCREATE") {
Trycreate
} else if val.starts_with("UIDNEXT") {
} else if val.starts_with(b"UIDNEXT") {
//FIXME
Uidnext(0)
} else if val.starts_with("UIDVALIDITY") {
} else if val.starts_with(b"UIDVALIDITY") {
//FIXME
Uidvalidity(0)
} else if val.starts_with("UNSEEN") {
} else if val.starts_with(b"UNSEEN") {
//FIXME
Unseen(0)
} else {
let msg = &val[val.as_bytes().find(b"] ").unwrap() + 1..].trim();
Alert(msg.to_string())
let msg = &val[val.find(b"] ").unwrap() + 1..].trim();
Alert(String::from_utf8_lossy(msg).to_string())
}
}
}
@ -255,12 +251,11 @@ pub enum ImapResponse {
Bye(ResponseCode),
}
impl TryFrom<&'_ str> for ImapResponse {
impl TryFrom<&'_ [u8]> for ImapResponse {
type Error = MeliError;
fn try_from(val: &'_ str) -> Result<ImapResponse> {
let val: &str = val.split_rn().last().unwrap_or(val.as_ref());
debug!(&val);
let mut val = val[val.as_bytes().find(b" ").ok_or_else(|| {
fn try_from(val: &'_ [u8]) -> Result<ImapResponse> {
let val: &[u8] = val.split_rn().last().unwrap_or(val.as_ref());
let mut val = val[val.find(b" ").ok_or_else(|| {
MeliError::new(format!(
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
val
@ -268,8 +263,8 @@ impl TryFrom<&'_ str> for ImapResponse {
})? + 1..]
.trim();
// 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).") {
val = &val[..val.as_bytes().rfind(b"(").ok_or_else(|| {
if val.ends_with(b" secs).") {
val = &val[..val.rfind(b"(").ok_or_else(|| {
MeliError::new(format!(
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
val
@ -277,16 +272,16 @@ impl TryFrom<&'_ str> for ImapResponse {
})?];
}
Ok(if val.starts_with("OK") {
Self::Ok(ResponseCode::from(&val["OK ".len()..]))
} else if val.starts_with("NO") {
Self::No(ResponseCode::from(&val["NO ".len()..]))
} else if val.starts_with("BAD") {
Self::Bad(ResponseCode::from(&val["BAD ".len()..]))
} else if val.starts_with("PREAUTH") {
Self::Preauth(ResponseCode::from(&val["PREAUTH ".len()..]))
} else if val.starts_with("BYE") {
Self::Bye(ResponseCode::from(&val["BYE ".len()..]))
Ok(if val.starts_with(b"OK") {
Self::Ok(ResponseCode::from(&val[b"OK ".len()..]))
} else if val.starts_with(b"NO") {
Self::No(ResponseCode::from(&val[b"NO ".len()..]))
} else if val.starts_with(b"BAD") {
Self::Bad(ResponseCode::from(&val[b"BAD ".len()..]))
} else if val.starts_with(b"PREAUTH") {
Self::Preauth(ResponseCode::from(&val[b"PREAUTH ".len()..]))
} else if val.starts_with(b"BYE") {
Self::Bye(ResponseCode::from(&val[b"BYE ".len()..]))
} else {
return Err(MeliError::new(format!(
"Expected tagged IMAP response (OK,NO,BAD, etc) but found {:?}",
@ -313,47 +308,48 @@ impl Into<Result<()>> for ImapResponse {
#[test]
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> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.slice.is_empty() {
None
} else if let Some(pos) = self.slice.rfind("\r\n") {
if self.slice[..pos].is_empty() {
self.slice = &self.slice[..pos];
} else if let Some(pos) = self.slice.rfind(b"\r\n") {
if self.slice.get(..pos).unwrap_or_default().is_empty() {
self.slice = self.slice.get(..pos).unwrap_or_default();
None
} else if let Some(prev_pos) = self.slice[..pos].rfind("\r\n") {
let ret = &self.slice[prev_pos + 2..pos + 2];
self.slice = &self.slice[..prev_pos + 2];
} else if let Some(prev_pos) = self.slice.get(..pos).unwrap_or_default().rfind(b"\r\n")
{
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)
} else {
let ret = self.slice;
self.slice = &self.slice[ret.len()..];
self.slice = self.slice.get(ret.len()..).unwrap_or_default();
Some(ret)
}
} else {
let ret = self.slice;
self.slice = &self.slice[ret.len()..];
self.slice = self.slice.get(ret.len()..).unwrap_or_default();
Some(ret)
}
}
}
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() {
None
} else if let Some(pos) = self.slice.find("\r\n") {
let ret = &self.slice[..pos + 2];
self.slice = &self.slice[pos + 2..];
} else if let Some(pos) = self.slice.find(b"\r\n") {
let ret = self.slice.get(..pos + 2).unwrap_or_default();
self.slice = self.slice.get(pos + 2..).unwrap_or_default();
Some(ret)
} else {
let ret = self.slice;
self.slice = &self.slice[ret.len()..];
self.slice = self.slice.get(ret.len()..).unwrap_or_default();
Some(ret)
}
}
@ -363,7 +359,7 @@ pub trait ImapLineSplit {
fn split_rn(&self) -> ImapLineIterator;
}
impl ImapLineSplit for str {
impl ImapLineSplit for [u8] {
fn split_rn(&self) -> ImapLineIterator {
ImapLineIterator { slice: self }
}
@ -416,7 +412,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
let separator: u8 = separator[0];
let mut f = ImapMailbox::default();
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' ') {
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);
}
}
f.imap_path = path.into();
f.imap_path = path.to_string();
f.hash = get_path_hash!(&f.imap_path);
f.path = if separator == b'/' {
f.imap_path.clone()
@ -462,26 +458,27 @@ pub struct FetchResponse<'a> {
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 {
($input:expr, $tag:literal) => {
if !$input.starts_with($tag) {
return Err(MeliError::new(format!(
"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 {
() => {
if i == input.len() {
return Err(MeliError::new(format!(
"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 {
() => {
while (input.as_bytes()[i] as char).is_whitespace() {
while (input[i] as char).is_whitespace() {
i += 1;
bounds!();
}
};