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

Issue:
Crash on start after update to 1.48.3
I use DeltaChat for more than 5 years and got this error after updating to version 1.48.3 from FDroid:

- Android version: 13 (GrapheneOS)
- Device: Google Pixel 4 flame)
- Delta Chat version: 1.48.3
- Expected behavior: Normal startup
- Actual behavior: App terminates after 1-2sec
- Steps to reproduce the problem: start the application
- Screenshots: none
- Logs:
```
type: crash
osVersion: google/flame/flame:13/TP1A.221005.002.B2/2024092100:user/release-keys
package: com.b44t.messenger:7012
process: com.b44t.messenger
processUptime: 0 + 0 ms
installer: org.fdroid.fdroid

signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'range end index 33 out of range for slice of length 4'

backtrace:
      #00 pc 000000000005b004  /apex/com.android.runtime/lib64/bionic/libc.so (abort+164) (BuildId: 61deb31c5b39d5f735b66f2155a38a77)
      deltachat/deltachat-android#1 pc 0000000000cd1784  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#2 pc 0000000000cd1658  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#3 pc 0000000000e0d5a0  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#4 pc 0000000000e101c8  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#5 pc 0000000000e0feb4  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#6 pc 0000000000e0fe1c  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#7 pc 0000000000e0fe14  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#8 pc 00000000008a8050  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#9 pc 00000000008a8c4c  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#10 pc 000000000079b1a0  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#11 pc 00000000008d3cfc  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#12 pc 00000000009680b0  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#13 pc 0000000000967df8  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#14 pc 00000000009674b0  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#15 pc 0000000000983d14  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#16 pc 00000000009b4888  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#17 pc 00000000009a970c  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#18 pc 00000000009a8678  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#19 pc 000000000099ef1c  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#20 pc 00000000009893ec  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#21 pc 0000000000a6e14c  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#22 pc 0000000000a6ac14  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#23 pc 0000000000a6a87c  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#24 pc 0000000000a6a4c8  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#25 pc 0000000000e2084c  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#26 pc 0000000000e1f550  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#27 pc 0000000000a96960  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#28 pc 0000000000e1e884  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#29 pc 0000000000e1e5e4  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#30 pc 0000000000e238e4  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#31 pc 0000000000e1231c  /data/app/~~F_guk_i69IT_U81ZRvjrSg==/com.b44t.messenger-Wl85xf-nRuTROsbTUsZPGQ==/lib/arm64/libnative-utils.so (BuildId: c49bc05053535cecf363374ea3aeb01c201eb537)
      deltachat/deltachat-android#32 pc 00000000000c910c  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+204) (BuildId: 61deb31c5b39d5f735b66f2155a38a77)
      deltachat/deltachat-android#33 pc 000000000005c7b0  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 61deb31c5b39d5f735b66f2155a38a77)
```


Discussion / hints:
Does it also crash if you are offline?
No!

Good idea, this way I can make backup and safe the current messages.
Do you have multiple accounts? It is probably crashing while accessing one of the accounts over the network or trying to process one of the recently received messages. You can try to figure out which account it is and remove some of the recently received messages from the server.
No. I have only one account configured.

But keep in mind: the configuration is 5 years old.
If I changed the phone I used a deltachat backup to reinitialize deltachat on the new phone.
I suspect you have recently received some message that makes the core crash, and this may not be related to 1.48.3 upgrade. So removing some recent messages from the server over IMAP or web interface could help, then Delta Chat will not download them anymore and stop crashing. Would be nice to figure out what exact message it is, but at least this should make Delta Chat usable again for you. If you move the last week of messages out of the server and start uploading them one by one with Thunderbird, you can find which message is the problem.
I run my own IMAP-server (dovecot):
- I can see in the logs, that deltachat connects to the server before the crash
- currently there are no unread messages (the folder `new` is empty)
- removing one month of messages does `not` fix the crash
If you can export a backup, can you also import it into another version, like older F-Droid or the latest nightly (https://download.delta.chat/android/nightly/2024-11-16/) and see if it crashes?
Maybe the crash is related to the recently introduced IMAP COMPRESS support.

You can try to set minimal capabilities (excluding `COMPRESS=DEFLATE`) in Dovecot config, restart Dovecot and see if Delta Chat stops crashing:
```
imap_capability = IMAP4rev1 IDLE MOVE QUOTA CONDSTORE
```

Fix patch:
diff --git a/deltachat-contact-tools/src/lib.rs b/deltachat-contact-tools/src/lib.rs
index e8ca3da3c3..4a6e5b037d 100644
--- a/deltachat-contact-tools/src/lib.rs
+++ b/deltachat-contact-tools/src/lib.rs
@@ -15,7 +15,7 @@
     clippy::explicit_into_iter_loop,
     clippy::cloned_instead_of_copied
 )]
