melib/imap: treat server input as bytes

Server input was assumed valid ascii and converted haphazardly to &str.
Don't do that, since it might not be valid UTF8.
memfd
Manos Pitsidianakis 2020-09-16 13:07:26 +03:00
parent 366e557e1c
commit 3618bdcffb
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
8 changed files with 366 additions and 332 deletions

View File

@ -44,7 +44,7 @@ use crate::backends::{
use crate::conf::AccountSettings;
use crate::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());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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