deltachat/
message.rs

1//! # Messages and their identifiers.
2
3use std::collections::BTreeSet;
4use std::collections::HashSet;
5use std::path::{Path, PathBuf};
6use std::str;
7
8use anyhow::{Context as _, Result, ensure, format_err};
9use deltachat_contact_tools::{VcardContact, parse_vcard};
10use deltachat_derive::{FromSql, ToSql};
11use humansize::BINARY;
12use humansize::format_size;
13use num_traits::FromPrimitive;
14use serde::{Deserialize, Serialize};
15use tokio::{fs, io};
16
17use crate::blob::BlobObject;
18use crate::chat::{Chat, ChatId, ChatIdBlocked, ChatVisibility, send_msg};
19use crate::chatlist_events;
20use crate::config::Config;
21use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, DC_MSG_ID_LAST_SPECIAL};
22use crate::contact::{self, Contact, ContactId};
23use crate::context::Context;
24use crate::debug_logging::set_debug_logging_xdc;
25use crate::download::DownloadState;
26use crate::ephemeral::{Timer as EphemeralTimer, start_ephemeral_timers_msgids};
27use crate::events::EventType;
28use crate::imap::markseen_on_imap_table;
29use crate::location::delete_poi_location;
30use crate::log::warn;
31use crate::mimeparser::{SystemMessage, parse_message_id};
32use crate::param::{Param, Params};
33use crate::pgp::split_armored_data;
34use crate::reaction::get_msg_reactions;
35use crate::sql;
36use crate::summary::Summary;
37use crate::sync::SyncData;
38use crate::tools::create_outgoing_rfc724_mid;
39use crate::tools::{
40    buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file,
41    sanitize_filename, time, timestamp_to_str,
42};
43
44/// Message ID, including reserved IDs.
45///
46/// Some message IDs are reserved to identify special message types.
47/// This type can represent both the special as well as normal
48/// messages.
49#[derive(
50    Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
51)]
52pub struct MsgId(u32);
53
54impl MsgId {
55    /// Create a new [MsgId].
56    pub fn new(id: u32) -> MsgId {
57        MsgId(id)
58    }
59
60    /// Create a new unset [MsgId].
61    pub fn new_unset() -> MsgId {
62        MsgId(0)
63    }
64
65    /// Whether the message ID signifies a special message.
66    ///
67    /// This kind of message ID can not be used for real messages.
68    pub fn is_special(self) -> bool {
69        self.0 <= DC_MSG_ID_LAST_SPECIAL
70    }
71
72    /// Whether the message ID is unset.
73    ///
74    /// When a message is created it initially has a ID of `0`, which
75    /// is filled in by a real message ID once the message is saved in
76    /// the database.  This returns true while the message has not
77    /// been saved and thus not yet been given an actual message ID.
78    ///
79    /// When this is `true`, [MsgId::is_special] will also always be
80    /// `true`.
81    pub fn is_unset(self) -> bool {
82        self.0 == 0
83    }
84
85    /// Returns message state.
86    pub async fn get_state(self, context: &Context) -> Result<MessageState> {
87        let result = context
88            .sql
89            .query_row_optional(
90                concat!(
91                    "SELECT m.state, mdns.msg_id",
92                    " FROM msgs m LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
93                    " WHERE id=?",
94                    " LIMIT 1",
95                ),
96                (self,),
97                |row| {
98                    let state: MessageState = row.get(0)?;
99                    let mdn_msg_id: Option<MsgId> = row.get(1)?;
100                    Ok(state.with_mdns(mdn_msg_id.is_some()))
101                },
102            )
103            .await?
104            .unwrap_or_default();
105        Ok(result)
106    }
107
108    pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
109        let res: Option<String> = context
110            .sql
111            .query_get_value("SELECT param FROM msgs WHERE id=?", (self,))
112            .await?;
113        Ok(res
114            .map(|s| s.parse().unwrap_or_default())
115            .unwrap_or_default())
116    }
117
118    /// Put message into trash chat and delete message text.
119    ///
120    /// It means the message is deleted locally, but not on the server.
121    /// We keep some infos to
122    /// 1. not download the same message again
123    /// 2. be able to delete the message on the server if we want to
124    ///
125    /// * `on_server`: Delete the message on the server also if it is seen on IMAP later, but only
126    ///   if all parts of the message are trashed with this flag. `true` if the user explicitly
127    ///   deletes the message. As for trashing a partially downloaded message when replacing it with
128    ///   a fully downloaded one, see `receive_imf::add_parts()`.
129    pub(crate) async fn trash(self, context: &Context, on_server: bool) -> Result<()> {
130        context
131            .sql
132            .execute(
133                // If you change which information is preserved here, also change
134                // `ChatId::delete_ex()`, `delete_expired_messages()` and which information
135                // `receive_imf::add_parts()` still adds to the db if chat_id is TRASH.
136                "
137INSERT OR REPLACE INTO msgs (id, rfc724_mid, pre_rfc724_mid, timestamp, chat_id, deleted)
138SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1
139                ",
140                (self, DC_CHAT_ID_TRASH, on_server),
141            )
142            .await?;
143
144        Ok(())
145    }
146
147    pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
148        update_msg_state(context, self, MessageState::OutDelivered).await?;
149        let chat_id: Option<ChatId> = context
150            .sql
151            .query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
152            .await?;
153        context.emit_event(EventType::MsgDelivered {
154            chat_id: chat_id.unwrap_or_default(),
155            msg_id: self,
156        });
157        if let Some(chat_id) = chat_id {
158            chatlist_events::emit_chatlist_item_changed(context, chat_id);
159        }
160        Ok(())
161    }
162
163    /// Bad evil escape hatch.
164    ///
165    /// Avoid using this, eventually types should be cleaned up enough
166    /// that it is no longer necessary.
167    pub fn to_u32(self) -> u32 {
168        self.0
169    }
170
171    /// Returns server foldernames and UIDs of a message, used for message info
172    pub async fn get_info_server_urls(
173        context: &Context,
174        rfc724_mid: String,
175    ) -> Result<Vec<String>> {
176        context
177            .sql
178            .query_map_vec(
179                "SELECT transports.addr, imap.folder, imap.uid
180                 FROM imap
181                 LEFT JOIN transports
182                 ON transports.id = imap.transport_id
183                 WHERE imap.rfc724_mid=?",
184                (rfc724_mid,),
185                |row| {
186                    let addr: String = row.get(0)?;
187                    let folder: String = row.get(1)?;
188                    let uid: u32 = row.get(2)?;
189                    Ok(format!("<{addr}/{folder}/;UID={uid}>"))
190                },
191            )
192            .await
193    }
194
195    /// Returns information about hops of a message, used for message info
196    pub async fn hop_info(self, context: &Context) -> Result<String> {
197        let hop_info = context
198            .sql
199            .query_get_value("SELECT IFNULL(hop_info, '') FROM msgs WHERE id=?", (self,))
200            .await?
201            .with_context(|| format!("Message {self} not found"))?;
202        Ok(hop_info)
203    }
204
205    /// Returns detailed message information in a multi-line text form.
206    pub async fn get_info(self, context: &Context) -> Result<String> {
207        let msg = Message::load_from_db(context, self).await?;
208
209        let mut ret = String::new();
210
211        let fts = timestamp_to_str(msg.get_timestamp());
212        ret += &format!("Sent: {fts}");
213
214        let from_contact = Contact::get_by_id(context, msg.from_id).await?;
215        let name = from_contact.get_name_n_addr();
216        if let Some(override_sender_name) = msg.get_override_sender_name() {
217            let addr = from_contact.get_addr();
218            ret += &format!(" by ~{override_sender_name} ({addr})");
219        } else {
220            ret += &format!(" by {name}");
221        }
222        ret += "\n";
223
224        if msg.from_id != ContactId::SELF {
225            let s = timestamp_to_str(if 0 != msg.timestamp_rcvd {
226                msg.timestamp_rcvd
227            } else {
228                msg.timestamp_sort
229            });
230            ret += &format!("Received: {}", &s);
231            ret += "\n";
232        }
233
234        if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer {
235            ret += &format!("Ephemeral timer: {duration}\n");
236        }
237
238        if msg.ephemeral_timestamp != 0 {
239            ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp));
240        }
241
242        if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO {
243            // device-internal message, no further details needed
244            return Ok(ret);
245        }
246
247        if let Ok(rows) = context
248            .sql
249            .query_map_vec(
250                "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
251                (self,),
252                |row| {
253                    let contact_id: ContactId = row.get(0)?;
254                    let ts: i64 = row.get(1)?;
255                    Ok((contact_id, ts))
256                },
257            )
258            .await
259        {
260            for (contact_id, ts) in rows {
261                let fts = timestamp_to_str(ts);
262                ret += &format!("Read: {fts}");
263
264                let name = Contact::get_by_id(context, contact_id)
265                    .await
266                    .map(|contact| contact.get_name_n_addr())
267                    .unwrap_or_default();
268
269                ret += &format!(" by {name}");
270                ret += "\n";
271            }
272        }
273
274        ret += &format!("State: {}", msg.state);
275
276        if msg.has_location() {
277            ret += ", Location sent";
278        }
279
280        if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() {
281            ret += ", Encrypted";
282        }
283
284        ret += "\n";
285
286        let reactions = get_msg_reactions(context, self).await?;
287        if !reactions.is_empty() {
288            ret += &format!("Reactions: {reactions}\n");
289        }
290
291        if let Some(error) = msg.error.as_ref() {
292            ret += &format!("Error: {error}");
293        }
294
295        if let Some(path) = msg.get_file(context) {
296            let bytes = get_filebytes(context, &path).await?;
297            ret += &format!(
298                "\nFile: {}, name: {}, {} bytes\n",
299                path.display(),
300                msg.get_filename().unwrap_or_default(),
301                bytes
302            );
303        }
304
305        if msg.viewtype != Viewtype::Text {
306            ret += "Type: ";
307            ret += &format!("{}", msg.viewtype);
308            ret += "\n";
309            ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
310        }
311        let w = msg.param.get_int(Param::Width).unwrap_or_default();
312        let h = msg.param.get_int(Param::Height).unwrap_or_default();
313        if w != 0 || h != 0 {
314            ret += &format!("Dimension: {w} x {h}\n",);
315        }
316        let duration = msg.param.get_int(Param::Duration).unwrap_or_default();
317        if duration != 0 {
318            ret += &format!("Duration: {duration} ms\n",);
319        }
320        if !msg.rfc724_mid.is_empty() {
321            ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
322
323            let server_urls = Self::get_info_server_urls(context, msg.rfc724_mid).await?;
324            for server_url in server_urls {
325                // Format as RFC 5092 relative IMAP URL.
326                ret += &format!("\nServer-URL: {server_url}");
327            }
328        }
329        let hop_info = self.hop_info(context).await?;
330
331        ret += "\n\n";
332        if hop_info.is_empty() {
333            ret += "No Hop Info";
334        } else {
335            ret += &hop_info;
336        }
337
338        Ok(ret)
339    }
340}
341
342impl std::fmt::Display for MsgId {
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        write!(f, "Msg#{}", self.0)
345    }
346}
347
348/// Allow converting [MsgId] to an SQLite type.
349///
350/// This allows you to directly store [MsgId] into the database.
351///
352/// # Errors
353///
354/// This **does** ensure that no special message IDs are written into
355/// the database and the conversion will fail if this is not the case.
356impl rusqlite::types::ToSql for MsgId {
357    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
358        if self.0 <= DC_MSG_ID_LAST_SPECIAL {
359            return Err(rusqlite::Error::ToSqlConversionFailure(
360                format_err!("Invalid MsgId {}", self.0).into(),
361            ));
362        }
363        let val = rusqlite::types::Value::Integer(i64::from(self.0));
364        let out = rusqlite::types::ToSqlOutput::Owned(val);
365        Ok(out)
366    }
367}
368
369/// Allow converting an SQLite integer directly into [MsgId].
370impl rusqlite::types::FromSql for MsgId {
371    fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
372        // Would be nice if we could use match here, but alas.
373        i64::column_result(value).and_then(|val| {
374            if 0 <= val && val <= i64::from(u32::MAX) {
375                Ok(MsgId::new(val as u32))
376            } else {
377                Err(rusqlite::types::FromSqlError::OutOfRange(val))
378            }
379        })
380    }
381}
382
383#[derive(
384    Debug,
385    Copy,
386    Clone,
387    PartialEq,
388    FromPrimitive,
389    ToPrimitive,
390    FromSql,
391    ToSql,
392    Serialize,
393    Deserialize,
394    Default,
395)]
396#[repr(u8)]
397pub(crate) enum MessengerMessage {
398    #[default]
399    No = 0,
400    Yes = 1,
401
402    /// No, but reply to messenger message.
403    Reply = 2,
404}
405
406/// An object representing a single message in memory.
407/// The message object is not updated.
408/// If you want an update, you have to recreate the object.
409#[derive(Debug, Clone, Default, Serialize, Deserialize)]
410pub struct Message {
411    /// Message ID.
412    pub(crate) id: MsgId,
413
414    /// `From:` contact ID.
415    pub(crate) from_id: ContactId,
416
417    /// ID of the first contact in the `To:` header.
418    pub(crate) to_id: ContactId,
419
420    /// ID of the chat message belongs to.
421    pub(crate) chat_id: ChatId,
422
423    /// Type of the message.
424    pub(crate) viewtype: Viewtype,
425
426    /// State of the message.
427    pub(crate) state: MessageState,
428    pub(crate) download_state: DownloadState,
429
430    /// Whether the message is hidden.
431    pub(crate) hidden: bool,
432    pub(crate) timestamp_sort: i64,
433    pub(crate) timestamp_sent: i64,
434    pub(crate) timestamp_rcvd: i64,
435    pub(crate) ephemeral_timer: EphemeralTimer,
436    pub(crate) ephemeral_timestamp: i64,
437    pub(crate) text: String,
438    /// Text that is added to the end of Message.text
439    ///
440    /// Currently used for adding the download information on pre-messages
441    pub(crate) additional_text: String,
442
443    /// Message subject.
444    ///
445    /// If empty, a default subject will be generated when sending.
446    pub(crate) subject: String,
447
448    /// `Message-ID` header value.
449    pub(crate) rfc724_mid: String,
450    /// `Message-ID` header value of the pre-message, if any.
451    pub(crate) pre_rfc724_mid: String,
452
453    /// `In-Reply-To` header value.
454    pub(crate) in_reply_to: Option<String>,
455    pub(crate) is_dc_message: MessengerMessage,
456    pub(crate) original_msg_id: MsgId,
457    pub(crate) mime_modified: bool,
458    pub(crate) chat_blocked: Blocked,
459    pub(crate) location_id: u32,
460    pub(crate) error: Option<String>,
461    pub(crate) param: Params,
462}
463
464impl Message {
465    /// Creates a new message with given view type.
466    pub fn new(viewtype: Viewtype) -> Self {
467        Message {
468            viewtype,
469            rfc724_mid: create_outgoing_rfc724_mid(),
470            ..Default::default()
471        }
472    }
473
474    /// Creates a new message with Viewtype::Text.
475    pub fn new_text(text: String) -> Self {
476        Message {
477            viewtype: Viewtype::Text,
478            text,
479            rfc724_mid: create_outgoing_rfc724_mid(),
480            ..Default::default()
481        }
482    }
483
484    /// Loads message with given ID from the database.
485    ///
486    /// Returns an error if the message does not exist.
487    pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message> {
488        let message = Self::load_from_db_optional(context, id)
489            .await?
490            .with_context(|| format!("Message {id} does not exist"))?;
491        Ok(message)
492    }
493
494    /// Loads message with given ID from the database.
495    ///
496    /// Returns `None` if the message does not exist.
497    pub async fn load_from_db_optional(context: &Context, id: MsgId) -> Result<Option<Message>> {
498        ensure!(
499            !id.is_special(),
500            "Can not load special message ID {id} from DB"
501        );
502        let mut msg = context
503            .sql
504            .query_row_optional(
505                concat!(
506                    "SELECT",
507                    "    m.id AS id,",
508                    "    rfc724_mid AS rfc724mid,",
509                    "    pre_rfc724_mid AS pre_rfc724mid,",
510                    "    m.mime_in_reply_to AS mime_in_reply_to,",
511                    "    m.chat_id AS chat_id,",
512                    "    m.from_id AS from_id,",
513                    "    m.to_id AS to_id,",
514                    "    m.timestamp AS timestamp,",
515                    "    m.timestamp_sent AS timestamp_sent,",
516                    "    m.timestamp_rcvd AS timestamp_rcvd,",
517                    "    m.ephemeral_timer AS ephemeral_timer,",
518                    "    m.ephemeral_timestamp AS ephemeral_timestamp,",
519                    "    m.type AS type,",
520                    "    m.state AS state,",
521                    "    mdns.msg_id AS mdn_msg_id,",
522                    "    m.download_state AS download_state,",
523                    "    m.error AS error,",
524                    "    m.msgrmsg AS msgrmsg,",
525                    "    m.starred AS original_msg_id,",
526                    "    m.mime_modified AS mime_modified,",
527                    "    m.txt AS txt,",
528                    "    m.subject AS subject,",
529                    "    m.param AS param,",
530                    "    m.hidden AS hidden,",
531                    "    m.location_id AS location,",
532                    "    c.blocked AS blocked",
533                    " FROM msgs m",
534                    " LEFT JOIN chats c ON c.id=m.chat_id",
535                    " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
536                    " WHERE m.id=? AND chat_id!=3",
537                    " LIMIT 1",
538                ),
539                (id,),
540                |row| {
541                    let state: MessageState = row.get("state")?;
542                    let mdn_msg_id: Option<MsgId> = row.get("mdn_msg_id")?;
543                    let text = match row.get_ref("txt")? {
544                        rusqlite::types::ValueRef::Text(buf) => {
545                            match String::from_utf8(buf.to_vec()) {
546                                Ok(t) => t,
547                                Err(_) => {
548                                    warn!(
549                                        context,
550                                        concat!(
551                                            "dc_msg_load_from_db: could not get ",
552                                            "text column as non-lossy utf8 id {}"
553                                        ),
554                                        id
555                                    );
556                                    String::from_utf8_lossy(buf).into_owned()
557                                }
558                            }
559                        }
560                        _ => String::new(),
561                    };
562                    let msg = Message {
563                        id: row.get("id")?,
564                        rfc724_mid: row.get::<_, String>("rfc724mid")?,
565                        pre_rfc724_mid: row.get::<_, String>("pre_rfc724mid")?,
566                        in_reply_to: row
567                            .get::<_, Option<String>>("mime_in_reply_to")?
568                            .and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
569                        chat_id: row.get("chat_id")?,
570                        from_id: row.get("from_id")?,
571                        to_id: row.get("to_id")?,
572                        timestamp_sort: row.get("timestamp")?,
573                        timestamp_sent: row.get("timestamp_sent")?,
574                        timestamp_rcvd: row.get("timestamp_rcvd")?,
575                        ephemeral_timer: row.get("ephemeral_timer")?,
576                        ephemeral_timestamp: row.get("ephemeral_timestamp")?,
577                        viewtype: row.get("type").unwrap_or_default(),
578                        state: state.with_mdns(mdn_msg_id.is_some()),
579                        download_state: row.get("download_state")?,
580                        error: Some(row.get::<_, String>("error")?)
581                            .filter(|error| !error.is_empty()),
582                        is_dc_message: row.get("msgrmsg")?,
583                        original_msg_id: row.get("original_msg_id")?,
584                        mime_modified: row.get("mime_modified")?,
585                        text,
586                        additional_text: String::new(),
587                        subject: row.get("subject")?,
588                        param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
589                        hidden: row.get("hidden")?,
590                        location_id: row.get("location")?,
591                        chat_blocked: row
592                            .get::<_, Option<Blocked>>("blocked")?
593                            .unwrap_or_default(),
594                    };
595                    Ok(msg)
596                },
597            )
598            .await
599            .with_context(|| format!("failed to load message {id} from the database"))?;
600
601        if let Some(msg) = &mut msg {
602            msg.additional_text =
603                Self::get_additional_text(context, msg.download_state, &msg.param).await?;
604        }
605
606        Ok(msg)
607    }
608
609    /// Returns additional text which is appended to the message's text field
610    /// when it is loaded from the database.
611    /// Currently this is used to add infomation to pre-messages of what the download will be and how large it is
612    async fn get_additional_text(
613        context: &Context,
614        download_state: DownloadState,
615        param: &Params,
616    ) -> Result<String> {
617        if download_state != DownloadState::Done {
618            let file_size = param
619                .get(Param::PostMessageFileBytes)
620                .and_then(|s| s.parse().ok())
621                .map(|file_size: usize| format_size(file_size, BINARY))
622                .unwrap_or("?".to_owned());
623            let viewtype = param
624                .get_i64(Param::PostMessageViewtype)
625                .and_then(Viewtype::from_i64)
626                .unwrap_or(Viewtype::Unknown);
627            let file_name = param
628                .get(Param::Filename)
629                .map(sanitize_filename)
630                .unwrap_or("?".to_owned());
631
632            return match viewtype {
633                Viewtype::File => Ok(format!(" [{file_name} – {file_size}]")),
634                _ => {
635                    let translated_viewtype = viewtype.to_locale_string(context).await;
636                    Ok(format!(" [{translated_viewtype} – {file_size}]"))
637                }
638            };
639        }
640        Ok(String::new())
641    }
642
643    /// Returns the MIME type of an attached file if it exists.
644    ///
645    /// If the MIME type is not known, the function guesses the MIME type
646    /// from the extension. `application/octet-stream` is used as a fallback
647    /// if MIME type is not known, but `None` is only returned if no file
648    /// is attached.
649    pub fn get_filemime(&self) -> Option<String> {
650        if let Some(m) = self.param.get(Param::MimeType) {
651            return Some(m.to_string());
652        } else if self.param.exists(Param::File) {
653            if let Some((_, mime)) = guess_msgtype_from_suffix(self) {
654                return Some(mime.to_string());
655            }
656            // we have a file but no mimetype, let's use a generic one
657            return Some("application/octet-stream".to_string());
658        }
659        // no mimetype and no file
660        None
661    }
662
663    /// Returns the full path to the file associated with a message.
664    pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
665        self.param.get_file_path(context).unwrap_or(None)
666    }
667
668    /// Returns vector of vcards if the file has a vCard attachment.
669    pub async fn vcard_contacts(&self, context: &Context) -> Result<Vec<VcardContact>> {
670        if self.viewtype != Viewtype::Vcard {
671            return Ok(Vec::new());
672        }
673
674        let path = self
675            .get_file(context)
676            .context("vCard message does not have an attachment")?;
677        let bytes = tokio::fs::read(path).await?;
678        let vcard_contents = std::str::from_utf8(&bytes).context("vCard is not a valid UTF-8")?;
679        Ok(parse_vcard(vcard_contents))
680    }
681
682    /// Save file copy at the user-provided path.
683    pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
684        let path_src = self.get_file(context).context("No file")?;
685        let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
686        let mut dst = fs::OpenOptions::new()
687            .write(true)
688            .create_new(true)
689            .open(path)
690            .await?;
691        io::copy(&mut src, &mut dst).await?;
692        Ok(())
693    }
694
695    /// If message is an image or gif, set Param::Width and Param::Height
696    pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
697        if self.viewtype.has_file() {
698            let file_param = self.param.get_file_path(context)?;
699            if let Some(path_and_filename) = file_param
700                && matches!(
701                    self.viewtype,
702                    Viewtype::Image | Viewtype::Gif | Viewtype::Sticker
703                )
704                && !self.param.exists(Param::Width)
705            {
706                let buf = read_file(context, &path_and_filename).await?;
707
708                match get_filemeta(&buf) {
709                    Ok((width, height)) => {
710                        self.param.set_int(Param::Width, width as i32);
711                        self.param.set_int(Param::Height, height as i32);
712                    }
713                    Err(err) => {
714                        self.param.set_int(Param::Width, 0);
715                        self.param.set_int(Param::Height, 0);
716                        warn!(
717                            context,
718                            "Failed to get width and height for {}: {err:#}.",
719                            path_and_filename.display()
720                        );
721                    }
722                }
723
724                if !self.id.is_unset() {
725                    self.update_param(context).await?;
726                }
727            }
728        }
729        Ok(())
730    }
731
732    /// Check if a message has a POI location bound to it.
733    /// These locations are also returned by [`location::get_range()`].
734    /// The UI may decide to display a special icon beside such messages.
735    ///
736    /// [`location::get_range()`]: crate::location::get_range
737    pub fn has_location(&self) -> bool {
738        self.location_id != 0
739    }
740
741    /// Set any location that should be bound to the message object.
742    /// The function is useful to add a marker to the map
743    /// at a position different from the self-location.
744    /// You should not call this function
745    /// if you want to bind the current self-location to a message;
746    /// this is done by [`location::set()`] and [`send_locations_to_chat()`].
747    ///
748    /// Typically results in the event [`LocationChanged`] with
749    /// `contact_id` set to [`ContactId::SELF`].
750    ///
751    /// `latitude` is the North-south position of the location.
752    /// `longitude` is the East-west position of the location.
753    ///
754    /// [`location::set()`]: crate::location::set
755    /// [`send_locations_to_chat()`]: crate::location::send_locations_to_chat
756    /// [`LocationChanged`]: crate::events::EventType::LocationChanged
757    pub fn set_location(&mut self, latitude: f64, longitude: f64) {
758        if latitude == 0.0 && longitude == 0.0 {
759            return;
760        }
761
762        self.param.set_float(Param::SetLatitude, latitude);
763        self.param.set_float(Param::SetLongitude, longitude);
764    }
765
766    /// Returns the message timestamp for display in the UI
767    /// as a unix timestamp in seconds.
768    pub fn get_timestamp(&self) -> i64 {
769        if 0 != self.timestamp_sent {
770            self.timestamp_sent
771        } else {
772            self.timestamp_sort
773        }
774    }
775
776    /// Returns the message ID.
777    pub fn get_id(&self) -> MsgId {
778        self.id
779    }
780
781    /// Returns the rfc724 message ID
782    /// May be empty
783    pub fn rfc724_mid(&self) -> &str {
784        &self.rfc724_mid
785    }
786
787    /// Returns the ID of the contact who wrote the message.
788    pub fn get_from_id(&self) -> ContactId {
789        self.from_id
790    }
791
792    /// Returns the chat ID.
793    pub fn get_chat_id(&self) -> ChatId {
794        self.chat_id
795    }
796
797    /// Returns the type of the message.
798    pub fn get_viewtype(&self) -> Viewtype {
799        self.viewtype
800    }
801
802    /// Forces the message to **keep** [Viewtype::Sticker]
803    /// e.g the message will not be converted to a [Viewtype::Image].
804    pub fn force_sticker(&mut self) {
805        self.param.set_int(Param::ForceSticker, 1);
806    }
807
808    /// Returns the state of the message.
809    pub fn get_state(&self) -> MessageState {
810        self.state
811    }
812
813    /// Returns the message receive time as a unix timestamp in seconds.
814    pub fn get_received_timestamp(&self) -> i64 {
815        self.timestamp_rcvd
816    }
817
818    /// Returns the timestamp of the message for sorting.
819    pub fn get_sort_timestamp(&self) -> i64 {
820        self.timestamp_sort
821    }
822
823    /// Returns the text of the message.
824    ///
825    /// Currently this includes `additional_text`, but this may change in future, when the UIs show
826    /// the necessary info themselves.
827    pub fn get_text(&self) -> String {
828        self.text.clone() + &self.additional_text
829    }
830
831    /// Returns message subject.
832    pub fn get_subject(&self) -> &str {
833        &self.subject
834    }
835
836    /// Returns original filename (as shown in chat).
837    ///
838    /// To get the full path, use [`Self::get_file()`].
839    pub fn get_filename(&self) -> Option<String> {
840        if let Some(name) = self.param.get(Param::Filename) {
841            return Some(sanitize_filename(name));
842        }
843        self.param
844            .get(Param::File)
845            .and_then(|file| Path::new(file).file_name())
846            .map(|name| sanitize_filename(&name.to_string_lossy()))
847    }
848
849    /// Returns the size of the file in bytes, if applicable.
850    /// If message is a pre-message, then this returns the size of the file to be downloaded.
851    pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
852        if self.download_state != DownloadState::Done
853            && let Some(file_size) = self
854                .param
855                .get(Param::PostMessageFileBytes)
856                .and_then(|s| s.parse().ok())
857        {
858            return Ok(Some(file_size));
859        }
860        if let Some(path) = self.param.get_file_path(context)? {
861            Ok(Some(get_filebytes(context, &path).await.with_context(
862                || format!("failed to get {} size in bytes", path.display()),
863            )?))
864        } else {
865            Ok(None)
866        }
867    }
868
869    /// If message is a Pre-Message,
870    /// then this returns the viewtype it will have when it is downloaded.
871    #[cfg(test)]
872    pub(crate) fn get_post_message_viewtype(&self) -> Option<Viewtype> {
873        if self.download_state != DownloadState::Done {
874            return self
875                .param
876                .get_i64(Param::PostMessageViewtype)
877                .and_then(Viewtype::from_i64);
878        }
879        None
880    }
881
882    /// Returns width of associated image or video file.
883    pub fn get_width(&self) -> i32 {
884        self.param.get_int(Param::Width).unwrap_or_default()
885    }
886
887    /// Returns height of associated image or video file.
888    pub fn get_height(&self) -> i32 {
889        self.param.get_int(Param::Height).unwrap_or_default()
890    }
891
892    /// Returns duration of associated audio or video file.
893    pub fn get_duration(&self) -> i32 {
894        self.param.get_int(Param::Duration).unwrap_or_default()
895    }
896
897    /// Returns true if padlock indicating message encryption should be displayed in the UI.
898    pub fn get_showpadlock(&self) -> bool {
899        self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
900            || self.from_id == ContactId::DEVICE
901    }
902
903    /// Returns true if message is auto-generated.
904    pub fn is_bot(&self) -> bool {
905        self.param.get_bool(Param::Bot).unwrap_or_default()
906    }
907
908    /// Return the ephemeral timer duration for a message.
909    pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
910        self.ephemeral_timer
911    }
912
913    /// Returns the timestamp of the epehemeral message removal.
914    pub fn get_ephemeral_timestamp(&self) -> i64 {
915        self.ephemeral_timestamp
916    }
917
918    /// Returns message summary for display in the search results.
919    pub async fn get_summary(&self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
920        let chat_loaded: Chat;
921        let chat = if let Some(chat) = chat {
922            chat
923        } else {
924            let chat = Chat::load_from_db(context, self.chat_id).await?;
925            chat_loaded = chat;
926            &chat_loaded
927        };
928
929        let contact = if self.from_id != ContactId::SELF {
930            match chat.typ {
931                Chattype::Group | Chattype::Mailinglist => {
932                    Some(Contact::get_by_id(context, self.from_id).await?)
933                }
934                Chattype::Single | Chattype::OutBroadcast | Chattype::InBroadcast => None,
935            }
936        } else {
937            None
938        };
939
940        Summary::new(context, self, chat, contact.as_ref()).await
941    }
942
943    // It's a little unfortunate that the UI has to first call `dc_msg_get_override_sender_name` and then if it was `NULL`, call
944    // `dc_contact_get_display_name` but this was the best solution:
945    // - We could load a Contact struct from the db here to call `dc_get_display_name` instead of returning `None`, but then we had a db
946    //   call every time (and this fn is called a lot while the user is scrolling through a group), so performance would be bad
947    // - We could pass both a Contact struct and a Message struct in the FFI, but at least on Android we would need to handle raw
948    //   C-data in the Java code (i.e. a `long` storing a C pointer)
949    // - We can't make a param `SenderDisplayname` for messages as sometimes the display name of a contact changes, and we want to show
950    //   the same display name over all messages from the same sender.
951    /// Returns the name that should be shown over the message instead of the contact display ame.
952    pub fn get_override_sender_name(&self) -> Option<String> {
953        self.param
954            .get(Param::OverrideSenderDisplayname)
955            .map(|name| name.to_string())
956    }
957
958    // Exposing this function over the ffi instead of get_override_sender_name() would mean that at least Android Java code has
959    // to handle raw C-data (as it is done for msg_get_summary())
960    pub(crate) fn get_sender_name(&self, contact: &Contact) -> String {
961        self.get_override_sender_name()
962            .unwrap_or_else(|| contact.get_display_name().to_string())
963    }
964
965    /// Returns true if a message has a deviating timestamp.
966    ///
967    /// A message has a deviating timestamp when it is sent on
968    /// another day as received/sorted by.
969    pub fn has_deviating_timestamp(&self) -> bool {
970        let cnv_to_local = gm2local_offset();
971        let sort_timestamp = self.get_sort_timestamp() + cnv_to_local;
972        let send_timestamp = self.get_timestamp() + cnv_to_local;
973
974        sort_timestamp / 86400 != send_timestamp / 86400
975    }
976
977    /// Returns true if the message was successfully delivered to the outgoing server or even
978    /// received a read receipt.
979    pub fn is_sent(&self) -> bool {
980        self.state >= MessageState::OutDelivered
981    }
982
983    /// Returns true if the message is a forwarded message.
984    pub fn is_forwarded(&self) -> bool {
985        0 != self.param.get_int(Param::Forwarded).unwrap_or_default()
986    }
987
988    /// Returns true if the message is edited.
989    pub fn is_edited(&self) -> bool {
990        self.param.get_bool(Param::IsEdited).unwrap_or_default()
991    }
992
993    /// Returns true if the message is an informational message.
994    pub fn is_info(&self) -> bool {
995        let cmd = self.param.get_cmd();
996        self.from_id == ContactId::INFO
997            || self.to_id == ContactId::INFO
998            || cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
999    }
1000
1001    /// Returns the type of an informational message.
1002    pub fn get_info_type(&self) -> SystemMessage {
1003        self.param.get_cmd()
1004    }
1005
1006    /// Return the contact ID of the profile to open when tapping the info message.
1007    pub async fn get_info_contact_id(&self, context: &Context) -> Result<Option<ContactId>> {
1008        match self.param.get_cmd() {
1009            SystemMessage::GroupNameChanged
1010            | SystemMessage::GroupImageChanged
1011            | SystemMessage::EphemeralTimerChanged => {
1012                if self.from_id != ContactId::INFO {
1013                    Ok(Some(self.from_id))
1014                } else {
1015                    Ok(None)
1016                }
1017            }
1018
1019            SystemMessage::MemberAddedToGroup | SystemMessage::MemberRemovedFromGroup => {
1020                if let Some(contact_i32) = self.param.get_int(Param::ContactAddedRemoved) {
1021                    let contact_id = ContactId::new(contact_i32.try_into()?);
1022                    if contact_id == ContactId::SELF
1023                        || Contact::real_exists_by_id(context, contact_id).await?
1024                    {
1025                        Ok(Some(contact_id))
1026                    } else {
1027                        Ok(None)
1028                    }
1029                } else {
1030                    Ok(None)
1031                }
1032            }
1033
1034            SystemMessage::AutocryptSetupMessage
1035            | SystemMessage::SecurejoinMessage
1036            | SystemMessage::LocationStreamingEnabled
1037            | SystemMessage::LocationOnly
1038            | SystemMessage::ChatE2ee
1039            | SystemMessage::ChatProtectionEnabled
1040            | SystemMessage::ChatProtectionDisabled
1041            | SystemMessage::InvalidUnencryptedMail
1042            | SystemMessage::SecurejoinWait
1043            | SystemMessage::SecurejoinWaitTimeout
1044            | SystemMessage::MultiDeviceSync
1045            | SystemMessage::WebxdcStatusUpdate
1046            | SystemMessage::WebxdcInfoMessage
1047            | SystemMessage::IrohNodeAddr
1048            | SystemMessage::CallAccepted
1049            | SystemMessage::CallEnded
1050            | SystemMessage::Unknown => Ok(None),
1051        }
1052    }
1053
1054    /// Returns true if the message is a system message.
1055    pub fn is_system_message(&self) -> bool {
1056        let cmd = self.param.get_cmd();
1057        cmd != SystemMessage::Unknown
1058    }
1059
1060    /// Returns true if the message is an Autocrypt Setup Message.
1061    pub fn is_setupmessage(&self) -> bool {
1062        if self.viewtype != Viewtype::File {
1063            return false;
1064        }
1065
1066        self.param.get_cmd() == SystemMessage::AutocryptSetupMessage
1067    }
1068
1069    /// Returns the first characters of the setup code.
1070    ///
1071    /// This is used to pre-fill the first entry field of the setup code.
1072    pub async fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
1073        if !self.is_setupmessage() {
1074            return None;
1075        }
1076
1077        if let Some(filename) = self.get_file(context)
1078            && let Ok(ref buf) = read_file(context, &filename).await
1079            && let Ok((typ, headers, _)) = split_armored_data(buf)
1080            && typ == pgp::armor::BlockType::Message
1081        {
1082            return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
1083        }
1084
1085        None
1086    }
1087
1088    /// Sets or unsets message text.
1089    pub fn set_text(&mut self, text: String) {
1090        self.text = text;
1091    }
1092
1093    /// Sets the email's subject. If it's empty, a default subject
1094    /// will be used (e.g. `Message from Alice` or `Re: <last subject>`).
1095    pub fn set_subject(&mut self, subject: String) {
1096        self.subject = subject;
1097    }
1098
1099    /// Sets the file associated with a message, deduplicating files with the same name.
1100    ///
1101    /// If `name` is Some, it is used as the file name
1102    /// and the actual current name of the file is ignored.
1103    ///
1104    /// If the source file is already in the blobdir, it will be renamed,
1105    /// otherwise it will be copied to the blobdir first.
1106    ///
1107    /// In order to deduplicate files that contain the same data,
1108    /// the file will be named `<hash>.<extension>`, e.g. `ce940175885d7b78f7b7e9f1396611f.jpg`.
1109    ///
1110    /// NOTE:
1111    /// - This function will rename the file. To get the new file path, call `get_file()`.
1112    /// - The file must not be modified after this function was called.
1113    pub fn set_file_and_deduplicate(
1114        &mut self,
1115        context: &Context,
1116        file: &Path,
1117        name: Option<&str>,
1118        filemime: Option<&str>,
1119    ) -> Result<()> {
1120        let name = if let Some(name) = name {
1121            name.to_string()
1122        } else {
1123            file.file_name()
1124                .map(|s| s.to_string_lossy().to_string())
1125                .unwrap_or_else(|| "unknown_file".to_string())
1126        };
1127
1128        let blob = BlobObject::create_and_deduplicate(context, file, Path::new(&name))?;
1129        self.param.set(Param::File, blob.as_name());
1130
1131        self.param.set(Param::Filename, name);
1132        self.param.set_optional(Param::MimeType, filemime);
1133
1134        Ok(())
1135    }
1136
1137    /// Creates a new blob and sets it as a file associated with a message.
1138    ///
1139    /// In order to deduplicate files that contain the same data,
1140    /// the file will be named `<hash>.<extension>`, e.g. `ce940175885d7b78f7b7e9f1396611f.jpg`.
1141    ///
1142    /// NOTE: The file must not be modified after this function was called.
1143    pub fn set_file_from_bytes(
1144        &mut self,
1145        context: &Context,
1146        name: &str,
1147        data: &[u8],
1148        filemime: Option<&str>,
1149    ) -> Result<()> {
1150        let blob = BlobObject::create_and_deduplicate_from_bytes(context, data, name)?;
1151        self.param.set(Param::Filename, name);
1152        self.param.set(Param::File, blob.as_name());
1153        self.param.set_optional(Param::MimeType, filemime);
1154
1155        Ok(())
1156    }
1157
1158    /// Makes message a vCard-containing message using the specified contacts.
1159    pub async fn make_vcard(&mut self, context: &Context, contacts: &[ContactId]) -> Result<()> {
1160        ensure!(
1161            matches!(self.viewtype, Viewtype::File | Viewtype::Vcard),
1162            "Wrong viewtype for vCard: {}",
1163            self.viewtype,
1164        );
1165        let vcard = contact::make_vcard(context, contacts).await?;
1166        self.set_file_from_bytes(context, "vcard.vcf", vcard.as_bytes(), None)
1167    }
1168
1169    /// Updates message state from the vCard attachment.
1170    pub(crate) async fn try_set_vcard(&mut self, context: &Context, path: &Path) -> Result<()> {
1171        let vcard = fs::read(path)
1172            .await
1173            .with_context(|| format!("Could not read {path:?}"))?;
1174        if let Some(summary) = get_vcard_summary(&vcard) {
1175            self.param.set(Param::Summary1, summary);
1176        } else {
1177            warn!(context, "try_set_vcard: Not a valid DeltaChat vCard.");
1178            self.viewtype = Viewtype::File;
1179        }
1180        Ok(())
1181    }
1182
1183    /// Set different sender name for a message.
1184    /// This overrides the name set by the `set_config()`-option `displayname`.
1185    pub fn set_override_sender_name(&mut self, name: Option<String>) {
1186        self.param
1187            .set_optional(Param::OverrideSenderDisplayname, name);
1188    }
1189
1190    /// Sets the dimensions of associated image or video file.
1191    pub fn set_dimension(&mut self, width: i32, height: i32) {
1192        self.param.set_int(Param::Width, width);
1193        self.param.set_int(Param::Height, height);
1194    }
1195
1196    /// Sets the duration of associated audio or video file.
1197    pub fn set_duration(&mut self, duration: i32) {
1198        self.param.set_int(Param::Duration, duration);
1199    }
1200
1201    /// Marks the message as reaction.
1202    pub(crate) fn set_reaction(&mut self) {
1203        self.param.set_int(Param::Reaction, 1);
1204    }
1205
1206    /// Changes the message width, height or duration,
1207    /// and stores it into the database.
1208    pub async fn latefiling_mediasize(
1209        &mut self,
1210        context: &Context,
1211        width: i32,
1212        height: i32,
1213        duration: i32,
1214    ) -> Result<()> {
1215        if width > 0 && height > 0 {
1216            self.param.set_int(Param::Width, width);
1217            self.param.set_int(Param::Height, height);
1218        }
1219        if duration > 0 {
1220            self.param.set_int(Param::Duration, duration);
1221        }
1222        self.update_param(context).await?;
1223        Ok(())
1224    }
1225
1226    /// Sets message quote text.
1227    ///
1228    /// If `text` is `Some((text_str, protect))`, `protect` specifies whether `text_str` should only
1229    /// be sent encrypted. If it should, but the message is unencrypted, `text_str` is replaced with
1230    /// "...".
1231    pub fn set_quote_text(&mut self, text: Option<(String, bool)>) {
1232        let Some((text, protect)) = text else {
1233            self.param.remove(Param::Quote);
1234            self.param.remove(Param::ProtectQuote);
1235            return;
1236        };
1237        self.param.set(Param::Quote, text);
1238        self.param.set_optional(
1239            Param::ProtectQuote,
1240            match protect {
1241                true => Some("1"),
1242                false => None,
1243            },
1244        );
1245    }
1246
1247    /// Sets message quote.
1248    ///
1249    /// Message-Id is used to set Reply-To field, message text is used for quote.
1250    ///
1251    /// Encryption is required if quoted message was encrypted.
1252    ///
1253    /// The message itself is not required to exist in the database,
1254    /// it may even be deleted from the database by the time the message is prepared.
1255    pub async fn set_quote(&mut self, context: &Context, quote: Option<&Message>) -> Result<()> {
1256        if let Some(quote) = quote {
1257            ensure!(
1258                !quote.rfc724_mid.is_empty(),
1259                "Message without Message-Id cannot be quoted"
1260            );
1261            self.in_reply_to = Some(quote.rfc724_mid.clone());
1262
1263            let text = quote.get_text();
1264            let text = if text.is_empty() {
1265                // Use summary, similar to "Image" to avoid sending empty quote.
1266                quote
1267                    .get_summary(context, None)
1268                    .await?
1269                    .truncated_text(500)
1270                    .to_string()
1271            } else {
1272                text
1273            };
1274            self.set_quote_text(Some((
1275                text,
1276                quote
1277                    .param
1278                    .get_bool(Param::GuaranteeE2ee)
1279                    .unwrap_or_default(),
1280            )));
1281        } else {
1282            self.in_reply_to = None;
1283            self.set_quote_text(None);
1284        }
1285
1286        Ok(())
1287    }
1288
1289    /// Returns quoted message text, if any.
1290    pub fn quoted_text(&self) -> Option<String> {
1291        self.param.get(Param::Quote).map(|s| s.to_string())
1292    }
1293
1294    /// Returns quoted message, if any.
1295    pub async fn quoted_message(&self, context: &Context) -> Result<Option<Message>> {
1296        if self.param.get(Param::Quote).is_some() && !self.is_forwarded() {
1297            return self.parent(context).await;
1298        }
1299        Ok(None)
1300    }
1301
1302    /// Returns parent message according to the `In-Reply-To` header
1303    /// if it exists in the database and is not trashed.
1304    ///
1305    /// `References` header is not taken into account.
1306    pub async fn parent(&self, context: &Context) -> Result<Option<Message>> {
1307        if let Some(in_reply_to) = &self.in_reply_to
1308            && let Some(msg_id) = rfc724_mid_exists(context, in_reply_to).await?
1309        {
1310            let msg = Message::load_from_db_optional(context, msg_id).await?;
1311            return Ok(msg);
1312        }
1313        Ok(None)
1314    }
1315
1316    /// Returns original message ID for message from "Saved Messages".
1317    pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1318        if !self.original_msg_id.is_special()
1319            && let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await?
1320        {
1321            return if msg.chat_id.is_trash() {
1322                Ok(None)
1323            } else {
1324                Ok(Some(msg.id))
1325            };
1326        }
1327        Ok(None)
1328    }
1329
1330    /// Check if the message was saved and returns the corresponding message inside "Saved Messages".
1331    /// UI can use this to show a symbol beside the message, indicating it was saved.
1332    /// The message can be un-saved by deleting the returned message.
1333    pub async fn get_saved_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1334        let res: Option<MsgId> = context
1335            .sql
1336            .query_get_value(
1337                "SELECT id FROM msgs WHERE starred=? AND chat_id!=?",
1338                (self.id, DC_CHAT_ID_TRASH),
1339            )
1340            .await?;
1341        Ok(res)
1342    }
1343
1344    /// Force the message to be sent in plain text.
1345    pub fn force_plaintext(&mut self) {
1346        self.param.set_int(Param::ForcePlaintext, 1);
1347    }
1348
1349    /// Updates `param` column of the message in the database without changing other columns.
1350    pub async fn update_param(&self, context: &Context) -> Result<()> {
1351        context
1352            .sql
1353            .execute(
1354                "UPDATE msgs SET param=? WHERE id=?;",
1355                (self.param.to_string(), self.id),
1356            )
1357            .await?;
1358        Ok(())
1359    }
1360
1361    /// Gets the error status of the message.
1362    ///
1363    /// A message can have an associated error status if something went wrong when sending or
1364    /// receiving message itself.  The error status is free-form text and should not be further parsed,
1365    /// rather it's presence is meant to indicate *something* went wrong with the message and the
1366    /// text of the error is detailed information on what.
1367    ///
1368    /// Some common reasons error can be associated with messages are:
1369    /// * Lack of valid signature on an e2ee message, usually for received messages.
1370    /// * Failure to decrypt an e2ee message, usually for received messages.
1371    /// * When a message could not be delivered to one or more recipients the non-delivery
1372    ///   notification text can be stored in the error status.
1373    pub fn error(&self) -> Option<String> {
1374        self.error.clone()
1375    }
1376}
1377
1378/// State of the message.
1379/// For incoming messages, stores the information on whether the message was read or not.
1380/// For outgoing message, the message could be pending, already delivered or confirmed.
1381#[derive(
1382    Debug,
1383    Default,
1384    Clone,
1385    Copy,
1386    PartialEq,
1387    Eq,
1388    PartialOrd,
1389    Ord,
1390    FromPrimitive,
1391    ToPrimitive,
1392    ToSql,
1393    FromSql,
1394    Serialize,
1395    Deserialize,
1396)]
1397#[repr(u32)]
1398pub enum MessageState {
1399    /// Undefined message state.
1400    #[default]
1401    Undefined = 0,
1402
1403    /// Incoming *fresh* message. Fresh messages are neither noticed
1404    /// nor seen and are typically shown in notifications.
1405    InFresh = 10,
1406
1407    /// Incoming *noticed* message. E.g. chat opened but message not
1408    /// yet read - noticed messages are not counted as unread but did
1409    /// not marked as read nor resulted in MDNs.
1410    InNoticed = 13,
1411
1412    /// Incoming message, really *seen* by the user. Marked as read on
1413    /// IMAP and MDN may be sent.
1414    InSeen = 16,
1415
1416    /// For files which need time to be prepared before they can be
1417    /// sent, the message enters this state before
1418    /// OutPending.
1419    OutPreparing = 18,
1420
1421    /// Message saved as draft.
1422    OutDraft = 19,
1423
1424    /// The user has pressed the "send" button but the message is not
1425    /// yet sent and is pending in some way. Maybe we're offline (no
1426    /// checkmark).
1427    OutPending = 20,
1428
1429    /// *Unrecoverable* error (*recoverable* errors result in pending
1430    /// messages).
1431    OutFailed = 24,
1432
1433    /// Outgoing message successfully delivered to server (one
1434    /// checkmark). Note, that already delivered messages may get into
1435    /// the OutFailed state if we get such a hint from the server.
1436    OutDelivered = 26,
1437
1438    /// Outgoing message read by the recipient (two checkmarks; this
1439    /// requires goodwill on the receiver's side). Not used in the db for new messages.
1440    OutMdnRcvd = 28,
1441}
1442
1443impl std::fmt::Display for MessageState {
1444    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1445        write!(
1446            f,
1447            "{}",
1448            match self {
1449                Self::Undefined => "Undefined",
1450                Self::InFresh => "Fresh",
1451                Self::InNoticed => "Noticed",
1452                Self::InSeen => "Seen",
1453                Self::OutPreparing => "Preparing",
1454                Self::OutDraft => "Draft",
1455                Self::OutPending => "Pending",
1456                Self::OutFailed => "Failed",
1457                Self::OutDelivered => "Delivered",
1458                Self::OutMdnRcvd => "Read",
1459            }
1460        )
1461    }
1462}
1463
1464impl MessageState {
1465    /// Returns true if the message can transition to `OutFailed` state from the current state.
1466    pub fn can_fail(self) -> bool {
1467        use MessageState::*;
1468        matches!(
1469            self,
1470            OutPreparing | OutPending | OutDelivered | OutMdnRcvd // OutMdnRcvd can still fail because it could be a group message and only some recipients failed.
1471        )
1472    }
1473
1474    /// Returns true for any outgoing message states.
1475    pub fn is_outgoing(self) -> bool {
1476        use MessageState::*;
1477        matches!(
1478            self,
1479            OutPreparing | OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
1480        )
1481    }
1482
1483    /// Returns adjusted message state if the message has MDNs.
1484    pub(crate) fn with_mdns(self, has_mdns: bool) -> Self {
1485        if self == MessageState::OutDelivered && has_mdns {
1486            return MessageState::OutMdnRcvd;
1487        }
1488        self
1489    }
1490}
1491
1492/// Returns contacts that sent read receipts and the time of reading.
1493pub async fn get_msg_read_receipts(
1494    context: &Context,
1495    msg_id: MsgId,
1496) -> Result<Vec<(ContactId, i64)>> {
1497    context
1498        .sql
1499        .query_map_vec(
1500            "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
1501            (msg_id,),
1502            |row| {
1503                let contact_id: ContactId = row.get(0)?;
1504                let ts: i64 = row.get(1)?;
1505                Ok((contact_id, ts))
1506            },
1507        )
1508        .await
1509}
1510
1511pub(crate) fn guess_msgtype_from_suffix(msg: &Message) -> Option<(Viewtype, &'static str)> {
1512    msg.param
1513        .get(Param::Filename)
1514        .or_else(|| msg.param.get(Param::File))
1515        .and_then(|file| guess_msgtype_from_path_suffix(Path::new(file)))
1516}
1517
1518pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, &'static str)> {
1519    let extension: &str = &path.extension()?.to_str()?.to_lowercase();
1520    let info = match extension {
1521        // before using viewtype other than Viewtype::File,
1522        // make sure, all target UIs support that type.
1523        //
1524        // it is a non-goal to support as many formats as possible in-app.
1525        // additional parser come at security and maintainance costs and
1526        // should only be added when strictly neccessary,
1527        // eg. when a format comes from the camera app on a significant number of devices.
1528        // it is okay, when eg. dragging some video from a browser results in a "File"
1529        // for everyone, sender as well as all receivers.
1530        //
1531        // if in doubt, it is better to default to Viewtype::File that passes handing to an external app.
1532        // (cmp. <https://developer.android.com/guide/topics/media/media-formats>)
1533        "3gp" => (Viewtype::Video, "video/3gpp"),
1534        "aac" => (Viewtype::Audio, "audio/aac"),
1535        "avi" => (Viewtype::Video, "video/x-msvideo"),
1536        "avif" => (Viewtype::File, "image/avif"), // supported since Android 12 / iOS 16
1537        "doc" => (Viewtype::File, "application/msword"),
1538        "docx" => (
1539            Viewtype::File,
1540            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1541        ),
1542        "epub" => (Viewtype::File, "application/epub+zip"),
1543        "flac" => (Viewtype::Audio, "audio/flac"),
1544        "gif" => (Viewtype::Gif, "image/gif"),
1545        "heic" => (Viewtype::File, "image/heic"), // supported since Android 10 / iOS 11
1546        "heif" => (Viewtype::File, "image/heif"), // supported since Android 10 / iOS 11
1547        "html" => (Viewtype::File, "text/html"),
1548        "htm" => (Viewtype::File, "text/html"),
1549        "ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
1550        "jar" => (Viewtype::File, "application/java-archive"),
1551        "jpeg" => (Viewtype::Image, "image/jpeg"),
1552        "jpe" => (Viewtype::Image, "image/jpeg"),
1553        "jpg" => (Viewtype::Image, "image/jpeg"),
1554        "json" => (Viewtype::File, "application/json"),
1555        "mov" => (Viewtype::Video, "video/quicktime"),
1556        "m4a" => (Viewtype::Audio, "audio/m4a"),
1557        "mp3" => (Viewtype::Audio, "audio/mpeg"),
1558        "mp4" => (Viewtype::Video, "video/mp4"),
1559        "odp" => (
1560            Viewtype::File,
1561            "application/vnd.oasis.opendocument.presentation",
1562        ),
1563        "ods" => (
1564            Viewtype::File,
1565            "application/vnd.oasis.opendocument.spreadsheet",
1566        ),
1567        "odt" => (Viewtype::File, "application/vnd.oasis.opendocument.text"),
1568        "oga" => (Viewtype::Audio, "audio/ogg"),
1569        "ogg" => (Viewtype::Audio, "audio/ogg"),
1570        "ogv" => (Viewtype::File, "video/ogg"),
1571        "opus" => (Viewtype::File, "audio/ogg"), // supported since Android 10
1572        "otf" => (Viewtype::File, "font/otf"),
1573        "pdf" => (Viewtype::File, "application/pdf"),
1574        "png" => (Viewtype::Image, "image/png"),
1575        "ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
1576        "pptx" => (
1577            Viewtype::File,
1578            "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1579        ),
1580        "rar" => (Viewtype::File, "application/vnd.rar"),
1581        "rtf" => (Viewtype::File, "application/rtf"),
1582        "spx" => (Viewtype::File, "audio/ogg"), // Ogg Speex Profile
1583        "svg" => (Viewtype::File, "image/svg+xml"),
1584        "tgs" => (Viewtype::File, "application/x-tgsticker"),
1585        "tiff" => (Viewtype::File, "image/tiff"),
1586        "tif" => (Viewtype::File, "image/tiff"),
1587        "ttf" => (Viewtype::File, "font/ttf"),
1588        "txt" => (Viewtype::File, "text/plain"),
1589        "vcard" => (Viewtype::Vcard, "text/vcard"),
1590        "vcf" => (Viewtype::Vcard, "text/vcard"),
1591        "wav" => (Viewtype::Audio, "audio/wav"),
1592        "weba" => (Viewtype::File, "audio/webm"),
1593        "webm" => (Viewtype::File, "video/webm"), // not supported natively by iOS nor by SDWebImage
1594        "webp" => (Viewtype::Image, "image/webp"), // iOS via SDWebImage, Android since 4.0
1595        "wmv" => (Viewtype::Video, "video/x-ms-wmv"),
1596        "xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
1597        "xhtml" => (Viewtype::File, "application/xhtml+xml"),
1598        "xls" => (Viewtype::File, "application/vnd.ms-excel"),
1599        "xlsx" => (
1600            Viewtype::File,
1601            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1602        ),
1603        "xml" => (Viewtype::File, "application/xml"),
1604        "zip" => (Viewtype::File, "application/zip"),
1605        _ => {
1606            return None;
1607        }
1608    };
1609    Some(info)
1610}
1611
1612/// Get the raw mime-headers of the given message.
1613/// Raw headers are saved for large messages
1614/// that need a "Show full message..."
1615/// to see HTML part.
1616///
1617/// Returns an empty vector if there are no headers saved for the given message.
1618pub(crate) async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8>> {
1619    let (headers, compressed) = context
1620        .sql
1621        .query_row(
1622            "SELECT mime_headers, mime_compressed FROM msgs WHERE id=?",
1623            (msg_id,),
1624            |row| {
1625                let headers = sql::row_get_vec(row, 0)?;
1626                let compressed: bool = row.get(1)?;
1627                Ok((headers, compressed))
1628            },
1629        )
1630        .await?;
1631    if compressed {
1632        return buf_decompress(&headers);
1633    }
1634
1635    let headers2 = headers.clone();
1636    let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) {
1637        Err(e) => {
1638            warn!(context, "get_mime_headers: buf_compress() failed: {}", e);
1639            return Ok(headers);
1640        }
1641        Ok(o) => o,
1642    };
1643    let update = |conn: &mut rusqlite::Connection| {
1644        match conn.execute(
1645            "\
1646            UPDATE msgs SET mime_headers=?, mime_compressed=1 \
1647            WHERE id=? AND mime_headers!='' AND mime_compressed=0",
1648            (compressed, msg_id),
1649        ) {
1650            Ok(rows_updated) => ensure!(rows_updated <= 1),
1651            Err(e) => {
1652                warn!(context, "get_mime_headers: UPDATE failed: {}", e);
1653                return Err(e.into());
1654            }
1655        }
1656        Ok(())
1657    };
1658    if let Err(e) = context.sql.call_write(update).await {
1659        warn!(
1660            context,
1661            "get_mime_headers: failed to update mime_headers: {}", e
1662        );
1663    }
1664
1665    Ok(headers)
1666}
1667
1668/// Delete a single message from the database, including references in other tables.
1669/// This may be called in batches; the final events are emitted in delete_msgs_locally_done() then.
1670pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> {
1671    if msg.location_id > 0 {
1672        delete_poi_location(context, msg.location_id).await?;
1673    }
1674    let on_server = true;
1675    msg.id
1676        .trash(context, on_server)
1677        .await
1678        .with_context(|| format!("Unable to trash message {}", msg.id))?;
1679
1680    context.emit_event(EventType::MsgDeleted {
1681        chat_id: msg.chat_id,
1682        msg_id: msg.id,
1683    });
1684
1685    if msg.viewtype == Viewtype::Webxdc {
1686        context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: msg.id });
1687    }
1688
1689    let logging_xdc_id = context
1690        .debug_logging
1691        .read()
1692        .expect("RwLock is poisoned")
1693        .as_ref()
1694        .map(|dl| dl.msg_id);
1695    if let Some(id) = logging_xdc_id
1696        && id == msg.id
1697    {
1698        set_debug_logging_xdc(context, None).await?;
1699    }
1700
1701    Ok(())
1702}
1703
1704/// Do final events and jobs after batch deletion using calls to delete_msg_locally().
1705/// To avoid additional database queries, collecting data is up to the caller.
1706pub(crate) async fn delete_msgs_locally_done(
1707    context: &Context,
1708    msg_ids: &[MsgId],
1709    modified_chat_ids: HashSet<ChatId>,
1710) -> Result<()> {
1711    for modified_chat_id in modified_chat_ids {
1712        context.emit_msgs_changed_without_msg_id(modified_chat_id);
1713        chatlist_events::emit_chatlist_item_changed(context, modified_chat_id);
1714    }
1715    if !msg_ids.is_empty() {
1716        context.emit_msgs_changed_without_ids();
1717        chatlist_events::emit_chatlist_changed(context);
1718        // Run housekeeping to delete unused blobs.
1719        context
1720            .set_config_internal(Config::LastHousekeeping, None)
1721            .await?;
1722    }
1723    Ok(())
1724}
1725
1726/// Delete messages on all devices and on IMAP.
1727pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
1728    delete_msgs_ex(context, msg_ids, false).await
1729}
1730
1731/// Delete messages on all devices, on IMAP and optionally for all chat members.
1732/// Deleted messages are moved to the trash chat and scheduling for deletion on IMAP.
1733/// When deleting messages for others, all messages must be self-sent and in the same chat.
1734pub async fn delete_msgs_ex(
1735    context: &Context,
1736    msg_ids: &[MsgId],
1737    delete_for_all: bool,
1738) -> Result<()> {
1739    let mut modified_chat_ids = HashSet::new();
1740    let mut deleted_rfc724_mid = Vec::new();
1741    let mut res = Ok(());
1742
1743    for &msg_id in msg_ids {
1744        let msg = Message::load_from_db(context, msg_id).await?;
1745        ensure!(
1746            !delete_for_all || msg.from_id == ContactId::SELF,
1747            "Can delete only own messages for others"
1748        );
1749        ensure!(
1750            !delete_for_all || msg.get_showpadlock(),
1751            "Cannot request deletion of unencrypted message for others"
1752        );
1753
1754        modified_chat_ids.insert(msg.chat_id);
1755        deleted_rfc724_mid.push(msg.rfc724_mid.clone());
1756
1757        let target = context.get_delete_msgs_target().await?;
1758        let update_db = |trans: &mut rusqlite::Transaction| {
1759            let mut stmt = trans.prepare("UPDATE imap SET target=? WHERE rfc724_mid=?")?;
1760            stmt.execute((&target, &msg.rfc724_mid))?;
1761            if !msg.pre_rfc724_mid.is_empty() {
1762                stmt.execute((&target, &msg.pre_rfc724_mid))?;
1763            }
1764            trans.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))?;
1765            trans.execute(
1766                "DELETE FROM download WHERE rfc724_mid=?",
1767                (&msg.rfc724_mid,),
1768            )?;
1769            trans.execute(
1770                "DELETE FROM available_post_msgs WHERE rfc724_mid=?",
1771                (&msg.rfc724_mid,),
1772            )?;
1773            Ok(())
1774        };
1775        if let Err(e) = context.sql.transaction(update_db).await {
1776            error!(context, "delete_msgs: failed to update db: {e:#}.");
1777            res = Err(e);
1778            continue;
1779        }
1780    }
1781    res?;
1782
1783    if delete_for_all {
1784        ensure!(
1785            modified_chat_ids.len() == 1,
1786            "Can delete only from same chat."
1787        );
1788        if let Some(chat_id) = modified_chat_ids.iter().next() {
1789            let mut msg = Message::new_text("🚮".to_owned());
1790            // We don't want to send deletion requests in chats w/o encryption:
1791            // - These are usually chats with non-DC clients who won't respect deletion requests
1792            //   anyway and display a weird trash bin message instead.
1793            // - Deletion of world-visible unencrypted messages seems not very useful.
1794            msg.param.set_int(Param::GuaranteeE2ee, 1);
1795            msg.param
1796                .set(Param::DeleteRequestFor, deleted_rfc724_mid.join(" "));
1797            msg.hidden = true;
1798            send_msg(context, *chat_id, &mut msg).await?;
1799        }
1800    } else {
1801        context
1802            .add_sync_item(SyncData::DeleteMessages {
1803                msgs: deleted_rfc724_mid,
1804            })
1805            .await?;
1806        context.scheduler.interrupt_smtp().await;
1807    }
1808
1809    for &msg_id in msg_ids {
1810        let msg = Message::load_from_db(context, msg_id).await?;
1811        delete_msg_locally(context, &msg).await?;
1812    }
1813    delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?;
1814
1815    // Interrupt Inbox loop to start message deletion, run housekeeping and call send_sync_msg().
1816    context.scheduler.interrupt_inbox().await;
1817
1818    Ok(())
1819}
1820
1821/// Marks requested messages as seen.
1822pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()> {
1823    if msg_ids.is_empty() {
1824        return Ok(());
1825    }
1826
1827    let old_last_msg_id = MsgId::new(context.get_config_u32(Config::LastMsgId).await?);
1828    let last_msg_id = msg_ids.iter().fold(&old_last_msg_id, std::cmp::max);
1829    context
1830        .set_config_internal(Config::LastMsgId, Some(&last_msg_id.to_u32().to_string()))
1831        .await?;
1832
1833    let mut msgs = Vec::with_capacity(msg_ids.len());
1834    for &id in &msg_ids {
1835        if let Some(msg) = context
1836            .sql
1837            .query_row_optional(
1838                "SELECT
1839                    m.chat_id AS chat_id,
1840                    m.state AS state,
1841                    m.ephemeral_timer AS ephemeral_timer,
1842                    m.param AS param,
1843                    m.from_id AS from_id,
1844                    m.rfc724_mid AS rfc724_mid,
1845                    c.archived AS archived,
1846                    c.blocked AS blocked
1847                 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
1848                 WHERE m.id=? AND m.chat_id>9",
1849                (id,),
1850                |row| {
1851                    let chat_id: ChatId = row.get("chat_id")?;
1852                    let state: MessageState = row.get("state")?;
1853                    let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
1854                    let from_id: ContactId = row.get("from_id")?;
1855                    let rfc724_mid: String = row.get("rfc724_mid")?;
1856                    let visibility: ChatVisibility = row.get("archived")?;
1857                    let blocked: Option<Blocked> = row.get("blocked")?;
1858                    let ephemeral_timer: EphemeralTimer = row.get("ephemeral_timer")?;
1859                    Ok((
1860                        (
1861                            id,
1862                            chat_id,
1863                            state,
1864                            param,
1865                            from_id,
1866                            rfc724_mid,
1867                            visibility,
1868                            blocked.unwrap_or_default(),
1869                        ),
1870                        ephemeral_timer,
1871                    ))
1872                },
1873            )
1874            .await?
1875        {
1876            msgs.push(msg);
1877        }
1878    }
1879
1880    if msgs
1881        .iter()
1882        .any(|(_, ephemeral_timer)| *ephemeral_timer != EphemeralTimer::Disabled)
1883    {
1884        start_ephemeral_timers_msgids(context, &msg_ids)
1885            .await
1886            .context("failed to start ephemeral timers")?;
1887    }
1888
1889    let mut updated_chat_ids = BTreeSet::new();
1890    let mut archived_chats_maybe_noticed = false;
1891    for (
1892        (
1893            id,
1894            curr_chat_id,
1895            curr_state,
1896            curr_param,
1897            curr_from_id,
1898            curr_rfc724_mid,
1899            curr_visibility,
1900            curr_blocked,
1901        ),
1902        _curr_ephemeral_timer,
1903    ) in msgs
1904    {
1905        if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
1906            update_msg_state(context, id, MessageState::InSeen).await?;
1907            info!(context, "Seen message {}.", id);
1908
1909            markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1910
1911            // Read receipts for system messages are never sent. These messages have no place to
1912            // display received read receipt anyway.  And since their text is locally generated,
1913            // quoting them is dangerous as it may contain contact names. E.g., for original message
1914            // "Group left by me", a read receipt will quote "Group left by <name>", and the name can
1915            // be a display name stored in address book rather than the name sent in the From field by
1916            // the user.
1917            //
1918            // We also don't send read receipts for contact requests.
1919            // Read receipts will not be sent even after accepting the chat.
1920            if curr_blocked == Blocked::Not
1921                && curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1922                && curr_param.get_cmd() == SystemMessage::Unknown
1923                && context.should_send_mdns().await?
1924            {
1925                context
1926                    .sql
1927                    .execute(
1928                        "INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1929                        (id, curr_from_id, curr_rfc724_mid),
1930                    )
1931                    .await
1932                    .context("failed to insert into smtp_mdns")?;
1933                context.scheduler.interrupt_smtp().await;
1934            }
1935            updated_chat_ids.insert(curr_chat_id);
1936        }
1937        archived_chats_maybe_noticed |=
1938            curr_state == MessageState::InFresh && curr_visibility == ChatVisibility::Archived;
1939    }
1940
1941    for updated_chat_id in updated_chat_ids {
1942        context.emit_event(EventType::MsgsNoticed(updated_chat_id));
1943        chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
1944    }
1945    if archived_chats_maybe_noticed {
1946        context.on_archived_chats_maybe_noticed();
1947    }
1948
1949    Ok(())
1950}
1951
1952/// Checks if the messages with given IDs exist.
1953///
1954/// Returns IDs of existing messages.
1955pub async fn get_existing_msg_ids(context: &Context, ids: &[MsgId]) -> Result<Vec<MsgId>> {
1956    let query_only = true;
1957    let res = context
1958        .sql
1959        .transaction_ex(query_only, |transaction| {
1960            let mut res: Vec<MsgId> = Vec::new();
1961            for id in ids {
1962                if transaction.query_one(
1963                    "SELECT COUNT(*) > 0 FROM msgs WHERE id=? AND chat_id!=3",
1964                    (id,),
1965                    |row| {
1966                        let exists: bool = row.get(0)?;
1967                        Ok(exists)
1968                    },
1969                )? {
1970                    res.push(*id);
1971                }
1972            }
1973            Ok(res)
1974        })
1975        .await?;
1976    Ok(res)
1977}
1978
1979pub(crate) async fn update_msg_state(
1980    context: &Context,
1981    msg_id: MsgId,
1982    state: MessageState,
1983) -> Result<()> {
1984    ensure!(
1985        state != MessageState::OutMdnRcvd,
1986        "Update msgs_mdns table instead!"
1987    );
1988    ensure!(state != MessageState::OutFailed, "use set_msg_failed()!");
1989    let error_subst = match state >= MessageState::OutPending {
1990        true => ", error=''",
1991        false => "",
1992    };
1993    context
1994        .sql
1995        .execute(
1996            &format!("UPDATE msgs SET state=? {error_subst} WHERE id=?"),
1997            (state, msg_id),
1998        )
1999        .await?;
2000    Ok(())
2001}
2002
2003// as we do not cut inside words, this results in about 32-42 characters.
2004// Do not use too long subjects - we add a tag after the subject which gets truncated by the clients otherwise.
2005// It should also be very clear, the subject is _not_ the whole message.
2006// The value is also used for CC:-summaries
2007
2008// Context functions to work with messages
2009
2010pub(crate) async fn set_msg_failed(
2011    context: &Context,
2012    msg: &mut Message,
2013    error: &str,
2014) -> Result<()> {
2015    if msg.state.can_fail() {
2016        msg.state = MessageState::OutFailed;
2017        warn!(context, "{} failed: {}", msg.id, error);
2018    } else {
2019        warn!(
2020            context,
2021            "{} seems to have failed ({}), but state is {}", msg.id, error, msg.state
2022        )
2023    }
2024    msg.error = Some(error.to_string());
2025
2026    let exists = context
2027        .sql
2028        .execute(
2029            "UPDATE msgs SET state=?, error=? WHERE id=?;",
2030            (msg.state, error, msg.id),
2031        )
2032        .await?
2033        > 0;
2034    context.emit_event(EventType::MsgFailed {
2035        chat_id: msg.chat_id,
2036        msg_id: msg.id,
2037    });
2038    if exists {
2039        chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
2040    }
2041    Ok(())
2042}
2043
2044/// The number of messages assigned to unblocked chats
2045pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
2046    match context
2047        .sql
2048        .count(
2049            "SELECT COUNT(*) \
2050         FROM msgs m  LEFT JOIN chats c ON c.id=m.chat_id \
2051         WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
2052            (),
2053        )
2054        .await
2055    {
2056        Ok(res) => res,
2057        Err(err) => {
2058            error!(context, "get_unblocked_msg_cnt() failed. {:#}", err);
2059            0
2060        }
2061    }
2062}
2063
2064/// Returns the number of messages in contact request chats.
2065pub async fn get_request_msg_cnt(context: &Context) -> usize {
2066    match context
2067        .sql
2068        .count(
2069            "SELECT COUNT(*) \
2070         FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2071         WHERE c.blocked=2;",
2072            (),
2073        )
2074        .await
2075    {
2076        Ok(res) => res,
2077        Err(err) => {
2078            error!(context, "get_request_msg_cnt() failed. {:#}", err);
2079            0
2080        }
2081    }
2082}
2083
2084/// Estimates the number of messages that will be deleted
2085/// by the options `delete_device_after` or `delete_server_after`.
2086///
2087/// This is typically used to show the estimated impact to the user
2088/// before actually enabling deletion of old messages.
2089///
2090/// If `from_server` is true,
2091/// estimate deletion count for server,
2092/// otherwise estimate deletion count for device.
2093///
2094/// Count messages older than the given number of `seconds`.
2095///
2096/// Returns the number of messages that are older than the given number of seconds.
2097/// This includes e-mails downloaded due to the `show_emails` option.
2098/// Messages in the "saved messages" folder are not counted as they will not be deleted automatically.
2099pub async fn estimate_deletion_cnt(
2100    context: &Context,
2101    from_server: bool,
2102    seconds: i64,
2103) -> Result<usize> {
2104    let self_chat_id = ChatIdBlocked::lookup_by_contact(context, ContactId::SELF)
2105        .await?
2106        .map(|c| c.id)
2107        .unwrap_or_default();
2108    let threshold_timestamp = time() - seconds;
2109
2110    let cnt = if from_server {
2111        context
2112            .sql
2113            .count(
2114                "SELECT COUNT(*)
2115             FROM msgs m
2116             WHERE m.id > ?
2117               AND timestamp < ?
2118               AND chat_id != ?
2119               AND EXISTS (SELECT * FROM imap WHERE rfc724_mid=m.rfc724_mid);",
2120                (DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id),
2121            )
2122            .await?
2123    } else {
2124        context
2125            .sql
2126            .count(
2127                "SELECT COUNT(*)
2128             FROM msgs m
2129             WHERE m.id > ?
2130               AND timestamp < ?
2131               AND chat_id != ?
2132               AND chat_id != ? AND hidden = 0;",
2133                (
2134                    DC_MSG_ID_LAST_SPECIAL,
2135                    threshold_timestamp,
2136                    self_chat_id,
2137                    DC_CHAT_ID_TRASH,
2138                ),
2139            )
2140            .await?
2141    };
2142    Ok(cnt)
2143}
2144
2145/// See [`rfc724_mid_exists_ex()`].
2146pub(crate) async fn rfc724_mid_exists(
2147    context: &Context,
2148    rfc724_mid: &str,
2149) -> Result<Option<MsgId>> {
2150    Ok(rfc724_mid_exists_ex(context, rfc724_mid, "1")
2151        .await?
2152        .map(|(id, _)| id))
2153}
2154
2155/// Returns [MsgId] of the most recent message with given `rfc724_mid`
2156/// (Message-ID header) and bool `expr` result if such messages exists in the db.
2157///
2158/// * `expr`: SQL expression additionally passed into `SELECT`. Evaluated to `true` iff it is true
2159///   for all messages with the given `rfc724_mid`.
2160pub(crate) async fn rfc724_mid_exists_ex(
2161    context: &Context,
2162    rfc724_mid: &str,
2163    expr: &str,
2164) -> Result<Option<(MsgId, bool)>> {
2165    let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2166    if rfc724_mid.is_empty() {
2167        warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
2168        return Ok(None);
2169    }
2170
2171    let res = context
2172        .sql
2173        .query_row_optional(
2174            &("SELECT id, timestamp_sent, MIN(".to_string()
2175                + expr
2176                + ") FROM msgs WHERE rfc724_mid=?1 OR pre_rfc724_mid=?1
2177              HAVING COUNT(*) > 0 -- Prevent MIN(expr) from returning NULL when there are no rows.
2178              ORDER BY timestamp_sent DESC"),
2179            (rfc724_mid,),
2180            |row| {
2181                let msg_id: MsgId = row.get(0)?;
2182                let expr_res: bool = row.get(2)?;
2183                Ok((msg_id, expr_res))
2184            },
2185        )
2186        .await?;
2187
2188    Ok(res)
2189}
2190
2191/// Returns `true` iff there is a message
2192/// with the given `rfc724_mid`
2193/// and a download state other than `DownloadState::Available`,
2194/// i.e. it was already tried to download the message or it's sent locally.
2195pub(crate) async fn rfc724_mid_download_tried(context: &Context, rfc724_mid: &str) -> Result<bool> {
2196    let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2197    if rfc724_mid.is_empty() {
2198        warn!(
2199            context,
2200            "Empty rfc724_mid passed to rfc724_mid_download_tried"
2201        );
2202        return Ok(false);
2203    }
2204
2205    let res = context
2206        .sql
2207        .exists(
2208            "SELECT COUNT(*) FROM msgs
2209             WHERE rfc724_mid=? AND download_state<>?",
2210            (rfc724_mid, DownloadState::Available),
2211        )
2212        .await?;
2213
2214    Ok(res)
2215}
2216
2217/// Given a list of Message-IDs, returns the most relevant message found in the database.
2218///
2219/// Relevance here is `(download_state == Done, index)`, where `index` is an index of Message-ID in
2220/// `mids`. This means Message-IDs should be ordered from the least late to the latest one (like in
2221/// the References header).
2222/// Only messages that are not in the trash chat are considered.
2223pub(crate) async fn get_by_rfc724_mids(
2224    context: &Context,
2225    mids: &[String],
2226) -> Result<Option<Message>> {
2227    let mut latest = None;
2228    for id in mids.iter().rev() {
2229        let Some(msg_id) = rfc724_mid_exists(context, id).await? else {
2230            continue;
2231        };
2232        let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2233            continue;
2234        };
2235        if msg.download_state == DownloadState::Done {
2236            return Ok(Some(msg));
2237        }
2238        latest.get_or_insert(msg);
2239    }
2240    Ok(latest)
2241}
2242
2243/// Returns the 1st part of summary text (i.e. before the dash if any) for a valid DeltaChat vCard.
2244pub(crate) fn get_vcard_summary(vcard: &[u8]) -> Option<String> {
2245    let vcard = str::from_utf8(vcard).ok()?;
2246    let contacts = deltachat_contact_tools::parse_vcard(vcard);
2247    let [c] = &contacts[..] else {
2248        return None;
2249    };
2250    if !deltachat_contact_tools::may_be_valid_addr(&c.addr) {
2251        return None;
2252    }
2253    Some(c.display_name().to_string())
2254}
2255
2256/// How a message is primarily displayed.
2257#[derive(
2258    Debug,
2259    Default,
2260    Display,
2261    Clone,
2262    Copy,
2263    PartialEq,
2264    Eq,
2265    FromPrimitive,
2266    ToPrimitive,
2267    FromSql,
2268    ToSql,
2269    Serialize,
2270    Deserialize,
2271)]
2272#[repr(u32)]
2273pub enum Viewtype {
2274    /// Unknown message type.
2275    #[default]
2276    Unknown = 0,
2277
2278    /// Text message.
2279    /// The text of the message is set using dc_msg_set_text() and retrieved with dc_msg_get_text().
2280    Text = 10,
2281
2282    /// Image message.
2283    /// If the image is a GIF and has the appropriate extension, the viewtype is auto-changed to
2284    /// `Gif` when sending the message.
2285    /// File, width and height are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_dimension()
2286    /// and retrieved via dc_msg_get_file(), dc_msg_get_height(), dc_msg_get_width().
2287    Image = 20,
2288
2289    /// Animated GIF message.
2290    /// File, width and height are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_dimension()
2291    /// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
2292    Gif = 21,
2293
2294    /// Message containing a sticker, similar to image.
2295    /// NB: When sending, the message viewtype may be changed to `Image` by some heuristics like
2296    /// checking for transparent pixels. Use `Message::force_sticker()` to disable them.
2297    ///
2298    /// If possible, the ui should display the image without borders in a transparent way.
2299    /// A click on a sticker will offer to install the sticker set in some future.
2300    Sticker = 23,
2301
2302    /// Message containing an Audio file.
2303    /// File and duration are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_duration()
2304    /// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
2305    Audio = 40,
2306
2307    /// A voice message that was directly recorded by the user.
2308    /// For all other audio messages, the type #DC_MSG_AUDIO should be used.
2309    /// File and duration are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_duration()
2310    /// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
2311    Voice = 41,
2312
2313    /// Video messages.
2314    /// File, width, height and durarion
2315    /// are set via dc_msg_set_file_and_deduplicate(), dc_msg_set_dimension(), dc_msg_set_duration()
2316    /// and retrieved via
2317    /// dc_msg_get_file(), dc_msg_get_width(),
2318    /// dc_msg_get_height(), dc_msg_get_duration().
2319    Video = 50,
2320
2321    /// Message containing any file, eg. a PDF.
2322    /// The file is set via dc_msg_set_file_and_deduplicate()
2323    /// and retrieved via dc_msg_get_file().
2324    File = 60,
2325
2326    /// Message is an incoming or outgoing call.
2327    Call = 71,
2328
2329    /// Message is an webxdc instance.
2330    Webxdc = 80,
2331
2332    /// Message containing shared contacts represented as a vCard (virtual contact file)
2333    /// with email addresses and possibly other fields.
2334    /// Use `parse_vcard()` to retrieve them.
2335    Vcard = 90,
2336}
2337
2338impl Viewtype {
2339    /// Whether a message with this [`Viewtype`] should have a file attachment.
2340    pub fn has_file(&self) -> bool {
2341        match self {
2342            Viewtype::Unknown => false,
2343            Viewtype::Text => false,
2344            Viewtype::Image => true,
2345            Viewtype::Gif => true,
2346            Viewtype::Sticker => true,
2347            Viewtype::Audio => true,
2348            Viewtype::Voice => true,
2349            Viewtype::Video => true,
2350            Viewtype::File => true,
2351            Viewtype::Call => false,
2352            Viewtype::Webxdc => true,
2353            Viewtype::Vcard => true,
2354        }
2355    }
2356}
2357
2358#[cfg(test)]
2359mod message_tests;