mirror of
https://github.com/chatmail/core.git
synced 2026-04-10 09:32:11 +03:00
Compare commits
3 Commits
refactor/m
...
feat_ui_co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ab014f96f | ||
|
|
70a69d5313 | ||
|
|
056faad029 |
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -470,15 +470,6 @@ dependencies = [
|
||||
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug_stub_derive"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.0.0-alpha.4"
|
||||
@@ -490,7 +481,6 @@ dependencies = [
|
||||
"cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"charset 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"deltachat_derive 0.1.0",
|
||||
"escaper 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1733,11 +1723,6 @@ dependencies = [
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.6.13"
|
||||
@@ -2353,16 +2338,6 @@ name = "subtle"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.14.9"
|
||||
@@ -2393,14 +2368,6 @@ dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synom"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.10.2"
|
||||
@@ -2674,11 +2641,6 @@ name = "unicode-width"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.1.0"
|
||||
@@ -2950,7 +2912,6 @@ dependencies = [
|
||||
"checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6"
|
||||
"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c"
|
||||
"checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1"
|
||||
"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b"
|
||||
"checksum derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ac53fa6a3cda160df823a9346442525dcaf1e171999a1cf23e67067e4fd64d4"
|
||||
"checksum derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0288a23da9333c246bb18c143426074a6ae96747995c5819d2947b64cd942b37"
|
||||
"checksum derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839"
|
||||
@@ -3078,7 +3039,6 @@ dependencies = [
|
||||
"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
|
||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
"checksum quick-xml 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c2b074258da4f2ccb1c450380c5bd8e77db2508de2161f574c70930d8e880482"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
"checksum quote 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49d77c41ca8767f2f41394c11a4eebccab83da25e7cc035387a3125f02be90a3"
|
||||
"checksum r2d2 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bc42ce75d9f4447fb2a04bbe1ed5d18dd949104572850ec19b164e274919f81b"
|
||||
@@ -3146,11 +3106,9 @@ dependencies = [
|
||||
"checksum strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1c33039533f051704951680f1adfd468fd37ac46816ded0d9ee068e60f05f"
|
||||
"checksum strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47cd23f5c7dee395a00fa20135e2ec0fffcdfa151c56182966d7a3261343432e"
|
||||
"checksum subtle 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "01f40907d9ffc762709e4ff3eb4a6f6b41b650375a3f09ac92b641942b7fb082"
|
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
|
||||
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
|
||||
"checksum syn 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2ae5cd13590144ea968ba5d5520da7a4c08415861014399b5b349f74591c375f"
|
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
|
||||
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||
@@ -3180,7 +3138,6 @@ dependencies = [
|
||||
"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426"
|
||||
"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
|
||||
@@ -51,7 +51,6 @@ quick-xml = "0.15.0"
|
||||
escaper = "0.1.0"
|
||||
bitflags = "1.1.0"
|
||||
jetscii = "0.4.4"
|
||||
debug_stub_derive = "0.3.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
|
||||
@@ -393,9 +393,8 @@ int dc_set_config (dc_context_t* context, const char*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as created by dc_context_new(). For querying system values, this can be NULL.
|
||||
* @param key The key to query.
|
||||
* @return Returns current value of "key", if "key" is unset, the default
|
||||
* value is returned. The returned value must be free()'d, NULL is never
|
||||
* returned. If there is an error an empty string will be returned.
|
||||
* @return Returns current value of "key", if "key" is unset, the default value is returned.
|
||||
* The returned value must be free()'d, NULL is never returned.
|
||||
*/
|
||||
char* dc_get_config (dc_context_t* context, const char* key);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,9 @@ use deltachat::message::*;
|
||||
use deltachat::peerstate::*;
|
||||
use deltachat::qr::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::types::*;
|
||||
use deltachat::x::*;
|
||||
use deltachat::Event;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
/// Reset database tables. This function is called from Core cmdline.
|
||||
/// Argument is a bitmask, executing single or multiple actions in one call.
|
||||
@@ -86,10 +87,7 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
|
||||
info!(context, "(8) Rest but server config reset.");
|
||||
}
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0, 0);
|
||||
|
||||
1
|
||||
}
|
||||
@@ -98,7 +96,7 @@ unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) ->
|
||||
/* mainly for testing, may be called by dc_import_spec() */
|
||||
let mut success: libc::c_int = 0i32;
|
||||
let mut data: *mut libc::c_char = ptr::null_mut();
|
||||
let mut data_bytes = 0;
|
||||
let mut data_bytes: size_t = 0;
|
||||
if !(dc_read_file(
|
||||
context,
|
||||
filename,
|
||||
@@ -201,10 +199,7 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
|
||||
as_str(real_spec)
|
||||
);
|
||||
if read_cnt > 0 {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
}
|
||||
success = 1
|
||||
}
|
||||
@@ -234,7 +229,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{} [{}]",
|
||||
prefix.as_ref(),
|
||||
dc_msg_get_id(msg) as libc::c_int,
|
||||
if dc_msg_get_showpadlock(msg) {
|
||||
if 0 != dc_msg_get_showpadlock(msg) {
|
||||
"🔒"
|
||||
} else {
|
||||
""
|
||||
@@ -253,7 +248,11 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
} else {
|
||||
"[FRESH]"
|
||||
},
|
||||
if dc_msg_is_info(msg) { "[INFO]" } else { "" },
|
||||
if 0 != dc_msg_is_info(msg) {
|
||||
"[INFO]"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
statestr,
|
||||
&temp2,
|
||||
);
|
||||
@@ -452,6 +451,14 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
============================================="
|
||||
),
|
||||
},
|
||||
"open" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing");
|
||||
dc_close(context);
|
||||
ensure!(dc_open(context, arg1, None), "Open failed");
|
||||
}
|
||||
"close" => {
|
||||
dc_close(context);
|
||||
}
|
||||
"initiate-key-transfer" => {
|
||||
let setup_code = dc_initiate_key_transfer(context);
|
||||
if !setup_code.is_null() {
|
||||
@@ -496,17 +503,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
}
|
||||
}
|
||||
"export-backup" => {
|
||||
dc_imex(context, 11, Some(context.get_blobdir()), ptr::null());
|
||||
dc_imex(context, 11, context.get_blobdir(), ptr::null());
|
||||
}
|
||||
"import-backup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
|
||||
dc_imex(context, 12, Some(arg1), ptr::null());
|
||||
dc_imex(context, 12, arg1_c, ptr::null());
|
||||
}
|
||||
"export-keys" => {
|
||||
dc_imex(context, 1, Some(context.get_blobdir()), ptr::null());
|
||||
dc_imex(context, 1, context.get_blobdir(), ptr::null());
|
||||
}
|
||||
"import-keys" => {
|
||||
dc_imex(context, 2, Some(context.get_blobdir()), ptr::null());
|
||||
dc_imex(context, 2, context.get_blobdir(), ptr::null());
|
||||
}
|
||||
"export-setup" => {
|
||||
let setup_code = dc_create_setup_code(context);
|
||||
@@ -543,12 +550,11 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
}
|
||||
"get" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <key> missing.");
|
||||
let key = config::Config::from_str(&arg1)?;
|
||||
let val = context.get_config(key);
|
||||
println!("{}={:?}", key, val);
|
||||
let val = context.get_config_from_str(&arg1);
|
||||
println!("{}={:?}", &arg1.to_string(), val);
|
||||
}
|
||||
"info" => {
|
||||
println!("{:#?}", context.get_info());
|
||||
println!("{}", to_string(dc_get_info(context)));
|
||||
}
|
||||
"maybenetwork" => {
|
||||
maybe_network(context);
|
||||
@@ -620,7 +626,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
);
|
||||
}
|
||||
}
|
||||
if location::is_sending_locations_to_chat(context, 0) {
|
||||
if location::is_sending_locations_to_chat(context, 0 as uint32_t) {
|
||||
info!(context, "Location streaming enabled.");
|
||||
}
|
||||
println!("{} chats", cnt);
|
||||
@@ -656,7 +662,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
},
|
||||
);
|
||||
log_msglist(context, &msglist)?;
|
||||
if let Some(draft) = chat::get_draft(context, sel_chat.get_id())? {
|
||||
if let Ok(draft) = chat::get_draft(context, sel_chat.get_id()) {
|
||||
log_msg(context, "Draft", &draft);
|
||||
}
|
||||
|
||||
@@ -669,7 +675,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
"createchat" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
let contact_id: libc::c_int = arg1.parse()?;
|
||||
let chat_id = chat::create_by_contact_id(context, contact_id as u32)?;
|
||||
let chat_id = chat::create_by_contact_id(context, contact_id as uint32_t)?;
|
||||
|
||||
println!("Single#{} created successfully.", chat_id,);
|
||||
}
|
||||
@@ -698,10 +704,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
|
||||
let contact_id_0: libc::c_int = arg1.parse()?;
|
||||
if chat::add_contact_to_chat(
|
||||
if 0 != chat::add_contact_to_chat(
|
||||
context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
contact_id_0 as u32,
|
||||
contact_id_0 as uint32_t,
|
||||
) {
|
||||
println!("Contact added to chat.");
|
||||
} else {
|
||||
@@ -715,7 +721,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
chat::remove_contact_from_chat(
|
||||
context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
contact_id_1 as u32,
|
||||
contact_id_1 as uint32_t,
|
||||
)?;
|
||||
|
||||
println!("Contact added to chat.");
|
||||
@@ -849,7 +855,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
0 as libc::c_uint
|
||||
};
|
||||
|
||||
let msglist = context.search_msgs(chat, arg1);
|
||||
let msglist = dc_search_msgs(context, chat, arg1_c);
|
||||
|
||||
log_msglist(context, &msglist)?;
|
||||
println!("{} messages.", msglist.len());
|
||||
@@ -912,7 +918,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
println!("{}", as_str(res));
|
||||
}
|
||||
"listfresh" => {
|
||||
let msglist = context.get_fresh_msgs();
|
||||
let msglist = dc_get_fresh_msgs(context);
|
||||
|
||||
log_msglist(context, &msglist)?;
|
||||
print!("{} fresh messages.", msglist.len());
|
||||
@@ -1018,17 +1024,16 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
|
||||
res.get_text2()
|
||||
);
|
||||
}
|
||||
// TODO: implement this again, unclear how to match this through though, without writing a parser.
|
||||
// "event" => {
|
||||
// ensure!(!arg1.is_empty(), "Argument <id> missing.");
|
||||
// let event = arg1.parse()?;
|
||||
// let event = Event::from_u32(event).ok_or(format_err!("Event::from_u32({})", event))?;
|
||||
// let r = context.call_cb(event, 0 as libc::uintptr_t, 0 as libc::uintptr_t);
|
||||
// println!(
|
||||
// "Sending event {:?}({}), received value {}.",
|
||||
// event, event as usize, r as libc::c_int,
|
||||
// );
|
||||
// }
|
||||
"event" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <id> missing.");
|
||||
let event = arg1.parse()?;
|
||||
let event = Event::from_u32(event).ok_or(format_err!("Event::from_u32({})", event))?;
|
||||
let r = context.call_cb(event, 0 as uintptr_t, 0 as uintptr_t);
|
||||
println!(
|
||||
"Sending event {:?}({}), received value {}.",
|
||||
event, event as usize, r as libc::c_int,
|
||||
);
|
||||
}
|
||||
"fileinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||
|
||||
|
||||
@@ -15,20 +15,21 @@ extern crate rusqlite;
|
||||
|
||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use deltachat::config;
|
||||
use deltachat::configure::*;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::job::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::types::*;
|
||||
use deltachat::x::*;
|
||||
use deltachat::Event;
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
use rustyline::config::OutputStreamType;
|
||||
use rustyline::error::ReadlineError;
|
||||
@@ -43,75 +44,96 @@ use self::cmdline::*;
|
||||
|
||||
// Event Handler
|
||||
|
||||
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
|
||||
unsafe extern "C" fn receive_event(
|
||||
_context: &Context,
|
||||
event: Event,
|
||||
data1: uintptr_t,
|
||||
data2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
match event {
|
||||
Event::GetString { .. } => {}
|
||||
Event::Info(msg) => {
|
||||
Event::GET_STRING => {}
|
||||
Event::INFO => {
|
||||
/* do not show the event as this would fill the screen */
|
||||
println!("{}", msg);
|
||||
println!("{}", to_string(data2 as *const _),);
|
||||
}
|
||||
Event::SmtpConnected(msg) => {
|
||||
println!("[DC_EVENT_SMTP_CONNECTED] {}", msg);
|
||||
Event::SMTP_CONNECTED => {
|
||||
println!("[DC_EVENT_SMTP_CONNECTED] {}", to_string(data2 as *const _));
|
||||
}
|
||||
Event::ImapConnected(msg) => {
|
||||
println!("[DC_EVENT_IMAP_CONNECTED] {}", msg);
|
||||
Event::IMAP_CONNECTED => {
|
||||
println!("[DC_EVENT_IMAP_CONNECTED] {}", to_string(data2 as *const _),);
|
||||
}
|
||||
Event::SmtpMessageSent(msg) => {
|
||||
println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg);
|
||||
}
|
||||
Event::Warning(msg) => {
|
||||
println!("[Warning] {}", msg);
|
||||
}
|
||||
Event::Error(msg) => {
|
||||
println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg);
|
||||
}
|
||||
Event::ErrorNetwork(msg) => {
|
||||
println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg);
|
||||
}
|
||||
Event::ErrorSelfNotInGroup(msg) => {
|
||||
println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg);
|
||||
}
|
||||
Event::MsgsChanged { chat_id, msg_id } => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m",
|
||||
chat_id, msg_id,
|
||||
Event::SMTP_MESSAGE_SENT => {
|
||||
println!(
|
||||
"[DC_EVENT_SMTP_MESSAGE_SENT] {}",
|
||||
to_string(data2 as *const _),
|
||||
);
|
||||
}
|
||||
Event::ContactsChanged(_) => {
|
||||
Event::WARNING => {
|
||||
println!("[Warning] {}", to_string(data2 as *const _),);
|
||||
}
|
||||
Event::ERROR => {
|
||||
println!(
|
||||
"\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m",
|
||||
to_string(data2 as *const _),
|
||||
);
|
||||
}
|
||||
Event::ERROR_NETWORK => {
|
||||
println!(
|
||||
"\x1b[31m[DC_EVENT_ERROR_NETWORK] first={}, msg={}\x1b[0m",
|
||||
data1 as usize,
|
||||
to_string(data2 as *const _),
|
||||
);
|
||||
}
|
||||
Event::ERROR_SELF_NOT_IN_GROUP => {
|
||||
println!(
|
||||
"\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m",
|
||||
to_string(data2 as *const _),
|
||||
);
|
||||
}
|
||||
Event::MSGS_CHANGED => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED({}, {})}}\n\x1b[0m",
|
||||
data1 as usize, data2 as usize,
|
||||
);
|
||||
}
|
||||
Event::CONTACTS_CHANGED => {
|
||||
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
|
||||
}
|
||||
Event::LocationChanged(contact) => {
|
||||
Event::LOCATION_CHANGED => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m",
|
||||
contact,
|
||||
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={})}}\n\x1b[0m",
|
||||
data1 as usize,
|
||||
);
|
||||
}
|
||||
Event::ConfigureProgress(progress) => {
|
||||
Event::CONFIGURE_PROGRESS => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
|
||||
progress,
|
||||
data1 as usize,
|
||||
);
|
||||
}
|
||||
Event::ImexProgress(progress) => {
|
||||
Event::IMEX_PROGRESS => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
|
||||
progress,
|
||||
data1 as usize,
|
||||
);
|
||||
}
|
||||
Event::ImexFileWritten(file) => {
|
||||
Event::IMEX_FILE_WRITTEN => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
|
||||
file.display()
|
||||
to_string(data1 as *const _)
|
||||
);
|
||||
}
|
||||
Event::ChatModified(chat) => {
|
||||
Event::CHAT_MODIFIED => {
|
||||
print!(
|
||||
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
|
||||
chat
|
||||
data1 as usize,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
|
||||
print!(
|
||||
"\x1b[33m{{Received {:?}({}, {})}}\n\x1b[0m",
|
||||
event, data1 as usize, data2 as usize,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,15 +387,15 @@ impl Highlighter for DcHelper {
|
||||
impl Helper for DcHelper {}
|
||||
|
||||
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
|
||||
if args.len() < 2 {
|
||||
let mut context = dc_context_new(Some(receive_event), ptr::null_mut(), Some("CLI".into()));
|
||||
|
||||
if args.len() == 2 {
|
||||
if unsafe { !dc_open(&mut context, &args[1], None) } {
|
||||
println!("Error: Cannot open {}.", args[0],);
|
||||
}
|
||||
} else if args.len() != 1 {
|
||||
println!("Error: Bad arguments, expected [db-name].");
|
||||
return Err(format_err!("No db-name specified"));
|
||||
}
|
||||
let context = Context::new(
|
||||
Box::new(receive_event),
|
||||
"CLI".into(),
|
||||
Path::new(&args[1]).to_path_buf(),
|
||||
)?;
|
||||
|
||||
println!("Delta Chat Core is awaiting your commands.");
|
||||
|
||||
@@ -473,7 +495,7 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
|
||||
configure(&ctx.read().unwrap());
|
||||
}
|
||||
"oauth2" => {
|
||||
if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) {
|
||||
if let Some(addr) = ctx.read().unwrap().get_config(&config::Config::Addr) {
|
||||
let oauth2_url = dc_get_oauth2_url(
|
||||
&ctx.read().unwrap(),
|
||||
&addr,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
extern crate deltachat;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::{thread, time};
|
||||
use tempfile::tempdir;
|
||||
@@ -8,43 +9,42 @@ use deltachat::chat;
|
||||
use deltachat::chatlist::*;
|
||||
use deltachat::config;
|
||||
use deltachat::configure::*;
|
||||
use deltachat::constants::Event;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::job::{
|
||||
perform_imap_fetch, perform_imap_idle, perform_imap_jobs, perform_smtp_idle, perform_smtp_jobs,
|
||||
};
|
||||
use deltachat::Event;
|
||||
|
||||
fn cb(_ctx: &Context, event: Event) -> usize {
|
||||
print!("[{:?}]", event);
|
||||
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
|
||||
println!("[{:?}]", event);
|
||||
|
||||
match event {
|
||||
Event::ConfigureProgress(progress) => {
|
||||
print!(" progress: {}\n", progress);
|
||||
Event::CONFIGURE_PROGRESS => {
|
||||
println!(" progress: {}", data1);
|
||||
0
|
||||
}
|
||||
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
|
||||
print!(" {}\n", msg);
|
||||
0
|
||||
}
|
||||
_ => {
|
||||
print!("\n");
|
||||
Event::INFO | Event::WARNING | Event::ERROR | Event::ERROR_NETWORK => {
|
||||
println!(
|
||||
" {}",
|
||||
unsafe { CStr::from_ptr(data2 as *const _) }
|
||||
.to_str()
|
||||
.unwrap()
|
||||
);
|
||||
0
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
println!("creating database {:?}", dbfile);
|
||||
let ctx =
|
||||
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
|
||||
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
|
||||
let running = Arc::new(RwLock::new(true));
|
||||
let info = ctx.get_info();
|
||||
let info = dc_get_info(&ctx);
|
||||
let info_s = CStr::from_ptr(info);
|
||||
let duration = time::Duration::from_millis(4000);
|
||||
println!("info: {:#?}", info);
|
||||
println!("info: {}", info_s.to_str().unwrap());
|
||||
|
||||
let ctx = Arc::new(ctx);
|
||||
let ctx1 = ctx.clone();
|
||||
@@ -73,6 +73,13 @@ fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
|
||||
println!("opening database {:?}", dbfile);
|
||||
|
||||
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
|
||||
|
||||
println!("configuring");
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
assert_eq!(args.len(), 2, "missing password");
|
||||
@@ -123,5 +130,6 @@ fn main() {
|
||||
t2.join().unwrap();
|
||||
|
||||
println!("closing");
|
||||
dc_close(&ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,11 @@ def test_callback_None2int():
|
||||
clear_context_callback(ctx)
|
||||
|
||||
|
||||
def test_dc_close_events(tmpdir):
|
||||
ctx = ffi.gc(
|
||||
capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL),
|
||||
lib.dc_context_unref,
|
||||
)
|
||||
def test_dc_close_events():
|
||||
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
|
||||
evlog = EventLogger(ctx)
|
||||
evlog.set_timeout(5)
|
||||
set_context_callback(
|
||||
ctx,
|
||||
lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2)
|
||||
)
|
||||
p = tmpdir.join("hello.db")
|
||||
lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL)
|
||||
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
|
||||
capi.lib.dc_close(ctx)
|
||||
|
||||
def find(info_string):
|
||||
|
||||
@@ -7,6 +7,7 @@ use mmime::mailimf_types::*;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::dc_tools::as_str;
|
||||
use crate::key::*;
|
||||
|
||||
/// Possible values for encryption preference
|
||||
@@ -63,8 +64,11 @@ impl Aheader {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
|
||||
if header.is_null() {
|
||||
pub fn from_imffields(
|
||||
wanted_from: *const libc::c_char,
|
||||
header: *const mailimf_fields,
|
||||
) -> Option<Self> {
|
||||
if wanted_from.is_null() || header.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -90,7 +94,7 @@ impl Aheader {
|
||||
|
||||
match Self::from_str(value) {
|
||||
Ok(test) => {
|
||||
if addr_cmp(&test.addr, wanted_from) {
|
||||
if addr_cmp(&test.addr, as_str(wanted_from)) {
|
||||
if fine_header.is_none() {
|
||||
fine_header = Some(test);
|
||||
} else {
|
||||
|
||||
289
src/chat.rs
289
src/chat.rs
@@ -1,28 +1,26 @@
|
||||
use std::ffi::CString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr;
|
||||
|
||||
use crate::chatlist::*;
|
||||
use crate::config::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::*;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
use std::ptr;
|
||||
|
||||
/// An object representing a single chat in memory.
|
||||
/// Chat objects are created using eg. `Chat::load_from_db`
|
||||
/// and are not updated on database changes;
|
||||
/// if you want an update, you have to recreate the object.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct Chat {
|
||||
pub id: u32,
|
||||
pub typ: Chattype,
|
||||
@@ -61,13 +59,15 @@ impl Chat {
|
||||
|
||||
match res {
|
||||
Err(err @ crate::error::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Err(err),
|
||||
Err(err) => {
|
||||
error!(
|
||||
context,
|
||||
"chat: failed to load from db {}: {:?}", chat_id, err
|
||||
);
|
||||
Err(err)
|
||||
}
|
||||
Err(err) => match err {
|
||||
_ => {
|
||||
error!(
|
||||
context,
|
||||
"chat: failed to load from db {}: {:?}", chat_id, err
|
||||
);
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
Ok(mut chat) => {
|
||||
match chat.id {
|
||||
DC_CHAT_ID_DEADDROP => {
|
||||
@@ -140,12 +140,13 @@ impl Chat {
|
||||
if self.typ == Chattype::Single {
|
||||
return context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT c.addr FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON c.id=cc.contact_id \
|
||||
WHERE cc.chat_id=?;",
|
||||
params![self.id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_else(|| "Err".into());
|
||||
}
|
||||
@@ -182,7 +183,7 @@ impl Chat {
|
||||
pub fn get_profile_image(&self, context: &Context) -> Option<PathBuf> {
|
||||
if let Some(image_rel) = self.param.get(Param::ProfileImage) {
|
||||
if !image_rel.is_empty() {
|
||||
return Some(dc_get_abs_path(context, image_rel));
|
||||
return Some(dc_get_abs_path_safe(context, image_rel));
|
||||
}
|
||||
} else if self.typ == Chattype::Single {
|
||||
let contacts = get_chat_contacts(context, self.id);
|
||||
@@ -259,14 +260,16 @@ impl Chat {
|
||||
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
|
||||
&& 0 == is_contact_in_chat(context, self.id, 1 as u32)
|
||||
{
|
||||
emit_event!(
|
||||
log_event!(
|
||||
context,
|
||||
Event::ErrorSelfNotInGroup("Cannot send message; self not in group.".into())
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot send message; self not in group.",
|
||||
);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if let Some(from) = context.get_config(Config::ConfiguredAddr) {
|
||||
if let Some(from) = context.sql.get_config(context, "configured_addr") {
|
||||
let new_rfc724_mid = {
|
||||
let grpid = match self.typ {
|
||||
Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()),
|
||||
@@ -276,10 +279,11 @@ impl Chat {
|
||||
};
|
||||
|
||||
if self.typ == Chattype::Single {
|
||||
if let Some(id) = context.sql.query_get_value(
|
||||
if let Some(id) = context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
|
||||
params![self.id as i32],
|
||||
0,
|
||||
) {
|
||||
to_id = id;
|
||||
} else {
|
||||
@@ -500,10 +504,7 @@ pub fn create_by_msg_id(context: &Context, msg_id: u32) -> Result<u32, Error> {
|
||||
}
|
||||
|
||||
if send_event {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0, 0);
|
||||
}
|
||||
|
||||
ensure!(chat_id > 0, "failed to load create chat");
|
||||
@@ -543,10 +544,7 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result<u32, E
|
||||
}
|
||||
};
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0i32 as uintptr_t, 0i32 as uintptr_t);
|
||||
|
||||
Ok(chat_id)
|
||||
}
|
||||
@@ -639,10 +637,11 @@ pub fn prepare_msg<'a>(
|
||||
|
||||
msg.state = MessageState::OutPreparing;
|
||||
let msg_id = prepare_msg_common(context, chat_id, msg)?;
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: msg.chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
msg.chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
|
||||
Ok(msg_id)
|
||||
}
|
||||
@@ -725,12 +724,13 @@ fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Res
|
||||
}
|
||||
|
||||
fn last_msg_in_chat_encrypted(context: &Context, sql: &Sql, chat_id: u32) -> bool {
|
||||
let packed: Option<String> = sql.query_get_value(
|
||||
let packed: Option<String> = sql.query_row_col(
|
||||
context,
|
||||
"SELECT param \
|
||||
FROM msgs WHERE timestamp=(SELECT MAX(timestamp) FROM msgs WHERE chat_id=?) \
|
||||
ORDER BY id DESC;",
|
||||
params![chat_id as i32],
|
||||
0,
|
||||
);
|
||||
|
||||
if let Some(ref packed) = packed {
|
||||
@@ -793,13 +793,14 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
|
||||
"Failed to initiate send job"
|
||||
);
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: msg.chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
msg.chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
context.call_cb(Event::LocationChanged(Some(DC_CONTACT_ID_SELF)));
|
||||
context.call_cb(Event::LOCATION_CHANGED, DC_CONTACT_ID_SELF as usize, 0);
|
||||
}
|
||||
|
||||
if 0 == chat_id {
|
||||
@@ -847,7 +848,7 @@ pub unsafe fn set_draft(context: &Context, chat_id: u32, msg: Option<&mut Messag
|
||||
return;
|
||||
}
|
||||
if set_draft_raw(context, chat_id, msg) {
|
||||
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
|
||||
context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0i32 as uintptr_t);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -871,7 +872,7 @@ unsafe fn set_draft_raw(context: &Context, chat_id: u32, mut msg: Option<&mut Me
|
||||
} else if msgtype_has_file(msg.type_0) {
|
||||
if let Some(path_filename) = msg.param.get(Param::File) {
|
||||
let mut path_filename = path_filename.to_string();
|
||||
if dc_msg_is_increation(msg) && !dc_is_blobdir_path(context, &path_filename) {
|
||||
if 0 != dc_msg_is_increation(msg) && !dc_is_blobdir_path(context, &path_filename) {
|
||||
OK_TO_CONTINUE = false;
|
||||
} else if !dc_make_rel_and_copy(context, &mut path_filename) {
|
||||
OK_TO_CONTINUE = false;
|
||||
@@ -911,21 +912,21 @@ unsafe fn set_draft_raw(context: &Context, chat_id: u32, mut msg: Option<&mut Me
|
||||
fn get_draft_msg_id(context: &Context, chat_id: u32) -> u32 {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, i32>(
|
||||
.query_row_col::<_, i32>(
|
||||
context,
|
||||
"SELECT id FROM msgs WHERE chat_id=? AND state=?;",
|
||||
params![chat_id as i32, MessageState::OutDraft],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default() as u32
|
||||
}
|
||||
|
||||
pub fn get_draft(context: &Context, chat_id: u32) -> Result<Option<Message>, Error> {
|
||||
pub unsafe fn get_draft(context: &Context, chat_id: u32) -> Result<Message, Error> {
|
||||
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
|
||||
let draft_msg_id = get_draft_msg_id(context, chat_id);
|
||||
if draft_msg_id == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(dc_msg_load_from_db(context, draft_msg_id)?))
|
||||
ensure!(draft_msg_id != 0, "Invalid draft message ID");
|
||||
|
||||
dc_msg_load_from_db(context, draft_msg_id)
|
||||
}
|
||||
|
||||
pub fn get_chat_msgs(context: &Context, chat_id: u32, flags: u32, marker1before: u32) -> Vec<u32> {
|
||||
@@ -1006,10 +1007,11 @@ pub fn get_chat_msgs(context: &Context, chat_id: u32, flags: u32, marker1before:
|
||||
pub fn get_msg_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, i32>(
|
||||
.query_row_col::<_, i32>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM msgs WHERE chat_id=?;",
|
||||
params![chat_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
@@ -1017,13 +1019,14 @@ pub fn get_msg_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
pub fn get_fresh_msg_cnt(context: &Context, chat_id: u32) -> usize {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, i32>(
|
||||
.query_row_col::<_, i32>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM msgs \
|
||||
WHERE state=10 \
|
||||
AND hidden=0 \
|
||||
AND chat_id=?;",
|
||||
params![chat_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
@@ -1044,10 +1047,7 @@ pub fn marknoticed_chat(context: &Context, chat_id: u32) -> Result<(), Error> {
|
||||
params![chat_id as i32],
|
||||
)?;
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1069,10 +1069,7 @@ pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> {
|
||||
params![],
|
||||
)?;
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
msg_id: 0,
|
||||
chat_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1177,10 +1174,7 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err
|
||||
params![archive, chat_id as i32],
|
||||
)?;
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
msg_id: 0,
|
||||
chat_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1222,10 +1216,7 @@ pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> {
|
||||
params![chat_id as i32],
|
||||
)?;
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
msg_id: 0,
|
||||
chat_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
|
||||
job_kill_action(context, Action::Housekeeping);
|
||||
job_add(context, Action::Housekeeping, 0, Params::new(), 10);
|
||||
@@ -1287,16 +1278,13 @@ pub unsafe fn create_group_chat(
|
||||
let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid);
|
||||
|
||||
if chat_id != 0 {
|
||||
if add_to_chat_contacts_table(context, chat_id, 1) {
|
||||
if 0 != add_to_chat_contacts_table(context, chat_id, 1) {
|
||||
let mut draft_msg = dc_msg_new(Viewtype::Text);
|
||||
dc_msg_set_text(&mut draft_msg, draft_txt.as_ptr());
|
||||
set_draft_raw(context, chat_id, Some(&mut draft_msg));
|
||||
}
|
||||
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
msg_id: 0,
|
||||
chat_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
}
|
||||
|
||||
Ok(chat_id)
|
||||
@@ -1304,7 +1292,8 @@ pub unsafe fn create_group_chat(
|
||||
|
||||
/* you MUST NOT modify this or the following strings */
|
||||
// Context functions to work with chats
|
||||
pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u32) -> bool {
|
||||
// TODO should return bool /rtn
|
||||
pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u32) -> libc::c_int {
|
||||
// add a contact to a chat; the function does not check the type or if any of the record exist or are already
|
||||
// added to the chat!
|
||||
sql::execute(
|
||||
@@ -1313,26 +1302,27 @@ pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u
|
||||
"INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)",
|
||||
params![chat_id as i32, contact_id as i32],
|
||||
)
|
||||
.is_ok()
|
||||
.is_ok() as libc::c_int
|
||||
}
|
||||
|
||||
pub unsafe fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> bool {
|
||||
pub unsafe fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> libc::c_int {
|
||||
add_contact_to_chat_ex(context, chat_id, contact_id, 0)
|
||||
}
|
||||
|
||||
// TODO should return bool /rtn
|
||||
#[allow(non_snake_case)]
|
||||
pub fn add_contact_to_chat_ex(
|
||||
context: &Context,
|
||||
chat_id: u32,
|
||||
contact_id: u32,
|
||||
flags: libc::c_int,
|
||||
) -> bool {
|
||||
) -> libc::c_int {
|
||||
let mut OK_TO_CONTINUE = true;
|
||||
let mut success = false;
|
||||
let mut success: libc::c_int = 0;
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
|
||||
if contact.is_err() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
let mut msg = dc_msg_new_untyped();
|
||||
|
||||
@@ -1345,11 +1335,11 @@ pub fn add_contact_to_chat_ex(
|
||||
|| !Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF)
|
||||
{
|
||||
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
|
||||
emit_event!(
|
||||
log_event!(
|
||||
context,
|
||||
Event::ErrorSelfNotInGroup(
|
||||
"Cannot add contact to group; self not in group.".into()
|
||||
)
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot add contact to group; self not in group.",
|
||||
);
|
||||
} else {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
@@ -1369,7 +1359,7 @@ pub fn add_contact_to_chat_ex(
|
||||
|
||||
if 0 != is_contact_in_chat(context, chat_id, contact_id) {
|
||||
if 0 == flags & 0x1 {
|
||||
success = true;
|
||||
success = 1;
|
||||
OK_TO_CONTINUE = false;
|
||||
}
|
||||
} else {
|
||||
@@ -1384,7 +1374,7 @@ pub fn add_contact_to_chat_ex(
|
||||
}
|
||||
}
|
||||
if OK_TO_CONTINUE {
|
||||
if !add_to_chat_contacts_table(context, chat_id, contact_id) {
|
||||
if 0 == add_to_chat_contacts_table(context, chat_id, contact_id) {
|
||||
OK_TO_CONTINUE = false;
|
||||
}
|
||||
}
|
||||
@@ -1402,13 +1392,14 @@ pub fn add_contact_to_chat_ex(
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
msg.param.set_int(Param::Arg2, flags);
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
}
|
||||
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
|
||||
success = true;
|
||||
context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0 as uintptr_t);
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1490,11 +1481,11 @@ pub unsafe fn remove_contact_from_chat(
|
||||
if let Ok(chat) = Chat::load_from_db(context, chat_id) {
|
||||
if real_group_exists(context, chat_id) {
|
||||
if !(is_contact_in_chat(context, chat_id, 1 as u32) == 1) {
|
||||
emit_event!(
|
||||
log_event!(
|
||||
context,
|
||||
Event::ErrorSelfNotInGroup(
|
||||
"Cannot remove contact from chat; self not in group.".into()
|
||||
)
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot remove contact from chat; self not in group.",
|
||||
);
|
||||
} else {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
@@ -1520,10 +1511,11 @@ pub unsafe fn remove_contact_from_chat(
|
||||
msg.param.set_int(Param::Cmd, 5);
|
||||
msg.param.set(Param::Arg, contact.get_addr());
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
if sql::execute(
|
||||
@@ -1534,7 +1526,7 @@ pub unsafe fn remove_contact_from_chat(
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@@ -1586,9 +1578,11 @@ pub unsafe fn set_chat_name(
|
||||
if &chat.name == new_name.as_ref() {
|
||||
success = true;
|
||||
} else if !(is_contact_in_chat(context, chat_id, 1) == 1) {
|
||||
emit_event!(
|
||||
log_event!(
|
||||
context,
|
||||
Event::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into())
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot set chat name; self not in group",
|
||||
);
|
||||
} else {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
@@ -1617,12 +1611,17 @@ pub unsafe fn set_chat_name(
|
||||
msg.param.set(Param::Arg, &chat.name);
|
||||
}
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id,
|
||||
});
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
chat_id as uintptr_t,
|
||||
msg.id as uintptr_t,
|
||||
);
|
||||
}
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@@ -1648,11 +1647,11 @@ pub fn set_chat_profile_image(
|
||||
if real_group_exists(context, chat_id) {
|
||||
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
|
||||
if !(is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) == 1i32) {
|
||||
emit_event!(
|
||||
log_event!(
|
||||
context,
|
||||
Event::ErrorSelfNotInGroup(
|
||||
"Cannot set chat profile image; self not in group.".into()
|
||||
)
|
||||
Event::ERROR_SELF_NOT_IN_GROUP,
|
||||
0,
|
||||
"Cannot set chat profile image; self not in group.",
|
||||
);
|
||||
bail!("Failed to set profile image");
|
||||
}
|
||||
@@ -1670,8 +1669,7 @@ pub fn set_chat_profile_image(
|
||||
if chat.update_param(context).is_ok() {
|
||||
if chat.is_promoted() {
|
||||
let mut msg = dc_msg_new_untyped();
|
||||
msg.param
|
||||
.set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
|
||||
msg.param.set_int(Param::Cmd, DC_CMD_GROUPIMAGE_CHANGED);
|
||||
msg.type_0 = Viewtype::Text;
|
||||
msg.text = Some(context.stock_system_msg(
|
||||
if new_image_rel == "" {
|
||||
@@ -1686,15 +1684,9 @@ pub fn set_chat_profile_image(
|
||||
DC_CONTACT_ID_SELF,
|
||||
));
|
||||
msg.id = send_msg(context, chat_id, &mut msg).unwrap_or_default();
|
||||
emit_event!(
|
||||
context,
|
||||
Event::MsgsChanged {
|
||||
chat_id,
|
||||
msg_id: msg.id
|
||||
}
|
||||
);
|
||||
emit_event!(context, Event::MSGS_CHANGED, chat_id, msg.id);
|
||||
}
|
||||
emit_event!(context, Event::ChatModified(chat_id));
|
||||
emit_event!(context, Event::CHAT_MODIFIED, chat_id, 0);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -1788,20 +1780,22 @@ pub unsafe fn forward_msgs(
|
||||
}
|
||||
|
||||
for i in (0..created_db_entries.len()).step_by(2) {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: created_db_entries[i],
|
||||
msg_id: created_db_entries[i + 1],
|
||||
});
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
created_db_entries[i] as uintptr_t,
|
||||
created_db_entries[i + 1] as uintptr_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> libc::c_int {
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;",
|
||||
params![chat_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -1811,10 +1805,11 @@ pub fn get_chat_cnt(context: &Context) -> usize {
|
||||
/* no database, no chats - this is no error (needed eg. for information) */
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(
|
||||
.query_row_col::<_, isize>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default() as usize
|
||||
} else {
|
||||
@@ -1872,51 +1867,9 @@ pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
|
||||
as_str(rfc724_mid),
|
||||
);
|
||||
unsafe { free(rfc724_mid as *mut libc::c_void) };
|
||||
context.call_cb(Event::MsgsChanged { chat_id, msg_id });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_get_draft_no_draft() {
|
||||
let t = dummy_context();
|
||||
let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
|
||||
let draft = get_draft(&t.ctx, chat_id).unwrap();
|
||||
assert!(draft.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_draft_special_chat_id() {
|
||||
let t = dummy_context();
|
||||
let draft = get_draft(&t.ctx, DC_CHAT_ID_LAST_SPECIAL);
|
||||
assert!(draft.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_draft_no_chat() {
|
||||
// This is a weird case, maybe this should be an error but we
|
||||
// do not get this info from the database currently.
|
||||
let t = dummy_context();
|
||||
let draft = get_draft(&t.ctx, 42).unwrap();
|
||||
assert!(draft.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_draft() {
|
||||
unsafe {
|
||||
let t = dummy_context();
|
||||
let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
|
||||
let mut msg = dc_msg_new(Viewtype::Text);
|
||||
dc_msg_set_text(&mut msg, b"hello\x00" as *const u8 as *const libc::c_char);
|
||||
set_draft(&t.ctx, chat_id, Some(&mut msg));
|
||||
let draft = get_draft(&t.ctx, chat_id).unwrap().unwrap();
|
||||
let msg_text = dc_msg_get_text(&msg);
|
||||
let draft_text = dc_msg_get_text(&draft);
|
||||
assert_eq!(as_str(msg_text), as_str(draft_text));
|
||||
}
|
||||
}
|
||||
context.call_cb(
|
||||
Event::MSGS_CHANGED,
|
||||
chat_id as uintptr_t,
|
||||
msg_id as uintptr_t,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ use crate::stock::StockMessage;
|
||||
/// first entry and only present on new messages, there is the rough idea that it can be optionally always
|
||||
/// present and sorted into the list by date. Rendering the deaddrop in the described way
|
||||
/// would not add extra work in the UI then.
|
||||
#[derive(Debug)]
|
||||
pub struct Chatlist {
|
||||
/// Stores pairs of `chat_id, message_id`
|
||||
ids: Vec<(u32, u32)>,
|
||||
@@ -302,10 +301,11 @@ impl Chatlist {
|
||||
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -315,7 +315,7 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
|
||||
// only few fresh messages.
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
WHERE m.state=10 \
|
||||
@@ -323,6 +323,7 @@ fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
|
||||
AND c.blocked=2 \
|
||||
ORDER BY m.timestamp DESC, m.id DESC;",
|
||||
params![],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use strum::{EnumProperty, IntoEnumIterator};
|
||||
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
|
||||
|
||||
@@ -8,9 +10,11 @@ use crate::error::Error;
|
||||
use crate::job::*;
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
pub const CONFIG_UI_PREFIX: &str = "ui.";
|
||||
|
||||
/// The available configuration keys.
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty,
|
||||
Debug, Clone, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty,
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Config {
|
||||
@@ -65,19 +69,26 @@ pub enum Config {
|
||||
SysMsgsizeMaxRecommended,
|
||||
#[strum(serialize = "sys.config_keys")]
|
||||
SysConfigKeys,
|
||||
Ui(String)
|
||||
}
|
||||
|
||||
impl Context {
|
||||
|
||||
pub fn get_config_from_str(&self, key: &str) -> Option<String> {
|
||||
self.get_config(&config_from_str(key))
|
||||
}
|
||||
|
||||
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
|
||||
pub fn get_config(&self, key: Config) -> Option<String> {
|
||||
pub fn get_config(&self, key: &Config) -> Option<String> {
|
||||
let value = match key {
|
||||
Config::Selfavatar => {
|
||||
let rel_path = self.sql.get_config(self, key);
|
||||
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
|
||||
rel_path.map(|p| dc_get_abs_path_safe(self, &p).to_str().unwrap().to_string())
|
||||
}
|
||||
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
|
||||
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
|
||||
Config::SysConfigKeys => Some(get_config_keys_string()),
|
||||
Config::Ui(key) => self.sql.get_config(self, format!("{}{}", CONFIG_UI_PREFIX, key)),
|
||||
_ => self.sql.get_config(self, key),
|
||||
};
|
||||
|
||||
@@ -92,6 +103,13 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_config_from_str(&self, key: &str, value: Option<&str>) -> Result<(), &str> {
|
||||
if self.sql.set_config(self, config_from_str(key), value).is_err() {
|
||||
return Err("Sql error");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
|
||||
@@ -125,7 +143,11 @@ impl Context {
|
||||
};
|
||||
|
||||
self.sql.set_config(self, key, val)
|
||||
}
|
||||
},
|
||||
Config::Ui(key) => {
|
||||
let key = format!("{}{}", CONFIG_UI_PREFIX, key);
|
||||
self.sql.set_config(self, key, value)
|
||||
},
|
||||
_ => self.sql.set_config(self, key, value),
|
||||
}
|
||||
}
|
||||
@@ -142,12 +164,23 @@ fn get_config_keys_string() -> String {
|
||||
format!(" {} ", keys)
|
||||
}
|
||||
|
||||
fn config_from_str(key: &str) -> Result<Config, &str> {
|
||||
if key.starts_with(CONFIG_UI_PREFIX) {
|
||||
Config::Ui(key[CONFIG_UI_PREFIX.len()-1..].to_string())
|
||||
} else {
|
||||
if let Ok(config) = Config::from_str(key) {
|
||||
config
|
||||
} else {
|
||||
Err("invalid key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_to_string() {
|
||||
@@ -165,4 +198,20 @@ mod tests {
|
||||
fn test_default_prop() {
|
||||
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_from_str() {
|
||||
assert_eq!(config_from_str("addr"), Config::Addr);
|
||||
assert_eq!(config_from_str("addrxyz"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_config_from_str() {
|
||||
let t = dummy_context();
|
||||
assert_eq!(t.ctx.set_config_from_str("addr", Some("foo@bar.bar")), Ok(()));
|
||||
assert_eq!(t.ctx.get_config_from_str("addr").unwrap(), "foo@bar.bar");
|
||||
|
||||
assert_eq!(t.ctx.set_config_from_str("ui.desktop.some_string", Some("foobar")), Ok(()));
|
||||
assert_eq!(t.ctx.get_config_from_str("ui.desktop.some_string").unwrap(), "foobar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
@@ -121,13 +120,13 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
13 => {
|
||||
let val_lower = val.to_lowercase();
|
||||
if val_lower == "ssl" {
|
||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
moz_ac.out.server_flags |= 0x200
|
||||
}
|
||||
if val_lower == "starttls" {
|
||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32
|
||||
moz_ac.out.server_flags |= 0x100
|
||||
}
|
||||
if val_lower == "plain" {
|
||||
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
||||
moz_ac.out.server_flags |= 0x400
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -140,13 +139,13 @@ fn moz_autoconfigure_text_cb<B: std::io::BufRead>(
|
||||
13 => {
|
||||
let val_lower = val.to_lowercase();
|
||||
if val_lower == "ssl" {
|
||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
moz_ac.out.server_flags |= 0x20000
|
||||
}
|
||||
if val_lower == "starttls" {
|
||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32
|
||||
moz_ac.out.server_flags |= 0x10000
|
||||
}
|
||||
if val_lower == "plain" {
|
||||
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
moz_ac.out.server_flags |= 0x40000
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::login_param::LoginParam;
|
||||
@@ -168,9 +167,9 @@ unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_au
|
||||
outlk_ad.out.mail_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.mail_port = port;
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
|
||||
outlk_ad.out.server_flags |= 0x200
|
||||
} else if 0 != ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
|
||||
outlk_ad.out.server_flags |= 0x400
|
||||
}
|
||||
outlk_ad.out_imap_set = 1
|
||||
} else if strcasecmp(
|
||||
@@ -182,9 +181,9 @@ unsafe fn outlk_autodiscover_endtag_cb(event: &BytesEnd, outlk_ad: &mut outlk_au
|
||||
outlk_ad.out.send_server = to_string(outlk_ad.config[2]);
|
||||
outlk_ad.out.send_port = port;
|
||||
if 0 != ssl_on {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
|
||||
outlk_ad.out.server_flags |= 0x20000
|
||||
} else if 0 != ssl_off {
|
||||
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
outlk_ad.out.server_flags |= 0x40000
|
||||
}
|
||||
outlk_ad.out_smtp_set = 1
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::constants::Event;
|
||||
use crate::constants::DC_CREATE_MVBOX;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
@@ -9,6 +10,7 @@ use crate::job::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::oauth2::*;
|
||||
use crate::param::Params;
|
||||
use crate::types::*;
|
||||
|
||||
mod auto_outlook;
|
||||
use auto_outlook::outlk_autodiscover;
|
||||
@@ -18,10 +20,14 @@ use auto_mozilla::moz_autoconfigure;
|
||||
macro_rules! progress {
|
||||
($context:tt, $progress:expr) => {
|
||||
assert!(
|
||||
$progress > 0 && $progress <= 1000,
|
||||
$progress >= 0 && $progress <= 1000,
|
||||
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
|
||||
);
|
||||
$context.call_cb($crate::events::Event::ConfigureProgress($progress));
|
||||
$context.call_cb(
|
||||
Event::CONFIGURE_PROGRESS,
|
||||
$progress as uintptr_t,
|
||||
0 as uintptr_t,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -277,14 +283,13 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|
||||
}
|
||||
}
|
||||
if param.send_port == 0 {
|
||||
param.send_port =
|
||||
if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
|
||||
587
|
||||
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
|
||||
25
|
||||
} else {
|
||||
465
|
||||
}
|
||||
param.send_port = if 0 != param.server_flags & 0x10000 {
|
||||
587
|
||||
} else if 0 != param.server_flags & 0x40000 {
|
||||
25
|
||||
} else {
|
||||
465
|
||||
}
|
||||
}
|
||||
if param.send_user.is_empty() && !param.mail_user.is_empty() {
|
||||
param.send_user = param.mail_user.clone();
|
||||
@@ -292,30 +297,24 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|
||||
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
|
||||
param.send_pw = param.mail_pw.clone()
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
|
||||
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_AUTH_NORMAL as i32
|
||||
if !dc_exactly_one_bit_set(param.server_flags & (0x2 | 0x4)) {
|
||||
param.server_flags &= !(0x2 | 0x4);
|
||||
param.server_flags |= 0x4
|
||||
}
|
||||
if !dc_exactly_one_bit_set(param.server_flags & (0x100 | 0x200 | 0x400)) {
|
||||
param.server_flags &= !(0x100 | 0x200 | 0x400);
|
||||
param.server_flags |= if param.send_port == 143 { 0x100 } else { 0x200 }
|
||||
}
|
||||
if !dc_exactly_one_bit_set(
|
||||
param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32,
|
||||
param.server_flags & (0x10000 | 0x20000 | 0x40000),
|
||||
) {
|
||||
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= if param.send_port == 143 {
|
||||
DC_LP_IMAP_SOCKET_STARTTLS as i32
|
||||
} else {
|
||||
DC_LP_IMAP_SOCKET_SSL as i32
|
||||
}
|
||||
}
|
||||
if !dc_exactly_one_bit_set(
|
||||
param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32),
|
||||
) {
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
|
||||
param.server_flags |= if param.send_port == 587 {
|
||||
DC_LP_SMTP_SOCKET_STARTTLS as i32
|
||||
0x10000
|
||||
} else if param.send_port == 25 {
|
||||
DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
0x40000
|
||||
} else {
|
||||
DC_LP_SMTP_SOCKET_SSL as i32
|
||||
0x20000
|
||||
}
|
||||
}
|
||||
/* do we have a complete configuration? */
|
||||
@@ -429,8 +428,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|
||||
success = false;
|
||||
} else {
|
||||
progress!(context, 850);
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
|
||||
param.server_flags |= 0x10000;
|
||||
param.send_port = 587;
|
||||
info!(context, "Trying: {}", ¶m);
|
||||
|
||||
@@ -445,8 +444,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|
||||
success = false;
|
||||
} else {
|
||||
progress!(context, 860);
|
||||
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
|
||||
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
|
||||
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
|
||||
param.server_flags |= 0x10000;
|
||||
param.send_port = 25;
|
||||
info!(context, "Trying: {}", ¶m);
|
||||
|
||||
@@ -562,7 +561,7 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
|
||||
dc_free_ongoing(context);
|
||||
}
|
||||
|
||||
progress!(context, if success { 1000 } else { 0 });
|
||||
progress!(context, (if success { 1000 } else { 0 }));
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
260
src/constants.rs
260
src/constants.rs
@@ -1,9 +1,10 @@
|
||||
//! Constants
|
||||
#![allow(non_camel_case_types, dead_code)]
|
||||
|
||||
use deltachat_derive::*;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use deltachat_derive::*;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
|
||||
}
|
||||
@@ -139,7 +140,7 @@ pub const DC_LP_AUTH_OAUTH2: usize = 0x2;
|
||||
|
||||
/// Force NORMAL authorization, this is the default.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
|
||||
const DC_LP_AUTH_NORMAL: usize = 0x4;
|
||||
|
||||
/// Connect to IMAP via STARTTLS.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
@@ -147,7 +148,7 @@ pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
|
||||
|
||||
/// Connect to IMAP via SSL.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
|
||||
const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
|
||||
|
||||
/// Connect to IMAP unencrypted, this should not be used.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
@@ -159,19 +160,19 @@ pub const DC_LP_SMTP_SOCKET_STARTTLS: usize = 0x10000;
|
||||
|
||||
/// Connect to SMTP via SSL.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
|
||||
const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
|
||||
|
||||
/// Connect to SMTP unencrypted, this should not be used.
|
||||
/// If this flag is set, automatic configuration is skipped.
|
||||
pub const DC_LP_SMTP_SOCKET_PLAIN: usize = 0x40000;
|
||||
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
||||
const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
||||
const DC_LP_IMAP_SOCKET_FLAGS: usize =
|
||||
(DC_LP_IMAP_SOCKET_STARTTLS | DC_LP_IMAP_SOCKET_SSL | DC_LP_IMAP_SOCKET_PLAIN);
|
||||
/// if none of these flags are set, the default is chosen
|
||||
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
||||
const DC_LP_SMTP_SOCKET_FLAGS: usize =
|
||||
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
|
||||
|
||||
// QR code scanning (view from Bob, the joiner)
|
||||
@@ -246,6 +247,242 @@ mod tests {
|
||||
// If you do not want to handle an event, it is always safe to return 0,
|
||||
// so there is no need to add a "case" for every event.
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||
#[repr(u32)]
|
||||
pub enum Event {
|
||||
/// The library-user may write an informational string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
INFO = 100,
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
SMTP_CONNECTED = 101,
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
IMAP_CONNECTED = 102,
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
SMTP_MESSAGE_SENT = 103,
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Warning string in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
WARNING = 300,
|
||||
|
||||
/// The library-user should report an error to the end-user.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// As most things are asynchronous, things may go wrong at any time and the user
|
||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
///
|
||||
/// However, for ongoing processes (eg. configure())
|
||||
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the _last_ error
|
||||
/// in a messasge box then.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Error string, always set, never NULL. Frequent error strings are
|
||||
/// localized using #DC_EVENT_GET_STRING, however, most error strings will be in english language.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
ERROR = 400,
|
||||
|
||||
/// An action cannot be performed because there is no network available.
|
||||
///
|
||||
/// The library will typically try over after a some time
|
||||
/// and when dc_maybe_network() is called.
|
||||
///
|
||||
/// Network errors should be reported to users in a non-disturbing way,
|
||||
/// however, as network errors may come in a sequence,
|
||||
/// it is not useful to raise each an every error to the user.
|
||||
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
|
||||
///
|
||||
/// Moreover, if the UI detects that the device is offline,
|
||||
/// it is probably more useful to report this to the user
|
||||
/// instead of the string from data2.
|
||||
///
|
||||
/// @param data1 (int) 1=first/new network error, should be reported the user;
|
||||
/// 0=subsequent network error, should be logged only
|
||||
/// @param data2 (const char*) Error string, always set, never NULL.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
ERROR_NETWORK = 401,
|
||||
|
||||
/// An action cannot be performed because the user is not in the group.
|
||||
/// Reported eg. after a call to
|
||||
/// dc_set_chat_name(), dc_set_chat_profile_image(),
|
||||
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
|
||||
/// dc_send_text_msg() or another sending function.
|
||||
///
|
||||
/// @param data1 0
|
||||
/// @param data2 (const char*) Info string in english language.
|
||||
/// Must not be free()'d or modified
|
||||
/// and is valid only until the callback returns.
|
||||
/// @return 0
|
||||
ERROR_SELF_NOT_IN_GROUP = 410,
|
||||
|
||||
/// Messages or chats changed. One or more messages or chats changed for various
|
||||
/// reasons in the database:
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
///
|
||||
/// @param data1 (int) chat_id for single added messages
|
||||
/// @param data2 (int) msg_id for single added messages
|
||||
/// @return 0
|
||||
MSGS_CHANGED = 2000,
|
||||
|
||||
/// There is a fresh message. Typically, the user will show an notification
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) msg_id
|
||||
/// @return 0
|
||||
INCOMING_MSG = 2005,
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) msg_id
|
||||
/// @return 0
|
||||
MSG_DELIVERED = 2010,
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) msg_id
|
||||
/// @return 0
|
||||
MSG_FAILED = 2012,
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 (int) msg_id
|
||||
/// @return 0
|
||||
MSG_READ = 2015,
|
||||
|
||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||
/// and dc_remove_contact_from_chat().
|
||||
///
|
||||
/// @param data1 (int) chat_id
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
CHAT_MODIFIED = 2020,
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If not 0, this is the contact_id of an added contact that should be selected.
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
CONTACTS_CHANGED = 2030,
|
||||
|
||||
/// Location of one or more contact has changed.
|
||||
///
|
||||
/// @param data1 (int) contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// eg. after calling dc_delete_all_locations(), this parameter is set to 0.
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
LOCATION_CHANGED = 2035,
|
||||
|
||||
/// Inform about the configuration progress started by configure().
|
||||
///
|
||||
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
CONFIGURE_PROGRESS = 2041,
|
||||
|
||||
/// Inform about the import/export progress started by dc_imex().
|
||||
///
|
||||
/// @param data1 (int) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
IMEX_PROGRESS = 2051,
|
||||
|
||||
/// A file has been exported. A file has been written by dc_imex().
|
||||
/// This event may be sent multiple times by a single call to dc_imex().
|
||||
///
|
||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||
/// services.
|
||||
///
|
||||
/// @param data1 (const char*) Path and file name.
|
||||
/// Must not be free()'d or modified and is valid only until the callback returns.
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
IMEX_FILE_WRITTEN = 2052,
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the inviter
|
||||
/// (Alice, the person who shows the QR code).
|
||||
///
|
||||
/// These events are typically sent after a joiner has scanned the QR code
|
||||
/// generated by dc_get_securejoin_qr().
|
||||
///
|
||||
/// @param data1 (int) ID of the contact that wants to join.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
/// 1000=Protocol finished for this contact.
|
||||
/// @return 0
|
||||
SECUREJOIN_INVITER_PROGRESS = 2060,
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the joiner
|
||||
/// (Bob, the person who scans the QR code).
|
||||
/// The events are typically sent while dc_join_securejoin(), which
|
||||
/// may take some time, is executed.
|
||||
/// @param data1 (int) ID of the inviting contact.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
/// @return 0
|
||||
SECUREJOIN_JOINER_PROGRESS = 2061,
|
||||
|
||||
// the following events are functions that should be provided by the frontends
|
||||
/// Requeste a localized string from the frontend.
|
||||
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
|
||||
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
||||
/// the ui may use this value to return different strings on different plural forms.
|
||||
/// @return (const char*) Null-terminated UTF-8 string.
|
||||
/// The string will be free()'d by the core,
|
||||
/// so it must be allocated using malloc() or a compatible function.
|
||||
/// Return 0 if the ui cannot provide the requested string
|
||||
/// the core will use a default string in english language then.
|
||||
GET_STRING = 2091,
|
||||
}
|
||||
|
||||
const DC_EVENT_FILE_COPIED: usize = 2055; // deprecated;
|
||||
const DC_EVENT_IS_OFFLINE: usize = 2081; // deprecated;
|
||||
const DC_ERROR_SEE_STRING: usize = 0; // deprecated;
|
||||
@@ -314,3 +551,12 @@ pub enum KeyType {
|
||||
Public = 0,
|
||||
Private = 1,
|
||||
}
|
||||
|
||||
pub const DC_CMD_GROUPNAME_CHANGED: libc::c_int = 2;
|
||||
pub const DC_CMD_GROUPIMAGE_CHANGED: libc::c_int = 3;
|
||||
pub const DC_CMD_MEMBER_ADDED_TO_GROUP: libc::c_int = 4;
|
||||
pub const DC_CMD_MEMBER_REMOVED_FROM_GROUP: libc::c_int = 5;
|
||||
pub const DC_CMD_AUTOCRYPT_SETUP_MESSAGE: libc::c_int = 6;
|
||||
pub const DC_CMD_SECUREJOIN_MESSAGE: libc::c_int = 7;
|
||||
pub const DC_CMD_LOCATION_STREAMING_ENABLED: libc::c_int = 8;
|
||||
pub const DC_CMD_LOCATION_ONLY: libc::c_int = 9;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use deltachat_derive::*;
|
||||
use itertools::Itertools;
|
||||
use rusqlite;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::config::Config;
|
||||
@@ -11,13 +10,13 @@ use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::Result;
|
||||
use crate::events::Event;
|
||||
use crate::key::*;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::MessageState;
|
||||
use crate::peerstate::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
|
||||
/// Contacts with at least this origin value are shown in the contact list.
|
||||
const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
||||
@@ -32,7 +31,6 @@ const DC_ORIGIN_MIN_CONTACT_LIST: i32 = 0x100;
|
||||
/// For this purpose, internally, two names are tracked -
|
||||
/// authorized-name and given-name.
|
||||
/// By default, these names are equal, but functions working with contact names
|
||||
#[derive(Debug)]
|
||||
pub struct Contact {
|
||||
/// The contact ID.
|
||||
///
|
||||
@@ -148,7 +146,7 @@ impl Contact {
|
||||
name: context.stock_str(StockMessage::SelfMsg).into(),
|
||||
authname: "".into(),
|
||||
addr: context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.get_config(&Config::ConfiguredAddr)
|
||||
.unwrap_or_default(),
|
||||
blocked: false,
|
||||
origin: Origin::Unknown,
|
||||
@@ -214,13 +212,15 @@ impl Contact {
|
||||
let (contact_id, sth_modified) =
|
||||
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?;
|
||||
let blocked = Contact::is_blocked_load(context, contact_id);
|
||||
context.call_cb(Event::ContactsChanged(
|
||||
if sth_modified == Modifier::Created {
|
||||
Some(contact_id)
|
||||
context.call_cb(
|
||||
Event::CONTACTS_CHANGED,
|
||||
(if sth_modified == Modifier::Created {
|
||||
contact_id
|
||||
} else {
|
||||
None
|
||||
},
|
||||
));
|
||||
0
|
||||
}) as uintptr_t,
|
||||
0 as uintptr_t,
|
||||
);
|
||||
if blocked {
|
||||
Contact::unblock(context, contact_id);
|
||||
}
|
||||
@@ -241,10 +241,7 @@ impl Contact {
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,14 +257,14 @@ impl Contact {
|
||||
|
||||
let addr_normalized = addr_normalize(addr.as_ref());
|
||||
let addr_self = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.get_config(&Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr_normalized == addr_self {
|
||||
return 1;
|
||||
}
|
||||
|
||||
context.sql.query_get_value(
|
||||
context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
|
||||
params![
|
||||
@@ -275,6 +272,7 @@ impl Contact {
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
DC_ORIGIN_MIN_CONTACT_LIST,
|
||||
],
|
||||
0
|
||||
).unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -297,7 +295,7 @@ impl Contact {
|
||||
|
||||
let addr = addr_normalize(addr.as_ref());
|
||||
let addr_self = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.get_config(&Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr == addr_self {
|
||||
@@ -437,7 +435,7 @@ impl Contact {
|
||||
}
|
||||
}
|
||||
if modify_cnt > 0 {
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
context.call_cb(Event::CONTACTS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
}
|
||||
|
||||
Ok(modify_cnt)
|
||||
@@ -458,7 +456,7 @@ impl Contact {
|
||||
query: Option<impl AsRef<str>>,
|
||||
) -> Result<Vec<u32>> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.get_config(&Config::ConfiguredAddr)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut add_self = false;
|
||||
@@ -501,7 +499,7 @@ impl Contact {
|
||||
},
|
||||
)?;
|
||||
|
||||
let self_name = context.get_config(Config::Displayname).unwrap_or_default();
|
||||
let self_name = context.get_config(&Config::Displayname).unwrap_or_default();
|
||||
let self_name2 = context.stock_str(StockMessage::SelfMsg);
|
||||
|
||||
if let Some(query) = query {
|
||||
@@ -540,10 +538,11 @@ impl Contact {
|
||||
pub fn get_blocked_cnt(context: &Context) -> usize {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(
|
||||
.query_row_col::<_, isize>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
@@ -646,20 +645,22 @@ impl Contact {
|
||||
|
||||
let count_contacts: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
|
||||
params![contact_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let count_msgs: i32 = if count_contacts > 0 {
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
|
||||
params![contact_id as i32, contact_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
@@ -674,7 +675,7 @@ impl Contact {
|
||||
params![contact_id as i32],
|
||||
) {
|
||||
Ok(_) => {
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
context.call_cb(Event::CONTACTS_CHANGED, 0, 0);
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -764,7 +765,7 @@ impl Contact {
|
||||
/// using dc_set_config(context, "selfavatar", image).
|
||||
pub fn get_profile_image(&self, context: &Context) -> Option<PathBuf> {
|
||||
if self.id == DC_CONTACT_ID_SELF {
|
||||
if let Some(p) = context.get_config(Config::Selfavatar) {
|
||||
if let Some(p) = context.get_config(&Config::Selfavatar) {
|
||||
return Some(PathBuf::from(p));
|
||||
}
|
||||
}
|
||||
@@ -843,10 +844,11 @@ impl Contact {
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(
|
||||
.query_row_col::<_, isize>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM contacts WHERE id>?;",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default() as usize
|
||||
}
|
||||
@@ -939,7 +941,11 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
|
||||
params![new_blocking, 100, contact_id as i32],
|
||||
).is_ok() {
|
||||
Contact::mark_noticed(context, contact_id);
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
context.call_cb(
|
||||
Event::CONTACTS_CHANGED,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1020,7 +1026,7 @@ pub fn addr_cmp(addr1: impl AsRef<str>, addr2: impl AsRef<str>) -> bool {
|
||||
pub fn addr_equals_self(context: &Context, addr: impl AsRef<str>) -> bool {
|
||||
if !addr.as_ref().is_empty() {
|
||||
let normalized_addr = addr_normalize(addr.as_ref());
|
||||
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
|
||||
if let Some(self_addr) = context.get_config(&Config::ConfiguredAddr) {
|
||||
return normalized_addr == self_addr;
|
||||
}
|
||||
}
|
||||
|
||||
924
src/context.rs
924
src/context.rs
@@ -1,15 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
|
||||
use libc::uintptr_t;
|
||||
|
||||
use crate::chat::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::error::*;
|
||||
use crate::events::Event;
|
||||
use crate::dc_receive_imf::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::imap::*;
|
||||
use crate::job::*;
|
||||
use crate::job_thread::JobThread;
|
||||
@@ -20,26 +15,16 @@ use crate::message::*;
|
||||
use crate::param::Params;
|
||||
use crate::smtp::*;
|
||||
use crate::sql::Sql;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
use std::ptr;
|
||||
|
||||
/// Callback function type for [Context]
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * `context` - The context object as returned by [Context::new].
|
||||
/// * `event` - One of the [Event] items.
|
||||
/// * `data1` - Depends on the event parameter, see [Event].
|
||||
/// * `data2` - Depends on the event parameter, see [Event].
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// This callback must return 0 unless stated otherwise in the event
|
||||
/// description at [Event].
|
||||
pub type ContextCallback = dyn Fn(&Context, Event) -> uintptr_t + Send + Sync;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(DebugStub)]
|
||||
pub struct Context {
|
||||
dbfile: PathBuf,
|
||||
blobdir: PathBuf,
|
||||
pub userdata: *mut libc::c_void,
|
||||
pub dbfile: Arc<RwLock<Option<PathBuf>>>,
|
||||
pub blobdir: Arc<RwLock<*mut libc::c_char>>,
|
||||
pub sql: Sql,
|
||||
pub inbox: Arc<RwLock<Imap>>,
|
||||
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
|
||||
@@ -49,8 +34,7 @@ pub struct Context {
|
||||
pub smtp: Arc<Mutex<Smtp>>,
|
||||
pub smtp_state: Arc<(Mutex<SmtpState>, Condvar)>,
|
||||
pub oauth2_critical: Arc<Mutex<()>>,
|
||||
#[debug_stub = "Callback"]
|
||||
pub cb: Box<ContextCallback>,
|
||||
pub cb: Option<dc_callback_t>,
|
||||
pub os_name: Option<String>,
|
||||
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
|
||||
pub bob: Arc<RwLock<BobStatus>>,
|
||||
@@ -60,6 +44,9 @@ pub struct Context {
|
||||
pub generating_key_mutex: Mutex<()>,
|
||||
}
|
||||
|
||||
unsafe impl std::marker::Send for Context {}
|
||||
unsafe impl std::marker::Sync for Context {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RunningState {
|
||||
pub ongoing_running: bool,
|
||||
@@ -67,330 +54,38 @@ pub struct RunningState {
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(cb: Box<ContextCallback>, os_name: String, dbfile: PathBuf) -> Result<Context> {
|
||||
let mut blob_fname = OsString::new();
|
||||
blob_fname.push(dbfile.file_name().unwrap_or_default());
|
||||
blob_fname.push("-blobs");
|
||||
let blobdir = dbfile.with_file_name(blob_fname);
|
||||
if !blobdir.exists() {
|
||||
std::fs::create_dir_all(&blobdir)?;
|
||||
}
|
||||
Context::with_blobdir(cb, os_name, dbfile, blobdir)
|
||||
pub fn has_dbfile(&self) -> bool {
|
||||
self.get_dbfile().is_some()
|
||||
}
|
||||
|
||||
pub fn with_blobdir(
|
||||
cb: Box<ContextCallback>,
|
||||
os_name: String,
|
||||
dbfile: PathBuf,
|
||||
blobdir: PathBuf,
|
||||
) -> Result<Context> {
|
||||
ensure!(
|
||||
blobdir.is_dir(),
|
||||
"Blobdir does not exist: {}",
|
||||
blobdir.display()
|
||||
);
|
||||
let ctx = Context {
|
||||
blobdir,
|
||||
dbfile,
|
||||
inbox: Arc::new(RwLock::new(Imap::new())),
|
||||
cb,
|
||||
os_name: Some(os_name),
|
||||
running_state: Arc::new(RwLock::new(Default::default())),
|
||||
sql: Sql::new(),
|
||||
smtp: Arc::new(Mutex::new(Smtp::new())),
|
||||
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
||||
oauth2_critical: Arc::new(Mutex::new(())),
|
||||
bob: Arc::new(RwLock::new(Default::default())),
|
||||
last_smeared_timestamp: Arc::new(RwLock::new(0)),
|
||||
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
|
||||
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||
"SENTBOX",
|
||||
"configured_sentbox_folder",
|
||||
Imap::new(),
|
||||
))),
|
||||
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||
"MVBOX",
|
||||
"configured_mvbox_folder",
|
||||
Imap::new(),
|
||||
))),
|
||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
};
|
||||
|
||||
ensure!(
|
||||
ctx.sql.open(&ctx, &ctx.dbfile, 0),
|
||||
"Failed opening sqlite database"
|
||||
);
|
||||
|
||||
Ok(ctx)
|
||||
pub fn has_blobdir(&self) -> bool {
|
||||
!self.get_blobdir().is_null()
|
||||
}
|
||||
|
||||
pub fn get_dbfile(&self) -> &Path {
|
||||
self.dbfile.as_path()
|
||||
pub fn get_dbfile(&self) -> Option<PathBuf> {
|
||||
(*self.dbfile.clone().read().unwrap())
|
||||
.as_ref()
|
||||
.map(|x| x.clone())
|
||||
}
|
||||
|
||||
pub fn get_blobdir(&self) -> &Path {
|
||||
self.blobdir.as_path()
|
||||
pub fn get_blobdir(&self) -> *const libc::c_char {
|
||||
*self.blobdir.clone().read().unwrap()
|
||||
}
|
||||
|
||||
pub fn call_cb(&self, event: Event) -> uintptr_t {
|
||||
(*self.cb)(self, event)
|
||||
}
|
||||
|
||||
pub fn get_info(&self) -> HashMap<&'static str, String> {
|
||||
let unset = "0";
|
||||
let l = LoginParam::from_database(self, "");
|
||||
let l2 = LoginParam::from_database(self, "configured_");
|
||||
let displayname = self.sql.get_config(self, "displayname");
|
||||
let chats = get_chat_cnt(self) as usize;
|
||||
let real_msgs = dc_get_real_msg_cnt(self) as usize;
|
||||
let deaddrop_msgs = dc_get_deaddrop_msg_cnt(self) as usize;
|
||||
let contacts = Contact::get_real_cnt(self) as usize;
|
||||
let is_configured = self
|
||||
.sql
|
||||
.get_config_int(self, "configured")
|
||||
.unwrap_or_default();
|
||||
let dbversion = self
|
||||
.sql
|
||||
.get_config_int(self, "dbversion")
|
||||
.unwrap_or_default();
|
||||
let e2ee_enabled = self
|
||||
.sql
|
||||
.get_config_int(self, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mdns_enabled = self
|
||||
.sql
|
||||
.get_config_int(self, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
let prv_key_cnt: Option<isize> =
|
||||
self.sql
|
||||
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS);
|
||||
|
||||
let pub_key_cnt: Option<isize> = self.sql.query_get_value(
|
||||
self,
|
||||
"SELECT COUNT(*) FROM acpeerstates;",
|
||||
rusqlite::NO_PARAMS,
|
||||
);
|
||||
|
||||
let fingerprint_str = if let Some(key) = Key::from_self_public(self, &l2.addr, &self.sql) {
|
||||
key.fingerprint()
|
||||
pub fn call_cb(&self, event: Event, data1: uintptr_t, data2: uintptr_t) -> uintptr_t {
|
||||
if let Some(cb) = self.cb {
|
||||
unsafe { cb(self, event, data1, data2) }
|
||||
} else {
|
||||
"<Not yet calculated>".into()
|
||||
};
|
||||
|
||||
let inbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "inbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let sentbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_watch = self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_move = self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_move")
|
||||
.unwrap_or_else(|| 1);
|
||||
let folders_configured = self
|
||||
.sql
|
||||
.get_config_int(self, "folders_configured")
|
||||
.unwrap_or_default();
|
||||
let configured_sentbox_folder = self
|
||||
.sql
|
||||
.get_config(self, "configured_sentbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
let configured_mvbox_folder = self
|
||||
.sql
|
||||
.get_config(self, "configured_mvbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
|
||||
let mut res = HashMap::new();
|
||||
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
|
||||
res.insert("sqlite_version", rusqlite::version().to_string());
|
||||
res.insert(
|
||||
"sqlite_thread_safe",
|
||||
unsafe { rusqlite::ffi::sqlite3_threadsafe() }.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"arch",
|
||||
(::std::mem::size_of::<*mut libc::c_void>())
|
||||
.wrapping_mul(8)
|
||||
.to_string(),
|
||||
);
|
||||
res.insert("number_of_chats", chats.to_string());
|
||||
res.insert("number_of_chat_messages", real_msgs.to_string());
|
||||
res.insert("messages_in_contact_requests", deaddrop_msgs.to_string());
|
||||
res.insert("number_of_contacts", contacts.to_string());
|
||||
res.insert("database_dir", self.get_dbfile().display().to_string());
|
||||
res.insert("database_version", dbversion.to_string());
|
||||
res.insert("blobdir", self.get_blobdir().display().to_string());
|
||||
res.insert("display_name", displayname.unwrap_or_else(|| unset.into()));
|
||||
res.insert("is_configured", is_configured.to_string());
|
||||
res.insert("entered_account_settings", l.to_string());
|
||||
res.insert("used_account_settings", l2.to_string());
|
||||
res.insert("inbox_watch", inbox_watch.to_string());
|
||||
res.insert("sentbox_watch", sentbox_watch.to_string());
|
||||
res.insert("mvbox_watch", mvbox_watch.to_string());
|
||||
res.insert("mvbox_move", mvbox_move.to_string());
|
||||
res.insert("folders_configured", folders_configured.to_string());
|
||||
res.insert("configured_sentbox_folder", configured_sentbox_folder);
|
||||
res.insert("configured_mvbox_folder", configured_mvbox_folder);
|
||||
res.insert("mdns_enabled", mdns_enabled.to_string());
|
||||
res.insert("e2ee_enabled", e2ee_enabled.to_string());
|
||||
res.insert(
|
||||
"private_key_count",
|
||||
prv_key_cnt.unwrap_or_default().to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"public_key_count",
|
||||
pub_key_cnt.unwrap_or_default().to_string(),
|
||||
);
|
||||
res.insert("fingerprint", fingerprint_str);
|
||||
res.insert("level", "awesome".into());
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn get_fresh_msgs(&self) -> Vec<u32> {
|
||||
let show_deaddrop = 0;
|
||||
|
||||
self.sql
|
||||
.query_map(
|
||||
"SELECT m.id FROM msgs m LEFT JOIN contacts ct \
|
||||
ON m.from_id=ct.id LEFT JOIN chats c ON m.chat_id=c.id WHERE m.state=? \
|
||||
AND m.hidden=0 \
|
||||
AND m.chat_id>? \
|
||||
AND ct.blocked=0 \
|
||||
AND (c.blocked=0 OR c.blocked=?) ORDER BY m.timestamp DESC,m.id DESC;",
|
||||
&[10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|
||||
|row| row.get(0),
|
||||
|rows| {
|
||||
let mut ret = Vec::new();
|
||||
for row in rows {
|
||||
let id: u32 = row?;
|
||||
ret.push(id);
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn search_msgs(&self, chat_id: u32, query: impl AsRef<str>) -> Vec<u32> {
|
||||
let real_query = query.as_ref().trim();
|
||||
if real_query.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let strLikeInText = format!("%{}%", real_query);
|
||||
let strLikeBeg = format!("{}%", real_query);
|
||||
|
||||
let query = if 0 != chat_id {
|
||||
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id WHERE m.chat_id=? \
|
||||
AND m.hidden=0 \
|
||||
AND ct.blocked=0 AND (txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp,m.id;"
|
||||
} else {
|
||||
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id \
|
||||
LEFT JOIN chats c ON m.chat_id=c.id WHERE m.chat_id>9 AND m.hidden=0 \
|
||||
AND (c.blocked=0 OR c.blocked=?) \
|
||||
AND ct.blocked=0 AND (m.txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp DESC,m.id DESC;"
|
||||
};
|
||||
|
||||
self.sql
|
||||
.query_map(
|
||||
query,
|
||||
params![chat_id as i32, &strLikeInText, &strLikeBeg],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|rows| {
|
||||
let mut ret = Vec::new();
|
||||
for id in rows {
|
||||
ret.push(id? as u32);
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_inbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
folder_name.as_ref() == "INBOX"
|
||||
}
|
||||
|
||||
pub fn is_sentbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let sentbox_name = self.sql.get_config(self, "configured_sentbox_folder");
|
||||
if let Some(name) = sentbox_name {
|
||||
name == folder_name.as_ref()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_mvbox(&self, folder_name: impl AsRef<str>) -> bool {
|
||||
let mvbox_name = self.sql.get_config(self, "configured_mvbox_folder");
|
||||
|
||||
if let Some(name) = mvbox_name {
|
||||
name == folder_name.as_ref()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_heuristics_moves(&self, folder: &str, msg_id: u32) {
|
||||
if self
|
||||
.sql
|
||||
.get_config_int(self, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.is_inbox(folder) && !self.is_sentbox(folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(msg) = dc_msg_new_load(self, msg_id) {
|
||||
if dc_msg_is_setupmessage(&msg) {
|
||||
// do not move setup messages;
|
||||
// there may be a non-delta device that wants to handle it
|
||||
return;
|
||||
}
|
||||
|
||||
if self.is_mvbox(folder) {
|
||||
dc_update_msg_move_state(self, msg.rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
if 0 != msg.is_dc_message {
|
||||
job_add(
|
||||
self,
|
||||
Action::MoveMsg,
|
||||
msg.id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
dc_update_msg_move_state(self, msg.rfc724_mid, MoveState::Moving);
|
||||
}
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
fn drop(&mut self) {
|
||||
info!(self, "disconnecting INBOX-watch",);
|
||||
self.inbox.read().unwrap().disconnect(self);
|
||||
info!(self, "disconnecting sentbox-thread",);
|
||||
self.sentbox_thread.read().unwrap().imap.disconnect(self);
|
||||
info!(self, "disconnecting mvbox-thread",);
|
||||
self.mvbox_thread.read().unwrap().imap.disconnect(self);
|
||||
info!(self, "disconnecting SMTP");
|
||||
self.smtp.clone().lock().unwrap().disconnect();
|
||||
self.sql.close(self);
|
||||
unsafe {
|
||||
dc_close(&self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +98,7 @@ impl Default for RunningState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default)]
|
||||
pub struct BobStatus {
|
||||
pub expects: i32,
|
||||
pub status: i32,
|
||||
@@ -419,75 +114,514 @@ pub struct SmtpState {
|
||||
pub probe_network: bool,
|
||||
}
|
||||
|
||||
pub fn get_version_str() -> &'static str {
|
||||
&DC_VERSION_STR
|
||||
// create/open/config/information
|
||||
pub fn dc_context_new(
|
||||
cb: Option<dc_callback_t>,
|
||||
userdata: *mut libc::c_void,
|
||||
os_name: Option<String>,
|
||||
) -> Context {
|
||||
Context {
|
||||
blobdir: Arc::new(RwLock::new(std::ptr::null_mut())),
|
||||
dbfile: Arc::new(RwLock::new(None)),
|
||||
inbox: Arc::new(RwLock::new({
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
)
|
||||
})),
|
||||
userdata,
|
||||
cb,
|
||||
os_name,
|
||||
running_state: Arc::new(RwLock::new(Default::default())),
|
||||
sql: Sql::new(),
|
||||
smtp: Arc::new(Mutex::new(Smtp::new())),
|
||||
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
|
||||
oauth2_critical: Arc::new(Mutex::new(())),
|
||||
bob: Arc::new(RwLock::new(Default::default())),
|
||||
last_smeared_timestamp: Arc::new(RwLock::new(0)),
|
||||
cmdline_sel_chat_id: Arc::new(RwLock::new(0)),
|
||||
sentbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||
"SENTBOX",
|
||||
"configured_sentbox_folder",
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
),
|
||||
))),
|
||||
mvbox_thread: Arc::new(RwLock::new(JobThread::new(
|
||||
"MVBOX",
|
||||
"configured_mvbox_folder",
|
||||
Imap::new(
|
||||
cb_get_config,
|
||||
cb_set_config,
|
||||
cb_precheck_imf,
|
||||
cb_receive_imf,
|
||||
),
|
||||
))),
|
||||
probe_imap_network: Arc::new(RwLock::new(false)),
|
||||
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
|
||||
generating_key_mutex: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn cb_receive_imf(
|
||||
context: &Context,
|
||||
imf_raw_not_terminated: *const libc::c_char,
|
||||
imf_raw_bytes: size_t,
|
||||
server_folder: &str,
|
||||
server_uid: uint32_t,
|
||||
flags: uint32_t,
|
||||
) {
|
||||
dc_receive_imf(
|
||||
context,
|
||||
imf_raw_not_terminated,
|
||||
imf_raw_bytes,
|
||||
server_folder,
|
||||
server_uid,
|
||||
flags,
|
||||
);
|
||||
}
|
||||
|
||||
unsafe fn cb_precheck_imf(
|
||||
context: &Context,
|
||||
rfc724_mid: *const libc::c_char,
|
||||
server_folder: &str,
|
||||
server_uid: uint32_t,
|
||||
) -> libc::c_int {
|
||||
let mut rfc724_mid_exists: libc::c_int = 0i32;
|
||||
let msg_id: uint32_t;
|
||||
let mut old_server_folder: *mut libc::c_char = ptr::null_mut();
|
||||
let mut old_server_uid: uint32_t = 0i32 as uint32_t;
|
||||
let mut mark_seen: libc::c_int = 0i32;
|
||||
msg_id = dc_rfc724_mid_exists(
|
||||
context,
|
||||
rfc724_mid,
|
||||
&mut old_server_folder,
|
||||
&mut old_server_uid,
|
||||
);
|
||||
if msg_id != 0i32 as libc::c_uint {
|
||||
rfc724_mid_exists = 1i32;
|
||||
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
|
||||
&& old_server_uid == 0i32 as libc::c_uint
|
||||
{
|
||||
info!(context, "[move] detected bbc-self {}", as_str(rfc724_mid),);
|
||||
mark_seen = 1i32
|
||||
} else if as_str(old_server_folder) != server_folder {
|
||||
info!(
|
||||
context,
|
||||
"[move] detected moved message {}",
|
||||
as_str(rfc724_mid),
|
||||
);
|
||||
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
|
||||
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
|
||||
}
|
||||
do_heuristics_moves(context, server_folder, msg_id);
|
||||
if 0 != mark_seen {
|
||||
job_add(
|
||||
context,
|
||||
Action::MarkseenMsgOnImap,
|
||||
msg_id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
free(old_server_folder as *mut libc::c_void);
|
||||
rfc724_mid_exists
|
||||
}
|
||||
|
||||
fn cb_set_config(context: &Context, key: &str, value: Option<&str>) {
|
||||
context.sql.set_config(context, key, value).ok();
|
||||
}
|
||||
|
||||
/* *
|
||||
* The following three callback are given to dc_imap_new() to read/write configuration
|
||||
* and to handle received messages. As the imap-functions are typically used in
|
||||
* a separate user-thread, also these functions may be called from a different thread.
|
||||
*
|
||||
* @private @memberof Context
|
||||
*/
|
||||
fn cb_get_config(context: &Context, key: &str) -> Option<String> {
|
||||
context.sql.get_config(context, key)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_close(context: &Context) {
|
||||
info!(context, "disconnecting INBOX-watch",);
|
||||
context.inbox.read().unwrap().disconnect(context);
|
||||
info!(context, "disconnecting sentbox-thread",);
|
||||
context
|
||||
.sentbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.disconnect(context);
|
||||
info!(context, "disconnecting mvbox-thread",);
|
||||
context
|
||||
.mvbox_thread
|
||||
.read()
|
||||
.unwrap()
|
||||
.imap
|
||||
.disconnect(context);
|
||||
|
||||
info!(context, "disconnecting SMTP");
|
||||
context.smtp.clone().lock().unwrap().disconnect();
|
||||
|
||||
context.sql.close(context);
|
||||
let mut dbfile = context.dbfile.write().unwrap();
|
||||
*dbfile = None;
|
||||
let mut blobdir = context.blobdir.write().unwrap();
|
||||
free(*blobdir as *mut libc::c_void);
|
||||
*blobdir = ptr::null_mut();
|
||||
}
|
||||
|
||||
pub unsafe fn dc_is_open(context: &Context) -> libc::c_int {
|
||||
context.sql.is_open() as libc::c_int
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_userdata(context: &mut Context) -> *mut libc::c_void {
|
||||
context.userdata as *mut _
|
||||
}
|
||||
|
||||
pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) -> bool {
|
||||
let mut success = false;
|
||||
if 0 != dc_is_open(context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*context.dbfile.write().unwrap() = Some(PathBuf::from(dbfile));
|
||||
let blobdir = blobdir.unwrap_or_default();
|
||||
if !blobdir.is_empty() {
|
||||
let dir = dc_ensure_no_slash_safe(blobdir).strdup();
|
||||
*context.blobdir.write().unwrap() = dir;
|
||||
} else {
|
||||
let dir = dbfile.to_string() + "-blobs";
|
||||
dc_create_folder(context, &dir);
|
||||
*context.blobdir.write().unwrap() = dir.strdup();
|
||||
}
|
||||
// Create/open sqlite database, this may already use the blobdir
|
||||
let dbfile_path = std::path::Path::new(dbfile);
|
||||
if context.sql.open(context, dbfile_path, 0) {
|
||||
success = true
|
||||
}
|
||||
if !success {
|
||||
dc_close(context);
|
||||
}
|
||||
success
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char {
|
||||
dc_strdup(*context.blobdir.clone().read().unwrap())
|
||||
}
|
||||
|
||||
/* ******************************************************************************
|
||||
* INI-handling, Information
|
||||
******************************************************************************/
|
||||
|
||||
pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
|
||||
let unset = "0";
|
||||
let l = LoginParam::from_database(context, "");
|
||||
let l2 = LoginParam::from_database(context, "configured_");
|
||||
let displayname = context.sql.get_config(context, "displayname");
|
||||
let chats = get_chat_cnt(context) as usize;
|
||||
let real_msgs = dc_get_real_msg_cnt(context) as usize;
|
||||
let deaddrop_msgs = dc_get_deaddrop_msg_cnt(context) as usize;
|
||||
let contacts = Contact::get_real_cnt(context) as usize;
|
||||
let is_configured = context
|
||||
.sql
|
||||
.get_config_int(context, "configured")
|
||||
.unwrap_or_default();
|
||||
let dbversion = context
|
||||
.sql
|
||||
.get_config_int(context, "dbversion")
|
||||
.unwrap_or_default();
|
||||
let e2ee_enabled = context
|
||||
.sql
|
||||
.get_config_int(context, "e2ee_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mdns_enabled = context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
.unwrap_or_else(|| 1);
|
||||
|
||||
let prv_key_cnt: Option<isize> = context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM keypairs;",
|
||||
rusqlite::NO_PARAMS,
|
||||
0,
|
||||
);
|
||||
|
||||
let pub_key_cnt: Option<isize> = context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM acpeerstates;",
|
||||
rusqlite::NO_PARAMS,
|
||||
0,
|
||||
);
|
||||
|
||||
let fingerprint_str = if let Some(key) = Key::from_self_public(context, &l2.addr, &context.sql)
|
||||
{
|
||||
key.fingerprint()
|
||||
} else {
|
||||
"<Not yet calculated>".into()
|
||||
};
|
||||
|
||||
let inbox_watch = context
|
||||
.sql
|
||||
.get_config_int(context, "inbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let sentbox_watch = context
|
||||
.sql
|
||||
.get_config_int(context, "sentbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_watch = context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_watch")
|
||||
.unwrap_or_else(|| 1);
|
||||
let mvbox_move = context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
.unwrap_or_else(|| 1);
|
||||
let folders_configured = context
|
||||
.sql
|
||||
.get_config_int(context, "folders_configured")
|
||||
.unwrap_or_default();
|
||||
let configured_sentbox_folder = context
|
||||
.sql
|
||||
.get_config(context, "configured_sentbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
let configured_mvbox_folder = context
|
||||
.sql
|
||||
.get_config(context, "configured_mvbox_folder")
|
||||
.unwrap_or_else(|| "<unset>".to_string());
|
||||
|
||||
let res = format!(
|
||||
"deltachat_core_version=v{}\n\
|
||||
sqlite_version={}\n\
|
||||
sqlite_thread_safe={}\n\
|
||||
arch={}\n\
|
||||
number_of_chats={}\n\
|
||||
number_of_chat_messages={}\n\
|
||||
messages_in_contact_requests={}\n\
|
||||
number_of_contacts={}\n\
|
||||
database_dir={}\n\
|
||||
database_version={}\n\
|
||||
blobdir={}\n\
|
||||
display_name={}\n\
|
||||
is_configured={}\n\
|
||||
entered_account_settings={}\n\
|
||||
used_account_settings={}\n\
|
||||
inbox_watch={}\n\
|
||||
sentbox_watch={}\n\
|
||||
mvbox_watch={}\n\
|
||||
mvbox_move={}\n\
|
||||
folders_configured={}\n\
|
||||
configured_sentbox_folder={}\n\
|
||||
configured_mvbox_folder={}\n\
|
||||
mdns_enabled={}\n\
|
||||
e2ee_enabled={}\n\
|
||||
private_key_count={}\n\
|
||||
public_key_count={}\n\
|
||||
fingerprint={}\n\
|
||||
level=awesome\n",
|
||||
&*DC_VERSION_STR,
|
||||
rusqlite::version(),
|
||||
sqlite3_threadsafe(),
|
||||
// arch
|
||||
(::std::mem::size_of::<*mut libc::c_void>()).wrapping_mul(8),
|
||||
chats,
|
||||
real_msgs,
|
||||
deaddrop_msgs,
|
||||
contacts,
|
||||
context
|
||||
.get_dbfile()
|
||||
.as_ref()
|
||||
.map_or(unset, |p| p.to_str().unwrap()),
|
||||
dbversion,
|
||||
if context.has_blobdir() {
|
||||
as_str(context.get_blobdir())
|
||||
} else {
|
||||
unset
|
||||
},
|
||||
displayname.unwrap_or_else(|| unset.into()),
|
||||
is_configured,
|
||||
l,
|
||||
l2,
|
||||
inbox_watch,
|
||||
sentbox_watch,
|
||||
mvbox_watch,
|
||||
mvbox_move,
|
||||
folders_configured,
|
||||
configured_sentbox_folder,
|
||||
configured_mvbox_folder,
|
||||
mdns_enabled,
|
||||
e2ee_enabled,
|
||||
prv_key_cnt.unwrap_or_default(),
|
||||
pub_key_cnt.unwrap_or_default(),
|
||||
fingerprint_str,
|
||||
);
|
||||
|
||||
res.strdup()
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_version_str() -> *mut libc::c_char {
|
||||
(&*DC_VERSION_STR).strdup()
|
||||
}
|
||||
|
||||
pub fn dc_get_fresh_msgs(context: &Context) -> Vec<u32> {
|
||||
let show_deaddrop = 0;
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT m.id FROM msgs m LEFT JOIN contacts ct \
|
||||
ON m.from_id=ct.id LEFT JOIN chats c ON m.chat_id=c.id WHERE m.state=? \
|
||||
AND m.hidden=0 \
|
||||
AND m.chat_id>? \
|
||||
AND ct.blocked=0 \
|
||||
AND (c.blocked=0 OR c.blocked=?) ORDER BY m.timestamp DESC,m.id DESC;",
|
||||
&[10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|
||||
|row| row.get(0),
|
||||
|rows| {
|
||||
let mut ret = Vec::new();
|
||||
for row in rows {
|
||||
let id: u32 = row?;
|
||||
ret.push(id);
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn dc_search_msgs(
|
||||
context: &Context,
|
||||
chat_id: uint32_t,
|
||||
query: *const libc::c_char,
|
||||
) -> Vec<u32> {
|
||||
if query.is_null() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let real_query = to_string(query).trim().to_string();
|
||||
if real_query.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let strLikeInText = format!("%{}%", &real_query);
|
||||
let strLikeBeg = format!("{}%", &real_query);
|
||||
|
||||
let query = if 0 != chat_id {
|
||||
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id WHERE m.chat_id=? \
|
||||
AND m.hidden=0 \
|
||||
AND ct.blocked=0 AND (txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp,m.id;"
|
||||
} else {
|
||||
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id \
|
||||
LEFT JOIN chats c ON m.chat_id=c.id WHERE m.chat_id>9 AND m.hidden=0 \
|
||||
AND (c.blocked=0 OR c.blocked=?) \
|
||||
AND ct.blocked=0 AND (m.txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp DESC,m.id DESC;"
|
||||
};
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
query,
|
||||
params![chat_id as libc::c_int, &strLikeInText, &strLikeBeg],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|rows| {
|
||||
let mut ret = Vec::new();
|
||||
for id in rows {
|
||||
ret.push(id? as u32);
|
||||
}
|
||||
Ok(ret)
|
||||
},
|
||||
)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn dc_is_inbox(_context: &Context, folder_name: impl AsRef<str>) -> bool {
|
||||
folder_name.as_ref() == "INBOX"
|
||||
}
|
||||
|
||||
pub fn dc_is_sentbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
|
||||
let sentbox_name = context.sql.get_config(context, "configured_sentbox_folder");
|
||||
if let Some(name) = sentbox_name {
|
||||
name == folder_name.as_ref()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_is_mvbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
|
||||
let mvbox_name = context.sql.get_config(context, "configured_mvbox_folder");
|
||||
|
||||
if let Some(name) = mvbox_name {
|
||||
name == folder_name.as_ref()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
|
||||
if context
|
||||
.sql
|
||||
.get_config_int(context, "mvbox_move")
|
||||
.unwrap_or_else(|| 1)
|
||||
== 0
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(msg) = dc_msg_new_load(context, msg_id) {
|
||||
if dc_msg_is_setupmessage(&msg) {
|
||||
// do not move setup messages;
|
||||
// there may be a non-delta device that wants to handle it
|
||||
return;
|
||||
}
|
||||
|
||||
if dc_is_mvbox(context, folder) {
|
||||
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
|
||||
// 1 = dc message, 2 = reply to dc message
|
||||
if 0 != msg.is_dc_message {
|
||||
job_add(
|
||||
context,
|
||||
Action::MoveMsg,
|
||||
msg.id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
dc_update_msg_move_state(context, msg.rfc724_mid, MoveState::Moving);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_wrong_db() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
std::fs::write(&dbfile, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| 0), "FakeOs".into(), dbfile);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blobdir_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
assert!(blobdir.is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_blogdir() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
std::fs::write(&blobdir, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqlite_parent_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let subdir = tmp.path().join("subdir");
|
||||
let dbfile = subdir.join("db.sqlite");
|
||||
let dbfile2 = dbfile.clone();
|
||||
Context::new(Box::new(|_, _| 0), "FakeOS".into(), dbfile).unwrap();
|
||||
assert!(subdir.is_dir());
|
||||
assert!(dbfile2.is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_blobdir_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("blobs");
|
||||
let res = Context::with_blobdir(Box::new(|_, _| 0), "FakeOS".into(), dbfile, blobdir);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_crashes_on_context_deref() {
|
||||
let t = dummy_context();
|
||||
std::mem::drop(t.ctx);
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), Some("Test OS".into()));
|
||||
std::mem::drop(ctx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_info() {
|
||||
let t = dummy_context();
|
||||
|
||||
let info = t.ctx.get_info();
|
||||
assert_eq!(info.get("level").unwrap(), "awesome");
|
||||
fn test_context_double_close() {
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
unsafe {
|
||||
dc_close(&ctx);
|
||||
dc_close(&ctx);
|
||||
}
|
||||
std::mem::drop(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::location::Location;
|
||||
use crate::types::*;
|
||||
|
||||
/* * the structure behind dc_array_t */
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum dc_array_t {
|
||||
Locations(Vec<Location>),
|
||||
@@ -18,7 +19,7 @@ impl dc_array_t {
|
||||
dc_array_t::Locations(Vec::with_capacity(capacity))
|
||||
}
|
||||
|
||||
pub fn add_id(&mut self, item: u32) {
|
||||
pub fn add_id(&mut self, item: uint32_t) {
|
||||
if let Self::Uint(array) = self {
|
||||
array.push(item);
|
||||
} else {
|
||||
@@ -34,10 +35,10 @@ impl dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self, index: usize) -> u32 {
|
||||
pub fn get_id(&self, index: usize) -> uint32_t {
|
||||
match self {
|
||||
Self::Locations(array) => array[index].location_id,
|
||||
Self::Uint(array) => array[index] as u32,
|
||||
Self::Uint(array) => array[index] as uint32_t,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
270
src/dc_imex.rs
270
src/dc_imex.rs
@@ -1,5 +1,4 @@
|
||||
use std::ffi::CString;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
|
||||
use mmime::mailmime_content::*;
|
||||
@@ -15,7 +14,6 @@ use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee;
|
||||
use crate::error::*;
|
||||
use crate::events::Event;
|
||||
use crate::job::*;
|
||||
use crate::key::*;
|
||||
use crate::message::*;
|
||||
@@ -23,6 +21,7 @@ use crate::param::*;
|
||||
use crate::pgp::*;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
// import/export and tools
|
||||
@@ -30,16 +29,16 @@ use crate::x::*;
|
||||
// param1 is a directory where the keys are searched in and read from
|
||||
// param1 is a directory where the backup is written to
|
||||
// param1 is the file with the backup to import
|
||||
pub fn dc_imex(
|
||||
pub unsafe fn dc_imex(
|
||||
context: &Context,
|
||||
what: libc::c_int,
|
||||
param1: Option<impl AsRef<Path>>,
|
||||
param1: *const libc::c_char,
|
||||
param2: *const libc::c_char,
|
||||
) {
|
||||
let mut param = Params::new();
|
||||
param.set_int(Param::Cmd, what as i32);
|
||||
if let Some(param1) = param1 {
|
||||
param.set(Param::Arg, param1.as_ref().to_string_lossy());
|
||||
if !param1.is_null() {
|
||||
param.set(Param::Arg, as_str(param1));
|
||||
}
|
||||
if !param2.is_null() {
|
||||
param.set(Param::Arg2, as_str(param2));
|
||||
@@ -52,9 +51,9 @@ pub fn dc_imex(
|
||||
/// Returns the filename of the backup if found, nullptr otherwise.
|
||||
pub unsafe fn dc_imex_has_backup(
|
||||
context: &Context,
|
||||
dir_name: impl AsRef<Path>,
|
||||
dir_name: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let dir_name = dir_name.as_ref();
|
||||
let dir_name = as_path(dir_name);
|
||||
let dir_iter = std::fs::read_dir(dir_name);
|
||||
if dir_iter.is_err() {
|
||||
info!(
|
||||
@@ -169,7 +168,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
if let Ok(msg) = dc_get_msg(context, msg_id) {
|
||||
if dc_msg_is_sent(&msg) {
|
||||
if 0 != dc_msg_is_sent(&msg) {
|
||||
info!(context, "... setup message sent.",);
|
||||
break;
|
||||
}
|
||||
@@ -242,7 +241,7 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
|
||||
}
|
||||
|
||||
pub fn dc_create_setup_code(_context: &Context) -> String {
|
||||
let mut random_val: u16;
|
||||
let mut random_val: uint16_t;
|
||||
let mut rng = thread_rng();
|
||||
let mut ret = String::new();
|
||||
|
||||
@@ -253,7 +252,7 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
|
||||
break;
|
||||
}
|
||||
}
|
||||
random_val = (random_val as libc::c_int % 10000) as u16;
|
||||
random_val = (random_val as libc::c_int % 10000) as uint16_t;
|
||||
ret += &format!(
|
||||
"{}{:04}",
|
||||
if 0 != i { "-" } else { "" },
|
||||
@@ -266,52 +265,58 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
|
||||
|
||||
pub unsafe fn dc_continue_key_transfer(
|
||||
context: &Context,
|
||||
msg_id: u32,
|
||||
msg_id: uint32_t,
|
||||
setup_code: *const libc::c_char,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
let mut filename: *mut libc::c_char = ptr::null_mut();
|
||||
let mut filecontent: *mut libc::c_char = ptr::null_mut();
|
||||
let mut filebytes: size_t = 0i32 as size_t;
|
||||
let mut armored_key: *mut libc::c_char = ptr::null_mut();
|
||||
let norm_sc;
|
||||
let mut norm_sc: *mut libc::c_char = ptr::null_mut();
|
||||
if msg_id <= 9i32 as libc::c_uint || setup_code.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let msg = dc_get_msg(context, msg_id);
|
||||
if msg.is_err() {
|
||||
error!(context, "Message is no Autocrypt Setup Message.");
|
||||
return false;
|
||||
}
|
||||
let msg = msg.unwrap();
|
||||
if !dc_msg_is_setupmessage(&msg) {
|
||||
error!(context, "Message is no Autocrypt Setup Message.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(filename) = dc_msg_get_file(context, &msg) {
|
||||
if let Some(buf) = dc_read_file_safe(context, filename) {
|
||||
norm_sc = dc_normalize_setup_code(context, setup_code);
|
||||
if norm_sc.is_null() {
|
||||
warn!(context, "Cannot normalize Setup Code.",);
|
||||
} else {
|
||||
armored_key = dc_decrypt_setup_file(context, norm_sc, buf.as_ptr().cast());
|
||||
if armored_key.is_null() {
|
||||
warn!(context, "Cannot decrypt Autocrypt Setup Message.",);
|
||||
} else if set_self_key(context, armored_key, 1) {
|
||||
/*set default*/
|
||||
/* error already logged */
|
||||
success = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(context, "Cannot read Autocrypt Setup Message file.",);
|
||||
return false;
|
||||
if msg.is_err()
|
||||
|| !dc_msg_is_setupmessage(msg.as_ref().unwrap())
|
||||
|| {
|
||||
filename = dc_msg_get_file(context, msg.as_ref().unwrap());
|
||||
filename.is_null()
|
||||
}
|
||||
|| *filename.offset(0isize) as libc::c_int == 0i32
|
||||
{
|
||||
error!(context, "Message is no Autocrypt Setup Message.",);
|
||||
} else if 0
|
||||
== dc_read_file(
|
||||
context,
|
||||
filename,
|
||||
&mut filecontent as *mut *mut libc::c_char as *mut *mut libc::c_void,
|
||||
&mut filebytes,
|
||||
)
|
||||
|| filecontent.is_null()
|
||||
|| filebytes <= 0
|
||||
{
|
||||
error!(context, "Cannot read Autocrypt Setup Message file.",);
|
||||
} else {
|
||||
error!(context, "Message is no Autocrypt Setup Message.");
|
||||
return false;
|
||||
norm_sc = dc_normalize_setup_code(context, setup_code);
|
||||
if norm_sc.is_null() {
|
||||
warn!(context, "Cannot normalize Setup Code.",);
|
||||
} else {
|
||||
armored_key = dc_decrypt_setup_file(context, norm_sc, filecontent);
|
||||
if armored_key.is_null() {
|
||||
warn!(context, "Cannot decrypt Autocrypt Setup Message.",);
|
||||
} else if set_self_key(context, armored_key, 1) {
|
||||
/*set default*/
|
||||
/* error already logged */
|
||||
success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(armored_key as *mut libc::c_void);
|
||||
free(filecontent as *mut libc::c_void);
|
||||
free(filename as *mut libc::c_void);
|
||||
free(norm_sc as *mut libc::c_void);
|
||||
|
||||
success
|
||||
@@ -363,7 +368,7 @@ fn set_self_key(
|
||||
error!(context, "File does not contain a private key.",);
|
||||
}
|
||||
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
let self_addr = context.sql.get_config(context, "configured_addr");
|
||||
|
||||
if self_addr.is_none() {
|
||||
error!(context, "Missing self addr");
|
||||
@@ -405,8 +410,8 @@ pub unsafe fn dc_decrypt_setup_file(
|
||||
let mut fc_headerline: *const libc::c_char = ptr::null();
|
||||
let mut fc_base64: *const libc::c_char = ptr::null();
|
||||
let mut binary: *mut libc::c_char = ptr::null_mut();
|
||||
let mut binary_bytes: libc::size_t = 0;
|
||||
let mut indx: libc::size_t = 0;
|
||||
let mut binary_bytes: size_t = 0i32 as size_t;
|
||||
let mut indx: size_t = 0i32 as size_t;
|
||||
|
||||
let mut payload: *mut libc::c_char = ptr::null_mut();
|
||||
fc_buf = dc_strdup(filecontent);
|
||||
@@ -490,9 +495,10 @@ pub unsafe fn dc_normalize_setup_code(
|
||||
pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
|
||||
let mut ok_to_continue = true;
|
||||
let mut success: libc::c_int = 0;
|
||||
let what: libc::c_int;
|
||||
|
||||
if dc_alloc_ongoing(context) {
|
||||
let what = job.param.get_int(Param::Cmd).unwrap_or_default();
|
||||
what = job.param.get_int(Param::Cmd).unwrap_or_default();
|
||||
let param1_s = job.param.get(Param::Arg).unwrap_or_default();
|
||||
let param1 = CString::yolo(param1_s);
|
||||
let _param2 = CString::yolo(job.param.get(Param::Arg2).unwrap_or_default());
|
||||
@@ -501,8 +507,7 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
|
||||
error!(context, "No Import/export dir/file given.",);
|
||||
} else {
|
||||
info!(context, "Import/export process started.",);
|
||||
context.call_cb(Event::ImexProgress(10));
|
||||
|
||||
context.call_cb(Event::IMEX_PROGRESS, 10 as uintptr_t, 0 as uintptr_t);
|
||||
if !context.sql.is_open() {
|
||||
error!(context, "Import/export: Database not opened.",);
|
||||
} else {
|
||||
@@ -521,7 +526,7 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
|
||||
if ok_to_continue {
|
||||
match what {
|
||||
1 => {
|
||||
if export_self_keys(context, param1.as_ptr()) {
|
||||
if 0 != export_self_keys(context, param1.as_ptr()) {
|
||||
info!(context, "Import/export completed.",);
|
||||
success = 1
|
||||
}
|
||||
@@ -533,13 +538,13 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
|
||||
}
|
||||
}
|
||||
11 => {
|
||||
if export_backup(context, param1.as_ptr()) {
|
||||
if 0 != export_backup(context, param1.as_ptr()) {
|
||||
info!(context, "Import/export completed.",);
|
||||
success = 1
|
||||
}
|
||||
}
|
||||
12 => {
|
||||
if import_backup(context, param1.as_ptr()) {
|
||||
if 0 != import_backup(context, param1.as_ptr()) {
|
||||
info!(context, "Import/export completed.",);
|
||||
success = 1
|
||||
}
|
||||
@@ -551,48 +556,63 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
|
||||
}
|
||||
dc_free_ongoing(context);
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(if 0 != success { 1000 } else { 0 }));
|
||||
context.call_cb(
|
||||
Event::IMEX_PROGRESS,
|
||||
(if 0 != success { 1000 } else { 0 }) as uintptr_t,
|
||||
0 as uintptr_t,
|
||||
);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Import backup
|
||||
******************************************************************************/
|
||||
|
||||
// TODO should return bool /rtn
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> bool {
|
||||
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> libc::c_int {
|
||||
info!(
|
||||
context,
|
||||
"Import \"{}\" to \"{}\".",
|
||||
as_str(backup_to_import),
|
||||
context.get_dbfile().display()
|
||||
context
|
||||
.get_dbfile()
|
||||
.as_ref()
|
||||
.map_or("<<None>>", |p| p.to_str().unwrap())
|
||||
);
|
||||
|
||||
if dc_is_configured(context) {
|
||||
error!(context, "Cannot import backups to accounts in use.");
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
&context.sql.close(&context);
|
||||
dc_delete_file(context, context.get_dbfile());
|
||||
if dc_file_exist(context, context.get_dbfile()) {
|
||||
dc_delete_file(context, context.get_dbfile().unwrap());
|
||||
if dc_file_exist(context, context.get_dbfile().unwrap()) {
|
||||
error!(
|
||||
context,
|
||||
"Cannot import backups: Cannot delete the old file.",
|
||||
);
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if !dc_copy_file(context, as_path(backup_to_import), context.get_dbfile()) {
|
||||
return false;
|
||||
if !dc_copy_file(
|
||||
context,
|
||||
as_path(backup_to_import),
|
||||
context.get_dbfile().unwrap(),
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
/* error already logged */
|
||||
/* re-open copied database file */
|
||||
if !context.sql.open(&context, &context.get_dbfile(), 0) {
|
||||
return false;
|
||||
if !context
|
||||
.sql
|
||||
.open(&context, &context.get_dbfile().unwrap(), 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
let total_files_cnt = context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
|
||||
.query_row_col::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![], 0)
|
||||
.unwrap_or_default() as usize;
|
||||
info!(
|
||||
context,
|
||||
@@ -633,19 +653,19 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
|
||||
if permille > 990 {
|
||||
permille = 990
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
context.call_cb(Event::IMEX_PROGRESS, permille as uintptr_t, 0);
|
||||
if file_blob.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pathNfilename = context.get_blobdir().join(file_name);
|
||||
let pathNfilename = format!("{}/{}", as_str(context.get_blobdir()), file_name);
|
||||
if dc_write_file_safe(context, &pathNfilename, &file_blob) {
|
||||
continue;
|
||||
}
|
||||
error!(
|
||||
context,
|
||||
"Storage full? Cannot write file {} with {} bytes.",
|
||||
pathNfilename.display(),
|
||||
&pathNfilename,
|
||||
file_blob.len(),
|
||||
);
|
||||
// otherwise the user may believe the stuff is imported correctly, but there are files missing ...
|
||||
@@ -666,7 +686,7 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
|
||||
sql::try_execute(context, &context.sql, "VACUUM;").ok();
|
||||
Ok(())
|
||||
})
|
||||
.is_ok()
|
||||
.is_ok() as libc::c_int
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -674,10 +694,11 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
|
||||
******************************************************************************/
|
||||
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
|
||||
The macro avoids weird values of 0% or 100% while still working. */
|
||||
// TODO should return bool /rtn
|
||||
#[allow(non_snake_case)]
|
||||
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_int {
|
||||
let mut ok_to_continue: bool;
|
||||
let mut success = false;
|
||||
let mut success: libc::c_int = 0;
|
||||
|
||||
let mut delete_dest_file: libc::c_int = 0;
|
||||
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
|
||||
@@ -702,11 +723,20 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
info!(
|
||||
context,
|
||||
"Backup \"{}\" to \"{}\".",
|
||||
context.get_dbfile().display(),
|
||||
context
|
||||
.get_dbfile()
|
||||
.as_ref()
|
||||
.map_or("<<None>>", |p| p.to_str().unwrap()),
|
||||
as_str(dest_pathNfilename),
|
||||
);
|
||||
if dc_copy_file(context, context.get_dbfile(), as_path(dest_pathNfilename)) {
|
||||
context.sql.open(&context, &context.get_dbfile(), 0);
|
||||
if dc_copy_file(
|
||||
context,
|
||||
context.get_dbfile().unwrap(),
|
||||
as_path(dest_pathNfilename),
|
||||
) {
|
||||
context
|
||||
.sql
|
||||
.open(&context, &context.get_dbfile().unwrap(), 0);
|
||||
closed = false;
|
||||
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
|
||||
/*for logging only*/
|
||||
@@ -731,14 +761,14 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
}
|
||||
if ok_to_continue {
|
||||
let mut total_files_cnt = 0;
|
||||
let dir = context.get_blobdir();
|
||||
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
|
||||
let dir = std::path::Path::new(as_str(context.get_blobdir()));
|
||||
if let Ok(dir_handle) = std::fs::read_dir(dir) {
|
||||
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
|
||||
|
||||
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||
if total_files_cnt > 0 {
|
||||
// scan directory, pass 2: copy files
|
||||
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
|
||||
if let Ok(dir_handle) = std::fs::read_dir(dir) {
|
||||
sql.prepare(
|
||||
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|
||||
move |mut stmt, _| {
|
||||
@@ -769,7 +799,11 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
if permille > 990 {
|
||||
permille = 990;
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
context.call_cb(
|
||||
Event::IMEX_PROGRESS,
|
||||
permille as uintptr_t,
|
||||
0 as uintptr_t,
|
||||
);
|
||||
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
@@ -778,7 +812,12 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
continue;
|
||||
} else {
|
||||
info!(context, "EXPORTing filename={}", name);
|
||||
let curr_pathNfilename = context.get_blobdir().join(entry.file_name());
|
||||
let curr_pathNfilename = format!(
|
||||
"{}/{}",
|
||||
as_str(context.get_blobdir()),
|
||||
name
|
||||
);
|
||||
|
||||
if let Some(buf) =
|
||||
dc_read_file_safe(context, &curr_pathNfilename)
|
||||
{
|
||||
@@ -789,7 +828,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
error!(
|
||||
context,
|
||||
"Disk full? Cannot add file \"{}\" to backup.",
|
||||
curr_pathNfilename.display(),
|
||||
&curr_pathNfilename,
|
||||
);
|
||||
/* this is not recoverable! writing to the sqlite database should work! */
|
||||
ok_to_continue = false;
|
||||
@@ -808,7 +847,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
error!(
|
||||
context,
|
||||
"Backup: Cannot copy from blob-directory \"{}\".",
|
||||
context.get_blobdir().display(),
|
||||
as_str(context.get_blobdir()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -820,24 +859,28 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
.set_config_int(context, "backup_time", now as i32)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::ImexFileWritten(
|
||||
as_path(dest_pathNfilename).to_path_buf(),
|
||||
));
|
||||
success = true;
|
||||
context.call_cb(
|
||||
Event::IMEX_FILE_WRITTEN,
|
||||
dest_pathNfilename as uintptr_t,
|
||||
0,
|
||||
);
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
context,
|
||||
"Backup: Cannot get info for blob-directory \"{}\".",
|
||||
context.get_blobdir().display(),
|
||||
as_str(context.get_blobdir())
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if closed {
|
||||
context.sql.open(&context, &context.get_dbfile(), 0);
|
||||
context
|
||||
.sql
|
||||
.open(&context, &context.get_dbfile().unwrap(), 0);
|
||||
}
|
||||
if 0 != delete_dest_file {
|
||||
dc_delete_file(context, as_path(dest_pathNfilename));
|
||||
@@ -862,7 +905,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
|
||||
let mut path_plus_name: *mut libc::c_char = ptr::null_mut();
|
||||
let mut set_default: libc::c_int;
|
||||
let mut buf: *mut libc::c_char = ptr::null_mut();
|
||||
let mut buf_bytes: libc::size_t = 0;
|
||||
let mut buf_bytes: size_t = 0 as size_t;
|
||||
// a pointer inside buf, MUST NOT be free()'d
|
||||
let mut private_key: *const libc::c_char;
|
||||
let mut buf2: *mut libc::c_char = ptr::null_mut();
|
||||
@@ -969,7 +1012,8 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
|
||||
imported_cnt
|
||||
}
|
||||
|
||||
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool {
|
||||
// TODO should return bool /rtn
|
||||
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc::c_int {
|
||||
let mut export_errors = 0;
|
||||
|
||||
context
|
||||
@@ -991,14 +1035,14 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool
|
||||
for key_pair in keys {
|
||||
let (id, public_key, private_key, is_default) = key_pair?;
|
||||
if let Some(key) = public_key {
|
||||
if export_key_to_asc_file(context, dir, id, &key, is_default) {
|
||||
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
export_errors += 1;
|
||||
}
|
||||
if let Some(key) = private_key {
|
||||
if export_key_to_asc_file(context, dir, id, &key, is_default) {
|
||||
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
|
||||
export_errors += 1;
|
||||
}
|
||||
} else {
|
||||
@@ -1011,20 +1055,25 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
export_errors == 0
|
||||
if export_errors == 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Classic key export
|
||||
******************************************************************************/
|
||||
// TODO should return bool /rtn
|
||||
unsafe fn export_key_to_asc_file(
|
||||
context: &Context,
|
||||
dir: *const libc::c_char,
|
||||
id: libc::c_int,
|
||||
key: &Key,
|
||||
is_default: libc::c_int,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
) -> libc::c_int {
|
||||
let mut success: libc::c_int = 0i32;
|
||||
let file_name;
|
||||
if 0 != is_default {
|
||||
file_name = dc_mprintf(
|
||||
@@ -1050,11 +1099,15 @@ unsafe fn export_key_to_asc_file(
|
||||
}
|
||||
info!(context, "Exporting key {}", as_str(file_name),);
|
||||
dc_delete_file(context, as_path(file_name));
|
||||
if !key.write_asc_to_file(as_path(file_name), context) {
|
||||
if !key.write_asc_to_file(file_name, context) {
|
||||
error!(context, "Cannot write key to {}", as_str(file_name),);
|
||||
} else {
|
||||
context.call_cb(Event::ImexFileWritten(as_path(file_name).to_path_buf()));
|
||||
success = true;
|
||||
context.call_cb(
|
||||
Event::IMEX_FILE_WRITTEN,
|
||||
file_name as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
success = 1i32
|
||||
}
|
||||
free(file_name as *mut libc::c_void);
|
||||
|
||||
@@ -1065,11 +1118,13 @@ unsafe fn export_key_to_asc_file(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_render_setup_file() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
let t = test_context(Some(logging_cb));
|
||||
|
||||
configure_alice_keypair(&t.ctx);
|
||||
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
|
||||
@@ -1087,19 +1142,22 @@ mod tests {
|
||||
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
|
||||
}
|
||||
|
||||
fn ac_setup_msg_cb(ctx: &Context, evt: Event) -> libc::uintptr_t {
|
||||
match evt {
|
||||
Event::GetString {
|
||||
id: StockMessage::AcSetupMsgBody,
|
||||
..
|
||||
} => unsafe { "hello\r\nthere".strdup() as usize },
|
||||
_ => logging_cb(ctx, evt),
|
||||
unsafe extern "C" fn ac_setup_msg_cb(
|
||||
ctx: &Context,
|
||||
evt: Event,
|
||||
d1: uintptr_t,
|
||||
d2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
if evt == Event::GET_STRING && d1 == StockMessage::AcSetupMsgBody.to_usize().unwrap() {
|
||||
"hello\r\nthere".strdup() as usize
|
||||
} else {
|
||||
logging_cb(ctx, evt, d1, d2)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_setup_file_newline_replace() {
|
||||
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
|
||||
let t = test_context(Some(ac_setup_msg_cb));
|
||||
configure_alice_keypair(&t.ctx);
|
||||
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
|
||||
println!("{}", &msg);
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::ffi::CString;
|
||||
use std::ptr;
|
||||
|
||||
use chrono::TimeZone;
|
||||
use mmime::clist::*;
|
||||
use mmime::mailimf_types::*;
|
||||
use mmime::mailimf_types_helper::*;
|
||||
use mmime::mailmime_disposition::*;
|
||||
@@ -15,8 +14,7 @@ use mmime::other::*;
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::{get_version_str, Context};
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::context::{dc_get_version_str, Context};
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee::*;
|
||||
@@ -25,6 +23,7 @@ use crate::location;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -40,14 +39,14 @@ pub struct dc_mimefactory_t<'a> {
|
||||
pub loaded: dc_mimefactory_loaded_t,
|
||||
pub msg: Message,
|
||||
pub chat: Option<Chat>,
|
||||
pub increation: bool,
|
||||
pub increation: libc::c_int,
|
||||
pub in_reply_to: *mut libc::c_char,
|
||||
pub references: *mut libc::c_char,
|
||||
pub req_mdn: libc::c_int,
|
||||
pub out: *mut MMAPString,
|
||||
pub out_encrypted: libc::c_int,
|
||||
pub out_gossiped: libc::c_int,
|
||||
pub out_last_added_location_id: u32,
|
||||
pub out_last_added_location_id: uint32_t,
|
||||
pub error: *mut libc::c_char,
|
||||
pub context: &'a Context,
|
||||
}
|
||||
@@ -103,7 +102,7 @@ pub unsafe fn dc_mimefactory_load_msg(
|
||||
loaded: DC_MF_NOTHING_LOADED,
|
||||
msg,
|
||||
chat: Some(chat),
|
||||
increation: false,
|
||||
increation: 0,
|
||||
in_reply_to: ptr::null_mut(),
|
||||
references: ptr::null_mut(),
|
||||
req_mdn: 0,
|
||||
@@ -171,10 +170,10 @@ pub unsafe fn dc_mimefactory_load_msg(
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let command = factory.msg.param.get_cmd();
|
||||
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
|
||||
let msg = &factory.msg;
|
||||
|
||||
if command == SystemMessage::MemberRemovedFromGroup {
|
||||
if command == 5 {
|
||||
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
|
||||
let email_to_remove_c = email_to_remove.strdup();
|
||||
|
||||
@@ -198,8 +197,8 @@ pub unsafe fn dc_mimefactory_load_msg(
|
||||
}
|
||||
}
|
||||
}
|
||||
if command != SystemMessage::AutocryptSetupMessage
|
||||
&& command != SystemMessage::SecurejoinMessage
|
||||
if command != 6
|
||||
&& command != 7
|
||||
&& 0 != context
|
||||
.sql
|
||||
.get_config_int(context, "mdns_enabled")
|
||||
@@ -265,7 +264,7 @@ unsafe fn load_from(factory: &mut dc_mimefactory_t) {
|
||||
|
||||
pub unsafe fn dc_mimefactory_load_mdn<'a>(
|
||||
context: &'a Context,
|
||||
msg_id: u32,
|
||||
msg_id: uint32_t,
|
||||
) -> Result<dc_mimefactory_t, Error> {
|
||||
if 0 == context
|
||||
.sql
|
||||
@@ -291,7 +290,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
|
||||
loaded: DC_MF_NOTHING_LOADED,
|
||||
msg,
|
||||
chat: None,
|
||||
increation: false,
|
||||
increation: 0,
|
||||
in_reply_to: ptr::null_mut(),
|
||||
references: ptr::null_mut(),
|
||||
req_mdn: 0,
|
||||
@@ -335,7 +334,11 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
|
||||
Ok(factory)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefactory_t) -> bool {
|
||||
// TODO should return bool /rtn
|
||||
pub unsafe fn dc_mimefactory_render(
|
||||
context: &Context,
|
||||
factory: &mut dc_mimefactory_t,
|
||||
) -> libc::c_int {
|
||||
let subject: *mut mailimf_subject;
|
||||
let mut ok_to_continue = true;
|
||||
let imf_fields: *mut mailimf_fields;
|
||||
@@ -345,7 +348,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
let mut subject_str: *mut libc::c_char = ptr::null_mut();
|
||||
let mut afwd_email: libc::c_int = 0;
|
||||
let mut col: libc::c_int = 0;
|
||||
let mut success = false;
|
||||
let mut success: libc::c_int = 0;
|
||||
let mut parts: libc::c_int = 0;
|
||||
let mut e2ee_guaranteed: libc::c_int = 0;
|
||||
let mut min_verified: libc::c_int = 0;
|
||||
@@ -459,18 +462,19 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
.map(|s| format!("/{}", s))
|
||||
.unwrap_or_default();
|
||||
let os_part = CString::new(os_part).expect("String -> CString conversion failed");
|
||||
let version = CString::yolo(get_version_str());
|
||||
let version = dc_get_version_str();
|
||||
mailimf_fields_add(
|
||||
imf_fields,
|
||||
mailimf_field_new_custom(
|
||||
strdup(b"X-Mailer\x00" as *const u8 as *const libc::c_char),
|
||||
dc_mprintf(
|
||||
b"Delta Chat Core %s%s\x00" as *const u8 as *const libc::c_char,
|
||||
version.as_ptr(),
|
||||
version,
|
||||
os_part.as_ptr(),
|
||||
),
|
||||
),
|
||||
);
|
||||
free(version.cast());
|
||||
|
||||
mailimf_fields_add(
|
||||
imf_fields,
|
||||
@@ -530,7 +534,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
}
|
||||
|
||||
/* build header etc. */
|
||||
let command = factory.msg.param.get_cmd();
|
||||
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
|
||||
if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
mailimf_fields_add(
|
||||
imf_fields,
|
||||
@@ -547,8 +551,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
dc_encode_header_words(name.as_ptr()),
|
||||
),
|
||||
);
|
||||
|
||||
if command == SystemMessage::MemberRemovedFromGroup {
|
||||
if command == DC_CMD_MEMBER_REMOVED_FROM_GROUP {
|
||||
let email_to_remove = factory
|
||||
.msg
|
||||
.param
|
||||
@@ -567,7 +570,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if command == SystemMessage::MemberAddedToGroup {
|
||||
} else if command == DC_CMD_MEMBER_ADDED_TO_GROUP {
|
||||
let msg = &factory.msg;
|
||||
do_gossip = 1;
|
||||
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
|
||||
@@ -598,7 +601,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if command == SystemMessage::GroupNameChanged {
|
||||
} else if command == DC_CMD_GROUPNAME_CHANGED {
|
||||
let msg = &factory.msg;
|
||||
|
||||
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
|
||||
@@ -611,7 +614,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
value_to_add,
|
||||
),
|
||||
);
|
||||
} else if command == SystemMessage::GroupImageChanged {
|
||||
} else if command == DC_CMD_GROUPIMAGE_CHANGED {
|
||||
let msg = &factory.msg;
|
||||
grpimage = msg.param.get(Param::Arg);
|
||||
if grpimage.is_none() {
|
||||
@@ -625,8 +628,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if command == SystemMessage::LocationStreamingEnabled {
|
||||
if command == DC_CMD_LOCATION_STREAMING_ENABLED {
|
||||
mailimf_fields_add(
|
||||
imf_fields,
|
||||
mailimf_field_new_custom(
|
||||
@@ -637,7 +639,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
),
|
||||
);
|
||||
}
|
||||
if command == SystemMessage::AutocryptSetupMessage {
|
||||
if command == DC_CMD_AUTOCRYPT_SETUP_MESSAGE {
|
||||
mailimf_fields_add(
|
||||
imf_fields,
|
||||
mailimf_field_new_custom(
|
||||
@@ -650,7 +652,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
.stock_str(StockMessage::AcSetupMsgBody)
|
||||
.strdup();
|
||||
}
|
||||
if command == SystemMessage::SecurejoinMessage {
|
||||
if command == DC_CMD_SECUREJOIN_MESSAGE {
|
||||
let msg = &factory.msg;
|
||||
let step = msg.param.get(Param::Arg).unwrap_or_default().strdup();
|
||||
if strlen(step) > 0 {
|
||||
@@ -724,7 +726,6 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(grpimage) = grpimage {
|
||||
info!(factory.context, "setting group image '{}'", grpimage);
|
||||
let mut meta = dc_msg_new_untyped();
|
||||
@@ -958,16 +959,16 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
message_text = format!("{}\r\n", p2).strdup();
|
||||
let human_mime_part: *mut mailmime = build_body_text(message_text);
|
||||
mailmime_add_part(multipart, human_mime_part);
|
||||
let version = CString::yolo(get_version_str());
|
||||
let version = dc_get_version_str();
|
||||
message_text2 =
|
||||
dc_mprintf(
|
||||
b"Reporting-UA: Delta Chat %s\r\nOriginal-Recipient: rfc822;%s\r\nFinal-Recipient: rfc822;%s\r\nOriginal-Message-ID: <%s>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n\x00"
|
||||
as *const u8 as *const libc::c_char,
|
||||
version.as_ptr(),
|
||||
version,
|
||||
factory.from_addr, factory.from_addr,
|
||||
factory.msg.rfc724_mid
|
||||
);
|
||||
|
||||
free(version.cast());
|
||||
let content_type_0: *mut mailmime_content = mailmime_content_new_with_str(
|
||||
b"message/disposition-notification\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
@@ -1045,7 +1046,7 @@ pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefact
|
||||
}
|
||||
factory.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
|
||||
mailmime_write_mem(factory.out, &mut col, message);
|
||||
success = true;
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1083,7 +1084,7 @@ unsafe fn get_subject(
|
||||
} else {
|
||||
b"\x00" as *const u8 as *const libc::c_char
|
||||
};
|
||||
if msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
if msg.param.get_int(Param::Cmd).unwrap_or_default() == DC_CMD_AUTOCRYPT_SETUP_MESSAGE {
|
||||
ret = context.stock_str(StockMessage::AcSetupMsgSubject).strdup()
|
||||
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
ret = format!(
|
||||
@@ -1251,7 +1252,7 @@ unsafe fn build_body_file(
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
0 as libc::size_t,
|
||||
0 as size_t,
|
||||
mailmime_parameter_new(
|
||||
strdup(
|
||||
b"filename*\x00" as *const u8 as *const libc::c_char,
|
||||
@@ -1288,10 +1289,7 @@ unsafe fn build_body_file(
|
||||
) as *mut libc::c_void,
|
||||
);
|
||||
mime_sub = mailmime_new_empty(content, mime_fields);
|
||||
let abs_path = dc_get_abs_path(context, path_filename)
|
||||
.to_c_string()
|
||||
.unwrap();
|
||||
mailmime_set_body_file(mime_sub, dc_strdup(abs_path.as_ptr()));
|
||||
mailmime_set_body_file(mime_sub, dc_get_abs_path(context, path_filename));
|
||||
if !ret_file_name_as_sent.is_null() {
|
||||
*ret_file_name_as_sent = dc_strdup(filename_to_send)
|
||||
}
|
||||
|
||||
2299
src/dc_mimeparser.rs
2299
src/dc_mimeparser.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ use mmime::other::*;
|
||||
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
|
||||
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
|
||||
b"utf-8\x00" as *const u8 as *const libc::c_char,
|
||||
mmapstr,
|
||||
begin,
|
||||
end.wrapping_offset_from(begin) as libc::size_t,
|
||||
end.wrapping_offset_from(begin) as size_t,
|
||||
) {
|
||||
ok_to_continue = false;
|
||||
continue;
|
||||
@@ -81,7 +82,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
|
||||
if mmap_string_append_len(
|
||||
mmapstr,
|
||||
end,
|
||||
cur.wrapping_offset_from(end) as libc::size_t,
|
||||
cur.wrapping_offset_from(end) as size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
@@ -92,7 +93,7 @@ pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut lib
|
||||
} else if mmap_string_append_len(
|
||||
mmapstr,
|
||||
begin,
|
||||
cur.wrapping_offset_from(begin) as libc::size_t,
|
||||
cur.wrapping_offset_from(begin) as size_t,
|
||||
)
|
||||
.is_null()
|
||||
{
|
||||
@@ -121,10 +122,10 @@ unsafe fn quote_word(
|
||||
display_charset: *const libc::c_char,
|
||||
mmapstr: *mut MMAPString,
|
||||
word: *const libc::c_char,
|
||||
size: libc::size_t,
|
||||
size: size_t,
|
||||
) -> bool {
|
||||
let mut cur: *const libc::c_char;
|
||||
let mut i = 0;
|
||||
let mut i: size_t = 0i32 as size_t;
|
||||
let mut hex: [libc::c_char; 4] = [0; 4];
|
||||
// let mut col: libc::c_int = 0i32;
|
||||
if mmap_string_append(mmapstr, b"=?\x00" as *const u8 as *const libc::c_char).is_null() {
|
||||
@@ -187,7 +188,7 @@ unsafe fn get_word(
|
||||
{
|
||||
cur = cur.offset(1isize)
|
||||
}
|
||||
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
|
||||
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as size_t);
|
||||
*pend = cur;
|
||||
}
|
||||
|
||||
@@ -196,9 +197,9 @@ unsafe fn get_word(
|
||||
******************************************************************************/
|
||||
|
||||
/* see comment below */
|
||||
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
|
||||
unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
|
||||
let mut cur: *const libc::c_char = word;
|
||||
let mut i = 0;
|
||||
let mut i: size_t = 0i32 as size_t;
|
||||
while i < size {
|
||||
match *cur as libc::c_int {
|
||||
44 | 58 | 33 | 34 | 35 | 36 | 64 | 91 | 92 | 93 | 94 | 96 | 123 | 124 | 125 | 126
|
||||
@@ -221,7 +222,7 @@ pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let mut out: *mut libc::c_char = ptr::null_mut();
|
||||
let mut cur_token = 0;
|
||||
let mut cur_token: size_t = 0i32 as size_t;
|
||||
let r: libc::c_int = mailmime_encoded_phrase_parse(
|
||||
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
|
||||
in_0,
|
||||
|
||||
@@ -6,16 +6,16 @@ use std::time::SystemTime;
|
||||
use std::{fmt, fs, ptr};
|
||||
|
||||
use chrono::{Local, TimeZone};
|
||||
use itertools::max;
|
||||
use libc::uintptr_t;
|
||||
use mmime::clist::*;
|
||||
use mmime::mailimf_types::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::context::Context;
|
||||
use crate::error::Error;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
use itertools::max;
|
||||
|
||||
/* Some tools and enhancements to the used libraries, there should be
|
||||
no references to Context and other "larger" classes here. */
|
||||
/* ** library-private **********************************************************/
|
||||
@@ -98,7 +98,7 @@ pub unsafe fn dc_str_replace(
|
||||
}
|
||||
|
||||
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
|
||||
let mut len: libc::size_t;
|
||||
let mut len: size_t;
|
||||
let mut cur: *const libc::c_uchar;
|
||||
if !buf.is_null() && 0 != *buf as libc::c_int {
|
||||
len = strlen(buf);
|
||||
@@ -118,7 +118,7 @@ unsafe fn dc_ltrim(buf: *mut libc::c_char) {
|
||||
}
|
||||
|
||||
unsafe fn dc_rtrim(buf: *mut libc::c_char) {
|
||||
let mut len: libc::size_t;
|
||||
let mut len: size_t;
|
||||
let mut cur: *mut libc::c_uchar;
|
||||
if !buf.is_null() && 0 != *buf as libc::c_int {
|
||||
len = strlen(buf);
|
||||
@@ -301,9 +301,9 @@ pub unsafe fn dc_truncate_n_unwrap_str(
|
||||
if *p1 as libc::c_int > ' ' as i32 {
|
||||
lastIsCharacter = 1
|
||||
} else if 0 != lastIsCharacter {
|
||||
let used_bytes = (p1 as uintptr_t).wrapping_sub(buf as uintptr_t) as libc::size_t;
|
||||
let used_bytes: size_t = (p1 as uintptr_t).wrapping_sub(buf as uintptr_t) as size_t;
|
||||
if dc_utf8_strnlen(buf, used_bytes) >= approx_characters as usize {
|
||||
let buf_bytes = strlen(buf);
|
||||
let buf_bytes: size_t = strlen(buf);
|
||||
if buf_bytes.wrapping_sub(used_bytes) >= strlen(ellipse_utf8) {
|
||||
strcpy(p1 as *mut libc::c_char, ellipse_utf8);
|
||||
}
|
||||
@@ -324,12 +324,12 @@ pub unsafe fn dc_truncate_n_unwrap_str(
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: libc::size_t) -> libc::size_t {
|
||||
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: size_t) -> size_t {
|
||||
if s.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut j: libc::size_t = 0;
|
||||
let mut j: size_t = 0;
|
||||
for i in 0..n {
|
||||
if *s.add(i) as libc::c_int & 0xc0 != 0x80 {
|
||||
j = j.wrapping_add(1)
|
||||
@@ -543,7 +543,7 @@ pub fn dc_create_id() -> String {
|
||||
- the group-id should be a string with the characters [a-zA-Z0-9\-_] */
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let buf: [u32; 3] = [rng.gen(), rng.gen(), rng.gen()];
|
||||
let buf: [uint32_t; 3] = [rng.gen(), rng.gen(), rng.gen()];
|
||||
|
||||
encode_66bits_as_base64(buf[0usize], buf[1usize], buf[2usize])
|
||||
}
|
||||
@@ -792,32 +792,57 @@ pub fn dc_get_filemeta(buf: &[u8]) -> Result<(u32, u32), Error> {
|
||||
///
|
||||
/// If `path` starts with "$BLOBDIR", replaces it with the blobdir path.
|
||||
/// Otherwise, returns path as is.
|
||||
pub fn dc_get_abs_path<P: AsRef<std::path::Path>>(
|
||||
pub fn dc_get_abs_path_safe<P: AsRef<std::path::Path>>(
|
||||
context: &Context,
|
||||
path: P,
|
||||
) -> std::path::PathBuf {
|
||||
let p: &std::path::Path = path.as_ref();
|
||||
if let Ok(p) = p.strip_prefix("$BLOBDIR") {
|
||||
context.get_blobdir().join(p)
|
||||
assert!(
|
||||
context.has_blobdir(),
|
||||
"Expected context to have blobdir to substitute $BLOBDIR",
|
||||
);
|
||||
std::path::PathBuf::from(as_str(context.get_blobdir())).join(p)
|
||||
} else {
|
||||
p.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_file_exist(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
|
||||
dc_get_abs_path(context, &path).exists()
|
||||
pub unsafe fn dc_get_abs_path(
|
||||
context: &Context,
|
||||
path_filename: impl AsRef<str>,
|
||||
) -> *mut libc::c_char {
|
||||
let starts = path_filename.as_ref().starts_with("$BLOBDIR");
|
||||
|
||||
if starts && !context.has_blobdir() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let mut path_filename_abs = path_filename.as_ref().strdup();
|
||||
if starts && context.has_blobdir() {
|
||||
dc_str_replace(
|
||||
&mut path_filename_abs,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
context.get_blobdir(),
|
||||
);
|
||||
}
|
||||
path_filename_abs
|
||||
}
|
||||
|
||||
pub fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> u64 {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
pub fn dc_file_exist(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
|
||||
dc_get_abs_path_safe(context, &path).exists()
|
||||
}
|
||||
|
||||
pub fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> uint64_t {
|
||||
let path_abs = dc_get_abs_path_safe(context, &path);
|
||||
match fs::metadata(&path_abs) {
|
||||
Ok(meta) => meta.len() as u64,
|
||||
Ok(meta) => meta.len() as uint64_t,
|
||||
Err(_err) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
let path_abs = dc_get_abs_path_safe(context, &path);
|
||||
if !path_abs.is_file() {
|
||||
warn!(
|
||||
context,
|
||||
@@ -841,8 +866,8 @@ pub fn dc_copy_file(
|
||||
src: impl AsRef<std::path::Path>,
|
||||
dest: impl AsRef<std::path::Path>,
|
||||
) -> bool {
|
||||
let src_abs = dc_get_abs_path(context, &src);
|
||||
let dest_abs = dc_get_abs_path(context, &dest);
|
||||
let src_abs = dc_get_abs_path_safe(context, &src);
|
||||
let dest_abs = dc_get_abs_path_safe(context, &dest);
|
||||
match fs::copy(&src_abs, &dest_abs) {
|
||||
Ok(_) => true,
|
||||
Err(_) => {
|
||||
@@ -858,7 +883,7 @@ pub fn dc_copy_file(
|
||||
}
|
||||
|
||||
pub fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
let path_abs = dc_get_abs_path_safe(context, &path);
|
||||
if !path_abs.exists() {
|
||||
match fs::create_dir_all(path_abs) {
|
||||
Ok(_) => true,
|
||||
@@ -881,7 +906,7 @@ pub unsafe fn dc_write_file(
|
||||
context: &Context,
|
||||
pathNfilename: *const libc::c_char,
|
||||
buf: *const libc::c_void,
|
||||
buf_bytes: libc::size_t,
|
||||
buf_bytes: size_t,
|
||||
) -> libc::c_int {
|
||||
let bytes = std::slice::from_raw_parts(buf as *const u8, buf_bytes);
|
||||
|
||||
@@ -893,7 +918,7 @@ pub fn dc_write_file_safe<P: AsRef<std::path::Path>>(
|
||||
path: P,
|
||||
buf: &[u8],
|
||||
) -> bool {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
let path_abs = dc_get_abs_path_safe(context, &path);
|
||||
if let Err(_err) = fs::write(&path_abs, buf) {
|
||||
warn!(
|
||||
context,
|
||||
@@ -912,7 +937,7 @@ pub unsafe fn dc_read_file(
|
||||
context: &Context,
|
||||
pathNfilename: *const libc::c_char,
|
||||
buf: *mut *mut libc::c_void,
|
||||
buf_bytes: *mut libc::size_t,
|
||||
buf_bytes: *mut size_t,
|
||||
) -> libc::c_int {
|
||||
if pathNfilename.is_null() {
|
||||
return 0;
|
||||
@@ -928,7 +953,7 @@ pub unsafe fn dc_read_file(
|
||||
}
|
||||
|
||||
pub fn dc_read_file_safe<P: AsRef<std::path::Path>>(context: &Context, path: P) -> Option<Vec<u8>> {
|
||||
let path_abs = dc_get_abs_path(context, &path);
|
||||
let path_abs = dc_get_abs_path_safe(context, &path);
|
||||
match fs::read(&path_abs) {
|
||||
Ok(bytes) => Some(bytes),
|
||||
Err(_err) => {
|
||||
@@ -997,22 +1022,13 @@ pub unsafe fn dc_get_fine_pathNfilename(
|
||||
}
|
||||
|
||||
pub fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
|
||||
context
|
||||
.get_blobdir()
|
||||
.to_str()
|
||||
.map(|s| path.as_ref().starts_with(s))
|
||||
.unwrap_or_default()
|
||||
path.as_ref().starts_with(as_str(context.get_blobdir()))
|
||||
|| path.as_ref().starts_with("$BLOBDIR")
|
||||
}
|
||||
|
||||
fn dc_make_rel_path(context: &Context, path: &mut String) {
|
||||
if context
|
||||
.get_blobdir()
|
||||
.to_str()
|
||||
.map(|s| path.starts_with(s))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
*path = path.replace("$BLOBDIR", context.get_blobdir().to_str().unwrap());
|
||||
if path.starts_with(as_str(context.get_blobdir())) {
|
||||
*path = path.replace("$BLOBDIR", as_str(context.get_blobdir()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
86
src/e2ee.rs
86
src/e2ee.rs
@@ -30,9 +30,10 @@ use crate::keyring::*;
|
||||
use crate::peerstate::*;
|
||||
use crate::pgp::*;
|
||||
use crate::securejoin::handle_degrade_event;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default)]
|
||||
pub struct E2eeHelper {
|
||||
pub encryption_successfull: bool,
|
||||
cdata_to_free: Option<Box<dyn Any>>,
|
||||
@@ -86,7 +87,7 @@ impl E2eeHelper {
|
||||
EncryptPreference::NoPreference
|
||||
};
|
||||
|
||||
let addr = context.get_config(Config::ConfiguredAddr);
|
||||
let addr = context.sql.get_config(context, "configured_addr");
|
||||
|
||||
if let Some(addr) = addr {
|
||||
let pubkey_ret = load_or_generate_self_public_key(context, &addr).map_err(|err| {
|
||||
@@ -165,7 +166,7 @@ impl E2eeHelper {
|
||||
let message_to_encrypt: *mut mailmime = mailmime_new(
|
||||
MAILMIME_MESSAGE as libc::c_int,
|
||||
ptr::null(),
|
||||
0 as libc::size_t,
|
||||
0i32 as size_t,
|
||||
mailmime_fields_new_empty(),
|
||||
mailmime_get_content_message(),
|
||||
ptr::null_mut(),
|
||||
@@ -311,7 +312,7 @@ impl E2eeHelper {
|
||||
/* create MIME-structure that will contain the encrypted text */
|
||||
let mut encrypted_part: *mut mailmime = new_data_part(
|
||||
ptr::null_mut(),
|
||||
0 as libc::size_t,
|
||||
0i32 as size_t,
|
||||
b"multipart/encrypted\x00" as *const u8
|
||||
as *const libc::c_char
|
||||
as *mut libc::c_char,
|
||||
@@ -386,17 +387,16 @@ impl E2eeHelper {
|
||||
/*just a pointer into mailmime structure, must not be freed*/
|
||||
let imffields: *mut mailimf_fields = mailmime_find_mailimf_fields(in_out_message);
|
||||
let mut message_time = 0;
|
||||
let mut from = None;
|
||||
let mut from: *mut libc::c_char = ptr::null_mut();
|
||||
let mut private_keyring = Keyring::default();
|
||||
let mut public_keyring_for_validate = Keyring::default();
|
||||
let mut gossip_headers: *mut mailimf_fields = ptr::null_mut();
|
||||
if !(in_out_message.is_null() || imffields.is_null()) {
|
||||
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
|
||||
let mut field: *mut mailimf_field =
|
||||
mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
|
||||
if !field.is_null() && !(*field).fld_data.fld_from.is_null() {
|
||||
from = mailimf_find_first_addr((*(*field).fld_data.fld_from).frm_mb_list)
|
||||
}
|
||||
|
||||
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
|
||||
if !field.is_null() && !(*field).fld_data.fld_orig_date.is_null() {
|
||||
let orig_date: *mut mailimf_orig_date = (*field).fld_data.fld_orig_date;
|
||||
@@ -408,32 +408,28 @@ impl E2eeHelper {
|
||||
}
|
||||
}
|
||||
let mut peerstate = None;
|
||||
let autocryptheader = from
|
||||
.as_ref()
|
||||
.and_then(|from| Aheader::from_imffields(from, imffields));
|
||||
if message_time > 0 {
|
||||
if let Some(ref from) = from {
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, from);
|
||||
let autocryptheader = Aheader::from_imffields(from, imffields);
|
||||
if message_time > 0 && !from.is_null() {
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, as_str(from));
|
||||
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if let Some(ref header) = autocryptheader {
|
||||
peerstate.apply_header(&header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false);
|
||||
} else if message_time > peerstate.last_seen_autocrypt
|
||||
&& !contains_report(in_out_message)
|
||||
{
|
||||
peerstate.degrade_encryption(message_time);
|
||||
peerstate.save_to_db(&context.sql, false);
|
||||
}
|
||||
} else if let Some(ref header) = autocryptheader {
|
||||
let p = Peerstate::from_header(context, header, message_time);
|
||||
assert!(p.save_to_db(&context.sql, true));
|
||||
peerstate = Some(p);
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if let Some(ref header) = autocryptheader {
|
||||
peerstate.apply_header(&header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false);
|
||||
} else if message_time > peerstate.last_seen_autocrypt
|
||||
&& !contains_report(in_out_message)
|
||||
{
|
||||
peerstate.degrade_encryption(message_time);
|
||||
peerstate.save_to_db(&context.sql, false);
|
||||
}
|
||||
} else if let Some(ref header) = autocryptheader {
|
||||
let p = Peerstate::from_header(context, header, message_time);
|
||||
assert!(p.save_to_db(&context.sql, true));
|
||||
peerstate = Some(p);
|
||||
}
|
||||
}
|
||||
/* load private key for decryption */
|
||||
let self_addr = context.get_config(Config::ConfiguredAddr);
|
||||
let self_addr = context.sql.get_config(context, "configured_addr");
|
||||
if let Some(self_addr) = self_addr {
|
||||
if private_keyring.load_self_private_for_decrypting(
|
||||
context,
|
||||
@@ -441,8 +437,7 @@ impl E2eeHelper {
|
||||
&context.sql,
|
||||
) {
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate =
|
||||
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
|
||||
peerstate = Peerstate::from_addr(&context, &context.sql, as_str(from));
|
||||
}
|
||||
if let Some(ref peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
@@ -491,12 +486,14 @@ impl E2eeHelper {
|
||||
if !gossip_headers.is_null() {
|
||||
mailimf_fields_free(gossip_headers);
|
||||
}
|
||||
|
||||
free(from as *mut libc::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn new_data_part(
|
||||
data: *mut libc::c_void,
|
||||
data_bytes: libc::size_t,
|
||||
data_bytes: size_t,
|
||||
default_content_type: *mut libc::c_char,
|
||||
default_encoding: libc::c_int,
|
||||
) -> *mut mailmime {
|
||||
@@ -747,7 +744,7 @@ unsafe fn decrypt_recursive(
|
||||
&mut decrypted_mime,
|
||||
) {
|
||||
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
|
||||
let mut dummy: libc::size_t = 0i32 as libc::size_t;
|
||||
let mut dummy: size_t = 0i32 as size_t;
|
||||
let mut test: *mut mailimf_fields = ptr::null_mut();
|
||||
if mailimf_envelope_and_optional_fields_parse(
|
||||
(*decrypted_mime).mm_mime_start,
|
||||
@@ -834,7 +831,7 @@ unsafe fn decrypt_part(
|
||||
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
|
||||
/* must not be free()'d */
|
||||
let mut decoded_data: *const libc::c_char = ptr::null_mut();
|
||||
let mut decoded_data_bytes: libc::size_t = 0;
|
||||
let mut decoded_data_bytes: size_t = 0i32 as size_t;
|
||||
let mut sth_decrypted = false;
|
||||
|
||||
*ret_decrypted_mime = ptr::null_mut();
|
||||
@@ -880,7 +877,7 @@ unsafe fn decrypt_part(
|
||||
}
|
||||
} else {
|
||||
let r: libc::c_int;
|
||||
let mut current_index: libc::size_t = 0;
|
||||
let mut current_index: size_t = 0i32 as size_t;
|
||||
r = mailmime_part_parse(
|
||||
(*mime_data).dt_data.dt_text.dt_data,
|
||||
(*mime_data).dt_data.dt_text.dt_length,
|
||||
@@ -917,7 +914,7 @@ unsafe fn decrypt_part(
|
||||
let plain_bytes = plain.len();
|
||||
let plain_buf = plain.as_ptr() as *const libc::c_char;
|
||||
|
||||
let mut index: libc::size_t = 0;
|
||||
let mut index: size_t = 0i32 as size_t;
|
||||
let mut decrypted_mime: *mut mailmime = ptr::null_mut();
|
||||
if mailmime_parse(
|
||||
plain_buf as *const _,
|
||||
@@ -1028,7 +1025,7 @@ unsafe fn contains_report(mime: *mut mailmime) -> bool {
|
||||
/// [Config::ConfiguredAddr] is configured, this address is returned.
|
||||
pub fn ensure_secret_key_exists(context: &Context) -> Result<String> {
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.get_config(&Config::ConfiguredAddr)
|
||||
.ok_or(format_err!(concat!(
|
||||
"Failed to get self address, ",
|
||||
"cannot ensure secret key if not configured."
|
||||
@@ -1095,12 +1092,15 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
let mut decoded_data_bytes = 0;
|
||||
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
|
||||
|
||||
assert!(mailmime_transfer_decode(
|
||||
msg1,
|
||||
&mut decoded_data,
|
||||
&mut decoded_data_bytes,
|
||||
&mut transfer_decoding_buffer,
|
||||
));
|
||||
assert_eq!(
|
||||
mailmime_transfer_decode(
|
||||
msg1,
|
||||
&mut decoded_data,
|
||||
&mut decoded_data_bytes,
|
||||
&mut transfer_decoding_buffer,
|
||||
),
|
||||
1
|
||||
);
|
||||
println!(
|
||||
"{:?}",
|
||||
String::from_utf8_lossy(std::slice::from_raw_parts(
|
||||
|
||||
@@ -22,8 +22,6 @@ pub enum Error {
|
||||
Image(image_meta::ImageError),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
Utf8(std::str::Utf8Error),
|
||||
#[fail(display = "{:?}", _0)]
|
||||
CStringError(crate::dc_tools::CStringError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -64,12 +62,6 @@ impl From<image_meta::ImageError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::dc_tools::CStringError> for Error {
|
||||
fn from(err: crate::dc_tools::CStringError) -> Error {
|
||||
Error::CStringError(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
($e:expr) => {
|
||||
|
||||
229
src/events.rs
229
src/events.rs
@@ -1,229 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use strum::EnumProperty;
|
||||
|
||||
use crate::stock::StockMessage;
|
||||
|
||||
impl Event {
|
||||
/// Returns the corresponding Event id.
|
||||
pub fn as_id(&self) -> i32 {
|
||||
self.get_str("id")
|
||||
.expect("missing id")
|
||||
.parse()
|
||||
.expect("invalid id")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, EnumProperty)]
|
||||
pub enum Event {
|
||||
/// The library-user may write an informational string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "100"))]
|
||||
Info(String),
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "101"))]
|
||||
SmtpConnected(String),
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "102"))]
|
||||
ImapConnected(String),
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "103"))]
|
||||
SmtpMessageSent(String),
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// This event should not be reported to the end-user using a popup or something like that.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "300"))]
|
||||
Warning(String),
|
||||
|
||||
/// The library-user should report an error to the end-user.
|
||||
/// Passed to the callback given to dc_context_new().
|
||||
///
|
||||
/// As most things are asynchronous, things may go wrong at any time and the user
|
||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
///
|
||||
/// However, for ongoing processes (eg. configure())
|
||||
/// or for functions that are expected to fail (eg. dc_continue_key_transfer())
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the _last_ error
|
||||
/// in a messasge box then.
|
||||
///
|
||||
/// @return
|
||||
#[strum(props(id = "400"))]
|
||||
Error(String),
|
||||
|
||||
/// An action cannot be performed because there is no network available.
|
||||
///
|
||||
/// The library will typically try over after a some time
|
||||
/// and when dc_maybe_network() is called.
|
||||
///
|
||||
/// Network errors should be reported to users in a non-disturbing way,
|
||||
/// however, as network errors may come in a sequence,
|
||||
/// it is not useful to raise each an every error to the user.
|
||||
/// For this purpose, data1 is set to 1 if the error is probably worth reporting.
|
||||
///
|
||||
/// Moreover, if the UI detects that the device is offline,
|
||||
/// it is probably more useful to report this to the user
|
||||
/// instead of the string from data2.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "401"))]
|
||||
ErrorNetwork(String),
|
||||
|
||||
/// An action cannot be performed because the user is not in the group.
|
||||
/// Reported eg. after a call to
|
||||
/// dc_set_chat_name(), dc_set_chat_profile_image(),
|
||||
/// dc_add_contact_to_chat(), dc_remove_contact_from_chat(),
|
||||
/// dc_send_text_msg() or another sending function.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "410"))]
|
||||
ErrorSelfNotInGroup(String),
|
||||
|
||||
/// Messages or chats changed. One or more messages or chats changed for various
|
||||
/// reasons in the database:
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2000"))]
|
||||
MsgsChanged { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// There is a fresh message. Typically, the user will show an notification
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2005"))]
|
||||
IncomingMsg { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2010"))]
|
||||
MsgDelivered { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2012"))]
|
||||
MsgFailed { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see dc_msg_get_state().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2015"))]
|
||||
MsgRead { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See dc_set_chat_name(), dc_set_chat_profile_image(), dc_add_contact_to_chat()
|
||||
/// and dc_remove_contact_from_chat().
|
||||
///
|
||||
/// @return 0
|
||||
#[strum(props(id = "2020"))]
|
||||
ChatModified(u32),
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2030"))]
|
||||
ContactsChanged(Option<u32>),
|
||||
|
||||
/// Location of one or more contact has changed.
|
||||
///
|
||||
/// @param data1 (u32) contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// eg. after calling dc_delete_all_locations(), this parameter is set to `None`.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2035"))]
|
||||
LocationChanged(Option<u32>),
|
||||
|
||||
/// Inform about the configuration progress started by configure().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @return 0
|
||||
#[strum(props(id = "2041"))]
|
||||
ConfigureProgress(usize),
|
||||
|
||||
/// Inform about the import/export progress started by dc_imex().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
#[strum(props(id = "2051"))]
|
||||
ImexProgress(usize),
|
||||
|
||||
/// A file has been exported. A file has been written by dc_imex().
|
||||
/// This event may be sent multiple times by a single call to dc_imex().
|
||||
///
|
||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||
/// services.
|
||||
///
|
||||
/// @param data2 0
|
||||
/// @return 0
|
||||
#[strum(props(id = "2052"))]
|
||||
ImexFileWritten(PathBuf),
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the inviter
|
||||
/// (Alice, the person who shows the QR code).
|
||||
///
|
||||
/// These events are typically sent after a joiner has scanned the QR code
|
||||
/// generated by dc_get_securejoin_qr().
|
||||
///
|
||||
/// @param data1 (int) ID of the contact that wants to join.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
/// 1000=Protocol finished for this contact.
|
||||
/// @return 0
|
||||
#[strum(props(id = "2060"))]
|
||||
SecurejoinInviterProgress { contact_id: u32, progress: usize },
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the joiner
|
||||
/// (Bob, the person who scans the QR code).
|
||||
/// The events are typically sent while dc_join_securejoin(), which
|
||||
/// may take some time, is executed.
|
||||
/// @param data1 (int) ID of the inviting contact.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
/// @return 0
|
||||
#[strum(props(id = "2061"))]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
// the following events are functions that should be provided by the frontends
|
||||
/// Requeste a localized string from the frontend.
|
||||
/// @param data1 (int) ID of the string to request, one of the DC_STR_/// constants.
|
||||
/// @param data2 (int) The count. If the requested string contains a placeholder for a numeric value,
|
||||
/// the ui may use this value to return different strings on different plural forms.
|
||||
/// @return (const char*) Null-terminated UTF-8 string.
|
||||
/// The string will be free()'d by the core,
|
||||
/// so it must be allocated using malloc() or a compatible function.
|
||||
/// Return 0 if the ui cannot provide the requested string
|
||||
/// the core will use a default string in english language then.
|
||||
#[strum(props(id = "2091"))]
|
||||
GetString { id: StockMessage, count: usize },
|
||||
}
|
||||
116
src/imap.rs
116
src/imap.rs
@@ -1,6 +1,5 @@
|
||||
use std::ffi::CString;
|
||||
use std::net;
|
||||
use std::ptr;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Condvar, Mutex, RwLock,
|
||||
@@ -9,15 +8,10 @@ use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_receive_imf::dc_receive_imf;
|
||||
use crate::dc_tools::CStringExt;
|
||||
use crate::dc_tools::*;
|
||||
use crate::events::Event;
|
||||
use crate::job::{job_add, Action};
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::{dc_rfc724_mid_exists, dc_update_msg_move_state, dc_update_server_uid};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::param::Params;
|
||||
use crate::types::*;
|
||||
|
||||
const DC_IMAP_SEEN: usize = 0x0001;
|
||||
const DC_REGENERATE: usize = 0x01;
|
||||
@@ -31,11 +25,15 @@ const PREFETCH_FLAGS: &str = "(UID ENVELOPE)";
|
||||
const BODY_FLAGS: &str = "(FLAGS BODY.PEEK[])";
|
||||
const FETCH_FLAGS: &str = "(FLAGS)";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Imap {
|
||||
config: Arc<RwLock<ImapConfig>>,
|
||||
watch: Arc<(Mutex<bool>, Condvar)>,
|
||||
|
||||
get_config: dc_get_config_t,
|
||||
set_config: dc_set_config_t,
|
||||
precheck_imf: dc_precheck_imf_t,
|
||||
receive_imf: dc_receive_imf_t,
|
||||
|
||||
session: Arc<Mutex<Option<Session>>>,
|
||||
stream: Arc<RwLock<Option<net::TcpStream>>>,
|
||||
connected: Arc<Mutex<bool>>,
|
||||
@@ -43,7 +41,6 @@ pub struct Imap {
|
||||
should_reconnect: AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OAuth2 {
|
||||
user: String,
|
||||
access_token: String,
|
||||
@@ -68,7 +65,6 @@ enum FolderMeaning {
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Client {
|
||||
Secure(
|
||||
imap::Client<native_tls::TlsStream<net::TcpStream>>,
|
||||
@@ -77,13 +73,11 @@ enum Client {
|
||||
Insecure(imap::Client<net::TcpStream>, net::TcpStream),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Session {
|
||||
Secure(imap::Session<native_tls::TlsStream<net::TcpStream>>),
|
||||
Insecure(imap::Session<net::TcpStream>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum IdleHandle<'a> {
|
||||
Secure(imap::extensions::idle::Handle<'a, native_tls::TlsStream<net::TcpStream>>),
|
||||
Insecure(imap::extensions::idle::Handle<'a, net::TcpStream>),
|
||||
@@ -313,7 +307,6 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ImapConfig {
|
||||
pub addr: String,
|
||||
pub imap_server: String,
|
||||
@@ -353,12 +346,21 @@ impl Default for ImapConfig {
|
||||
}
|
||||
|
||||
impl Imap {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(
|
||||
get_config: dc_get_config_t,
|
||||
set_config: dc_set_config_t,
|
||||
precheck_imf: dc_precheck_imf_t,
|
||||
receive_imf: dc_receive_imf_t,
|
||||
) -> Self {
|
||||
Imap {
|
||||
session: Arc::new(Mutex::new(None)),
|
||||
stream: Arc::new(RwLock::new(None)),
|
||||
config: Arc::new(RwLock::new(ImapConfig::default())),
|
||||
watch: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
get_config,
|
||||
set_config,
|
||||
precheck_imf,
|
||||
receive_imf,
|
||||
connected: Arc::new(Mutex::new(false)),
|
||||
should_reconnect: AtomicBool::new(false),
|
||||
}
|
||||
@@ -438,12 +440,14 @@ impl Imap {
|
||||
let imap_server: &str = config.imap_server.as_ref();
|
||||
let imap_port = config.imap_port;
|
||||
|
||||
emit_event!(
|
||||
log_event!(
|
||||
context,
|
||||
Event::ErrorNetwork(format!(
|
||||
"Could not connect to IMAP-server {}:{}. ({})",
|
||||
imap_server, imap_port, err
|
||||
))
|
||||
Event::ERROR_NETWORK,
|
||||
0,
|
||||
"Could not connect to IMAP-server {}:{}. ({})",
|
||||
imap_server,
|
||||
imap_port,
|
||||
err
|
||||
);
|
||||
|
||||
return false;
|
||||
@@ -459,10 +463,7 @@ impl Imap {
|
||||
true
|
||||
}
|
||||
Err((err, _)) => {
|
||||
emit_event!(
|
||||
context,
|
||||
Event::ErrorNetwork(format!("Cannot login ({})", err))
|
||||
);
|
||||
log_event!(context, Event::ERROR_NETWORK, 0, "Cannot login ({})", err);
|
||||
self.unsetup_handle(context);
|
||||
|
||||
false
|
||||
@@ -555,12 +556,13 @@ impl Imap {
|
||||
let caps_list = caps
|
||||
.iter()
|
||||
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
|
||||
emit_event!(
|
||||
log_event!(
|
||||
context,
|
||||
Event::ImapConnected(format!(
|
||||
"IMAP-LOGIN as {}, capabilities: {}",
|
||||
lp.mail_user, caps_list,
|
||||
))
|
||||
Event::IMAP_CONNECTED,
|
||||
0,
|
||||
"IMAP-LOGIN as {}, capabilities: {}",
|
||||
lp.mail_user,
|
||||
caps_list,
|
||||
);
|
||||
(false, can_idle, has_xlist)
|
||||
}
|
||||
@@ -692,7 +694,7 @@ impl Imap {
|
||||
|
||||
fn get_config_last_seen_uid<S: AsRef<str>>(&self, context: &Context, folder: S) -> (u32, u32) {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
if let Some(entry) = context.sql.get_config(context, &key) {
|
||||
if let Some(entry) = (self.get_config)(context, &key) {
|
||||
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
|
||||
let mut parts = entry.split(':');
|
||||
(
|
||||
@@ -826,7 +828,7 @@ impl Imap {
|
||||
|
||||
if 0 == unsafe {
|
||||
let message_id_c = CString::yolo(message_id);
|
||||
precheck_imf(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
|
||||
(self.precheck_imf)(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
|
||||
} {
|
||||
// check passed, go fetch the rest
|
||||
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
|
||||
@@ -890,7 +892,7 @@ impl Imap {
|
||||
let key = format!("imap.mailbox.{}", folder.as_ref());
|
||||
let val = format!("{}:{}", uidvalidity, lastseenuid);
|
||||
|
||||
context.sql.set_config(context, &key, Some(&val)).ok();
|
||||
(self.set_config)(context, &key, Some(&val));
|
||||
}
|
||||
|
||||
fn fetch_single_msg<S: AsRef<str>>(
|
||||
@@ -960,7 +962,7 @@ impl Imap {
|
||||
if !is_deleted && msg.body().is_some() {
|
||||
let body = msg.body().unwrap();
|
||||
unsafe {
|
||||
dc_receive_imf(
|
||||
(self.receive_imf)(
|
||||
context,
|
||||
body.as_ptr() as *const libc::c_char,
|
||||
body.len(),
|
||||
@@ -1645,53 +1647,3 @@ fn get_folder_meaning(folder_name: &imap::types::Name) -> FolderMeaning {
|
||||
_ => res,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn precheck_imf(
|
||||
context: &Context,
|
||||
rfc724_mid: *const libc::c_char,
|
||||
server_folder: &str,
|
||||
server_uid: u32,
|
||||
) -> libc::c_int {
|
||||
let mut rfc724_mid_exists: libc::c_int = 0i32;
|
||||
let msg_id: u32;
|
||||
let mut old_server_folder: *mut libc::c_char = ptr::null_mut();
|
||||
let mut old_server_uid: u32 = 0i32 as u32;
|
||||
let mut mark_seen: libc::c_int = 0i32;
|
||||
msg_id = dc_rfc724_mid_exists(
|
||||
context,
|
||||
rfc724_mid,
|
||||
&mut old_server_folder,
|
||||
&mut old_server_uid,
|
||||
);
|
||||
if msg_id != 0i32 as libc::c_uint {
|
||||
rfc724_mid_exists = 1i32;
|
||||
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
|
||||
&& old_server_uid == 0i32 as libc::c_uint
|
||||
{
|
||||
info!(context, "[move] detected bbc-self {}", as_str(rfc724_mid),);
|
||||
mark_seen = 1i32
|
||||
} else if as_str(old_server_folder) != server_folder {
|
||||
info!(
|
||||
context,
|
||||
"[move] detected moved message {}",
|
||||
as_str(rfc724_mid),
|
||||
);
|
||||
dc_update_msg_move_state(context, rfc724_mid, MoveState::Stay);
|
||||
}
|
||||
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
|
||||
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
|
||||
}
|
||||
context.do_heuristics_moves(server_folder, msg_id);
|
||||
if 0 != mark_seen {
|
||||
job_add(
|
||||
context,
|
||||
Action::MarkseenMsgOnImap,
|
||||
msg_id as libc::c_int,
|
||||
Params::new(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
libc::free(old_server_folder as *mut libc::c_void);
|
||||
rfc724_mid_exists
|
||||
}
|
||||
|
||||
26
src/job.rs
26
src/job.rs
@@ -3,7 +3,6 @@ use std::ptr;
|
||||
use std::time::Duration;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use mmime::clist::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::chat;
|
||||
@@ -13,13 +12,13 @@ use crate::context::Context;
|
||||
use crate::dc_imex::*;
|
||||
use crate::dc_mimefactory::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::events::Event;
|
||||
use crate::imap::*;
|
||||
use crate::location;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
/// Thread IDs
|
||||
@@ -183,16 +182,18 @@ impl Job {
|
||||
);
|
||||
let chat_id: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![self.foreign_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
context.call_cb(Event::MsgDelivered {
|
||||
chat_id: chat_id as u32,
|
||||
msg_id: self.foreign_id,
|
||||
});
|
||||
context.call_cb(
|
||||
Event::MSG_DELIVERED,
|
||||
chat_id as uintptr_t,
|
||||
self.foreign_id as uintptr_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -598,10 +599,11 @@ pub fn perform_smtp_idle(context: &Context) {
|
||||
fn get_next_wakeup_time(context: &Context, thread: Thread) -> Duration {
|
||||
let t: i64 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
|
||||
params![thread],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -642,7 +644,7 @@ pub fn job_action_exists(context: &Context, action: Action) -> bool {
|
||||
|
||||
/* special case for DC_JOB_SEND_MSG_TO_SMTP */
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
|
||||
pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
|
||||
let mut success = 0;
|
||||
|
||||
/* load message data */
|
||||
@@ -680,7 +682,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
|
||||
}
|
||||
}
|
||||
/* create message */
|
||||
if !dc_mimefactory_render(context, &mut mimefactory) {
|
||||
if 0 == dc_mimefactory_render(context, &mut mimefactory) {
|
||||
dc_set_msg_failed(context, msg_id, as_opt_str(mimefactory.error));
|
||||
} else if 0
|
||||
!= mimefactory
|
||||
@@ -988,9 +990,9 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
|
||||
ret_connected
|
||||
}
|
||||
|
||||
fn send_mdn(context: &Context, msg_id: u32) {
|
||||
fn send_mdn(context: &Context, msg_id: uint32_t) {
|
||||
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
|
||||
if unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
|
||||
if 0 != unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
|
||||
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::configure::*;
|
||||
use crate::context::Context;
|
||||
use crate::imap::Imap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JobThread {
|
||||
pub name: &'static str,
|
||||
pub folder_config_name: &'static str,
|
||||
|
||||
66
src/key.rs
66
src/key.rs
@@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
|
||||
use libc;
|
||||
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
|
||||
@@ -11,6 +11,7 @@ use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::sql::{self, Sql};
|
||||
use crate::x::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Key {
|
||||
@@ -141,10 +142,11 @@ impl Key {
|
||||
) -> Option<Self> {
|
||||
let addr = self_addr.as_ref();
|
||||
|
||||
sql.query_get_value(
|
||||
sql.query_row_col(
|
||||
context,
|
||||
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
|
||||
&[addr],
|
||||
0,
|
||||
)
|
||||
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
|
||||
}
|
||||
@@ -154,10 +156,11 @@ impl Key {
|
||||
self_addr: impl AsRef<str>,
|
||||
sql: &Sql,
|
||||
) -> Option<Self> {
|
||||
sql.query_get_value(
|
||||
sql.query_row_col(
|
||||
context,
|
||||
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
|
||||
&[self_addr.as_ref()],
|
||||
0,
|
||||
)
|
||||
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
|
||||
}
|
||||
@@ -215,15 +218,30 @@ impl Key {
|
||||
.expect("failed to serialize key")
|
||||
}
|
||||
|
||||
pub fn write_asc_to_file(&self, file: impl AsRef<Path>, context: &Context) -> bool {
|
||||
let file_content = self.to_asc(None).into_bytes();
|
||||
|
||||
if dc_write_file_safe(context, &file, &file_content) {
|
||||
return true;
|
||||
} else {
|
||||
error!(context, "Cannot write key to {}", file.as_ref().display());
|
||||
pub fn write_asc_to_file(&self, file: *const libc::c_char, context: &Context) -> bool {
|
||||
if file.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let file_content = self.to_asc(None);
|
||||
let file_content_c = CString::new(file_content).unwrap();
|
||||
|
||||
let success = if 0
|
||||
== unsafe {
|
||||
dc_write_file(
|
||||
context,
|
||||
file,
|
||||
file_content_c.as_ptr() as *const libc::c_void,
|
||||
file_content_c.as_bytes().len(),
|
||||
)
|
||||
} {
|
||||
error!(context, "Cannot write key to {}", to_string(file));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
success
|
||||
}
|
||||
|
||||
pub fn fingerprint(&self) -> String {
|
||||
@@ -233,11 +251,23 @@ impl Key {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fingerprint_c(&self) -> *mut libc::c_char {
|
||||
let res = CString::new(self.fingerprint()).unwrap();
|
||||
|
||||
unsafe { strdup(res.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn formatted_fingerprint(&self) -> String {
|
||||
let rawhex = self.fingerprint();
|
||||
dc_format_fingerprint(&rawhex)
|
||||
}
|
||||
|
||||
pub fn formatted_fingerprint_c(&self) -> *mut libc::c_char {
|
||||
let res = CString::new(self.formatted_fingerprint()).unwrap();
|
||||
|
||||
unsafe { strdup(res.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn split_key(&self) -> Option<Key> {
|
||||
match self {
|
||||
Key::Public(_) => None,
|
||||
@@ -283,6 +313,14 @@ pub fn dc_format_fingerprint(fingerprint: &str) -> String {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn dc_format_fingerprint_c(fp: *const libc::c_char) -> *mut libc::c_char {
|
||||
let input = unsafe { CStr::from_ptr(fp).to_str().unwrap() };
|
||||
let res = dc_format_fingerprint(input);
|
||||
let res_c = CString::new(res).unwrap();
|
||||
|
||||
unsafe { strdup(res_c.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
|
||||
pub fn dc_normalize_fingerprint(fp: &str) -> String {
|
||||
fp.to_uppercase()
|
||||
@@ -291,6 +329,14 @@ pub fn dc_normalize_fingerprint(fp: &str) -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn dc_normalize_fingerprint_c(fp: *const libc::c_char) -> *mut libc::c_char {
|
||||
let input = unsafe { CStr::from_ptr(fp).to_str().unwrap() };
|
||||
let res = dc_normalize_fingerprint(input);
|
||||
let res_c = CString::new(res).unwrap();
|
||||
|
||||
unsafe { strdup(res_c.as_ptr()) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -33,10 +33,11 @@ impl<'a> Keyring<'a> {
|
||||
self_addr: impl AsRef<str>,
|
||||
sql: &Sql,
|
||||
) -> bool {
|
||||
sql.query_get_value(
|
||||
sql.query_row_col(
|
||||
context,
|
||||
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
|
||||
&[self_addr.as_ref()],
|
||||
0,
|
||||
)
|
||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
|
||||
.map(|key| self.add_owned(key))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![deny(clippy::correctness, missing_debug_implementations)]
|
||||
#![deny(clippy::correctness)]
|
||||
// TODO: make all of these errors, such that clippy actually passes.
|
||||
#![warn(clippy::all, clippy::perf, clippy::not_unsafe_ptr_arg_deref)]
|
||||
// This is nice, but for now just annoying.
|
||||
@@ -18,17 +18,12 @@ extern crate strum;
|
||||
extern crate strum_macros;
|
||||
#[macro_use]
|
||||
extern crate jetscii;
|
||||
#[macro_use]
|
||||
extern crate debug_stub_derive;
|
||||
|
||||
#[macro_use]
|
||||
mod log;
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
|
||||
pub(crate) mod events;
|
||||
pub use events::*;
|
||||
|
||||
mod aheader;
|
||||
pub mod chat;
|
||||
pub mod chatlist;
|
||||
@@ -54,6 +49,7 @@ pub mod qr;
|
||||
mod smtp;
|
||||
pub mod sql;
|
||||
mod stock;
|
||||
pub mod types;
|
||||
pub mod x;
|
||||
|
||||
pub mod dc_array;
|
||||
|
||||
@@ -3,16 +3,17 @@ use quick_xml;
|
||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||
|
||||
use crate::chat;
|
||||
use crate::constants::Event;
|
||||
use crate::constants::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::*;
|
||||
use crate::message::*;
|
||||
use crate::param::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
|
||||
// location handling
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -224,7 +225,11 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
|
||||
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
}
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
if 0 != seconds {
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
|
||||
job_add(
|
||||
@@ -287,7 +292,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
|
||||
}
|
||||
}
|
||||
if continue_streaming {
|
||||
context.call_cb(Event::LocationChanged(Some(1)));
|
||||
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
|
||||
};
|
||||
schedule_MAYBE_SEND_LOCATIONS(context, 0);
|
||||
}
|
||||
@@ -364,7 +369,7 @@ fn is_marker(txt: &str) -> bool {
|
||||
|
||||
pub fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?;
|
||||
context.call_cb(Event::LocationChanged(None));
|
||||
context.call_cb(Event::LOCATION_CHANGED, 0, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -648,7 +653,11 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
|
||||
).is_ok() {
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_device_msg(context, chat_id, stock_str);
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
context.call_cb(
|
||||
Event::CHAT_MODIFIED,
|
||||
chat_id as usize,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
src/log.rs
24
src/log.rs
@@ -4,8 +4,7 @@ macro_rules! info {
|
||||
info!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
emit_event!($ctx, $crate::Event::Info(formatted));
|
||||
log_event!($ctx, $crate::constants::Event::INFO, 0, $msg, $($args),*);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,8 +14,7 @@ macro_rules! warn {
|
||||
warn!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
emit_event!($ctx, $crate::Event::Warning(formatted));
|
||||
log_event!($ctx, $crate::constants::Event::WARNING, 0, $msg, $($args),*);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,14 +24,26 @@ macro_rules! error {
|
||||
error!($ctx, $msg,)
|
||||
};
|
||||
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
log_event!($ctx, $crate::constants::Event::ERROR, 0, $msg, $($args),*);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_event {
|
||||
($ctx:expr, $data1:expr, $msg:expr) => {
|
||||
log_event!($ctx, $data1, $msg,)
|
||||
};
|
||||
($ctx:expr, $event:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
|
||||
let formatted = format!($msg, $($args),*);
|
||||
emit_event!($ctx, $crate::Event::Error(formatted));
|
||||
let formatted_c = std::ffi::CString::new(formatted).unwrap();
|
||||
$ctx.call_cb($event, $data1 as libc::uintptr_t,
|
||||
formatted_c.as_ptr() as libc::uintptr_t);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! emit_event {
|
||||
($ctx:expr, $event:expr) => {
|
||||
$ctx.call_cb($event);
|
||||
($ctx:expr, $event:expr, $data1:expr, $data2:expr) => {
|
||||
$ctx.call_cb($event, $data1 as libc::uintptr_t, $data2 as libc::uintptr_t);
|
||||
};
|
||||
}
|
||||
|
||||
161
src/message.rs
161
src/message.rs
@@ -1,5 +1,5 @@
|
||||
use std::ffi::CString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
@@ -9,16 +9,15 @@ use crate::chat::{self, Chat};
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::*;
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::dc_tools::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::job::*;
|
||||
use crate::lot::{Lot, LotState, Meaning};
|
||||
use crate::param::*;
|
||||
use crate::pgp::*;
|
||||
use crate::sql;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
/// In practice, the user additionally cuts the string himself pixel-accurate.
|
||||
@@ -88,7 +87,7 @@ impl Lot {
|
||||
self.text1 = Some(context.stock_str(StockMessage::Draft).to_owned().into());
|
||||
self.text1_meaning = Meaning::Text1Draft;
|
||||
} else if msg.from_id == DC_CONTACT_ID_SELF {
|
||||
if dc_msg_is_info(msg) || chat.is_self_talk() {
|
||||
if 0 != dc_msg_is_info(msg) || chat.is_self_talk() {
|
||||
self.text1 = None;
|
||||
self.text1_meaning = Meaning::None;
|
||||
} else {
|
||||
@@ -96,7 +95,7 @@ impl Lot {
|
||||
self.text1_meaning = Meaning::Text1Self;
|
||||
}
|
||||
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
if dc_msg_is_info(msg) || contact.is_none() {
|
||||
if 0 != dc_msg_is_info(msg) || contact.is_none() {
|
||||
self.text1 = None;
|
||||
self.text1_meaning = Meaning::None;
|
||||
} else {
|
||||
@@ -137,7 +136,7 @@ impl Lot {
|
||||
/// to check if a mail was sent, use dc_msg_is_sent()
|
||||
/// approx. max. length returned by dc_msg_get_text()
|
||||
/// approx. max. length returned by dc_get_msg_info()
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct Message {
|
||||
pub id: u32,
|
||||
pub from_id: u32,
|
||||
@@ -152,7 +151,7 @@ pub struct Message {
|
||||
pub timestamp_rcvd: i64,
|
||||
pub text: Option<String>,
|
||||
pub rfc724_mid: *mut libc::c_char,
|
||||
pub in_reply_to: Option<String>,
|
||||
pub in_reply_to: *mut libc::c_char,
|
||||
pub server_folder: Option<String>,
|
||||
pub server_uid: u32,
|
||||
// TODO: enum
|
||||
@@ -174,10 +173,11 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
|
||||
|
||||
let msg = msg.unwrap();
|
||||
|
||||
let rawtxt: Option<String> = context.sql.query_get_value(
|
||||
let rawtxt: Option<String> = context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT txt_raw FROM msgs WHERE id=?;",
|
||||
params![msg_id as i32],
|
||||
0,
|
||||
);
|
||||
|
||||
if rawtxt.is_none() {
|
||||
@@ -269,10 +269,15 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(path) = dc_msg_get_file(context, &msg) {
|
||||
let bytes = dc_get_filebytes(context, &path);
|
||||
ret += &format!("\nFile: {}, {}, bytes\n", path.display(), bytes);
|
||||
let p = dc_msg_get_file(context, &msg);
|
||||
if !p.is_null() && 0 != *p.offset(0isize) as libc::c_int {
|
||||
ret += &format!(
|
||||
"\nFile: {}, {}, bytes\n",
|
||||
as_str(p),
|
||||
dc_get_filebytes(context, as_path(p)) as libc::c_int,
|
||||
);
|
||||
}
|
||||
free(p as *mut libc::c_void);
|
||||
|
||||
if msg.type_0 != Viewtype::Text {
|
||||
ret += "Type: ";
|
||||
@@ -323,7 +328,7 @@ pub fn dc_msg_new(viewtype: Viewtype) -> Message {
|
||||
timestamp_rcvd: 0,
|
||||
text: None,
|
||||
rfc724_mid: std::ptr::null_mut(),
|
||||
in_reply_to: None,
|
||||
in_reply_to: std::ptr::null_mut(),
|
||||
server_folder: None,
|
||||
server_uid: 0,
|
||||
is_dc_message: 0,
|
||||
@@ -338,6 +343,7 @@ impl Drop for Message {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
free(self.rfc724_mid.cast());
|
||||
free(self.in_reply_to.cast());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,10 +378,17 @@ pub fn dc_msg_guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)>
|
||||
KNOWN.get(extension).map(|x| *x)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_msg_get_file(context: &Context, msg: &Message) -> Option<PathBuf> {
|
||||
msg.param
|
||||
.get(Param::File)
|
||||
.map(|f| dc_get_abs_path(context, f))
|
||||
pub unsafe fn dc_msg_get_file(context: &Context, msg: &Message) -> *mut libc::c_char {
|
||||
let mut file_abs = ptr::null_mut();
|
||||
|
||||
if let Some(file_rel) = msg.param.get(Param::File) {
|
||||
file_abs = dc_get_abs_path(context, file_rel);
|
||||
}
|
||||
if !file_abs.is_null() {
|
||||
file_abs
|
||||
} else {
|
||||
dc_strdup(0 as *const libc::c_char)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,7 +452,10 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
|
||||
let mut msg = dc_msg_new_untyped();
|
||||
msg.id = row.get::<_, i32>(0)? as u32;
|
||||
msg.rfc724_mid = row.get::<_, String>(1)?.strdup();
|
||||
msg.in_reply_to = row.get::<_, Option<String>>(2)?;
|
||||
msg.in_reply_to = match row.get::<_, Option<String>>(2)? {
|
||||
Some(s) => s.strdup(),
|
||||
None => std::ptr::null_mut(),
|
||||
};
|
||||
msg.server_folder = row.get::<_, Option<String>>(3)?;
|
||||
msg.server_uid = row.get(4)?;
|
||||
msg.move_state = row.get(5)?;
|
||||
@@ -487,10 +503,11 @@ pub fn dc_msg_load_from_db(context: &Context, id: u32) -> Result<Message, Error>
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_mime_headers(context: &Context, msg_id: u32) -> *mut libc::c_char {
|
||||
let headers: Option<String> = context.sql.query_get_value(
|
||||
let headers: Option<String> = context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT mime_headers FROM msgs WHERE id=?;",
|
||||
params![msg_id as i32],
|
||||
0,
|
||||
);
|
||||
|
||||
if let Some(headers) = headers {
|
||||
@@ -519,10 +536,7 @@ pub unsafe fn dc_delete_msgs(context: &Context, msg_ids: *const u32, msg_cnt: li
|
||||
}
|
||||
|
||||
if 0 != msg_cnt {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
|
||||
job_kill_action(context, Action::Housekeeping);
|
||||
job_add(context, Action::Housekeeping, 0, Params::new(), 10);
|
||||
};
|
||||
@@ -590,10 +604,7 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize)
|
||||
}
|
||||
|
||||
if send_event {
|
||||
context.call_cb(Event::MsgsChanged {
|
||||
chat_id: 0,
|
||||
msg_id: 0,
|
||||
});
|
||||
context.call_cb(Event::MSGS_CHANGED, 0, 0);
|
||||
}
|
||||
|
||||
true
|
||||
@@ -687,7 +698,7 @@ pub unsafe fn dc_msg_get_filename(msg: &Message) -> *mut libc::c_char {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_msg_get_filebytes(context: &Context, msg: &Message) -> u64 {
|
||||
pub fn dc_msg_get_filebytes(context: &Context, msg: &Message) -> uint64_t {
|
||||
if let Some(file) = msg.param.get(Param::File) {
|
||||
return dc_get_filebytes(context, &file);
|
||||
}
|
||||
@@ -706,8 +717,13 @@ pub fn dc_msg_get_duration(msg: &Message) -> libc::c_int {
|
||||
msg.param.get_int(Param::Duration).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn dc_msg_get_showpadlock(msg: &Message) -> bool {
|
||||
msg.param.get_int(Param::GuranteeE2ee).unwrap_or_default() != 0
|
||||
// TODO should return bool /rtn
|
||||
pub fn dc_msg_get_showpadlock(msg: &Message) -> libc::c_int {
|
||||
if msg.param.get_int(Param::GuranteeE2ee).unwrap_or_default() != 0 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
pub fn dc_msg_get_summary(context: &Context, msg: &mut Message, chat: Option<&Chat>) -> Lot {
|
||||
@@ -768,7 +784,7 @@ pub fn dc_msg_get_summarytext_by_raw(
|
||||
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(),
|
||||
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(),
|
||||
Viewtype::Audio | Viewtype::File => {
|
||||
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
if param.get_int(Param::Cmd) == Some(6) {
|
||||
append_text = false;
|
||||
context
|
||||
.stock_str(StockMessage::AcSetupMsgSubject)
|
||||
@@ -794,7 +810,7 @@ pub fn dc_msg_get_summarytext_by_raw(
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if param.get_cmd() != SystemMessage::LocationOnly {
|
||||
if param.get_int(Param::Cmd) != Some(9) {
|
||||
"".to_string()
|
||||
} else {
|
||||
append_text = false;
|
||||
@@ -827,27 +843,48 @@ pub unsafe fn dc_msg_has_deviating_timestamp(msg: &Message) -> libc::c_int {
|
||||
(sort_timestamp / 86400 != send_timestamp / 86400) as libc::c_int
|
||||
}
|
||||
|
||||
pub fn dc_msg_is_sent(msg: &Message) -> bool {
|
||||
msg.state as i32 >= MessageState::OutDelivered as i32
|
||||
// TODO should return bool /rtn
|
||||
pub fn dc_msg_is_sent(msg: &Message) -> libc::c_int {
|
||||
if msg.state as i32 >= MessageState::OutDelivered as i32 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_msg_is_starred(msg: &Message) -> bool {
|
||||
msg.starred
|
||||
}
|
||||
|
||||
pub fn dc_msg_is_forwarded(msg: &Message) -> bool {
|
||||
0 != msg.param.get_int(Param::Forwarded).unwrap_or_default()
|
||||
// TODO should return bool /rtn
|
||||
pub fn dc_msg_is_forwarded(msg: &Message) -> libc::c_int {
|
||||
if 0 != msg.param.get_int(Param::Forwarded).unwrap_or_default() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_msg_is_info(msg: &Message) -> bool {
|
||||
let cmd = msg.param.get_cmd();
|
||||
msg.from_id == 2i32 as libc::c_uint
|
||||
// TODO should return bool /rtn
|
||||
pub fn dc_msg_is_info(msg: &Message) -> libc::c_int {
|
||||
let cmd = msg.param.get_int(Param::Cmd).unwrap_or_default();
|
||||
if msg.from_id == 2i32 as libc::c_uint
|
||||
|| msg.to_id == 2i32 as libc::c_uint
|
||||
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
|
||||
|| 0 != cmd && cmd != 6i32
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
pub fn dc_msg_is_increation(msg: &Message) -> bool {
|
||||
chat::msgtype_has_file(msg.type_0) && msg.state == MessageState::OutPreparing
|
||||
// TODO should return bool /rtn
|
||||
pub fn dc_msg_is_increation(msg: &Message) -> libc::c_int {
|
||||
if chat::msgtype_has_file(msg.type_0) && msg.state == MessageState::OutPreparing {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
|
||||
@@ -855,20 +892,33 @@ pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage
|
||||
msg.param.get_int(Param::Cmd) == Some(6)
|
||||
}
|
||||
|
||||
pub unsafe fn dc_msg_get_setupcodebegin(context: &Context, msg: &Message) -> *mut libc::c_char {
|
||||
let mut filename: *mut libc::c_char = ptr::null_mut();
|
||||
let mut buf: *mut libc::c_char = ptr::null_mut();
|
||||
let mut buf_bytes: size_t = 0i32 as size_t;
|
||||
// just a pointer inside buf, MUST NOT be free()'d
|
||||
let mut buf_headerline: *const libc::c_char = ptr::null();
|
||||
// just a pointer inside buf, MUST NOT be free()'d
|
||||
let mut buf_setupcodebegin: *const libc::c_char = ptr::null();
|
||||
let mut ret: *mut libc::c_char = ptr::null_mut();
|
||||
if dc_msg_is_setupmessage(msg) {
|
||||
if let Some(filename) = dc_msg_get_file(context, msg) {
|
||||
if let Some(mut buf) = dc_read_file_safe(context, filename) {
|
||||
filename = dc_msg_get_file(context, msg);
|
||||
if !(filename.is_null() || *filename.offset(0isize) as libc::c_int == 0i32) {
|
||||
if !(0
|
||||
== dc_read_file(
|
||||
context,
|
||||
filename,
|
||||
&mut buf as *mut *mut libc::c_char as *mut *mut libc::c_void,
|
||||
&mut buf_bytes,
|
||||
)
|
||||
|| buf.is_null()
|
||||
|| buf_bytes <= 0)
|
||||
{
|
||||
if dc_split_armored_data(
|
||||
buf.as_mut_ptr().cast(),
|
||||
buf,
|
||||
&mut buf_headerline,
|
||||
&mut buf_setupcodebegin,
|
||||
ptr::null_mut(),
|
||||
@@ -884,6 +934,8 @@ pub unsafe fn dc_msg_get_setupcodebegin(context: &Context, msg: &Message) -> *mu
|
||||
}
|
||||
}
|
||||
}
|
||||
free(filename as *mut libc::c_void);
|
||||
free(buf as *mut libc::c_void);
|
||||
if !ret.is_null() {
|
||||
ret
|
||||
} else {
|
||||
@@ -983,10 +1035,11 @@ pub fn dc_msg_exists(context: &Context, msg_id: u32) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
let chat_id: Option<u32> = context.sql.query_get_value(
|
||||
let chat_id: Option<u32> = context.sql.query_row_col(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?;",
|
||||
params![msg_id],
|
||||
0,
|
||||
);
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
@@ -1030,10 +1083,11 @@ pub fn dc_set_msg_failed(context: &Context, msg_id: u32, error: Option<impl AsRe
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
context.call_cb(Event::MsgFailed {
|
||||
chat_id: msg.chat_id,
|
||||
msg_id,
|
||||
});
|
||||
context.call_cb(
|
||||
Event::MSG_FAILED,
|
||||
msg.chat_id as uintptr_t,
|
||||
msg_id as uintptr_t,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1104,10 +1158,11 @@ pub unsafe fn dc_mdn_from_ext(
|
||||
/* send event about new state */
|
||||
let ist_cnt: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
|
||||
params![*ret_msg_id as i32],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
/*
|
||||
@@ -1152,7 +1207,7 @@ pub fn dc_get_real_msg_cnt(context: &Context) -> libc::c_int {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
|
||||
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> size_t {
|
||||
match context.sql.query_row(
|
||||
"SELECT COUNT(*) \
|
||||
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
@@ -1160,7 +1215,7 @@ pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
|
||||
rusqlite::NO_PARAMS,
|
||||
|row| row.get::<_, isize>(0),
|
||||
) {
|
||||
Ok(res) => res as libc::size_t,
|
||||
Ok(res) => res as size_t,
|
||||
Err(err) => {
|
||||
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err);
|
||||
0
|
||||
|
||||
10
src/param.rs
10
src/param.rs
@@ -4,7 +4,6 @@ use std::str;
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
use crate::dc_mimeparser::SystemMessage;
|
||||
use crate::error;
|
||||
|
||||
/// Available param keys.
|
||||
@@ -95,7 +94,7 @@ impl fmt::Display for Params {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (i, (key, value)) in self.inner.iter().enumerate() {
|
||||
if i > 0 {
|
||||
writeln!(f)?;
|
||||
write!(f, "\n")?;
|
||||
}
|
||||
write!(f, "{}={}", *key as u8 as char, value)?;
|
||||
}
|
||||
@@ -179,13 +178,6 @@ impl Params {
|
||||
self.get(key).and_then(|s| s.parse().ok())
|
||||
}
|
||||
|
||||
/// Get the parameter behind `Param::Cmd` interpreted as `SystemMessage`.
|
||||
pub fn get_cmd(&self) -> SystemMessage {
|
||||
self.get_int(Param::Cmd)
|
||||
.and_then(SystemMessage::from_i32)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Get the given parameter and parse as `f64`.
|
||||
pub fn get_float(&self, key: Param) -> Option<f64> {
|
||||
self.get(key).and_then(|s| s.parse().ok())
|
||||
|
||||
@@ -14,6 +14,7 @@ use rand::thread_rng;
|
||||
use crate::dc_tools::*;
|
||||
use crate::key::*;
|
||||
use crate::keyring::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
pub unsafe fn dc_split_armored_data(
|
||||
@@ -24,7 +25,7 @@ pub unsafe fn dc_split_armored_data(
|
||||
ret_base64: *mut *const libc::c_char,
|
||||
) -> bool {
|
||||
let mut success = false;
|
||||
let mut line_chars: libc::size_t = 0;
|
||||
let mut line_chars: size_t = 0i32 as size_t;
|
||||
let mut line: *mut libc::c_char = buf;
|
||||
let mut p1: *mut libc::c_char = buf;
|
||||
let mut p2: *mut libc::c_char;
|
||||
@@ -104,7 +105,7 @@ pub unsafe fn dc_split_armored_data(
|
||||
}
|
||||
p1 = p1.offset(1isize);
|
||||
line = p1;
|
||||
line_chars = 0;
|
||||
line_chars = 0i32 as size_t
|
||||
} else {
|
||||
p1 = p1.offset(1isize);
|
||||
line_chars = line_chars.wrapping_add(1)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use mmime::mailimf_types::*;
|
||||
use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||
use std::ptr;
|
||||
|
||||
use crate::aheader::EncryptPreference;
|
||||
use crate::chat::{self, Chat};
|
||||
use crate::config::*;
|
||||
use crate::configure::*;
|
||||
use crate::constants::*;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_mimeparser::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::e2ee::*;
|
||||
use crate::error::Error;
|
||||
use crate::events::Event;
|
||||
use crate::key::*;
|
||||
use crate::lot::LotState;
|
||||
use crate::message::*;
|
||||
@@ -19,32 +20,39 @@ use crate::peerstate::*;
|
||||
use crate::qr::check_qr;
|
||||
use crate::stock::StockMessage;
|
||||
use crate::token;
|
||||
use crate::types::*;
|
||||
|
||||
pub const NON_ALPHANUMERIC_WITHOUT_DOT: &AsciiSet = &NON_ALPHANUMERIC.remove(b'.');
|
||||
|
||||
macro_rules! joiner_progress {
|
||||
($context:tt, $contact_id:expr, $progress:expr) => {
|
||||
macro_rules! progress {
|
||||
($context:tt, $event:expr, $contact_id:expr, $progress:expr) => {
|
||||
assert!(
|
||||
$progress >= 0 && $progress <= 1000,
|
||||
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
|
||||
);
|
||||
$context.call_cb($crate::events::Event::SecurejoinJoinerProgress {
|
||||
contact_id: $contact_id,
|
||||
progress: $progress,
|
||||
});
|
||||
$context.call_cb($event, $contact_id as uintptr_t, $progress as uintptr_t);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! joiner_progress {
|
||||
($context:tt, $contact_id:expr, $progress:expr) => {
|
||||
progress!(
|
||||
$context,
|
||||
Event::SECUREJOIN_JOINER_PROGRESS,
|
||||
$contact_id,
|
||||
$progress
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! inviter_progress {
|
||||
($context:tt, $contact_id:expr, $progress:expr) => {
|
||||
assert!(
|
||||
$progress >= 0 && $progress <= 1000,
|
||||
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
|
||||
progress!(
|
||||
$context,
|
||||
Event::SECUREJOIN_INVITER_PROGRESS,
|
||||
$contact_id,
|
||||
$progress
|
||||
);
|
||||
$context.call_cb($crate::events::Event::SecurejoinInviterProgress {
|
||||
contact_id: $contact_id,
|
||||
progress: $progress,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,7 +71,7 @@ macro_rules! get_qr_attr {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<String> {
|
||||
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: uint32_t) -> Option<String> {
|
||||
/* =========================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Step 1 in "Setup verified contact" protocol ====
|
||||
@@ -74,7 +82,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
|
||||
ensure_secret_key_exists(context).ok();
|
||||
let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id);
|
||||
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id);
|
||||
let self_addr = match context.get_config(Config::ConfiguredAddr) {
|
||||
let self_addr = match context.sql.get_config(context, "configured_addr") {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
error!(context, "Not configured, cannot generate QR code.",);
|
||||
@@ -131,7 +139,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<Str
|
||||
}
|
||||
|
||||
fn get_self_fingerprint(context: &Context) -> Option<String> {
|
||||
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
|
||||
if let Some(self_addr) = context.sql.get_config(context, "configured_addr") {
|
||||
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
|
||||
return Some(key.fingerprint());
|
||||
}
|
||||
@@ -139,7 +147,7 @@ fn get_self_fingerprint(context: &Context) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
||||
pub fn dc_join_securejoin(context: &Context, qr: &str) -> uint32_t {
|
||||
let cleanup =
|
||||
|context: &Context, contact_chat_id: u32, ongoing_allocated: bool, join_vg: bool| {
|
||||
let mut bob = context.bob.write().unwrap();
|
||||
@@ -162,13 +170,13 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
|
||||
if ongoing_allocated {
|
||||
dc_free_ongoing(context);
|
||||
}
|
||||
ret_chat_id as u32
|
||||
ret_chat_id as uint32_t
|
||||
};
|
||||
/* ==========================================================
|
||||
==== Bob - the joiner's side =====
|
||||
==== Step 2 in "Setup verified contact" protocol =====
|
||||
========================================================== */
|
||||
let mut contact_chat_id: u32 = 0;
|
||||
let mut contact_chat_id: uint32_t = 0;
|
||||
let mut join_vg: bool = false;
|
||||
|
||||
info!(context, "Requesting secure-join ...",);
|
||||
@@ -260,7 +268,7 @@ fn check_exit(context: &Context) -> bool {
|
||||
|
||||
fn send_handshake_msg(
|
||||
context: &Context,
|
||||
contact_chat_id: u32,
|
||||
contact_chat_id: uint32_t,
|
||||
step: &str,
|
||||
param2: impl AsRef<str>,
|
||||
fingerprint: Option<String>,
|
||||
@@ -297,7 +305,7 @@ fn send_handshake_msg(
|
||||
chat::send_msg(context, contact_chat_id, &mut msg).unwrap();
|
||||
}
|
||||
|
||||
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
|
||||
fn chat_id_2_contact_id(context: &Context, contact_chat_id: uint32_t) -> uint32_t {
|
||||
let contacts = chat::get_chat_contacts(context, contact_chat_id);
|
||||
if contacts.len() == 1 {
|
||||
contacts[0]
|
||||
@@ -332,15 +340,15 @@ fn fingerprint_equals_sender(
|
||||
/* library private: secure-join */
|
||||
pub fn handle_securejoin_handshake(
|
||||
context: &Context,
|
||||
mimeparser: &MimeParser,
|
||||
contact_id: u32,
|
||||
mimeparser: &dc_mimeparser_t,
|
||||
contact_id: uint32_t,
|
||||
) -> libc::c_int {
|
||||
let own_fingerprint: String;
|
||||
|
||||
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
|
||||
return 0;
|
||||
}
|
||||
let step = match mimeparser.lookup_optional_field("Secure-Join") {
|
||||
let step = match lookup_field(mimeparser, "Secure-Join") {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return 0;
|
||||
@@ -369,7 +377,7 @@ pub fn handle_securejoin_handshake(
|
||||
// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
|
||||
// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
|
||||
// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
|
||||
let invitenumber = match mimeparser.lookup_optional_field("Secure-Join-Invitenumber") {
|
||||
let invitenumber = match lookup_field(mimeparser, "Secure-Join-Invitenumber") {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
warn!(context, "Secure-join denied (invitenumber missing).",);
|
||||
@@ -456,7 +464,7 @@ pub fn handle_securejoin_handshake(
|
||||
==== Step 6 in "Out-of-band verified groups" protocol ====
|
||||
============================================================ */
|
||||
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
|
||||
let fingerprint = match mimeparser.lookup_optional_field("Secure-Join-Fingerprint") {
|
||||
let fingerprint = match lookup_field(mimeparser, "Secure-Join-Fingerprint") {
|
||||
Some(fp) => fp,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
@@ -485,7 +493,7 @@ pub fn handle_securejoin_handshake(
|
||||
}
|
||||
info!(context, "Fingerprint verified.",);
|
||||
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
|
||||
let auth_0 = match mimeparser.lookup_optional_field("Secure-Join-Auth") {
|
||||
let auth_0 = match lookup_field(mimeparser, "Secure-Join-Auth") {
|
||||
Some(auth) => auth,
|
||||
None => {
|
||||
could_not_establish_secure_connection(
|
||||
@@ -511,12 +519,10 @@ pub fn handle_securejoin_handshake(
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited);
|
||||
info!(context, "Auth verified.",);
|
||||
secure_connection_established(context, contact_chat_id);
|
||||
emit_event!(context, Event::ContactsChanged(Some(contact_id)));
|
||||
emit_event!(context, Event::CONTACTS_CHANGED, contact_id, 0);
|
||||
inviter_progress!(context, contact_id, 600);
|
||||
if join_vg {
|
||||
let field_grpid = mimeparser
|
||||
.lookup_optional_field("Secure-Join-Group")
|
||||
.unwrap_or_default();
|
||||
let field_grpid = lookup_field(mimeparser, "Secure-Join-Group").unwrap_or_default();
|
||||
let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid);
|
||||
if group_chat_id == 0 {
|
||||
error!(context, "Chat {} not found.", &field_grpid);
|
||||
@@ -584,10 +590,9 @@ pub fn handle_securejoin_handshake(
|
||||
return ret;
|
||||
}
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
|
||||
emit_event!(context, Event::ContactsChanged(None));
|
||||
let cg_member_added = mimeparser
|
||||
.lookup_optional_field("Chat-Group-Member-Added")
|
||||
.unwrap_or_default();
|
||||
emit_event!(context, Event::CONTACTS_CHANGED, 0, 0);
|
||||
let cg_member_added =
|
||||
lookup_field(mimeparser, "Chat-Group-Member-Added").unwrap_or_default();
|
||||
if join_vg && !addr_equals_self(context, cg_member_added) {
|
||||
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
|
||||
return ret;
|
||||
@@ -638,8 +643,8 @@ fn end_bobs_joining(context: &Context, status: libc::c_int) {
|
||||
dc_stop_ongoing_process(context);
|
||||
}
|
||||
|
||||
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
|
||||
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id);
|
||||
fn secure_connection_established(context: &Context, contact_chat_id: uint32_t) {
|
||||
let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id);
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
let addr = if let Ok(ref contact) = contact {
|
||||
contact.get_addr()
|
||||
@@ -648,10 +653,32 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) {
|
||||
};
|
||||
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
|
||||
chat::add_device_msg(context, contact_chat_id, msg);
|
||||
emit_event!(context, Event::ChatModified(contact_chat_id));
|
||||
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
|
||||
}
|
||||
|
||||
fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32, details: &str) {
|
||||
fn lookup_field(mimeparser: &dc_mimeparser_t, key: &str) -> Option<String> {
|
||||
let field: *mut mailimf_field = dc_mimeparser_lookup_field(mimeparser, key);
|
||||
unsafe {
|
||||
let mut value: *const libc::c_char = ptr::null();
|
||||
if field.is_null()
|
||||
|| (*field).fld_type != MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int
|
||||
|| (*field).fld_data.fld_optional_field.is_null()
|
||||
|| {
|
||||
value = (*(*field).fld_data.fld_optional_field).fld_value;
|
||||
value.is_null()
|
||||
}
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(as_str(value).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn could_not_establish_secure_connection(
|
||||
context: &Context,
|
||||
contact_chat_id: uint32_t,
|
||||
details: &str,
|
||||
) {
|
||||
let contact_id = chat_id_2_contact_id(context, contact_chat_id);
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
let msg = context.stock_string_repl_str(
|
||||
@@ -688,7 +715,10 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
||||
* Tools: Misc.
|
||||
******************************************************************************/
|
||||
|
||||
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
|
||||
fn encrypted_and_signed(
|
||||
mimeparser: &dc_mimeparser_t,
|
||||
expected_fingerprint: impl AsRef<str>,
|
||||
) -> bool {
|
||||
if !mimeparser.e2ee_helper.encrypted {
|
||||
warn!(mimeparser.context, "Message not encrypted.",);
|
||||
false
|
||||
@@ -723,10 +753,11 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
|
||||
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
|
||||
let contact_id: i32 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
.query_row_col(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?;",
|
||||
params![&peerstate.addr],
|
||||
0,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
if contact_id > 0 {
|
||||
@@ -741,7 +772,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
|
||||
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
|
||||
|
||||
chat::add_device_msg(context, contact_chat_id, msg);
|
||||
emit_event!(context, Event::ChatModified(contact_chat_id));
|
||||
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
src/smtp.rs
22
src/smtp.rs
@@ -1,15 +1,13 @@
|
||||
use lettre::smtp::client::net::*;
|
||||
use lettre::*;
|
||||
|
||||
use crate::constants::Event;
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::events::Event;
|
||||
use crate::login_param::LoginParam;
|
||||
use crate::oauth2::*;
|
||||
|
||||
#[derive(DebugStub)]
|
||||
pub struct Smtp {
|
||||
#[debug_stub(some = "SmtpTransport")]
|
||||
transport: Option<lettre::smtp::SmtpTransport>,
|
||||
transport_connected: bool,
|
||||
/// Email address we are sending from.
|
||||
@@ -52,7 +50,7 @@ impl Smtp {
|
||||
}
|
||||
|
||||
if lp.send_server.is_empty() || lp.send_port == 0 {
|
||||
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
|
||||
log_event!(context, Event::ERROR_NETWORK, 0, "SMTP bad parameters.",);
|
||||
}
|
||||
|
||||
self.from = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
|
||||
@@ -111,10 +109,13 @@ impl Smtp {
|
||||
.credentials(creds)
|
||||
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
|
||||
self.transport = Some(client.transport());
|
||||
context.call_cb(Event::SmtpConnected(format!(
|
||||
log_event!(
|
||||
context,
|
||||
Event::SMTP_CONNECTED,
|
||||
0,
|
||||
"SMTP-LOGIN as {} ok",
|
||||
lp.send_user,
|
||||
)));
|
||||
);
|
||||
true
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -140,9 +141,12 @@ impl Smtp {
|
||||
|
||||
match transport.send(mail) {
|
||||
Ok(_) => {
|
||||
context.call_cb(Event::SmtpMessageSent(
|
||||
"Message was sent to SMTP server".into(),
|
||||
));
|
||||
log_event!(
|
||||
context,
|
||||
Event::SMTP_MESSAGE_SENT,
|
||||
0,
|
||||
"Message was sent to SMTP server",
|
||||
);
|
||||
self.transport_connected = true;
|
||||
1
|
||||
}
|
||||
|
||||
21
src/sql.rs
21
src/sql.rs
@@ -13,10 +13,8 @@ use crate::peerstate::*;
|
||||
const DC_OPEN_READONLY: usize = 0x01;
|
||||
|
||||
/// A wrapper around the underlying Sqlite3 object.
|
||||
#[derive(DebugStub)]
|
||||
pub struct Sql {
|
||||
pool: RwLock<Option<r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>>>,
|
||||
#[debug_stub = "ThreadLocal<String>"]
|
||||
in_use: Arc<ThreadLocal<String>>,
|
||||
}
|
||||
|
||||
@@ -157,13 +155,19 @@ impl Sql {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn query_get_value<P, T>(&self, context: &Context, query: &str, params: P) -> Option<T>
|
||||
pub fn query_row_col<P, T>(
|
||||
&self,
|
||||
context: &Context,
|
||||
query: &str,
|
||||
params: P,
|
||||
column: usize,
|
||||
) -> Option<T>
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: rusqlite::ToSql,
|
||||
T: rusqlite::types::FromSql,
|
||||
{
|
||||
match self.query_row(query, params, |row| row.get::<_, T>(0)) {
|
||||
match self.query_row(query, params, |row| row.get::<_, T>(column)) {
|
||||
Ok(res) => Some(res),
|
||||
Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => None,
|
||||
Err(Error::Sql(rusqlite::Error::InvalidColumnType(
|
||||
@@ -234,10 +238,11 @@ impl Sql {
|
||||
if !self.is_open() || key.as_ref().is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.query_get_value(
|
||||
self.query_row_col(
|
||||
context,
|
||||
"SELECT value FROM config WHERE keyname=?;",
|
||||
params![key.as_ref()],
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -793,7 +798,7 @@ fn open(
|
||||
|
||||
let repl_from = sql
|
||||
.get_config(context, "backup_for")
|
||||
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
|
||||
.unwrap_or_else(|| to_string(context.get_blobdir()));
|
||||
|
||||
let repl_from = dc_ensure_no_slash_safe(&repl_from);
|
||||
sql.execute(
|
||||
@@ -992,7 +997,7 @@ pub fn housekeeping(context: &Context) {
|
||||
|
||||
info!(context, "{} files in use.", files_in_use.len(),);
|
||||
/* go through directory and delete unused files */
|
||||
let p = context.get_blobdir();
|
||||
let p = std::path::Path::new(as_str(context.get_blobdir()));
|
||||
match std::fs::read_dir(p) {
|
||||
Ok(dir_handle) => {
|
||||
/* avoid deletion of files that are just created to build a message object */
|
||||
@@ -1052,7 +1057,7 @@ pub fn housekeeping(context: &Context) {
|
||||
warn!(
|
||||
context,
|
||||
"Housekeeping: Cannot open {}. ({})",
|
||||
context.get_blobdir().display(),
|
||||
as_str(context.get_blobdir()),
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
66
src/stock.rs
66
src/stock.rs
@@ -3,10 +3,10 @@ use std::borrow::Cow;
|
||||
use strum::EnumProperty;
|
||||
use strum_macros::EnumProperty;
|
||||
|
||||
use crate::constants::Event;
|
||||
use crate::contact::*;
|
||||
use crate::context::Context;
|
||||
use crate::dc_tools::*;
|
||||
use crate::events::Event;
|
||||
use libc::free;
|
||||
|
||||
/// Stock strings
|
||||
@@ -128,7 +128,7 @@ impl Context {
|
||||
/// translation, then this string will be returned. Otherwise a
|
||||
/// default (English) string is returned.
|
||||
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
|
||||
let ptr = self.call_cb(Event::GetString { id, count: 0 }) as *mut libc::c_char;
|
||||
let ptr = self.call_cb(Event::GET_STRING, id as usize, 0) as *mut libc::c_char;
|
||||
if ptr.is_null() {
|
||||
Cow::Borrowed(id.fallback())
|
||||
} else {
|
||||
@@ -140,14 +140,12 @@ impl Context {
|
||||
|
||||
/// Return stock string, replacing placeholders with provided string.
|
||||
///
|
||||
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
|
||||
/// This replaces both the *first* `%1$s` **and** `%1$d`
|
||||
/// placeholders with the provided string.
|
||||
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
|
||||
pub fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef<str>) -> String {
|
||||
self.stock_str(id)
|
||||
.replacen("%1$s", insert.as_ref(), 1)
|
||||
.replacen("%1$d", insert.as_ref(), 1)
|
||||
.replacen("%1$@", insert.as_ref(), 1)
|
||||
}
|
||||
|
||||
/// Return stock string, replacing placeholders with provided int.
|
||||
@@ -160,10 +158,9 @@ impl Context {
|
||||
|
||||
/// Return stock string, replacing 2 placeholders with provided string.
|
||||
///
|
||||
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
|
||||
/// This replaces both the *first* `%1$s` **and** `%1$d`
|
||||
/// placeholders with the string in `insert` and does the same for
|
||||
/// `%2$s`, `%2$d` and `%2$@` for `insert2`.
|
||||
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
|
||||
/// `%2$s` and `%2$d` for `insert2`.
|
||||
fn stock_string_repl_str2(
|
||||
&self,
|
||||
id: StockMessage,
|
||||
@@ -173,10 +170,8 @@ impl Context {
|
||||
self.stock_str(id)
|
||||
.replacen("%1$s", insert.as_ref(), 1)
|
||||
.replacen("%1$d", insert.as_ref(), 1)
|
||||
.replacen("%1$@", insert.as_ref(), 1)
|
||||
.replacen("%2$s", insert2.as_ref(), 1)
|
||||
.replacen("%2$d", insert2.as_ref(), 1)
|
||||
.replacen("%2$@", insert2.as_ref(), 1)
|
||||
}
|
||||
|
||||
/// Return some kind of stock message
|
||||
@@ -237,8 +232,11 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
use crate::constants::DC_CONTACT_ID_SELF;
|
||||
use libc::uintptr_t;
|
||||
use crate::context::dc_context_new;
|
||||
use crate::types::uintptr_t;
|
||||
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
@@ -255,32 +253,36 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_stock_str() {
|
||||
let t = dummy_context();
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||
}
|
||||
|
||||
fn test_stock_str_no_fallback_cb(_ctx: &Context, evt: Event) -> uintptr_t {
|
||||
match evt {
|
||||
Event::GetString {
|
||||
id: StockMessage::NoMessages,
|
||||
..
|
||||
} => unsafe { "Hello there".strdup() as usize },
|
||||
_ => 0,
|
||||
unsafe extern "C" fn test_stock_str_no_fallback_cb(
|
||||
_ctx: &Context,
|
||||
evt: Event,
|
||||
d1: uintptr_t,
|
||||
_d2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
if evt == Event::GET_STRING && d1 == StockMessage::NoMessages.to_usize().unwrap() {
|
||||
let tmp = CString::new("Hello there").unwrap();
|
||||
dc_strdup(tmp.as_ptr()) as usize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_str_no_fallback() {
|
||||
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
|
||||
let t = test_context(Some(test_stock_str_no_fallback_cb));
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_str() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
// uses %1$s substitution
|
||||
assert_eq!(
|
||||
t.ctx.stock_string_repl_str(StockMessage::Member, "42"),
|
||||
ctx.stock_string_repl_str(StockMessage::Member, "42"),
|
||||
"42 member(s)"
|
||||
);
|
||||
// We have no string using %1$d to test...
|
||||
@@ -288,38 +290,36 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_int() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
t.ctx.stock_string_repl_int(StockMessage::Member, 42),
|
||||
ctx.stock_string_repl_int(StockMessage::Member, 42),
|
||||
"42 member(s)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_str2() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
t.ctx
|
||||
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
|
||||
ctx.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
|
||||
"Response from foo: bar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_simple() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
t.ctx
|
||||
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
|
||||
ctx.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
|
||||
"Location streaming enabled."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_add_member_by_me() {
|
||||
let t = dummy_context();
|
||||
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
ctx.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
//!
|
||||
//! This module is only compiled for test runs.
|
||||
|
||||
use std::ffi::CStr;
|
||||
|
||||
use libc::uintptr_t;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::constants::KeyType;
|
||||
use crate::context::{Context, ContextCallback};
|
||||
use crate::events::Event;
|
||||
use crate::constants::{Event, KeyType};
|
||||
use crate::context::{dc_context_new, dc_open, Context};
|
||||
use crate::key;
|
||||
use crate::types::dc_callback_t;
|
||||
|
||||
/// A Context and temporary directory.
|
||||
///
|
||||
@@ -26,15 +28,18 @@ pub struct TestContext {
|
||||
/// "db.sqlite" in the [TestContext.dir] directory.
|
||||
///
|
||||
/// [Context]: crate::context::Context
|
||||
pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let cb: Box<ContextCallback> = match callback {
|
||||
Some(cb) => cb,
|
||||
None => Box::new(|_, _| 0),
|
||||
};
|
||||
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
pub fn test_context(cb: Option<dc_callback_t>) -> TestContext {
|
||||
unsafe {
|
||||
let mut ctx = dc_context_new(cb, std::ptr::null_mut(), None);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
assert!(
|
||||
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
|
||||
"Failed to open {}",
|
||||
dbfile.display(),
|
||||
);
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a dummy [TestContext].
|
||||
@@ -46,11 +51,17 @@ pub fn dummy_context() -> TestContext {
|
||||
test_context(None)
|
||||
}
|
||||
|
||||
pub fn logging_cb(_ctx: &Context, evt: Event) -> uintptr_t {
|
||||
pub unsafe extern "C" fn logging_cb(
|
||||
_ctx: &Context,
|
||||
evt: Event,
|
||||
_d1: uintptr_t,
|
||||
d2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
let to_str = |x| CStr::from_ptr(x as *const libc::c_char).to_str().unwrap();
|
||||
match evt {
|
||||
Event::Info(msg) => println!("I: {}", msg),
|
||||
Event::Warning(msg) => println!("W: {}", msg),
|
||||
Event::Error(msg) => println!("E: {}", msg),
|
||||
Event::INFO => println!("I: {}", to_str(d2)),
|
||||
Event::WARNING => println!("W: {}", to_str(d2)),
|
||||
Event::ERROR => println!("E: {}", to_str(d2)),
|
||||
_ => (),
|
||||
}
|
||||
0
|
||||
|
||||
@@ -35,10 +35,11 @@ pub fn save(context: &Context, namespace: Namespace, foreign_id: u32) -> String
|
||||
}
|
||||
|
||||
pub fn lookup(context: &Context, namespace: Namespace, foreign_id: u32) -> Option<String> {
|
||||
context.sql.query_get_value::<_, String>(
|
||||
context.sql.query_row_col::<_, String>(
|
||||
context,
|
||||
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
|
||||
params![namespace, foreign_id as i32],
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
43
src/types.rs
Normal file
43
src/types.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::constants::Event;
|
||||
use crate::context::Context;
|
||||
|
||||
pub use mmime::clist::*;
|
||||
pub use rusqlite::ffi::*;
|
||||
|
||||
/// Callback function that should be given to dc_context_new().
|
||||
///
|
||||
/// @memberof Context
|
||||
/// @param context The context object as returned by dc_context_new().
|
||||
/// @param event one of the @ref DC_EVENT constants
|
||||
/// @param data1 depends on the event parameter
|
||||
/// @param data2 depends on the event parameter
|
||||
/// @return return 0 unless stated otherwise in the event parameter documentation
|
||||
pub type dc_callback_t =
|
||||
unsafe extern "C" fn(_: &Context, _: Event, _: uintptr_t, _: uintptr_t) -> uintptr_t;
|
||||
|
||||
pub type dc_receive_imf_t = unsafe fn(
|
||||
_: &Context,
|
||||
_: *const libc::c_char,
|
||||
_: size_t,
|
||||
_: &str,
|
||||
_: uint32_t,
|
||||
_: uint32_t,
|
||||
) -> ();
|
||||
|
||||
/* Purpose: Reading from IMAP servers with no dependencies to the database.
|
||||
Context is only used for logging and to get information about
|
||||
the online state. */
|
||||
|
||||
pub type dc_precheck_imf_t =
|
||||
unsafe fn(_: &Context, _: *const libc::c_char, _: &str, _: u32) -> libc::c_int;
|
||||
pub type dc_set_config_t = fn(_: &Context, _: &str, _: Option<&str>) -> ();
|
||||
pub type dc_get_config_t = fn(_: &Context, _: &str) -> Option<String>;
|
||||
|
||||
pub type int32_t = i32;
|
||||
pub type uintptr_t = libc::uintptr_t;
|
||||
pub type size_t = libc::size_t;
|
||||
pub type uint32_t = libc::c_uint;
|
||||
pub type uint8_t = libc::c_uchar;
|
||||
pub type uint16_t = libc::c_ushort;
|
||||
pub type uint64_t = u64;
|
||||
266
tests/stress.rs
266
tests/stress.rs
@@ -7,6 +7,7 @@ use tempfile::{tempdir, TempDir};
|
||||
|
||||
use deltachat::chat::{self, Chat};
|
||||
use deltachat::config;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_imex::*;
|
||||
@@ -14,8 +15,8 @@ use deltachat::dc_tools::*;
|
||||
use deltachat::keyring::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::pgp::*;
|
||||
use deltachat::types::*;
|
||||
use deltachat::x::*;
|
||||
use deltachat::Event;
|
||||
use libc;
|
||||
|
||||
/* some data used for testing
|
||||
@@ -29,101 +30,105 @@ static mut S_EM_SETUPFILE: *const libc::c_char =
|
||||
as *const u8 as *const libc::c_char;
|
||||
|
||||
unsafe fn stress_functions(context: &Context) {
|
||||
if dc_file_exist(context, "$BLOBDIR/foobar")
|
||||
|| dc_file_exist(context, "$BLOBDIR/dada")
|
||||
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|
||||
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
|
||||
{
|
||||
dc_delete_file(context, "$BLOBDIR/foobar");
|
||||
dc_delete_file(context, "$BLOBDIR/dada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar-folder");
|
||||
}
|
||||
dc_write_file(
|
||||
context,
|
||||
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
|
||||
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
|
||||
7,
|
||||
);
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
|
||||
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/foobar",), 7);
|
||||
|
||||
let abs_path = context
|
||||
.get_blobdir()
|
||||
.join("foobar")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
assert!(dc_is_blobdir_path(context, &abs_path));
|
||||
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
|
||||
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
|
||||
assert!(dc_file_exist(context, &abs_path));
|
||||
|
||||
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
|
||||
|
||||
let mut buf: *mut libc::c_void = ptr::null_mut();
|
||||
let mut buf_bytes: libc::size_t = 0;
|
||||
|
||||
assert_eq!(
|
||||
dc_read_file(
|
||||
if 0 != dc_is_open(context) {
|
||||
if dc_file_exist(context, "$BLOBDIR/foobar")
|
||||
|| dc_file_exist(context, "$BLOBDIR/dada")
|
||||
|| dc_file_exist(context, "$BLOBDIR/foobar.dadada")
|
||||
|| dc_file_exist(context, "$BLOBDIR/foobar-folder")
|
||||
{
|
||||
dc_delete_file(context, "$BLOBDIR/foobar");
|
||||
dc_delete_file(context, "$BLOBDIR/dada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar.dadada");
|
||||
dc_delete_file(context, "$BLOBDIR/foobar-folder");
|
||||
}
|
||||
dc_write_file(
|
||||
context,
|
||||
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
|
||||
&mut buf,
|
||||
&mut buf_bytes,
|
||||
),
|
||||
1
|
||||
);
|
||||
assert_eq!(buf_bytes, 7);
|
||||
assert_eq!(
|
||||
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
|
||||
"content"
|
||||
);
|
||||
b"$BLOBDIR/foobar\x00" as *const u8 as *const libc::c_char,
|
||||
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
|
||||
7i32 as size_t,
|
||||
);
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar",));
|
||||
assert!(!dc_file_exist(context, "$BLOBDIR/foobarx"));
|
||||
assert_eq!(
|
||||
dc_get_filebytes(context, "$BLOBDIR/foobar",),
|
||||
7i32 as libc::c_ulonglong
|
||||
);
|
||||
|
||||
free(buf as *mut _);
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
|
||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
|
||||
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
|
||||
let fn0: *mut libc::c_char = dc_get_fine_pathNfilename(
|
||||
context,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(!fn0.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
let abs_path: *mut libc::c_char = dc_mprintf(
|
||||
b"%s/%s\x00" as *const u8 as *const libc::c_char,
|
||||
context.get_blobdir(),
|
||||
b"foobar\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(dc_is_blobdir_path(context, as_str(abs_path)));
|
||||
assert!(dc_is_blobdir_path(context, "$BLOBDIR/fofo",));
|
||||
assert!(!dc_is_blobdir_path(context, "/BLOBDIR/fofo",));
|
||||
assert!(dc_file_exist(context, as_path(abs_path)));
|
||||
free(abs_path as *mut libc::c_void);
|
||||
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
|
||||
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
|
||||
|
||||
let mut buf: *mut libc::c_void = ptr::null_mut();
|
||||
let mut buf_bytes: size_t = 0;
|
||||
|
||||
assert_eq!(
|
||||
dc_read_file(
|
||||
context,
|
||||
b"$BLOBDIR/dada\x00" as *const u8 as *const libc::c_char,
|
||||
&mut buf,
|
||||
&mut buf_bytes,
|
||||
),
|
||||
1
|
||||
);
|
||||
assert_eq!(buf_bytes, 7);
|
||||
assert_eq!(
|
||||
std::str::from_utf8(std::slice::from_raw_parts(buf as *const u8, buf_bytes)).unwrap(),
|
||||
"content"
|
||||
);
|
||||
|
||||
free(buf as *mut _);
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/foobar"));
|
||||
assert!(dc_delete_file(context, "$BLOBDIR/dada"));
|
||||
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
|
||||
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
|
||||
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
|
||||
let fn0: *mut libc::c_char = dc_get_fine_pathNfilename(
|
||||
context,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(!fn0.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
fn0,
|
||||
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
dc_write_file(
|
||||
context,
|
||||
fn0,
|
||||
b"$BLOBDIR/foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
dc_write_file(
|
||||
context,
|
||||
fn0,
|
||||
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
|
||||
7,
|
||||
);
|
||||
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
|
||||
context,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(!fn1.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
fn1,
|
||||
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
assert!(dc_delete_file(context, as_path(fn0)));
|
||||
free(fn0 as *mut libc::c_void);
|
||||
free(fn1 as *mut libc::c_void);
|
||||
b"content\x00" as *const u8 as *const libc::c_char as *const libc::c_void,
|
||||
7i32 as size_t,
|
||||
);
|
||||
let fn1: *mut libc::c_char = dc_get_fine_pathNfilename(
|
||||
context,
|
||||
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
|
||||
b"foobar.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
assert!(!fn1.is_null());
|
||||
assert_eq!(
|
||||
strcmp(
|
||||
fn1,
|
||||
b"$BLOBDIR/foobar-1.dadada\x00" as *const u8 as *const libc::c_char,
|
||||
),
|
||||
0
|
||||
);
|
||||
assert!(dc_delete_file(context, as_path(fn0)));
|
||||
free(fn0 as *mut libc::c_void);
|
||||
free(fn1 as *mut libc::c_void);
|
||||
}
|
||||
|
||||
let res = context.get_config(config::Config::SysConfigKeys).unwrap();
|
||||
let res = context.get_config(&config::Config::SysConfigKeys).unwrap();
|
||||
|
||||
assert!(!res.contains(" probably_never_a_key "));
|
||||
assert!(res.contains(" addr "));
|
||||
@@ -562,7 +567,12 @@ fn test_encryption_decryption() {
|
||||
assert_eq!(plain, original_text);
|
||||
}
|
||||
|
||||
fn cb(_context: &Context, _event: Event) -> libc::uintptr_t {
|
||||
unsafe extern "C" fn cb(
|
||||
_context: &Context,
|
||||
_event: Event,
|
||||
_data1: uintptr_t,
|
||||
_data2: uintptr_t,
|
||||
) -> uintptr_t {
|
||||
0
|
||||
}
|
||||
|
||||
@@ -572,16 +582,21 @@ struct TestContext {
|
||||
dir: TempDir,
|
||||
}
|
||||
|
||||
fn create_test_context() -> TestContext {
|
||||
unsafe fn create_test_context() -> TestContext {
|
||||
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
|
||||
assert!(
|
||||
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
|
||||
"Failed to open {}",
|
||||
dbfile.display()
|
||||
);
|
||||
TestContext { ctx: ctx, dir: dir }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_url() {
|
||||
let ctx = create_test_context();
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let redirect_uri = "chat.delta:/com.b44t.messenger";
|
||||
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
|
||||
@@ -591,7 +606,7 @@ fn test_dc_get_oauth2_url() {
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_addr() {
|
||||
let ctx = create_test_context();
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let code = "fail";
|
||||
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
|
||||
@@ -601,7 +616,7 @@ fn test_dc_get_oauth2_addr() {
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_token() {
|
||||
let ctx = create_test_context();
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let code = "fail";
|
||||
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0);
|
||||
@@ -619,33 +634,50 @@ fn test_stress_tests() {
|
||||
|
||||
#[test]
|
||||
fn test_get_contacts() {
|
||||
let context = create_test_context();
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
unsafe {
|
||||
let context = create_test_context();
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
|
||||
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(id, 0);
|
||||
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(id, 0);
|
||||
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
|
||||
assert_eq!(contacts.len(), 1);
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
|
||||
assert_eq!(contacts.len(), 1);
|
||||
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chat() {
|
||||
let context = create_test_context();
|
||||
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(contact1, 0);
|
||||
unsafe {
|
||||
let context = create_test_context();
|
||||
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
assert_ne!(contact1, 0);
|
||||
|
||||
let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert!(chat_id > 9, "chat_id too small {}", chat_id);
|
||||
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
|
||||
let chat_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert!(chat_id > 9, "chat_id too small {}", chat_id);
|
||||
let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap();
|
||||
|
||||
let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert_eq!(chat2_id, chat_id);
|
||||
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
|
||||
let chat2_id = chat::create_by_contact_id(&context.ctx, contact1).unwrap();
|
||||
assert_eq!(chat2_id, chat_id);
|
||||
let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap();
|
||||
|
||||
assert_eq!(chat2.name, chat.name);
|
||||
assert_eq!(chat2.name, chat.name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_db() {
|
||||
unsafe {
|
||||
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
std::fs::write(&dbfile, b"123").unwrap();
|
||||
|
||||
let res = dc_open(&mut ctx, dbfile.to_str().unwrap(), None);
|
||||
assert!(!res);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user