mirror of
https://github.com/chatmail/core.git
synced 2026-05-01 20:36:31 +03:00
Change type of function from `const char *' to &str (#451)
Change type of function from `const char *' to &str
This commit is contained in:
173
src/chat.rs
173
src/chat.rs
@@ -171,51 +171,20 @@ impl<'a> Chat<'a> {
|
|||||||
return "Err".into();
|
return "Err".into();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_parent_mime_headers(
|
pub fn get_parent_mime_headers(&self) -> Option<(String, String, String)> {
|
||||||
&self,
|
let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?));
|
||||||
parent_rfc724_mid: *mut *mut libc::c_char,
|
let params = params![self.id as i32, DC_CONTACT_ID_SELF as i32];
|
||||||
parent_in_reply_to: *mut *mut libc::c_char,
|
let sql = &self.context.sql;
|
||||||
parent_references: *mut *mut libc::c_char,
|
let main_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
|
||||||
) -> Result<(), Error> {
|
|
||||||
if !(parent_rfc724_mid.is_null()
|
|
||||||
|| parent_in_reply_to.is_null()
|
|
||||||
|| parent_references.is_null())
|
|
||||||
{
|
|
||||||
// prefer a last message that isn't from us
|
|
||||||
let next = self
|
|
||||||
.context
|
|
||||||
.sql
|
|
||||||
.query_row(
|
|
||||||
"SELECT rfc724_mid, mime_in_reply_to, mime_references \
|
|
||||||
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \
|
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT max(timestamp) \
|
||||||
FROM msgs WHERE chat_id=?1 AND from_id!=?2);",
|
FROM msgs WHERE chat_id=?1 AND from_id!=?2);";
|
||||||
params![self.id as i32, DC_CONTACT_ID_SELF as i32],
|
let fallback_query = "SELECT rfc724_mid, mime_in_reply_to, mime_references \
|
||||||
|row| {
|
|
||||||
*parent_rfc724_mid = row.get::<_, String>(0)?.strdup();
|
|
||||||
*parent_in_reply_to = row.get::<_, String>(1)?.strdup();
|
|
||||||
*parent_references = row.get::<_, String>(2)?.strdup();
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.is_ok();
|
|
||||||
|
|
||||||
if !next {
|
|
||||||
self.context.sql.query_row(
|
|
||||||
"SELECT rfc724_mid, mime_in_reply_to, mime_references \
|
|
||||||
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \
|
FROM msgs WHERE chat_id=?1 AND timestamp=(SELECT min(timestamp) \
|
||||||
FROM msgs WHERE chat_id=?1 AND from_id==?2);",
|
FROM msgs WHERE chat_id=?1 AND from_id==?2);";
|
||||||
params![self.id as i32, DC_CONTACT_ID_SELF as i32],
|
|
||||||
|row| {
|
|
||||||
*parent_rfc724_mid = row.get::<_, String>(0)?.strdup();
|
|
||||||
*parent_in_reply_to = row.get::<_, String>(1)?.strdup();
|
|
||||||
*parent_references = row.get::<_, String>(2)?.strdup();
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
sql.query_row(main_query, params, collect)
|
||||||
|
.or_else(|_| sql.query_row(fallback_query, params, collect))
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn get_profile_image(&self) -> Option<String> {
|
pub unsafe fn get_profile_image(&self) -> Option<String> {
|
||||||
@@ -277,13 +246,8 @@ impl<'a> Chat<'a> {
|
|||||||
) -> Result<u32, Error> {
|
) -> Result<u32, Error> {
|
||||||
let mut do_guarantee_e2ee: libc::c_int;
|
let mut do_guarantee_e2ee: libc::c_int;
|
||||||
let e2ee_enabled: libc::c_int;
|
let e2ee_enabled: libc::c_int;
|
||||||
let mut OK_TO_CONTINUE = true;
|
let mut new_references = "".into();
|
||||||
let mut parent_rfc724_mid = ptr::null_mut();
|
let mut new_in_reply_to = "".into();
|
||||||
let mut parent_references = ptr::null_mut();
|
|
||||||
let mut parent_in_reply_to = ptr::null_mut();
|
|
||||||
let mut new_rfc724_mid = ptr::null_mut();
|
|
||||||
let mut new_references = ptr::null_mut();
|
|
||||||
let mut new_in_reply_to = ptr::null_mut();
|
|
||||||
let mut msg_id = 0;
|
let mut msg_id = 0;
|
||||||
let mut to_id = 0;
|
let mut to_id = 0;
|
||||||
let mut location_id = 0;
|
let mut location_id = 0;
|
||||||
@@ -293,7 +257,10 @@ impl<'a> Chat<'a> {
|
|||||||
|| self.typ == Chattype::VerifiedGroup)
|
|| self.typ == Chattype::VerifiedGroup)
|
||||||
{
|
{
|
||||||
error!(context, 0, "Cannot send to chat type #{}.", self.typ,);
|
error!(context, 0, "Cannot send to chat type #{}.", self.typ,);
|
||||||
} else if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
|
||||||
&& 0 == is_contact_in_chat(context, self.id, 1 as u32)
|
&& 0 == is_contact_in_chat(context, self.id, 1 as u32)
|
||||||
{
|
{
|
||||||
log_event!(
|
log_event!(
|
||||||
@@ -302,17 +269,17 @@ impl<'a> Chat<'a> {
|
|||||||
0,
|
0,
|
||||||
"Cannot send message; self not in group.",
|
"Cannot send message; self not in group.",
|
||||||
);
|
);
|
||||||
} else {
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(from) = context.sql.get_config(context, "configured_addr") {
|
if let Some(from) = context.sql.get_config(context, "configured_addr") {
|
||||||
let from_c = CString::yolo(from);
|
let new_rfc724_mid = {
|
||||||
new_rfc724_mid = dc_create_outgoing_rfc724_mid(
|
let grpid = match self.typ {
|
||||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()),
|
||||||
self.grpid.strdup()
|
_ => None,
|
||||||
} else {
|
};
|
||||||
ptr::null_mut()
|
dc_create_outgoing_rfc724_mid_safe(grpid, &from)
|
||||||
},
|
};
|
||||||
from_c.as_ptr(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.typ == Chattype::Single {
|
if self.typ == Chattype::Single {
|
||||||
if let Some(id) = context.sql.query_row_col(
|
if let Some(id) = context.sql.query_row_col(
|
||||||
@@ -327,7 +294,7 @@ impl<'a> Chat<'a> {
|
|||||||
context,
|
context,
|
||||||
0, "Cannot send message, contact for chat #{} not found.", self.id,
|
0, "Cannot send message, contact for chat #{} not found.", self.id,
|
||||||
);
|
);
|
||||||
OK_TO_CONTINUE = false;
|
return Ok(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
if self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup {
|
||||||
@@ -337,7 +304,7 @@ impl<'a> Chat<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if OK_TO_CONTINUE {
|
|
||||||
/* check if we can guarantee E2EE for this message.
|
/* check if we can guarantee E2EE for this message.
|
||||||
if we guarantee E2EE, and circumstances change
|
if we guarantee E2EE, and circumstances change
|
||||||
so that E2EE is no longer available at a later point (reset, changed settings),
|
so that E2EE is no longer available at a later point (reset, changed settings),
|
||||||
@@ -405,56 +372,29 @@ impl<'a> Chat<'a> {
|
|||||||
msg.param.set_int(Param::GuranteeE2ee, 1);
|
msg.param.set_int(Param::GuranteeE2ee, 1);
|
||||||
}
|
}
|
||||||
msg.param.remove(Param::ErroneousE2ee);
|
msg.param.remove(Param::ErroneousE2ee);
|
||||||
if !self.is_self_talk()
|
if !self.is_self_talk() {
|
||||||
&& self
|
if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) =
|
||||||
.get_parent_mime_headers(
|
self.get_parent_mime_headers()
|
||||||
&mut parent_rfc724_mid,
|
|
||||||
&mut parent_in_reply_to,
|
|
||||||
&mut parent_references,
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
{
|
||||||
if !parent_rfc724_mid.is_null()
|
if !parent_rfc724_mid.is_empty() {
|
||||||
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
|
new_in_reply_to = parent_rfc724_mid.clone();
|
||||||
{
|
|
||||||
new_in_reply_to = dc_strdup(parent_rfc724_mid)
|
|
||||||
}
|
}
|
||||||
if !parent_references.is_null() {
|
let parent_references = if let Some(n) = parent_references.find(' ') {
|
||||||
let space: *mut libc::c_char;
|
&parent_references[0..n]
|
||||||
space = strchr(parent_references, ' ' as i32);
|
} else {
|
||||||
if !space.is_null() {
|
&parent_references
|
||||||
*space = 0 as libc::c_char
|
};
|
||||||
|
|
||||||
|
if !parent_references.is_empty() && !parent_rfc724_mid.is_empty() {
|
||||||
|
new_references = format!("{} {}", parent_references, parent_rfc724_mid);
|
||||||
|
} else if !parent_references.is_empty() {
|
||||||
|
new_references = parent_references.to_string();
|
||||||
|
} else if !parent_in_reply_to.is_empty() && !parent_rfc724_mid.is_empty() {
|
||||||
|
new_references = format!("{} {}", parent_in_reply_to, parent_rfc724_mid);
|
||||||
|
} else if !parent_in_reply_to.is_empty() {
|
||||||
|
new_references = parent_in_reply_to.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !parent_references.is_null()
|
|
||||||
&& 0 != *parent_references.offset(0isize) as libc::c_int
|
|
||||||
&& !parent_rfc724_mid.is_null()
|
|
||||||
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
|
|
||||||
{
|
|
||||||
new_references = dc_mprintf(
|
|
||||||
b"%s %s\x00" as *const u8 as *const libc::c_char,
|
|
||||||
parent_references,
|
|
||||||
parent_rfc724_mid,
|
|
||||||
)
|
|
||||||
} else if !parent_references.is_null()
|
|
||||||
&& 0 != *parent_references.offset(0isize) as libc::c_int
|
|
||||||
{
|
|
||||||
new_references = dc_strdup(parent_references)
|
|
||||||
} else if !parent_in_reply_to.is_null()
|
|
||||||
&& 0 != *parent_in_reply_to.offset(0isize) as libc::c_int
|
|
||||||
&& !parent_rfc724_mid.is_null()
|
|
||||||
&& 0 != *parent_rfc724_mid.offset(0isize) as libc::c_int
|
|
||||||
{
|
|
||||||
new_references = dc_mprintf(
|
|
||||||
b"%s %s\x00" as *const u8 as *const libc::c_char,
|
|
||||||
parent_in_reply_to,
|
|
||||||
parent_rfc724_mid,
|
|
||||||
)
|
|
||||||
} else if !parent_in_reply_to.is_null()
|
|
||||||
&& 0 != *parent_in_reply_to.offset(0isize) as libc::c_int
|
|
||||||
{
|
|
||||||
new_references = dc_strdup(parent_in_reply_to)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add independent location to database
|
// add independent location to database
|
||||||
@@ -495,7 +435,7 @@ impl<'a> Chat<'a> {
|
|||||||
&context.sql,
|
&context.sql,
|
||||||
"INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);",
|
"INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);",
|
||||||
params![
|
params![
|
||||||
as_str(new_rfc724_mid),
|
new_rfc724_mid,
|
||||||
self.id as i32,
|
self.id as i32,
|
||||||
1i32,
|
1i32,
|
||||||
to_id as i32,
|
to_id as i32,
|
||||||
@@ -505,8 +445,8 @@ impl<'a> Chat<'a> {
|
|||||||
msg.text.as_ref().map_or("", String::as_str),
|
msg.text.as_ref().map_or("", String::as_str),
|
||||||
msg.param.to_string(),
|
msg.param.to_string(),
|
||||||
msg.hidden,
|
msg.hidden,
|
||||||
to_string(new_in_reply_to),
|
new_in_reply_to,
|
||||||
to_string(new_references),
|
new_references,
|
||||||
location_id as i32,
|
location_id as i32,
|
||||||
]
|
]
|
||||||
).is_ok() {
|
).is_ok() {
|
||||||
@@ -515,7 +455,7 @@ impl<'a> Chat<'a> {
|
|||||||
&context.sql,
|
&context.sql,
|
||||||
"msgs",
|
"msgs",
|
||||||
"rfc724_mid",
|
"rfc724_mid",
|
||||||
as_str(new_rfc724_mid),
|
new_rfc724_mid,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
@@ -525,18 +465,9 @@ impl<'a> Chat<'a> {
|
|||||||
self.id,
|
self.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
error!(context, 0, "Cannot send message, not configured.",);
|
error!(context, 0, "Cannot send message, not configured.",);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
free(parent_rfc724_mid as *mut libc::c_void);
|
|
||||||
free(parent_in_reply_to as *mut libc::c_void);
|
|
||||||
free(parent_references as *mut libc::c_void);
|
|
||||||
free(new_rfc724_mid as *mut libc::c_void);
|
|
||||||
free(new_in_reply_to as *mut libc::c_void);
|
|
||||||
free(new_references as *mut libc::c_void);
|
|
||||||
|
|
||||||
Ok(msg_id)
|
Ok(msg_id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ pub unsafe fn dc_mimefactory_load_msg(
|
|||||||
for row in rows {
|
for row in rows {
|
||||||
let (authname, addr) = row?;
|
let (authname, addr) = row?;
|
||||||
let addr_c = addr.strdup();
|
let addr_c = addr.strdup();
|
||||||
if clist_search_string_nocase(factory.recipients_addr, addr_c) == 0 {
|
if !clist_search_string_nocase(factory.recipients_addr, addr_c) {
|
||||||
clist_insert_after(
|
clist_insert_after(
|
||||||
factory.recipients_names,
|
factory.recipients_names,
|
||||||
(*factory.recipients_names).last,
|
(*factory.recipients_names).last,
|
||||||
@@ -183,7 +183,7 @@ pub unsafe fn dc_mimefactory_load_msg(
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if !email_to_remove.is_empty() && email_to_remove != self_addr {
|
if !email_to_remove.is_empty() && email_to_remove != self_addr {
|
||||||
if clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) == 0 {
|
if !clist_search_string_nocase(factory.recipients_addr, email_to_remove_c) {
|
||||||
clist_insert_after(
|
clist_insert_after(
|
||||||
factory.recipients_names,
|
factory.recipients_names,
|
||||||
(*factory.recipients_names).last,
|
(*factory.recipients_names).last,
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ dc_mimeparser_t has no deep dependencies to Context or to the database
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct dc_mimepart_t {
|
pub struct dc_mimepart_t {
|
||||||
pub type_0: Viewtype,
|
pub type_0: Viewtype,
|
||||||
pub is_meta: libc::c_int,
|
pub is_meta: bool,
|
||||||
pub int_mimetype: libc::c_int,
|
pub int_mimetype: libc::c_int,
|
||||||
pub msg: Option<String>,
|
pub msg: Option<String>,
|
||||||
pub msg_raw: *mut libc::c_char,
|
pub msg_raw: Option<String>,
|
||||||
pub bytes: libc::c_int,
|
pub bytes: libc::c_int,
|
||||||
pub param: Params,
|
pub param: Params,
|
||||||
}
|
}
|
||||||
@@ -50,11 +50,11 @@ pub struct dc_mimeparser_t<'a> {
|
|||||||
pub header: HashMap<String, *mut mailimf_field>,
|
pub header: HashMap<String, *mut mailimf_field>,
|
||||||
pub header_root: *mut mailimf_fields,
|
pub header_root: *mut mailimf_fields,
|
||||||
pub header_protected: *mut mailimf_fields,
|
pub header_protected: *mut mailimf_fields,
|
||||||
pub subject: *mut libc::c_char,
|
pub subject: Option<String>,
|
||||||
pub is_send_by_messenger: bool,
|
pub is_send_by_messenger: bool,
|
||||||
pub decrypting_failed: libc::c_int,
|
pub decrypting_failed: bool,
|
||||||
pub e2ee_helper: E2eeHelper,
|
pub e2ee_helper: E2eeHelper,
|
||||||
pub is_forwarded: libc::c_int,
|
pub is_forwarded: bool,
|
||||||
pub context: &'a Context,
|
pub context: &'a Context,
|
||||||
pub reports: Vec<*mut mailmime>,
|
pub reports: Vec<*mut mailmime>,
|
||||||
pub is_system_message: libc::c_int,
|
pub is_system_message: libc::c_int,
|
||||||
@@ -69,11 +69,11 @@ pub fn dc_mimeparser_new(context: &Context) -> dc_mimeparser_t {
|
|||||||
header: Default::default(),
|
header: Default::default(),
|
||||||
header_root: std::ptr::null_mut(),
|
header_root: std::ptr::null_mut(),
|
||||||
header_protected: std::ptr::null_mut(),
|
header_protected: std::ptr::null_mut(),
|
||||||
subject: std::ptr::null_mut(),
|
subject: None,
|
||||||
is_send_by_messenger: false,
|
is_send_by_messenger: false,
|
||||||
decrypting_failed: 0,
|
decrypting_failed: false,
|
||||||
e2ee_helper: Default::default(),
|
e2ee_helper: Default::default(),
|
||||||
is_forwarded: 0,
|
is_forwarded: false,
|
||||||
context,
|
context,
|
||||||
reports: Vec::new(),
|
reports: Vec::new(),
|
||||||
is_system_message: 0,
|
is_system_message: 0,
|
||||||
@@ -87,10 +87,7 @@ pub unsafe fn dc_mimeparser_unref(mimeparser: &mut dc_mimeparser_t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn dc_mimeparser_empty(mimeparser: &mut dc_mimeparser_t) {
|
unsafe fn dc_mimeparser_empty(mimeparser: &mut dc_mimeparser_t) {
|
||||||
for part in mimeparser.parts.drain(..) {
|
mimeparser.parts = vec![];
|
||||||
dc_mimepart_unref(part);
|
|
||||||
}
|
|
||||||
assert!(mimeparser.parts.is_empty());
|
|
||||||
mimeparser.header_root = ptr::null_mut();
|
mimeparser.header_root = ptr::null_mut();
|
||||||
mimeparser.header.clear();
|
mimeparser.header.clear();
|
||||||
if !mimeparser.header_protected.is_null() {
|
if !mimeparser.header_protected.is_null() {
|
||||||
@@ -99,26 +96,20 @@ unsafe fn dc_mimeparser_empty(mimeparser: &mut dc_mimeparser_t) {
|
|||||||
}
|
}
|
||||||
mimeparser.is_send_by_messenger = false;
|
mimeparser.is_send_by_messenger = false;
|
||||||
mimeparser.is_system_message = 0i32;
|
mimeparser.is_system_message = 0i32;
|
||||||
free(mimeparser.subject as *mut libc::c_void);
|
mimeparser.subject = None;
|
||||||
mimeparser.subject = ptr::null_mut();
|
|
||||||
if !mimeparser.mimeroot.is_null() {
|
if !mimeparser.mimeroot.is_null() {
|
||||||
mailmime_free(mimeparser.mimeroot);
|
mailmime_free(mimeparser.mimeroot);
|
||||||
mimeparser.mimeroot = ptr::null_mut()
|
mimeparser.mimeroot = ptr::null_mut()
|
||||||
}
|
}
|
||||||
mimeparser.is_forwarded = 0i32;
|
mimeparser.is_forwarded = false;
|
||||||
mimeparser.reports.clear();
|
mimeparser.reports.clear();
|
||||||
mimeparser.decrypting_failed = 0i32;
|
mimeparser.decrypting_failed = false;
|
||||||
mimeparser.e2ee_helper.thanks();
|
mimeparser.e2ee_helper.thanks();
|
||||||
|
|
||||||
mimeparser.location_kml = None;
|
mimeparser.location_kml = None;
|
||||||
mimeparser.message_kml = None;
|
mimeparser.message_kml = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn dc_mimepart_unref(mut mimepart: dc_mimepart_t) {
|
|
||||||
mimepart.msg = None;
|
|
||||||
free(mimepart.msg_raw as *mut libc::c_void);
|
|
||||||
mimepart.msg_raw = ptr::null_mut();
|
|
||||||
}
|
|
||||||
const DC_MIMETYPE_AC_SETUP_FILE: i32 = 111;
|
const DC_MIMETYPE_AC_SETUP_FILE: i32 = 111;
|
||||||
|
|
||||||
pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_mimeparser_t<'a> {
|
pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_mimeparser_t<'a> {
|
||||||
@@ -141,7 +132,15 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
dc_mimeparser_parse_mime_recursive(mimeparser_ref, mimeparser_ref.mimeroot);
|
dc_mimeparser_parse_mime_recursive(mimeparser_ref, mimeparser_ref.mimeroot);
|
||||||
let field: *mut mailimf_field = dc_mimeparser_lookup_field(&mimeparser, "Subject");
|
let field: *mut mailimf_field = dc_mimeparser_lookup_field(&mimeparser, "Subject");
|
||||||
if !field.is_null() && (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
|
if !field.is_null() && (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
|
||||||
mimeparser.subject = dc_decode_header_words((*(*field).fld_data.fld_subject).sbj_value)
|
let decoded = dc_decode_header_words((*(*field).fld_data.fld_subject).sbj_value);
|
||||||
|
if decoded.is_null()
|
||||||
|
/* XXX: can it happen? */
|
||||||
|
{
|
||||||
|
mimeparser.subject = None
|
||||||
|
} else {
|
||||||
|
mimeparser.subject = Some(to_string(decoded));
|
||||||
|
free(decoded.cast());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !dc_mimeparser_lookup_optional_field(&mut mimeparser, "Chat-Version").is_null() {
|
if !dc_mimeparser_lookup_optional_field(&mut mimeparser, "Chat-Version").is_null() {
|
||||||
mimeparser.is_send_by_messenger = true
|
mimeparser.is_send_by_messenger = true
|
||||||
@@ -169,8 +168,7 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i != mimeparser.parts.len() {
|
while i != mimeparser.parts.len() {
|
||||||
if mimeparser.parts[i].int_mimetype != 111 {
|
if mimeparser.parts[i].int_mimetype != 111 {
|
||||||
let part = mimeparser.parts.remove(i);
|
mimeparser.parts.remove(i);
|
||||||
dc_mimepart_unref(part);
|
|
||||||
} else {
|
} else {
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
@@ -196,7 +194,7 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
if mimeparser.parts.len() >= 2 {
|
if mimeparser.parts.len() >= 2 {
|
||||||
let imgpart = &mut mimeparser.parts[1];
|
let imgpart = &mut mimeparser.parts[1];
|
||||||
if imgpart.type_0 == Viewtype::Image {
|
if imgpart.type_0 == Viewtype::Image {
|
||||||
imgpart.is_meta = 1i32
|
imgpart.is_meta = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,7 +210,7 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
|| filepart.type_0 == Viewtype::Voice
|
|| filepart.type_0 == Viewtype::Voice
|
||||||
|| filepart.type_0 == Viewtype::Video
|
|| filepart.type_0 == Viewtype::Video
|
||||||
|| filepart.type_0 == Viewtype::File)
|
|| filepart.type_0 == Viewtype::File)
|
||||||
&& 0 == filepart.is_meta
|
&& !filepart.is_meta
|
||||||
};
|
};
|
||||||
|
|
||||||
if need_drop {
|
if need_drop {
|
||||||
@@ -225,42 +223,37 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
mimeparser.parts[0].msg = None;
|
mimeparser.parts[0].msg = None;
|
||||||
|
|
||||||
// swap new with old
|
// swap new with old
|
||||||
let old = std::mem::replace(&mut mimeparser.parts[0], filepart);
|
std::mem::replace(&mut mimeparser.parts[0], filepart);
|
||||||
|
|
||||||
// unref old one
|
|
||||||
dc_mimepart_unref(old);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !mimeparser.subject.is_null() {
|
if let Some(ref subject) = mimeparser.subject {
|
||||||
let mut prepend_subject: libc::c_int = 1i32;
|
let mut prepend_subject: libc::c_int = 1i32;
|
||||||
if 0 == mimeparser.decrypting_failed {
|
if !mimeparser.decrypting_failed {
|
||||||
let p: *mut libc::c_char = strchr(mimeparser.subject, ':' as i32);
|
let colon = subject.find(':');
|
||||||
if p.wrapping_offset_from(mimeparser.subject) == 2
|
if colon == Some(2)
|
||||||
|| p.wrapping_offset_from(mimeparser.subject) == 3
|
|| colon == Some(3)
|
||||||
|| mimeparser.is_send_by_messenger
|
|| mimeparser.is_send_by_messenger
|
||||||
|| !strstr(
|
|| subject.contains("Chat:")
|
||||||
mimeparser.subject,
|
|
||||||
b"Chat:\x00" as *const u8 as *const libc::c_char,
|
|
||||||
)
|
|
||||||
.is_null()
|
|
||||||
{
|
{
|
||||||
prepend_subject = 0i32
|
prepend_subject = 0i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if 0 != prepend_subject {
|
if 0 != prepend_subject {
|
||||||
let subj: *mut libc::c_char = dc_strdup(mimeparser.subject);
|
let subj = if let Some(n) = subject.find('[') {
|
||||||
let p_0: *mut libc::c_char = strchr(subj, '[' as i32);
|
&subject[0..n]
|
||||||
if !p_0.is_null() {
|
} else {
|
||||||
*p_0 = 0i32 as libc::c_char
|
subject
|
||||||
}
|
}
|
||||||
dc_trim(subj);
|
.trim();
|
||||||
if 0 != *subj.offset(0isize) {
|
|
||||||
|
if !subj.is_empty() {
|
||||||
|
let subj_c = CString::yolo(subj);
|
||||||
for part in mimeparser.parts.iter_mut() {
|
for part in mimeparser.parts.iter_mut() {
|
||||||
if part.type_0 == Viewtype::Text {
|
if part.type_0 == Viewtype::Text {
|
||||||
let msg_c = part.msg.as_ref().unwrap().strdup();
|
let msg_c = part.msg.as_ref().unwrap().strdup();
|
||||||
let new_txt: *mut libc::c_char = dc_mprintf(
|
let new_txt: *mut libc::c_char = dc_mprintf(
|
||||||
b"%s \xe2\x80\x93 %s\x00" as *const u8 as *const libc::c_char,
|
b"%s \xe2\x80\x93 %s\x00" as *const u8 as *const libc::c_char,
|
||||||
subj,
|
subj_c.as_ptr(),
|
||||||
msg_c,
|
msg_c,
|
||||||
);
|
);
|
||||||
free(msg_c.cast());
|
free(msg_c.cast());
|
||||||
@@ -270,10 +263,9 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(subj as *mut libc::c_void);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if 0 != mimeparser.is_forwarded {
|
if mimeparser.is_forwarded {
|
||||||
for part in mimeparser.parts.iter_mut() {
|
for part in mimeparser.parts.iter_mut() {
|
||||||
part.param.set_int(Param::Forwarded, 1);
|
part.param.set_int(Param::Forwarded, 1);
|
||||||
}
|
}
|
||||||
@@ -301,7 +293,7 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if 0 == mimeparser.decrypting_failed {
|
if !mimeparser.decrypting_failed {
|
||||||
let dn_field: *const mailimf_optional_field = dc_mimeparser_lookup_optional_field(
|
let dn_field: *const mailimf_optional_field = dc_mimeparser_lookup_optional_field(
|
||||||
&mimeparser,
|
&mimeparser,
|
||||||
"Chat-Disposition-Notification-To",
|
"Chat-Disposition-Notification-To",
|
||||||
@@ -350,10 +342,12 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
if dc_mimeparser_get_last_nonmeta(&mut mimeparser).is_none() && mimeparser.reports.is_empty() {
|
if dc_mimeparser_get_last_nonmeta(&mut mimeparser).is_none() && mimeparser.reports.is_empty() {
|
||||||
let mut part_5 = dc_mimepart_new();
|
let mut part_5 = dc_mimepart_new();
|
||||||
part_5.type_0 = Viewtype::Text;
|
part_5.type_0 = Viewtype::Text;
|
||||||
if !mimeparser.subject.is_null() && !mimeparser.is_send_by_messenger {
|
|
||||||
part_5.msg = Some(to_string(mimeparser.subject));
|
|
||||||
} else {
|
|
||||||
part_5.msg = Some("".into());
|
part_5.msg = Some("".into());
|
||||||
|
|
||||||
|
if let Some(ref subject) = mimeparser.subject {
|
||||||
|
if !mimeparser.is_send_by_messenger {
|
||||||
|
part_5.msg = Some(subject.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mimeparser.parts.push(part_5);
|
mimeparser.parts.push(part_5);
|
||||||
};
|
};
|
||||||
@@ -366,10 +360,10 @@ pub unsafe fn dc_mimeparser_parse<'a>(context: &'a Context, body: &[u8]) -> dc_m
|
|||||||
unsafe fn dc_mimepart_new() -> dc_mimepart_t {
|
unsafe fn dc_mimepart_new() -> dc_mimepart_t {
|
||||||
dc_mimepart_t {
|
dc_mimepart_t {
|
||||||
type_0: Viewtype::Unknown,
|
type_0: Viewtype::Unknown,
|
||||||
is_meta: 0,
|
is_meta: false,
|
||||||
int_mimetype: 0,
|
int_mimetype: 0,
|
||||||
msg: None,
|
msg: None,
|
||||||
msg_raw: std::ptr::null_mut(),
|
msg_raw: None,
|
||||||
bytes: 0,
|
bytes: 0,
|
||||||
param: Params::new(),
|
param: Params::new(),
|
||||||
}
|
}
|
||||||
@@ -378,11 +372,7 @@ unsafe fn dc_mimepart_new() -> dc_mimepart_t {
|
|||||||
pub fn dc_mimeparser_get_last_nonmeta<'a>(
|
pub fn dc_mimeparser_get_last_nonmeta<'a>(
|
||||||
mimeparser: &'a mut dc_mimeparser_t,
|
mimeparser: &'a mut dc_mimeparser_t,
|
||||||
) -> Option<&'a mut dc_mimepart_t> {
|
) -> Option<&'a mut dc_mimepart_t> {
|
||||||
mimeparser
|
mimeparser.parts.iter_mut().rev().find(|part| !part.is_meta)
|
||||||
.parts
|
|
||||||
.iter_mut()
|
|
||||||
.rev()
|
|
||||||
.find(|part| part.is_meta == 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*the result must be freed*/
|
/*the result must be freed*/
|
||||||
@@ -596,12 +586,12 @@ unsafe fn dc_mimeparser_parse_mime_recursive(
|
|||||||
.stock_str(StockMessage::CantDecryptMsgBody);
|
.stock_str(StockMessage::CantDecryptMsgBody);
|
||||||
|
|
||||||
let txt = format!("[{}]", msg_body);
|
let txt = format!("[{}]", msg_body);
|
||||||
part.msg_raw = txt.strdup();
|
part.msg_raw = Some(txt.clone());
|
||||||
part.msg = Some(txt);
|
part.msg = Some(txt);
|
||||||
|
|
||||||
mimeparser.parts.push(part);
|
mimeparser.parts.push(part);
|
||||||
any_part_added = 1i32;
|
any_part_added = 1i32;
|
||||||
mimeparser.decrypting_failed = 1i32
|
mimeparser.decrypting_failed = true;
|
||||||
}
|
}
|
||||||
46 => {
|
46 => {
|
||||||
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
|
cur = (*(*mime).mm_data.mm_multipart.mm_mp_list).first;
|
||||||
@@ -1124,13 +1114,18 @@ unsafe fn dc_mimeparser_add_single_part_if_known(
|
|||||||
part.type_0 = Viewtype::Text;
|
part.type_0 = Viewtype::Text;
|
||||||
part.int_mimetype = mime_type;
|
part.int_mimetype = mime_type;
|
||||||
part.msg = Some(simplified_txt);
|
part.msg = Some(simplified_txt);
|
||||||
part.msg_raw =
|
part.msg_raw = {
|
||||||
|
let raw_c =
|
||||||
strndup(decoded_data, decoded_data_bytes as libc::c_ulong);
|
strndup(decoded_data, decoded_data_bytes as libc::c_ulong);
|
||||||
|
let raw = to_string(raw_c);
|
||||||
|
free(raw_c.cast());
|
||||||
|
Some(raw)
|
||||||
|
};
|
||||||
do_add_single_part(mimeparser, part);
|
do_add_single_part(mimeparser, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
if simplifier.unwrap().is_forwarded {
|
if simplifier.unwrap().is_forwarded {
|
||||||
mimeparser.is_forwarded = 1i32
|
mimeparser.is_forwarded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1284,7 +1279,7 @@ unsafe fn dc_mimeparser_add_single_part_if_known(
|
|||||||
mimeparser,
|
mimeparser,
|
||||||
msg_type,
|
msg_type,
|
||||||
mime_type,
|
mime_type,
|
||||||
raw_mime,
|
as_str(raw_mime),
|
||||||
decoded_data,
|
decoded_data,
|
||||||
decoded_data_bytes,
|
decoded_data_bytes,
|
||||||
desired_filename,
|
desired_filename,
|
||||||
@@ -1312,7 +1307,7 @@ unsafe fn do_add_single_file_part(
|
|||||||
parser: &mut dc_mimeparser_t,
|
parser: &mut dc_mimeparser_t,
|
||||||
msg_type: Viewtype,
|
msg_type: Viewtype,
|
||||||
mime_type: libc::c_int,
|
mime_type: libc::c_int,
|
||||||
raw_mime: *const libc::c_char,
|
raw_mime: &str,
|
||||||
decoded_data: *const libc::c_char,
|
decoded_data: *const libc::c_char,
|
||||||
decoded_data_bytes: size_t,
|
decoded_data_bytes: size_t,
|
||||||
desired_filename: *const libc::c_char,
|
desired_filename: *const libc::c_char,
|
||||||
@@ -1338,7 +1333,7 @@ unsafe fn do_add_single_file_part(
|
|||||||
part.int_mimetype = mime_type;
|
part.int_mimetype = mime_type;
|
||||||
part.bytes = decoded_data_bytes as libc::c_int;
|
part.bytes = decoded_data_bytes as libc::c_int;
|
||||||
part.param.set(Param::File, as_str(pathNfilename));
|
part.param.set(Param::File, as_str(pathNfilename));
|
||||||
part.param.set(Param::MimeType, as_str(raw_mime));
|
part.param.set(Param::MimeType, raw_mime);
|
||||||
if mime_type == 80 {
|
if mime_type == 80 {
|
||||||
assert!(!decoded_data.is_null(), "invalid image data");
|
assert!(!decoded_data.is_null(), "invalid image data");
|
||||||
let data = std::slice::from_raw_parts(
|
let data = std::slice::from_raw_parts(
|
||||||
@@ -1655,9 +1650,7 @@ pub unsafe fn dc_mimeparser_repl_msg_by_error(
|
|||||||
let part = &mut mimeparser.parts[0];
|
let part = &mut mimeparser.parts[0];
|
||||||
part.type_0 = Viewtype::Text;
|
part.type_0 = Viewtype::Text;
|
||||||
part.msg = Some(format!("[{}]", to_string(error_msg)));
|
part.msg = Some(format!("[{}]", to_string(error_msg)));
|
||||||
for part in mimeparser.parts.drain(1..) {
|
mimeparser.parts.truncate(1);
|
||||||
dc_mimepart_unref(part);
|
|
||||||
}
|
|
||||||
assert_eq!(mimeparser.parts.len(), 1);
|
assert_eq!(mimeparser.parts.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1814,10 +1807,7 @@ mod tests {
|
|||||||
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
|
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
|
||||||
let mut mimeparser = dc_mimeparser_parse(&context.ctx, &raw[..]);
|
let mut mimeparser = dc_mimeparser_parse(&context.ctx, &raw[..]);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(mimeparser.subject, Some("inner-subject".into()));
|
||||||
&to_string(mimeparser.subject as *const libc::c_char),
|
|
||||||
"inner-subject",
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut of: *mut mailimf_optional_field =
|
let mut of: *mut mailimf_optional_field =
|
||||||
dc_mimeparser_lookup_optional_field(&mimeparser, "X-Special-A");
|
dc_mimeparser_lookup_optional_field(&mimeparser, "X-Special-A");
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::ffi::CString;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use itertools::join;
|
use itertools::join;
|
||||||
@@ -625,7 +626,7 @@ unsafe fn add_parts(
|
|||||||
|mut stmt, conn| {
|
|mut stmt, conn| {
|
||||||
for i in 0..icnt {
|
for i in 0..icnt {
|
||||||
let part = &mut mime_parser.parts[i];
|
let part = &mut mime_parser.parts[i];
|
||||||
if part.is_meta != 0 {
|
if part.is_meta {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,14 +642,18 @@ unsafe fn add_parts(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if part.type_0 == Viewtype::Text {
|
if part.type_0 == Viewtype::Text {
|
||||||
|
let msg_raw = CString::yolo(part.msg_raw.as_ref().unwrap().clone());
|
||||||
|
let subject_c = CString::yolo(
|
||||||
|
mime_parser
|
||||||
|
.subject
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.unwrap_or("".into()),
|
||||||
|
);
|
||||||
txt_raw = dc_mprintf(
|
txt_raw = dc_mprintf(
|
||||||
b"%s\n\n%s\x00" as *const u8 as *const libc::c_char,
|
b"%s\n\n%s\x00" as *const u8 as *const libc::c_char,
|
||||||
if !mime_parser.subject.is_null() {
|
subject_c.as_ptr(),
|
||||||
mime_parser.subject
|
msg_raw.as_ptr(),
|
||||||
} else {
|
|
||||||
b"\x00" as *const u8 as *const libc::c_char
|
|
||||||
},
|
|
||||||
part.msg_raw,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if 0 != mime_parser.is_system_message {
|
if 0 != mime_parser.is_system_message {
|
||||||
@@ -1568,8 +1573,8 @@ unsafe fn create_or_lookup_adhoc_group(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use subject as initial chat name
|
// use subject as initial chat name
|
||||||
if !mime_parser.subject.is_null() && 0 != *mime_parser.subject.offset(0isize) as libc::c_int {
|
if let Some(subject) = mime_parser.subject.as_ref().filter(|s| !s.is_empty()) {
|
||||||
grpname = dc_strdup(mime_parser.subject)
|
grpname = subject.strdup();
|
||||||
} else {
|
} else {
|
||||||
grpname = context
|
grpname = context
|
||||||
.stock_string_repl_int(StockMessage::Member, member_ids.len() as libc::c_int)
|
.stock_string_repl_int(StockMessage::Member, member_ids.len() as libc::c_int)
|
||||||
|
|||||||
@@ -435,21 +435,10 @@ pub unsafe fn clist_free_content(haystack: *const clist) {
|
|||||||
pub unsafe fn clist_search_string_nocase(
|
pub unsafe fn clist_search_string_nocase(
|
||||||
haystack: *const clist,
|
haystack: *const clist,
|
||||||
needle: *const libc::c_char,
|
needle: *const libc::c_char,
|
||||||
) -> libc::c_int {
|
) -> bool {
|
||||||
let mut iter = (*haystack).first;
|
(&*haystack)
|
||||||
|
.into_iter()
|
||||||
while !iter.is_null() {
|
.any(|data| strcasecmp(data.cast(), needle) == 0)
|
||||||
if strcasecmp((*iter).data as *const libc::c_char, needle) == 0 {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
iter = if !iter.is_null() {
|
|
||||||
(*iter).next
|
|
||||||
} else {
|
|
||||||
ptr::null_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* date/time tools */
|
/* date/time tools */
|
||||||
@@ -630,6 +619,21 @@ pub unsafe fn dc_create_outgoing_rfc724_mid(
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate globally-unique message-id for a new outgoing message.
|
||||||
|
///
|
||||||
|
/// Note: Do not add a counter or any private data as as this may give
|
||||||
|
/// unneeded information to the receiver
|
||||||
|
pub fn dc_create_outgoing_rfc724_mid_safe(grpid: Option<&str>, from_addr: &str) -> String {
|
||||||
|
let hostname = from_addr
|
||||||
|
.find('@')
|
||||||
|
.map(|k| &from_addr[k..])
|
||||||
|
.unwrap_or("@nohost");
|
||||||
|
match grpid {
|
||||||
|
Some(grpid) => format!("Gr.{}.{}{}", grpid, dc_create_id(), hostname),
|
||||||
|
None => format!("Mr.{}.{}{}", dc_create_id(), dc_create_id(), hostname),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract the group id (grpid) from a message id (mid)
|
/// Extract the group id (grpid) from a message id (mid)
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|||||||
@@ -692,9 +692,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
/* unrecoverable */
|
/* unrecoverable */
|
||||||
if clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr)
|
if !clist_search_string_nocase(mimefactory.recipients_addr, mimefactory.from_addr) {
|
||||||
== 0i32
|
|
||||||
{
|
|
||||||
clist_insert_after(
|
clist_insert_after(
|
||||||
mimefactory.recipients_names,
|
mimefactory.recipients_names,
|
||||||
(*mimefactory.recipients_names).last,
|
(*mimefactory.recipients_names).last,
|
||||||
|
|||||||
Reference in New Issue
Block a user