diff --git a/src/blob.rs b/src/blob.rs index 5f9ae40..e20303c 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -53,6 +53,32 @@ pub(crate) async fn get_mail_blob( Ok(resp.into_iter().next().expect("checked length")) } +pub(crate) async fn get_mail_draft_blob( + client: &Client, + session: &Session, + archive_id: &str, + blob_id: &str, +) -> Result { + let resp: Vec = client + .do_json(Request { + method: Method::GET, + host: DEFAULT_HOST, + prefix: Prefix::Tutanota, + path: &format!("maildetailsdraft/{archive_id}"), + data: &(), + access_token: Some(&session.access_token), + query: &[("ids", &[blob_id].join(","))], + }) + .await + .context("blob download")?; + + if resp.len() != 1 { + bail!("invalid reponse length") + } + + Ok(resp.into_iter().next().expect("checked length")) +} + pub(crate) async fn get_attachment_blob( client: &Client, session: &Session, diff --git a/src/eml.rs b/src/eml.rs index e61bfb6..e9d2cca 100644 --- a/src/eml.rs +++ b/src/eml.rs @@ -198,6 +198,7 @@ mod tests { mail_id: "mail_id".to_owned(), archive_id: "archive_id".to_owned(), blob_id: "blob_id".to_owned(), + is_draft: false, session_key: Key::Aes256([0; 32]), date: DateTime::parse_from_rfc3339("2020-03-04T11:22:33Z") .unwrap() @@ -242,6 +243,7 @@ mod tests { mail_id: "mail_id".to_owned(), archive_id: "archive_id".to_owned(), blob_id: "blob_id".to_owned(), + is_draft: false, session_key: Key::Aes256([0; 32]), date: DateTime::parse_from_rfc3339("2020-03-04T11:22:33Z") .unwrap() @@ -283,6 +285,7 @@ mod tests { mail_id: "mail_id".to_owned(), archive_id: "archive_id".to_owned(), blob_id: "blob_id".to_owned(), + is_draft: false, session_key: Key::Aes256([0; 32]), date: DateTime::parse_from_rfc3339("2020-03-04T11:22:33Z") .unwrap() @@ -324,6 +327,7 @@ mod tests { mail_id: "mail_id".to_owned(), archive_id: "archive_id".to_owned(), blob_id: "blob_id".to_owned(), + is_draft: false, session_key: Key::Aes256([0; 32]), date: DateTime::parse_from_rfc3339("2020-03-04T11:22:33Z") .unwrap() @@ -370,6 +374,7 @@ mod tests { mail_id: "mail_id".to_owned(), archive_id: "archive_id".to_owned(), blob_id: "blob_id".to_owned(), + is_draft: false, session_key: Key::Aes256([0; 32]), date: DateTime::parse_from_rfc3339("2020-03-04T11:22:33Z") .unwrap() @@ -412,6 +417,7 @@ mod tests { mail_id: "mail_id".to_owned(), archive_id: "archive_id".to_owned(), blob_id: "blob_id".to_owned(), + is_draft: false, session_key: Key::Aes256([0; 32]), date: DateTime::parse_from_rfc3339("2020-03-04T11:22:33Z") .unwrap() @@ -502,6 +508,7 @@ mod tests { mail_id: "mail_id".to_owned(), archive_id: "archive_id".to_owned(), blob_id: "blob_id".to_owned(), + is_draft: false, session_key: Key::Aes256([0; 32]), date: DateTime::parse_from_rfc3339("2020-03-04T11:22:33Z") .unwrap() @@ -545,6 +552,7 @@ mod tests { mail_id: "mail_id".to_owned(), archive_id: "archive_id".to_owned(), blob_id: "blob_id".to_owned(), + is_draft: false, session_key: Key::Aes256([0; 32]), date: DateTime::parse_from_rfc3339("2020-03-04T11:22:33Z") .unwrap() diff --git a/src/mails.rs b/src/mails.rs index 704c435..0ffcbf0 100644 --- a/src/mails.rs +++ b/src/mails.rs @@ -7,7 +7,7 @@ use reqwest::Method; use tracing::warn; use crate::{ - blob::{get_attachment_blob, get_mail_blob}, + blob::{get_attachment_blob, get_mail_blob, get_mail_draft_blob}, client::{Client, Prefix, Request, DEFAULT_HOST}, compression::decompress_value, crypto::encryption::{decrypt_key, decrypt_value}, @@ -44,6 +44,7 @@ pub(crate) struct Mail { pub(crate) mail_id: String, pub(crate) archive_id: String, pub(crate) blob_id: String, + pub(crate) is_draft: bool, pub(crate) session_key: Key, pub(crate) date: DateTime, pub(crate) subject: String, @@ -88,11 +89,23 @@ impl Mail { let sender = Address::decode(resp.sender, session_key).context("decode sender")?; + let ([archive_id, blob_id], is_draft) = match (resp.mail_details, resp.mail_details_draft) { + (Some(_), Some(_)) => { + bail!("mail as both `mailDetails` and `mailDetailsDraft`"); + } + (Some(x), None) => (x, false), + (None, Some(x)) => (x, true), + (None, None) => { + bail!("mail has neither `mailDetails` nor `mailDetailsDraft`"); + } + }; + Ok(Self { folder_id, mail_id: resp.id[1].clone(), - archive_id: resp.mail_details[0].clone(), - blob_id: resp.mail_details[1].clone(), + archive_id, + blob_id, + is_draft, session_key, date: resp.received_date.0, subject, @@ -110,10 +123,17 @@ impl Mail { client: &Client, session: &Session, ) -> Result { - let mail_details = get_mail_blob(client, session, &self.archive_id, &self.blob_id) - .await - .context("download mail details")? - .details; + let mail_details = if self.is_draft { + get_mail_draft_blob(client, session, &self.archive_id, &self.blob_id) + .await + .context("download mail draft details")? + .details + } else { + get_mail_blob(client, session, &self.archive_id, &self.blob_id) + .await + .context("download mail details")? + .details + }; let body = decrypt_and_decompress( self.session_key, diff --git a/src/proto/messages.rs b/src/proto/messages.rs index 048fa94..964eb48 100644 --- a/src/proto/messages.rs +++ b/src/proto/messages.rs @@ -164,7 +164,8 @@ pub(crate) struct MailReponse { #[serde(rename = "_id")] pub(crate) id: [String; 2], - pub(crate) mail_details: [String; 2], + pub(crate) mail_details: Option<[String; 2]>, + pub(crate) mail_details_draft: Option<[String; 2]>, pub(crate) received_date: UnixDate, pub(crate) subject: Base64String, diff --git a/tests/reference/2024-09-22-19h54m31s-test.eml b/tests/reference/2024-09-22-19h54m31s-test.eml new file mode 100644 index 0000000..7d91a93 --- /dev/null +++ b/tests/reference/2024-09-22-19h54m31s-test.eml @@ -0,0 +1,16 @@ +From: =?UTF-8?B??= +MIME-Version: 1.0 +Subject: =?UTF-8?B?dGVzdA==?= +To: =?UTF-8?B?WA==?= +Content-Type: multipart/related; boundary="----------79Bu5A16qPEYcVIZL@tutanota" + +------------79Bu5A16qPEYcVIZL@tutanota +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: base64 + +PGRpdiBkaXI9ImF1dG8iPjxicj48L2Rpdj48ZGl2IGRpcj0iYXV0byI+PGJyPjwvZGl2PjxkaXYgZG +lyPSJhdXRvIj4tLSA8YnI+PC9kaXY+PGRpdiBkaXI9ImF1dG8iPiBTZW50IHdpdGggVHV0YTsgZW5q +b3kgc2VjdXJlICZhbXA7IGFkLWZyZWUgZW1haWxzOiA8YnI+PC9kaXY+PGRpdiBkaXI9ImF1dG8iPi +BodHRwczovL3R1dGEuY29tPGJyPjwvZGl2Pg== + +------------79Bu5A16qPEYcVIZL@tutanota-- \ No newline at end of file