-#![cfg_attr(not(test), warn(clippy::indexing_slicing))]
+#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
 #![allow(
     clippy::match_bool,
     clippy::mixed_read_write_in_expression,
diff --git a/deltachat-jsonrpc/src/lib.rs b/deltachat-jsonrpc/src/lib.rs
index eb633cbd3f..912a6883a7 100644
--- a/deltachat-jsonrpc/src/lib.rs
+++ b/deltachat-jsonrpc/src/lib.rs
@@ -1,4 +1,5 @@
 #![recursion_limit = "256"]
+#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
 pub mod api;
 pub use yerpc;
 
diff --git a/format-flowed/src/lib.rs b/format-flowed/src/lib.rs
index 10f9400b55..055dd66778 100644
--- a/format-flowed/src/lib.rs
+++ b/format-flowed/src/lib.rs
@@ -8,6 +8,7 @@
 //! is assumed to be set to "no".
 //!
 //! For received messages, DelSp parameter is honoured.
+#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
 
 /// Wraps line to 72 characters using format=flowed soft breaks.
 ///
diff --git a/src/authres.rs b/src/authres.rs
index 4d6e86d83f..84504d1ee2 100644
--- a/src/authres.rs
+++ b/src/authres.rs
@@ -260,7 +260,6 @@ fn parse_authservid_candidates_config(config: &Option<String>) -> BTreeSet<&str>
 
 #[cfg(test)]
 mod tests {
-    #![allow(clippy::indexing_slicing)]
     use tokio::fs;
     use tokio::io::AsyncReadExt;
 
diff --git a/src/configure.rs b/src/configure.rs
index 990501468e..c317f6819a 100644
--- a/src/configure.rs
+++ b/src/configure.rs
@@ -611,8 +611,6 @@ pub enum Error {
 
 #[cfg(test)]
 mod tests {
-    #![allow(clippy::indexing_slicing)]
-
     use super::*;
     use crate::config::Config;
     use crate::login_param::EnteredServerLoginParam;
diff --git a/src/configure/auto_mozilla.rs b/src/configure/auto_mozilla.rs
index 4560bf52d8..1e007e9018 100644
--- a/src/configure/auto_mozilla.rs
+++ b/src/configure/auto_mozilla.rs
@@ -272,8 +272,6 @@ pub(crate) async fn moz_autoconfigure(
 
 #[cfg(test)]
 mod tests {
-    #![allow(clippy::indexing_slicing)]
-
     use super::*;
 
     #[test]
diff --git a/src/configure/auto_outlook.rs b/src/configure/auto_outlook.rs
index a3fef6f322..533305ebae 100644
--- a/src/configure/auto_outlook.rs
+++ b/src/configure/auto_outlook.rs
@@ -215,8 +215,6 @@ pub(crate) async fn outlk_autodiscover(
 
 #[cfg(test)]
 mod tests {
-    #![allow(clippy::indexing_slicing)]
-
     use super::*;
 
     #[test]
diff --git a/src/lib.rs b/src/lib.rs
index 2c35c8389b..7124bd5eb2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,7 +16,7 @@
     clippy::explicit_into_iter_loop,
     clippy::cloned_instead_of_copied
 )]
-#![cfg_attr(not(test), warn(clippy::indexing_slicing))]
+#![cfg_attr(not(test), forbid(clippy::indexing_slicing))]
 #![allow(
     clippy::match_bool,
     clippy::mixed_read_write_in_expression,
diff --git a/src/location.rs b/src/location.rs
index f2fa919b83..5dcc9a258b 100644
--- a/src/location.rs
+++ b/src/location.rs
@@ -880,8 +880,6 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
 
 #[cfg(test)]
 mod tests {
-    #![allow(clippy::indexing_slicing)]
-
     use super::*;
     use crate::config::Config;
     use crate::message::MessageState;
diff --git a/src/mimefactory.rs b/src/mimefactory.rs
index 83556c0d01..fb294b107d 100644
--- a/src/mimefactory.rs
+++ b/src/mimefactory.rs
@@ -2274,7 +2274,6 @@ mod tests {
         let msg = message.as_string();
 
         let header_end = msg.find("Hi").unwrap();
-        #[allow(clippy::indexing_slicing)]
         let headers = msg[0..header_end].trim();
 
         assert!(!headers.lines().any(|l| l.trim().is_empty()));
diff --git a/src/mimeparser.rs b/src/mimeparser.rs
index 095d4e12fb..e2c7583aa4 100644
--- a/src/mimeparser.rs
+++ b/src/mimeparser.rs
@@ -665,11 +665,13 @@ impl MimeMessage {
     /// Delta Chat sends attachments, such as images, in two-part messages, with the first message
     /// containing a description. If such a message is detected, text from the first part can be
     /// moved to the second part, and the first part dropped.
-    #[allow(clippy::indexing_slicing)]
     fn squash_attachment_parts(&mut self) {
-        if let [textpart, filepart] = &self.parts[..] {
-            let need_drop = textpart.typ == Viewtype::Text
-                && match filepart.typ {
+        if self.parts.len() == 2
+            && self.parts.first().map(|textpart| textpart.typ) == Some(Viewtype::Text)
+            && self
+                .parts
+                .get(1)
+                .map_or(false, |filepart| match filepart.typ {
                     Viewtype::Image
                     | Viewtype::Gif
                     | Viewtype::Sticker
@@ -680,24 +682,24 @@ impl MimeMessage {
                     | Viewtype::File
                     | Viewtype::Webxdc => true,
                     Viewtype::Unknown | Viewtype::Text | Viewtype::VideochatInvitation => false,
-                };
-
-            if need_drop {
-                let mut filepart = self.parts.swap_remove(1);
-
-                // insert new one
-                filepart.msg.clone_from(&self.parts[0].msg);
-                if let Some(quote) = self.parts[0].param.get(Param::Quote) {
-                    filepart.param.set(Param::Quote, quote);
-                }
-
-                // forget the one we use now
-                self.parts[0].msg = "".to_string();
+                })
+        {
+            let mut parts = std::mem::take(&mut self.parts);
+            let Some(mut filepart) = parts.pop() else {
+                // Should never happen.
+                return;
+            };
+            let Some(textpart) = parts.pop() else {
+                // Should never happen.
+                return;
+            };
 
-                // swap new with old
-                self.parts.push(filepart); // push to the end
-                let _ = self.parts.swap_remove(0); // drops first element, replacing it with the last one in O(1)
+            filepart.msg.clone_from(&textpart.msg);
+            if let Some(quote) = textpart.param.get(Param::Quote) {
+                filepart.param.set(Param::Quote, quote);
             }
+
+            self.parts = vec![filepart];
         }
     }
 
@@ -1751,7 +1753,6 @@ impl MimeMessage {
     /// Some providers like GMX and Yahoo do not send standard NDNs (Non Delivery notifications).
     /// If you improve heuristics here you might also have to change prefetch_should_download() in imap/mod.rs.
     /// Also you should add a test in receive_imf.rs (there already are lots of test_parse_ndn_* tests).
-    #[allow(clippy::indexing_slicing)]
     async fn heuristically_parse_ndn(&mut self, context: &Context) {
         let maybe_ndn = if let Some(from) = self.get_header(HeaderDef::From_) {
             let from = from.to_ascii_lowercase();
@@ -1914,18 +1915,17 @@ pub(crate) struct DeliveryReport {
     pub failure: bool,
 }
 
-#[allow(clippy::indexing_slicing)]
 pub(crate) fn parse_message_ids(ids: &str) -> Vec<String> {
     // take care with mailparse::msgidparse() that is pretty untolerant eg. wrt missing `<` or `>`
     let mut msgids = Vec::new();
     for id in ids.split_whitespace() {
         let mut id = id.to_string();
-        if id.starts_with('<') {
-            id = id[1..].to_string();
-        }
-        if id.ends_with('>') {
-            id = id[..id.len() - 1].to_string();
-        }
+        if let Some(id_without_prefix) = id.strip_prefix('<') {
+            id = id_without_prefix.to_string();
+        };
+        if let Some(id_without_suffix) = id.strip_suffix('>') {
+            id = id_without_suffix.to_string();
+        };
         if !id.is_empty() {
             msgids.push(id);
         }
@@ -2362,8 +2362,6 @@ async fn ndn_maybe_add_info_msg(
 
 #[cfg(test)]
 mod tests {
-    #![allow(clippy::indexing_slicing)]
-
     use mailparse::ParsedMail;
 
     use super::*;
diff --git a/src/provider.rs b/src/provider.rs
index a909d3ead6..78aade13c6 100644
--- a/src/provider.rs
+++ b/src/provider.rs
@@ -288,8 +288,6 @@ pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> {
 
 #[cfg(test)]
 mod tests {
-    #![allow(clippy::indexing_slicing)]
-
     use super::*;
     use crate::test_utils::TestContext;
 
diff --git a/src/qr.rs b/src/qr.rs
index 23baaf3c9b..30f0eb4e69 100644
--- a/src/qr.rs
+++ b/src/qr.rs
@@ -367,9 +367,10 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
 /// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH`
 ///     or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH`
 ///     or: `OPENPGP4FPR:FINGERPRINT#a=ADDR`
-#[allow(clippy::indexing_slicing)]
 async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
-    let payload = &qr[OPENPGP4FPR_SCHEME.len()..];
+    let payload = qr
+        .get(OPENPGP4FPR_SCHEME.len()..)
+        .context("Invalid OPENPGP4FPR scheme")?;
 
     // macOS and iOS sometimes replace the # with %23 (uri encode it), we should be able to parse this wrong format too.
     // see issue https://github.com/deltachat/deltachat-core-rust/issues/1969 for more info
@@ -546,7 +547,7 @@ async fn decode_ideltachat(context: &Context, prefix: &str, qr: &str) -> Result<
 fn decode_account(qr: &str) -> Result<Qr> {
     let payload = qr
         .get(DCACCOUNT_SCHEME.len()..)
-        .context("invalid DCACCOUNT payload")?;
+        .context("Invalid DCACCOUNT payload")?;
     let url = url::Url::parse(payload).context("Invalid account URL")?;
     if url.scheme() == "http" || url.scheme() == "https" {
         Ok(Qr::Account {
@@ -564,7 +565,7 @@ fn decode_account(qr: &str) -> Result<Qr> {
 fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
     let payload = qr
         .get(DCWEBRTC_SCHEME.len()..)
-        .context("invalid DCWEBRTC payload")?;
+        .context("Invalid DCWEBRTC payload")?;
 
     let (_type, url) = Message::parse_webrtc_instance(payload);
     let url = url::Url::parse(&url).context("Invalid WebRTC instance")?;
@@ -637,7 +638,7 @@ fn decode_shadowsocks_proxy(qr: &str) -> Result<Qr> {
 fn decode_backup2(qr: &str) -> Result<Qr> {
     let payload = qr
         .strip_prefix(DCBACKUP2_SCHEME)
-        .ok_or_else(|| anyhow!("invalid DCBACKUP scheme"))?;
+        .ok_or_else(|| anyhow!("Invalid DCBACKUP2 scheme"))?;
     let (auth_token, node_addr) = payload
         .split_once('&')
         .context("Backup QR code has no separator")?;
@@ -668,9 +669,10 @@ struct CreateAccountErrorResponse {
 /// take a qr of the type DC_QR_ACCOUNT, parse it's parameters,
 /// download additional information from the contained url and set the parameters.
 /// on success, a configure::configure() should be able to log in to the account
-#[allow(clippy::indexing_slicing)]
 async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
-    let url_str = &qr[DCACCOUNT_SCHEME.len()..];
+    let url_str = qr
+        .get(DCACCOUNT_SCHEME.len()..)
+        .context("Invalid DCACCOUNT scheme")?;
 
     if !url_str.starts_with(HTTPS_SCHEME) {
         bail!("DCACCOUNT QR codes must use HTTPS scheme");
@@ -800,9 +802,10 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
 /// Extract address for the mailto scheme.
 ///
 /// Scheme: `mailto:addr...?subject=...&body=..`
-#[allow(clippy::indexing_slicing)]
 async fn decode_mailto(context: &Context, qr: &str) -> Result<Qr> {
-    let payload = &qr[MAILTO_SCHEME.len()..];
+    let payload = qr
+        .get(MAILTO_SCHEME.len()..)
+        .context("Invalid mailto: scheme")?;
 
     let (addr, query) = if let Some(query_index) = payload.find('?') {
         (&payload[..query_index], &payload[query_index + 1..])
@@ -855,9 +858,8 @@ async fn decode_mailto(context: &Context, qr: &str) -> Result<Qr> {
 /// Extract address for the smtp scheme.
 ///
 /// Scheme: `SMTP:addr...:subject...:body...`
-#[allow(clippy::indexing_slicing)]
 async fn decode_smtp(context: &Context, qr: &str) -> Result<Qr> {
-    let payload = &qr[SMTP_SCHEME.len()..];
+    let payload = qr.get(SMTP_SCHEME.len()..).context("Invalid SMTP scheme")?;
 
     let addr = if let Some(query_index) = payload.find(':') {
         &payload[..query_index]
@@ -875,14 +877,13 @@ async fn decode_smtp(context: &Context, qr: &str) -> Result<Qr> {
 /// Scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;`
 ///
 /// There may or may not be linebreaks after the fields.
-#[allow(clippy::indexing_slicing)]
 async fn decode_matmsg(context: &Context, qr: &str) -> Result<Qr> {
     // Does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field.
     // we ignore this case.
     let addr = if let Some(to_index) = qr.find("TO:") {
-        let addr = qr[to_index + 3..].trim();
+        let addr = qr.get(to_index + 3..).unwrap_or_default().trim();
         if let Some(semi_index) = addr.find(';') {
-            addr[..semi_index].trim()
+            addr.get(..semi_index).unwrap_or_default().trim()
         } else {
             addr
         }
@@ -903,7 +904,6 @@ static VCARD_EMAIL_RE: Lazy<regex::Regex> =
 /// Extract address for the vcard scheme.
 ///
 /// Scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL;<type>:addr...;`
-#[allow(clippy::indexing_slicing)]
 async fn decode_vcard(context: &Context, qr: &str) -> Result<Qr> {
     let name = VCARD_NAME_RE
         .captures(qr)
@@ -915,8 +915,8 @@ async fn decode_vcard(context: &Context, qr: &str) -> Result<Qr> {
         })
         .unwrap_or_default();
 
-    let addr = if let Some(caps) = VCARD_EMAIL_RE.captures(qr) {
-        normalize_address(caps[2].trim())?
+    let addr = if let Some(cap) = VCARD_EMAIL_RE.captures(qr).and_then(|caps| caps.get(2)) {
+        normalize_address(cap.as_str().trim())?
     } else {
         bail!("Bad e-mail address");
     };
diff --git a/src/receive_imf.rs b/src/receive_imf.rs
index e4bbeac892..2ad2a714d5 100644
--- a/src/receive_imf.rs
+++ b/src/receive_imf.rs
@@ -2465,14 +2465,16 @@ async fn create_or_lookup_mailinglist(
     }
 }
 
-#[allow(clippy::indexing_slicing)]
 fn compute_mailinglist_name(
     list_id_header: &str,
     listid: &str,
     mime_parser: &MimeMessage,
 ) -> String {
-    let mut name = match LIST_ID_REGEX.captures(list_id_header) {
-        Some(cap) => cap[1].trim().to_string(),
+    let mut name = match LIST_ID_REGEX
+        .captures(list_id_header)
+        .and_then(|caps| caps.get(1))
+    {
+        Some(cap) => cap.as_str().trim().to_string(),
         None => "".to_string(),
     };
 
@@ -2519,8 +2521,11 @@ fn compute_mailinglist_name(
         // 51231231231231231231231232869f58.xing.com -> xing.com
         static PREFIX_32_CHARS_HEX: Lazy<Regex> =
             Lazy::new(|| Regex::new(r"([0-9a-fA-F]{32})\.(.{6,})").unwrap());
-        if let Some(cap) = PREFIX_32_CHARS_HEX.captures(listid) {
-            name = cap[2].to_string();
+        if let Some(cap) = PREFIX_32_CHARS_HEX
+            .captures(listid)
+            .and_then(|caps| caps.get(2))
+        {
+            name = cap.as_str().to_string();
         } else {
             name = listid.to_string();
         }
diff --git a/src/securejoin.rs b/src/securejoin.rs
index 7118c8db28..863d2b4f2f 100644
--- a/src/securejoin.rs
+++ b/src/securejoin.rs
@@ -279,7 +279,6 @@ pub(crate) enum HandshakeMessage {
 ///
 /// When `handle_securejoin_handshake()` is called, the message is not yet filed in the
 /// database; this is done by `receive_imf()` later on as needed.
-#[allow(clippy::indexing_slicing)]
 pub(crate) async fn handle_securejoin_handshake(
     context: &Context,
     mime_message: &MimeMessage,
diff --git a/src/simplify.rs b/src/simplify.rs
index 1d714a6b8f..6e589a5fca 100644
--- a/src/simplify.rs
+++ b/src/simplify.rs
@@ -1,4 +1,5 @@
 //! # Simplify incoming plaintext.
+use crate::tools::IsNoneOrEmpty;
 
 /// Protects lines starting with `--` against being treated as a footer.
 /// for that, we insert a ZERO WIDTH SPACE (ZWSP, 0x200B);
@@ -20,7 +21,6 @@ pub fn escape_message_footer_marks(text: &str) -> String {
 /// Returns `(lines, footer_lines)` tuple;
 /// `footer_lines` is set to `Some` if the footer was actually removed from `lines`
 /// (which is equal to the input array otherwise).
-#[allow(clippy::indexing_slicing)]
 pub(crate) fn remove_message_footer<'a>(
     lines: &'a [&str],
 ) -> (&'a [&'a str], Option<&'a [&'a str]>) {
@@ -28,14 +28,13 @@ pub(crate) fn remove_message_footer<'a>(
     for (ix, &line) in lines.iter().enumerate() {
         match line {
             // some providers encode `-- ` to `-- =20` which results in `--  `
-            "-- " | "--  " => return (&lines[..ix], lines.get(ix + 1..)),
+            "-- " | "--  " => return (lines.get(..ix).unwrap_or(lines), lines.get(ix + 1..)),
             // some providers encode `-- ` to `=2D-` which results in only `--`;
             // use that only when no other footer is found
             // and if the line before is empty and the line after is not empty
             "--" => {
-                if (ix == 0 || lines[ix - 1].is_empty())
-                    && ix != lines.len() - 1
-                    && !lines[ix + 1].is_empty()
+                if (ix == 0 || lines.get(ix.saturating_sub(1)).is_none_or_empty())
+                    && !lines.get(ix + 1).is_none_or_empty()
                 {
                     nearly_standard_footer = Some(ix);
                 }
@@ -44,7 +43,7 @@ pub(crate) fn remove_message_footer<'a>(
         }
     }
     if let Some(ix) = nearly_standard_footer {
-        return (&lines[..ix], lines.get(ix + 1..));
+        return (lines.get(..ix).unwrap_or(lines), lines.get(ix + 1..));
     }
     (lines, None)
 }
@@ -53,7 +52,6 @@ pub(crate) fn remove_message_footer<'a>(
 /// Returns `(lines, is_footer_removed)` tuple;
 /// `is_footer_removed` is set to `true` if the footer was actually removed from `lines`
 /// (which is equal to the input array otherwise).
-#[allow(clippy::indexing_slicing)]
 fn remove_nonstandard_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
     for (ix, &line) in lines.iter().enumerate() {
         if line == "--"
@@ -63,7 +61,10 @@ fn remove_nonstandard_footer<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
             || line.starts_with("*****")
             || line.starts_with("~~~~~")
         {
-            return (&lines[..ix], true);
+            // `get` should always return `Some` here.
+            if let Some(lines) = lines.get(..ix) {
+                return (lines, true);
+            }
         }
     }
     (lines, false)
@@ -173,7 +174,6 @@ fn skip_forward_header<'a>(lines: &'a [&str]) -> (&'a [&'a str], bool) {
     }
 }
 
-#[allow(clippy::indexing_slicing)]
 fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>) {
     let mut first_quoted_line = lines.len();
     let mut last_quoted_line = None;
@@ -188,30 +188,36 @@ fn remove_bottom_quote<'a>(lines: &'a [&str]) -> (&'a [&'a str], Option<String>)
         }
     }
     if let Some(mut l_last) = last_quoted_line {
-        let quoted_text = lines[l_last..first_quoted_line]
+        let quoted_text = lines
             .iter()
+            .take(first_quoted_line)
+            .skip(l_last)
             .map(|s| {
                 s.strip_prefix('>')
                     .map_or(*s, |u| u.strip_prefix(' ').unwrap_or(u))
             })
             .collect::<Vec<&str>>()
             .join("\n");
-        if l_last > 1 && is_empty_line(lines[l_last - 1]) {
-            l_last -= 1
+        if l_last > 1 {
+            if let Some(line) = lines.get(l_last - 1) {
+                if is_empty_line(line) {
+                    l_last -= 1
+                }
+            }
         }
         if l_last > 1 {
-            let line = lines[l_last - 1];
-            if is_quoted_headline(line) {
-                l_last -= 1
+            if let Some(line) = lines.get(l_last - 1) {
+                if is_quoted_headline(line) {
+                    l_last -= 1
+                }
             }
         }
-        (&lines[..l_last], Some(quoted_text))
+        (lines.get(..l_last).unwrap_or(lines), Some(quoted_text))
     } else {
         (lines, None)
     }
 }
 
-#[allow(clippy::indexing_slicing)]
 fn remove_top_quote<'a>(
     lines: &'a [&str],
     is_chat_message: bool,
@@ -238,10 +244,12 @@ fn remove_top_quote<'a>(
     }
     if let Some(last_quoted_line) = last_quoted_line {
         (
-            &lines[last_quoted_line + 1..],
+            lines.get(last_quoted_line + 1..).unwrap_or(lines),
             Some(
-                lines[first_quoted_line..last_quoted_line + 1]
+                lines
                     .iter()
+                    .take(last_quoted_line + 1)
+                    .skip(first_quoted_line)
                     .map(|s| {
                         s.strip_prefix('>')
                             .map_or(*s, |u| u.strip_prefix(' ').unwrap_or(u))
diff --git a/src/sql.rs b/src/sql.rs
index 0356f851c6..3b6f2e4e92 100644
--- a/src/sql.rs
+++ b/src/sql.rs
@@ -942,15 +942,12 @@ pub async fn remove_unused_files(context: &Context) -> Result<()> {
     Ok(())
 }
 
-#[allow(clippy::indexing_slicing)]
 fn is_file_in_use(files_in_use: &HashSet<String>, namespc_opt: Option<&str>, name: &str) -> bool {
     let name_to_check = if let Some(namespc) = namespc_opt {
-        let name_len = name.len();
-        let namespc_len = namespc.len();
-        if name_len <= namespc_len || !name.ends_with(namespc) {
+        let Some(name) = name.strip_suffix(namespc) else {
             return false;
-        }
-        &name[..name_len - namespc_len]
+        };
+        name
     } else {
         name
     };
diff --git a/src/tools.rs b/src/tools.rs
index 89c50759d1..ef460316c7 100644
--- a/src/tools.rs
+++ b/src/tools.rs
@@ -45,7 +45,6 @@ use crate::stock_str;
 
 /// Shortens a string to a specified length and adds "[...]" to the
 /// end of the shortened string.
-#[allow(clippy::indexing_slicing)]
 pub(crate) fn truncate(buf: &str, approx_chars: usize) -> Cow<str> {
     let count = buf.chars().count();
     if count > approx_chars + DC_ELLIPSIS.len() {
@@ -709,8 +708,6 @@ pub(crate) fn inc_and_check<T: PrimInt + AddAssign + std::fmt::Debug>(
 
 #[cfg(test)]
 mod tests {
-    #![allow(clippy::indexing_slicing)]
-
     use chrono::NaiveDate;
     use proptest::prelude::*;
 


Test patch:
diff --git a/src/test_utils.rs b/src/test_utils.rs
index 4a8473bc05..85d232ded1 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -1,7 +1,6 @@
 //! Utilities to help writing tests.
 //!
 //! This private module is only compiled for test runs.
-#![allow(clippy::indexing_slicing)]
 use std::collections::{BTreeMap, HashSet};
 use std::fmt::Write;
 use std::ops::{Deref, DerefMut};
diff --git a/src/tests/aeap.rs b/src/tests/aeap.rs
index b2b599e0ec..18ea10041c 100644
--- a/src/tests/aeap.rs
+++ b/src/tests/aeap.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::indexing_slicing)]
-
 use anyhow::Result;
 
 use crate::chat;