Repository: deltachat/deltachat-core-rust (language: python)

Issue:
No HTML if multiple text parts are cut
I have received an email which consists of a short `text/plain` part, long inline `message/rfc822` (forwarded mail) and a `text/plain` attachment.

The second part is cut, but "Show Full Message..." is displayed under the first part and displays the same short message I can already see. The second part I can only fully read in the message info.
No HTML if multiple text parts are cut
I have received an email which consists of a short `text/plain` part, long inline `message/rfc822` (forwarded mail) and a `text/plain` attachment.

The second part is cut, but "Show Full Message..." is displayed under the first part and displays the same short message I can already see. The second part I can only fully read in the message info.


Discussion / hints:
The problem starts with a mimeparser, which stores `is_mime_modified` for the whole message rather than a part:
https://github.com/deltachat/deltachat-core-rust/blob/4767f1ce743d2170142c3cc126838d55505eb807/src/mimeparser.rs#L103
Whether to display "Show Full Message..." should be decided per part, not for all parts at once. In extreme case it should be possible to receive an email consisting of 3 multipart/alternative parts, each of which contains a text and HTML subpart in turn. In this case `receive_imf` should add 3 message bubbles, each with a "Show Full Message..." link. This also means that `decoded_data` should be per-part, not a single one for the whole message, so each message bubble can have a different "full message":
https://github.com/deltachat/deltachat-core-rust/blob/4767f1ce743d2170142c3cc126838d55505eb807/src/receive_imf.rs#L1077
Before, `mime_headers` column was always storing the whole decrypted message, but now it should be able to store message parts which contain the relevant HTML part.
So this mostly looks like a refactoring, i can try to implement this, but if there are possible problems, let me know
I tried to forward a long message using Thunderbird to Delta Chat, adding also a long text and an attachment, but i see only one message bubble with the attachment, with the correctly truncated text, and if i press on `Show Full Message...`, i see the full text and the forwarded message. So, i can't reproduce it with Thunderbird, seems it never creates messages with multiple `multipart/alternative` parts. @link2xt, do you have an example message? I can try to form such a message manually, just interesting where such messages come from
I found the mail, it seems to be produced by mutt.

There are actually two parts, first is `text/plain` and second is `message/rfc822`.
`message/rfc822` is a forwarded mail consisting of `text/plain` text part and a `text/plain` attachment.

Here is how decrypted part looks like:
```
Content-Type: multipart/mixed; boundary="zRs3OquGy6eU58KF"


--zRs3OquGy6eU58KF
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hello!

Best regards,
Alice

--zRs3OquGy6eU58KF
Content-Type: message/rfc822
Content-Disposition: inline

From: AAA <aaa@example.org>
To: Alice <alice@example.org>
Subject: Some subject
Date: Fri, 2 Jun 2023 12:29:17 +0000
Message-ID: <foobar@localhost>
In-Reply-To: <barbaz@localhost>
Content-Type: multipart/mixed;
	boundary="_innerboundary_"
MIME-Version: 1.0

--_innerboundary_
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: quoted-printable

Hi
Hi
Hi
Tons of text here
Tons of text
...

--=20

Kind regards,

Bob

--_innerboundary_
Content-Type: text/plain; name="deltachat-log.txt"
Content-Description: deltachat-log.txt
Content-Disposition: attachment; filename="deltachat-log.txt";
	size=55254; creation-date="Fri, 02 Jun 2023 11:33:49 GMT";
	modification-date="Fri, 02 Jun 2023 12:29:17 GMT"

Huge huge Delta Chat log here.

--_innerboundary_--

--zRs3OquGy6eU58KF--
```
The problem starts with a mimeparser, which stores `is_mime_modified` for the whole message rather than a part:
https://github.com/deltachat/deltachat-core-rust/blob/4767f1ce743d2170142c3cc126838d55505eb807/src/mimeparser.rs#L103
Whether to display "Show Full Message..." should be decided per part, not for all parts at once. In extreme case it should be possible to receive an email consisting of 3 multipart/alternative parts, each of which contains a text and HTML subpart in turn. In this case `receive_imf` should add 3 message bubbles, each with a "Show Full Message..." link. This also means that `decoded_data` should be per-part, not a single one for the whole message, so each message bubble can have a different "full message":
https://github.com/deltachat/deltachat-core-rust/blob/4767f1ce743d2170142c3cc126838d55505eb807/src/receive_imf.rs#L1077
Before, `mime_headers` column was always storing the whole decrypted message, but now it should be able to store message parts which contain the relevant HTML part.
So this mostly looks like a refactoring, i can try to implement this, but if there are possible problems, let me know
I tried to forward a long message using Thunderbird to Delta Chat, adding also a long text and an attachment, but i see only one message bubble with the attachment, with the correctly truncated text, and if i press on `Show Full Message...`, i see the full text and the forwarded message. So, i can't reproduce it with Thunderbird, seems it never creates messages with multiple `multipart/alternative` parts. @link2xt, do you have an example message? I can try to form such a message manually, just interesting where such messages come from
I found the mail, it seems to be produced by mutt.

