mirror of
https://github.com/chatmail/core.git
synced 2026-05-14 20:36:30 +03:00
Merge remote-tracking branch 'origin/master' into feat/async-jobs
This commit is contained in:
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -681,7 +681,7 @@ dependencies = [
|
|||||||
"lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)",
|
"lettre_email 0.9.2 (git+https://github.com/deltachat/lettre)",
|
||||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mailparse 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mailparse 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -1504,7 +1504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mailparse"
|
name = "mailparse"
|
||||||
version = "0.10.4"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -3332,7 +3332,7 @@ dependencies = [
|
|||||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||||
"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
|
"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
|
||||||
"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
|
"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
|
||||||
"checksum mailparse 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6c03df7fe4bab038aaa2f313baae7600de0afd606f8244860801c46f53babdd8"
|
"checksum mailparse 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7181507a68fef921f011b0c0f143efca20871da5ab3963bdc064537278469cd2"
|
||||||
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||||
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||||
"checksum md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8"
|
"checksum md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8"
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ bitflags = "1.1.0"
|
|||||||
debug_stub_derive = "0.3.0"
|
debug_stub_derive = "0.3.0"
|
||||||
sanitize-filename = "0.2.1"
|
sanitize-filename = "0.2.1"
|
||||||
stop-token = { version = "0.1.1", features = ["unstable"] }
|
stop-token = { version = "0.1.1", features = ["unstable"] }
|
||||||
mailparse = "0.10.2"
|
mailparse = "0.12.0"
|
||||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||||
native-tls = "0.2.3"
|
native-tls = "0.2.3"
|
||||||
image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] }
|
image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] }
|
||||||
|
|||||||
@@ -380,6 +380,14 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* - `save_mime_headers` = 1=save mime headers
|
* - `save_mime_headers` = 1=save mime headers
|
||||||
* and make dc_get_mime_headers() work for subsequent calls,
|
* and make dc_get_mime_headers() work for subsequent calls,
|
||||||
* 0=do not save mime headers (default)
|
* 0=do not save mime headers (default)
|
||||||
|
* - `delete_device_after` = 0=do not delete messages from device automatically (default),
|
||||||
|
* >=1=seconds, after which messages are deleted automatically from the device.
|
||||||
|
* Messages in the "saved messages" chat (see dc_chat_is_self_talk()) are skipped.
|
||||||
|
* Messages are deleted whether they were seen or not, the UI should clearly point that out.
|
||||||
|
* - `delete_server_after` = 0=do not delete messages from server automatically (default),
|
||||||
|
* >=1=seconds, after which messages are deleted automatically from the server.
|
||||||
|
* "Saved messages" are deleted from the server as well as
|
||||||
|
* emails matching the `show_emails` settings above, the UI should clearly point that out.
|
||||||
*
|
*
|
||||||
* If you want to retrieve a value, use dc_get_config().
|
* If you want to retrieve a value, use dc_get_config().
|
||||||
*
|
*
|
||||||
@@ -1012,6 +1020,21 @@ int dc_get_msg_cnt (dc_context_t* context, uint32_t ch
|
|||||||
int dc_get_fresh_msg_cnt (dc_context_t* context, uint32_t chat_id);
|
int dc_get_fresh_msg_cnt (dc_context_t* context, uint32_t chat_id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate the number of messages that will be deleted
|
||||||
|
* by the dc_set_config()-options `delete_device_after` or `delete_server_after`.
|
||||||
|
* This is typically used to show the estimated impact to the user before actually enabling ephemeral messages.
|
||||||
|
*
|
||||||
|
* @param context The context object as returned from dc_context_new().
|
||||||
|
* @param from_server 1=Estimate deletion count for server, 0=Estimate deletion count for device
|
||||||
|
* @param seconds Count messages older than the given number of seconds.
|
||||||
|
* @return Number of messages that are older than the given number of seconds.
|
||||||
|
* This includes emails downloaded due to the `show_emails` option.
|
||||||
|
* Messages in the "saved messages" folder are not counted as they will not be deleted automatically.
|
||||||
|
*/
|
||||||
|
int dc_estimate_deletion_cnt (dc_context_t* context, int from_server, int64_t seconds);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the message IDs of all _fresh_ messages of any chat.
|
* Returns the message IDs of all _fresh_ messages of any chat.
|
||||||
* Typically used for implementing notification summaries.
|
* Typically used for implementing notification summaries.
|
||||||
@@ -1375,8 +1398,9 @@ char* dc_get_mime_headers (dc_context_t* context, uint32_t ms
|
|||||||
*/
|
*/
|
||||||
void dc_delete_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt);
|
void dc_delete_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt);
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Empty IMAP server folder: delete all messages.
|
* Empty IMAP server folder: delete all messages.
|
||||||
|
* Deprecated, use dc_set_config() with the key "delete_server_after" instead.
|
||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object as created by dc_context_new()
|
* @param context The context object as created by dc_context_new()
|
||||||
@@ -3843,28 +3867,8 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/**
|
#define DC_EMPTY_MVBOX 0x01 // Deprecated, flag for dc_empty_server(): Clear all mvbox messages
|
||||||
* @defgroup DC_EMPTY DC_EMPTY
|
#define DC_EMPTY_INBOX 0x02 // Deprecated, flag for dc_empty_server(): Clear all INBOX messages
|
||||||
*
|
|
||||||
* These constants configure emptying imap folders with dc_empty_server()
|
|
||||||
*
|
|
||||||
* @addtogroup DC_EMPTY
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all mvbox messages.
|
|
||||||
*/
|
|
||||||
#define DC_EMPTY_MVBOX 0x01
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all INBOX messages.
|
|
||||||
*/
|
|
||||||
#define DC_EMPTY_INBOX 0x02
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -401,7 +401,7 @@ pub unsafe extern "C" fn dc_set_config_from_qr(
|
|||||||
pub unsafe extern "C" fn dc_get_info(context: *mut dc_context_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_get_info(context: *mut dc_context_t) -> *mut libc::c_char {
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_get_info()");
|
eprintln!("ignoring careless call to dc_get_info()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_context = &*context;
|
let ffi_context = &*context;
|
||||||
|
|
||||||
@@ -1061,6 +1061,25 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt(
|
|||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_estimate_deletion_cnt(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
from_server: libc::c_int,
|
||||||
|
seconds: i64,
|
||||||
|
) -> libc::c_int {
|
||||||
|
if context.is_null() || seconds < 0 {
|
||||||
|
eprintln!("ignoring careless call to dc_estimate_deletion_cnt()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let ffi_context = &*context;
|
||||||
|
ffi_context
|
||||||
|
.with_inner(|ctx| {
|
||||||
|
message::estimate_deletion_cnt(ctx, from_server != 0, seconds).unwrap_or(0)
|
||||||
|
as libc::c_int
|
||||||
|
})
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_get_fresh_msgs(
|
pub unsafe extern "C" fn dc_get_fresh_msgs(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -1488,7 +1507,7 @@ pub unsafe extern "C" fn dc_get_msg_info(
|
|||||||
) -> *mut libc::c_char {
|
) -> *mut libc::c_char {
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_get_msg_info()");
|
eprintln!("ignoring careless call to dc_get_msg_info()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_context = &*context;
|
let ffi_context = &*context;
|
||||||
|
|
||||||
@@ -2486,7 +2505,7 @@ pub unsafe extern "C" fn dc_chat_get_type(chat: *mut dc_chat_t) -> libc::c_int {
|
|||||||
pub unsafe extern "C" fn dc_chat_get_name(chat: *mut dc_chat_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_chat_get_name(chat: *mut dc_chat_t) -> *mut libc::c_char {
|
||||||
if chat.is_null() {
|
if chat.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_chat_get_name()");
|
eprintln!("ignoring careless call to dc_chat_get_name()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_chat = &*chat;
|
let ffi_chat = &*chat;
|
||||||
ffi_chat.chat.get_name().strdup()
|
ffi_chat.chat.get_name().strdup()
|
||||||
@@ -2810,7 +2829,7 @@ pub unsafe extern "C" fn dc_msg_get_sort_timestamp(msg: *mut dc_msg_t) -> i64 {
|
|||||||
pub unsafe extern "C" fn dc_msg_get_text(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_msg_get_text(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_msg_get_text()");
|
eprintln!("ignoring careless call to dc_msg_get_text()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_msg = &*msg;
|
let ffi_msg = &*msg;
|
||||||
ffi_msg.message.get_text().unwrap_or_default().strdup()
|
ffi_msg.message.get_text().unwrap_or_default().strdup()
|
||||||
@@ -2829,8 +2848,7 @@ pub unsafe extern "C" fn dc_msg_get_file(msg: *mut dc_msg_t) -> *mut libc::c_cha
|
|||||||
ffi_msg
|
ffi_msg
|
||||||
.message
|
.message
|
||||||
.get_file(ctx)
|
.get_file(ctx)
|
||||||
.and_then(|p| p.to_c_string().ok())
|
.map(|p| p.strdup())
|
||||||
.map(|cs| dc_strdup(cs.as_ptr()))
|
|
||||||
.unwrap_or_else(|| "".strdup())
|
.unwrap_or_else(|| "".strdup())
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|_| "".strdup())
|
.unwrap_or_else(|_| "".strdup())
|
||||||
@@ -2840,7 +2858,7 @@ pub unsafe extern "C" fn dc_msg_get_file(msg: *mut dc_msg_t) -> *mut libc::c_cha
|
|||||||
pub unsafe extern "C" fn dc_msg_get_filename(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_msg_get_filename(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_msg_get_filename()");
|
eprintln!("ignoring careless call to dc_msg_get_filename()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_msg = &*msg;
|
let ffi_msg = &*msg;
|
||||||
ffi_msg.message.get_filename().unwrap_or_default().strdup()
|
ffi_msg.message.get_filename().unwrap_or_default().strdup()
|
||||||
@@ -2850,13 +2868,13 @@ pub unsafe extern "C" fn dc_msg_get_filename(msg: *mut dc_msg_t) -> *mut libc::c
|
|||||||
pub unsafe extern "C" fn dc_msg_get_filemime(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_msg_get_filemime(msg: *mut dc_msg_t) -> *mut libc::c_char {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_msg_get_filemime()");
|
eprintln!("ignoring careless call to dc_msg_get_filemime()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_msg = &*msg;
|
let ffi_msg = &*msg;
|
||||||
if let Some(x) = ffi_msg.message.get_filemime() {
|
if let Some(x) = ffi_msg.message.get_filemime() {
|
||||||
x.strdup()
|
x.strdup()
|
||||||
} else {
|
} else {
|
||||||
dc_strdup(ptr::null())
|
"".strdup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3180,7 +3198,7 @@ pub unsafe extern "C" fn dc_contact_get_id(contact: *mut dc_contact_t) -> u32 {
|
|||||||
pub unsafe extern "C" fn dc_contact_get_addr(contact: *mut dc_contact_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_contact_get_addr(contact: *mut dc_contact_t) -> *mut libc::c_char {
|
||||||
if contact.is_null() {
|
if contact.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_contact_get_addr()");
|
eprintln!("ignoring careless call to dc_contact_get_addr()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_contact = &*contact;
|
let ffi_contact = &*contact;
|
||||||
ffi_contact.contact.get_addr().strdup()
|
ffi_contact.contact.get_addr().strdup()
|
||||||
@@ -3190,7 +3208,7 @@ pub unsafe extern "C" fn dc_contact_get_addr(contact: *mut dc_contact_t) -> *mut
|
|||||||
pub unsafe extern "C" fn dc_contact_get_name(contact: *mut dc_contact_t) -> *mut libc::c_char {
|
pub unsafe extern "C" fn dc_contact_get_name(contact: *mut dc_contact_t) -> *mut libc::c_char {
|
||||||
if contact.is_null() {
|
if contact.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_contact_get_name()");
|
eprintln!("ignoring careless call to dc_contact_get_name()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_contact = &*contact;
|
let ffi_contact = &*contact;
|
||||||
ffi_contact.contact.get_name().strdup()
|
ffi_contact.contact.get_name().strdup()
|
||||||
@@ -3202,7 +3220,7 @@ pub unsafe extern "C" fn dc_contact_get_display_name(
|
|||||||
) -> *mut libc::c_char {
|
) -> *mut libc::c_char {
|
||||||
if contact.is_null() {
|
if contact.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_contact_get_display_name()");
|
eprintln!("ignoring careless call to dc_contact_get_display_name()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_contact = &*contact;
|
let ffi_contact = &*contact;
|
||||||
ffi_contact.contact.get_display_name().strdup()
|
ffi_contact.contact.get_display_name().strdup()
|
||||||
@@ -3214,7 +3232,7 @@ pub unsafe extern "C" fn dc_contact_get_name_n_addr(
|
|||||||
) -> *mut libc::c_char {
|
) -> *mut libc::c_char {
|
||||||
if contact.is_null() {
|
if contact.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_contact_get_name_n_addr()");
|
eprintln!("ignoring careless call to dc_contact_get_name_n_addr()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_contact = &*contact;
|
let ffi_contact = &*contact;
|
||||||
ffi_contact.contact.get_name_n_addr().strdup()
|
ffi_contact.contact.get_name_n_addr().strdup()
|
||||||
@@ -3226,7 +3244,7 @@ pub unsafe extern "C" fn dc_contact_get_first_name(
|
|||||||
) -> *mut libc::c_char {
|
) -> *mut libc::c_char {
|
||||||
if contact.is_null() {
|
if contact.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_contact_get_first_name()");
|
eprintln!("ignoring careless call to dc_contact_get_first_name()");
|
||||||
return dc_strdup(ptr::null());
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_contact = &*contact;
|
let ffi_contact = &*contact;
|
||||||
ffi_contact.contact.get_first_name().strdup()
|
ffi_contact.contact.get_first_name().strdup()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::ptr;
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust,norun
|
/// ```rust,norun
|
||||||
/// use deltachat::dc_tools::{dc_strdup, to_string_lossy};
|
/// use crate::string::{dc_strdup, to_string_lossy};
|
||||||
/// unsafe {
|
/// unsafe {
|
||||||
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
/// let str_a = b"foobar\x00" as *const u8 as *const libc::c_char;
|
||||||
/// let str_a_copy = dc_strdup(str_a);
|
/// let str_a_copy = dc_strdup(str_a);
|
||||||
@@ -17,7 +17,7 @@ use std::ptr;
|
|||||||
/// assert_ne!(str_a, str_a_copy);
|
/// assert_ne!(str_a, str_a_copy);
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||||
let ret: *mut libc::c_char;
|
let ret: *mut libc::c_char;
|
||||||
if !s.is_null() {
|
if !s.is_null() {
|
||||||
ret = libc::strdup(s);
|
ret = libc::strdup(s);
|
||||||
@@ -32,7 +32,7 @@ pub unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
|||||||
|
|
||||||
/// Error type for the [OsStrExt] trait
|
/// Error type for the [OsStrExt] trait
|
||||||
#[derive(Debug, Fail, PartialEq)]
|
#[derive(Debug, Fail, PartialEq)]
|
||||||
pub enum CStringError {
|
pub(crate) enum CStringError {
|
||||||
/// The string contains an interior null byte
|
/// The string contains an interior null byte
|
||||||
#[fail(display = "String contains an interior null byte")]
|
#[fail(display = "String contains an interior null byte")]
|
||||||
InteriorNullByte,
|
InteriorNullByte,
|
||||||
@@ -66,7 +66,7 @@ pub enum CStringError {
|
|||||||
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
|
/// let mut c_ptr: *mut libc::c_char = dc_strdup(path_c.as_ptr());
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait OsStrExt {
|
pub(crate) trait OsStrExt {
|
||||||
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
|
/// Convert a [std::ffi::OsStr] to an [std::ffi::CString]
|
||||||
///
|
///
|
||||||
/// This is useful to convert e.g. a [std::path::Path] to
|
/// This is useful to convert e.g. a [std::path::Path] to
|
||||||
@@ -131,15 +131,16 @@ fn os_str_to_c_string_unicode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience methods/associated functions for working with [CString]
|
/// Convenience methods/associated functions for working with [CString]
|
||||||
///
|
trait CStringExt {
|
||||||
/// This is helps transitioning from unsafe code.
|
/// Create a new [CString], best effort
|
||||||
pub trait CStringExt {
|
|
||||||
/// Create a new [CString], yolo style
|
|
||||||
///
|
///
|
||||||
/// This unwrap the result, panicking when there are embedded NULL
|
/// Like the [to_string_lossy] this doesn't give up in the face of
|
||||||
/// bytes.
|
/// bad input (embedded null bytes in this case) instead it does
|
||||||
fn yolo<T: Into<Vec<u8>>>(t: T) -> CString {
|
/// the best it can by stripping the embedded null bytes.
|
||||||
CString::new(t).expect("String contains null byte, can not be CString")
|
fn new_lossy<T: Into<Vec<u8>>>(t: T) -> CString {
|
||||||
|
let mut s = t.into();
|
||||||
|
s.retain(|&c| c != 0);
|
||||||
|
CString::new(s).unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +152,7 @@ impl CStringExt for CString {}
|
|||||||
/// Rust strings to raw C strings. This can be clumsy to do correctly
|
/// Rust strings to raw C strings. This can be clumsy to do correctly
|
||||||
/// and the compiler sometimes allows it in an unsafe way. These
|
/// and the compiler sometimes allows it in an unsafe way. These
|
||||||
/// methods make it more succinct and help you get it right.
|
/// methods make it more succinct and help you get it right.
|
||||||
pub trait StrExt {
|
pub(crate) trait Strdup {
|
||||||
/// Allocate a new raw C `*char` version of this string.
|
/// Allocate a new raw C `*char` version of this string.
|
||||||
///
|
///
|
||||||
/// This allocates a new raw C string which must be freed using
|
/// This allocates a new raw C string which must be freed using
|
||||||
@@ -168,35 +169,44 @@ pub trait StrExt {
|
|||||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<str>> StrExt for T {
|
impl<T: AsRef<str>> Strdup for T {
|
||||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||||
let tmp = CString::yolo(self.as_ref());
|
let tmp = CString::new_lossy(self.as_ref());
|
||||||
|
dc_strdup(tmp.as_ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can not implement for AsRef<OsStr> because we already implement
|
||||||
|
// AsRev<str> and this conflicts. So implement for Path directly.
|
||||||
|
impl Strdup for std::path::Path {
|
||||||
|
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||||
|
let tmp = self.to_c_string().unwrap_or_else(|_| CString::default());
|
||||||
dc_strdup(tmp.as_ptr())
|
dc_strdup(tmp.as_ptr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience methods to turn optional strings into C strings.
|
/// Convenience methods to turn optional strings into C strings.
|
||||||
///
|
///
|
||||||
/// This is the same as the [StrExt] trait but a different trait name
|
/// This is the same as the [Strdup] trait but a different trait name
|
||||||
/// to work around the type system not allowing to implement [StrExt]
|
/// to work around the type system not allowing to implement [Strdup]
|
||||||
/// for `Option<impl StrExt>` When we already have an [StrExt] impl
|
/// for `Option<impl Strdup>` When we already have an [Strdup] impl
|
||||||
/// for `AsRef<&str>`.
|
/// for `AsRef<&str>`.
|
||||||
///
|
///
|
||||||
/// When the [Option] is [Option::Some] this behaves just like
|
/// When the [Option] is [Option::Some] this behaves just like
|
||||||
/// [StrExt::strdup], when it is [Option::None] a null pointer is
|
/// [Strdup::strdup], when it is [Option::None] a null pointer is
|
||||||
/// returned.
|
/// returned.
|
||||||
pub trait OptStrExt {
|
pub(crate) trait OptStrdup {
|
||||||
/// Allocate a new raw C `*char` version of this string, or NULL.
|
/// Allocate a new raw C `*char` version of this string, or NULL.
|
||||||
///
|
///
|
||||||
/// See [StrExt::strdup] for details.
|
/// See [Strdup::strdup] for details.
|
||||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<str>> OptStrExt for Option<T> {
|
impl<T: AsRef<str>> OptStrdup for Option<T> {
|
||||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||||
match self {
|
match self {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
let tmp = CString::yolo(s.as_ref());
|
let tmp = CString::new_lossy(s.as_ref());
|
||||||
dc_strdup(tmp.as_ptr())
|
dc_strdup(tmp.as_ptr())
|
||||||
}
|
}
|
||||||
None => ptr::null_mut(),
|
None => ptr::null_mut(),
|
||||||
@@ -204,7 +214,7 @@ impl<T: AsRef<str>> OptStrExt for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
pub(crate) fn to_string_lossy(s: *const libc::c_char) -> String {
|
||||||
if s.is_null() {
|
if s.is_null() {
|
||||||
return "".into();
|
return "".into();
|
||||||
}
|
}
|
||||||
@@ -214,7 +224,7 @@ pub fn to_string_lossy(s: *const libc::c_char) -> String {
|
|||||||
cstr.to_string_lossy().to_string()
|
cstr.to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
pub(crate) fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||||
if s.is_null() {
|
if s.is_null() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -235,7 +245,7 @@ pub fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
|||||||
///
|
///
|
||||||
/// [Path]: std::path::Path
|
/// [Path]: std::path::Path
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -247,7 +257,7 @@ pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
|||||||
|
|
||||||
// as_path() implementation for windows, documented above.
|
// as_path() implementation for windows, documented above.
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||||
as_path_unicode(s)
|
as_path_unicode(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,8 +364,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cstring_yolo() {
|
fn test_cstring_new_lossy() {
|
||||||
assert_eq!(CString::new("hello").unwrap(), CString::yolo("hello"));
|
assert!(CString::new("hel\x00lo").is_err());
|
||||||
|
assert!(CString::new(String::from("hel\x00o")).is_err());
|
||||||
|
let r = CString::new("hello").unwrap();
|
||||||
|
assert_eq!(CString::new_lossy("hello"), r);
|
||||||
|
assert_eq!(CString::new_lossy("hel\x00lo"), r);
|
||||||
|
assert_eq!(CString::new_lossy(String::from("hello")), r);
|
||||||
|
assert_eq!(CString::new_lossy(String::from("hel\x00lo")), r);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
126
draft/group-sync.rst
Normal file
126
draft/group-sync.rst
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
Problem: missing eventual group consistency
|
||||||
|
--------------------------------------------
|
||||||
|
|
||||||
|
If group members are concurrently adding new members,
|
||||||
|
the new members will miss each other's additions, example:
|
||||||
|
|
||||||
|
- Alice and Bob are in a two-member group
|
||||||
|
|
||||||
|
- Alice adds Carol, concurrently Bob adds Doris
|
||||||
|
|
||||||
|
- Carol will see a three-member group (Alice, Bob, Carol),
|
||||||
|
Doris will see a different three-member group (Alice, Bob, Doris),
|
||||||
|
and only Alice and Bob will have all four members.
|
||||||
|
|
||||||
|
Note that for verified groups any mitigation mechanism likely
|
||||||
|
needs to make all clients to know who originally added a member.
|
||||||
|
|
||||||
|
|
||||||
|
solution: memorize+attach (possible encrypted) chat-meta mime messages
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
For reference, please see https://github.com/deltachat/deltachat-core-rust/blob/master/spec.md#add-and-remove-members how MemberAdded/Removed messages are shaped.
|
||||||
|
|
||||||
|
|
||||||
|
- All Chat-Group-Member-Added/Removed messages are recorded in their
|
||||||
|
full raw (signed and encrypted) mime-format in the DB
|
||||||
|
|
||||||
|
- If an incoming member-add/member-delete messages has a member list
|
||||||
|
which is, apart from the added/removed member, not consistent
|
||||||
|
with our own view, broadcast a "Chat-Group-Member-Correction" message to
|
||||||
|
all members, attaching the original added/removed mime-message for all mismatching
|
||||||
|
contacts. If we have no relevant add/del information, don't send a
|
||||||
|
correction message out.
|
||||||
|
|
||||||
|
- Upong receiving added/removed attachments we don't do the
|
||||||
|
check_consistency+correction message dance.
|
||||||
|
This avoids recursion problems and hard-to-reason-about chatter.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- mechanism works for both encrypted and unencrypted add/del messages
|
||||||
|
|
||||||
|
- we already have a "mime_headers" column in the DB for each incoming message.
|
||||||
|
We could extend it to also include the payload and store mime unconditionally
|
||||||
|
for member-added/removed messages.
|
||||||
|
|
||||||
|
- multiple member-added/removed messages can be attached in a single
|
||||||
|
correction message
|
||||||
|
|
||||||
|
- it is minimal on the number of overall messages to reach group consistency
|
||||||
|
(best-case: no extra messages, the ABCD case above: max two extra messages)
|
||||||
|
|
||||||
|
- somewhat backward compatible: older clients will probably ignore
|
||||||
|
messages which are signed by someone who is not the outer From-address.
|
||||||
|
|
||||||
|
- the correction-protocol also helps with dropped messages. If a member
|
||||||
|
did not see a member-added/removed message, the next member add/removed
|
||||||
|
message in the group will likely heal group consistency for this member.
|
||||||
|
|
||||||
|
- we can quite easily extend the mechanism to also provide the group-avatar or
|
||||||
|
other meta-information.
|
||||||
|
|
||||||
|
Discussions of variants
|
||||||
|
++++++++++++++++++++++++
|
||||||
|
|
||||||
|
- instead of acting on MemberAdded/Removed message we could send
|
||||||
|
corrections for any received message that addresses inconsistent group members but
|
||||||
|
a) this would delay group-membership healing
|
||||||
|
b) could lead to a lot of members sending corrections
|
||||||
|
|
||||||
|
- instead of broadcasting correction messages we could only send it to
|
||||||
|
the sender of the inconsistent member-added/removed message.
|
||||||
|
A receiver of such a correction message would then need to forward
|
||||||
|
the message to the members it thinks also have an inconsistent view.
|
||||||
|
This sounds complicated and error-prone. Concretely, if Alice
|
||||||
|
receives Bob's "Member-added: Doris" message, then Alice
|
||||||
|
broadcasting the correction message with "Member-added: Carol"
|
||||||
|
would reach all four members, healing group consistency in one step.
|
||||||
|
If Bob meanwhile receives Alice's "Member-Added: Carol" message,
|
||||||
|
Bob would broadcast a correction message to all four members as well.
|
||||||
|
(Imagine a situation where Alice/Bob added Carol/Doris
|
||||||
|
while both being in an offline or bad-connection situation).
|
||||||
|
|
||||||
|
|
||||||
|
solution2: repeat member-added/removed messages
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
Introduce a new Chat-Group-Member-Changed header and deprecate Chat-Group-Member-Added/Removed
|
||||||
|
but keep sending out the old headers until the new protocol is sufficiently deployed.
|
||||||
|
|
||||||
|
The new Chat-Group-Member-Changed header contains a Time-to-Live number (TTL)
|
||||||
|
which controls repetition of the signed "add/del e-mail address" payload.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
Chat-Group-Member-Changed: TTL add "somedisplayname" someone@example.org
|
||||||
|
owEBYQGe/pANAwACAY47A6J5t3LWAcsxYgBeTQypYWRkICJzb21lZGlzcGxheW5h
|
||||||
|
bWUiIHNvbWVvbmVAZXhhbXBsZS5vcmcgCokBHAQAAQIABgUCXk0MqQAKCRCOOwOi
|
||||||
|
ebdy1hfRB/wJ74tgFQulicthcv9n+ZsqzwOtBKMEVIHqJCzzDB/Hg/2z8ogYoZNR
|
||||||
|
iUKKrv3Y1XuFvdKyOC+wC/unXAWKFHYzY6Tv6qDp6r+amt+ad+8Z02q53h9E55IP
|
||||||
|
FUBdq2rbS8hLGjQB+mVRowYrUACrOqGgNbXMZjQfuV7fSc7y813OsCQgi3tjstup
|
||||||
|
b+uduVzxCp3PChGhcZPs3iOGCnQvSB8VAaLGMWE2d7nTo/yMQ0Jx69x5qwfXogTk
|
||||||
|
mTt5rOJyrosbtf09TMKFzGgtqBcEqHLp3+mQpZQ+WHUKAbsRa8Jc9DOUOSKJ8SNM
|
||||||
|
clKdskprY+4LY0EBwLD3SQ7dPkTITCRD
|
||||||
|
=P6GG
|
||||||
|
|
||||||
|
TTL is set to "2" on an initial Chat-Group-Member-Changed add/del message.
|
||||||
|
Receivers will apply the add/del change to the group-membership,
|
||||||
|
decrease the TTL by 1, and if TTL>0 re-sent the header.
|
||||||
|
|
||||||
|
The "add|del e-mail address" payload is pgp-signed and repeated verbatim.
|
||||||
|
This allows to propagate, in a cryptographically secured way,
|
||||||
|
who added a member. This is particularly important for allowing
|
||||||
|
to show in verified groups who added a member (planned).
|
||||||
|
|
||||||
|
Disadvantage to solution 1:
|
||||||
|
|
||||||
|
- requires to specify encoding and precise rules for what/how is signed.
|
||||||
|
|
||||||
|
- causes O(N^2) extra messages
|
||||||
|
|
||||||
|
- Not easily extendable for other things (without introducing a new
|
||||||
|
header / encoding)
|
||||||
|
|
||||||
|
|
||||||
@@ -394,6 +394,7 @@ pub async fn cmdline(
|
|||||||
providerinfo <addr>\n\
|
providerinfo <addr>\n\
|
||||||
event <event-id to test>\n\
|
event <event-id to test>\n\
|
||||||
fileinfo <file>\n\
|
fileinfo <file>\n\
|
||||||
|
estimatedeletion <seconds>\n\
|
||||||
emptyserver <flags> (1=MVBOX 2=INBOX)\n\
|
emptyserver <flags> (1=MVBOX 2=INBOX)\n\
|
||||||
clear -- clear screen\n\
|
clear -- clear screen\n\
|
||||||
exit or quit\n\
|
exit or quit\n\
|
||||||
@@ -1049,6 +1050,16 @@ pub async fn cmdline(
|
|||||||
bail!("Command failed.");
|
bail!("Command failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"estimatedeletion" => {
|
||||||
|
ensure!(!arg1.is_empty(), "Argument <seconds> missing");
|
||||||
|
let seconds = arg1.parse()?;
|
||||||
|
let device_cnt = message::estimate_deletion_cnt(context, false, seconds)?;
|
||||||
|
let server_cnt = message::estimate_deletion_cnt(context, true, seconds)?;
|
||||||
|
println!(
|
||||||
|
"estimated count of messages older than {} seconds:\non device: {}\non server: {}",
|
||||||
|
seconds, device_cnt, server_cnt
|
||||||
|
);
|
||||||
|
}
|
||||||
"emptyserver" => {
|
"emptyserver" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <flags> missing");
|
ensure!(!arg1.is_empty(), "Argument <flags> missing");
|
||||||
|
|
||||||
|
|||||||
@@ -207,8 +207,17 @@ const CONTACT_COMMANDS: [&str; 6] = [
|
|||||||
"delcontact",
|
"delcontact",
|
||||||
"cleanupcontacts",
|
"cleanupcontacts",
|
||||||
];
|
];
|
||||||
const MISC_COMMANDS: [&str; 9] = [
|
const MISC_COMMANDS: [&str; 10] = [
|
||||||
"getqr", "getbadqr", "checkqr", "event", "fileinfo", "clear", "exit", "quit", "help",
|
"getqr",
|
||||||
|
"getbadqr",
|
||||||
|
"checkqr",
|
||||||
|
"event",
|
||||||
|
"fileinfo",
|
||||||
|
"clear",
|
||||||
|
"exit",
|
||||||
|
"quit",
|
||||||
|
"help",
|
||||||
|
"estimatedeletion",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl Hinter for DcHelper {
|
impl Hinter for DcHelper {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ impl Aheader {
|
|||||||
wanted_from: &str,
|
wanted_from: &str,
|
||||||
headers: &[mailparse::MailHeader<'_>],
|
headers: &[mailparse::MailHeader<'_>],
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
if let Ok(Some(value)) = headers.get_header_value(HeaderDef::Autocrypt) {
|
if let Some(value) = headers.get_header_value(HeaderDef::Autocrypt) {
|
||||||
match Self::from_str(&value) {
|
match Self::from_str(&value) {
|
||||||
Ok(header) => {
|
Ok(header) => {
|
||||||
if addr_cmp(&header.addr, wanted_from) {
|
if addr_cmp(&header.addr, wanted_from) {
|
||||||
|
|||||||
91
src/chat.rs
91
src/chat.rs
@@ -301,10 +301,7 @@ impl ChatId {
|
|||||||
/// Returns `true`, if message was deleted, `false` otherwise.
|
/// Returns `true`, if message was deleted, `false` otherwise.
|
||||||
async fn maybe_delete_draft(self, context: &Context) -> bool {
|
async fn maybe_delete_draft(self, context: &Context) -> bool {
|
||||||
match self.get_draft_msg_id(context).await {
|
match self.get_draft_msg_id(context).await {
|
||||||
Some(msg_id) => {
|
Some(msg_id) => msg_id.delete_from_db(context).await.is_ok(),
|
||||||
Message::delete_from_db(context, msg_id).await;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,6 +379,26 @@ impl ChatId {
|
|||||||
.unwrap_or_default() as usize
|
.unwrap_or_default() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn get_param(self, context: &Context) -> Result<Params, Error> {
|
||||||
|
let res: Option<String> = context
|
||||||
|
.sql
|
||||||
|
.query_get_value_result("SELECT param FROM chats WHERE id=?", paramsv![self])
|
||||||
|
.await?;
|
||||||
|
Ok(res
|
||||||
|
.map(|s| s.parse().unwrap_or_default())
|
||||||
|
.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if chat is a saved messages chat.
|
||||||
|
pub async fn is_self_talk(self, context: &Context) -> Result<bool, Error> {
|
||||||
|
Ok(self.get_param(context).await?.exists(Param::Selftalk))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if chat is a device chat.
|
||||||
|
pub async fn is_device_talk(self, context: &Context) -> Result<bool, Error> {
|
||||||
|
Ok(self.get_param(context).await?.exists(Param::Devicetalk))
|
||||||
|
}
|
||||||
|
|
||||||
/// Bad evil escape hatch.
|
/// Bad evil escape hatch.
|
||||||
///
|
///
|
||||||
/// Avoid using this, eventually types should be cleaned up enough
|
/// Avoid using this, eventually types should be cleaned up enough
|
||||||
@@ -1518,6 +1535,18 @@ pub async fn get_chat_msgs(
|
|||||||
flags: u32,
|
flags: u32,
|
||||||
marker1before: Option<MsgId>,
|
marker1before: Option<MsgId>,
|
||||||
) -> Vec<MsgId> {
|
) -> Vec<MsgId> {
|
||||||
|
match hide_device_expired_messages(context).await {
|
||||||
|
Err(err) => warn!(context, "Failed to delete expired messages: {}", err),
|
||||||
|
Ok(messages_deleted) => {
|
||||||
|
if messages_deleted {
|
||||||
|
context.call_cb(Event::MsgsChanged {
|
||||||
|
msg_id: MsgId::new(0),
|
||||||
|
chat_id: ChatId::new(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let process_row =
|
let process_row =
|
||||||
|row: &rusqlite::Row| Ok((row.get::<_, MsgId>("id")?, row.get::<_, i64>("timestamp")?));
|
|row: &rusqlite::Row| Ok((row.get::<_, MsgId>("id")?, row.get::<_, i64>("timestamp")?));
|
||||||
let process_rows = |rows: rusqlite::MappedRows<_>| {
|
let process_rows = |rows: rusqlite::MappedRows<_>| {
|
||||||
@@ -1671,6 +1700,52 @@ pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hides messages which are expired according to "delete_device_after" setting.
|
||||||
|
///
|
||||||
|
/// Returns true if any message is hidden, so event can be emitted. If nothing
|
||||||
|
/// has been hidden, returns false.
|
||||||
|
pub async fn hide_device_expired_messages(context: &Context) -> Result<bool, Error> {
|
||||||
|
if let Some(delete_device_after) = context.get_config_delete_device_after().await {
|
||||||
|
let threshold_timestamp = time() - delete_device_after;
|
||||||
|
|
||||||
|
let self_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_SELF)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.0;
|
||||||
|
let device_chat_id = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.0;
|
||||||
|
|
||||||
|
// Hide expired messages
|
||||||
|
//
|
||||||
|
// Only update the rows that have to be updated, to avoid emitting
|
||||||
|
// unnecessary "chat modified" events.
|
||||||
|
let rows_modified = context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"UPDATE msgs \
|
||||||
|
SET txt = 'DELETED', hidden = 1 \
|
||||||
|
WHERE timestamp < ? \
|
||||||
|
AND chat_id > ? \
|
||||||
|
AND chat_id != ? \
|
||||||
|
AND chat_id != ? \
|
||||||
|
AND NOT hidden",
|
||||||
|
paramsv![
|
||||||
|
threshold_timestamp,
|
||||||
|
DC_CHAT_ID_LAST_SPECIAL,
|
||||||
|
self_chat_id,
|
||||||
|
device_chat_id
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(rows_modified > 0)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_chat_media(
|
pub async fn get_chat_media(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
chat_id: ChatId,
|
chat_id: ChatId,
|
||||||
@@ -2182,7 +2257,7 @@ pub async fn remove_contact_from_chat(
|
|||||||
"Cannot remove contact from chat; self not in group.".into()
|
"Cannot remove contact from chat; self not in group.".into()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else if remove_from_chat_contacts_table(context, chat_id, contact_id).await {
|
||||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||||
if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
|
if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
|
||||||
if chat.is_promoted() {
|
if chat.is_promoted() {
|
||||||
@@ -2220,10 +2295,8 @@ pub async fn remove_contact_from_chat(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if remove_from_chat_contacts_table(context, chat_id, contact_id).await {
|
context.call_cb(Event::ChatModified(chat_id));
|
||||||
context.call_cb(Event::ChatModified(chat_id));
|
success = true;
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ impl Chatlist {
|
|||||||
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
|
||||||
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are *any* archived
|
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are *any* archived
|
||||||
/// chats
|
/// chats
|
||||||
/// - the flag DC_GCL_FOR_FORWARDING sorts "Saved messages" to the top of the chatlist,
|
/// - the flag DC_GCL_FOR_FORWARDING sorts "Saved messages" to the top of the chatlist
|
||||||
|
/// and hides the device-chat,
|
||||||
// typically used on forwarding, may be combined with DC_GCL_NO_SPECIALS
|
// typically used on forwarding, may be combined with DC_GCL_NO_SPECIALS
|
||||||
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
|
||||||
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
|
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
|
||||||
@@ -91,6 +92,12 @@ impl Chatlist {
|
|||||||
query: Option<&str>,
|
query: Option<&str>,
|
||||||
query_contact_id: Option<u32>,
|
query_contact_id: Option<u32>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
|
// Note that we do not emit DC_EVENT_MSGS_MODIFIED here even if some
|
||||||
|
// messages get hidden to avoid reloading the same chatlist.
|
||||||
|
if let Err(err) = hide_device_expired_messages(context).await {
|
||||||
|
warn!(context, "Failed to hide expired messages: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
let mut add_archived_link_item = false;
|
let mut add_archived_link_item = false;
|
||||||
|
|
||||||
let process_row = |row: &rusqlite::Row| {
|
let process_row = |row: &rusqlite::Row| {
|
||||||
@@ -104,6 +111,15 @@ impl Chatlist {
|
|||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let skip_id = if 0 != listflags & DC_GCL_FOR_FORWARDING {
|
||||||
|
chat::lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.0
|
||||||
|
} else {
|
||||||
|
ChatId::new(0)
|
||||||
|
};
|
||||||
|
|
||||||
// select with left join and minimum:
|
// select with left join and minimum:
|
||||||
//
|
//
|
||||||
// - the inner select must use `hidden` and _not_ `m.hidden`
|
// - the inner select must use `hidden` and _not_ `m.hidden`
|
||||||
@@ -142,6 +158,9 @@ impl Chatlist {
|
|||||||
).await?
|
).await?
|
||||||
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
|
||||||
// show archived chats
|
// show archived chats
|
||||||
|
// (this includes the archived device-chat; we could skip it,
|
||||||
|
// however, then the number of archived chats do not match, which might be even more irritating.
|
||||||
|
// and adapting the number requires larger refactorings and seems not to be worth the effort)
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.query_map(
|
.query_map(
|
||||||
@@ -186,13 +205,13 @@ impl Chatlist {
|
|||||||
SELECT MAX(timestamp)
|
SELECT MAX(timestamp)
|
||||||
FROM msgs
|
FROM msgs
|
||||||
WHERE chat_id=c.id
|
WHERE chat_id=c.id
|
||||||
AND (hidden=0 OR state=?))
|
AND (hidden=0 OR state=?1))
|
||||||
WHERE c.id>9
|
WHERE c.id>9 AND c.id!=?2
|
||||||
AND c.blocked=0
|
AND c.blocked=0
|
||||||
AND c.name LIKE ?
|
AND c.name LIKE ?3
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||||
paramsv![MessageState::OutDraft, str_like_cmd],
|
paramsv![MessageState::OutDraft, skip_id, str_like_cmd],
|
||||||
process_row,
|
process_row,
|
||||||
process_rows,
|
process_rows,
|
||||||
)
|
)
|
||||||
@@ -217,12 +236,12 @@ impl Chatlist {
|
|||||||
FROM msgs
|
FROM msgs
|
||||||
WHERE chat_id=c.id
|
WHERE chat_id=c.id
|
||||||
AND (hidden=0 OR state=?1))
|
AND (hidden=0 OR state=?1))
|
||||||
WHERE c.id>9
|
WHERE c.id>9 AND c.id!=?2
|
||||||
AND c.blocked=0
|
AND c.blocked=0
|
||||||
AND NOT c.archived=?2
|
AND NOT c.archived=?3
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
ORDER BY c.id=?3 DESC, c.archived=?4 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
ORDER BY c.id=?4 DESC, c.archived=?5 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||||
paramsv![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
|
paramsv![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
|
||||||
process_row,
|
process_row,
|
||||||
process_rows,
|
process_rows,
|
||||||
).await?;
|
).await?;
|
||||||
@@ -446,16 +465,21 @@ mod tests {
|
|||||||
async fn test_sort_self_talk_up_on_forward() {
|
async fn test_sort_self_talk_up_on_forward() {
|
||||||
let t = dummy_context().await;
|
let t = dummy_context().await;
|
||||||
t.ctx.update_device_chats().await.unwrap();
|
t.ctx.update_device_chats().await.unwrap();
|
||||||
|
create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||||
assert!(Chat::load_from_db(&t.ctx, chats.get_chat_id(0))
|
assert!(chats.len() == 3);
|
||||||
|
assert!(!Chat::load_from_db(&t.ctx, chats.get_chat_id(0))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_device_talk());
|
.is_self_talk());
|
||||||
|
|
||||||
let chats = Chatlist::try_load(&t.ctx, DC_GCL_FOR_FORWARDING, None, None)
|
let chats = Chatlist::try_load(&t.ctx, DC_GCL_FOR_FORWARDING, None, None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
assert!(chats.len() == 2); // device chat cannot be written and is skipped on forwarding
|
||||||
assert!(Chat::load_from_db(&t.ctx, chats.get_chat_id(0))
|
assert!(Chat::load_from_db(&t.ctx, chats.get_chat_id(0))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ use strum::{EnumProperty, IntoEnumIterator};
|
|||||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
||||||
|
|
||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
|
use crate::chat::ChatId;
|
||||||
use crate::constants::DC_VERSION_STR;
|
use crate::constants::DC_VERSION_STR;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
|
use crate::events::Event;
|
||||||
|
use crate::message::MsgId;
|
||||||
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
|
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
|
|
||||||
@@ -63,6 +66,25 @@ pub enum Config {
|
|||||||
#[strum(props(default = "0"))]
|
#[strum(props(default = "0"))]
|
||||||
KeyGenType,
|
KeyGenType,
|
||||||
|
|
||||||
|
/// Timer in seconds after which the message is deleted from the
|
||||||
|
/// server.
|
||||||
|
///
|
||||||
|
/// Equals to 0 by default, which means the message is never
|
||||||
|
/// deleted.
|
||||||
|
///
|
||||||
|
/// Value 1 is treated as "delete at once": messages are deleted
|
||||||
|
/// immediately, without moving to DeltaChat folder.
|
||||||
|
#[strum(props(default = "0"))]
|
||||||
|
DeleteServerAfter,
|
||||||
|
|
||||||
|
/// Timer in seconds after which the message is deleted from the
|
||||||
|
/// device.
|
||||||
|
///
|
||||||
|
/// Equals to 0 by default, which means the message is never
|
||||||
|
/// deleted.
|
||||||
|
#[strum(props(default = "0"))]
|
||||||
|
DeleteDeviceAfter,
|
||||||
|
|
||||||
SaveMimeHeaders,
|
SaveMimeHeaders,
|
||||||
ConfiguredAddr,
|
ConfiguredAddr,
|
||||||
ConfiguredMailServer,
|
ConfiguredMailServer,
|
||||||
@@ -127,6 +149,29 @@ impl Context {
|
|||||||
self.get_config_int(key).await != 0
|
self.get_config_int(key).await != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets configured "delete_server_after" value.
|
||||||
|
///
|
||||||
|
/// `None` means never delete the message, `Some(0)` means delete
|
||||||
|
/// at once, `Some(x)` means delete after `x` seconds.
|
||||||
|
pub async fn get_config_delete_server_after(&self) -> Option<i64> {
|
||||||
|
match self.get_config_int(Config::DeleteServerAfter).await {
|
||||||
|
0 => None,
|
||||||
|
1 => Some(0),
|
||||||
|
x => Some(x as i64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets configured "delete_device_after" value.
|
||||||
|
///
|
||||||
|
/// `None` means never delete the message, `Some(x)` means delete
|
||||||
|
/// after `x` seconds.
|
||||||
|
pub async fn get_config_delete_device_after(&self) -> Option<i64> {
|
||||||
|
match self.get_config_int(Config::DeleteDeviceAfter).await {
|
||||||
|
0 => None,
|
||||||
|
x => Some(x as i64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the given config key.
|
/// Set the given config key.
|
||||||
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
|
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
|
||||||
pub async fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> {
|
pub async fn set_config(&self, key: Config, value: Option<&str>) -> crate::sql::Result<()> {
|
||||||
@@ -174,6 +219,15 @@ impl Context {
|
|||||||
|
|
||||||
self.sql.set_raw_config(self, key, val).await
|
self.sql.set_raw_config(self, key, val).await
|
||||||
}
|
}
|
||||||
|
Config::DeleteDeviceAfter => {
|
||||||
|
let ret = self.sql.set_raw_config(self, key, value).await;
|
||||||
|
// Force chatlist reload to delete old messages immediately.
|
||||||
|
self.call_cb(Event::MsgsChanged {
|
||||||
|
msg_id: MsgId::new(0),
|
||||||
|
chat_id: ChatId::new(0),
|
||||||
|
});
|
||||||
|
ret
|
||||||
|
}
|
||||||
_ => self.sql.set_raw_config(self, key, value).await,
|
_ => self.sql.set_raw_config(self, key, value).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::message::{self, MessageState, MessengerMessage, MsgId};
|
|||||||
use crate::mimeparser::*;
|
use crate::mimeparser::*;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
use crate::peerstate::*;
|
use crate::peerstate::*;
|
||||||
use crate::securejoin::{self, handle_securejoin_handshake};
|
use crate::securejoin::{self, handle_securejoin_handshake, observe_securejoin_on_other_device};
|
||||||
use crate::stock::StockMessage;
|
use crate::stock::StockMessage;
|
||||||
use crate::{contact, location};
|
use crate::{contact, location};
|
||||||
|
|
||||||
@@ -196,20 +196,27 @@ pub async fn dc_receive_imf(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we delete we don't need to try moving messages
|
// Get user-configured server deletion
|
||||||
if needs_delete_job && !created_db_entries.is_empty() {
|
let delete_server_after = context.get_config_delete_server_after().await;
|
||||||
job::add(
|
|
||||||
context,
|
if !created_db_entries.is_empty() {
|
||||||
Action::DeleteMsgOnImap,
|
if needs_delete_job || delete_server_after == Some(0) {
|
||||||
created_db_entries[0].1.to_u32() as i32,
|
for db_entry in &created_db_entries {
|
||||||
Params::new(),
|
job::add(
|
||||||
0,
|
context,
|
||||||
)
|
Action::DeleteMsgOnImap,
|
||||||
.await;
|
db_entry.1.to_u32() as i32,
|
||||||
} else {
|
Params::new(),
|
||||||
context
|
0,
|
||||||
.do_heuristics_moves(server_folder.as_ref(), insert_msg_id)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Move message if we don't delete it immediately.
|
||||||
|
context
|
||||||
|
.do_heuristics_moves(server_folder.as_ref(), insert_msg_id)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
@@ -220,7 +227,7 @@ pub async fn dc_receive_imf(
|
|||||||
cleanup(context, &create_event_to_send, created_db_entries);
|
cleanup(context, &create_event_to_send, created_db_entries);
|
||||||
|
|
||||||
mime_parser
|
mime_parser
|
||||||
.handle_reports(context, from_id, sent_timestamp, &server_folder, server_uid)
|
.handle_reports(context, from_id, sent_timestamp)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -351,11 +358,9 @@ async fn add_parts(
|
|||||||
};
|
};
|
||||||
to_id = DC_CONTACT_ID_SELF;
|
to_id = DC_CONTACT_ID_SELF;
|
||||||
|
|
||||||
// handshake messages must be processed _before_ chats are created
|
// handshake may mark contacts as verified and must be processed before chats are created
|
||||||
// (eg. contacs may be marked as verified)
|
|
||||||
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
|
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
|
||||||
// avoid discarding by show_emails setting
|
msgrmsg = MessengerMessage::Yes; // avoid discarding by show_emails setting
|
||||||
msgrmsg = MessengerMessage::Yes;
|
|
||||||
*chat_id = ChatId::new(0);
|
*chat_id = ChatId::new(0);
|
||||||
allow_creation = true;
|
allow_creation = true;
|
||||||
match handle_securejoin_handshake(context, mime_parser, from_id).await {
|
match handle_securejoin_handshake(context, mime_parser, from_id).await {
|
||||||
@@ -369,8 +374,7 @@ async fn add_parts(
|
|||||||
state = MessageState::InSeen;
|
state = MessageState::InSeen;
|
||||||
}
|
}
|
||||||
Ok(securejoin::HandshakeMessage::Propagate) => {
|
Ok(securejoin::HandshakeMessage::Propagate) => {
|
||||||
// Message will still be processed as "member
|
// process messages as "member added" normally
|
||||||
// added" or similar system message.
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
*hidden = true;
|
*hidden = true;
|
||||||
@@ -491,6 +495,27 @@ async fn add_parts(
|
|||||||
// We cannot recreate other states (read, error).
|
// We cannot recreate other states (read, error).
|
||||||
state = MessageState::OutDelivered;
|
state = MessageState::OutDelivered;
|
||||||
to_id = to_ids.get_index(0).cloned().unwrap_or_default();
|
to_id = to_ids.get_index(0).cloned().unwrap_or_default();
|
||||||
|
|
||||||
|
// handshake may mark contacts as verified and must be processed before chats are created
|
||||||
|
if mime_parser.get(HeaderDef::SecureJoin).is_some() {
|
||||||
|
msgrmsg = MessengerMessage::Yes; // avoid discarding by show_emails setting
|
||||||
|
*chat_id = ChatId::new(0);
|
||||||
|
allow_creation = true;
|
||||||
|
match observe_securejoin_on_other_device(context, mime_parser, to_id) {
|
||||||
|
Ok(securejoin::HandshakeMessage::Done)
|
||||||
|
| Ok(securejoin::HandshakeMessage::Ignore) => {
|
||||||
|
*hidden = true;
|
||||||
|
}
|
||||||
|
Ok(securejoin::HandshakeMessage::Propagate) => {
|
||||||
|
// process messages as "member added" normally
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
*hidden = true;
|
||||||
|
error!(context, "Error in Secure-Join watching: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !to_ids.is_empty() {
|
if !to_ids.is_empty() {
|
||||||
if chat_id.is_unset() {
|
if chat_id.is_unset() {
|
||||||
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group(
|
||||||
@@ -604,6 +629,7 @@ async fn add_parts(
|
|||||||
let sent_timestamp = *sent_timestamp;
|
let sent_timestamp = *sent_timestamp;
|
||||||
let is_hidden = *hidden;
|
let is_hidden = *hidden;
|
||||||
let chat_id = *chat_id;
|
let chat_id = *chat_id;
|
||||||
|
let is_mdn = !mime_parser.reports.is_empty();
|
||||||
|
|
||||||
// TODO: can this clone be avoided?
|
// TODO: can this clone be avoided?
|
||||||
let rfc724_mid = rfc724_mid.to_string();
|
let rfc724_mid = rfc724_mid.to_string();
|
||||||
@@ -624,8 +650,11 @@ async fn add_parts(
|
|||||||
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if location_kml_is && icnt == 1 && (part.msg == "-location-" || part.msg.is_empty())
|
let is_location_kml = location_kml_is
|
||||||
{
|
&& icnt == 1
|
||||||
|
&& (part.msg == "-location-" || part.msg.is_empty());
|
||||||
|
|
||||||
|
if is_mdn || is_location_kml {
|
||||||
is_hidden = true;
|
is_hidden = true;
|
||||||
if state == MessageState::InFresh {
|
if state == MessageState::InFresh {
|
||||||
state = MessageState::InNoticed;
|
state = MessageState::InNoticed;
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ pub async fn try_decrypt(
|
|||||||
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
|
) -> Result<(Option<Vec<u8>>, HashSet<String>)> {
|
||||||
let from = mail
|
let from = mail
|
||||||
.headers
|
.headers
|
||||||
.get_header_value(HeaderDef::From_)?
|
.get_header_value(HeaderDef::From_)
|
||||||
.and_then(|from_addr| mailparse::addrparse(&from_addr).ok())
|
.and_then(|from_addr| mailparse::addrparse(&from_addr).ok())
|
||||||
.and_then(|from| from.extract_single_info())
|
.and_then(|from| from.extract_single_info())
|
||||||
.map(|from| from.addr)
|
.map(|from| from.addr)
|
||||||
@@ -425,7 +425,6 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
#[ignore] // generating keys is expensive
|
|
||||||
async fn test_generate() {
|
async fn test_generate() {
|
||||||
let t = dummy_context().await;
|
let t = dummy_context().await;
|
||||||
let addr = "alice@example.org";
|
let addr = "alice@example.org";
|
||||||
@@ -437,7 +436,6 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
#[ignore]
|
|
||||||
async fn test_generate_concurrent() {
|
async fn test_generate_concurrent() {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::strum::AsStaticRef;
|
use crate::strum::AsStaticRef;
|
||||||
use mailparse::{MailHeader, MailHeaderMap, MailParseError};
|
use mailparse::{MailHeader, MailHeaderMap};
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames, AsStaticStr)]
|
#[derive(Debug, Display, Clone, PartialEq, Eq, EnumVariantNames, AsStaticStr)]
|
||||||
#[strum(serialize_all = "kebab_case")]
|
#[strum(serialize_all = "kebab_case")]
|
||||||
@@ -52,11 +52,11 @@ impl HeaderDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait HeaderDefMap {
|
pub trait HeaderDefMap {
|
||||||
fn get_header_value(&self, headerdef: HeaderDef) -> Result<Option<String>, MailParseError>;
|
fn get_header_value(&self, headerdef: HeaderDef) -> Option<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeaderDefMap for [MailHeader<'_>] {
|
impl HeaderDefMap for [MailHeader<'_>] {
|
||||||
fn get_header_value(&self, headerdef: HeaderDef) -> Result<Option<String>, MailParseError> {
|
fn get_header_value(&self, headerdef: HeaderDef) -> Option<String> {
|
||||||
self.get_first_value(headerdef.get_headername())
|
self.get_first_value(headerdef.get_headername())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,18 +79,13 @@ mod tests {
|
|||||||
let (headers, _) =
|
let (headers, _) =
|
||||||
mailparse::parse_headers(b"fRoM: Bob\naUtoCryPt-SeTup-MessAge: v99").unwrap();
|
mailparse::parse_headers(b"fRoM: Bob\naUtoCryPt-SeTup-MessAge: v99").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
headers
|
headers.get_header_value(HeaderDef::AutocryptSetupMessage),
|
||||||
.get_header_value(HeaderDef::AutocryptSetupMessage)
|
|
||||||
.unwrap(),
|
|
||||||
Some("v99".to_string())
|
Some("v99".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
headers.get_header_value(HeaderDef::From_).unwrap(),
|
headers.get_header_value(HeaderDef::From_),
|
||||||
Some("Bob".to_string())
|
Some("Bob".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(headers.get_header_value(HeaderDef::Autocrypt), None);
|
||||||
headers.get_header_value(HeaderDef::Autocrypt).unwrap(),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
src/imap/mod.rs
115
src/imap/mod.rs
@@ -823,7 +823,6 @@ impl Imap {
|
|||||||
folder: &str,
|
folder: &str,
|
||||||
uid: u32,
|
uid: u32,
|
||||||
dest_folder: &str,
|
dest_folder: &str,
|
||||||
dest_uid: &mut u32,
|
|
||||||
) -> ImapActionResult {
|
) -> ImapActionResult {
|
||||||
if folder == dest_folder {
|
if folder == dest_folder {
|
||||||
info!(
|
info!(
|
||||||
@@ -839,10 +838,6 @@ impl Imap {
|
|||||||
return imapresult;
|
return imapresult;
|
||||||
}
|
}
|
||||||
// we are connected, and the folder is selected
|
// we are connected, and the folder is selected
|
||||||
|
|
||||||
// XXX Rust-Imap provides no target uid on mv, so just set it to 0
|
|
||||||
*dest_uid = 0;
|
|
||||||
|
|
||||||
let set = format!("{}", uid);
|
let set = format!("{}", uid);
|
||||||
let display_folder_id = format!("{}/{}", folder, uid);
|
let display_folder_id = format!("{}/{}", folder, uid);
|
||||||
|
|
||||||
@@ -1025,10 +1020,10 @@ impl Imap {
|
|||||||
context: &Context,
|
context: &Context,
|
||||||
message_id: &str,
|
message_id: &str,
|
||||||
folder: &str,
|
folder: &str,
|
||||||
uid: &mut u32,
|
uid: u32,
|
||||||
) -> ImapActionResult {
|
) -> ImapActionResult {
|
||||||
if let Some(imapresult) = self
|
if let Some(imapresult) = self
|
||||||
.prepare_imap_operation_on_msg(context, folder, *uid)
|
.prepare_imap_operation_on_msg(context, folder, uid)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return imapresult;
|
return imapresult;
|
||||||
@@ -1052,7 +1047,7 @@ impl Imap {
|
|||||||
display_imap_id,
|
display_imap_id,
|
||||||
message_id,
|
message_id,
|
||||||
);
|
);
|
||||||
return ImapActionResult::Failed;
|
return ImapActionResult::AlreadyDone;
|
||||||
};
|
};
|
||||||
|
|
||||||
let remote_message_id = get_fetch_headers(&fetch)
|
let remote_message_id = get_fetch_headers(&fetch)
|
||||||
@@ -1067,26 +1062,26 @@ impl Imap {
|
|||||||
remote_message_id,
|
remote_message_id,
|
||||||
message_id,
|
message_id,
|
||||||
);
|
);
|
||||||
*uid = 0;
|
return ImapActionResult::Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
"Cannot delete {} on IMAP: {}", display_imap_id, err
|
"Cannot delete on IMAP, {}: {}", display_imap_id, err,
|
||||||
);
|
);
|
||||||
*uid = 0;
|
return ImapActionResult::RetryLater;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark the message for deletion
|
// mark the message for deletion
|
||||||
if !self.add_flag_finalized(context, *uid, "\\Deleted").await {
|
if !self.add_flag_finalized(context, uid, "\\Deleted").await {
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
"Cannot mark message {} as \"Deleted\".", display_imap_id
|
"Cannot mark message {} as \"Deleted\".", display_imap_id
|
||||||
);
|
);
|
||||||
ImapActionResult::Failed
|
ImapActionResult::RetryLater
|
||||||
} else {
|
} else {
|
||||||
emit_event!(
|
emit_event!(
|
||||||
context,
|
context,
|
||||||
@@ -1232,11 +1227,6 @@ impl Imap {
|
|||||||
.set_raw_config_int(context, "folders_configured", DC_FOLDERS_CONFIGURED_VERSION)
|
.set_raw_config_int(context, "folders_configured", DC_FOLDERS_CONFIGURED_VERSION)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.set_raw_config_int(context, "folders_configured", 3)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!(context, "FINISHED configuring IMAP-folders.");
|
info!(context, "FINISHED configuring IMAP-folders.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1262,14 +1252,6 @@ impl Imap {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self
|
|
||||||
.add_flag_finalized_with_set(context, SELECT_ALL, "\\Deleted")
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
error!(context, "Cannot mark messages for deletion {}", folder);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we now trigger expunge to actually delete messages
|
// we now trigger expunge to actually delete messages
|
||||||
self.config.selected_folder_needs_expunge = true;
|
self.config.selected_folder_needs_expunge = true;
|
||||||
match self.select_folder::<String>(context, None).await {
|
match self.select_folder::<String>(context, None).await {
|
||||||
@@ -1347,20 +1329,55 @@ async fn precheck_imf(
|
|||||||
message::rfc724_mid_exists(context, &rfc724_mid).await
|
message::rfc724_mid_exists(context, &rfc724_mid).await
|
||||||
{
|
{
|
||||||
if old_server_folder.is_empty() && old_server_uid == 0 {
|
if old_server_folder.is_empty() && old_server_uid == 0 {
|
||||||
info!(context, "[move] detected bcc-self {}", rfc724_mid,);
|
info!(
|
||||||
context
|
|
||||||
.do_heuristics_moves(server_folder.as_ref(), msg_id)
|
|
||||||
.await;
|
|
||||||
job::add(
|
|
||||||
context,
|
context,
|
||||||
Action::MarkseenMsgOnImap,
|
"[move] detected bcc-self {} as {}/{}", rfc724_mid, server_folder, server_uid
|
||||||
msg_id.to_u32() as i32,
|
);
|
||||||
Params::new(),
|
|
||||||
0,
|
let delete_server_after = context.get_config_delete_server_after().await;
|
||||||
)
|
|
||||||
.await;
|
if delete_server_after != Some(0) {
|
||||||
|
context
|
||||||
|
.do_heuristics_moves(server_folder.as_ref(), msg_id)
|
||||||
|
.await;
|
||||||
|
job::add(
|
||||||
|
context,
|
||||||
|
Action::MarkseenMsgOnImap,
|
||||||
|
msg_id.to_u32() as i32,
|
||||||
|
Params::new(),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
} else if old_server_folder != server_folder {
|
} else if old_server_folder != server_folder {
|
||||||
info!(context, "[move] detected moved message {}", rfc724_mid,);
|
info!(
|
||||||
|
context,
|
||||||
|
"[move] detected message {} moved by other device from {}/{} to {}/{}",
|
||||||
|
rfc724_mid,
|
||||||
|
old_server_folder,
|
||||||
|
old_server_uid,
|
||||||
|
server_folder,
|
||||||
|
server_uid
|
||||||
|
);
|
||||||
|
} else if old_server_uid == 0 {
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"[move] detected message {} moved by us from {}/{} to {}/{}",
|
||||||
|
rfc724_mid,
|
||||||
|
old_server_folder,
|
||||||
|
old_server_uid,
|
||||||
|
server_folder,
|
||||||
|
server_uid
|
||||||
|
);
|
||||||
|
} else if old_server_uid != server_uid {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"UID for message {} in folder {} changed from {} to {}",
|
||||||
|
rfc724_mid,
|
||||||
|
server_folder,
|
||||||
|
old_server_uid,
|
||||||
|
server_uid
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if old_server_folder != server_folder || old_server_uid != server_uid {
|
if old_server_folder != server_folder || old_server_uid != server_uid {
|
||||||
@@ -1382,7 +1399,7 @@ fn get_fetch_headers(prefetch_msg: &Fetch) -> Result<Vec<mailparse::MailHeader>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prefetch_get_message_id(headers: &[mailparse::MailHeader]) -> Result<String> {
|
fn prefetch_get_message_id(headers: &[mailparse::MailHeader]) -> Result<String> {
|
||||||
if let Some(message_id) = headers.get_header_value(HeaderDef::MessageId)? {
|
if let Some(message_id) = headers.get_header_value(HeaderDef::MessageId) {
|
||||||
Ok(crate::mimeparser::parse_message_id(&message_id)?)
|
Ok(crate::mimeparser::parse_message_id(&message_id)?)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::Other("prefetch: No message ID found".to_string()))
|
Err(Error::Other("prefetch: No message ID found".to_string()))
|
||||||
@@ -1392,20 +1409,20 @@ fn prefetch_get_message_id(headers: &[mailparse::MailHeader]) -> Result<String>
|
|||||||
async fn prefetch_is_reply_to_chat_message(
|
async fn prefetch_is_reply_to_chat_message(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
headers: &[mailparse::MailHeader<'_>],
|
headers: &[mailparse::MailHeader<'_>],
|
||||||
) -> Result<bool> {
|
) -> bool {
|
||||||
if let Some(value) = headers.get_header_value(HeaderDef::InReplyTo)? {
|
if let Some(value) = headers.get_header_value(HeaderDef::InReplyTo) {
|
||||||
if is_msgrmsg_rfc724_mid_in_list(context, &value).await {
|
if is_msgrmsg_rfc724_mid_in_list(context, &value).await {
|
||||||
return Ok(true);
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = headers.get_header_value(HeaderDef::References)? {
|
if let Some(value) = headers.get_header_value(HeaderDef::References) {
|
||||||
if is_msgrmsg_rfc724_mid_in_list(context, &value).await {
|
if is_msgrmsg_rfc724_mid_in_list(context, &value).await {
|
||||||
return Ok(true);
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(false)
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prefetch_should_download(
|
async fn prefetch_should_download(
|
||||||
@@ -1413,16 +1430,16 @@ async fn prefetch_should_download(
|
|||||||
headers: &[mailparse::MailHeader<'_>],
|
headers: &[mailparse::MailHeader<'_>],
|
||||||
show_emails: ShowEmails,
|
show_emails: ShowEmails,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion)?.is_some();
|
let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion).is_some();
|
||||||
let is_reply_to_chat_message = prefetch_is_reply_to_chat_message(context, &headers).await?;
|
let is_reply_to_chat_message = prefetch_is_reply_to_chat_message(context, &headers).await;
|
||||||
|
|
||||||
// Autocrypt Setup Message should be shown even if it is from non-chat client.
|
// Autocrypt Setup Message should be shown even if it is from non-chat client.
|
||||||
let is_autocrypt_setup_message = headers
|
let is_autocrypt_setup_message = headers
|
||||||
.get_header_value(HeaderDef::AutocryptSetupMessage)?
|
.get_header_value(HeaderDef::AutocryptSetupMessage)
|
||||||
.is_some();
|
.is_some();
|
||||||
|
|
||||||
let from_field = headers
|
let from_field = headers
|
||||||
.get_header_value(HeaderDef::From_)?
|
.get_header_value(HeaderDef::From_)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let (_contact_id, blocked_contact, origin) =
|
let (_contact_id, blocked_contact, origin) =
|
||||||
|
|||||||
@@ -792,7 +792,6 @@ mod tests {
|
|||||||
assert!(msg.contains("-----BEGIN PGP MESSAGE-----\r\n"));
|
assert!(msg.contains("-----BEGIN PGP MESSAGE-----\r\n"));
|
||||||
assert!(msg.contains("Passphrase-Format: numeric9x4\r\n"));
|
assert!(msg.contains("Passphrase-Format: numeric9x4\r\n"));
|
||||||
assert!(msg.contains("Passphrase-Begin: he\n"));
|
assert!(msg.contains("Passphrase-Begin: he\n"));
|
||||||
assert!(msg.contains("==\n"));
|
|
||||||
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
|
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
180
src/job.rs
180
src/job.rs
@@ -78,10 +78,13 @@ pub enum Action {
|
|||||||
// Jobs in the INBOX-thread, range from DC_IMAP_THREAD..DC_IMAP_THREAD+999
|
// Jobs in the INBOX-thread, range from DC_IMAP_THREAD..DC_IMAP_THREAD+999
|
||||||
Housekeeping = 105, // low priority ...
|
Housekeeping = 105, // low priority ...
|
||||||
EmptyServer = 107,
|
EmptyServer = 107,
|
||||||
DeleteMsgOnImap = 110,
|
OldDeleteMsgOnImap = 110,
|
||||||
MarkseenMdnOnImap = 120,
|
|
||||||
MarkseenMsgOnImap = 130,
|
MarkseenMsgOnImap = 130,
|
||||||
|
|
||||||
|
// Moving message is prioritized lower than deletion so we don't
|
||||||
|
// bother moving message if it is already scheduled for deletion.
|
||||||
MoveMsg = 200,
|
MoveMsg = 200,
|
||||||
|
DeleteMsgOnImap = 210,
|
||||||
|
|
||||||
// Jobs in the SMTP-thread, range from DC_SMTP_THREAD..DC_SMTP_THREAD+999
|
// Jobs in the SMTP-thread, range from DC_SMTP_THREAD..DC_SMTP_THREAD+999
|
||||||
MaybeSendLocations = 5005, // low priority ...
|
MaybeSendLocations = 5005, // low priority ...
|
||||||
@@ -104,9 +107,9 @@ impl From<Action> for Thread {
|
|||||||
Unknown => Thread::Unknown,
|
Unknown => Thread::Unknown,
|
||||||
|
|
||||||
Housekeeping => Thread::Imap,
|
Housekeeping => Thread::Imap,
|
||||||
|
OldDeleteMsgOnImap => Thread::Imap,
|
||||||
DeleteMsgOnImap => Thread::Imap,
|
DeleteMsgOnImap => Thread::Imap,
|
||||||
EmptyServer => Thread::Imap,
|
EmptyServer => Thread::Imap,
|
||||||
MarkseenMdnOnImap => Thread::Imap,
|
|
||||||
MarkseenMsgOnImap => Thread::Imap,
|
MarkseenMsgOnImap => Thread::Imap,
|
||||||
MoveMsg => Thread::Imap,
|
MoveMsg => Thread::Imap,
|
||||||
|
|
||||||
@@ -417,22 +420,15 @@ impl Job {
|
|||||||
|
|
||||||
if let Some(dest_folder) = dest_folder {
|
if let Some(dest_folder) = dest_folder {
|
||||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||||
let mut dest_uid = 0;
|
|
||||||
|
|
||||||
match imap
|
match imap
|
||||||
.mv(
|
.mv(context, server_folder, msg.server_uid, &dest_folder)
|
||||||
context,
|
|
||||||
server_folder,
|
|
||||||
msg.server_uid,
|
|
||||||
&dest_folder,
|
|
||||||
&mut dest_uid,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
ImapActionResult::RetryLater => Status::RetryLater,
|
ImapActionResult::RetryLater => Status::RetryLater,
|
||||||
ImapActionResult::Success => {
|
ImapActionResult::Success => {
|
||||||
message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid)
|
// XXX Rust-Imap provides no target uid on mv, so just set it to 0
|
||||||
.await;
|
message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, 0).await;
|
||||||
Status::Finished(Ok(()))
|
Status::Finished(Ok(()))
|
||||||
}
|
}
|
||||||
ImapActionResult::Failed => {
|
ImapActionResult::Failed => {
|
||||||
@@ -445,11 +441,26 @@ impl Job {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes a message on the server.
|
||||||
|
///
|
||||||
|
/// foreign_id is a MsgId pointing to a message in the trash chat
|
||||||
|
/// or a hidden message.
|
||||||
|
///
|
||||||
|
/// This job removes the database record. If there are no more
|
||||||
|
/// records pointing to the same message on the server, the job
|
||||||
|
/// also removes the message on the server.
|
||||||
async fn delete_msg_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status {
|
async fn delete_msg_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status {
|
||||||
let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
|
let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
|
||||||
|
|
||||||
if !msg.rfc724_mid.is_empty() {
|
if !msg.rfc724_mid.is_empty() {
|
||||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid).await > 1 {
|
let cnt = message::rfc724_mid_cnt(context, &msg.rfc724_mid).await;
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"Running delete job for message {} which has {} entries in the database",
|
||||||
|
&msg.rfc724_mid,
|
||||||
|
cnt
|
||||||
|
);
|
||||||
|
if cnt > 1 {
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
"The message is deleted from the server when all parts are deleted.",
|
"The message is deleted from the server when all parts are deleted.",
|
||||||
@@ -459,15 +470,48 @@ impl Job {
|
|||||||
we delete the message from the server */
|
we delete the message from the server */
|
||||||
let mid = msg.rfc724_mid;
|
let mid = msg.rfc724_mid;
|
||||||
let server_folder = msg.server_folder.as_ref().unwrap();
|
let server_folder = msg.server_folder.as_ref().unwrap();
|
||||||
let res = imap
|
let res = if msg.server_uid == 0 {
|
||||||
.delete_msg(context, &mid, server_folder, &mut msg.server_uid)
|
// Message is already deleted on IMAP server.
|
||||||
.await;
|
ImapActionResult::AlreadyDone
|
||||||
if res == ImapActionResult::RetryLater {
|
} else {
|
||||||
// XXX RetryLater is converted to RetryNow here
|
imap.delete_msg(context, &mid, server_folder, msg.server_uid)
|
||||||
return Status::RetryNow;
|
.await
|
||||||
|
};
|
||||||
|
match res {
|
||||||
|
ImapActionResult::AlreadyDone | ImapActionResult::Success => {}
|
||||||
|
ImapActionResult::RetryLater | ImapActionResult::Failed => {
|
||||||
|
// If job has failed, for example due to some
|
||||||
|
// IMAP bug, we postpone it instead of failing
|
||||||
|
// immediately. This will prevent adding it
|
||||||
|
// immediately again if user has enabled
|
||||||
|
// automatic message deletion. Without this,
|
||||||
|
// we might waste a lot of traffic constantly
|
||||||
|
// retrying message deletion.
|
||||||
|
return Status::RetryLater;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::delete_from_db(context, msg.id).await;
|
if msg.chat_id.is_trash() || msg.hidden {
|
||||||
|
// Messages are stored in trash chat only to keep
|
||||||
|
// their server UID and Message-ID. Once message is
|
||||||
|
// deleted from the server, database record can be
|
||||||
|
// removed as well.
|
||||||
|
//
|
||||||
|
// Hidden messages are similar to trashed, but are
|
||||||
|
// related to some chat. We also delete their
|
||||||
|
// database records.
|
||||||
|
job_try!(msg.id.delete_from_db(context).await)
|
||||||
|
} else {
|
||||||
|
// Remove server UID from the database record.
|
||||||
|
//
|
||||||
|
// We have either just removed the message from the
|
||||||
|
// server, in which case UID is not valid anymore, or
|
||||||
|
// we have more refernces to the same server UID, so
|
||||||
|
// we remove UID to reduce the number of messages
|
||||||
|
// pointing to the corresponding UID. Once the counter
|
||||||
|
// reaches zero, we will remove the message.
|
||||||
|
job_try!(msg.id.unlink(context).await);
|
||||||
|
}
|
||||||
Status::Finished(Ok(()))
|
Status::Finished(Ok(()))
|
||||||
} else {
|
} else {
|
||||||
/* eg. device messages have no Message-ID */
|
/* eg. device messages have no Message-ID */
|
||||||
@@ -515,46 +559,6 @@ impl Job {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn markseen_mdn_on_imap(&mut self, context: &Context, imap: &mut Imap) -> Status {
|
|
||||||
let folder = self
|
|
||||||
.param
|
|
||||||
.get(Param::ServerFolder)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string();
|
|
||||||
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
|
|
||||||
|
|
||||||
if imap.set_seen(context, &folder, uid).await == ImapActionResult::RetryLater {
|
|
||||||
return Status::RetryLater;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.param.get_bool(Param::AlsoMove).unwrap_or_default() {
|
|
||||||
if let Err(err) = imap.ensure_configured_folders(context, true).await {
|
|
||||||
warn!(context, "configuring folders failed: {:?}", err);
|
|
||||||
return Status::RetryLater;
|
|
||||||
}
|
|
||||||
let dest_folder = context
|
|
||||||
.sql
|
|
||||||
.get_raw_config(context, "configured_mvbox_folder")
|
|
||||||
.await;
|
|
||||||
if let Some(dest_folder) = dest_folder {
|
|
||||||
let mut dest_uid = 0;
|
|
||||||
if ImapActionResult::RetryLater
|
|
||||||
== imap
|
|
||||||
.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Status::RetryLater
|
|
||||||
} else {
|
|
||||||
Status::Finished(Ok(()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Status::Finished(Err(format_err!("MVBOX is not configured")))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Status::Finished(Ok(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete all pending jobs with the given action.
|
/// Delete all pending jobs with the given action.
|
||||||
@@ -628,7 +632,11 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
|||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let lowercase_from = from.to_lowercase();
|
let lowercase_from = from.to_lowercase();
|
||||||
|
|
||||||
|
// Send BCC to self if it is enabled and we are not going to
|
||||||
|
// delete it immediately.
|
||||||
if context.get_config_bool(Config::BccSelf).await
|
if context.get_config_bool(Config::BccSelf).await
|
||||||
|
&& context.get_config_delete_server_after().await != Some(0)
|
||||||
&& !recipients
|
&& !recipients
|
||||||
.iter()
|
.iter()
|
||||||
.any(|x| x.to_lowercase() == lowercase_from)
|
.any(|x| x.to_lowercase() == lowercase_from)
|
||||||
@@ -716,6 +724,45 @@ pub enum Connection<'a> {
|
|||||||
Smtp(&'a mut Smtp),
|
Smtp(&'a mut Smtp),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn add_imap_deletion_jobs(context: &Context) -> sql::Result<()> {
|
||||||
|
if let Some(delete_server_after) = context.get_config_delete_server_after().await {
|
||||||
|
let threshold_timestamp = time() - delete_server_after;
|
||||||
|
|
||||||
|
// Select all expired messages which don't have a
|
||||||
|
// corresponding message deletion job yet.
|
||||||
|
let msg_ids = context
|
||||||
|
.sql
|
||||||
|
.query_map(
|
||||||
|
"SELECT id FROM msgs \
|
||||||
|
WHERE timestamp < ? \
|
||||||
|
AND server_uid != 0 \
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM jobs WHERE foreign_id = msgs.id \
|
||||||
|
AND action = ?)",
|
||||||
|
paramsv![threshold_timestamp, Action::DeleteMsgOnImap],
|
||||||
|
|row| row.get::<_, MsgId>(0),
|
||||||
|
|ids| {
|
||||||
|
ids.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Schedule IMAP deletion for expired messages.
|
||||||
|
for msg_id in msg_ids {
|
||||||
|
add(
|
||||||
|
context,
|
||||||
|
Action::DeleteMsgOnImap,
|
||||||
|
msg_id.to_u32() as i32,
|
||||||
|
Params::new(),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Display for Connection<'a> {
|
impl<'a> fmt::Display for Connection<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -811,10 +858,7 @@ async fn perform_job_action(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let try_res = match job.action {
|
let try_res = match job.action {
|
||||||
Action::Unknown => {
|
Action::Unknown => Status::Finished(Err(format_err!("Unknown job id found"))),
|
||||||
warn!(context, "ignoring unknown job");
|
|
||||||
Status::Finished(Ok(()))
|
|
||||||
}
|
|
||||||
Action::SendMsgToSmtp => job.send_msg_to_smtp(context, connection.smtp()).await,
|
Action::SendMsgToSmtp => job.send_msg_to_smtp(context, connection.smtp()).await,
|
||||||
Action::SendMdn => job.send_mdn(context, connection.smtp()).await,
|
Action::SendMdn => job.send_mdn(context, connection.smtp()).await,
|
||||||
Action::MaybeSendLocations => location::job_maybe_send_locations(context, job).await,
|
Action::MaybeSendLocations => location::job_maybe_send_locations(context, job).await,
|
||||||
@@ -822,9 +866,9 @@ async fn perform_job_action(
|
|||||||
location::job_maybe_send_locations_ended(context, job).await
|
location::job_maybe_send_locations_ended(context, job).await
|
||||||
}
|
}
|
||||||
Action::EmptyServer => job.empty_server(context, connection.inbox()).await,
|
Action::EmptyServer => job.empty_server(context, connection.inbox()).await,
|
||||||
|
Action::OldDeleteMsgOnImap => job.delete_msg_on_imap(context, connection.inbox()).await,
|
||||||
Action::DeleteMsgOnImap => job.delete_msg_on_imap(context, connection.inbox()).await,
|
Action::DeleteMsgOnImap => job.delete_msg_on_imap(context, connection.inbox()).await,
|
||||||
Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await,
|
Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await,
|
||||||
Action::MarkseenMdnOnImap => job.markseen_mdn_on_imap(context, connection.inbox()).await,
|
|
||||||
Action::MoveMsg => job.move_msg(context, connection.inbox()).await,
|
Action::MoveMsg => job.move_msg(context, connection.inbox()).await,
|
||||||
Action::Housekeeping => {
|
Action::Housekeeping => {
|
||||||
sql::housekeeping(context).await;
|
sql::housekeeping(context).await;
|
||||||
@@ -913,8 +957,8 @@ pub async fn add(
|
|||||||
Action::Unknown => unreachable!(),
|
Action::Unknown => unreachable!(),
|
||||||
Action::Housekeeping
|
Action::Housekeeping
|
||||||
| Action::EmptyServer
|
| Action::EmptyServer
|
||||||
|
| Action::OldDeleteMsgOnImap
|
||||||
| Action::DeleteMsgOnImap
|
| Action::DeleteMsgOnImap
|
||||||
| Action::MarkseenMdnOnImap
|
|
||||||
| Action::MarkseenMsgOnImap
|
| Action::MarkseenMsgOnImap
|
||||||
| Action::MoveMsg => {
|
| Action::MoveMsg => {
|
||||||
context.interrupt_inbox().await;
|
context.interrupt_inbox().await;
|
||||||
|
|||||||
139
src/message.rs
139
src/message.rs
@@ -83,6 +83,56 @@ impl MsgId {
|
|||||||
self.0 == DC_MSG_ID_DAYMARKER
|
self.0 == DC_MSG_ID_DAYMARKER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Put message into trash chat and delete message text.
|
||||||
|
///
|
||||||
|
/// It means the message is deleted locally, but not on the server
|
||||||
|
/// yet.
|
||||||
|
pub async fn trash(self, context: &Context) -> crate::sql::Result<()> {
|
||||||
|
let chat_id = ChatId::new(DC_CHAT_ID_TRASH);
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"UPDATE msgs SET chat_id=?, txt='', txt_raw='' WHERE id=?",
|
||||||
|
paramsv![chat_id, self],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes a message and corresponding MDNs from the database.
|
||||||
|
pub async fn delete_from_db(self, context: &Context) -> crate::sql::Result<()> {
|
||||||
|
// We don't use transactions yet, so remove MDNs first to make
|
||||||
|
// sure they are not left while the message is deleted.
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![self])
|
||||||
|
.await?;
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute("DELETE FROM msgs WHERE id=?;", paramsv![self])
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes IMAP server UID and folder from the database record.
|
||||||
|
///
|
||||||
|
/// It is used to avoid trying to remove the message from the
|
||||||
|
/// server multiple times when there are multiple message records
|
||||||
|
/// pointing to the same server UID.
|
||||||
|
pub(crate) async fn unlink(self, context: &Context) -> crate::sql::Result<()> {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"UPDATE msgs \
|
||||||
|
SET server_folder='', server_uid=0 \
|
||||||
|
WHERE id=?",
|
||||||
|
paramsv![self],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Bad evil escape hatch.
|
/// Bad evil escape hatch.
|
||||||
///
|
///
|
||||||
/// Avoid using this, eventually types should be cleaned up enough
|
/// Avoid using this, eventually types should be cleaned up enough
|
||||||
@@ -305,21 +355,6 @@ impl Message {
|
|||||||
Ok(msg)
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_from_db(context: &Context, msg_id: MsgId) {
|
|
||||||
if let Ok(msg) = Message::load_from_db(context, msg_id).await {
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.execute("DELETE FROM msgs WHERE id=?;", paramsv![msg.id])
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![msg.id])
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_filemime(&self) -> Option<String> {
|
pub fn get_filemime(&self) -> Option<String> {
|
||||||
if let Some(m) = self.param.get(Param::MimeType) {
|
if let Some(m) = self.param.get(Param::MimeType) {
|
||||||
return Some(m.to_string());
|
return Some(m.to_string());
|
||||||
@@ -982,7 +1017,9 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
|
|||||||
delete_poi_location(context, msg.location_id).await;
|
delete_poi_location(context, msg.location_id).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update_msg_chat_id(context, *msg_id, ChatId::new(DC_CHAT_ID_TRASH)).await;
|
if let Err(err) = msg_id.trash(context).await {
|
||||||
|
error!(context, "Unable to trash message {}: {}", msg_id, err);
|
||||||
|
}
|
||||||
job::add(
|
job::add(
|
||||||
context,
|
context,
|
||||||
Action::DeleteMsgOnImap,
|
Action::DeleteMsgOnImap,
|
||||||
@@ -1003,17 +1040,6 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool {
|
|
||||||
context
|
|
||||||
.sql
|
|
||||||
.execute(
|
|
||||||
"UPDATE msgs SET chat_id=? WHERE id=?;",
|
|
||||||
paramsv![chat_id, msg_id],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
|
async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
@@ -1404,12 +1430,64 @@ pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn estimate_deletion_cnt(
|
||||||
|
context: &Context,
|
||||||
|
from_server: bool,
|
||||||
|
seconds: i64,
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
let self_chat_id = chat::lookup_by_contact_id(context, DC_CONTACT_ID_SELF)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.0;
|
||||||
|
let threshold_timestamp = time() - seconds;
|
||||||
|
|
||||||
|
let cnt: isize = if from_server {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*)
|
||||||
|
FROM msgs m
|
||||||
|
WHERE m.id > ?
|
||||||
|
AND timestamp < ?
|
||||||
|
AND chat_id != ?
|
||||||
|
AND server_uid != 0;",
|
||||||
|
paramsv![DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.query_row(
|
||||||
|
"SELECT COUNT(*)
|
||||||
|
FROM msgs m
|
||||||
|
WHERE m.id > ?
|
||||||
|
AND timestamp < ?
|
||||||
|
AND chat_id != ?
|
||||||
|
AND chat_id != ? AND hidden = 0;",
|
||||||
|
paramsv![
|
||||||
|
DC_MSG_ID_LAST_SPECIAL,
|
||||||
|
threshold_timestamp,
|
||||||
|
self_chat_id,
|
||||||
|
ChatId::new(DC_CHAT_ID_TRASH)
|
||||||
|
],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
Ok(cnt as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Counts number of database records pointing to specified
|
||||||
|
/// Message-ID.
|
||||||
|
///
|
||||||
|
/// Unlinked messages are excluded.
|
||||||
pub async fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 {
|
pub async fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 {
|
||||||
// check the number of messages with the same rfc724_mid
|
// check the number of messages with the same rfc724_mid
|
||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;",
|
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=? AND NOT server_uid = 0",
|
||||||
paramsv![rfc724_mid],
|
paramsv![rfc724_mid],
|
||||||
|row| row.get(0),
|
|row| row.get(0),
|
||||||
)
|
)
|
||||||
@@ -1456,8 +1534,9 @@ pub async fn update_server_uid(
|
|||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;",
|
"UPDATE msgs SET server_folder=?, server_uid=? \
|
||||||
paramsv![server_folder.as_ref().to_string(), server_uid, rfc724_mid],
|
WHERE rfc724_mid=?",
|
||||||
|
paramsv![server_folder.as_ref(), server_uid, rfc724_mid],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use mailparse::{DispositionType, MailAddr, MailHeaderMap};
|
|||||||
use crate::aheader::Aheader;
|
use crate::aheader::Aheader;
|
||||||
use crate::bail;
|
use crate::bail;
|
||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::config::Config;
|
|
||||||
use crate::constants::Viewtype;
|
use crate::constants::Viewtype;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
@@ -19,7 +18,6 @@ use crate::e2ee;
|
|||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||||
use crate::job::{self, Action};
|
|
||||||
use crate::location;
|
use crate::location;
|
||||||
use crate::message;
|
use crate::message;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
@@ -87,7 +85,7 @@ impl MimeMessage {
|
|||||||
|
|
||||||
let message_time = mail
|
let message_time = mail
|
||||||
.headers
|
.headers
|
||||||
.get_header_value(HeaderDef::Date)?
|
.get_header_value(HeaderDef::Date)
|
||||||
.and_then(|v| mailparse::dateparse(&v).ok())
|
.and_then(|v| mailparse::dateparse(&v).ok())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
@@ -113,8 +111,7 @@ impl MimeMessage {
|
|||||||
|
|
||||||
// Handle any gossip headers if the mail was encrypted. See section
|
// Handle any gossip headers if the mail was encrypted. See section
|
||||||
// "3.6 Key Gossip" of https://autocrypt.org/autocrypt-spec-1.1.0.pdf
|
// "3.6 Key Gossip" of https://autocrypt.org/autocrypt-spec-1.1.0.pdf
|
||||||
let gossip_headers =
|
let gossip_headers = decrypted_mail.headers.get_all_values("Autocrypt-Gossip");
|
||||||
decrypted_mail.headers.get_all_values("Autocrypt-Gossip")?;
|
|
||||||
gossipped_addr =
|
gossipped_addr =
|
||||||
update_gossip_peerstates(context, message_time, &mail, gossip_headers)
|
update_gossip_peerstates(context, message_time, &mail, gossip_headers)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -554,6 +551,16 @@ impl MimeMessage {
|
|||||||
if let Some(report) = self.process_report(context, mail)? {
|
if let Some(report) = self.process_report(context, mail)? {
|
||||||
self.reports.push(report);
|
self.reports.push(report);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add MDN part so we can track it, avoid
|
||||||
|
// downloading the message again and
|
||||||
|
// delete if automatic message deletion is
|
||||||
|
// enabled.
|
||||||
|
let mut part = Part::default();
|
||||||
|
part.typ = Viewtype::Unknown;
|
||||||
|
self.parts.push(part);
|
||||||
|
|
||||||
|
any_part_added = true;
|
||||||
} else {
|
} else {
|
||||||
/* eg. `report-type=delivery-status`;
|
/* eg. `report-type=delivery-status`;
|
||||||
maybe we should show them as a little error icon */
|
maybe we should show them as a little error icon */
|
||||||
@@ -756,16 +763,13 @@ impl MimeMessage {
|
|||||||
|
|
||||||
fn merge_headers(headers: &mut HashMap<String, String>, fields: &[mailparse::MailHeader<'_>]) {
|
fn merge_headers(headers: &mut HashMap<String, String>, fields: &[mailparse::MailHeader<'_>]) {
|
||||||
for field in fields {
|
for field in fields {
|
||||||
if let Ok(key) = field.get_key() {
|
// lowercasing all headers is technically not correct, but makes things work better
|
||||||
// lowercasing all headers is technically not correct, but makes things work better
|
let key = field.get_key().to_lowercase();
|
||||||
let key = key.to_lowercase();
|
if !headers.contains_key(&key) || // key already exists, only overwrite known types (protected headers)
|
||||||
if !headers.contains_key(&key) || // key already exists, only overwrite known types (protected headers)
|
|
||||||
is_known(&key) || key.starts_with("chat-")
|
is_known(&key) || key.starts_with("chat-")
|
||||||
{
|
{
|
||||||
if let Ok(value) = field.get_value() {
|
let value = field.get_value();
|
||||||
headers.insert(key, value);
|
headers.insert(key.to_string(), value);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -780,21 +784,13 @@ impl MimeMessage {
|
|||||||
let (report_fields, _) = mailparse::parse_headers(&report_body)?;
|
let (report_fields, _) = mailparse::parse_headers(&report_body)?;
|
||||||
|
|
||||||
// must be present
|
// must be present
|
||||||
if let Some(_disposition) = report_fields
|
if let Some(_disposition) = report_fields.get_header_value(HeaderDef::Disposition) {
|
||||||
.get_header_value(HeaderDef::Disposition)
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
{
|
|
||||||
if let Some(original_message_id) = report_fields
|
if let Some(original_message_id) = report_fields
|
||||||
.get_header_value(HeaderDef::OriginalMessageId)
|
.get_header_value(HeaderDef::OriginalMessageId)
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.and_then(|v| parse_message_id(&v).ok())
|
.and_then(|v| parse_message_id(&v).ok())
|
||||||
{
|
{
|
||||||
let additional_message_ids = report_fields
|
let additional_message_ids = report_fields
|
||||||
.get_header_value(HeaderDef::AdditionalMessageIds)
|
.get_header_value(HeaderDef::AdditionalMessageIds)
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.map_or_else(Vec::new, |v| {
|
.map_or_else(Vec::new, |v| {
|
||||||
v.split(' ')
|
v.split(' ')
|
||||||
.filter_map(|s| parse_message_id(s).ok())
|
.filter_map(|s| parse_message_id(s).ok())
|
||||||
@@ -810,26 +806,18 @@ impl MimeMessage {
|
|||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
"ignoring unknown disposition-notification, Message-Id: {:?}",
|
"ignoring unknown disposition-notification, Message-Id: {:?}",
|
||||||
report_fields.get_header_value(HeaderDef::MessageId).ok()
|
report_fields.get_header_value(HeaderDef::MessageId)
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle reports (only MDNs for now)
|
/// Handle reports (only MDNs for now)
|
||||||
pub async fn handle_reports(
|
pub async fn handle_reports(&self, context: &Context, from_id: u32, sent_timestamp: i64) {
|
||||||
&self,
|
|
||||||
context: &Context,
|
|
||||||
from_id: u32,
|
|
||||||
sent_timestamp: i64,
|
|
||||||
server_folder: impl AsRef<str>,
|
|
||||||
server_uid: u32,
|
|
||||||
) {
|
|
||||||
if self.reports.is_empty() {
|
if self.reports.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mdn_recognized = false;
|
|
||||||
for report in &self.reports {
|
for report in &self.reports {
|
||||||
for original_message_id in
|
for original_message_id in
|
||||||
std::iter::once(&report.original_message_id).chain(&report.additional_message_ids)
|
std::iter::once(&report.original_message_id).chain(&report.additional_message_ids)
|
||||||
@@ -839,20 +827,9 @@ impl MimeMessage {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
context.call_cb(Event::MsgRead { chat_id, msg_id });
|
context.call_cb(Event::MsgRead { chat_id, msg_id });
|
||||||
mdn_recognized = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.has_chat_version() || mdn_recognized {
|
|
||||||
let mut param = Params::new();
|
|
||||||
param.set(Param::ServerFolder, server_folder.as_ref());
|
|
||||||
param.set_int(Param::ServerUid, server_uid as i32);
|
|
||||||
if self.has_chat_version() && context.get_config_bool(Config::MvboxMove).await {
|
|
||||||
param.set_int(Param::AlsoMove, 1);
|
|
||||||
}
|
|
||||||
job::add(context, Action::MarkseenMdnOnImap, 0, param, 0).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,14 +848,9 @@ async fn update_gossip_peerstates(
|
|||||||
|
|
||||||
if let Ok(ref header) = gossip_header {
|
if let Ok(ref header) = gossip_header {
|
||||||
if recipients.is_none() {
|
if recipients.is_none() {
|
||||||
recipients = Some(get_recipients(mail.headers.iter().filter_map(|v| {
|
recipients = Some(get_recipients(
|
||||||
let key = v.get_key();
|
mail.headers.iter().map(|v| (v.get_key(), v.get_value())),
|
||||||
let value = v.get_value();
|
));
|
||||||
if key.is_err() || value.is_err() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some((v.get_key().unwrap(), v.get_value().unwrap()))
|
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if recipients
|
if recipients
|
||||||
@@ -926,13 +898,8 @@ pub(crate) fn parse_message_id(value: &str) -> crate::error::Result<String> {
|
|||||||
let ids = mailparse::msgidparse(value)
|
let ids = mailparse::msgidparse(value)
|
||||||
.map_err(|err| format_err!("failed to parse message id {:?}", err))?;
|
.map_err(|err| format_err!("failed to parse message id {:?}", err))?;
|
||||||
|
|
||||||
if ids.len() == 1 {
|
if let Some(id) = ids.first() {
|
||||||
let id = &ids[0];
|
Ok(id.to_string())
|
||||||
if id.starts_with('<') && id.ends_with('>') {
|
|
||||||
Ok(id.chars().skip(1).take(id.len() - 2).collect())
|
|
||||||
} else {
|
|
||||||
bail!("message-ID {} is not enclosed in < and >", value);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
bail!("could not parse message_id: {}", value);
|
bail!("could not parse message_id: {}", value);
|
||||||
}
|
}
|
||||||
@@ -998,15 +965,12 @@ fn get_mime_type(mail: &mailparse::ParsedMail<'_>) -> Result<(Mime, Viewtype)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool {
|
fn is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool {
|
||||||
if let Ok(ct) = mail.get_content_disposition() {
|
let ct = mail.get_content_disposition();
|
||||||
return ct.disposition == DispositionType::Attachment
|
ct.disposition == DispositionType::Attachment
|
||||||
&& ct
|
&& ct
|
||||||
.params
|
.params
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(key, _value)| key.starts_with("filename"));
|
.any(|(key, _value)| key.starts_with("filename"))
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to get attachment filename.
|
/// Tries to get attachment filename.
|
||||||
@@ -1021,7 +985,7 @@ fn get_attachment_filename(mail: &mailparse::ParsedMail) -> Result<Option<String
|
|||||||
// or `Content-Disposition: ... filename*0*=... filename*1*=... filename*2*=...`
|
// or `Content-Disposition: ... filename*0*=... filename*1*=... filename*2*=...`
|
||||||
// or `Content-Disposition: ... filename=...`
|
// or `Content-Disposition: ... filename=...`
|
||||||
|
|
||||||
let ct = mail.get_content_disposition()?;
|
let ct = mail.get_content_disposition();
|
||||||
|
|
||||||
let desired_filename: Option<String> = ct
|
let desired_filename: Option<String> = ct
|
||||||
.params
|
.params
|
||||||
@@ -1398,7 +1362,7 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
|||||||
Some("Chat: Message opened".to_string())
|
Some("Chat: Message opened".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(message.parts.len(), 0);
|
assert_eq!(message.parts.len(), 1);
|
||||||
assert_eq!(message.reports.len(), 1);
|
assert_eq!(message.reports.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1478,7 +1442,7 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
|||||||
Some("Chat: Message opened".to_string())
|
Some("Chat: Message opened".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(message.parts.len(), 0);
|
assert_eq!(message.parts.len(), 2);
|
||||||
assert_eq!(message.reports.len(), 2);
|
assert_eq!(message.reports.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1525,7 +1489,7 @@ Additional-Message-IDs: <foo@example.com> <foo@example.net>\n\
|
|||||||
Some("Chat: Message opened".to_string())
|
Some("Chat: Message opened".to_string())
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(message.parts.len(), 0);
|
assert_eq!(message.parts.len(), 1);
|
||||||
assert_eq!(message.reports.len(), 1);
|
assert_eq!(message.reports.len(), 1);
|
||||||
assert_eq!(message.reports[0].original_message_id, "foo@example.org");
|
assert_eq!(message.reports[0].original_message_id, "foo@example.org");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -88,12 +88,6 @@ pub enum Param {
|
|||||||
/// For Jobs
|
/// For Jobs
|
||||||
SetLongitude = b'n',
|
SetLongitude = b'n',
|
||||||
|
|
||||||
/// For Jobs
|
|
||||||
ServerFolder = b'Z',
|
|
||||||
|
|
||||||
/// For Jobs
|
|
||||||
ServerUid = b'z',
|
|
||||||
|
|
||||||
/// For Jobs
|
/// For Jobs
|
||||||
AlsoMove = b'M',
|
AlsoMove = b'M',
|
||||||
|
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ pub(crate) fn create_keypair(
|
|||||||
keygen_type: KeyGenType,
|
keygen_type: KeyGenType,
|
||||||
) -> std::result::Result<KeyPair, PgpKeygenError> {
|
) -> std::result::Result<KeyPair, PgpKeygenError> {
|
||||||
let (secret_key_type, public_key_type) = match keygen_type {
|
let (secret_key_type, public_key_type) = match keygen_type {
|
||||||
KeyGenType::Rsa2048 | KeyGenType::Default => (PgpKeyType::Rsa(2048), PgpKeyType::Rsa(2048)),
|
KeyGenType::Rsa2048 => (PgpKeyType::Rsa(2048), PgpKeyType::Rsa(2048)),
|
||||||
KeyGenType::Ed25519 => (PgpKeyType::EdDSA, PgpKeyType::ECDH),
|
KeyGenType::Ed25519 | KeyGenType::Default => (PgpKeyType::EdDSA, PgpKeyType::ECDH),
|
||||||
};
|
};
|
||||||
|
|
||||||
let user_id = format!("<{}>", addr);
|
let user_id = format!("<{}>", addr);
|
||||||
@@ -394,7 +394,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // is too expensive
|
|
||||||
fn test_create_keypair() {
|
fn test_create_keypair() {
|
||||||
let keypair0 = create_keypair(
|
let keypair0 = create_keypair(
|
||||||
EmailAddress::new("foo@bar.de").unwrap(),
|
EmailAddress::new("foo@bar.de").unwrap(),
|
||||||
|
|||||||
@@ -824,9 +824,42 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// observe_securejoin_on_other_device() must be called when a self-sent securejoin message is seen.
|
||||||
|
/// currently, the message is only ignored, in the future,
|
||||||
|
/// we may mark peers as verified accross devices:
|
||||||
|
///
|
||||||
|
/// in a multi-device-setup, there may be other devices that "see" the handshake messages.
|
||||||
|
/// if the seen messages seen are self-sent messages encrypted+signed correctly with our key,
|
||||||
|
/// we can make some conclusions of it:
|
||||||
|
///
|
||||||
|
/// - if we see the self-sent-message vg-member-added/vc-contact-confirm,
|
||||||
|
/// we know that we're an inviter-observer.
|
||||||
|
/// the inviting device has marked a peer as verified on vg-request-with-auth/vc-request-with-auth
|
||||||
|
/// before sending vg-member-added/vc-contact-confirm - so, if we observe vg-member-added/vc-contact-confirm,
|
||||||
|
/// we can mark the peer as verified as well.
|
||||||
|
///
|
||||||
|
/// - if we see the self-sent-message vg-member-added-received
|
||||||
|
/// we know that we're an joiner-observer.
|
||||||
|
/// the joining device has marked the peer as verified on vg-member-added/vc-contact-confirm
|
||||||
|
/// before sending vg-member-added-received - so, if we observe vg-member-added-received,
|
||||||
|
/// we can mark the peer as verified as well.
|
||||||
|
///
|
||||||
|
/// to make this work, (a) some messages must not be deleted,
|
||||||
|
/// (b) we need a vc-contact-confirm-received message if bcc_self is set,
|
||||||
|
/// (c) we should make sure, we do not only rely on the unencrypted To:-header for identifying the peer
|
||||||
|
/// (in handle_securejoin_handshake() we have the oob information for that)
|
||||||
|
pub(crate) fn observe_securejoin_on_other_device(
|
||||||
|
_context: &Context,
|
||||||
|
_mime_message: &MimeMessage,
|
||||||
|
_contact_id: u32,
|
||||||
|
) -> Result<HandshakeMessage, HandshakeError> {
|
||||||
|
Ok(HandshakeMessage::Ignore)
|
||||||
|
}
|
||||||
|
|
||||||
async fn secure_connection_established(context: &Context, contact_chat_id: ChatId) {
|
async fn secure_connection_established(context: &Context, contact_chat_id: ChatId) {
|
||||||
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await;
|
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await;
|
||||||
let contact = Contact::get_by_id(context, contact_id).await;
|
let contact = Contact::get_by_id(context, contact_id).await;
|
||||||
|
|
||||||
let addr = if let Ok(ref contact) = contact {
|
let addr = if let Ok(ref contact) = contact {
|
||||||
contact.get_addr()
|
contact.get_addr()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
22
src/sql.rs
22
src/sql.rs
@@ -11,7 +11,7 @@ use rusqlite::{Connection, Error as SqlError, OpenFlags};
|
|||||||
use thread_local_object::ThreadLocal;
|
use thread_local_object::ThreadLocal;
|
||||||
|
|
||||||
use crate::chat::{update_device_icon, update_saved_messages_icon};
|
use crate::chat::{update_device_icon, update_saved_messages_icon};
|
||||||
use crate::constants::ShowEmails;
|
use crate::constants::{ShowEmails, DC_CHAT_ID_TRASH};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::param::*;
|
use crate::param::*;
|
||||||
@@ -629,6 +629,13 @@ pub async fn housekeeping(context: &Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Err(err) = prune_tombstones(context).await {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"Houskeeping: Cannot prune message tombstones: {}", err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
info!(context, "Housekeeping done.",);
|
info!(context, "Housekeeping done.",);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1325,6 +1332,19 @@ async fn open(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn prune_tombstones(context: &Context) -> Result<()> {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"DELETE FROM msgs \
|
||||||
|
WHERE (chat_id = ? OR hidden) \
|
||||||
|
AND server_uid = 0",
|
||||||
|
paramsv![DC_CHAT_ID_TRASH],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
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
|
||||||
@@ -1 +1 @@
|
|||||||
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
|
||||||
@@ -46,9 +46,9 @@ async fn stress_functions(context: &Context) {
|
|||||||
// assert!(dc_is_configured(context) != 0, "Missing configured context");
|
// assert!(dc_is_configured(context) != 0, "Missing configured context");
|
||||||
|
|
||||||
// let setupcode = dc_create_setup_code(context);
|
// let setupcode = dc_create_setup_code(context);
|
||||||
// let setupcode_c = CString::yolo(setupcode.clone());
|
// let setupcode_c = CString::new(setupcode.clone()).unwrap();
|
||||||
// let setupfile = dc_render_setup_file(context, &setupcode).unwrap();
|
// let setupfile = dc_render_setup_file(context, &setupcode).unwrap();
|
||||||
// let setupfile_c = CString::yolo(setupfile);
|
// let setupfile_c = CString::new(setupfile).unwrap();
|
||||||
// let mut headerline_2: *const libc::c_char = ptr::null();
|
// let mut headerline_2: *const libc::c_char = ptr::null();
|
||||||
// let payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr());
|
// let payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user