mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
2027 lines
72 KiB
Rust
2027 lines
72 KiB
Rust
use std::ffi::CString;
|
|
use std::ptr;
|
|
|
|
use itertools::join;
|
|
use libc::{free, strcmp, strlen};
|
|
use mmime::clist::*;
|
|
use mmime::mailimf::*;
|
|
use mmime::mailimf_types::*;
|
|
use mmime::mailmime::*;
|
|
use mmime::mailmime_content::*;
|
|
use mmime::mailmime_types::*;
|
|
use mmime::other::*;
|
|
use sha2::{Digest, Sha256};
|
|
|
|
use crate::chat::{self, Chat};
|
|
use crate::constants::*;
|
|
use crate::contact::*;
|
|
use crate::context::Context;
|
|
use crate::dc_mimeparser::*;
|
|
use crate::dc_strencode::*;
|
|
use crate::dc_tools::*;
|
|
use crate::error::Result;
|
|
use crate::events::Event;
|
|
use crate::job::*;
|
|
use crate::location;
|
|
use crate::message::{self, MessageState};
|
|
use crate::param::*;
|
|
use crate::peerstate::*;
|
|
use crate::securejoin::handle_securejoin_handshake;
|
|
use crate::sql;
|
|
use crate::stock::StockMessage;
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
enum CreateEvent {
|
|
MsgsChanged,
|
|
IncomingMsg,
|
|
}
|
|
|
|
/// Receive a message and add it to the database.
|
|
pub unsafe fn dc_receive_imf(
|
|
context: &Context,
|
|
imf_raw_not_terminated: *const libc::c_char,
|
|
imf_raw_bytes: libc::size_t,
|
|
server_folder: impl AsRef<str>,
|
|
server_uid: u32,
|
|
flags: u32,
|
|
) {
|
|
info!(
|
|
context,
|
|
"Receiving message {}/{}...",
|
|
if !server_folder.as_ref().is_empty() {
|
|
server_folder.as_ref()
|
|
} else {
|
|
"?"
|
|
},
|
|
server_uid,
|
|
);
|
|
|
|
// Parse the imf to mailimf_message. normally, this is done by mailimf_message_parse(),
|
|
// however, as we also need the MIME data,
|
|
// we use mailmime_parse() through dc_mimeparser (both call mailimf_struct_multiple_parse()
|
|
// somewhen, I did not found out anything that speaks against this approach yet)
|
|
|
|
let body = std::slice::from_raw_parts(imf_raw_not_terminated as *const u8, imf_raw_bytes);
|
|
let mut mime_parser = MimeParser::new(context);
|
|
mime_parser.parse(body);
|
|
|
|
if mime_parser.header.is_empty() {
|
|
// Error - even adding an empty record won't help as we do not know the message ID
|
|
info!(context, "No header.");
|
|
return;
|
|
}
|
|
|
|
// the function returns the number of created messages in the database
|
|
let mut incoming = 1;
|
|
let mut incoming_origin = Origin::Unknown;
|
|
let mut to_self = 0;
|
|
let mut from_id = 0u32;
|
|
let mut from_id_blocked = 0;
|
|
let mut to_id = 0u32;
|
|
let mut chat_id = 0;
|
|
let mut hidden = 0;
|
|
|
|
let mut add_delete_job: libc::c_int = 0;
|
|
let mut insert_msg_id = 0;
|
|
|
|
let mut sent_timestamp = 0;
|
|
let mut created_db_entries = Vec::new();
|
|
let mut create_event_to_send = Some(CreateEvent::MsgsChanged);
|
|
let mut rr_event_to_send = Vec::new();
|
|
|
|
let mut to_ids = Vec::with_capacity(16);
|
|
|
|
// helper method to handle early exit and memory cleanup
|
|
let cleanup = |context: &Context,
|
|
create_event_to_send: &Option<CreateEvent>,
|
|
created_db_entries: &Vec<(usize, usize)>,
|
|
rr_event_to_send: &Vec<(u32, u32)>| {
|
|
if let Some(create_event_to_send) = create_event_to_send {
|
|
for (chat_id, msg_id) in created_db_entries {
|
|
let event = match create_event_to_send {
|
|
CreateEvent::MsgsChanged => Event::MsgsChanged {
|
|
msg_id: *msg_id as u32,
|
|
chat_id: *chat_id as u32,
|
|
},
|
|
CreateEvent::IncomingMsg => Event::IncomingMsg {
|
|
msg_id: *msg_id as u32,
|
|
chat_id: *chat_id as u32,
|
|
},
|
|
};
|
|
context.call_cb(event);
|
|
}
|
|
}
|
|
for (chat_id, msg_id) in rr_event_to_send {
|
|
context.call_cb(Event::MsgRead {
|
|
chat_id: *chat_id,
|
|
msg_id: *msg_id,
|
|
});
|
|
}
|
|
};
|
|
|
|
if let Some(field) = mime_parser.lookup_field_typ("Date", MAILIMF_FIELD_ORIG_DATE) {
|
|
let orig_date = (*field).fld_data.fld_orig_date;
|
|
if !orig_date.is_null() {
|
|
// is not yet checked against bad times! we do this later if we have the database information.
|
|
sent_timestamp = dc_timestamp_from_date((*orig_date).dt_date_time)
|
|
}
|
|
}
|
|
|
|
// get From: and check if it is known (for known From:'s we add the other To:/Cc: in the 3rd pass)
|
|
// or if From: is equal to SELF (in this case, it is any outgoing messages,
|
|
// we do not check Return-Path any more as this is unreliable, see issue #150
|
|
if let Some(field) = mime_parser.lookup_field_typ("From", MAILIMF_FIELD_FROM) {
|
|
let fld_from = (*field).fld_data.fld_from;
|
|
if !fld_from.is_null() {
|
|
let mut check_self = 0;
|
|
let mut from_list = Vec::with_capacity(16);
|
|
dc_add_or_lookup_contacts_by_mailbox_list(
|
|
context,
|
|
(*fld_from).frm_mb_list,
|
|
Origin::IncomingUnknownFrom,
|
|
&mut from_list,
|
|
&mut check_self,
|
|
);
|
|
if 0 != check_self {
|
|
incoming = 0;
|
|
if mime_parser.sender_equals_recipient() {
|
|
from_id = DC_CONTACT_ID_SELF;
|
|
}
|
|
} else if from_list.len() >= 1 {
|
|
// if there is no from given, from_id stays 0 which is just fine. These messages
|
|
// are very rare, however, we have to add them to the database (they go to the
|
|
// "deaddrop" chat) to avoid a re-download from the server. See also [**]
|
|
from_id = from_list[0];
|
|
incoming_origin = Contact::get_origin_by_id(context, from_id, &mut from_id_blocked)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure, to_ids starts with the first To:-address (Cc: is added in the loop below pass)
|
|
if let Some(field) = mime_parser.lookup_field_typ("To", MAILIMF_FIELD_TO) {
|
|
let fld_to = (*field).fld_data.fld_to;
|
|
if !fld_to.is_null() {
|
|
dc_add_or_lookup_contacts_by_address_list(
|
|
context,
|
|
(*fld_to).to_addr_list,
|
|
if 0 == incoming {
|
|
Origin::OutgoingTo
|
|
} else if incoming_origin.is_verified() {
|
|
Origin::IncomingTo
|
|
} else {
|
|
Origin::IncomingUnknownTo
|
|
},
|
|
&mut to_ids,
|
|
&mut to_self,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Add parts
|
|
|
|
let rfc724_mid = match mime_parser.get_rfc724_mid() {
|
|
Some(x) => x,
|
|
None => {
|
|
// missing Message-IDs may come if the mail was set from this account with another
|
|
// client that relies in the SMTP server to generate one.
|
|
// true eg. for the Webmailer used in all-inkl-KAS
|
|
match dc_create_incoming_rfc724_mid(sent_timestamp, from_id, &to_ids) {
|
|
Some(x) => x.to_string(),
|
|
None => {
|
|
error!(context, "can not create incoming rfc724_mid");
|
|
cleanup(
|
|
context,
|
|
&create_event_to_send,
|
|
&created_db_entries,
|
|
&rr_event_to_send,
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
if mime_parser.get_last_nonmeta().is_some() {
|
|
if let Err(err) = add_parts(
|
|
context,
|
|
&mut mime_parser,
|
|
imf_raw_not_terminated,
|
|
imf_raw_bytes,
|
|
incoming,
|
|
&mut incoming_origin,
|
|
server_folder.as_ref(),
|
|
server_uid,
|
|
&mut to_ids,
|
|
&rfc724_mid,
|
|
&mut sent_timestamp,
|
|
&mut from_id,
|
|
from_id_blocked,
|
|
&mut hidden,
|
|
&mut chat_id,
|
|
&mut to_id,
|
|
flags,
|
|
&mut add_delete_job,
|
|
to_self,
|
|
&mut insert_msg_id,
|
|
&mut created_db_entries,
|
|
&mut create_event_to_send,
|
|
) {
|
|
info!(context, "{}", err);
|
|
|
|
cleanup(
|
|
context,
|
|
&create_event_to_send,
|
|
&created_db_entries,
|
|
&rr_event_to_send,
|
|
);
|
|
return;
|
|
}
|
|
} else {
|
|
// there are no non-meta data in message, do some basic calculations so that the varaiables
|
|
// are correct in the further processing
|
|
if sent_timestamp > time() {
|
|
sent_timestamp = time()
|
|
}
|
|
}
|
|
|
|
if !mime_parser.reports.is_empty() {
|
|
handle_reports(
|
|
context,
|
|
&mime_parser,
|
|
from_id,
|
|
sent_timestamp,
|
|
&mut rr_event_to_send,
|
|
server_folder,
|
|
server_uid,
|
|
);
|
|
}
|
|
|
|
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL {
|
|
save_locations(
|
|
context,
|
|
&mime_parser,
|
|
chat_id,
|
|
from_id,
|
|
insert_msg_id,
|
|
hidden,
|
|
);
|
|
}
|
|
|
|
if 0 != add_delete_job && !created_db_entries.is_empty() {
|
|
job_add(
|
|
context,
|
|
Action::DeleteMsgOnImap,
|
|
created_db_entries[0].1 as i32,
|
|
Params::new(),
|
|
0,
|
|
);
|
|
}
|
|
|
|
info!(
|
|
context,
|
|
"received message {} has Message-Id: {}", server_uid, rfc724_mid
|
|
);
|
|
|
|
cleanup(
|
|
context,
|
|
&create_event_to_send,
|
|
&created_db_entries,
|
|
&rr_event_to_send,
|
|
);
|
|
}
|
|
|
|
unsafe fn add_parts(
|
|
context: &Context,
|
|
mut mime_parser: &mut MimeParser,
|
|
imf_raw_not_terminated: *const libc::c_char,
|
|
imf_raw_bytes: libc::size_t,
|
|
incoming: i32,
|
|
incoming_origin: &mut Origin,
|
|
server_folder: impl AsRef<str>,
|
|
server_uid: u32,
|
|
to_ids: &mut Vec<u32>,
|
|
rfc724_mid: &str,
|
|
sent_timestamp: &mut i64,
|
|
from_id: &mut u32,
|
|
from_id_blocked: i32,
|
|
hidden: &mut libc::c_int,
|
|
chat_id: &mut u32,
|
|
to_id: &mut u32,
|
|
flags: u32,
|
|
add_delete_job: &mut libc::c_int,
|
|
to_self: i32,
|
|
insert_msg_id: &mut u32,
|
|
created_db_entries: &mut Vec<(usize, usize)>,
|
|
create_event_to_send: &mut Option<CreateEvent>,
|
|
) -> Result<()> {
|
|
let mut state: MessageState;
|
|
let mut msgrmsg: libc::c_int;
|
|
let mut chat_id_blocked = Blocked::Not;
|
|
let mut sort_timestamp = 0;
|
|
let mut rcvd_timestamp = 0;
|
|
let mut mime_in_reply_to = std::ptr::null_mut();
|
|
let mut mime_references = std::ptr::null_mut();
|
|
|
|
let cleanup = |mime_in_reply_to: *mut libc::c_char, mime_references: *mut libc::c_char| {
|
|
free(mime_in_reply_to.cast());
|
|
free(mime_references.cast());
|
|
};
|
|
|
|
// collect the rest information, CC: is added to the to-list, BCC: is ignored
|
|
// (we should not add BCC to groups as this would split groups. We could add them as "known contacts",
|
|
// however, the benefit is very small and this may leak data that is expected to be hidden)
|
|
if let Some(field) = mime_parser.lookup_field_typ("Cc", MAILIMF_FIELD_CC) {
|
|
let fld_cc = (*field).fld_data.fld_cc;
|
|
if !fld_cc.is_null() {
|
|
dc_add_or_lookup_contacts_by_address_list(
|
|
context,
|
|
(*fld_cc).cc_addr_list,
|
|
if 0 == incoming {
|
|
Origin::OutgoingCc
|
|
} else if incoming_origin.is_verified() {
|
|
Origin::IncomingCc
|
|
} else {
|
|
Origin::IncomingUnknownCc
|
|
},
|
|
to_ids,
|
|
std::ptr::null_mut(),
|
|
);
|
|
}
|
|
}
|
|
|
|
// check, if the mail is already in our database - if so, just update the folder/uid
|
|
// (if the mail was moved around) and finish. (we may get a mail twice eg. if it is
|
|
// moved between folders. make sure, this check is done eg. before securejoin-processing) */
|
|
if let Ok((old_server_folder, old_server_uid, _)) =
|
|
message::rfc724_mid_exists(context, &rfc724_mid)
|
|
{
|
|
if old_server_folder != server_folder.as_ref() || old_server_uid != server_uid {
|
|
message::update_server_uid(context, &rfc724_mid, server_folder.as_ref(), server_uid);
|
|
}
|
|
|
|
cleanup(mime_in_reply_to, mime_references);
|
|
bail!("Message already in DB");
|
|
}
|
|
|
|
// 1 or 0 for yes/no
|
|
msgrmsg = mime_parser.is_send_by_messenger as _;
|
|
if msgrmsg == 0 && 0 != dc_is_reply_to_messenger_message(context, mime_parser) {
|
|
// 2=no, but is reply to messenger message
|
|
msgrmsg = 2;
|
|
}
|
|
// incoming non-chat messages may be discarded;
|
|
// maybe this can be optimized later, by checking the state before the message body is downloaded
|
|
let mut allow_creation = 1;
|
|
if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == 0 {
|
|
let show_emails = context
|
|
.sql
|
|
.get_config_int(context, "show_emails")
|
|
.unwrap_or_default();
|
|
if show_emails == 0 {
|
|
*chat_id = 3;
|
|
allow_creation = 0
|
|
} else if show_emails == 1 {
|
|
allow_creation = 0
|
|
}
|
|
}
|
|
|
|
// check if the message introduces a new chat:
|
|
// - outgoing messages introduce a chat with the first to: address if they are sent by a messenger
|
|
// - incoming messages introduce a chat only for known contacts if they are sent by a messenger
|
|
// (of course, the user can add other chats manually later)
|
|
if 0 != incoming {
|
|
state = if 0 != flags & DC_IMAP_SEEN {
|
|
MessageState::InSeen
|
|
} else {
|
|
MessageState::InFresh
|
|
};
|
|
*to_id = 1;
|
|
// handshake messages must be processed _before_ chats are created
|
|
// (eg. contacs may be marked as verified)
|
|
if mime_parser.lookup_field("Secure-Join").is_some() {
|
|
// avoid discarding by show_emails setting
|
|
msgrmsg = 1;
|
|
*chat_id = 0;
|
|
allow_creation = 1;
|
|
let handshake = handle_securejoin_handshake(context, mime_parser, *from_id);
|
|
if 0 != handshake & DC_HANDSHAKE_STOP_NORMAL_PROCESSING {
|
|
*hidden = 1;
|
|
*add_delete_job = handshake & DC_HANDSHAKE_ADD_DELETE_JOB;
|
|
state = MessageState::InSeen;
|
|
}
|
|
}
|
|
|
|
let (test_normal_chat_id, test_normal_chat_id_blocked) =
|
|
chat::lookup_by_contact_id(context, *from_id).unwrap_or_default();
|
|
|
|
// get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list,
|
|
// it might also be blocked and displayed in the deaddrop as a result
|
|
if *chat_id == 0 {
|
|
// try to create a group
|
|
// (groups appear automatically only if the _sender_ is known, see core issue #54)
|
|
|
|
let create_blocked = if 0 != test_normal_chat_id
|
|
&& test_normal_chat_id_blocked == Blocked::Not
|
|
|| incoming_origin.is_start_new_chat()
|
|
{
|
|
Blocked::Not
|
|
} else {
|
|
Blocked::Deaddrop
|
|
};
|
|
|
|
create_or_lookup_group(
|
|
context,
|
|
&mut mime_parser,
|
|
allow_creation,
|
|
create_blocked,
|
|
*from_id,
|
|
to_ids,
|
|
chat_id,
|
|
&mut chat_id_blocked,
|
|
);
|
|
if 0 != *chat_id && Blocked::Not != chat_id_blocked && create_blocked == Blocked::Not {
|
|
chat::unblock(context, *chat_id);
|
|
chat_id_blocked = Blocked::Not;
|
|
}
|
|
}
|
|
|
|
if *chat_id == 0 {
|
|
// check if the message belongs to a mailing list
|
|
if mime_parser.is_mailinglist_message() {
|
|
*chat_id = 3;
|
|
info!(context, "Message belongs to a mailing list and is ignored.",);
|
|
}
|
|
}
|
|
|
|
if *chat_id == 0 {
|
|
// try to create a normal chat
|
|
let create_blocked = if incoming_origin.is_start_new_chat() || *from_id == *to_id {
|
|
Blocked::Not
|
|
} else {
|
|
Blocked::Deaddrop
|
|
};
|
|
|
|
if 0 != test_normal_chat_id {
|
|
*chat_id = test_normal_chat_id;
|
|
chat_id_blocked = test_normal_chat_id_blocked;
|
|
} else if 0 != allow_creation {
|
|
let (id, bl) =
|
|
chat::create_or_lookup_by_contact_id(context, *from_id, create_blocked)
|
|
.unwrap_or_default();
|
|
*chat_id = id;
|
|
chat_id_blocked = bl;
|
|
}
|
|
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
|
|
if Blocked::Not == create_blocked {
|
|
chat::unblock(context, *chat_id);
|
|
chat_id_blocked = Blocked::Not;
|
|
} else if 0 != dc_is_reply_to_known_message(context, mime_parser) {
|
|
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
|
// the contact requests will pop up and this should be just fine.
|
|
Contact::scaleup_origin_by_id(context, *from_id, Origin::IncomingReplyTo);
|
|
info!(
|
|
context,
|
|
"Message is a reply to a known message, mark sender as known.",
|
|
);
|
|
if !incoming_origin.is_verified() {
|
|
*incoming_origin = Origin::IncomingReplyTo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if *chat_id == 0 {
|
|
// maybe from_id is null or sth. else is suspicious, move message to trash
|
|
*chat_id = DC_CHAT_ID_TRASH;
|
|
}
|
|
|
|
// if the chat_id is blocked,
|
|
// for unknown senders and non-delta messages set the state to NOTICED
|
|
// to not result in a contact request (this would require the state FRESH)
|
|
if Blocked::Not != chat_id_blocked && state == MessageState::InFresh {
|
|
if !incoming_origin.is_verified() && msgrmsg == 0 {
|
|
state = MessageState::InNoticed;
|
|
}
|
|
}
|
|
} else {
|
|
// Outgoing
|
|
|
|
// the mail is on the IMAP server, probably it is also delivered.
|
|
// We cannot recreate other states (read, error).
|
|
state = MessageState::OutDelivered;
|
|
*from_id = DC_CONTACT_ID_SELF;
|
|
if !to_ids.is_empty() {
|
|
*to_id = to_ids[0];
|
|
if *chat_id == 0 {
|
|
create_or_lookup_group(
|
|
context,
|
|
&mut mime_parser,
|
|
allow_creation,
|
|
Blocked::Not,
|
|
*from_id,
|
|
to_ids,
|
|
chat_id,
|
|
&mut chat_id_blocked,
|
|
);
|
|
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
|
|
chat::unblock(context, *chat_id);
|
|
chat_id_blocked = Blocked::Not;
|
|
}
|
|
}
|
|
if *chat_id == 0 && 0 != allow_creation {
|
|
let create_blocked = if 0 != msgrmsg && !Contact::is_blocked_load(context, *to_id) {
|
|
Blocked::Not
|
|
} else {
|
|
Blocked::Deaddrop
|
|
};
|
|
let (id, bl) =
|
|
chat::create_or_lookup_by_contact_id(context, *to_id, create_blocked)
|
|
.unwrap_or_default();
|
|
*chat_id = id;
|
|
chat_id_blocked = bl;
|
|
|
|
if 0 != *chat_id
|
|
&& Blocked::Not != chat_id_blocked
|
|
&& Blocked::Not == create_blocked
|
|
{
|
|
chat::unblock(context, *chat_id);
|
|
chat_id_blocked = Blocked::Not;
|
|
}
|
|
}
|
|
}
|
|
if *chat_id == 0 {
|
|
if to_ids.is_empty() && 0 != to_self {
|
|
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
|
|
// maybe an Autocrypt Setup Messag
|
|
let (id, bl) = chat::create_or_lookup_by_contact_id(context, 1, Blocked::Not)
|
|
.unwrap_or_default();
|
|
*chat_id = id;
|
|
chat_id_blocked = bl;
|
|
|
|
if 0 != *chat_id && Blocked::Not != chat_id_blocked {
|
|
chat::unblock(context, *chat_id);
|
|
chat_id_blocked = Blocked::Not;
|
|
}
|
|
}
|
|
}
|
|
if *chat_id == 0 {
|
|
*chat_id = DC_CHAT_ID_TRASH;
|
|
}
|
|
}
|
|
// correct message_timestamp, it should not be used before,
|
|
// however, we cannot do this earlier as we need from_id to be set
|
|
calc_timestamps(
|
|
context,
|
|
*chat_id,
|
|
*from_id,
|
|
*sent_timestamp,
|
|
if 0 != flags & DC_IMAP_SEEN { 0 } else { 1 },
|
|
&mut sort_timestamp,
|
|
sent_timestamp,
|
|
&mut rcvd_timestamp,
|
|
);
|
|
|
|
// unarchive chat
|
|
chat::unarchive(context, *chat_id).unwrap();
|
|
|
|
// if the mime-headers should be saved, find out its size
|
|
// (the mime-header ends with an empty line)
|
|
let save_mime_headers = context.sql.get_config_bool(context, "save_mime_headers");
|
|
if let Some(field) = mime_parser.lookup_field_typ("In-Reply-To", MAILIMF_FIELD_IN_REPLY_TO) {
|
|
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
|
if !fld_in_reply_to.is_null() {
|
|
mime_in_reply_to = dc_str_from_clist(
|
|
(*(*field).fld_data.fld_in_reply_to).mid_list,
|
|
b" \x00" as *const u8 as *const libc::c_char,
|
|
)
|
|
}
|
|
}
|
|
|
|
if let Some(field) = mime_parser.lookup_field_typ("References", MAILIMF_FIELD_REFERENCES) {
|
|
let fld_references = (*field).fld_data.fld_references;
|
|
if !fld_references.is_null() {
|
|
mime_references = dc_str_from_clist(
|
|
(*(*field).fld_data.fld_references).mid_list,
|
|
b" \x00" as *const u8 as *const libc::c_char,
|
|
)
|
|
}
|
|
}
|
|
|
|
// fine, so far. now, split the message into simple parts usable as "short messages"
|
|
// and add them to the database (mails sent by other messenger clients should result
|
|
// into only one message; mails sent by other clients may result in several messages
|
|
// (eg. one per attachment))
|
|
let icnt = mime_parser.parts.len();
|
|
let mut txt_raw = None;
|
|
|
|
let is_ok = context
|
|
.sql
|
|
.prepare(
|
|
"INSERT INTO msgs \
|
|
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \
|
|
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \
|
|
bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \
|
|
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|
|
|mut stmt, conn| {
|
|
for i in 0..icnt {
|
|
let part = &mut mime_parser.parts[i];
|
|
if part.is_meta {
|
|
continue;
|
|
}
|
|
|
|
if let Some(ref msg) = part.msg {
|
|
if !mime_parser.location_kml.is_none()
|
|
&& icnt == 1
|
|
&& (msg == "-location-" || msg.is_empty())
|
|
{
|
|
*hidden = 1;
|
|
if state == MessageState::InFresh {
|
|
state = MessageState::InNoticed;
|
|
}
|
|
}
|
|
}
|
|
if part.typ == Viewtype::Text {
|
|
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
|
|
let subject = mime_parser
|
|
.subject
|
|
.as_ref()
|
|
.map(|s| s.to_string())
|
|
.unwrap_or("".into());
|
|
txt_raw = Some(format!("{}\n\n{}", subject, msg_raw));
|
|
}
|
|
if mime_parser.is_system_message != SystemMessage::Unknown {
|
|
part.param
|
|
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
|
|
}
|
|
|
|
/*
|
|
info!(
|
|
context,
|
|
"received mime message {:?}",
|
|
String::from_utf8_lossy(std::slice::from_raw_parts(
|
|
imf_raw_not_terminated as *const u8,
|
|
imf_raw_bytes,
|
|
))
|
|
);
|
|
*/
|
|
|
|
stmt.execute(params![
|
|
rfc724_mid,
|
|
server_folder.as_ref(),
|
|
server_uid as libc::c_int,
|
|
*chat_id as libc::c_int,
|
|
*from_id as libc::c_int,
|
|
*to_id as libc::c_int,
|
|
sort_timestamp,
|
|
*sent_timestamp,
|
|
rcvd_timestamp,
|
|
part.typ,
|
|
state,
|
|
msgrmsg,
|
|
part.msg.as_ref().map_or("", String::as_str),
|
|
// txt_raw might contain invalid utf8
|
|
txt_raw.unwrap_or_default(),
|
|
part.param.to_string(),
|
|
part.bytes,
|
|
*hidden,
|
|
if save_mime_headers {
|
|
Some(String::from_utf8_lossy(std::slice::from_raw_parts(
|
|
imf_raw_not_terminated as *const u8,
|
|
imf_raw_bytes,
|
|
)))
|
|
} else {
|
|
None
|
|
},
|
|
to_string(mime_in_reply_to),
|
|
to_string(mime_references),
|
|
])?;
|
|
|
|
txt_raw = None;
|
|
*insert_msg_id =
|
|
sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid);
|
|
created_db_entries.push((*chat_id as usize, *insert_msg_id as usize));
|
|
}
|
|
Ok(())
|
|
},
|
|
)
|
|
.is_ok();
|
|
|
|
if !is_ok {
|
|
// i/o error - there is nothing more we can do - in other cases, we try to write at least an empty record
|
|
cleanup(mime_in_reply_to, mime_references);
|
|
bail!("Cannot write DB.");
|
|
}
|
|
|
|
info!(
|
|
context,
|
|
"Message has {} parts and is assigned to chat #{}.", icnt, *chat_id,
|
|
);
|
|
|
|
// check event to send
|
|
if *chat_id == DC_CHAT_ID_TRASH {
|
|
*create_event_to_send = None;
|
|
} else if 0 != incoming && state == MessageState::InFresh {
|
|
if 0 != from_id_blocked {
|
|
*create_event_to_send = None;
|
|
} else if Blocked::Not != chat_id_blocked {
|
|
*create_event_to_send = Some(CreateEvent::MsgsChanged);
|
|
} else {
|
|
*create_event_to_send = Some(CreateEvent::IncomingMsg);
|
|
}
|
|
}
|
|
|
|
context.do_heuristics_moves(server_folder.as_ref(), *insert_msg_id);
|
|
cleanup(mime_in_reply_to, mime_references);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Handle reports (mainly MDNs)
|
|
unsafe fn handle_reports(
|
|
context: &Context,
|
|
mime_parser: &MimeParser,
|
|
from_id: u32,
|
|
sent_timestamp: i64,
|
|
rr_event_to_send: &mut Vec<(u32, u32)>,
|
|
server_folder: impl AsRef<str>,
|
|
server_uid: u32,
|
|
) {
|
|
let mdns_enabled = context
|
|
.sql
|
|
.get_config_int(context, "mdns_enabled")
|
|
.unwrap_or_else(|| DC_MDNS_DEFAULT_ENABLED);
|
|
|
|
for report_root in &mime_parser.reports {
|
|
let report_root = *report_root;
|
|
let mut mdn_consumed = 0;
|
|
let report_type = mailmime_find_ct_parameter(report_root, "report-type");
|
|
|
|
if report_root.is_null() || report_type.is_null() || (*report_type).pa_value.is_null() {
|
|
continue;
|
|
}
|
|
|
|
// the first part is for humans, the second for machines
|
|
if strcmp(
|
|
(*report_type).pa_value,
|
|
b"disposition-notification\x00" as *const u8 as *const libc::c_char,
|
|
) == 0
|
|
&& (*(*report_root).mm_data.mm_multipart.mm_mp_list).count >= 2
|
|
{
|
|
// to get a clear functionality, do not show incoming MDNs if the options is disabled
|
|
if 0 != mdns_enabled {
|
|
let report_data = (if !if !(*(*report_root).mm_data.mm_multipart.mm_mp_list)
|
|
.first
|
|
.is_null()
|
|
{
|
|
(*(*(*report_root).mm_data.mm_multipart.mm_mp_list).first).next
|
|
} else {
|
|
ptr::null_mut()
|
|
}
|
|
.is_null()
|
|
{
|
|
(*if !(*(*report_root).mm_data.mm_multipart.mm_mp_list)
|
|
.first
|
|
.is_null()
|
|
{
|
|
(*(*(*report_root).mm_data.mm_multipart.mm_mp_list).first).next
|
|
} else {
|
|
ptr::null_mut()
|
|
})
|
|
.data
|
|
} else {
|
|
ptr::null_mut()
|
|
}) as *mut mailmime;
|
|
|
|
if !report_data.is_null()
|
|
&& (*(*(*report_data).mm_content_type).ct_type).tp_type
|
|
== MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int
|
|
&& (*(*(*(*report_data).mm_content_type).ct_type)
|
|
.tp_data
|
|
.tp_composite_type)
|
|
.ct_type
|
|
== MAILMIME_COMPOSITE_TYPE_MESSAGE as libc::c_int
|
|
&& strcmp(
|
|
(*(*report_data).mm_content_type).ct_subtype,
|
|
b"disposition-notification\x00" as *const u8 as *const libc::c_char,
|
|
) == 0
|
|
{
|
|
if let Ok(report_body) = mailmime_transfer_decode(report_data) {
|
|
let mut report_parsed = std::ptr::null_mut();
|
|
let mut dummy = 0;
|
|
|
|
if mailmime_parse(
|
|
report_body.as_ptr() as *const _,
|
|
report_body.len(),
|
|
&mut dummy,
|
|
&mut report_parsed,
|
|
) == MAIL_NO_ERROR as libc::c_int
|
|
&& !report_parsed.is_null()
|
|
{
|
|
let report_fields = mailmime_find_mailimf_fields(report_parsed);
|
|
if !report_fields.is_null() {
|
|
let of_disposition = mailimf_find_optional_field(
|
|
report_fields,
|
|
b"Disposition\x00" as *const u8 as *const libc::c_char,
|
|
);
|
|
let of_org_msgid = mailimf_find_optional_field(
|
|
report_fields,
|
|
b"Original-Message-ID\x00" as *const u8 as *const libc::c_char,
|
|
);
|
|
if !of_disposition.is_null()
|
|
&& !(*of_disposition).fld_value.is_null()
|
|
&& !of_org_msgid.is_null()
|
|
&& !(*of_org_msgid).fld_value.is_null()
|
|
{
|
|
let mut rfc724_mid_0 = std::ptr::null_mut();
|
|
dummy = 0;
|
|
|
|
if mailimf_msg_id_parse(
|
|
(*of_org_msgid).fld_value,
|
|
strlen((*of_org_msgid).fld_value),
|
|
&mut dummy,
|
|
&mut rfc724_mid_0,
|
|
) == MAIL_NO_ERROR as libc::c_int
|
|
&& !rfc724_mid_0.is_null()
|
|
{
|
|
let mut chat_id_0 = 0;
|
|
let mut msg_id = 0;
|
|
|
|
if message::mdn_from_ext(
|
|
context,
|
|
from_id,
|
|
as_str(rfc724_mid_0),
|
|
sent_timestamp,
|
|
&mut chat_id_0,
|
|
&mut msg_id,
|
|
) {
|
|
rr_event_to_send.push((chat_id_0, msg_id));
|
|
}
|
|
mdn_consumed = (msg_id != 0) as libc::c_int;
|
|
free(rfc724_mid_0.cast());
|
|
}
|
|
}
|
|
}
|
|
mailmime_free(report_parsed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if mime_parser.is_send_by_messenger || 0 != mdn_consumed {
|
|
let mut param = Params::new();
|
|
param.set(Param::ServerFolder, server_folder.as_ref());
|
|
param.set_int(Param::ServerUid, server_uid as i32);
|
|
if mime_parser.is_send_by_messenger
|
|
&& 0 != context
|
|
.sql
|
|
.get_config_int(context, "mvbox_move")
|
|
.unwrap_or_else(|| 1)
|
|
{
|
|
param.set_int(Param::AlsoMove, 1);
|
|
}
|
|
job_add(context, Action::MarkseenMdnOnImap, 0, param, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn save_locations(
|
|
context: &Context,
|
|
mime_parser: &MimeParser,
|
|
chat_id: u32,
|
|
from_id: u32,
|
|
insert_msg_id: u32,
|
|
hidden: i32,
|
|
) {
|
|
let mut location_id_written = false;
|
|
let mut send_event = false;
|
|
|
|
if !mime_parser.message_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
|
|
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
|
|
let newest_location_id =
|
|
location::save(context, chat_id, from_id, locations, 1).unwrap_or_default();
|
|
if 0 != newest_location_id && 0 == hidden {
|
|
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
|
|
location_id_written = true;
|
|
send_event = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if !mime_parser.location_kml.is_none() && chat_id > DC_CHAT_ID_LAST_SPECIAL as libc::c_uint {
|
|
if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr {
|
|
if let Ok(contact) = Contact::get_by_id(context, from_id) {
|
|
if !contact.get_addr().is_empty()
|
|
&& contact.get_addr().to_lowercase() == addr.to_lowercase()
|
|
{
|
|
let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
|
|
let newest_location_id =
|
|
location::save(context, chat_id, from_id, locations, 0).unwrap_or_default();
|
|
if newest_location_id != 0 && hidden == 0 && !location_id_written {
|
|
if let Err(err) = location::set_msg_location_id(
|
|
context,
|
|
insert_msg_id,
|
|
newest_location_id,
|
|
) {
|
|
error!(context, "Failed to set msg_location_id: {:?}", err);
|
|
}
|
|
}
|
|
send_event = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if send_event {
|
|
context.call_cb(Event::LocationChanged(Some(from_id)));
|
|
}
|
|
}
|
|
|
|
unsafe fn calc_timestamps(
|
|
context: &Context,
|
|
chat_id: u32,
|
|
from_id: u32,
|
|
message_timestamp: i64,
|
|
is_fresh_msg: libc::c_int,
|
|
sort_timestamp: *mut i64,
|
|
sent_timestamp: *mut i64,
|
|
rcvd_timestamp: *mut i64,
|
|
) {
|
|
*rcvd_timestamp = time();
|
|
*sent_timestamp = message_timestamp;
|
|
if *sent_timestamp > *rcvd_timestamp {
|
|
*sent_timestamp = *rcvd_timestamp
|
|
}
|
|
*sort_timestamp = message_timestamp;
|
|
if 0 != is_fresh_msg {
|
|
let last_msg_time: Option<i64> = context.sql.query_get_value(
|
|
context,
|
|
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?",
|
|
params![chat_id as i32, from_id as i32, *sort_timestamp],
|
|
);
|
|
if let Some(last_msg_time) = last_msg_time {
|
|
if last_msg_time > 0 {
|
|
if *sort_timestamp <= last_msg_time {
|
|
*sort_timestamp = last_msg_time + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if *sort_timestamp >= dc_smeared_time(context) {
|
|
*sort_timestamp = dc_create_smeared_timestamp(context);
|
|
}
|
|
}
|
|
|
|
/// This function tries extracts the group-id from the message and returns the
|
|
/// corresponding chat_id. If the chat_id is not existent, it is created.
|
|
/// If the message contains groups commands (name, profile image, changed members),
|
|
/// they are executed as well.
|
|
///
|
|
/// if no group-id could be extracted from the message, create_or_lookup_adhoc_group() is called
|
|
/// which tries to create or find out the chat_id by:
|
|
/// - is there a group with the same recipients? if so, use this (if there are multiple, use the most recent one)
|
|
/// - create an ad-hoc group based on the recipient list
|
|
///
|
|
/// So when the function returns, the caller has the group id matching the current state of the group.
|
|
#[allow(non_snake_case)]
|
|
unsafe fn create_or_lookup_group(
|
|
context: &Context,
|
|
mime_parser: &mut MimeParser,
|
|
allow_creation: libc::c_int,
|
|
create_blocked: Blocked,
|
|
from_id: u32,
|
|
to_ids: &mut Vec<u32>,
|
|
ret_chat_id: *mut u32,
|
|
ret_chat_id_blocked: &mut Blocked,
|
|
) {
|
|
let group_explicitly_left: bool;
|
|
let mut chat_id = 0;
|
|
let mut chat_id_blocked = Blocked::Not;
|
|
let mut grpid = "".to_string();
|
|
let mut grpname = None;
|
|
let to_ids_cnt = to_ids.len();
|
|
let mut recreate_member_list = 0;
|
|
let mut send_EVENT_CHAT_MODIFIED = 0;
|
|
let mut X_MrRemoveFromGrp = None;
|
|
let mut X_MrAddToGrp = None;
|
|
let mut X_MrGrpNameChanged = 0;
|
|
let mut X_MrGrpImageChanged = "".to_string();
|
|
let mut better_msg: String = From::from("");
|
|
|
|
let cleanup = |ret_chat_id: *mut u32,
|
|
ret_chat_id_blocked: &mut Blocked,
|
|
chat_id: u32,
|
|
chat_id_blocked: Blocked| {
|
|
if !ret_chat_id.is_null() {
|
|
*ret_chat_id = chat_id;
|
|
}
|
|
*ret_chat_id_blocked = if 0 != chat_id {
|
|
chat_id_blocked
|
|
} else {
|
|
Blocked::Not
|
|
};
|
|
};
|
|
|
|
if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled {
|
|
better_msg =
|
|
context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32)
|
|
}
|
|
set_better_msg(mime_parser, &better_msg);
|
|
|
|
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-ID") {
|
|
grpid = optional_field;
|
|
}
|
|
|
|
if grpid.is_empty() {
|
|
if let Some(field) = mime_parser.lookup_field_typ("Message-ID", MAILIMF_FIELD_MESSAGE_ID) {
|
|
let fld_message_id = (*field).fld_data.fld_message_id;
|
|
if !fld_message_id.is_null() {
|
|
if let Some(extracted_grpid) =
|
|
dc_extract_grpid_from_rfc724_mid(as_str((*fld_message_id).mid_value))
|
|
{
|
|
grpid = extracted_grpid.to_string();
|
|
} else {
|
|
grpid = "".to_string();
|
|
}
|
|
}
|
|
}
|
|
if grpid.is_empty() {
|
|
if let Some(field) =
|
|
mime_parser.lookup_field_typ("In-Reply-To", MAILIMF_FIELD_IN_REPLY_TO)
|
|
{
|
|
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
|
if !fld_in_reply_to.is_null() {
|
|
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
|
|
(*fld_in_reply_to).mid_list,
|
|
));
|
|
}
|
|
}
|
|
if grpid.is_empty() {
|
|
if let Some(field) =
|
|
mime_parser.lookup_field_typ("References", MAILIMF_FIELD_REFERENCES)
|
|
{
|
|
let fld_references = (*field).fld_data.fld_references;
|
|
if !fld_references.is_null() {
|
|
grpid = to_string(dc_extract_grpid_from_rfc724_mid_list(
|
|
(*fld_references).mid_list,
|
|
));
|
|
}
|
|
}
|
|
|
|
if grpid.is_empty() {
|
|
create_or_lookup_adhoc_group(
|
|
context,
|
|
mime_parser,
|
|
allow_creation,
|
|
create_blocked,
|
|
from_id,
|
|
to_ids,
|
|
&mut chat_id,
|
|
&mut chat_id_blocked,
|
|
);
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Name") {
|
|
grpname = Some(dc_decode_header_words_safe(&optional_field));
|
|
}
|
|
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Member-Removed") {
|
|
X_MrRemoveFromGrp = Some(optional_field);
|
|
mime_parser.is_system_message = SystemMessage::MemberRemovedFromGroup;
|
|
let left_group = (Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap())
|
|
== from_id as u32) as libc::c_int;
|
|
better_msg = context.stock_system_msg(
|
|
if 0 != left_group {
|
|
StockMessage::MsgGroupLeft
|
|
} else {
|
|
StockMessage::MsgDelMember
|
|
},
|
|
X_MrRemoveFromGrp.as_ref().unwrap(),
|
|
"",
|
|
from_id as u32,
|
|
)
|
|
} else {
|
|
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Member-Added") {
|
|
X_MrAddToGrp = Some(optional_field);
|
|
mime_parser.is_system_message = SystemMessage::MemberAddedToGroup;
|
|
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Image") {
|
|
X_MrGrpImageChanged = optional_field;
|
|
}
|
|
better_msg = context.stock_system_msg(
|
|
StockMessage::MsgAddMember,
|
|
X_MrAddToGrp.as_ref().unwrap(),
|
|
"",
|
|
from_id as u32,
|
|
)
|
|
} else {
|
|
if let Some(optional_field) =
|
|
mime_parser.lookup_optional_field("Chat-Group-Name-Changed")
|
|
{
|
|
X_MrGrpNameChanged = 1;
|
|
mime_parser.is_system_message = SystemMessage::GroupNameChanged;
|
|
better_msg = context.stock_system_msg(
|
|
StockMessage::MsgGrpName,
|
|
&optional_field,
|
|
if let Some(ref name) = grpname {
|
|
name
|
|
} else {
|
|
""
|
|
},
|
|
from_id as u32,
|
|
)
|
|
} else {
|
|
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Image")
|
|
{
|
|
// fld_value is a pointer somewhere into mime_parser, must not be freed
|
|
X_MrGrpImageChanged = optional_field;
|
|
mime_parser.is_system_message = SystemMessage::GroupImageChanged;
|
|
better_msg = context.stock_system_msg(
|
|
if X_MrGrpImageChanged == "0" {
|
|
StockMessage::MsgGrpImgDeleted
|
|
} else {
|
|
StockMessage::MsgGrpImgChanged
|
|
},
|
|
"",
|
|
"",
|
|
from_id as u32,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
set_better_msg(mime_parser, &better_msg);
|
|
|
|
// check, if we have a chat with this group ID
|
|
let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid);
|
|
if chat_id != 0 {
|
|
let mut failure_reason = std::ptr::null_mut();
|
|
|
|
if chat_id_verified
|
|
&& 0 == check_verified_properties(
|
|
context,
|
|
mime_parser,
|
|
from_id as u32,
|
|
to_ids,
|
|
&mut failure_reason,
|
|
)
|
|
{
|
|
mime_parser.repl_msg_by_error(to_string(failure_reason));
|
|
}
|
|
|
|
free(failure_reason.cast());
|
|
}
|
|
|
|
// check if the sender is a member of the existing group -
|
|
// if not, we'll recreate the group list
|
|
if chat_id != 0 && !chat::is_contact_in_chat(context, chat_id, from_id as u32) {
|
|
recreate_member_list = 1;
|
|
}
|
|
|
|
// check if the group does not exist but should be created
|
|
group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default();
|
|
|
|
let self_addr = context
|
|
.sql
|
|
.get_config(context, "configured_addr")
|
|
.unwrap_or_default();
|
|
if chat_id == 0
|
|
&& !mime_parser.is_mailinglist_message()
|
|
&& !grpid.is_empty()
|
|
&& grpname.is_some()
|
|
// otherwise, a pending "quit" message may pop up
|
|
&& X_MrRemoveFromGrp.is_none()
|
|
// re-create explicitly left groups only if ourself is re-added
|
|
&& (!group_explicitly_left
|
|
|| X_MrAddToGrp.is_some() && addr_cmp(&self_addr, X_MrAddToGrp.as_ref().unwrap()))
|
|
{
|
|
let mut create_verified = VerifiedStatus::Unverified;
|
|
if mime_parser.lookup_field("Chat-Verified").is_some() {
|
|
create_verified = VerifiedStatus::Verified;
|
|
let mut failure_reason = std::ptr::null_mut();
|
|
|
|
if 0 == check_verified_properties(
|
|
context,
|
|
mime_parser,
|
|
from_id as u32,
|
|
to_ids,
|
|
&mut failure_reason,
|
|
) {
|
|
mime_parser.repl_msg_by_error(to_string(failure_reason));
|
|
}
|
|
free(failure_reason.cast());
|
|
}
|
|
if 0 == allow_creation {
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
return;
|
|
}
|
|
chat_id = create_group_record(
|
|
context,
|
|
&grpid,
|
|
grpname.as_ref().unwrap(),
|
|
create_blocked,
|
|
create_verified,
|
|
);
|
|
chat_id_blocked = create_blocked;
|
|
recreate_member_list = 1;
|
|
}
|
|
|
|
// again, check chat_id
|
|
if chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
|
chat_id = 0;
|
|
if group_explicitly_left {
|
|
chat_id = DC_CHAT_ID_TRASH;
|
|
} else {
|
|
create_or_lookup_adhoc_group(
|
|
context,
|
|
mime_parser,
|
|
allow_creation,
|
|
create_blocked,
|
|
from_id,
|
|
to_ids,
|
|
&mut chat_id,
|
|
&mut chat_id_blocked,
|
|
);
|
|
}
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
return;
|
|
}
|
|
|
|
// execute group commands
|
|
if X_MrAddToGrp.is_some() || X_MrRemoveFromGrp.is_some() {
|
|
recreate_member_list = 1;
|
|
} else if 0 != X_MrGrpNameChanged {
|
|
if let Some(ref grpname) = grpname {
|
|
if grpname.len() < 200 {
|
|
info!(context, "updating grpname for chat {}", chat_id);
|
|
if sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"UPDATE chats SET name=? WHERE id=?;",
|
|
params![grpname, chat_id as i32],
|
|
)
|
|
.is_ok()
|
|
{
|
|
context.call_cb(Event::ChatModified(chat_id));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !X_MrGrpImageChanged.is_empty() {
|
|
info!(
|
|
context,
|
|
"grp-image-change {} chat {}", X_MrGrpImageChanged, chat_id
|
|
);
|
|
let mut changed = false;
|
|
let mut grpimage = "".to_string();
|
|
if X_MrGrpImageChanged == "0" {
|
|
changed = true;
|
|
} else {
|
|
for part in &mut mime_parser.parts {
|
|
if part.typ == Viewtype::Image {
|
|
grpimage = part
|
|
.param
|
|
.get(Param::File)
|
|
.map(|s| s.to_string())
|
|
.unwrap_or_else(|| "".to_string());
|
|
info!(context, "found image {:?}", grpimage);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
if changed {
|
|
info!(context, "New group image set to '{}'.", grpimage);
|
|
if let Ok(mut chat) = Chat::load_from_db(context, chat_id) {
|
|
if grpimage.is_empty() {
|
|
chat.param.remove(Param::ProfileImage);
|
|
} else {
|
|
chat.param.set(Param::ProfileImage, grpimage);
|
|
}
|
|
chat.update_param(context).unwrap();
|
|
send_EVENT_CHAT_MODIFIED = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add members to group/check members
|
|
// for recreation: we should add a timestamp
|
|
if 0 != recreate_member_list {
|
|
// TODO: the member list should only be recreated if the corresponding message is newer
|
|
// than the one that is responsible for the current member list, see
|
|
// https://github.com/deltachat/deltachat-core/issues/127
|
|
|
|
let skip = X_MrRemoveFromGrp.as_ref();
|
|
sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"DELETE FROM chats_contacts WHERE chat_id=?;",
|
|
params![chat_id as i32],
|
|
)
|
|
.ok();
|
|
if skip.is_none() || !addr_cmp(&self_addr, skip.unwrap()) {
|
|
chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF);
|
|
}
|
|
if from_id > DC_CHAT_ID_LAST_SPECIAL {
|
|
if !Contact::addr_equals_contact(context, &self_addr, from_id as u32)
|
|
&& (skip.is_none()
|
|
|| !Contact::addr_equals_contact(context, skip.unwrap(), from_id as u32))
|
|
{
|
|
chat::add_to_chat_contacts_table(context, chat_id, from_id as u32);
|
|
}
|
|
}
|
|
for &to_id in to_ids.iter() {
|
|
if !Contact::addr_equals_contact(context, &self_addr, to_id)
|
|
&& (skip.is_none() || !Contact::addr_equals_contact(context, skip.unwrap(), to_id))
|
|
{
|
|
chat::add_to_chat_contacts_table(context, chat_id, to_id);
|
|
}
|
|
}
|
|
send_EVENT_CHAT_MODIFIED = 1;
|
|
chat::reset_gossiped_timestamp(context, chat_id);
|
|
}
|
|
|
|
if 0 != send_EVENT_CHAT_MODIFIED {
|
|
context.call_cb(Event::ChatModified(chat_id));
|
|
}
|
|
|
|
// check the number of receivers -
|
|
// the only critical situation is if the user hits "Reply" instead of "Reply all" in a non-messenger-client */
|
|
if to_ids_cnt == 1 && !mime_parser.is_send_by_messenger {
|
|
let is_contact_cnt = chat::get_chat_contact_cnt(context, chat_id);
|
|
if is_contact_cnt > 3 {
|
|
// to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt.
|
|
// So everything up to 3 is no error.
|
|
chat_id = 0;
|
|
create_or_lookup_adhoc_group(
|
|
context,
|
|
mime_parser,
|
|
allow_creation,
|
|
create_blocked,
|
|
from_id,
|
|
to_ids,
|
|
&mut chat_id,
|
|
&mut chat_id_blocked,
|
|
);
|
|
}
|
|
}
|
|
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
}
|
|
|
|
/// Handle groups for received messages
|
|
unsafe fn create_or_lookup_adhoc_group(
|
|
context: &Context,
|
|
mime_parser: &MimeParser,
|
|
allow_creation: libc::c_int,
|
|
create_blocked: Blocked,
|
|
from_id: u32,
|
|
to_ids: &mut Vec<u32>,
|
|
ret_chat_id: *mut u32,
|
|
ret_chat_id_blocked: &mut Blocked,
|
|
) {
|
|
// if we're here, no grpid was found, check there is an existing ad-hoc
|
|
// group matching the to-list or if we can create one
|
|
let mut chat_id = 0;
|
|
let mut chat_id_blocked = Blocked::Not;
|
|
|
|
let cleanup = |ret_chat_id: *mut u32,
|
|
ret_chat_id_blocked: &mut Blocked,
|
|
chat_id: u32,
|
|
chat_id_blocked: Blocked| {
|
|
if !ret_chat_id.is_null() {
|
|
*ret_chat_id = chat_id;
|
|
}
|
|
*ret_chat_id_blocked = chat_id_blocked;
|
|
};
|
|
|
|
// build member list from the given ids
|
|
if to_ids.is_empty() || mime_parser.is_mailinglist_message() {
|
|
// too few contacts or a mailinglist
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
return;
|
|
}
|
|
|
|
let mut member_ids = to_ids.clone();
|
|
if !member_ids.contains(&from_id) {
|
|
member_ids.push(from_id);
|
|
}
|
|
if !member_ids.contains(&1) {
|
|
member_ids.push(1);
|
|
}
|
|
if member_ids.len() < 3 {
|
|
// too few contacts given
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
return;
|
|
}
|
|
|
|
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids);
|
|
if !chat_ids.is_empty() {
|
|
let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ",");
|
|
let res = context.sql.query_row(
|
|
format!(
|
|
"SELECT c.id, c.blocked FROM chats c \
|
|
LEFT JOIN msgs m ON m.chat_id=c.id WHERE c.id IN({}) ORDER BY m.timestamp DESC, m.id DESC LIMIT 1;",
|
|
chat_ids_str
|
|
),
|
|
params![],
|
|
|row| {
|
|
Ok((row.get::<_, i32>(0)?, row.get::<_, Option<Blocked>>(1)?.unwrap_or_default()))
|
|
}
|
|
);
|
|
|
|
if let Ok((id, id_blocked)) = res {
|
|
chat_id = id as u32;
|
|
chat_id_blocked = id_blocked;
|
|
/* success, chat found */
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if 0 == allow_creation {
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
return;
|
|
}
|
|
// we do not check if the message is a reply to another group, this may result in
|
|
// chats with unclear member list. instead we create a new group in the following lines ...
|
|
|
|
// create a new ad-hoc group
|
|
// - there is no need to check if this group exists; otherwise we would have caught it above
|
|
let grpid = create_adhoc_grp_id(context, &member_ids);
|
|
if grpid.is_empty() {
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
return;
|
|
}
|
|
|
|
// use subject as initial chat name
|
|
let grpname = if let Some(subject) = mime_parser.subject.as_ref().filter(|s| !s.is_empty()) {
|
|
subject.to_string()
|
|
} else {
|
|
context.stock_string_repl_int(StockMessage::Member, member_ids.len() as libc::c_int)
|
|
};
|
|
|
|
// create group record
|
|
chat_id = create_group_record(
|
|
context,
|
|
&grpid,
|
|
grpname,
|
|
create_blocked,
|
|
VerifiedStatus::Unverified,
|
|
);
|
|
chat_id_blocked = create_blocked;
|
|
for &member_id in &member_ids {
|
|
chat::add_to_chat_contacts_table(context, chat_id, member_id);
|
|
}
|
|
|
|
context.call_cb(Event::ChatModified(chat_id));
|
|
|
|
cleanup(ret_chat_id, ret_chat_id_blocked, chat_id, chat_id_blocked);
|
|
}
|
|
|
|
fn create_group_record(
|
|
context: &Context,
|
|
grpid: impl AsRef<str>,
|
|
grpname: impl AsRef<str>,
|
|
create_blocked: Blocked,
|
|
create_verified: VerifiedStatus,
|
|
) -> u32 {
|
|
if sql::execute(
|
|
context,
|
|
&context.sql,
|
|
"INSERT INTO chats (type, name, grpid, blocked) VALUES(?, ?, ?, ?);",
|
|
params![
|
|
if VerifiedStatus::Unverified != create_verified {
|
|
Chattype::VerifiedGroup
|
|
} else {
|
|
Chattype::Group
|
|
},
|
|
grpname.as_ref(),
|
|
grpid.as_ref(),
|
|
create_blocked,
|
|
],
|
|
)
|
|
.is_err()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref())
|
|
}
|
|
|
|
fn create_adhoc_grp_id(context: &Context, member_ids: &Vec<u32>) -> String {
|
|
/* algorithm:
|
|
- sort normalized, lowercased, e-mail addresses alphabetically
|
|
- put all e-mail addresses into a single string, separate the address by a single comma
|
|
- sha-256 this string (without possibly terminating null-characters)
|
|
- encode the first 64 bits of the sha-256 output as lowercase hex (results in 16 characters from the set [0-9a-f])
|
|
*/
|
|
let member_ids_str = join(member_ids.iter().map(|x| x.to_string()), ",");
|
|
let member_cs = context
|
|
.sql
|
|
.get_config(context, "configured_addr")
|
|
.unwrap_or_else(|| "no-self".to_string())
|
|
.to_lowercase();
|
|
|
|
let members = context
|
|
.sql
|
|
.query_map(
|
|
format!(
|
|
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1",
|
|
member_ids_str
|
|
),
|
|
params![],
|
|
|row| row.get::<_, String>(0),
|
|
|rows| {
|
|
let mut addrs = rows.collect::<std::result::Result<Vec<_>, _>>()?;
|
|
addrs.sort();
|
|
let mut acc = member_cs.clone();
|
|
for addr in &addrs {
|
|
acc += ",";
|
|
acc += &addr.to_lowercase();
|
|
}
|
|
Ok(acc)
|
|
},
|
|
)
|
|
.unwrap_or_else(|_| member_cs);
|
|
|
|
hex_hash(&members)
|
|
}
|
|
|
|
fn hex_hash(s: impl AsRef<str>) -> String {
|
|
let bytes = s.as_ref().as_bytes();
|
|
let result = Sha256::digest(bytes);
|
|
hex::encode(&result[..8])
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
fn search_chat_ids_by_contact_ids(context: &Context, unsorted_contact_ids: &Vec<u32>) -> Vec<u32> {
|
|
/* searches chat_id's by the given contact IDs, may return zero, one or more chat_id's */
|
|
let mut contact_ids = Vec::with_capacity(23);
|
|
let mut chat_ids = Vec::with_capacity(23);
|
|
|
|
/* copy array, remove duplicates and SELF, sort by ID */
|
|
if !unsorted_contact_ids.is_empty() {
|
|
for &curr_id in unsorted_contact_ids {
|
|
if curr_id != 1 && !contact_ids.contains(&curr_id) {
|
|
contact_ids.push(curr_id);
|
|
}
|
|
}
|
|
if !contact_ids.is_empty() {
|
|
contact_ids.sort();
|
|
let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ",");
|
|
context.sql.query_map(
|
|
format!(
|
|
"SELECT DISTINCT cc.chat_id, cc.contact_id \
|
|
FROM chats_contacts cc \
|
|
LEFT JOIN chats c ON c.id=cc.chat_id \
|
|
WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) \
|
|
AND c.type=120 \
|
|
AND cc.contact_id!=1 \
|
|
ORDER BY cc.chat_id, cc.contact_id;",
|
|
contact_ids_str
|
|
),
|
|
params![],
|
|
|row| Ok((row.get::<_, u32>(0)?, row.get::<_, u32>(1)?)),
|
|
|rows| {
|
|
let mut last_chat_id = 0;
|
|
let mut matches = 0;
|
|
let mut mismatches = 0;
|
|
|
|
for row in rows {
|
|
let (chat_id, contact_id) = row?;
|
|
if chat_id != last_chat_id {
|
|
if matches == contact_ids.len() && mismatches == 0 {
|
|
chat_ids.push(last_chat_id);
|
|
}
|
|
last_chat_id = chat_id;
|
|
matches = 0;
|
|
mismatches = 0;
|
|
}
|
|
if matches < contact_ids.len() && contact_id == contact_ids[matches] {
|
|
matches += 1;
|
|
} else {
|
|
mismatches += 1;
|
|
}
|
|
}
|
|
|
|
if matches == contact_ids.len() && mismatches == 0 {
|
|
chat_ids.push(last_chat_id);
|
|
}
|
|
Ok(())
|
|
}
|
|
).unwrap(); // TODO: better error handling
|
|
}
|
|
}
|
|
|
|
chat_ids
|
|
}
|
|
|
|
unsafe fn check_verified_properties(
|
|
context: &Context,
|
|
mimeparser: &MimeParser,
|
|
from_id: u32,
|
|
to_ids: &Vec<u32>,
|
|
failure_reason: *mut *mut libc::c_char,
|
|
) -> libc::c_int {
|
|
let verify_fail = |reason: String| {
|
|
*failure_reason = format!("{}. See \"Info\" for details.", reason).strdup();
|
|
warn!(context, "{}", reason);
|
|
};
|
|
|
|
let contact = match Contact::load_from_db(context, from_id) {
|
|
Ok(contact) => contact,
|
|
Err(_err) => {
|
|
verify_fail("Internal Error; cannot load contact".into());
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
if !mimeparser.e2ee_helper.encrypted {
|
|
verify_fail("This message is not encrypted".into());
|
|
return 0;
|
|
}
|
|
|
|
// ensure, the contact is verified
|
|
// and the message is signed with a verified key of the sender.
|
|
// this check is skipped for SELF as there is no proper SELF-peerstate
|
|
// and results in group-splits otherwise.
|
|
if from_id != 1 {
|
|
let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr());
|
|
|
|
if peerstate.is_none()
|
|
|| contact.is_verified_ex(context, peerstate.as_ref())
|
|
!= VerifiedStatus::BidirectVerified
|
|
{
|
|
verify_fail("The sender of this message is not verified.".into());
|
|
return 0;
|
|
}
|
|
|
|
if let Some(peerstate) = peerstate {
|
|
if !peerstate.has_verified_key(&mimeparser.e2ee_helper.signatures) {
|
|
verify_fail("The message was sent with non-verified encryption.".into());
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ",");
|
|
|
|
let rows = context.sql.query_map(
|
|
format!(
|
|
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
|
|
LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ",
|
|
to_ids_str,
|
|
),
|
|
params![],
|
|
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1)?)),
|
|
|rows| {
|
|
rows.collect::<std::result::Result<Vec<_>, _>>()
|
|
.map_err(Into::into)
|
|
},
|
|
);
|
|
|
|
if rows.is_err() {
|
|
return 0;
|
|
}
|
|
for (to_addr, mut is_verified) in rows.unwrap().into_iter() {
|
|
let mut peerstate = Peerstate::from_addr(context, &context.sql, &to_addr);
|
|
if mimeparser.e2ee_helper.gossipped_addr.contains(&to_addr) && peerstate.is_some() {
|
|
let peerstate = peerstate.as_mut().unwrap();
|
|
|
|
// if we're here, we know the gossip key is verified:
|
|
// - use the gossip-key as verified-key if there is no verified-key
|
|
// - OR if the verified-key does not match public-key or gossip-key
|
|
// (otherwise a verified key can _only_ be updated through QR scan which might be annoying,
|
|
// see https://github.com/nextleap-project/countermitm/issues/46 for a discussion about this point)
|
|
if 0 == is_verified
|
|
|| peerstate.verified_key_fingerprint != peerstate.public_key_fingerprint
|
|
&& peerstate.verified_key_fingerprint != peerstate.gossip_key_fingerprint
|
|
{
|
|
info!(context, "{} has verfied {}.", contact.get_addr(), to_addr,);
|
|
let fp = peerstate.gossip_key_fingerprint.clone();
|
|
if let Some(fp) = fp {
|
|
peerstate.set_verified(0, &fp, 2);
|
|
peerstate.save_to_db(&context.sql, false);
|
|
is_verified = 1;
|
|
}
|
|
}
|
|
}
|
|
if 0 == is_verified {
|
|
verify_fail(format!(
|
|
"{} is not a member of this verified group",
|
|
to_addr
|
|
));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
1
|
|
}
|
|
|
|
fn set_better_msg(mime_parser: &mut MimeParser, better_msg: impl AsRef<str>) {
|
|
let msg = better_msg.as_ref();
|
|
if msg.len() > 0 && !mime_parser.parts.is_empty() {
|
|
let part = &mut mime_parser.parts[0];
|
|
if part.typ == Viewtype::Text {
|
|
part.msg = Some(msg.to_string());
|
|
}
|
|
};
|
|
}
|
|
|
|
unsafe fn dc_is_reply_to_known_message(context: &Context, mime_parser: &MimeParser) -> libc::c_int {
|
|
/* check if the message is a reply to a known message; the replies are identified by the Message-ID from
|
|
`In-Reply-To`/`References:` (to support non-Delta-Clients) or from `Chat-Predecessor:` (Delta clients, see comment in dc_chat.c) */
|
|
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Predecessor") {
|
|
let optional_field_c = CString::new(optional_field).unwrap();
|
|
if 0 != is_known_rfc724_mid(context, optional_field_c.as_ptr()) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if let Some(field) = mime_parser.lookup_field("In-Reply-To") {
|
|
if (*field).fld_type == MAILIMF_FIELD_IN_REPLY_TO as libc::c_int {
|
|
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
|
if !fld_in_reply_to.is_null() {
|
|
if is_known_rfc724_mid_in_list(
|
|
context,
|
|
(*(*field).fld_data.fld_in_reply_to).mid_list,
|
|
) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(field) = mime_parser.lookup_field("References") {
|
|
if (*field).fld_type == MAILIMF_FIELD_REFERENCES as libc::c_int {
|
|
let fld_references = (*field).fld_data.fld_references;
|
|
if !fld_references.is_null() {
|
|
if is_known_rfc724_mid_in_list(
|
|
context,
|
|
(*(*field).fld_data.fld_references).mid_list,
|
|
) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
unsafe fn is_known_rfc724_mid_in_list(context: &Context, mid_list: *const clist) -> bool {
|
|
if mid_list.is_null() {
|
|
return false;
|
|
}
|
|
|
|
for data in &*mid_list {
|
|
if 0 != is_known_rfc724_mid(context, data.cast()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Check if a message is a reply to a known message (messenger or non-messenger).
|
|
fn is_known_rfc724_mid(context: &Context, rfc724_mid: *const libc::c_char) -> libc::c_int {
|
|
if rfc724_mid.is_null() {
|
|
return 0;
|
|
}
|
|
context
|
|
.sql
|
|
.exists(
|
|
"SELECT m.id FROM msgs m \
|
|
LEFT JOIN chats c ON m.chat_id=c.id \
|
|
WHERE m.rfc724_mid=? \
|
|
AND m.chat_id>9 AND c.blocked=0;",
|
|
params![as_str(rfc724_mid)],
|
|
)
|
|
.unwrap_or_default() as libc::c_int
|
|
}
|
|
|
|
unsafe fn dc_is_reply_to_messenger_message(
|
|
context: &Context,
|
|
mime_parser: &MimeParser,
|
|
) -> libc::c_int {
|
|
/* function checks, if the message defined by mime_parser references a message send by us from Delta Chat.
|
|
This is similar to is_reply_to_known_message() but
|
|
- checks also if any of the referenced IDs are send by a messenger
|
|
- it is okay, if the referenced messages are moved to trash here
|
|
- no check for the Chat-* headers (function is only called if it is no messenger message itself) */
|
|
|
|
if let Some(field) = mime_parser.lookup_field("In-Reply-To") {
|
|
if (*field).fld_type == MAILIMF_FIELD_IN_REPLY_TO as libc::c_int {
|
|
let fld_in_reply_to = (*field).fld_data.fld_in_reply_to;
|
|
if !fld_in_reply_to.is_null() {
|
|
if 0 != is_msgrmsg_rfc724_mid_in_list(
|
|
context,
|
|
(*(*field).fld_data.fld_in_reply_to).mid_list,
|
|
) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(field) = mime_parser.lookup_field("References") {
|
|
if (*field).fld_type == MAILIMF_FIELD_REFERENCES as libc::c_int {
|
|
let fld_references: *mut mailimf_references = (*field).fld_data.fld_references;
|
|
if !fld_references.is_null() {
|
|
if 0 != is_msgrmsg_rfc724_mid_in_list(
|
|
context,
|
|
(*(*field).fld_data.fld_references).mid_list,
|
|
) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
0
|
|
}
|
|
|
|
unsafe fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: *const clist) -> libc::c_int {
|
|
if !mid_list.is_null() {
|
|
let mut cur: *mut clistiter = (*mid_list).first;
|
|
while !cur.is_null() {
|
|
if 0 != is_msgrmsg_rfc724_mid(
|
|
context,
|
|
if !cur.is_null() {
|
|
as_str((*cur).data as *const libc::c_char)
|
|
} else {
|
|
""
|
|
},
|
|
) {
|
|
return 1;
|
|
}
|
|
cur = if !cur.is_null() {
|
|
(*cur).next
|
|
} else {
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
/// Check if a message is a reply to any messenger message.
|
|
fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> libc::c_int {
|
|
if rfc724_mid.is_empty() {
|
|
return 0;
|
|
}
|
|
context
|
|
.sql
|
|
.exists(
|
|
"SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;",
|
|
params![rfc724_mid],
|
|
)
|
|
.unwrap_or_default() as libc::c_int
|
|
}
|
|
|
|
unsafe fn dc_add_or_lookup_contacts_by_address_list(
|
|
context: &Context,
|
|
adr_list: *const mailimf_address_list,
|
|
origin: Origin,
|
|
ids: &mut Vec<u32>,
|
|
check_self: *mut libc::c_int,
|
|
) {
|
|
if adr_list.is_null() {
|
|
return;
|
|
}
|
|
let mut cur: *mut clistiter = (*(*adr_list).ad_list).first;
|
|
while !cur.is_null() {
|
|
let adr: *mut mailimf_address = (if !cur.is_null() {
|
|
(*cur).data
|
|
} else {
|
|
ptr::null_mut()
|
|
}) as *mut mailimf_address;
|
|
if !adr.is_null() {
|
|
if (*adr).ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
|
|
let mb: *mut mailimf_mailbox = (*adr).ad_data.ad_mailbox;
|
|
if !mb.is_null() {
|
|
add_or_lookup_contact_by_addr(
|
|
context,
|
|
(*mb).mb_display_name,
|
|
(*mb).mb_addr_spec,
|
|
origin,
|
|
ids,
|
|
check_self,
|
|
);
|
|
}
|
|
} else if (*adr).ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
|
|
let group: *mut mailimf_group = (*adr).ad_data.ad_group;
|
|
if !group.is_null() && !(*group).grp_mb_list.is_null() {
|
|
dc_add_or_lookup_contacts_by_mailbox_list(
|
|
context,
|
|
(*group).grp_mb_list,
|
|
origin,
|
|
ids,
|
|
check_self,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
cur = if !cur.is_null() {
|
|
(*cur).next
|
|
} else {
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn dc_add_or_lookup_contacts_by_mailbox_list(
|
|
context: &Context,
|
|
mb_list: *const mailimf_mailbox_list,
|
|
origin: Origin,
|
|
ids: &mut Vec<u32>,
|
|
check_self: *mut libc::c_int,
|
|
) {
|
|
if mb_list.is_null() {
|
|
return;
|
|
}
|
|
let mut cur: *mut clistiter = (*(*mb_list).mb_list).first;
|
|
while !cur.is_null() {
|
|
let mb: *mut mailimf_mailbox = (if !cur.is_null() {
|
|
(*cur).data
|
|
} else {
|
|
ptr::null_mut()
|
|
}) as *mut mailimf_mailbox;
|
|
if !mb.is_null() {
|
|
add_or_lookup_contact_by_addr(
|
|
context,
|
|
(*mb).mb_display_name,
|
|
(*mb).mb_addr_spec,
|
|
origin,
|
|
ids,
|
|
check_self,
|
|
);
|
|
}
|
|
cur = if !cur.is_null() {
|
|
(*cur).next
|
|
} else {
|
|
ptr::null_mut()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Add contacts to database on receiving messages.
|
|
unsafe fn add_or_lookup_contact_by_addr(
|
|
context: &Context,
|
|
display_name_enc: *const libc::c_char,
|
|
addr_spec: *const libc::c_char,
|
|
origin: Origin,
|
|
ids: &mut Vec<u32>,
|
|
mut check_self: *mut libc::c_int,
|
|
) {
|
|
/* is addr_spec equal to SELF? */
|
|
let mut dummy: libc::c_int = 0;
|
|
if check_self.is_null() {
|
|
check_self = &mut dummy
|
|
}
|
|
if addr_spec.is_null() {
|
|
return;
|
|
}
|
|
*check_self = 0;
|
|
let self_addr = context
|
|
.sql
|
|
.get_config(context, "configured_addr")
|
|
.unwrap_or_default();
|
|
|
|
if addr_cmp(self_addr, as_str(addr_spec)) {
|
|
*check_self = 1;
|
|
}
|
|
|
|
if 0 != *check_self {
|
|
return;
|
|
}
|
|
/* add addr_spec if missing, update otherwise */
|
|
let mut display_name_dec = "".to_string();
|
|
if !display_name_enc.is_null() {
|
|
let tmp = as_str(dc_decode_header_words(display_name_enc));
|
|
display_name_dec = normalize_name(&tmp);
|
|
}
|
|
/*can be NULL*/
|
|
let row_id = Contact::add_or_lookup(context, display_name_dec, as_str(addr_spec), origin)
|
|
.map(|(id, _)| id)
|
|
.unwrap_or_default();
|
|
if 0 != row_id {
|
|
if !ids.contains(&row_id) {
|
|
ids.push(row_id);
|
|
}
|
|
};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_hex_hash() {
|
|
let data = "hello world";
|
|
|
|
let res = hex_hash(data);
|
|
assert_eq!(res, "b94d27b9934d3e08");
|
|
}
|
|
}
|