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.
parent
366e557e1c
commit
3618bdcffb
|
@ -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);
|
||||
debug!(
|
||||
"searching for {} returned: {}",
|
||||
query_str,
|
||||
String::from_utf8_lossy(&response)
|
||||
);
|
||||
|
||||
let mut lines = response.lines();
|
||||
for l in lines.by_ref() {
|
||||
if l.starts_with("* SEARCH") {
|
||||
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());
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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!();
|
||||
}
|
||||
};
|
||||
(break) => {
|
||||
while (input.as_bytes()[i] as char).is_whitespace() {
|
||||
while (input[i] as char).is_whitespace() {
|
||||
i += 1;
|
||||
bounds!(break);
|
||||
}
|
||||
|
@ -516,8 +513,8 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
|||
envelope: None,
|
||||
};
|
||||
|
||||
while input.as_bytes()[i].is_ascii_digit() {
|
||||
let b: u8 = input.as_bytes()[i] - 0x30;
|
||||
while input[i].is_ascii_digit() {
|
||||
let b: u8 = input[i] - 0x30;
|
||||
ret.message_sequence_number *= 10;
|
||||
ret.message_sequence_number += b as MessageSequenceNumber;
|
||||
i += 1;
|
||||
|
@ -525,18 +522,17 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
|||
}
|
||||
|
||||
eat_whitespace!();
|
||||
should_start_with!(input[i..], "FETCH (");
|
||||
i += "FETCH (".len();
|
||||
should_start_with!(&input[i..], b"FETCH (");
|
||||
i += b"FETCH (".len();
|
||||
let mut has_attachments = false;
|
||||
while i < input.len() {
|
||||
eat_whitespace!(break);
|
||||
bounds!(break);
|
||||
|
||||
if input[i..].starts_with("UID ") {
|
||||
i += "UID ".len();
|
||||
if let Ok((rest, uid)) = take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(
|
||||
is_digit,
|
||||
)(input[i..].as_bytes())
|
||||
if input[i..].starts_with(b"UID ") {
|
||||
i += b"UID ".len();
|
||||
if let Ok((rest, uid)) =
|
||||
take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(is_digit)(&input[i..])
|
||||
{
|
||||
i += input.len() - i - rest.len();
|
||||
ret.uid =
|
||||
|
@ -544,25 +540,24 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
|||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
input
|
||||
String::from_utf8_lossy(&input)
|
||||
))));
|
||||
}
|
||||
} else if input[i..].starts_with("FLAGS (") {
|
||||
i += "FLAGS (".len();
|
||||
} else if input[i..].starts_with(b"FLAGS (") {
|
||||
i += b"FLAGS (".len();
|
||||
if let Ok((rest, flags)) = flags(&input[i..]) {
|
||||
ret.flags = Some(flags);
|
||||
i += (input.len() - i - rest.len()) + 1;
|
||||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
input
|
||||
String::from_utf8_lossy(&input)
|
||||
))));
|
||||
}
|
||||
} else if input[i..].starts_with("MODSEQ (") {
|
||||
i += "MODSEQ (".len();
|
||||
if let Ok((rest, modseq)) = take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(
|
||||
is_digit,
|
||||
)(input[i..].as_bytes())
|
||||
} else if input[i..].starts_with(b"MODSEQ (") {
|
||||
i += b"MODSEQ (".len();
|
||||
if let Ok((rest, modseq)) =
|
||||
take_while::<_, &[u8], (&[u8], nom::error::ErrorKind)>(is_digit)(&input[i..])
|
||||
{
|
||||
i += (input.len() - i - rest.len()) + 1;
|
||||
ret.modseq = u64::from_str(to_str!(modseq))
|
||||
|
@ -572,11 +567,11 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
|||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing MODSEQ in UID FETCH response. Got: `{:.40}`",
|
||||
input
|
||||
String::from_utf8_lossy(&input)
|
||||
))));
|
||||
}
|
||||
} else if input[i..].starts_with("RFC822 {") {
|
||||
i += "RFC822 ".len();
|
||||
} else if input[i..].starts_with(b"RFC822 {") {
|
||||
i += b"RFC822 ".len();
|
||||
if let Ok((rest, body)) =
|
||||
length_data::<_, _, (&[u8], nom::error::ErrorKind), _>(delimited(
|
||||
tag("{"),
|
||||
|
@ -584,41 +579,41 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
|||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||
}),
|
||||
tag("}\r\n"),
|
||||
))(input[i..].as_bytes())
|
||||
))(&input[i..])
|
||||
{
|
||||
ret.body = Some(body);
|
||||
i += input.len() - i - rest.len();
|
||||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
input
|
||||
String::from_utf8_lossy(&input)
|
||||
))));
|
||||
}
|
||||
} else if input[i..].starts_with("ENVELOPE (") {
|
||||
i += "ENVELOPE ".len();
|
||||
if let Ok((rest, envelope)) = envelope(input[i..].as_bytes()) {
|
||||
} else if input[i..].starts_with(b"ENVELOPE (") {
|
||||
i += b"ENVELOPE ".len();
|
||||
if let Ok((rest, envelope)) = envelope(&input[i..]) {
|
||||
ret.envelope = Some(envelope);
|
||||
i += input.len() - i - rest.len();
|
||||
} else {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
&input[i..]
|
||||
String::from_utf8_lossy(&input[i..])
|
||||
))));
|
||||
}
|
||||
} else if input[i..].starts_with("BODYSTRUCTURE ") {
|
||||
i += "BODYSTRUCTURE ".len();
|
||||
} else if input[i..].starts_with(b"BODYSTRUCTURE ") {
|
||||
i += b"BODYSTRUCTURE ".len();
|
||||
let mut struct_ptr = i;
|
||||
let mut parenth_level = 0;
|
||||
let mut inside_quote = false;
|
||||
while struct_ptr != input.len() {
|
||||
if !inside_quote {
|
||||
if input.as_bytes()[struct_ptr] == b'(' {
|
||||
if input[struct_ptr] == b'(' {
|
||||
parenth_level += 1;
|
||||
} else if input.as_bytes()[struct_ptr] == b')' {
|
||||
} else if input[struct_ptr] == b')' {
|
||||
if parenth_level == 0 {
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
|
||||
&input[struct_ptr..]
|
||||
String::from_utf8_lossy(&input[struct_ptr..])
|
||||
))));
|
||||
}
|
||||
parenth_level -= 1;
|
||||
|
@ -626,30 +621,30 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
|
|||
struct_ptr += 1;
|
||||
break;
|
||||
}
|
||||
} else if input.as_bytes()[struct_ptr] == b'"' {
|
||||
} else if input[struct_ptr] == b'"' {
|
||||
inside_quote = true;
|
||||
}
|
||||
} else if input.as_bytes()[struct_ptr] == b'\"'
|
||||
&& (struct_ptr == 0 || (input.as_bytes()[struct_ptr - 1] != b'\\'))
|
||||
} else if input[struct_ptr] == b'\"'
|
||||
&& (struct_ptr == 0 || (input[struct_ptr - 1] != b'\\'))
|
||||
{
|
||||
inside_quote = false;
|
||||
}
|
||||
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;
|
||||
} else if input[i..].starts_with(")\r\n") {
|
||||
i += ")\r\n".len();
|
||||
} else if input[i..].starts_with(b")\r\n") {
|
||||
i += b")\r\n".len();
|
||||
break;
|
||||
} else {
|
||||
debug!(
|
||||
"Got unexpected token while parsing UID FETCH response:\n`{}`\n",
|
||||
input
|
||||
String::from_utf8_lossy(&input)
|
||||
);
|
||||
return debug!(Err(MeliError::new(format!(
|
||||
"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))
|
||||
}
|
||||
|
||||
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 alert: Option<Alert> = None;
|
||||
|
||||
while input.starts_with("* ") {
|
||||
while input.starts_with(b"* ") {
|
||||
let next_response = fetch_response(input);
|
||||
match next_response {
|
||||
Ok((rest, el, el_alert)) => {
|
||||
|
@ -683,7 +678,8 @@ pub fn fetch_responses(mut input: &str) -> ImapParseResult<Vec<FetchResponse<'_>
|
|||
Err(err) => {
|
||||
return Err(MeliError::new(format!(
|
||||
"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 {
|
||||
return Err(MeliError::new(format!(
|
||||
"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 (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>("* ")(input)?;
|
||||
let (input, num) = map_res::<_, _, _, (&str, nom::error::ErrorKind), _, _, _>(digit1, |s| {
|
||||
ImapNum::from_str(s)
|
||||
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(b"* ")(input)?;
|
||||
let (input, num) = map_res::<_, _, _, (&[u8], nom::error::ErrorKind), _, _, _>(digit1, |s| {
|
||||
ImapNum::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||
})(input)?;
|
||||
let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>(" ")(input)?;
|
||||
let (input, _tag) = take_until::<_, &str, (&str, nom::error::ErrorKind)>("\r\n")(input)?;
|
||||
let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>("\r\n")(input)?;
|
||||
let (input, _) = tag::<_, &[u8], (&[u8], nom::error::ErrorKind)>(b" ")(input)?;
|
||||
let (input, _tag) =
|
||||
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);
|
||||
Ok((
|
||||
input,
|
||||
{
|
||||
use UntaggedResponse::*;
|
||||
match _tag {
|
||||
"EXPUNGE" => Some(Expunge(num)),
|
||||
"EXISTS" => Some(Exists(num)),
|
||||
"RECENT" => Some(Recent(num)),
|
||||
_ if _tag.starts_with("FETCH ") => Some(Fetch(fetch_response(orig_input)?.1)),
|
||||
b"EXPUNGE" => Some(Expunge(num)),
|
||||
b"EXISTS" => Some(Exists(num)),
|
||||
b"RECENT" => Some(Recent(num)),
|
||||
_ 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
|
||||
}
|
||||
}
|
||||
|
@ -876,14 +876,14 @@ fn test_untagged_responses() {
|
|||
use std::convert::TryInto;
|
||||
use UntaggedResponse::*;
|
||||
assert_eq!(
|
||||
untagged_responses("* 2 EXISTS\r\n")
|
||||
untagged_responses(b"* 2 EXISTS\r\n")
|
||||
.map(|(_, v, _)| v)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
Exists(2)
|
||||
);
|
||||
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)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
|
@ -897,7 +897,7 @@ fn test_untagged_responses() {
|
|||
})
|
||||
);
|
||||
assert_eq!(
|
||||
untagged_responses("* 1 FETCH (FLAGS (\\Seen))\r\n")
|
||||
untagged_responses(b"* 1 FETCH (FLAGS (\\Seen))\r\n")
|
||||
.map(|(_, v, _)| v)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
|
@ -1001,59 +1001,70 @@ pub struct SelectResponse {
|
|||
* * OK [UIDVALIDITY 1554422056] UIDs valid
|
||||
* * OK [UIDNEXT 50] Predicted next UID
|
||||
*/
|
||||
pub fn select_response(input: &str) -> Result<SelectResponse> {
|
||||
if input.contains("* OK") {
|
||||
pub fn select_response(input: &[u8]) -> Result<SelectResponse> {
|
||||
if input.contains_subsequence(b"* OK") {
|
||||
let mut ret = SelectResponse::default();
|
||||
for l in input.split_rn() {
|
||||
if l.starts_with("* ") && l.ends_with(" EXISTS\r\n") {
|
||||
ret.exists = ImapNum::from_str(&l["* ".len()..l.len() - " EXISTS\r\n".len()])?;
|
||||
} else if l.starts_with("* ") && l.ends_with(" RECENT\r\n") {
|
||||
ret.recent = ImapNum::from_str(&l["* ".len()..l.len() - " RECENT\r\n".len()])?;
|
||||
} else if l.starts_with("* FLAGS (") {
|
||||
ret.flags = flags(&l["* FLAGS (".len()..l.len() - ")".len()]).map(|(_, v)| v)?;
|
||||
} else if l.starts_with("* OK [UNSEEN ") {
|
||||
ret.unseen = MessageSequenceNumber::from_str(
|
||||
&l["* OK [UNSEEN ".len()..l.find(']').unwrap()],
|
||||
)?;
|
||||
} else if l.starts_with("* OK [UIDVALIDITY ") {
|
||||
ret.uidvalidity =
|
||||
UIDVALIDITY::from_str(&l["* OK [UIDVALIDITY ".len()..l.find(']').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("* OK [PERMANENTFLAGS (") {
|
||||
if l.starts_with(b"* ") && l.ends_with(b" EXISTS\r\n") {
|
||||
ret.exists = ImapNum::from_str(&String::from_utf8_lossy(
|
||||
&l[b"* ".len()..l.len() - b" EXISTS\r\n".len()],
|
||||
))?;
|
||||
} else if l.starts_with(b"* ") && l.ends_with(b" RECENT\r\n") {
|
||||
ret.recent = ImapNum::from_str(&String::from_utf8_lossy(
|
||||
&l[b"* ".len()..l.len() - b" RECENT\r\n".len()],
|
||||
))?;
|
||||
} 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(b"* OK [UNSEEN ") {
|
||||
ret.unseen = MessageSequenceNumber::from_str(&String::from_utf8_lossy(
|
||||
&l[b"* OK [UNSEEN ".len()..l.find(b"]").unwrap()],
|
||||
))?;
|
||||
} else if l.starts_with(b"* OK [UIDVALIDITY ") {
|
||||
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 =
|
||||
flags(&l["* OK [PERMANENTFLAGS (".len()..l.find(')').unwrap()])
|
||||
flags(&l[b"* OK [PERMANENTFLAGS (".len()..l.find(b")").unwrap()])
|
||||
.map(|(_, v)| v)?;
|
||||
ret.can_create_flags = l.contains("\\*");
|
||||
} else if l.contains("OK [READ-WRITE]") {
|
||||
ret.can_create_flags = l.contains_subsequence(b"\\*");
|
||||
} else if l.contains_subsequence(b"OK [READ-WRITE]" as &[u8]) {
|
||||
ret.read_only = false;
|
||||
} else if l.contains("OK [READ-ONLY]") {
|
||||
} else if l.contains_subsequence(b"OK [READ-ONLY]") {
|
||||
ret.read_only = true;
|
||||
} else if l.starts_with("* OK [HIGHESTMODSEQ ") {
|
||||
let res: IResult<&str, &str> = take_until("]")(&l["* OK [HIGHESTMODSEQ ".len()..]);
|
||||
} else if l.starts_with(b"* OK [HIGHESTMODSEQ ") {
|
||||
let res: IResult<&[u8], &[u8]> =
|
||||
take_until(&b"]"[..])(&l[b"* OK [HIGHESTMODSEQ ".len()..]);
|
||||
let (_, highestmodseq) = res?;
|
||||
ret.highestmodseq = Some(
|
||||
std::num::NonZeroU64::new(u64::from_str(&highestmodseq)?)
|
||||
.map(|u| Ok(ModSequence(u)))
|
||||
.unwrap_or(Err(())),
|
||||
std::num::NonZeroU64::new(u64::from_str(&String::from_utf8_lossy(
|
||||
&highestmodseq,
|
||||
))?)
|
||||
.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(()));
|
||||
} else if !l.is_empty() {
|
||||
debug!("select response: {}", l);
|
||||
debug!("select response: {}", String::from_utf8_lossy(&l));
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
} else {
|
||||
debug!("BAD/NO response in select: {}", input);
|
||||
Err(MeliError::new(input.to_string()))
|
||||
let ret = String::from_utf8_lossy(&input).to_string();
|
||||
debug!("BAD/NO response in select: {}", &ret);
|
||||
Err(MeliError::new(ret))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_response() {
|
||||
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!(
|
||||
select_response(r).expect("Could not parse IMAP select response"),
|
||||
|
@ -1076,7 +1087,7 @@ fn test_select_response() {
|
|||
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!(
|
||||
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!(
|
||||
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 keywords = Vec::new();
|
||||
|
||||
let mut input = input;
|
||||
while !input.starts_with(')') && !input.is_empty() {
|
||||
if input.starts_with('\\') {
|
||||
while !input.starts_with(b")") && !input.is_empty() {
|
||||
if input.starts_with(b"\\") {
|
||||
input = &input[1..];
|
||||
}
|
||||
let mut match_end = 0;
|
||||
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;
|
||||
}
|
||||
match_end += 1;
|
||||
}
|
||||
|
||||
match &input[..match_end] {
|
||||
"Answered" => {
|
||||
b"Answered" => {
|
||||
ret.set(Flag::REPLIED, true);
|
||||
}
|
||||
"Flagged" => {
|
||||
b"Flagged" => {
|
||||
ret.set(Flag::FLAGGED, true);
|
||||
}
|
||||
"Deleted" => {
|
||||
b"Deleted" => {
|
||||
ret.set(Flag::TRASHED, true);
|
||||
}
|
||||
"Seen" => {
|
||||
b"Seen" => {
|
||||
ret.set(Flag::SEEN, true);
|
||||
}
|
||||
"Draft" => {
|
||||
b"Draft" => {
|
||||
ret.set(Flag::DRAFT, true);
|
||||
}
|
||||
f => {
|
||||
keywords.push(f.to_string());
|
||||
keywords.push(String::from_utf8_lossy(&f).into());
|
||||
}
|
||||
}
|
||||
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>)> {
|
||||
let i = unsafe { std::str::from_utf8_unchecked(input) };
|
||||
match flags(i) {
|
||||
Ok((rest, ret)) => Ok((rest.as_bytes(), ret)),
|
||||
Err(nom::Err::Error(err)) => Err(nom::Err::Error(err.as_bytes())),
|
||||
Err(nom::Err::Failure(err)) => Err(nom::Err::Error(err.as_bytes())),
|
||||
match flags(input) {
|
||||
Ok((rest, ret)) => Ok((rest, ret)),
|
||||
Err(nom::Err::Error(err)) => Err(nom::Err::Error(err)),
|
||||
Err(nom::Err::Failure(err)) => Err(nom::Err::Error(err)),
|
||||
Err(nom::Err::Incomplete(_)) => {
|
||||
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.
|
||||
// ; Refer to section 5.1 for further
|
||||
// ; 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)?;
|
||||
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
|
||||
|
|
|
@ -33,7 +33,7 @@ use crate::error::*;
|
|||
use std::convert::TryInto;
|
||||
|
||||
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 {
|
||||
($mailbox_hash: expr, $($result:expr)+) => {
|
||||
$(if let Err(err) = $result {
|
||||
|
@ -59,7 +59,7 @@ impl ImapConnection {
|
|||
let mut cache_handle = super::cache::DefaultCache::get(self.uid_store.clone())?;
|
||||
#[cfg(feature = "sqlite3")]
|
||||
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 =
|
||||
match super::protocol_parser::untagged_responses(line).map(|(_, v, _)| v) {
|
||||
Ok(None) | Err(_) => {
|
||||
|
@ -233,7 +233,7 @@ impl ImapConnection {
|
|||
self.send_command(b"UID SEARCH RECENT").await
|
||||
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
||||
);
|
||||
match super::protocol_parser::search_results_raw(response.as_bytes())
|
||||
match super::protocol_parser::search_results_raw(&response)
|
||||
.map(|(_, v)| v)
|
||||
.map_err(MeliError::from)
|
||||
{
|
||||
|
@ -339,7 +339,7 @@ impl ImapConnection {
|
|||
debug!(
|
||||
"UID SEARCH RECENT err: {}\nresp: {}",
|
||||
e.to_string(),
|
||||
&response
|
||||
to_str!(&response)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -381,9 +381,9 @@ impl ImapConnection {
|
|||
).await
|
||||
self.read_response(&mut response, RequiredResponses::SEARCH).await
|
||||
);
|
||||
debug!(&response);
|
||||
debug!(to_str!(&response));
|
||||
match super::protocol_parser::search_results(
|
||||
response.split_rn().next().unwrap_or("").as_bytes(),
|
||||
response.split_rn().next().unwrap_or(b""),
|
||||
)
|
||||
.map(|(_, v)| v)
|
||||
{
|
||||
|
@ -392,7 +392,7 @@ impl ImapConnection {
|
|||
return Ok(false);
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(&response);
|
||||
debug!(to_str!(&response));
|
||||
debug!(e);
|
||||
return Ok(false);
|
||||
}
|
||||
|
|
|
@ -74,12 +74,12 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
}
|
||||
};
|
||||
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
|
||||
.select_mailbox(mailbox_hash, &mut response, true)
|
||||
.await?
|
||||
.unwrap();
|
||||
debug!("select response {}", &response);
|
||||
debug!("select response {}", String::from_utf8_lossy(&response));
|
||||
{
|
||||
let mut uidvalidities = uid_store.uidvalidity.lock().unwrap();
|
||||
|
||||
|
@ -153,14 +153,14 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
}
|
||||
watch = now;
|
||||
}
|
||||
if to_str!(&line)
|
||||
if line
|
||||
.split_rn()
|
||||
.filter(|l| {
|
||||
!l.starts_with("+ ")
|
||||
&& !l.starts_with("* ok")
|
||||
&& !l.starts_with("* ok")
|
||||
&& !l.starts_with("* Ok")
|
||||
&& !l.starts_with("* OK")
|
||||
!l.starts_with(b"+ ")
|
||||
&& !l.starts_with(b"* ok")
|
||||
&& !l.starts_with(b"* ok")
|
||||
&& !l.starts_with(b"* Ok")
|
||||
&& !l.starts_with(b"* OK")
|
||||
})
|
||||
.count()
|
||||
== 0
|
||||
|
@ -173,13 +173,13 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
.conn
|
||||
.read_response(&mut response, RequiredResponses::empty())
|
||||
.await?;
|
||||
for l in to_str!(&line).split_rn() {
|
||||
for l in line.split_rn() {
|
||||
debug!("process_untagged {:?}", &l);
|
||||
if l.starts_with("+ ")
|
||||
|| l.starts_with("* ok")
|
||||
|| l.starts_with("* ok")
|
||||
|| l.starts_with("* Ok")
|
||||
|| l.starts_with("* OK")
|
||||
if l.starts_with(b"+ ")
|
||||
|| l.starts_with(b"* ok")
|
||||
|| l.starts_with(b"* ok")
|
||||
|| l.starts_with(b"* Ok")
|
||||
|| l.starts_with(b"* OK")
|
||||
{
|
||||
debug!("ignore continuation mark");
|
||||
continue;
|
||||
|
@ -214,7 +214,7 @@ pub async fn examine_updates(
|
|||
let mut cache_handle = super::cache::DefaultCache::get(uid_store.clone())?;
|
||||
#[cfg(feature = "sqlite3")]
|
||||
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
|
||||
.examine_mailbox(mailbox_hash, &mut response, true)
|
||||
.await?
|
||||
|
@ -249,9 +249,12 @@ pub async fn examine_updates(
|
|||
conn.send_command(b"UID SEARCH RECENT").await?;
|
||||
conn.read_response(&mut response, RequiredResponses::SEARCH)
|
||||
.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() {
|
||||
debug!("search response was empty: {}", response);
|
||||
debug!(
|
||||
"search response was empty: {}",
|
||||
String::from_utf8_lossy(&response)
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let mut cmd = "UID FETCH ".to_string();
|
||||
|
|
|
@ -177,9 +177,19 @@ macro_rules! is_whitespace {
|
|||
pub trait BytesExt {
|
||||
fn rtrim(&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 find(&self, needle: &[u8]) -> Option<usize>;
|
||||
fn rfind(&self, needle: &[u8]) -> Option<usize>;
|
||||
fn find<T: AsRef<[u8]>>(&self, needle: T) -> 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 is_quoted(&self) -> bool;
|
||||
}
|
||||
|
@ -202,8 +212,10 @@ impl BytesExt for [u8] {
|
|||
fn trim(&self) -> &[u8] {
|
||||
self.rtrim().ltrim()
|
||||
}
|
||||
|
||||
// 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() {
|
||||
return None;
|
||||
}
|
||||
|
@ -211,7 +223,8 @@ impl BytesExt for [u8] {
|
|||
.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() {
|
||||
return None;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue