1use std::collections::BTreeSet;
4use std::collections::HashSet;
5use std::path::{Path, PathBuf};
6use std::str;
7
8use anyhow::{ensure, format_err, Context as _, Result};
9use deltachat_contact_tools::{parse_vcard, VcardContact};
10use deltachat_derive::{FromSql, ToSql};
11use serde::{Deserialize, Serialize};
12use tokio::{fs, io};
13
14use crate::blob::BlobObject;
15use crate::chat::{send_msg, Chat, ChatId, ChatIdBlocked, ChatVisibility};
16use crate::chatlist_events;
17use crate::config::Config;
18use crate::constants::{
19 Blocked, Chattype, VideochatType, DC_CHAT_ID_TRASH, DC_MSG_ID_LAST_SPECIAL,
20};
21use crate::contact::{self, Contact, ContactId};
22use crate::context::Context;
23use crate::debug_logging::set_debug_logging_xdc;
24use crate::download::DownloadState;
25use crate::ephemeral::{start_ephemeral_timers_msgids, Timer as EphemeralTimer};
26use crate::events::EventType;
27use crate::imap::markseen_on_imap_table;
28use crate::location::delete_poi_location;
29use crate::log::{error, info, warn};
30use crate::mimeparser::{parse_message_id, SystemMessage};
31use crate::param::{Param, Params};
32use crate::pgp::split_armored_data;
33use crate::reaction::get_msg_reactions;
34use crate::sql;
35use crate::summary::Summary;
36use crate::sync::SyncData;
37use crate::tools::create_outgoing_rfc724_mid;
38use crate::tools::{
39 buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file,
40 sanitize_filename, time, timestamp_to_str,
41};
42
43#[derive(
49 Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
50)]
51pub struct MsgId(u32);
52
53impl MsgId {
54 pub fn new(id: u32) -> MsgId {
56 MsgId(id)
57 }
58
59 pub fn new_unset() -> MsgId {
61 MsgId(0)
62 }
63
64 pub fn is_special(self) -> bool {
68 self.0 <= DC_MSG_ID_LAST_SPECIAL
69 }
70
71 pub fn is_unset(self) -> bool {
81 self.0 == 0
82 }
83
84 pub async fn get_state(self, context: &Context) -> Result<MessageState> {
86 let result = context
87 .sql
88 .query_row_optional(
89 concat!(
90 "SELECT m.state, mdns.msg_id",
91 " FROM msgs m LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
92 " WHERE id=?",
93 " LIMIT 1",
94 ),
95 (self,),
96 |row| {
97 let state: MessageState = row.get(0)?;
98 let mdn_msg_id: Option<MsgId> = row.get(1)?;
99 Ok(state.with_mdns(mdn_msg_id.is_some()))
100 },
101 )
102 .await?
103 .unwrap_or_default();
104 Ok(result)
105 }
106
107 pub(crate) async fn get_param(self, context: &Context) -> Result<Params> {
108 let res: Option<String> = context
109 .sql
110 .query_get_value("SELECT param FROM msgs WHERE id=?", (self,))
111 .await?;
112 Ok(res
113 .map(|s| s.parse().unwrap_or_default())
114 .unwrap_or_default())
115 }
116
117 pub async fn trash(self, context: &Context, on_server: bool) -> Result<()> {
129 let chat_id = DC_CHAT_ID_TRASH;
130 let deleted_subst = match on_server {
131 true => ", deleted=1",
132 false => "",
133 };
134 context
135 .sql
136 .execute(
137 &format!(
140 "UPDATE msgs SET \
141 chat_id=?, txt='', txt_normalized=NULL, \
142 subject='', txt_raw='', \
143 mime_headers='', \
144 from_id=0, to_id=0, \
145 param=''{deleted_subst} \
146 WHERE id=?"
147 ),
148 (chat_id, self),
149 )
150 .await?;
151
152 Ok(())
153 }
154
155 pub(crate) async fn set_delivered(self, context: &Context) -> Result<()> {
156 update_msg_state(context, self, MessageState::OutDelivered).await?;
157 let chat_id: Option<ChatId> = context
158 .sql
159 .query_get_value("SELECT chat_id FROM msgs WHERE id=?", (self,))
160 .await?;
161 context.emit_event(EventType::MsgDelivered {
162 chat_id: chat_id.unwrap_or_default(),
163 msg_id: self,
164 });
165 if let Some(chat_id) = chat_id {
166 chatlist_events::emit_chatlist_item_changed(context, chat_id);
167 }
168 Ok(())
169 }
170
171 pub fn to_u32(self) -> u32 {
176 self.0
177 }
178
179 pub async fn get_info_server_urls(
181 context: &Context,
182 rfc724_mid: String,
183 ) -> Result<Vec<String>> {
184 context
185 .sql
186 .query_map(
187 "SELECT folder, uid FROM imap WHERE rfc724_mid=?",
188 (rfc724_mid,),
189 |row| {
190 let folder: String = row.get("folder")?;
191 let uid: u32 = row.get("uid")?;
192 Ok(format!("</{folder}/;UID={uid}>"))
193 },
194 |rows| {
195 rows.collect::<std::result::Result<Vec<_>, _>>()
196 .map_err(Into::into)
197 },
198 )
199 .await
200 }
201
202 pub async fn hop_info(self, context: &Context) -> Result<String> {
204 let hop_info = context
205 .sql
206 .query_get_value("SELECT IFNULL(hop_info, '') FROM msgs WHERE id=?", (self,))
207 .await?
208 .with_context(|| format!("Message {self} not found"))?;
209 Ok(hop_info)
210 }
211
212 pub async fn get_info(self, context: &Context) -> Result<String> {
214 let msg = Message::load_from_db(context, self).await?;
215
216 let mut ret = String::new();
217
218 let fts = timestamp_to_str(msg.get_timestamp());
219 ret += &format!("Sent: {fts}");
220
221 let from_contact = Contact::get_by_id(context, msg.from_id).await?;
222 let name = from_contact.get_name_n_addr();
223 if let Some(override_sender_name) = msg.get_override_sender_name() {
224 let addr = from_contact.get_addr();
225 ret += &format!(" by ~{override_sender_name} ({addr})");
226 } else {
227 ret += &format!(" by {name}");
228 }
229 ret += "\n";
230
231 if msg.from_id != ContactId::SELF {
232 let s = timestamp_to_str(if 0 != msg.timestamp_rcvd {
233 msg.timestamp_rcvd
234 } else {
235 msg.timestamp_sort
236 });
237 ret += &format!("Received: {}", &s);
238 ret += "\n";
239 }
240
241 if let EphemeralTimer::Enabled { duration } = msg.ephemeral_timer {
242 ret += &format!("Ephemeral timer: {duration}\n");
243 }
244
245 if msg.ephemeral_timestamp != 0 {
246 ret += &format!("Expires: {}\n", timestamp_to_str(msg.ephemeral_timestamp));
247 }
248
249 if msg.from_id == ContactId::INFO || msg.to_id == ContactId::INFO {
250 return Ok(ret);
252 }
253
254 if let Ok(rows) = context
255 .sql
256 .query_map(
257 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
258 (self,),
259 |row| {
260 let contact_id: ContactId = row.get(0)?;
261 let ts: i64 = row.get(1)?;
262 Ok((contact_id, ts))
263 },
264 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
265 )
266 .await
267 {
268 for (contact_id, ts) in rows {
269 let fts = timestamp_to_str(ts);
270 ret += &format!("Read: {fts}");
271
272 let name = Contact::get_by_id(context, contact_id)
273 .await
274 .map(|contact| contact.get_name_n_addr())
275 .unwrap_or_default();
276
277 ret += &format!(" by {name}");
278 ret += "\n";
279 }
280 }
281
282 ret += &format!("State: {}", msg.state);
283
284 if msg.has_location() {
285 ret += ", Location sent";
286 }
287
288 if 0 != msg.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() {
289 ret += ", Encrypted";
290 }
291
292 ret += "\n";
293
294 let reactions = get_msg_reactions(context, self).await?;
295 if !reactions.is_empty() {
296 ret += &format!("Reactions: {reactions}\n");
297 }
298
299 if let Some(error) = msg.error.as_ref() {
300 ret += &format!("Error: {error}");
301 }
302
303 if let Some(path) = msg.get_file(context) {
304 let bytes = get_filebytes(context, &path).await?;
305 ret += &format!(
306 "\nFile: {}, name: {}, {} bytes\n",
307 path.display(),
308 msg.get_filename().unwrap_or_default(),
309 bytes
310 );
311 }
312
313 if msg.viewtype != Viewtype::Text {
314 ret += "Type: ";
315 ret += &format!("{}", msg.viewtype);
316 ret += "\n";
317 ret += &format!("Mimetype: {}\n", &msg.get_filemime().unwrap_or_default());
318 }
319 let w = msg.param.get_int(Param::Width).unwrap_or_default();
320 let h = msg.param.get_int(Param::Height).unwrap_or_default();
321 if w != 0 || h != 0 {
322 ret += &format!("Dimension: {w} x {h}\n",);
323 }
324 let duration = msg.param.get_int(Param::Duration).unwrap_or_default();
325 if duration != 0 {
326 ret += &format!("Duration: {duration} ms\n",);
327 }
328 if !msg.rfc724_mid.is_empty() {
329 ret += &format!("\nMessage-ID: {}", msg.rfc724_mid);
330
331 let server_urls = Self::get_info_server_urls(context, msg.rfc724_mid).await?;
332 for server_url in server_urls {
333 ret += &format!("\nServer-URL: {server_url}");
335 }
336 }
337 let hop_info = self.hop_info(context).await?;
338
339 ret += "\n\n";
340 if hop_info.is_empty() {
341 ret += "No Hop Info";
342 } else {
343 ret += &hop_info;
344 }
345
346 Ok(ret)
347 }
348}
349
350impl std::fmt::Display for MsgId {
351 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352 write!(f, "Msg#{}", self.0)
353 }
354}
355
356impl rusqlite::types::ToSql for MsgId {
365 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
366 if self.0 <= DC_MSG_ID_LAST_SPECIAL {
367 return Err(rusqlite::Error::ToSqlConversionFailure(
368 format_err!("Invalid MsgId {}", self.0).into(),
369 ));
370 }
371 let val = rusqlite::types::Value::Integer(i64::from(self.0));
372 let out = rusqlite::types::ToSqlOutput::Owned(val);
373 Ok(out)
374 }
375}
376
377impl rusqlite::types::FromSql for MsgId {
379 fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
380 i64::column_result(value).and_then(|val| {
382 if 0 <= val && val <= i64::from(u32::MAX) {
383 Ok(MsgId::new(val as u32))
384 } else {
385 Err(rusqlite::types::FromSqlError::OutOfRange(val))
386 }
387 })
388 }
389}
390
391#[derive(
392 Debug,
393 Copy,
394 Clone,
395 PartialEq,
396 FromPrimitive,
397 ToPrimitive,
398 FromSql,
399 ToSql,
400 Serialize,
401 Deserialize,
402)]
403#[repr(u8)]
404pub(crate) enum MessengerMessage {
405 No = 0,
406 Yes = 1,
407
408 Reply = 2,
410}
411
412impl Default for MessengerMessage {
413 fn default() -> Self {
414 Self::No
415 }
416}
417
418#[derive(Debug, Clone, Default, Serialize, Deserialize)]
422pub struct Message {
423 pub(crate) id: MsgId,
425
426 pub(crate) from_id: ContactId,
428
429 pub(crate) to_id: ContactId,
431
432 pub(crate) chat_id: ChatId,
434
435 pub(crate) viewtype: Viewtype,
437
438 pub(crate) state: MessageState,
440 pub(crate) download_state: DownloadState,
441
442 pub(crate) hidden: bool,
444 pub(crate) timestamp_sort: i64,
445 pub(crate) timestamp_sent: i64,
446 pub(crate) timestamp_rcvd: i64,
447 pub(crate) ephemeral_timer: EphemeralTimer,
448 pub(crate) ephemeral_timestamp: i64,
449 pub(crate) text: String,
450
451 pub(crate) subject: String,
455
456 pub(crate) rfc724_mid: String,
458
459 pub(crate) in_reply_to: Option<String>,
461 pub(crate) is_dc_message: MessengerMessage,
462 pub(crate) original_msg_id: MsgId,
463 pub(crate) mime_modified: bool,
464 pub(crate) chat_blocked: Blocked,
465 pub(crate) location_id: u32,
466 pub(crate) error: Option<String>,
467 pub(crate) param: Params,
468}
469
470impl Message {
471 pub fn new(viewtype: Viewtype) -> Self {
473 Message {
474 viewtype,
475 rfc724_mid: create_outgoing_rfc724_mid(),
476 ..Default::default()
477 }
478 }
479
480 pub fn new_text(text: String) -> Self {
482 Message {
483 viewtype: Viewtype::Text,
484 text,
485 rfc724_mid: create_outgoing_rfc724_mid(),
486 ..Default::default()
487 }
488 }
489
490 pub async fn load_from_db(context: &Context, id: MsgId) -> Result<Message> {
494 let message = Self::load_from_db_optional(context, id)
495 .await?
496 .with_context(|| format!("Message {id} does not exist"))?;
497 Ok(message)
498 }
499
500 pub async fn load_from_db_optional(context: &Context, id: MsgId) -> Result<Option<Message>> {
504 ensure!(
505 !id.is_special(),
506 "Can not load special message ID {} from DB",
507 id
508 );
509 let msg = context
510 .sql
511 .query_row_optional(
512 concat!(
513 "SELECT",
514 " m.id AS id,",
515 " rfc724_mid AS rfc724mid,",
516 " m.mime_in_reply_to AS mime_in_reply_to,",
517 " m.chat_id AS chat_id,",
518 " m.from_id AS from_id,",
519 " m.to_id AS to_id,",
520 " m.timestamp AS timestamp,",
521 " m.timestamp_sent AS timestamp_sent,",
522 " m.timestamp_rcvd AS timestamp_rcvd,",
523 " m.ephemeral_timer AS ephemeral_timer,",
524 " m.ephemeral_timestamp AS ephemeral_timestamp,",
525 " m.type AS type,",
526 " m.state AS state,",
527 " mdns.msg_id AS mdn_msg_id,",
528 " m.download_state AS download_state,",
529 " m.error AS error,",
530 " m.msgrmsg AS msgrmsg,",
531 " m.starred AS original_msg_id,",
532 " m.mime_modified AS mime_modified,",
533 " m.txt AS txt,",
534 " m.subject AS subject,",
535 " m.param AS param,",
536 " m.hidden AS hidden,",
537 " m.location_id AS location,",
538 " c.blocked AS blocked",
539 " FROM msgs m",
540 " LEFT JOIN chats c ON c.id=m.chat_id",
541 " LEFT JOIN msgs_mdns mdns ON mdns.msg_id=m.id",
542 " WHERE m.id=? AND chat_id!=3",
543 " LIMIT 1",
544 ),
545 (id,),
546 |row| {
547 let state: MessageState = row.get("state")?;
548 let mdn_msg_id: Option<MsgId> = row.get("mdn_msg_id")?;
549 let text = match row.get_ref("txt")? {
550 rusqlite::types::ValueRef::Text(buf) => {
551 match String::from_utf8(buf.to_vec()) {
552 Ok(t) => t,
553 Err(_) => {
554 warn!(
555 context,
556 concat!(
557 "dc_msg_load_from_db: could not get ",
558 "text column as non-lossy utf8 id {}"
559 ),
560 id
561 );
562 String::from_utf8_lossy(buf).into_owned()
563 }
564 }
565 }
566 _ => String::new(),
567 };
568 let msg = Message {
569 id: row.get("id")?,
570 rfc724_mid: row.get::<_, String>("rfc724mid")?,
571 in_reply_to: row
572 .get::<_, Option<String>>("mime_in_reply_to")?
573 .and_then(|in_reply_to| parse_message_id(&in_reply_to).ok()),
574 chat_id: row.get("chat_id")?,
575 from_id: row.get("from_id")?,
576 to_id: row.get("to_id")?,
577 timestamp_sort: row.get("timestamp")?,
578 timestamp_sent: row.get("timestamp_sent")?,
579 timestamp_rcvd: row.get("timestamp_rcvd")?,
580 ephemeral_timer: row.get("ephemeral_timer")?,
581 ephemeral_timestamp: row.get("ephemeral_timestamp")?,
582 viewtype: row.get("type")?,
583 state: state.with_mdns(mdn_msg_id.is_some()),
584 download_state: row.get("download_state")?,
585 error: Some(row.get::<_, String>("error")?)
586 .filter(|error| !error.is_empty()),
587 is_dc_message: row.get("msgrmsg")?,
588 original_msg_id: row.get("original_msg_id")?,
589 mime_modified: row.get("mime_modified")?,
590 text,
591 subject: row.get("subject")?,
592 param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
593 hidden: row.get("hidden")?,
594 location_id: row.get("location")?,
595 chat_blocked: row
596 .get::<_, Option<Blocked>>("blocked")?
597 .unwrap_or_default(),
598 };
599 Ok(msg)
600 },
601 )
602 .await
603 .with_context(|| format!("failed to load message {id} from the database"))?;
604
605 Ok(msg)
606 }
607
608 pub fn get_filemime(&self) -> Option<String> {
615 if let Some(m) = self.param.get(Param::MimeType) {
616 return Some(m.to_string());
617 } else if self.param.exists(Param::File) {
618 if let Some((_, mime)) = guess_msgtype_from_suffix(self) {
619 return Some(mime.to_string());
620 }
621 return Some("application/octet-stream".to_string());
623 }
624 None
626 }
627
628 pub fn get_file(&self, context: &Context) -> Option<PathBuf> {
630 self.param.get_file_path(context).unwrap_or(None)
631 }
632
633 pub async fn vcard_contacts(&self, context: &Context) -> Result<Vec<VcardContact>> {
635 if self.viewtype != Viewtype::Vcard {
636 return Ok(Vec::new());
637 }
638
639 let path = self
640 .get_file(context)
641 .context("vCard message does not have an attachment")?;
642 let bytes = tokio::fs::read(path).await?;
643 let vcard_contents = std::str::from_utf8(&bytes).context("vCard is not a valid UTF-8")?;
644 Ok(parse_vcard(vcard_contents))
645 }
646
647 pub async fn save_file(&self, context: &Context, path: &Path) -> Result<()> {
649 let path_src = self.get_file(context).context("No file")?;
650 let mut src = fs::OpenOptions::new().read(true).open(path_src).await?;
651 let mut dst = fs::OpenOptions::new()
652 .write(true)
653 .create_new(true)
654 .open(path)
655 .await?;
656 io::copy(&mut src, &mut dst).await?;
657 Ok(())
658 }
659
660 pub(crate) async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<()> {
662 if self.viewtype.has_file() {
663 let file_param = self.param.get_file_path(context)?;
664 if let Some(path_and_filename) = file_param {
665 if (self.viewtype == Viewtype::Image || self.viewtype == Viewtype::Gif)
666 && !self.param.exists(Param::Width)
667 {
668 let buf = read_file(context, &path_and_filename).await?;
669
670 match get_filemeta(&buf) {
671 Ok((width, height)) => {
672 self.param.set_int(Param::Width, width as i32);
673 self.param.set_int(Param::Height, height as i32);
674 }
675 Err(err) => {
676 self.param.set_int(Param::Width, 0);
677 self.param.set_int(Param::Height, 0);
678 warn!(
679 context,
680 "Failed to get width and height for {}: {err:#}.",
681 path_and_filename.display()
682 );
683 }
684 }
685
686 if !self.id.is_unset() {
687 self.update_param(context).await?;
688 }
689 }
690 }
691 }
692 Ok(())
693 }
694
695 pub fn has_location(&self) -> bool {
701 self.location_id != 0
702 }
703
704 pub fn set_location(&mut self, latitude: f64, longitude: f64) {
721 if latitude == 0.0 && longitude == 0.0 {
722 return;
723 }
724
725 self.param.set_float(Param::SetLatitude, latitude);
726 self.param.set_float(Param::SetLongitude, longitude);
727 }
728
729 pub fn get_timestamp(&self) -> i64 {
732 if 0 != self.timestamp_sent {
733 self.timestamp_sent
734 } else {
735 self.timestamp_sort
736 }
737 }
738
739 pub fn get_id(&self) -> MsgId {
741 self.id
742 }
743
744 pub fn rfc724_mid(&self) -> &str {
747 &self.rfc724_mid
748 }
749
750 pub fn get_from_id(&self) -> ContactId {
752 self.from_id
753 }
754
755 pub fn get_chat_id(&self) -> ChatId {
757 self.chat_id
758 }
759
760 pub fn get_viewtype(&self) -> Viewtype {
762 self.viewtype
763 }
764
765 pub fn force_sticker(&mut self) {
768 self.param.set_int(Param::ForceSticker, 1);
769 }
770
771 pub fn get_state(&self) -> MessageState {
773 self.state
774 }
775
776 pub fn get_received_timestamp(&self) -> i64 {
778 self.timestamp_rcvd
779 }
780
781 pub fn get_sort_timestamp(&self) -> i64 {
783 self.timestamp_sort
784 }
785
786 pub fn get_text(&self) -> String {
788 self.text.clone()
789 }
790
791 pub fn get_subject(&self) -> &str {
793 &self.subject
794 }
795
796 pub fn get_filename(&self) -> Option<String> {
800 if let Some(name) = self.param.get(Param::Filename) {
801 return Some(sanitize_filename(name));
802 }
803 self.param
804 .get(Param::File)
805 .and_then(|file| Path::new(file).file_name())
806 .map(|name| sanitize_filename(&name.to_string_lossy()))
807 }
808
809 pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
811 if let Some(path) = self.param.get_file_path(context)? {
812 Ok(Some(get_filebytes(context, &path).await.with_context(
813 || format!("failed to get {} size in bytes", path.display()),
814 )?))
815 } else {
816 Ok(None)
817 }
818 }
819
820 pub fn get_width(&self) -> i32 {
822 self.param.get_int(Param::Width).unwrap_or_default()
823 }
824
825 pub fn get_height(&self) -> i32 {
827 self.param.get_int(Param::Height).unwrap_or_default()
828 }
829
830 pub fn get_duration(&self) -> i32 {
832 self.param.get_int(Param::Duration).unwrap_or_default()
833 }
834
835 pub fn get_showpadlock(&self) -> bool {
837 self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0
838 || self.from_id == ContactId::DEVICE
839 }
840
841 pub fn is_bot(&self) -> bool {
843 self.param.get_bool(Param::Bot).unwrap_or_default()
844 }
845
846 pub fn get_ephemeral_timer(&self) -> EphemeralTimer {
848 self.ephemeral_timer
849 }
850
851 pub fn get_ephemeral_timestamp(&self) -> i64 {
853 self.ephemeral_timestamp
854 }
855
856 pub async fn get_summary(&self, context: &Context, chat: Option<&Chat>) -> Result<Summary> {
858 let chat_loaded: Chat;
859 let chat = if let Some(chat) = chat {
860 chat
861 } else {
862 let chat = Chat::load_from_db(context, self.chat_id).await?;
863 chat_loaded = chat;
864 &chat_loaded
865 };
866
867 let contact = if self.from_id != ContactId::SELF {
868 match chat.typ {
869 Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
870 Some(Contact::get_by_id(context, self.from_id).await?)
871 }
872 Chattype::Single => None,
873 }
874 } else {
875 None
876 };
877
878 Summary::new(context, self, chat, contact.as_ref()).await
879 }
880
881 pub fn get_override_sender_name(&self) -> Option<String> {
891 self.param
892 .get(Param::OverrideSenderDisplayname)
893 .map(|name| name.to_string())
894 }
895
896 pub(crate) fn get_sender_name(&self, contact: &Contact) -> String {
899 self.get_override_sender_name()
900 .unwrap_or_else(|| contact.get_display_name().to_string())
901 }
902
903 pub fn has_deviating_timestamp(&self) -> bool {
908 let cnv_to_local = gm2local_offset();
909 let sort_timestamp = self.get_sort_timestamp() + cnv_to_local;
910 let send_timestamp = self.get_timestamp() + cnv_to_local;
911
912 sort_timestamp / 86400 != send_timestamp / 86400
913 }
914
915 pub fn is_sent(&self) -> bool {
918 self.state >= MessageState::OutDelivered
919 }
920
921 pub fn is_forwarded(&self) -> bool {
923 0 != self.param.get_int(Param::Forwarded).unwrap_or_default()
924 }
925
926 pub fn is_edited(&self) -> bool {
928 self.param.get_bool(Param::IsEdited).unwrap_or_default()
929 }
930
931 pub fn is_info(&self) -> bool {
933 let cmd = self.param.get_cmd();
934 self.from_id == ContactId::INFO
935 || self.to_id == ContactId::INFO
936 || cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
937 }
938
939 pub fn get_info_type(&self) -> SystemMessage {
941 self.param.get_cmd()
942 }
943
944 pub async fn get_info_contact_id(&self, context: &Context) -> Result<Option<ContactId>> {
946 match self.param.get_cmd() {
947 SystemMessage::GroupNameChanged
948 | SystemMessage::GroupImageChanged
949 | SystemMessage::EphemeralTimerChanged => {
950 if self.from_id != ContactId::INFO {
951 Ok(Some(self.from_id))
952 } else {
953 Ok(None)
954 }
955 }
956
957 SystemMessage::MemberAddedToGroup | SystemMessage::MemberRemovedFromGroup => {
958 if let Some(contact_i32) = self.param.get_int(Param::ContactAddedRemoved) {
959 let contact_id = ContactId::new(contact_i32.try_into()?);
960 if contact_id == ContactId::SELF
961 || Contact::real_exists_by_id(context, contact_id).await?
962 {
963 Ok(Some(contact_id))
964 } else {
965 Ok(None)
966 }
967 } else {
968 Ok(None)
969 }
970 }
971
972 SystemMessage::AutocryptSetupMessage
973 | SystemMessage::SecurejoinMessage
974 | SystemMessage::LocationStreamingEnabled
975 | SystemMessage::LocationOnly
976 | SystemMessage::ChatProtectionEnabled
977 | SystemMessage::ChatProtectionDisabled
978 | SystemMessage::InvalidUnencryptedMail
979 | SystemMessage::SecurejoinWait
980 | SystemMessage::SecurejoinWaitTimeout
981 | SystemMessage::MultiDeviceSync
982 | SystemMessage::WebxdcStatusUpdate
983 | SystemMessage::WebxdcInfoMessage
984 | SystemMessage::IrohNodeAddr
985 | SystemMessage::Unknown => Ok(None),
986 }
987 }
988
989 pub fn is_system_message(&self) -> bool {
991 let cmd = self.param.get_cmd();
992 cmd != SystemMessage::Unknown
993 }
994
995 pub fn is_setupmessage(&self) -> bool {
997 if self.viewtype != Viewtype::File {
998 return false;
999 }
1000
1001 self.param.get_cmd() == SystemMessage::AutocryptSetupMessage
1002 }
1003
1004 pub async fn get_setupcodebegin(&self, context: &Context) -> Option<String> {
1008 if !self.is_setupmessage() {
1009 return None;
1010 }
1011
1012 if let Some(filename) = self.get_file(context) {
1013 if let Ok(ref buf) = read_file(context, &filename).await {
1014 if let Ok((typ, headers, _)) = split_armored_data(buf) {
1015 if typ == pgp::armor::BlockType::Message {
1016 return headers.get(crate::pgp::HEADER_SETUPCODE).cloned();
1017 }
1018 }
1019 }
1020 }
1021
1022 None
1023 }
1024
1025 pub(crate) fn create_webrtc_instance(instance: &str, room: &str) -> String {
1028 let (videochat_type, mut url) = Message::parse_webrtc_instance(instance);
1029
1030 if !url.contains(':') {
1032 url = format!("https://{url}");
1033 }
1034
1035 let url = if url.contains("$ROOM") {
1037 url.replace("$ROOM", room)
1038 } else if url.contains("$NOROOM") {
1039 url.replace("$NOROOM", "")
1045 } else {
1046 let maybe_slash = if url.ends_with('/')
1049 || url.ends_with('?')
1050 || url.ends_with('#')
1051 || url.ends_with('=')
1052 {
1053 ""
1054 } else {
1055 "/"
1056 };
1057 format!("{url}{maybe_slash}{room}")
1058 };
1059
1060 match videochat_type {
1062 VideochatType::BasicWebrtc => format!("basicwebrtc:{url}"),
1063 VideochatType::Jitsi => format!("jitsi:{url}"),
1064 VideochatType::Unknown => url,
1065 }
1066 }
1067
1068 pub fn parse_webrtc_instance(instance: &str) -> (VideochatType, String) {
1070 let instance: String = instance.split_whitespace().collect();
1071 let mut split = instance.splitn(2, ':');
1072 let type_str = split.next().unwrap_or_default().to_lowercase();
1073 let url = split.next();
1074 match type_str.as_str() {
1075 "basicwebrtc" => (
1076 VideochatType::BasicWebrtc,
1077 url.unwrap_or_default().to_string(),
1078 ),
1079 "jitsi" => (VideochatType::Jitsi, url.unwrap_or_default().to_string()),
1080 _ => (VideochatType::Unknown, instance.to_string()),
1081 }
1082 }
1083
1084 pub fn get_videochat_url(&self) -> Option<String> {
1086 if self.viewtype == Viewtype::VideochatInvitation {
1087 if let Some(instance) = self.param.get(Param::WebrtcRoom) {
1088 return Some(Message::parse_webrtc_instance(instance).1);
1089 }
1090 }
1091 None
1092 }
1093
1094 pub fn get_videochat_type(&self) -> Option<VideochatType> {
1096 if self.viewtype == Viewtype::VideochatInvitation {
1097 if let Some(instance) = self.param.get(Param::WebrtcRoom) {
1098 return Some(Message::parse_webrtc_instance(instance).0);
1099 }
1100 }
1101 None
1102 }
1103
1104 pub fn set_text(&mut self, text: String) {
1106 self.text = text;
1107 }
1108
1109 pub fn set_subject(&mut self, subject: String) {
1112 self.subject = subject;
1113 }
1114
1115 pub fn set_file_and_deduplicate(
1130 &mut self,
1131 context: &Context,
1132 file: &Path,
1133 name: Option<&str>,
1134 filemime: Option<&str>,
1135 ) -> Result<()> {
1136 let name = if let Some(name) = name {
1137 name.to_string()
1138 } else {
1139 file.file_name()
1140 .map(|s| s.to_string_lossy().to_string())
1141 .unwrap_or_else(|| "unknown_file".to_string())
1142 };
1143
1144 let blob = BlobObject::create_and_deduplicate(context, file, Path::new(&name))?;
1145 self.param.set(Param::File, blob.as_name());
1146
1147 self.param.set(Param::Filename, name);
1148 self.param.set_optional(Param::MimeType, filemime);
1149
1150 Ok(())
1151 }
1152
1153 pub fn set_file_from_bytes(
1160 &mut self,
1161 context: &Context,
1162 name: &str,
1163 data: &[u8],
1164 filemime: Option<&str>,
1165 ) -> Result<()> {
1166 let blob = BlobObject::create_and_deduplicate_from_bytes(context, data, name)?;
1167 self.param.set(Param::Filename, name);
1168 self.param.set(Param::File, blob.as_name());
1169 self.param.set_optional(Param::MimeType, filemime);
1170
1171 Ok(())
1172 }
1173
1174 pub async fn make_vcard(&mut self, context: &Context, contacts: &[ContactId]) -> Result<()> {
1176 ensure!(
1177 matches!(self.viewtype, Viewtype::File | Viewtype::Vcard),
1178 "Wrong viewtype for vCard: {}",
1179 self.viewtype,
1180 );
1181 let vcard = contact::make_vcard(context, contacts).await?;
1182 self.set_file_from_bytes(context, "vcard.vcf", vcard.as_bytes(), None)
1183 }
1184
1185 pub(crate) async fn try_set_vcard(&mut self, context: &Context, path: &Path) -> Result<()> {
1187 let vcard = fs::read(path)
1188 .await
1189 .with_context(|| format!("Could not read {path:?}"))?;
1190 if let Some(summary) = get_vcard_summary(&vcard) {
1191 self.param.set(Param::Summary1, summary);
1192 } else {
1193 warn!(context, "try_set_vcard: Not a valid DeltaChat vCard.");
1194 self.viewtype = Viewtype::File;
1195 }
1196 Ok(())
1197 }
1198
1199 pub fn set_override_sender_name(&mut self, name: Option<String>) {
1202 self.param
1203 .set_optional(Param::OverrideSenderDisplayname, name);
1204 }
1205
1206 pub fn set_dimension(&mut self, width: i32, height: i32) {
1208 self.param.set_int(Param::Width, width);
1209 self.param.set_int(Param::Height, height);
1210 }
1211
1212 pub fn set_duration(&mut self, duration: i32) {
1214 self.param.set_int(Param::Duration, duration);
1215 }
1216
1217 pub(crate) fn set_reaction(&mut self) {
1219 self.param.set_int(Param::Reaction, 1);
1220 }
1221
1222 pub async fn latefiling_mediasize(
1225 &mut self,
1226 context: &Context,
1227 width: i32,
1228 height: i32,
1229 duration: i32,
1230 ) -> Result<()> {
1231 if width > 0 && height > 0 {
1232 self.param.set_int(Param::Width, width);
1233 self.param.set_int(Param::Height, height);
1234 }
1235 if duration > 0 {
1236 self.param.set_int(Param::Duration, duration);
1237 }
1238 self.update_param(context).await?;
1239 Ok(())
1240 }
1241
1242 pub fn set_quote_text(&mut self, text: Option<(String, bool)>) {
1248 let Some((text, protect)) = text else {
1249 self.param.remove(Param::Quote);
1250 self.param.remove(Param::ProtectQuote);
1251 return;
1252 };
1253 self.param.set(Param::Quote, text);
1254 self.param.set_optional(
1255 Param::ProtectQuote,
1256 match protect {
1257 true => Some("1"),
1258 false => None,
1259 },
1260 );
1261 }
1262
1263 pub async fn set_quote(&mut self, context: &Context, quote: Option<&Message>) -> Result<()> {
1272 if let Some(quote) = quote {
1273 ensure!(
1274 !quote.rfc724_mid.is_empty(),
1275 "Message without Message-Id cannot be quoted"
1276 );
1277 self.in_reply_to = Some(quote.rfc724_mid.clone());
1278
1279 let text = quote.get_text();
1280 let text = if text.is_empty() {
1281 quote
1283 .get_summary(context, None)
1284 .await?
1285 .truncated_text(500)
1286 .to_string()
1287 } else {
1288 text
1289 };
1290 self.set_quote_text(Some((
1291 text,
1292 quote
1293 .param
1294 .get_bool(Param::GuaranteeE2ee)
1295 .unwrap_or_default(),
1296 )));
1297 } else {
1298 self.in_reply_to = None;
1299 self.set_quote_text(None);
1300 }
1301
1302 Ok(())
1303 }
1304
1305 pub fn quoted_text(&self) -> Option<String> {
1307 self.param.get(Param::Quote).map(|s| s.to_string())
1308 }
1309
1310 pub async fn quoted_message(&self, context: &Context) -> Result<Option<Message>> {
1312 if self.param.get(Param::Quote).is_some() && !self.is_forwarded() {
1313 return self.parent(context).await;
1314 }
1315 Ok(None)
1316 }
1317
1318 pub async fn parent(&self, context: &Context) -> Result<Option<Message>> {
1323 if let Some(in_reply_to) = &self.in_reply_to {
1324 if let Some((msg_id, _ts_sent)) = rfc724_mid_exists(context, in_reply_to).await? {
1325 let msg = Message::load_from_db_optional(context, msg_id).await?;
1326 return Ok(msg);
1327 }
1328 }
1329 Ok(None)
1330 }
1331
1332 pub async fn get_original_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1334 if !self.original_msg_id.is_special() {
1335 if let Some(msg) = Message::load_from_db_optional(context, self.original_msg_id).await?
1336 {
1337 return if msg.chat_id.is_trash() {
1338 Ok(None)
1339 } else {
1340 Ok(Some(msg.id))
1341 };
1342 }
1343 }
1344 Ok(None)
1345 }
1346
1347 pub async fn get_saved_msg_id(&self, context: &Context) -> Result<Option<MsgId>> {
1351 let res: Option<MsgId> = context
1352 .sql
1353 .query_get_value(
1354 "SELECT id FROM msgs WHERE starred=? AND chat_id!=?",
1355 (self.id, DC_CHAT_ID_TRASH),
1356 )
1357 .await?;
1358 Ok(res)
1359 }
1360
1361 pub fn force_plaintext(&mut self) {
1363 self.param.set_int(Param::ForcePlaintext, 1);
1364 }
1365
1366 pub async fn update_param(&self, context: &Context) -> Result<()> {
1368 context
1369 .sql
1370 .execute(
1371 "UPDATE msgs SET param=? WHERE id=?;",
1372 (self.param.to_string(), self.id),
1373 )
1374 .await?;
1375 Ok(())
1376 }
1377
1378 pub(crate) async fn update_subject(&self, context: &Context) -> Result<()> {
1379 context
1380 .sql
1381 .execute(
1382 "UPDATE msgs SET subject=? WHERE id=?;",
1383 (&self.subject, self.id),
1384 )
1385 .await?;
1386 Ok(())
1387 }
1388
1389 pub fn error(&self) -> Option<String> {
1402 self.error.clone()
1403 }
1404}
1405
1406#[derive(
1410 Debug,
1411 Default,
1412 Clone,
1413 Copy,
1414 PartialEq,
1415 Eq,
1416 PartialOrd,
1417 Ord,
1418 FromPrimitive,
1419 ToPrimitive,
1420 ToSql,
1421 FromSql,
1422 Serialize,
1423 Deserialize,
1424)]
1425#[repr(u32)]
1426pub enum MessageState {
1427 #[default]
1429 Undefined = 0,
1430
1431 InFresh = 10,
1434
1435 InNoticed = 13,
1439
1440 InSeen = 16,
1443
1444 OutPreparing = 18,
1448
1449 OutDraft = 19,
1451
1452 OutPending = 20,
1456
1457 OutFailed = 24,
1460
1461 OutDelivered = 26,
1465
1466 OutMdnRcvd = 28,
1469}
1470
1471impl std::fmt::Display for MessageState {
1472 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1473 write!(
1474 f,
1475 "{}",
1476 match self {
1477 Self::Undefined => "Undefined",
1478 Self::InFresh => "Fresh",
1479 Self::InNoticed => "Noticed",
1480 Self::InSeen => "Seen",
1481 Self::OutPreparing => "Preparing",
1482 Self::OutDraft => "Draft",
1483 Self::OutPending => "Pending",
1484 Self::OutFailed => "Failed",
1485 Self::OutDelivered => "Delivered",
1486 Self::OutMdnRcvd => "Read",
1487 }
1488 )
1489 }
1490}
1491
1492impl MessageState {
1493 pub fn can_fail(self) -> bool {
1495 use MessageState::*;
1496 matches!(
1497 self,
1498 OutPreparing | OutPending | OutDelivered | OutMdnRcvd )
1500 }
1501
1502 pub fn is_outgoing(self) -> bool {
1504 use MessageState::*;
1505 matches!(
1506 self,
1507 OutPreparing | OutDraft | OutPending | OutFailed | OutDelivered | OutMdnRcvd
1508 )
1509 }
1510
1511 pub(crate) fn with_mdns(self, has_mdns: bool) -> Self {
1513 if self == MessageState::OutDelivered && has_mdns {
1514 return MessageState::OutMdnRcvd;
1515 }
1516 self
1517 }
1518}
1519
1520pub async fn get_msg_read_receipts(
1522 context: &Context,
1523 msg_id: MsgId,
1524) -> Result<Vec<(ContactId, i64)>> {
1525 context
1526 .sql
1527 .query_map(
1528 "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
1529 (msg_id,),
1530 |row| {
1531 let contact_id: ContactId = row.get(0)?;
1532 let ts: i64 = row.get(1)?;
1533 Ok((contact_id, ts))
1534 },
1535 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
1536 )
1537 .await
1538}
1539
1540pub(crate) fn guess_msgtype_from_suffix(msg: &Message) -> Option<(Viewtype, &'static str)> {
1541 msg.param
1542 .get(Param::Filename)
1543 .or_else(|| msg.param.get(Param::File))
1544 .and_then(|file| guess_msgtype_from_path_suffix(Path::new(file)))
1545}
1546
1547pub(crate) fn guess_msgtype_from_path_suffix(path: &Path) -> Option<(Viewtype, &'static str)> {
1548 let extension: &str = &path.extension()?.to_str()?.to_lowercase();
1549 let info = match extension {
1550 "3gp" => (Viewtype::Video, "video/3gpp"),
1555 "aac" => (Viewtype::Audio, "audio/aac"),
1556 "avi" => (Viewtype::Video, "video/x-msvideo"),
1557 "avif" => (Viewtype::File, "image/avif"), "doc" => (Viewtype::File, "application/msword"),
1559 "docx" => (
1560 Viewtype::File,
1561 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1562 ),
1563 "epub" => (Viewtype::File, "application/epub+zip"),
1564 "flac" => (Viewtype::Audio, "audio/flac"),
1565 "gif" => (Viewtype::Gif, "image/gif"),
1566 "heic" => (Viewtype::File, "image/heic"), "heif" => (Viewtype::File, "image/heif"), "html" => (Viewtype::File, "text/html"),
1569 "htm" => (Viewtype::File, "text/html"),
1570 "ico" => (Viewtype::File, "image/vnd.microsoft.icon"),
1571 "jar" => (Viewtype::File, "application/java-archive"),
1572 "jpeg" => (Viewtype::Image, "image/jpeg"),
1573 "jpe" => (Viewtype::Image, "image/jpeg"),
1574 "jpg" => (Viewtype::Image, "image/jpeg"),
1575 "json" => (Viewtype::File, "application/json"),
1576 "mov" => (Viewtype::Video, "video/quicktime"),
1577 "m4a" => (Viewtype::Audio, "audio/m4a"),
1578 "mp3" => (Viewtype::Audio, "audio/mpeg"),
1579 "mp4" => (Viewtype::Video, "video/mp4"),
1580 "odp" => (
1581 Viewtype::File,
1582 "application/vnd.oasis.opendocument.presentation",
1583 ),
1584 "ods" => (
1585 Viewtype::File,
1586 "application/vnd.oasis.opendocument.spreadsheet",
1587 ),
1588 "odt" => (Viewtype::File, "application/vnd.oasis.opendocument.text"),
1589 "oga" => (Viewtype::Audio, "audio/ogg"),
1590 "ogg" => (Viewtype::Audio, "audio/ogg"),
1591 "ogv" => (Viewtype::File, "video/ogg"),
1592 "opus" => (Viewtype::File, "audio/ogg"), "otf" => (Viewtype::File, "font/otf"),
1594 "pdf" => (Viewtype::File, "application/pdf"),
1595 "png" => (Viewtype::Image, "image/png"),
1596 "ppt" => (Viewtype::File, "application/vnd.ms-powerpoint"),
1597 "pptx" => (
1598 Viewtype::File,
1599 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1600 ),
1601 "rar" => (Viewtype::File, "application/vnd.rar"),
1602 "rtf" => (Viewtype::File, "application/rtf"),
1603 "spx" => (Viewtype::File, "audio/ogg"), "svg" => (Viewtype::File, "image/svg+xml"),
1605 "tgs" => (Viewtype::Sticker, "application/x-tgsticker"),
1606 "tiff" => (Viewtype::File, "image/tiff"),
1607 "tif" => (Viewtype::File, "image/tiff"),
1608 "ttf" => (Viewtype::File, "font/ttf"),
1609 "txt" => (Viewtype::File, "text/plain"),
1610 "vcard" => (Viewtype::Vcard, "text/vcard"),
1611 "vcf" => (Viewtype::Vcard, "text/vcard"),
1612 "wav" => (Viewtype::Audio, "audio/wav"),
1613 "weba" => (Viewtype::File, "audio/webm"),
1614 "webm" => (Viewtype::Video, "video/webm"),
1615 "webp" => (Viewtype::Image, "image/webp"), "wmv" => (Viewtype::Video, "video/x-ms-wmv"),
1617 "xdc" => (Viewtype::Webxdc, "application/webxdc+zip"),
1618 "xhtml" => (Viewtype::File, "application/xhtml+xml"),
1619 "xls" => (Viewtype::File, "application/vnd.ms-excel"),
1620 "xlsx" => (
1621 Viewtype::File,
1622 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1623 ),
1624 "xml" => (Viewtype::File, "application/xml"),
1625 "zip" => (Viewtype::File, "application/zip"),
1626 _ => {
1627 return None;
1628 }
1629 };
1630 Some(info)
1631}
1632
1633pub(crate) async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8>> {
1640 let (headers, compressed) = context
1641 .sql
1642 .query_row(
1643 "SELECT mime_headers, mime_compressed FROM msgs WHERE id=?",
1644 (msg_id,),
1645 |row| {
1646 let headers = sql::row_get_vec(row, 0)?;
1647 let compressed: bool = row.get(1)?;
1648 Ok((headers, compressed))
1649 },
1650 )
1651 .await?;
1652 if compressed {
1653 return buf_decompress(&headers);
1654 }
1655
1656 let headers2 = headers.clone();
1657 let compressed = match tokio::task::block_in_place(move || buf_compress(&headers2)) {
1658 Err(e) => {
1659 warn!(context, "get_mime_headers: buf_compress() failed: {}", e);
1660 return Ok(headers);
1661 }
1662 Ok(o) => o,
1663 };
1664 let update = |conn: &mut rusqlite::Connection| {
1665 match conn.execute(
1666 "\
1667 UPDATE msgs SET mime_headers=?, mime_compressed=1 \
1668 WHERE id=? AND mime_headers!='' AND mime_compressed=0",
1669 (compressed, msg_id),
1670 ) {
1671 Ok(rows_updated) => ensure!(rows_updated <= 1),
1672 Err(e) => {
1673 warn!(context, "get_mime_headers: UPDATE failed: {}", e);
1674 return Err(e.into());
1675 }
1676 }
1677 Ok(())
1678 };
1679 if let Err(e) = context.sql.call_write(update).await {
1680 warn!(
1681 context,
1682 "get_mime_headers: failed to update mime_headers: {}", e
1683 );
1684 }
1685
1686 Ok(headers)
1687}
1688
1689pub(crate) async fn delete_msg_locally(context: &Context, msg: &Message) -> Result<()> {
1692 if msg.location_id > 0 {
1693 delete_poi_location(context, msg.location_id).await?;
1694 }
1695 let on_server = true;
1696 msg.id
1697 .trash(context, on_server)
1698 .await
1699 .with_context(|| format!("Unable to trash message {}", msg.id))?;
1700
1701 context.emit_event(EventType::MsgDeleted {
1702 chat_id: msg.chat_id,
1703 msg_id: msg.id,
1704 });
1705
1706 if msg.viewtype == Viewtype::Webxdc {
1707 context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: msg.id });
1708 }
1709
1710 let logging_xdc_id = context
1711 .debug_logging
1712 .read()
1713 .expect("RwLock is poisoned")
1714 .as_ref()
1715 .map(|dl| dl.msg_id);
1716 if let Some(id) = logging_xdc_id {
1717 if id == msg.id {
1718 set_debug_logging_xdc(context, None).await?;
1719 }
1720 }
1721
1722 Ok(())
1723}
1724
1725pub(crate) async fn delete_msgs_locally_done(
1728 context: &Context,
1729 msg_ids: &[MsgId],
1730 modified_chat_ids: HashSet<ChatId>,
1731) -> Result<()> {
1732 for modified_chat_id in modified_chat_ids {
1733 context.emit_msgs_changed_without_msg_id(modified_chat_id);
1734 chatlist_events::emit_chatlist_item_changed(context, modified_chat_id);
1735 }
1736 if !msg_ids.is_empty() {
1737 context.emit_msgs_changed_without_ids();
1738 chatlist_events::emit_chatlist_changed(context);
1739 context
1741 .set_config_internal(Config::LastHousekeeping, None)
1742 .await?;
1743 }
1744 Ok(())
1745}
1746
1747pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
1749 delete_msgs_ex(context, msg_ids, false).await
1750}
1751
1752pub async fn delete_msgs_ex(
1756 context: &Context,
1757 msg_ids: &[MsgId],
1758 delete_for_all: bool,
1759) -> Result<()> {
1760 let mut modified_chat_ids = HashSet::new();
1761 let mut deleted_rfc724_mid = Vec::new();
1762 let mut res = Ok(());
1763
1764 for &msg_id in msg_ids {
1765 let msg = Message::load_from_db(context, msg_id).await?;
1766 ensure!(
1767 !delete_for_all || msg.from_id == ContactId::SELF,
1768 "Can delete only own messages for others"
1769 );
1770 ensure!(
1771 !delete_for_all || msg.get_showpadlock(),
1772 "Cannot request deletion of unencrypted message for others"
1773 );
1774
1775 modified_chat_ids.insert(msg.chat_id);
1776 deleted_rfc724_mid.push(msg.rfc724_mid.clone());
1777
1778 let target = context.get_delete_msgs_target().await?;
1779 let update_db = |trans: &mut rusqlite::Transaction| {
1780 trans.execute(
1781 "UPDATE imap SET target=? WHERE rfc724_mid=?",
1782 (target, msg.rfc724_mid),
1783 )?;
1784 trans.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))?;
1785 Ok(())
1786 };
1787 if let Err(e) = context.sql.transaction(update_db).await {
1788 error!(context, "delete_msgs: failed to update db: {e:#}.");
1789 res = Err(e);
1790 continue;
1791 }
1792 }
1793 res?;
1794
1795 if delete_for_all {
1796 ensure!(
1797 modified_chat_ids.len() == 1,
1798 "Can delete only from same chat."
1799 );
1800 if let Some(chat_id) = modified_chat_ids.iter().next() {
1801 let mut msg = Message::new_text("🚮".to_owned());
1802 msg.param.set_int(Param::GuaranteeE2ee, 1);
1807 msg.param
1808 .set(Param::DeleteRequestFor, deleted_rfc724_mid.join(" "));
1809 msg.hidden = true;
1810 send_msg(context, *chat_id, &mut msg).await?;
1811 }
1812 } else {
1813 context
1814 .add_sync_item(SyncData::DeleteMessages {
1815 msgs: deleted_rfc724_mid,
1816 })
1817 .await?;
1818 }
1819
1820 for &msg_id in msg_ids {
1821 let msg = Message::load_from_db(context, msg_id).await?;
1822 delete_msg_locally(context, &msg).await?;
1823 }
1824 delete_msgs_locally_done(context, msg_ids, modified_chat_ids).await?;
1825
1826 context.scheduler.interrupt_inbox().await;
1828
1829 Ok(())
1830}
1831
1832pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()> {
1834 if msg_ids.is_empty() {
1835 return Ok(());
1836 }
1837
1838 let old_last_msg_id = MsgId::new(context.get_config_u32(Config::LastMsgId).await?);
1839 let last_msg_id = msg_ids.iter().fold(&old_last_msg_id, std::cmp::max);
1840 context
1841 .set_config_internal(Config::LastMsgId, Some(&last_msg_id.to_u32().to_string()))
1842 .await?;
1843
1844 let mut msgs = Vec::with_capacity(msg_ids.len());
1845 for &id in &msg_ids {
1846 if let Some(msg) = context
1847 .sql
1848 .query_row_optional(
1849 "SELECT
1850 m.chat_id AS chat_id,
1851 m.state AS state,
1852 m.download_state as download_state,
1853 m.ephemeral_timer AS ephemeral_timer,
1854 m.param AS param,
1855 m.from_id AS from_id,
1856 m.rfc724_mid AS rfc724_mid,
1857 c.archived AS archived,
1858 c.blocked AS blocked
1859 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
1860 WHERE m.id=? AND m.chat_id>9",
1861 (id,),
1862 |row| {
1863 let chat_id: ChatId = row.get("chat_id")?;
1864 let state: MessageState = row.get("state")?;
1865 let download_state: DownloadState = row.get("download_state")?;
1866 let param: Params = row.get::<_, String>("param")?.parse().unwrap_or_default();
1867 let from_id: ContactId = row.get("from_id")?;
1868 let rfc724_mid: String = row.get("rfc724_mid")?;
1869 let visibility: ChatVisibility = row.get("archived")?;
1870 let blocked: Option<Blocked> = row.get("blocked")?;
1871 let ephemeral_timer: EphemeralTimer = row.get("ephemeral_timer")?;
1872 Ok((
1873 (
1874 id,
1875 chat_id,
1876 state,
1877 download_state,
1878 param,
1879 from_id,
1880 rfc724_mid,
1881 visibility,
1882 blocked.unwrap_or_default(),
1883 ),
1884 ephemeral_timer,
1885 ))
1886 },
1887 )
1888 .await?
1889 {
1890 msgs.push(msg);
1891 }
1892 }
1893
1894 if msgs
1895 .iter()
1896 .any(|(_, ephemeral_timer)| *ephemeral_timer != EphemeralTimer::Disabled)
1897 {
1898 start_ephemeral_timers_msgids(context, &msg_ids)
1899 .await
1900 .context("failed to start ephemeral timers")?;
1901 }
1902
1903 let mut updated_chat_ids = BTreeSet::new();
1904 let mut archived_chats_maybe_noticed = false;
1905 for (
1906 (
1907 id,
1908 curr_chat_id,
1909 curr_state,
1910 curr_download_state,
1911 curr_param,
1912 curr_from_id,
1913 curr_rfc724_mid,
1914 curr_visibility,
1915 curr_blocked,
1916 ),
1917 _curr_ephemeral_timer,
1918 ) in msgs
1919 {
1920 if curr_download_state != DownloadState::Done {
1921 if curr_state == MessageState::InFresh {
1922 update_msg_state(context, id, MessageState::InNoticed).await?;
1925 updated_chat_ids.insert(curr_chat_id);
1926 }
1927 } else if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
1928 update_msg_state(context, id, MessageState::InSeen).await?;
1929 info!(context, "Seen message {}.", id);
1930
1931 markseen_on_imap_table(context, &curr_rfc724_mid).await?;
1932
1933 if curr_blocked == Blocked::Not
1943 && curr_param.get_bool(Param::WantsMdn).unwrap_or_default()
1944 && curr_param.get_cmd() == SystemMessage::Unknown
1945 && context.should_send_mdns().await?
1946 {
1947 context
1948 .sql
1949 .execute(
1950 "INSERT INTO smtp_mdns (msg_id, from_id, rfc724_mid) VALUES(?, ?, ?)",
1951 (id, curr_from_id, curr_rfc724_mid),
1952 )
1953 .await
1954 .context("failed to insert into smtp_mdns")?;
1955 context.scheduler.interrupt_smtp().await;
1956 }
1957 updated_chat_ids.insert(curr_chat_id);
1958 }
1959 archived_chats_maybe_noticed |=
1960 curr_state == MessageState::InFresh && curr_visibility == ChatVisibility::Archived;
1961 }
1962
1963 for updated_chat_id in updated_chat_ids {
1964 context.emit_event(EventType::MsgsNoticed(updated_chat_id));
1965 chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
1966 }
1967 if archived_chats_maybe_noticed {
1968 context.on_archived_chats_maybe_noticed();
1969 }
1970
1971 Ok(())
1972}
1973
1974pub(crate) async fn update_msg_state(
1975 context: &Context,
1976 msg_id: MsgId,
1977 state: MessageState,
1978) -> Result<()> {
1979 ensure!(
1980 state != MessageState::OutMdnRcvd,
1981 "Update msgs_mdns table instead!"
1982 );
1983 ensure!(state != MessageState::OutFailed, "use set_msg_failed()!");
1984 let error_subst = match state >= MessageState::OutPending {
1985 true => ", error=''",
1986 false => "",
1987 };
1988 context
1989 .sql
1990 .execute(
1991 &format!("UPDATE msgs SET state=? {error_subst} WHERE id=?"),
1992 (state, msg_id),
1993 )
1994 .await?;
1995 Ok(())
1996}
1997
1998pub(crate) async fn set_msg_failed(
2006 context: &Context,
2007 msg: &mut Message,
2008 error: &str,
2009) -> Result<()> {
2010 if msg.state.can_fail() {
2011 msg.state = MessageState::OutFailed;
2012 warn!(context, "{} failed: {}", msg.id, error);
2013 } else {
2014 warn!(
2015 context,
2016 "{} seems to have failed ({}), but state is {}", msg.id, error, msg.state
2017 )
2018 }
2019 msg.error = Some(error.to_string());
2020
2021 let exists = context
2022 .sql
2023 .execute(
2024 "UPDATE msgs SET state=?, error=? WHERE id=?;",
2025 (msg.state, error, msg.id),
2026 )
2027 .await?
2028 > 0;
2029 context.emit_event(EventType::MsgFailed {
2030 chat_id: msg.chat_id,
2031 msg_id: msg.id,
2032 });
2033 if exists {
2034 chatlist_events::emit_chatlist_item_changed(context, msg.chat_id);
2035 }
2036 Ok(())
2037}
2038
2039pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
2041 match context
2042 .sql
2043 .count(
2044 "SELECT COUNT(*) \
2045 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2046 WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
2047 (),
2048 )
2049 .await
2050 {
2051 Ok(res) => res,
2052 Err(err) => {
2053 error!(context, "get_unblocked_msg_cnt() failed. {:#}", err);
2054 0
2055 }
2056 }
2057}
2058
2059pub async fn get_request_msg_cnt(context: &Context) -> usize {
2061 match context
2062 .sql
2063 .count(
2064 "SELECT COUNT(*) \
2065 FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
2066 WHERE c.blocked=2;",
2067 (),
2068 )
2069 .await
2070 {
2071 Ok(res) => res,
2072 Err(err) => {
2073 error!(context, "get_request_msg_cnt() failed. {:#}", err);
2074 0
2075 }
2076 }
2077}
2078
2079pub async fn estimate_deletion_cnt(
2095 context: &Context,
2096 from_server: bool,
2097 seconds: i64,
2098) -> Result<usize> {
2099 let self_chat_id = ChatIdBlocked::lookup_by_contact(context, ContactId::SELF)
2100 .await?
2101 .map(|c| c.id)
2102 .unwrap_or_default();
2103 let threshold_timestamp = time() - seconds;
2104
2105 let cnt = if from_server {
2106 context
2107 .sql
2108 .count(
2109 "SELECT COUNT(*)
2110 FROM msgs m
2111 WHERE m.id > ?
2112 AND timestamp < ?
2113 AND chat_id != ?
2114 AND EXISTS (SELECT * FROM imap WHERE rfc724_mid=m.rfc724_mid);",
2115 (DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id),
2116 )
2117 .await?
2118 } else {
2119 context
2120 .sql
2121 .count(
2122 "SELECT COUNT(*)
2123 FROM msgs m
2124 WHERE m.id > ?
2125 AND timestamp < ?
2126 AND chat_id != ?
2127 AND chat_id != ? AND hidden = 0;",
2128 (
2129 DC_MSG_ID_LAST_SPECIAL,
2130 threshold_timestamp,
2131 self_chat_id,
2132 DC_CHAT_ID_TRASH,
2133 ),
2134 )
2135 .await?
2136 };
2137 Ok(cnt)
2138}
2139
2140pub(crate) async fn rfc724_mid_exists(
2142 context: &Context,
2143 rfc724_mid: &str,
2144) -> Result<Option<(MsgId, i64)>> {
2145 Ok(rfc724_mid_exists_ex(context, rfc724_mid, "1")
2146 .await?
2147 .map(|(id, ts_sent, _)| (id, ts_sent)))
2148}
2149
2150pub(crate) async fn rfc724_mid_exists_ex(
2156 context: &Context,
2157 rfc724_mid: &str,
2158 expr: &str,
2159) -> Result<Option<(MsgId, i64, bool)>> {
2160 let rfc724_mid = rfc724_mid.trim_start_matches('<').trim_end_matches('>');
2161 if rfc724_mid.is_empty() {
2162 warn!(context, "Empty rfc724_mid passed to rfc724_mid_exists");
2163 return Ok(None);
2164 }
2165
2166 let res = context
2167 .sql
2168 .query_row_optional(
2169 &("SELECT id, timestamp_sent, MIN(".to_string()
2170 + expr
2171 + ") FROM msgs WHERE rfc724_mid=?
2172 HAVING COUNT(*) > 0 -- Prevent MIN(expr) from returning NULL when there are no rows.
2173 ORDER BY timestamp_sent DESC"),
2174 (rfc724_mid,),
2175 |row| {
2176 let msg_id: MsgId = row.get(0)?;
2177 let timestamp_sent: i64 = row.get(1)?;
2178 let expr_res: bool = row.get(2)?;
2179 Ok((msg_id, timestamp_sent, expr_res))
2180 },
2181 )
2182 .await?;
2183
2184 Ok(res)
2185}
2186
2187pub(crate) async fn get_by_rfc724_mids(
2194 context: &Context,
2195 mids: &[String],
2196) -> Result<Option<Message>> {
2197 let mut latest = None;
2198 for id in mids.iter().rev() {
2199 let Some((msg_id, _)) = rfc724_mid_exists(context, id).await? else {
2200 continue;
2201 };
2202 let Some(msg) = Message::load_from_db_optional(context, msg_id).await? else {
2203 continue;
2204 };
2205 if msg.download_state == DownloadState::Done {
2206 return Ok(Some(msg));
2207 }
2208 latest.get_or_insert(msg);
2209 }
2210 Ok(latest)
2211}
2212
2213pub(crate) fn get_vcard_summary(vcard: &[u8]) -> Option<String> {
2215 let vcard = str::from_utf8(vcard).ok()?;
2216 let contacts = deltachat_contact_tools::parse_vcard(vcard);
2217 let [c] = &contacts[..] else {
2218 return None;
2219 };
2220 if !deltachat_contact_tools::may_be_valid_addr(&c.addr) {
2221 return None;
2222 }
2223 Some(c.display_name().to_string())
2224}
2225
2226#[derive(
2228 Debug,
2229 Default,
2230 Display,
2231 Clone,
2232 Copy,
2233 PartialEq,
2234 Eq,
2235 FromPrimitive,
2236 ToPrimitive,
2237 FromSql,
2238 ToSql,
2239 Serialize,
2240 Deserialize,
2241)]
2242#[repr(u32)]
2243pub enum Viewtype {
2244 #[default]
2246 Unknown = 0,
2247
2248 Text = 10,
2251
2252 Image = 20,
2258
2259 Gif = 21,
2263
2264 Sticker = 23,
2271
2272 Audio = 40,
2276
2277 Voice = 41,
2282
2283 Video = 50,
2290
2291 File = 60,
2295
2296 VideochatInvitation = 70,
2298
2299 Webxdc = 80,
2301
2302 Vcard = 90,
2306}
2307
2308impl Viewtype {
2309 pub fn has_file(&self) -> bool {
2311 match self {
2312 Viewtype::Unknown => false,
2313 Viewtype::Text => false,
2314 Viewtype::Image => true,
2315 Viewtype::Gif => true,
2316 Viewtype::Sticker => true,
2317 Viewtype::Audio => true,
2318 Viewtype::Voice => true,
2319 Viewtype::Video => true,
2320 Viewtype::File => true,
2321 Viewtype::VideochatInvitation => false,
2322 Viewtype::Webxdc => true,
2323 Viewtype::Vcard => true,
2324 }
2325 }
2326}
2327
2328pub(crate) fn normalize_text(text: &str) -> Option<String> {
2331 if text.is_ascii() {
2332 return None;
2333 };
2334 Some(text.to_lowercase()).filter(|t| t != text)
2335}
2336
2337#[cfg(test)]
2338mod message_tests;