There are actually two parts, first is `text/plain` and second is `message/rfc822`.
`message/rfc822` is a forwarded mail consisting of `text/plain` text part and a `text/plain` attachment.

Here is how decrypted part looks like:
```
Content-Type: multipart/mixed; boundary="zRs3OquGy6eU58KF"


--zRs3OquGy6eU58KF
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hello!

Best regards,
Alice

--zRs3OquGy6eU58KF
Content-Type: message/rfc822
Content-Disposition: inline

From: AAA <aaa@example.org>
To: Alice <alice@example.org>
Subject: Some subject
Date: Fri, 2 Jun 2023 12:29:17 +0000
Message-ID: <foobar@localhost>
In-Reply-To: <barbaz@localhost>
Content-Type: multipart/mixed;
	boundary="_innerboundary_"
MIME-Version: 1.0

--_innerboundary_
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: quoted-printable

Hi
Hi
Hi
Tons of text here
Tons of text
...

--=20

Kind regards,

Bob

--_innerboundary_
Content-Type: text/plain; name="deltachat-log.txt"
Content-Description: deltachat-log.txt
Content-Disposition: attachment; filename="deltachat-log.txt";
	size=55254; creation-date="Fri, 02 Jun 2023 11:33:49 GMT";
	modification-date="Fri, 02 Jun 2023 12:29:17 GMT"

Huge huge Delta Chat log here.

--_innerboundary_--

--zRs3OquGy6eU58KF--
```

Fix patch:
diff --git a/src/html.rs b/src/html.rs
index e32ee43575..c58f099c83 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -7,6 +7,8 @@
 //! `MsgId.get_html()` will return HTML -
 //! this allows nice quoting, handling linebreaks properly etc.
 
+use std::mem;
+
 use anyhow::{Context as _, Result};
 use base64::Engine as _;
 use lettre_email::mime::Mime;
