melib/imap: support fetching with BODY[] for buggy servers

It appears icloud IMAP servers do not support fetching RFC822 items with
the FETCH command. They reply to the fetch successfully but with the
item missing. To support this clearly non-standard and buggy behavior,
retry one more time by fetching with BODY[] instead if the RFC822 is
missing.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/453/head
Manos Pitsidianakis 3 months ago
parent 8a74920dc7
commit 197132cca0
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -51,6 +51,99 @@ impl ImapOp {
}
}
impl ImapOp {
async fn fetch(self) -> Result<Vec<u8>> {
let mut response = Vec::with_capacity(8 * 1024);
let mut use_body = false;
let (_uid, _flags, body) = loop {
{
let mut conn = timeout(self.uid_store.timeout, self.connection.lock()).await?;
conn.connect().await?;
conn.examine_mailbox(self.mailbox_hash, &mut response, false)
.await?;
conn.send_command(CommandBody::fetch(
self.uid,
vec![
MessageDataItemName::Flags,
if use_body {
MessageDataItemName::BodyExt {
section: None,
partial: None,
peek: false,
}
} else {
MessageDataItemName::Rfc822
},
],
true,
)?)
.await?;
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
.await?;
}
log::trace!(
"fetch response is {} bytes and {} lines",
response.len(),
String::from_utf8_lossy(&response).lines().count()
);
let mut results = protocol_parser::fetch_responses(&response)?.1;
if results.len() > 1 {
return Err(
Error::new(format!("Invalid/unexpected response: {:?}", response))
.set_summary(format!("Message with UID {} was not found.", self.uid))
.set_kind(ErrorKind::ProtocolError),
);
}
let Some(fetch_response) = results.pop() else {
// https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.8:
//
// A non-existent unique identifier is ignored without any error message
// generated. Thus, it is possible for a UID FETCH command to return an OK
// without any data or a UID COPY or UID STORE to return an OK without
// performing any operations.
return Err(Error::new("Not found")
.set_summary(format!("Message with UID {} was not found.", self.uid))
.set_kind(ErrorKind::NotFound));
};
let FetchResponse {
uid: Some(_uid),
flags: _flags,
body: Some(body),
..
} = fetch_response
else {
if !use_body {
use_body = true;
continue;
}
return Err(Error::new("Invalid/unexpected response from server")
.set_summary(format!("Message with UID {} was not found.", self.uid))
.set_details(format!("Full response: {:?}", fetch_response))
.set_kind(ErrorKind::ProtocolError));
};
break (_uid, _flags, body);
};
if _uid != self.uid {
return Err(Error::new("Invalid/unexpected response from server")
.set_summary(format!("Message with UID {} was not found.", self.uid))
.set_details(format!(
"Requested UID {} but UID FETCH response was for UID {}. Full response: {:?}",
self.uid,
_uid,
String::from_utf8_lossy(&response)
))
.set_kind(ErrorKind::ProtocolError));
}
let mut bytes_cache = self.uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(self.uid).or_default();
if let Some((_flags, _)) = _flags {
cache.flags = Some(_flags);
}
cache.bytes = Some(body.to_vec());
Ok(body.to_vec())
}
}
impl BackendOp for ImapOp {
fn as_bytes(&self) -> ResultFuture<Vec<u8>> {
let connection = self.connection.clone();
@ -64,76 +157,14 @@ impl BackendOp for ImapOp {
cache.bytes.is_some()
};
if !exists_in_cache {
let mut response = Vec::with_capacity(8 * 1024);
{
let mut conn = timeout(uid_store.timeout, connection.lock()).await?;
conn.connect().await?;
conn.examine_mailbox(mailbox_hash, &mut response, false)
.await?;
conn.send_command(CommandBody::fetch(
uid,
vec![MessageDataItemName::Flags, MessageDataItemName::Rfc822],
true,
)?)
.await?;
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
.await?;
}
log::trace!(
"fetch response is {} bytes and {} lines",
response.len(),
String::from_utf8_lossy(&response).lines().count()
);
let mut results = protocol_parser::fetch_responses(&response)?.1;
if results.len() > 1 {
return Err(
Error::new(format!("Invalid/unexpected response: {:?}", response))
.set_summary(format!("Message with UID {} was not found.", uid))
.set_kind(ErrorKind::ProtocolError),
);
}
let Some(fetch_response) = results.pop() else {
// https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.8:
//
// A non-existent unique identifier is ignored without any error message
// generated. Thus, it is possible for a UID FETCH command to return an OK
// without any data or a UID COPY or UID STORE to return an OK without
// performing any operations.
return Err(Error::new("Not found")
.set_summary(format!("Message with UID {} was not found.", uid))
.set_kind(ErrorKind::NotFound));
};
let FetchResponse {
uid: Some(_uid),
flags: _flags,
body: Some(body),
..
} = fetch_response
else {
return Err(Error::new("Invalid/unexpected response from server")
.set_summary(format!("Message with UID {} was not found.", uid))
.set_details(format!("Full response: {:?}", fetch_response))
.set_kind(ErrorKind::ProtocolError));
};
if _uid != uid {
return Err(Error::new("Invalid/unexpected response from server")
.set_summary(format!("Message with UID {} was not found.", uid))
.set_details(format!(
"Requested UID {} but UID FETCH response was for UID {}. Full \
response: {:?}",
uid,
_uid,
String::from_utf8_lossy(&response)
))
.set_kind(ErrorKind::ProtocolError));
}
let mut bytes_cache = uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(uid).or_default();
if let Some((_flags, _)) = _flags {
cache.flags = Some(_flags);
return Self {
connection,
mailbox_hash,
uid,
uid_store,
}
cache.bytes = Some(body.to_vec());
return Ok(body.to_vec());
.fetch()
.await;
}
let mut bytes_cache = uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(uid).or_default();

Loading…
Cancel
Save