diff --git a/melib/src/imap/operations.rs b/melib/src/imap/operations.rs index 5c052ed9..b6c4d47b 100644 --- a/melib/src/imap/operations.rs +++ b/melib/src/imap/operations.rs @@ -51,6 +51,99 @@ impl ImapOp { } } +impl ImapOp { + async fn fetch(self) -> Result> { + 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> { 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();