@@ -77,21 +79,26 @@ fn get_mime_multipart_type(ctype: &ParsedContentType) -> MimeMultipartType {
 struct HtmlMsgParser {
     pub html: String,
     pub plain: Option<PlainText>,
+    pub(crate) msg_html: String,
 }
 
 impl HtmlMsgParser {
     /// Function takes a raw mime-message string,
     /// searches for the main-text part
     /// and returns that as parser.html
-    pub async fn from_bytes(context: &Context, rawmime: &[u8]) -> Result<Self> {
+    pub async fn from_bytes<'a>(
+        context: &Context,
+        rawmime: &'a [u8],
+    ) -> Result<(Self, mailparse::ParsedMail<'a>)> {
         let mut parser = HtmlMsgParser {
             html: "".to_string(),
             plain: None,
+            msg_html: "".to_string(),
         };
 
-        let parsedmail = mailparse::parse_mail(rawmime)?;
+        let parsedmail = mailparse::parse_mail(rawmime).context("Failed to parse mail")?;
 
-        parser.collect_texts_recursive(&parsedmail).await?;
+        parser.collect_texts_recursive(context, &parsedmail).await?;
 
         if parser.html.is_empty() {
             if let Some(plain) = &parser.plain {
@@ -100,8 +107,8 @@ impl HtmlMsgParser {
         } else {
             parser.cid_to_data_recursive(context, &parsedmail).await?;
         }
-
-        Ok(parser)
+        parser.html += &mem::take(&mut parser.msg_html);
+        Ok((parser, parsedmail))
     }
 
     /// Function iterates over all mime-parts
@@ -114,12 +121,13 @@ impl HtmlMsgParser {
     /// therefore we use the first one.
     async fn collect_texts_recursive<'a>(
         &'a mut self,
+        context: &'a Context,
         mail: &'a mailparse::ParsedMail<'a>,
     ) -> Result<()> {
         match get_mime_multipart_type(&mail.ctype) {
             MimeMultipartType::Multiple => {
                 for cur_data in &mail.subparts {
-                    Box::pin(self.collect_texts_recursive(cur_data)).await?
+                    Box::pin(self.collect_texts_recursive(context, cur_data)).await?
                 }
                 Ok(())
             }
@@ -128,8 +136,35 @@ impl HtmlMsgParser {
                 if raw.is_empty() {
                     return Ok(());
                 }
-                let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
-                Box::pin(self.collect_texts_recursive(&mail)).await
+                let (parser, mail) = Box::pin(HtmlMsgParser::from_bytes(context, &raw)).await?;
+                if !parser.html.is_empty() {
+                    let mut text = "\r\n\r\n".to_string();
+                    for h in mail.headers {
+                        let key = h.get_key();
+                        if matches!(
+                            key.to_lowercase().as_str(),
+                            "date"
+                                | "from"
+                                | "sender"
+                                | "reply-to"
+                                | "to"
+                                | "cc"
+                                | "bcc"
+                                | "subject"
+                        ) {
+                            text += &format!("{key}: {}\r\n", h.get_value());
+                        }
+                    }
+                    text += "\r\n";
+                    self.msg_html += &PlainText {
+                        text,
+                        flowed: false,
+                        delsp: false,
+                    }
+                    .to_html();
+                    self.msg_html += &parser.html;
+                }
+                Ok(())
             }
             MimeMultipartType::Single => {
                 let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
@@ -175,14 +210,7 @@ impl HtmlMsgParser {
                 }
                 Ok(())
             }
-            MimeMultipartType::Message => {
-                let raw = mail.get_body_raw()?;
-                if raw.is_empty() {
-                    return Ok(());
-                }
-                let mail = mailparse::parse_mail(&raw).context("failed to parse mail")?;
-                Box::pin(self.cid_to_data_recursive(context, &mail)).await
-            }
+            MimeMultipartType::Message => Ok(()),
             MimeMultipartType::Single => {
                 let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
                 if mimetype.type_() == mime::IMAGE {
@@ -240,7 +268,7 @@ impl MsgId {
                     warn!(context, "get_html: parser error: {:#}", err);
                     Ok(None)
                 }
-                Ok(parser) => Ok(Some(parser.html)),
+                Ok((parser, _)) => Ok(Some(parser.html)),
             }
         } else {
             warn!(context, "get_html: no mime for {}", self);
@@ -274,7 +302,7 @@ mod tests {
     async fn test_htmlparse_plain_unspecified() {
         let t = TestContext::new().await;
         let raw = include_bytes!("../test-data/message/text_plain_unspecified.eml");
-        let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
+        let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
         assert_eq!(
             parser.html,
             r#"<!DOCTYPE html>
@@ -292,7 +320,7 @@ This message does not have Content-Type nor Subject.<br/>
     async fn test_htmlparse_plain_iso88591() {
         let t = TestContext::new().await;
         let raw = include_bytes!("../test-data/message/text_plain_iso88591.eml");
-        let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
+        let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
         assert_eq!(
             parser.html,
             r#"<!DOCTYPE html>
@@ -310,7 +338,7 @@ message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
     async fn test_htmlparse_plain_flowed() {
         let t = TestContext::new().await;
         let raw = include_bytes!("../test-data/message/text_plain_flowed.eml");
-        let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
+        let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
         assert!(parser.plain.unwrap().flowed);
         assert_eq!(
             parser.html,
@@ -332,7 +360,7 @@ and will be wrapped as usual.<br/>
     async fn test_htmlparse_alt_plain() {
         let t = TestContext::new().await;
         let raw = include_bytes!("../test-data/message/text_alt_plain.eml");
-        let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
+        let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
         assert_eq!(
             parser.html,
             r#"<!DOCTYPE html>
@@ -353,7 +381,7 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
     async fn test_htmlparse_html() {
         let t = TestContext::new().await;
         let raw = include_bytes!("../test-data/message/text_html.eml");
-        let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
+        let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
 
         // on windows, `\r\n` linends are returned from mimeparser,
         // however, rust multiline-strings use just `\n`;
@@ -371,7 +399,7 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
     async fn test_htmlparse_alt_html() {
         let t = TestContext::new().await;
         let raw = include_bytes!("../test-data/message/text_alt_html.eml");
-        let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
+        let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
         assert_eq!(
             parser.html.replace('\r', ""), // see comment in test_htmlparse_html()
             r##"<html>
@@ -386,7 +414,7 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
     async fn test_htmlparse_alt_plain_html() {
         let t = TestContext::new().await;
         let raw = include_bytes!("../test-data/message/text_alt_plain_html.eml");
-        let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
+        let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
         assert_eq!(
             parser.html.replace('\r', ""), // see comment in test_htmlparse_html()
             r##"<html>
@@ -411,7 +439,7 @@ test some special html-characters as &lt; &gt; and &amp; but also &quot; and &#x
         assert!(test.find("data:").is_none());
 
         // parsing converts cid: to data:
-        let parser = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
+        let (parser, _) = HtmlMsgParser::from_bytes(&t.ctx, raw).await.unwrap();
         assert!(parser.html.contains("<html>"));
         assert!(!parser.html.contains("Content-Id:"));
         assert!(parser.html.contains("data:image/jpeg;base64,/9j/4AAQ"));
diff --git a/src/mimeparser.rs b/src/mimeparser.rs
index 52219a8f0a..6775346853 100644
--- a/src/mimeparser.rs
+++ b/src/mimeparser.rs
@@ -105,14 +105,12 @@ pub(crate) struct MimeMessage {
     /// received.
     pub(crate) footer: Option<String>,
 
-    // if this flag is set, the parts/text/etc. are just close to the original mime-message;
-    // clients should offer a way to view the original message in this case
+    /// If set, this is a modified MIME message; clients should offer a way to view the original
+    /// MIME message in this case.
     pub is_mime_modified: bool,
 
-    /// The decrypted, raw mime structure.
-    ///
-    /// This is non-empty iff `is_mime_modified` and the message was actually encrypted. It is used
-    /// for e.g. late-parsing HTML.
+    /// Decrypted, raw MIME structure. Nonempty iff `is_mime_modified` and the message was actually
+    /// encrypted.
     pub decoded_data: Vec<u8>,
 
     /// Hop info for debugging.
diff --git a/src/receive_imf.rs b/src/receive_imf.rs
index 0d2d6ee97d..3a58c5473b 100644
--- a/src/receive_imf.rs
+++ b/src/receive_imf.rs
@@ -1406,10 +1406,11 @@ async fn add_parts(
     // we save the full mime-message and add a flag
     // that the ui should show button to display the full message.
 
-    // a flag used to avoid adding "show full message" button to multiple parts of the message.
-    let mut save_mime_modified = mime_parser.is_mime_modified;
+    // We add "Show Full Message" button to the last message bubble (part) if this flag evaluates to
+    // `true` finally.
+    let mut save_mime_modified = false;
 
-    let mime_headers = if save_mime_headers || save_mime_modified {
+    let mime_headers = if save_mime_headers || mime_parser.is_mime_modified {
         let headers = if !mime_parser.decoded_data.is_empty() {
             mime_parser.decoded_data.clone()
         } else {
@@ -1475,7 +1476,8 @@ async fn add_parts(
         }
     }
 
-    for part in &mime_parser.parts {
+    let mut parts = mime_parser.parts.iter().peekable();
+    while let Some(part) = parts.next() {
         if part.is_reaction {
             let reaction_str = simplify::remove_footers(part.msg.as_str());
             let is_incoming_fresh = mime_parser.incoming && !seen && !fetching_existing_messages;
@@ -1519,14 +1521,11 @@ async fn add_parts(
         } else {
             (&part.msg, part.typ)
         };
-
         let part_is_empty =
             typ == Viewtype::Text && msg.is_empty() && part.param.get(Param::Quote).is_none();
-        let mime_modified = save_mime_modified && !part_is_empty;
-        if mime_modified {
-            // Avoid setting mime_modified for more than one part.
-            save_mime_modified = false;
-        }
+
+        save_mime_modified |= mime_parser.is_mime_modified && !part_is_empty && !hidden;
+        let save_mime_modified = save_mime_modified && parts.peek().is_none();
 
         if part.typ == Viewtype::Text {
             let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
@@ -1546,8 +1545,7 @@ async fn add_parts(
 
         // If you change which information is skipped if the message is trashed,
         // also change `MsgId::trash()` and `delete_expired_messages()`
-        let trash =
-            chat_id.is_trash() || (is_location_kml && msg.is_empty() && typ == Viewtype::Text);
+        let trash = chat_id.is_trash() || (is_location_kml && part_is_empty && !save_mime_modified);
 
         let row_id = context
             .sql
@@ -1610,14 +1608,14 @@ RETURNING id
                     },
                     hidden,
                     part.bytes as isize,
-                    if (save_mime_headers || mime_modified) && !trash {
+                    if (save_mime_headers || save_mime_modified) && !trash {
                         mime_headers.clone()
                     } else {
                         Vec::new()
                     },
                     mime_in_reply_to,
                     mime_references,
-                    mime_modified,
+                    save_mime_modified,
                     part.error.as_deref().unwrap_or_default(),
                     ephemeral_timer,
                     ephemeral_timestamp,


Test patch:
diff --git a/src/receive_imf/tests.rs b/src/receive_imf/tests.rs
index f798407f89..0bb83ceb95 100644
--- a/src/receive_imf/tests.rs
+++ b/src/receive_imf/tests.rs
@@ -3827,6 +3827,61 @@ async fn test_messed_up_message_id() -> Result<()> {
     Ok(())
 }
 
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
+async fn test_big_forwarded_with_big_attachment() -> Result<()> {
+    let t = &TestContext::new_bob().await;
+
+    let raw = include_bytes!("../../test-data/message/big_forwarded_with_big_attachment.eml");
+    let rcvd = receive_imf(t, raw, false).await?.unwrap();
+    assert_eq!(rcvd.msg_ids.len(), 3);
+
+    let msg = Message::load_from_db(t, rcvd.msg_ids[0]).await?;
+    assert_eq!(msg.get_viewtype(), Viewtype::Text);
+    assert_eq!(msg.get_text(), "Hello!");
+    assert!(!msg.has_html());
+
+    let msg = Message::load_from_db(t, rcvd.msg_ids[1]).await?;
+    assert_eq!(msg.get_viewtype(), Viewtype::Text);
+    assert!(msg
+        .get_text()
+        .starts_with("this text with 42 chars is just repeated."));
+    assert!(msg.get_text().ends_with("[...]"));
+    assert!(!msg.has_html());
+
+    let msg = Message::load_from_db(t, rcvd.msg_ids[2]).await?;
+    assert_eq!(msg.get_viewtype(), Viewtype::File);
+    assert!(msg.has_html());
+    let html = msg.id.get_html(t).await?.unwrap();
+    let tail = html
+        .split_once("Hello!")
+        .unwrap()
+        .1
+        .split_once("From: AAA")
+        .unwrap()
+        .1
+        .split_once("aaa@example.org")
+        .unwrap()
+        .1
+        .split_once("To: Alice")
+        .unwrap()
+        .1
+        .split_once("alice@example.org")
+        .unwrap()
+        .1
+        .split_once("Subject: Some subject")
+        .unwrap()
+        .1
+        .split_once("Date: Fri, 2 Jun 2023 12:29:17 +0000")
+        .unwrap()
+        .1;
+    assert_eq!(
+        tail.matches("this text with 42 chars is just repeated.")
+            .count(),
+        128
+    );
+    Ok(())
+}
+
 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
 async fn test_mua_user_adds_member() -> Result<()> {
     let t = TestContext::new_alice().await;
diff --git a/test-data/message/big_forwarded_with_big_attachment.eml b/test-data/message/big_forwarded_with_big_attachment.eml
new file mode 100644
index 0000000000..f6ae391328
--- /dev/null
+++ b/test-data/message/big_forwarded_with_big_attachment.eml
@@ -0,0 +1,305 @@
+From: Alice <alice@example.org>
+To: Bob <bob@example.net>
+Date: Fri, 2 Jun 2023 13:29:17 +0000
+Message-ID: <foobar1@localhost>
+Content-Type: multipart/mixed; boundary="zRs3OquGy6eU58KF"
+
+
+--zRs3OquGy6eU58KF
+Content-Type: text/plain; charset=us-ascii
+Content-Disposition: inline
+
+Hello!
+
+--zRs3OquGy6eU58KF
+Content-Type: message/rfc822
+Content-Disposition: inline
+
+From: AAA <aaa@example.org>
+To: Alice <alice@example.org>
+Subject: Some subject
+Date: Fri, 2 Jun 2023 12:29:17 +0000
+Message-ID: <foobar@localhost>
+In-Reply-To: <barbaz@localhost>
+Content-Type: multipart/mixed;
+	boundary="_innerboundary_"
+MIME-Version: 1.0
+
+--_innerboundary_
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: quoted-printable
+
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+
+--=20
+
+Kind regards,
+
+Bob
+
+--_innerboundary_
+Content-Type: text/plain; name="deltachat-log.txt"
+Content-Description: deltachat-log.txt
+Content-Disposition: attachment; filename="deltachat-log.txt";
+	size=55254; creation-date="Fri, 02 Jun 2023 11:33:49 GMT";
+	modification-date="Fri, 02 Jun 2023 12:29:17 GMT"
+
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+this text with 42 chars is just repeated.
+
+--_innerboundary_--
+
+--zRs3OquGy6eU58KF--