Compare commits

..

73 Commits

Author SHA1 Message Date
holger krekel
601b284ed8 fix #538 -- don't crash on wrong setup codes for ac-message, don't use "expect(), added test 2019-09-18 16:31:20 +02:00
Dmitry Bogatov
8a3bf6a5d9 Remove use of `dbg!' macro
According to documentation[^1], this macro should not be in version
control, and should only be used for debugging.

https://doc.rust-lang.org/std/macro.dbg.html
2019-09-18 14:35:49 +02:00
Dmitry Bogatov
d5f361d386 Use include_str! macro instead of embedding key into source
Additionally, this change reduces duplication: now test public key is
stored in only one place, and used in two instead of copy-paste of very
long line.
2019-09-18 14:35:49 +02:00
Dmitry Bogatov
7bb4a27b60 Make Context.cb field private (no code changes) 2019-09-18 14:35:49 +02:00
holger krekel
3bd36feede fix broken rust mimeparser fuzzy tests 2019-09-18 14:15:16 +02:00
holger krekel
fc1f1ce37c streamline fixtures for online accounts, reducing test functions 2019-09-18 14:15:16 +02:00
holger krekel
ee327dc87d remove an ancient hack on context-teardown, refine and use more regular API for finalizing online accounts 2019-09-18 14:15:16 +02:00
Dmitry Bogatov
d644ca5563 Add property test for simplify_plain_text 2019-09-18 07:34:56 +02:00
Dmitry Bogatov
b3b1e37192 Add tests for dc_dehtml 2019-09-18 07:34:56 +02:00
Dmitry Bogatov
38f39c8d32 Add smoke test for Context.get_fresh_msgs 2019-09-18 07:34:56 +02:00
Dmitry Bogatov
a773b7929c Add fuzzy-test for dc_mimeparser_parser 2019-09-18 07:33:22 +02:00
Dmitry Bogatov
7b73103133 Add test for mime parsing reportedly crash-inducing message 2019-09-18 07:33:22 +02:00
holger krekel
b6803191cb Merge branch 'refactor/remove-mprintf' 2019-09-17 16:23:25 +02:00
holger krekel
2b038a34c9 Merge branch 'master' into refactor/remove-mprintf 2019-09-17 15:46:05 +02:00
B. Petersen
8c2c3f8bee move spec.md from separate repo here 2019-09-17 10:49:34 +02:00
dignifiedquire
e710836276 cleanup and fix earlier introduced scoping error 2019-09-16 22:58:10 +02:00
dignifiedquire
6be3c9a48a refactor: improve mime field lookup 2019-09-16 22:58:10 +02:00
dignifiedquire
c0747bf68d refactor: use enum for system messages 2019-09-16 22:58:10 +02:00
dignifiedquire
0346dd15d9 refactor(mimeparser): some more sanity 2019-09-16 22:58:10 +02:00
dignifiedquire
ff0aa8423d refactor: remove now unused build.rs and extern declarations 2019-09-16 07:59:41 +02:00
dignifiedquire
0f718e0d08 refactor(mimefactory): remove dc_mprintf 2019-09-16 07:59:41 +02:00
dignifiedquire
9534a9ad30 refactor(mimeparser): remove dc_mprintf 2019-09-16 07:59:41 +02:00
dignifiedquire
d091857cef refactor(imex): remove dc_mprintf 2019-09-16 07:59:41 +02:00
dignifiedquire
72a9ca0aa5 refactor(receive_imf): remove dc_mprintf 2019-09-16 07:58:08 +02:00
dignifiedquire
ecaae42b80 refactor(examples): remove dc_mprintf 2019-09-16 07:58:08 +02:00
dignifiedquire
84bf1ec6e7 refactor(tools): no more dc_mprintf 2019-09-16 07:58:08 +02:00
dignifiedquire
0bf3d20e07 fix and implement ffi tranlation 2019-09-15 23:40:08 +02:00
dignifiedquire
5486ac5b9f refacor: use an enum for events 2019-09-15 23:40:08 +02:00
Alexander Krotov
ac12b2e643 chore(key): remove unused *fingerprint_c functions 2019-09-15 19:32:16 +02:00
dignifiedquire
16c281a9b7 refactor(context): safe interface 2019-09-15 16:36:31 +02:00
dignifiedquire
413e3eb62d apply more CR 2019-09-15 16:36:31 +02:00
dignifiedquire
f31f341a50 feat: enforce Debug implementations and remove mod types 2019-09-15 16:36:31 +02:00
dignifiedquire
c2501258b6 apply CR feedback 2019-09-15 16:36:31 +02:00
dignifiedquire
de1e3e1d4f refactor(context): remove last unsafe bits from the context struct 2019-09-15 16:36:31 +02:00
Floris Bruynooghe
a3f64d4e95 Handle new Context creation API on the FFI
This adopts the FFI API to use the new Context::new() and
Contest::drop() Rust-style APIs while preserving the semantics of the
existing FFI API.  It introduces a wrapper around the context which
keeps an optional reference to the actual context, changing dc_open()
and dc_close() to create/destroy the actual reference.

This changes the events emitted on closing slightly: they only get
emitted when dc_close() was called while the context was open.  While
dc_close() remains idempotent in that it can still be called safely
multiple times, the close events will only occur if the context is
actually closed.
2019-09-15 02:37:13 +02:00
Floris Bruynooghe
afc9a31080 Remove dc_open call
A new context is now created by calling Context::new and therefore you
always have a valid context.  This is much more in Rust style and will
allow a lot of furture simplifications on the context itself.

The FFI layer has not yet been adjusted in this commit and thus will
fail.
2019-09-15 02:37:13 +02:00
Simon Laux
e5699e8ba9 Merge pull request #517 from deltachat/fix_get_draft
Fix get_draft to just return null and not produce errors if there's no
2019-09-14 23:39:09 +02:00
Floris Bruynooghe
8a7143b791 Return a Result<Option<Message>> for get_draft
The function can fail, so we need to still have an error return as
well as a no-draft return.

Add tests.
2019-09-14 23:00:13 +02:00
björn petersen
79d23909b5 fix 'this message is not encrypted' error in verified groups (#522) 2019-09-14 18:55:19 +02:00
Alexander Krotov
24fe4740d3 Merge pull request #498 from deltachat/return_bool
Resolve "should return bool" TODOs
2019-09-14 14:31:50 +00:00
Alexander Krotov
1e1d3f4aa8 Merge pull request #521 from deltachat/server_flags
Restore DC_LP_* constants from C code
2019-09-14 14:19:02 +00:00
Alexander Krotov
51bf875826 Use DC_LP_* constants in configure module 2019-09-14 13:22:59 +03:00
Alexander Krotov
f9ce6c7c81 Use DC_LP_* constants in auto_outlook.rs 2019-09-14 13:16:21 +03:00
Alexander Krotov
2e91c3d334 Use DC_LP_* constants in auto_mozilla.rs 2019-09-14 13:14:35 +03:00
Alexander Krotov
d1b762af04 Make DC_LP_* constants public 2019-09-14 13:14:18 +03:00
Alexander Krotov
e1e02839d1 Return bool from dc_mimefactory_render 2019-09-14 12:06:33 +03:00
Alexander Krotov
50a812ea5e Return bool from mailmime_transfer_decode 2019-09-14 12:03:10 +03:00
Alexander Krotov
27a4adb9c6 Return bool from export_key_to_asc_file 2019-09-14 12:03:10 +03:00
Alexander Krotov
3932d8f48f Return bool from export_self_keys 2019-09-14 12:03:10 +03:00
Alexander Krotov
d978a8e0a2 Return bool from {import,export}_backup 2019-09-14 12:03:10 +03:00
Alexander Krotov
fad49e08d7 message.rs: resolve "should return bool" TODOs 2019-09-14 12:03:10 +03:00
Alexander Krotov
0f67b16f29 Return bool from add_contact_to_chat_ex 2019-09-14 12:03:10 +03:00
Alexander Krotov
97edd23910 Return bool from add_to_chat_contacts_table 2019-09-14 12:03:10 +03:00
Alexander Krotov
400ab2cdab Return bool from dc_msg_get_showpadlock 2019-09-14 12:03:10 +03:00
jikstra
db20ecf985 Remove println 2019-09-13 22:35:31 +02:00
jikstra
e59f421e79 cargo fmt 2019-09-13 22:25:18 +02:00
jikstra
75b7de712a Fix get_draft to just return null and not produce errors if there's no
draft
2019-09-13 18:19:52 +02:00
Alexander Krotov
fa8192177d Merge pull request #514 from deltachat/in_reply_to
Store In-Reply-To header as Option<String>
2019-09-13 10:21:32 +00:00
Alexander Krotov
48ffef7955 Store In-Reply-To header as Option<String> 2019-09-13 11:43:48 +03:00
Alexander Krotov
055796e6b3 Merge pull request #496 from KAction/safe
Make dc_msg_get_file return PathBuf, not char*
2019-09-13 00:43:28 +00:00
B. Petersen
34433c4962 support additional placeholder in stock-string 2019-09-12 23:44:40 +02:00
Alexander Krotov
6d1076e1f6 Mark dc_imex() as safe 2019-09-12 23:43:25 +02:00
Dmitry Bogatov
bb4081e503 Rename query_row_col to query_get_value
Since function `query_row_col` no longer accept column number argument,
it is misleading to mention column in its name.
2019-09-12 18:42:12 +00:00
Dmitry Bogatov
bef25ad5f6 Avoid excessive allocation in dc_search_msgs 2019-09-12 17:12:26 +00:00
Dmitry Bogatov
f47652e72d Change argument type of write_asc_to_file from const char * to &Path 2019-09-12 17:12:23 +00:00
Dmitry Bogatov
7c4d7fb3dd Make Aheader::from_imffields accept &str, not const char * 2019-09-12 06:32:13 +00:00
Dmitry Bogatov
cdfc7281d0 Simplify Sql.query_row_col function
Function `query_row` executes query and calls callback function to process
row returned. Function query_row_col() is special case, that provides
callback function, which returns value of one particular column of row,
ignoring others.

In all cases, that particular column was 0 (first and only column of
query result), since there is no point to select more than one column
with this function -- they are discarded anyway.

This commit removes that redundancy, removing column number argument of
query_row_col() function and adjusting call sites accordingly.
2019-09-12 05:01:06 +00:00
Dmitry Bogatov
500e80784a Replace some of context.sql.get_config() with context.get_config()
Pattern `context.sql.get_config(context, {foo})` is unnecessary
redundant in Rust: unlike C, Rust has associated functions (methods).
2019-09-12 04:41:06 +00:00
Dmitry Bogatov
c5803d9b4c Remove useless catch-all pattern-matching 2019-09-12 04:17:00 +00:00
Dmitry Bogatov
ebac00fbde Use safe version of dc_write_file
* src/key.rs(write_asc_to_file): simplify code using safe version of
   dc_write_file() function
2019-09-12 04:11:44 +00:00
Dmitry Bogatov
f198ce29b1 Drop unsafe version of dc_get_abs_path 2019-09-12 04:11:43 +00:00
Dmitry Bogatov
df47e0ed63 Use safe version of dc_get_abs_path.
* src/dc_mimefactory.rs(build_body_file): use safe version of
   dc_get_abs_path() function.
2019-09-12 04:10:05 +00:00
Dmitry Bogatov
477470ff70 Make dc_msg_get_file return PathBuf, not char*
Adjust call sites as apporiate:

 * src/dc_imex.rs(dc_continue_key_transfer): use if-let pattern
   with dc_read_file_safe() and dc_msg_get_file()

 * src/message.rs(dc_msg_get_setupcodebegin): ditto

 * src/message.rs(dc_get_msg_info): simplify code to print information
   about file inside a message.

 * src/message.rs(dc_msg_get_file): simplify function using
   dc_get_abs_path_safe()

 * deltachat-ffi/src/lib.rs(dc_msg_get_file): convert PathBuf to `char *`
   to preserve C API
2019-09-12 04:10:05 +00:00
64 changed files with 4679 additions and 4961 deletions

4
.gitattributes vendored
View File

@@ -2,6 +2,10 @@
# ensures this even if the user has not set core.autocrlf.
* text=auto
# This directory contains email messages verbatim, and changing CRLF to
# LF will corrupt them.
test-data/* text=false
# binary files should be detected by git, however, to be sure, you can add them here explicitly
*.png binary
*.jpg binary

491
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,6 @@ authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
license = "MPL"
[build-dependencies]
cc = "1.0.35"
pkg-config = "0.3"
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
libc = "0.2.51"
@@ -51,6 +47,7 @@ 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"

View File

@@ -1,38 +0,0 @@
extern crate cc;
fn link_static(lib: &str) {
println!("cargo:rustc-link-lib=static={}", lib);
}
fn link_framework(fw: &str) {
println!("cargo:rustc-link-lib=framework={}", fw);
}
fn add_search_path(p: &str) {
println!("cargo:rustc-link-search={}", p);
}
fn build_tools() {
let mut config = cc::Build::new();
config.file("misc.c").compile("libtools.a");
println!("rerun-if-changed=build.rs");
println!("rerun-if-changed=misc.h");
println!("rerun-if-changed=misc.c");
}
fn main() {
build_tools();
add_search_path("/usr/local/lib");
let target = std::env::var("TARGET").unwrap();
if target.contains("-apple") || target.contains("-darwin") {
link_framework("CoreFoundation");
link_framework("CoreServices");
link_framework("Security");
}
// local tools
link_static("tools");
}

View File

@@ -393,8 +393,9 @@ 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.
* @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.
*/
char* dc_get_config (dc_context_t* context, const char* key);

File diff suppressed because it is too large Load Diff

View File

@@ -20,9 +20,8 @@ use deltachat::message::*;
use deltachat::peerstate::*;
use deltachat::qr::*;
use deltachat::sql;
use deltachat::types::*;
use deltachat::x::*;
use num_traits::FromPrimitive;
use deltachat::Event;
/// Reset database tables. This function is called from Core cmdline.
/// Argument is a bitmask, executing single or multiple actions in one call.
@@ -87,7 +86,10 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
info!(context, "(8) Rest but server config reset.");
}
context.call_cb(Event::MSGS_CHANGED, 0, 0);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
1
}
@@ -96,7 +98,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: size_t = 0;
let mut data_bytes = 0;
if !(dc_read_file(
context,
filename,
@@ -199,7 +201,10 @@ 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::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
}
success = 1
}
@@ -229,7 +234,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 0 != dc_msg_get_showpadlock(msg) {
if dc_msg_get_showpadlock(msg) {
"🔒"
} else {
""
@@ -248,11 +253,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
} else {
"[FRESH]"
},
if 0 != dc_msg_is_info(msg) {
"[INFO]"
} else {
""
},
if dc_msg_is_info(msg) { "[INFO]" } else { "" },
statestr,
&temp2,
);
@@ -451,14 +452,6 @@ 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() {
@@ -503,32 +496,28 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
}
"export-backup" => {
dc_imex(context, 11, context.get_blobdir(), ptr::null());
dc_imex(context, 11, Some(context.get_blobdir()), ptr::null());
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
dc_imex(context, 12, arg1_c, ptr::null());
dc_imex(context, 12, Some(arg1), ptr::null());
}
"export-keys" => {
dc_imex(context, 1, context.get_blobdir(), ptr::null());
dc_imex(context, 1, Some(context.get_blobdir()), ptr::null());
}
"import-keys" => {
dc_imex(context, 2, context.get_blobdir(), ptr::null());
dc_imex(context, 2, Some(context.get_blobdir()), ptr::null());
}
"export-setup" => {
let setup_code = dc_create_setup_code(context);
let file_name: *mut libc::c_char = dc_mprintf(
b"%s/autocrypt-setup-message.html\x00" as *const u8 as *const libc::c_char,
context.get_blobdir(),
);
let file_name = context.get_blobdir().join("autocrypt-setup-message.html");
let file_content = dc_render_setup_file(context, &setup_code)?;
std::fs::write(as_str(file_name), file_content)?;
std::fs::write(&file_name, file_content)?;
println!(
"Setup message written to: {}\nSetup code: {}",
as_str(file_name),
file_name.display(),
&setup_code,
);
free(file_name as *mut libc::c_void);
}
"poke" => {
ensure!(0 != poke_spec(context, arg1_c), "Poke failed");
@@ -555,7 +544,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{}={:?}", key, val);
}
"info" => {
println!("{}", to_string(dc_get_info(context)));
println!("{:#?}", context.get_info());
}
"maybenetwork" => {
maybe_network(context);
@@ -627,7 +616,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
}
}
if location::is_sending_locations_to_chat(context, 0 as uint32_t) {
if location::is_sending_locations_to_chat(context, 0) {
info!(context, "Location streaming enabled.");
}
println!("{} chats", cnt);
@@ -663,7 +652,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
);
log_msglist(context, &msglist)?;
if let Ok(draft) = chat::get_draft(context, sel_chat.get_id()) {
if let Some(draft) = chat::get_draft(context, sel_chat.get_id())? {
log_msg(context, "Draft", &draft);
}
@@ -676,7 +665,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 uint32_t)?;
let chat_id = chat::create_by_contact_id(context, contact_id as u32)?;
println!("Single#{} created successfully.", chat_id,);
}
@@ -705,10 +694,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 0 != chat::add_contact_to_chat(
if chat::add_contact_to_chat(
context,
sel_chat.as_ref().unwrap().get_id(),
contact_id_0 as uint32_t,
contact_id_0 as u32,
) {
println!("Contact added to chat.");
} else {
@@ -722,7 +711,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 uint32_t,
contact_id_1 as u32,
)?;
println!("Contact added to chat.");
@@ -856,7 +845,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
0 as libc::c_uint
};
let msglist = dc_search_msgs(context, chat, arg1_c);
let msglist = context.search_msgs(chat, arg1);
log_msglist(context, &msglist)?;
println!("{} messages.", msglist.len());
@@ -919,7 +908,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{}", as_str(res));
}
"listfresh" => {
let msglist = dc_get_fresh_msgs(context);
let msglist = context.get_fresh_msgs();
log_msglist(context, &msglist)?;
print!("{} fresh messages.", msglist.len());
@@ -1025,16 +1014,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
res.get_text2()
);
}
"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,
);
}
// 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,
// );
// }
"fileinfo" => {
ensure!(!arg1.is_empty(), "Argument <file> missing.");

View File

@@ -15,21 +15,20 @@ 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;
@@ -44,96 +43,75 @@ use self::cmdline::*;
// Event Handler
unsafe extern "C" fn receive_event(
_context: &Context,
event: Event,
data1: uintptr_t,
data2: uintptr_t,
) -> uintptr_t {
fn receive_event(_context: &Context, event: Event) -> libc::uintptr_t {
match event {
Event::GET_STRING => {}
Event::INFO => {
Event::GetString { .. } => {}
Event::Info(msg) => {
/* do not show the event as this would fill the screen */
println!("{}", to_string(data2 as *const _),);
println!("{}", msg);
}
Event::SMTP_CONNECTED => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", to_string(data2 as *const _));
Event::SmtpConnected(msg) => {
println!("[DC_EVENT_SMTP_CONNECTED] {}", msg);
}
Event::IMAP_CONNECTED => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", to_string(data2 as *const _),);
Event::ImapConnected(msg) => {
println!("[DC_EVENT_IMAP_CONNECTED] {}", msg);
}
Event::SMTP_MESSAGE_SENT => {
println!(
"[DC_EVENT_SMTP_MESSAGE_SENT] {}",
to_string(data2 as *const _),
);
Event::SmtpMessageSent(msg) => {
println!("[DC_EVENT_SMTP_MESSAGE_SENT] {}", msg);
}
Event::WARNING => {
println!("[Warning] {}", to_string(data2 as *const _),);
Event::Warning(msg) => {
println!("[Warning] {}", msg);
}
Event::ERROR => {
println!(
"\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m",
to_string(data2 as *const _),
);
Event::Error(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR] {}\x1b[0m", msg);
}
Event::ERROR_NETWORK => {
println!(
"\x1b[31m[DC_EVENT_ERROR_NETWORK] first={}, msg={}\x1b[0m",
data1 as usize,
to_string(data2 as *const _),
);
Event::ErrorNetwork(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_NETWORK] msg={}\x1b[0m", msg);
}
Event::ERROR_SELF_NOT_IN_GROUP => {
println!(
"\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m",
to_string(data2 as *const _),
);
Event::ErrorSelfNotInGroup(msg) => {
println!("\x1b[31m[DC_EVENT_ERROR_SELF_NOT_IN_GROUP] {}\x1b[0m", msg);
}
Event::MSGS_CHANGED => {
Event::MsgsChanged { chat_id, msg_id } => {
print!(
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED({}, {})}}\n\x1b[0m",
data1 as usize, data2 as usize,
"\x1b[33m{{Received DC_EVENT_MSGS_CHANGED(chat_id={}, msg_id={})}}\n\x1b[0m",
chat_id, msg_id,
);
}
Event::CONTACTS_CHANGED => {
Event::ContactsChanged(_) => {
print!("\x1b[33m{{Received DC_EVENT_CONTACTS_CHANGED()}}\n\x1b[0m");
}
Event::LOCATION_CHANGED => {
Event::LocationChanged(contact) => {
print!(
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={})}}\n\x1b[0m",
data1 as usize,
"\x1b[33m{{Received DC_EVENT_LOCATION_CHANGED(contact={:?})}}\n\x1b[0m",
contact,
);
}
Event::CONFIGURE_PROGRESS => {
Event::ConfigureProgress(progress) => {
print!(
"\x1b[33m{{Received DC_EVENT_CONFIGURE_PROGRESS({} ‰)}}\n\x1b[0m",
data1 as usize,
progress,
);
}
Event::IMEX_PROGRESS => {
Event::ImexProgress(progress) => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_PROGRESS({} ‰)}}\n\x1b[0m",
data1 as usize,
progress,
);
}
Event::IMEX_FILE_WRITTEN => {
Event::ImexFileWritten(file) => {
print!(
"\x1b[33m{{Received DC_EVENT_IMEX_FILE_WRITTEN({})}}\n\x1b[0m",
to_string(data1 as *const _)
file.display()
);
}
Event::CHAT_MODIFIED => {
Event::ChatModified(chat) => {
print!(
"\x1b[33m{{Received DC_EVENT_CHAT_MODIFIED({})}}\n\x1b[0m",
data1 as usize,
chat
);
}
_ => {
print!(
"\x1b[33m{{Received {:?}({}, {})}}\n\x1b[0m",
event, data1 as usize, data2 as usize,
);
print!("\x1b[33m{{Received {:?}}}\n\x1b[0m", event);
}
}
@@ -387,15 +365,15 @@ impl Highlighter for DcHelper {
impl Helper for DcHelper {}
fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
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 {
if args.len() < 2 {
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.");

View File

@@ -1,6 +1,5 @@
extern crate deltachat;
use std::ffi::CStr;
use std::sync::{Arc, RwLock};
use std::{thread, time};
use tempfile::tempdir;
@@ -9,42 +8,43 @@ 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;
extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> usize {
println!("[{:?}]", event);
fn cb(_ctx: &Context, event: Event) -> usize {
print!("[{:?}]", event);
match event {
Event::CONFIGURE_PROGRESS => {
println!(" progress: {}", data1);
Event::ConfigureProgress(progress) => {
print!(" progress: {}\n", progress);
0
}
Event::INFO | Event::WARNING | Event::ERROR | Event::ERROR_NETWORK => {
println!(
" {}",
unsafe { CStr::from_ptr(data2 as *const _) }
.to_str()
.unwrap()
);
Event::Info(msg) | Event::Warning(msg) | Event::Error(msg) | Event::ErrorNetwork(msg) => {
print!(" {}\n", msg);
0
}
_ => {
print!("\n");
0
}
_ => 0,
}
}
fn main() {
unsafe {
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
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 running = Arc::new(RwLock::new(true));
let info = dc_get_info(&ctx);
let info_s = CStr::from_ptr(info);
let info = ctx.get_info();
let duration = time::Duration::from_millis(4000);
println!("info: {}", info_s.to_str().unwrap());
println!("info: {:#?}", info);
let ctx = Arc::new(ctx);
let ctx1 = ctx.clone();
@@ -73,13 +73,6 @@ 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");
@@ -130,6 +123,5 @@ fn main() {
t2.join().unwrap();
println!("closing");
dc_close(&ctx);
}
}

52
misc.c
View File

@@ -1,52 +0,0 @@
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include "misc.h"
static char* internal_dc_strdup(const char* s) /* strdup(NULL) is undefined, save_strdup(NULL) returns an empty string in this case */
{
char* ret = NULL;
if (s) {
if ((ret=strdup(s))==NULL) {
exit(16); /* cannot allocate (little) memory, unrecoverable error */
}
}
else {
if ((ret=(char*)calloc(1, 1))==NULL) {
exit(17); /* cannot allocate little memory, unrecoverable error */
}
}
return ret;
}
char* dc_mprintf(const char* format, ...)
{
char testbuf[1];
char* buf = NULL;
int char_cnt_without_zero = 0;
va_list argp;
va_list argp_copy;
va_start(argp, format);
va_copy(argp_copy, argp);
char_cnt_without_zero = vsnprintf(testbuf, 0, format, argp);
va_end(argp);
if (char_cnt_without_zero < 0) {
va_end(argp_copy);
return internal_dc_strdup("ErrFmt");
}
buf = malloc(char_cnt_without_zero+2 /* +1 would be enough, however, protect against off-by-one-errors */);
if (buf==NULL) {
va_end(argp_copy);
return internal_dc_strdup("ErrMem");
}
vsnprintf(buf, char_cnt_without_zero+1, format, argp_copy);
va_end(argp_copy);
return buf;
}

1
misc.h
View File

@@ -1 +0,0 @@
char* dc_mprintf (const char* format, ...); /* The result must be free()'d. */

View File

@@ -50,9 +50,8 @@ class Account(object):
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_completed = threading.Event()
# XXX this can cause "illegal instructions" at test ends so we omit it for now
# def __del__(self):
# self.shutdown()
def __del__(self):
self.shutdown()
def _check_config_key(self, name):
if name not in self._configkeys:
@@ -397,7 +396,8 @@ class Account(object):
def shutdown(self, wait=True):
""" stop threads and close and remove underlying dc_context and callbacks. """
if hasattr(self, "_dc_context") and hasattr(self, "_threads"):
self.stop_threads(wait=False) # to interrupt idle and tell python threads to stop
# print("SHUTDOWN", self)
self.stop_threads(wait=False)
lib.dc_close(self._dc_context)
self.stop_threads(wait=wait) # to wait for threads
deltachat.clear_context_callback(self._dc_context)

View File

@@ -305,7 +305,6 @@ class Chat(object):
def get_contacts(self):
""" get all contacts for this chat.
:params: contact object.
:raises ValueError: if contact could not be added
:returns: list of :class:`deltachat.chatting.Contact` objects for this chat
"""

View File

@@ -110,7 +110,13 @@ class Message(object):
def continue_key_transfer(self, setup_code):
""" extract key and use it as primary key for this account. """
lib.dc_continue_key_transfer(self._dc_context, self.id, as_dc_charpointer(setup_code))
res = lib.dc_continue_key_transfer(
self._dc_context,
self.id,
as_dc_charpointer(setup_code)
)
if res == 0:
raise ValueError("could not decrypt")
@props.with_doc
def time_sent(self):

View File

@@ -24,17 +24,6 @@ def pytest_configure(config):
config.option.liveconfig = cfg
@pytest.hookimpl(trylast=True)
def pytest_runtest_call(item):
# perform early finalization because we otherwise get cloberred
# output from concurrent threads printing between execution
# of the test function and the teardown phase of that test function
if "acfactory" in item.funcargs:
print("*"*30, "finalizing", "*"*30)
acfactory = item.funcargs["acfactory"]
acfactory.finalize()
def pytest_report_header(config, startdir):
summary = []
@@ -136,13 +125,17 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
fin = self._finalizers.pop()
fin()
def make_account(self, path, logid):
ac = Account(path, logid=logid)
self._finalizers.append(ac.shutdown)
return ac
def get_unconfigured_account(self):
self.offline_count += 1
tmpdb = tmpdir.join("offlinedb%d" % self.offline_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.offline_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(2)
self._finalizers.append(ac.shutdown)
return ac
def get_configured_offline_account(self):
@@ -165,26 +158,35 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig):
if "e2ee_enabled" not in configdict:
configdict["e2ee_enabled"] = "1"
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(30)
ac.configure(**configdict)
ac.start_threads()
self._finalizers.append(ac.shutdown)
return ac
def get_two_online_accounts(self):
ac1 = self.get_online_configuring_account()
ac2 = self.get_online_configuring_account()
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
return ac1, ac2
def clone_online_account(self, account):
self.live_count += 1
tmpdb = tmpdir.join("livedb%d" % self.live_count)
ac = Account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac = self.make_account(tmpdb.strpath, logid="ac{}".format(self.live_count))
ac._evlogger.init_time = self.init_time
ac._evlogger.set_timeout(30)
ac.configure(addr=account.get_config("addr"), mail_pw=account.get_config("mail_pw"))
ac.start_threads()
self._finalizers.append(ac.shutdown)
return ac
return AccountMaker()
am = AccountMaker()
request.addfinalizer(am.finalize)
return am
@pytest.fixture

View File

@@ -334,10 +334,11 @@ class TestOfflineChat:
class TestOnlineAccount:
def test_one_account_init(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
def get_chat(self, ac1, ac2):
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
return chat
def test_one_account_send(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
@@ -353,15 +354,8 @@ class TestOnlineAccount:
assert ev[1] == msg_out.id
def test_two_accounts_send_receive(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
msg_out = chat.send_text("message1")
@@ -372,15 +366,8 @@ class TestOnlineAccount:
assert msg_in.text == "message1"
def test_forward_messages(self, acfactory):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
wait_successful_IMAP_SMTP_connection(ac1)
wait_configuration_progress(ac1, 1000)
wait_successful_IMAP_SMTP_connection(ac2)
wait_configuration_progress(ac2, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
msg_out = chat.send_text("message2")
@@ -404,15 +391,10 @@ class TestOnlineAccount:
assert not chat3.get_messages()
def test_send_and_receive_message(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
ac1, ac2 = acfactory.get_two_online_accounts()
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("ac1: create chat with ac2")
chat = self.get_chat(ac1, ac2)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
@@ -454,15 +436,10 @@ class TestOnlineAccount:
assert msg_out.is_out_mdn_received()
def test_send_and_receive_will_encrypt_decrypt(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
ac1, ac2 = acfactory.get_two_online_accounts()
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
lp.sec("ac1: create chat with ac2")
chat = self.get_chat(ac1, ac2)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
@@ -485,14 +462,12 @@ class TestOnlineAccount:
assert msg_back.text == "message-back"
def test_saved_mime_on_received_message(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("configure ac2 to save mime headers, create ac1/ac2 chat")
ac2.set_config("save_mime_headers", "1")
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
chat = self.get_chat(ac1, ac2)
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
@@ -506,14 +481,8 @@ class TestOnlineAccount:
assert mime.get_all("Received")
def test_send_and_receive_image(self, acfactory, lp, data):
lp.sec("starting accounts, waiting for configuration")
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2)
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
chat = self.get_chat(ac1, ac2)
lp.sec("sending image message from ac1 to ac2")
path = data.get_path("d.png")
@@ -533,13 +502,13 @@ class TestOnlineAccount:
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online(self, acfactory, tmpdir):
backupdir = tmpdir.mkdir("backup")
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("msg1")
backupdir = tmpdir.mkdir("backup")
path = ac1.export_to_dir(backupdir.strpath)
assert os.path.exists(path)
@@ -568,16 +537,16 @@ class TestOnlineAccount:
ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message()
# first try a bad setup code
with pytest.raises(ValueError):
msg.continue_key_transfer(str(reversed(setup_code)))
print("*************** Incoming ASM File at: ", msg.filename)
print("*************** Setup Code: ", setup_code)
msg.continue_key_transfer(setup_code)
assert ac1.get_info()["fingerprint"] == ac2.get_info()["fingerprint"]
def test_qr_setup_contact(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
qr = ac1.get_setup_contact_qr()
lp.sec("ac2: start QR-code based setup contact protocol")
@@ -586,12 +555,8 @@ class TestOnlineAccount:
wait_securejoin_inviter_progress(ac1, 1000)
def test_qr_join_chat(self, acfactory, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("ac1: create QR code and let ac2 scan it, starting the securejoin")
chat = ac1.create_group_chat("hello")
qr = chat.get_join_qr()
lp.sec("ac2: start QR-code based join-group protocol")
@@ -600,10 +565,7 @@ class TestOnlineAccount:
wait_securejoin_inviter_progress(ac1, 1000)
def test_set_get_profile_image(self, acfactory, data, lp):
ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac2, 1000)
wait_configuration_progress(ac1, 1000)
ac1, ac2 = acfactory.get_two_online_accounts()
lp.sec("create unpromoted group chat")
chat = ac1.create_group_chat("hello")

View File

@@ -17,11 +17,19 @@ def test_callback_None2int():
clear_context_callback(ctx)
def test_dc_close_events():
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL)
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,
)
evlog = EventLogger(ctx)
evlog.set_timeout(5)
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
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)
capi.lib.dc_close(ctx)
def find(info_string):

308
spec.md Normal file
View File

@@ -0,0 +1,308 @@
# Chat-over-Email specification
Version 0.19.0
This document describes how emails can be used to implement typical messenger functions while staying compatible to existing MUAs.
- [Encryption](#encryption)
- [Outgoing messages](#outgoing-messages)
- [Incoming messages](#incoming-messages)
- [Forwarded messages](#forwarded-messages)
- [Groups](#groups)
- [Outgoing group messages](#outgoing-group-messages)
- [Incoming group messages](#incoming-group-messages)
- [Add and remove members](#add-and-remove-members)
- [Change group name](#change-group-name)
- [Set group image](#set-group-image)
- [Set profile image](#set-profile-image)
- [Miscellaneous](#miscellaneous)
# Encryption
Messages SHOULD be encrypted by the [Autocrypt](https://autocrypt.org/level1.html) standard; `prefer-encrypt=mutual` MAY be set by default.
Meta data (at least the subject and all chat-headers) SHOULD be encrypted by the [Memoryhole](https://github.com/autocrypt/memoryhole) standard.
If Memoryhole is not used, the subject of encrypted messages SHOULD be replaced by the string
`Chat: Encrypted message` where the part after the colon MAY be localized.
# Outgoing messages
Messengers MUST add a `Chat-Version: 1.0` header to outgoing messages.
For filtering and smart appearance of the messages in normal MUAs,
the `Subject` header SHOULD start with the characters `Chat:` and SHOULD be an excerpt of the message.
Replies to messages MAY follow the typical `Re:`-format.
The body MAY contain text which MUST have the content type `text/plain`
or `mulipart/alternative` containing `text/plain`.
The text MAY be divided into a user-text-part and a footer-part using the
line `-- ` (minus, minus, space, lineend).
The user-text-part MUST contain only user generated content.
User generated content are eg. texts a user has actually typed or pasted or
forwarded from another user.
Full quotes, footers or sth. like that MUST NOT go to the user-text-part.
From: sender@domain
To: rcpt@domain
Chat-Version: 1.0
Content-Type: text/plain
Subject: Chat: Hello ...
Hello world!
# Incoming messages
The `Chat-Version` header MAY be used to detect if a messages comes from a compatible messenger.
The `Subject` header MUST NOT be used to detect compatible messengers, groups or whatever.
Messenger SHOULD show the `Subject` if the message comes from a normal MUA together with the email-body.
The email-body SHOULD be converted to plain text, full-quotes and similar regions SHOULD be cut.
Attachments SHOULD be shown where possible. If an attachment cannot be shown, a non-distracting warning SHOULD be printed.
# Forwarded messages
Forwarded messages are outgoing messages that contain a forwarded-header
before the user generated content.
The forwarded header MUST contain two lines:
The first line contains the text
`---------- Forwarded message ----------`
(10 minus, space, text `Forwarded message`, space, 10 minus).
The second line starts with `From: ` followed by the original sender
which SHOULD be anonymized or just a placeholder.
From: sender@domain
To: rcpt@domain
Chat-Version: 1.0
Content-Type: text/plain
Subject: Chat: Forwarded message
---------- Forwarded message ----------
From: Messenger
Hello world!
Incoming forwarded messages are detected by the header.
The messenger SHOULD mark these messages in a way that it becomes obvious
that the message is not created by the sender.
Note that most messengers do not show the original sender with forwarded messages
but MUAs typically expose the sender in the UI.
# Groups
Groups are chats with usually more than one recipient, each defined by an email-address.
The sender plus the recipients are the group members.
To allow different groups with the same members, groups are identified by a group-id.
The group-id MUST be created only from the characters `0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`.
Groups MUST have a group-name. The group-name is any non-zero-length UTF-8 string.
Groups MAY have a group-image.
## Outgoing groups messages
All group members MUST be added to the `From`/`To` headers.
The group-id MUST be written to the `Chat-Group-ID` header.
The group-name MUST be written to `Chat-Group-Name` header (the forced presence of this header makes it easier to join a group chat on a second device any time).
The `Subject` header of outgoing group messages SHOULD start with the characters `Chat:` followed by the group-name and a colon followed by an excerpt of the message.
To identify the group-id on replies from normal MUAs, the group-id MUST also be added to
the message-id of outgoing messages. The message-id MUST have the
format `Gr.<group-id>.<unique data>`.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Message-ID: Gr.1234xyZ.0001@domain
Subject: Chat: My Group: Hello group ...
Hello group - this group contains three members
Messengers adding the member list in the form `Name <email-address>` MUST take care only to spread the names authorized by the contacts themselves.
Otherwise, names as _Daddy_ or _Honey_ may be spread (this issue is also true for normal MUAs, however, for more contact- and chat-centralized apps
such situations happen more frequently).
## Incoming group messages
The messenger MUST search incoming messages for the group-id in the following headers: `Chat-Group-ID`,
`Message-ID`, `In-Reply-To` and `References` (in this order).
If the messenger finds a valid and existent group-id, the message SHOULD be assigned to the given group.
If the messenger finds a valid but not existent group-id, the messenger MAY create a new group.
If no group-id is found, the message MAY be assigned to a normal single-user chat with the email-address given in `From`.
## Add and remove members
Messenger clients MUST construct the member list from the `From`/`To` headers only on the first group message or if they see a `Chat-Group-Member-Added` or `Chat-Group-Member-Removed` action header.
Both headers MUST have the email-address of the added or removed member as the value.
Messenger clients MUST NOT construct the member list on other group messages (this is to avoid accidentally altered To-lists in normal MUAs; the user
does not expect adding a user to a _message_ will also add him to the _group_ "forever").
The messenger SHOULD send an explicit mail for each added or removed member.
The body of the message SHOULD contain a localized description about what happened
and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain, member4@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Chat-Group-Member-Added: member4@domain
Message-ID: Gr.1234xyZ.0002@domain
Subject: Chat: My Group: Hello, ...
Hello, I've added member4@domain to our group. Now we have 4 members.
To remove a member:
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: My Group
Chat-Group-Member-Removed: member4@domain
Message-ID: Gr.1234xyZ.0003@domain
Subject: Chat: My Group: Hello, ...
Hello, I've removed member4@domain from our group. Now we have 3 members.
## Change group name
To change the group-name,
the messenger MUST send the action header `Chat-Group-Name-Changed`
with the value set to the old group name to all group members.
The new group name goes to the header `Chat-Group-Name`.
The messenger SHOULD send an explicit mail for each name change.
The body of the message SHOULD contain a localized description about what happened
and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: Our Group
Chat-Group-Name-Changed: My Group
Message-ID: Gr.1234xyZ.0004@domain
Subject: Chat: Our Group: Hello, ...
Hello, I've changed the group name from "My Group" to "Our Group".
## Set group image
A group MAY have a group-image.
To change or set the group-image, the messenger MUST attach an image file to a message and MUST add the header `Chat-Group-Image` with the
value set to the image name.
To remove the group-image, the messenger MUST add the header `Chat-Group-Image: 0`.
The messenger SHOULD send an explicit mail for each group image change.
The body of the message SHOULD contain a localized description about what happened
and the message SHOULD appear as a message or action from the sender.
From: member1@domain
To: member2@domain, member3@domain
Chat-Version: 1.0
Chat-Group-ID: 1234xyZ
Chat-Group-Name: Our Group
Chat-Group-Image: image.jpg
Message-ID: Gr.1234xyZ.0005@domain
Subject: Chat: Our Group: Hello, ...
Content-Type: multipart/mixed; boundary="==break=="
--==break==
Content-Type: text/plain
Hello, I've changed the group image.
--==break==
Content-Type: image/jpeg
Content-Disposition: attachment; filename="image.jpg"
/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBw ...
--==break==--
The image format SHOULD be image/jpeg or image/png. To save data, it is RECOMMENDED to add a `Chat-Group-Image` only on image changes.
# Set profile image
A user MAY have a profile-image that MAY be spread to his contacts.
To change or set the profile-image, the messenger MUST attach an image file to a message and MUST add the header `Chat-Profile-Image` with the
value set to the image name.
To remove the profile-image, the messenger MUST add the header `Chat-Profile-Image: 0`.
To spread the image, the messenger MAY send the profile image together with the next mail to a given contact
(to do this only once, the messenger has to keep a `profile_image_update_state` somewhere).
Alternatively, the messenger MAY send an explicit mail for each profile-image change to all contacts using a compatible messenger.
The messenger SHOULD NOT send an explicit mail to normal MUAs.
From: sender@domain
To: rcpt@domain
Chat-Version: 1.0
Chat-Profile-Image: photo.jpg
Subject: Chat: Hello, ...
Content-Type: multipart/mixed; boundary="==break=="
--==break==
Content-Type: text/plain
Hello, I've changed my profile image.
--==break==
Content-Type: image/jpeg
Content-Disposition: attachment; filename="photo.jpg"
AKCgkJi3j4l5kjoldfUAKCgkJi3j4lldfHjgWICwgIEBQYFBA ...
--==break==--
The image format SHOULD be image/jpeg or image/png. Note that `Chat-Profile-Image` may appear together with all other headers, eg. there may be a
`Chat-Profile-Image` and a `Chat-Group-Image` header in the same message. To save data, it is RECOMMENDED to add a `Chat-Profile-Image` header only on image changes.
# Miscellaneous
Messengers SHOULD use the header `Chat-Predecessor` instead of `In-Reply-To` as
the latter one results in infinite threads on typical MUAs.
Messengers SHOULD add a `Chat-Voice-message: 1` header if an attached audio file is a voice message.
Messengers MAY add a `Chat-Duration` header to specify the duration of attached audio or video files.
The value MUST be the duration in milliseconds.
This allows the receiver to show the time without knowing the file format.
Chat-Predecessor: foo123@domain
Chat-Voice-Message: 1
Chat-Duration: 10000
Messengers MAY send and receive Message Disposition Notifications (MDNs, [RFC 8098](https://tools.ietf.org/html/rfc8098), [RFC 3503](https://tools.ietf.org/html/rfc3503))
using the `Chat-Disposition-Notification-To` header instead of the `Disposition-Notification-To` (which unfortunately forces many other MUAs to send weird mails not following any
standard).
## Sync messages
If some action is required by a message header, the action should only be performed if the _effective date_ is newer than the date the last action was performed.
We define the effective date of a message as the sending time of the message as indicated by its Date header,
or the time of first receipt if that date is in the future or unavailable.
Copyright © 2017-2019 Delta Chat contributors.

View File

@@ -7,7 +7,6 @@ use mmime::mailimf_types::*;
use crate::constants::*;
use crate::contact::*;
use crate::dc_tools::as_str;
use crate::key::*;
/// Possible values for encryption preference
@@ -64,11 +63,8 @@ impl Aheader {
}
}
pub fn from_imffields(
wanted_from: *const libc::c_char,
header: *const mailimf_fields,
) -> Option<Self> {
if wanted_from.is_null() || header.is_null() {
pub fn from_imffields(wanted_from: &str, header: *const mailimf_fields) -> Option<Self> {
if header.is_null() {
return None;
}
@@ -94,7 +90,7 @@ impl Aheader {
match Self::from_str(value) {
Ok(test) => {
if addr_cmp(&test.addr, as_str(wanted_from)) {
if addr_cmp(&test.addr, wanted_from) {
if fine_header.is_none() {
fine_header = Some(test);
} else {

View File

@@ -1,26 +1,28 @@
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(Clone)]
#[derive(Debug, Clone)]
pub struct Chat {
pub id: u32,
pub typ: Chattype,
@@ -59,15 +61,13 @@ impl Chat {
match res {
Err(err @ crate::error::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Err(err),
Err(err) => match err {
_ => {
error!(
context,
"chat: failed to load from db {}: {:?}", chat_id, err
);
Err(err)
}
},
Err(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,13 +140,12 @@ impl Chat {
if self.typ == Chattype::Single {
return context
.sql
.query_row_col(
.query_get_value(
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());
}
@@ -183,7 +182,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_safe(context, image_rel));
return Some(dc_get_abs_path(context, image_rel));
}
} else if self.typ == Chattype::Single {
let contacts = get_chat_contacts(context, self.id);
@@ -260,16 +259,14 @@ impl Chat {
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
&& 0 == is_contact_in_chat(context, self.id, 1 as u32)
{
log_event!(
emit_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot send message; self not in group.",
Event::ErrorSelfNotInGroup("Cannot send message; self not in group.".into())
);
return Ok(0);
}
if let Some(from) = context.sql.get_config(context, "configured_addr") {
if let Some(from) = context.get_config(Config::ConfiguredAddr) {
let new_rfc724_mid = {
let grpid = match self.typ {
Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()),
@@ -279,11 +276,10 @@ impl Chat {
};
if self.typ == Chattype::Single {
if let Some(id) = context.sql.query_row_col(
if let Some(id) = context.sql.query_get_value(
context,
"SELECT contact_id FROM chats_contacts WHERE chat_id=?;",
params![self.id as i32],
0,
) {
to_id = id;
} else {
@@ -504,7 +500,10 @@ pub fn create_by_msg_id(context: &Context, msg_id: u32) -> Result<u32, Error> {
}
if send_event {
context.call_cb(Event::MSGS_CHANGED, 0, 0);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
}
ensure!(chat_id > 0, "failed to load create chat");
@@ -544,7 +543,10 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result<u32, E
}
};
context.call_cb(Event::MSGS_CHANGED, 0i32 as uintptr_t, 0i32 as uintptr_t);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
Ok(chat_id)
}
@@ -637,11 +639,10 @@ pub fn prepare_msg<'a>(
msg.state = MessageState::OutPreparing;
let msg_id = prepare_msg_common(context, chat_id, msg)?;
context.call_cb(
Event::MSGS_CHANGED,
msg.chat_id as uintptr_t,
msg.id as uintptr_t,
);
context.call_cb(Event::MsgsChanged {
chat_id: msg.chat_id,
msg_id: msg.id,
});
Ok(msg_id)
}
@@ -724,13 +725,12 @@ 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_row_col(
let packed: Option<String> = sql.query_get_value(
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,14 +793,13 @@ pub fn send_msg(context: &Context, chat_id: u32, msg: &mut Message) -> Result<u3
"Failed to initiate send job"
);
context.call_cb(
Event::MSGS_CHANGED,
msg.chat_id as uintptr_t,
msg.id as uintptr_t,
);
context.call_cb(Event::MsgsChanged {
chat_id: msg.chat_id,
msg_id: msg.id,
});
if msg.param.exists(Param::SetLatitude) {
context.call_cb(Event::LOCATION_CHANGED, DC_CONTACT_ID_SELF as usize, 0);
context.call_cb(Event::LocationChanged(Some(DC_CONTACT_ID_SELF)));
}
if 0 == chat_id {
@@ -848,7 +847,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::MSGS_CHANGED, chat_id as uintptr_t, 0i32 as uintptr_t);
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
};
}
@@ -872,7 +871,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 0 != dc_msg_is_increation(msg) && !dc_is_blobdir_path(context, &path_filename) {
if 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;
@@ -912,21 +911,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_row_col::<_, i32>(
.query_get_value::<_, 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 unsafe fn get_draft(context: &Context, chat_id: u32) -> Result<Message, Error> {
pub fn get_draft(context: &Context, chat_id: u32) -> Result<Option<Message>, Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat ID");
let draft_msg_id = get_draft_msg_id(context, chat_id);
ensure!(draft_msg_id != 0, "Invalid draft message ID");
dc_msg_load_from_db(context, draft_msg_id)
if draft_msg_id == 0 {
return Ok(None);
}
Ok(Some(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> {
@@ -1007,11 +1006,10 @@ 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_row_col::<_, i32>(
.query_get_value::<_, i32>(
context,
"SELECT COUNT(*) FROM msgs WHERE chat_id=?;",
params![chat_id as i32],
0,
)
.unwrap_or_default() as usize
}
@@ -1019,14 +1017,13 @@ 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_row_col::<_, i32>(
.query_get_value::<_, 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
}
@@ -1047,7 +1044,10 @@ pub fn marknoticed_chat(context: &Context, chat_id: u32) -> Result<(), Error> {
params![chat_id as i32],
)?;
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
Ok(())
}
@@ -1069,7 +1069,10 @@ pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> {
params![],
)?;
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::MsgsChanged {
msg_id: 0,
chat_id: 0,
});
Ok(())
}
@@ -1174,7 +1177,10 @@ pub fn archive(context: &Context, chat_id: u32, archive: bool) -> Result<(), Err
params![archive, chat_id as i32],
)?;
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::MsgsChanged {
msg_id: 0,
chat_id: 0,
});
Ok(())
}
@@ -1216,7 +1222,10 @@ pub fn delete(context: &Context, chat_id: u32) -> Result<(), Error> {
params![chat_id as i32],
)?;
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::MsgsChanged {
msg_id: 0,
chat_id: 0,
});
job_kill_action(context, Action::Housekeeping);
job_add(context, Action::Housekeeping, 0, Params::new(), 10);
@@ -1278,13 +1287,16 @@ pub unsafe fn create_group_chat(
let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid);
if chat_id != 0 {
if 0 != add_to_chat_contacts_table(context, chat_id, 1) {
if 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::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::MsgsChanged {
msg_id: 0,
chat_id: 0,
});
}
Ok(chat_id)
@@ -1292,8 +1304,7 @@ pub unsafe fn create_group_chat(
/* you MUST NOT modify this or the following strings */
// Context functions to work with chats
// TODO should return bool /rtn
pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u32) -> libc::c_int {
pub fn add_to_chat_contacts_table(context: &Context, chat_id: u32, contact_id: u32) -> bool {
// 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(
@@ -1302,27 +1313,26 @@ 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() as libc::c_int
.is_ok()
}
pub unsafe fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> libc::c_int {
pub unsafe fn add_contact_to_chat(context: &Context, chat_id: u32, contact_id: u32) -> bool {
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,
) -> libc::c_int {
) -> bool {
let mut OK_TO_CONTINUE = true;
let mut success: libc::c_int = 0;
let mut success = false;
let contact = Contact::get_by_id(context, contact_id);
if contact.is_err() || chat_id <= DC_CHAT_ID_LAST_SPECIAL {
return 0;
return false;
}
let mut msg = dc_msg_new_untyped();
@@ -1335,11 +1345,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) {
log_event!(
emit_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot add contact to group; self not in group.",
Event::ErrorSelfNotInGroup(
"Cannot add contact to group; self not in group.".into()
)
);
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
@@ -1359,7 +1369,7 @@ pub fn add_contact_to_chat_ex(
if 0 != is_contact_in_chat(context, chat_id, contact_id) {
if 0 == flags & 0x1 {
success = 1;
success = true;
OK_TO_CONTINUE = false;
}
} else {
@@ -1374,7 +1384,7 @@ pub fn add_contact_to_chat_ex(
}
}
if OK_TO_CONTINUE {
if 0 == add_to_chat_contacts_table(context, chat_id, contact_id) {
if !add_to_chat_contacts_table(context, chat_id, contact_id) {
OK_TO_CONTINUE = false;
}
}
@@ -1392,14 +1402,13 @@ 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::MSGS_CHANGED,
chat_id as uintptr_t,
msg.id as uintptr_t,
);
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
});
}
context.call_cb(Event::MSGS_CHANGED, chat_id as uintptr_t, 0 as uintptr_t);
success = 1;
context.call_cb(Event::MsgsChanged { chat_id, msg_id: 0 });
success = true;
}
}
}
@@ -1481,11 +1490,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) {
log_event!(
emit_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot remove contact from chat; self not in group.",
Event::ErrorSelfNotInGroup(
"Cannot remove contact from chat; self not in group.".into()
)
);
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
@@ -1511,11 +1520,10 @@ 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::MSGS_CHANGED,
chat_id as uintptr_t,
msg.id as uintptr_t,
);
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
});
}
}
if sql::execute(
@@ -1526,7 +1534,7 @@ pub unsafe fn remove_contact_from_chat(
)
.is_ok()
{
context.call_cb(Event::CHAT_MODIFIED, chat_id as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::ChatModified(chat_id));
success = true;
}
}
@@ -1578,11 +1586,9 @@ 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) {
log_event!(
emit_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot set chat name; self not in group",
Event::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into())
);
} else {
/* we should respect this - whatever we send to the group, it gets discarded anyway! */
@@ -1611,17 +1617,12 @@ 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::MSGS_CHANGED,
chat_id as uintptr_t,
msg.id as uintptr_t,
);
context.call_cb(Event::MsgsChanged {
chat_id,
msg_id: msg.id,
});
}
context.call_cb(
Event::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
context.call_cb(Event::ChatModified(chat_id));
success = true;
}
}
@@ -1647,11 +1648,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) {
log_event!(
emit_event!(
context,
Event::ERROR_SELF_NOT_IN_GROUP,
0,
"Cannot set chat profile image; self not in group.",
Event::ErrorSelfNotInGroup(
"Cannot set chat profile image; self not in group.".into()
)
);
bail!("Failed to set profile image");
}
@@ -1669,7 +1670,8 @@ 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, DC_CMD_GROUPIMAGE_CHANGED);
msg.param
.set_int(Param::Cmd, SystemMessage::GroupImageChanged as i32);
msg.type_0 = Viewtype::Text;
msg.text = Some(context.stock_system_msg(
if new_image_rel == "" {
@@ -1684,9 +1686,15 @@ 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::MSGS_CHANGED, chat_id, msg.id);
emit_event!(
context,
Event::MsgsChanged {
chat_id,
msg_id: msg.id
}
);
}
emit_event!(context, Event::CHAT_MODIFIED, chat_id, 0);
emit_event!(context, Event::ChatModified(chat_id));
return Ok(());
}
}
@@ -1780,22 +1788,20 @@ pub unsafe fn forward_msgs(
}
for i in (0..created_db_entries.len()).step_by(2) {
context.call_cb(
Event::MSGS_CHANGED,
created_db_entries[i] as uintptr_t,
created_db_entries[i + 1] as uintptr_t,
);
context.call_cb(Event::MsgsChanged {
chat_id: created_db_entries[i],
msg_id: created_db_entries[i + 1],
});
}
}
pub fn get_chat_contact_cnt(context: &Context, chat_id: u32) -> libc::c_int {
context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;",
params![chat_id as i32],
0,
)
.unwrap_or_default()
}
@@ -1805,11 +1811,10 @@ pub fn get_chat_cnt(context: &Context) -> usize {
/* no database, no chats - this is no error (needed eg. for information) */
context
.sql
.query_row_col::<_, isize>(
.query_get_value::<_, isize>(
context,
"SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;",
params![],
0,
)
.unwrap_or_default() as usize
} else {
@@ -1867,9 +1872,51 @@ 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::MSGS_CHANGED,
chat_id as uintptr_t,
msg_id as uintptr_t,
);
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));
}
}
}

View File

@@ -31,6 +31,7 @@ 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)>,
@@ -301,11 +302,10 @@ impl Chatlist {
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
.query_row_col(
.query_get_value(
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_row_col(
.query_get_value(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
@@ -323,7 +323,6 @@ 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()
}

View File

@@ -73,7 +73,7 @@ impl Context {
let value = match key {
Config::Selfavatar => {
let rel_path = self.sql.get_config(self, key);
rel_path.map(|p| dc_get_abs_path_safe(self, &p).to_str().unwrap().to_string())
rel_path.map(|p| dc_get_abs_path(self, &p).to_str().unwrap().to_string())
}
Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
@@ -129,23 +129,6 @@ impl Context {
_ => self.sql.set_config(self, key, value),
}
}
pub fn set_ui_config(&self, key: &str, value: Option<&str>) -> Result<(), &str> {
if !key.starts_with("ui.") {
return Err("Ui config key has to be prefixed with 'ui.'");
}
if self.sql.set_config(self, key, value).is_err() {
return Err("Sql error");
}
Ok(())
}
pub fn get_ui_config(&self, key: &str) -> Option<String> {
if key.starts_with("ui.") {
return self.sql.get_config(self, key);
}
None
}
}
/// Returns all available configuration keys concated together.

View File

@@ -1,6 +1,7 @@
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;
@@ -120,13 +121,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 |= 0x200
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= 0x100
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS as i32
}
if val_lower == "plain" {
moz_ac.out.server_flags |= 0x400
moz_ac.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
}
}
_ => {}
@@ -139,13 +140,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 |= 0x20000
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
}
if val_lower == "starttls" {
moz_ac.out.server_flags |= 0x10000
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32
}
if val_lower == "plain" {
moz_ac.out.server_flags |= 0x40000
moz_ac.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
}
_ => {}

View File

@@ -1,6 +1,7 @@
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;
@@ -167,9 +168,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 |= 0x200
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_SSL as i32
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= 0x400
outlk_ad.out.server_flags |= DC_LP_IMAP_SOCKET_PLAIN as i32
}
outlk_ad.out_imap_set = 1
} else if strcasecmp(
@@ -181,9 +182,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 |= 0x20000
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_SSL as i32
} else if 0 != ssl_off {
outlk_ad.out.server_flags |= 0x40000
outlk_ad.out.server_flags |= DC_LP_SMTP_SOCKET_PLAIN as i32
}
outlk_ad.out_smtp_set = 1
}

View File

@@ -1,7 +1,6 @@
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::constants::Event;
use crate::constants::DC_CREATE_MVBOX;
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::*;
use crate::e2ee;
@@ -10,7 +9,6 @@ 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;
@@ -20,14 +18,10 @@ 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(
Event::CONFIGURE_PROGRESS,
$progress as uintptr_t,
0 as uintptr_t,
);
$context.call_cb($crate::events::Event::ConfigureProgress($progress));
};
}
@@ -283,13 +277,14 @@ 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 & 0x10000 {
587
} else if 0 != param.server_flags & 0x40000 {
25
} else {
465
}
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
}
}
if param.send_user.is_empty() && !param.mail_user.is_empty() {
param.send_user = param.mail_user.clone();
@@ -297,24 +292,30 @@ 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 & (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_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 & (0x10000 | 0x20000 | 0x40000),
param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32,
) {
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= if param.send_port == 587 {
0x10000
} else if param.send_port == 25 {
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 {
0x20000
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 |= if param.send_port == 587 {
DC_LP_SMTP_SOCKET_STARTTLS as i32
} else if param.send_port == 25 {
DC_LP_SMTP_SOCKET_PLAIN as i32
} else {
DC_LP_SMTP_SOCKET_SSL as i32
}
}
/* do we have a complete configuration? */
@@ -428,8 +429,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
success = false;
} else {
progress!(context, 850);
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.send_port = 587;
info!(context, "Trying: {}", &param);
@@ -444,8 +445,8 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context, _job: &Job) {
success = false;
} else {
progress!(context, 860);
param.server_flags &= !(0x10000 | 0x20000 | 0x40000);
param.server_flags |= 0x10000;
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.send_port = 25;
info!(context, "Trying: {}", &param);
@@ -561,7 +562,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 });
}
/*******************************************************************************

View File

@@ -1,9 +1,8 @@
//! Constants
#![allow(non_camel_case_types, dead_code)]
use lazy_static::lazy_static;
use deltachat_derive::*;
use lazy_static::lazy_static;
lazy_static! {
pub static ref DC_VERSION_STR: String = env!("CARGO_PKG_VERSION").to_string();
@@ -140,7 +139,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.
const DC_LP_AUTH_NORMAL: usize = 0x4;
pub const DC_LP_AUTH_NORMAL: usize = 0x4;
/// Connect to IMAP via STARTTLS.
/// If this flag is set, automatic configuration is skipped.
@@ -148,7 +147,7 @@ pub const DC_LP_IMAP_SOCKET_STARTTLS: usize = 0x100;
/// Connect to IMAP via SSL.
/// If this flag is set, automatic configuration is skipped.
const DC_LP_IMAP_SOCKET_SSL: usize = 0x200;
pub 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.
@@ -160,19 +159,19 @@ pub const DC_LP_SMTP_SOCKET_STARTTLS: usize = 0x10000;
/// Connect to SMTP via SSL.
/// If this flag is set, automatic configuration is skipped.
const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
pub 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
const DC_LP_AUTH_FLAGS: usize = (DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL);
pub 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
const DC_LP_IMAP_SOCKET_FLAGS: usize =
pub 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
const DC_LP_SMTP_SOCKET_FLAGS: usize =
pub 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)
@@ -247,242 +246,6 @@ 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;
@@ -551,12 +314,3 @@ 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;

View File

@@ -1,7 +1,8 @@
use std::path::PathBuf;
use deltachat_derive::*;
use itertools::Itertools;
use rusqlite;
use std::path::PathBuf;
use crate::aheader::EncryptPreference;
use crate::config::Config;
@@ -10,13 +11,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;
@@ -31,6 +32,7 @@ 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.
///
@@ -212,15 +214,13 @@ 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::CONTACTS_CHANGED,
(if sth_modified == Modifier::Created {
contact_id
context.call_cb(Event::ContactsChanged(
if sth_modified == Modifier::Created {
Some(contact_id)
} else {
0
}) as uintptr_t,
0 as uintptr_t,
);
None
},
));
if blocked {
Contact::unblock(context, contact_id);
}
@@ -241,7 +241,10 @@ impl Contact {
)
.is_ok()
{
context.call_cb(Event::MSGS_CHANGED, 0, 0);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
}
}
@@ -264,7 +267,7 @@ impl Contact {
return 1;
}
context.sql.query_row_col(
context.sql.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
params![
@@ -272,7 +275,6 @@ impl Contact {
DC_CONTACT_ID_LAST_SPECIAL as i32,
DC_ORIGIN_MIN_CONTACT_LIST,
],
0
).unwrap_or_default()
}
@@ -435,7 +437,7 @@ impl Contact {
}
}
if modify_cnt > 0 {
context.call_cb(Event::CONTACTS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::ContactsChanged(None));
}
Ok(modify_cnt)
@@ -538,11 +540,10 @@ impl Contact {
pub fn get_blocked_cnt(context: &Context) -> usize {
context
.sql
.query_row_col::<_, isize>(
.query_get_value::<_, 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
}
@@ -645,22 +646,20 @@ impl Contact {
let count_contacts: i32 = context
.sql
.query_row_col(
.query_get_value(
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_row_col(
.query_get_value(
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 {
@@ -675,7 +674,7 @@ impl Contact {
params![contact_id as i32],
) {
Ok(_) => {
context.call_cb(Event::CONTACTS_CHANGED, 0, 0);
context.call_cb(Event::ContactsChanged(None));
return Ok(());
}
Err(err) => {
@@ -844,11 +843,10 @@ impl Contact {
context
.sql
.query_row_col::<_, isize>(
.query_get_value::<_, isize>(
context,
"SELECT COUNT(*) FROM contacts WHERE id>?;",
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
0,
)
.unwrap_or_default() as usize
}
@@ -941,11 +939,7 @@ 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::CONTACTS_CHANGED,
0,
0,
);
context.call_cb(Event::ContactsChanged(None));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
use crate::location::Location;
use crate::types::*;
/* * the structure behind dc_array_t */
#[derive(Clone)]
#[derive(Debug, Clone)]
#[allow(non_camel_case_types)]
pub enum dc_array_t {
Locations(Vec<Location>),
@@ -19,7 +18,7 @@ impl dc_array_t {
dc_array_t::Locations(Vec::with_capacity(capacity))
}
pub fn add_id(&mut self, item: uint32_t) {
pub fn add_id(&mut self, item: u32) {
if let Self::Uint(array) = self {
array.push(item);
} else {
@@ -35,10 +34,10 @@ impl dc_array_t {
}
}
pub fn get_id(&self, index: usize) -> uint32_t {
pub fn get_id(&self, index: usize) -> u32 {
match self {
Self::Locations(array) => array[index].location_id,
Self::Uint(array) => array[index] as uint32_t,
Self::Uint(array) => array[index] as u32,
}
}

View File

@@ -165,3 +165,28 @@ fn dehtml_starttag_cb<B: std::io::BufRead>(
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dc_dehtml() {
let cases = vec![
(
"<a href='https://example.com'> Foo </a>",
"[ Foo ](https://example.com)",
),
("<img href='/foo.png'>", ""),
("<b> bar </b>", "* bar *"),
("<b> bar <i> foo", "* bar _ foo"),
("&amp; bar", "& bar"),
// Note missing '
("<a href='/foo.png>Hi</a> ", ""),
("", ""),
];
for (input, output) in cases {
assert_eq!(dc_dehtml(input), output);
}
}
}

View File

@@ -1,4 +1,5 @@
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use mmime::mailmime_content::*;
@@ -14,6 +15,7 @@ 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::*;
@@ -21,7 +23,6 @@ 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
@@ -29,16 +30,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 unsafe fn dc_imex(
pub fn dc_imex(
context: &Context,
what: libc::c_int,
param1: *const libc::c_char,
param1: Option<impl AsRef<Path>>,
param2: *const libc::c_char,
) {
let mut param = Params::new();
param.set_int(Param::Cmd, what as i32);
if !param1.is_null() {
param.set(Param::Arg, as_str(param1));
if let Some(param1) = param1 {
param.set(Param::Arg, param1.as_ref().to_string_lossy());
}
if !param2.is_null() {
param.set(Param::Arg2, as_str(param2));
@@ -51,9 +52,9 @@ pub unsafe fn dc_imex(
/// Returns the filename of the backup if found, nullptr otherwise.
pub unsafe fn dc_imex_has_backup(
context: &Context,
dir_name: *const libc::c_char,
dir_name: impl AsRef<Path>,
) -> *mut libc::c_char {
let dir_name = as_path(dir_name);
let dir_name = dir_name.as_ref();
let dir_iter = std::fs::read_dir(dir_name);
if dir_iter.is_err() {
info!(
@@ -100,7 +101,6 @@ pub unsafe fn dc_imex_has_backup(
}
pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
let mut setup_file_name: *mut libc::c_char = ptr::null_mut();
let mut msg: Message;
if !dc_alloc_ongoing(context) {
return std::ptr::null_mut();
@@ -114,8 +114,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.unwrap()
.shall_stop_ongoing
{
if let Ok(setup_file_content) = dc_render_setup_file(context, &setup_code) {
let setup_file_content_c = CString::yolo(setup_file_content.as_str());
if let Ok(ref setup_file_content) = dc_render_setup_file(context, &setup_code) {
/* encrypting may also take a while ... */
if !context
.running_state
@@ -124,23 +123,14 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
.unwrap()
.shall_stop_ongoing
{
setup_file_name = dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
b"autocrypt-setup-message.html\x00" as *const u8 as *const libc::c_char,
);
if !(setup_file_name.is_null()
|| 0 == dc_write_file(
context,
setup_file_name,
setup_file_content_c.as_ptr() as *const libc::c_void,
setup_file_content_c.as_bytes().len(),
))
{
let setup_file_name =
dc_get_fine_path_filename(context, "$BLOBDIR", "autocrypt-setup-message.html");
if dc_write_file(context, &setup_file_name, setup_file_content.as_bytes()) {
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
msg = dc_msg_new_untyped();
msg.type_0 = Viewtype::File;
msg.param.set(Param::File, as_str(setup_file_name));
msg.param
.set(Param::File, setup_file_name.to_string_lossy());
msg.param
.set(Param::MimeType, "application/autocrypt-setup");
@@ -168,7 +158,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 0 != dc_msg_is_sent(&msg) {
if dc_msg_is_sent(&msg) {
info!(context, "... setup message sent.",);
break;
}
@@ -181,7 +171,6 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
}
}
}
free(setup_file_name as *mut libc::c_void);
dc_free_ongoing(context);
setup_code.strdup()
@@ -241,7 +230,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: uint16_t;
let mut random_val: u16;
let mut rng = thread_rng();
let mut ret = String::new();
@@ -252,7 +241,7 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
break;
}
}
random_val = (random_val as libc::c_int % 10000) as uint16_t;
random_val = (random_val as libc::c_int % 10000) as u16;
ret += &format!(
"{}{:04}",
if 0 != i { "-" } else { "" },
@@ -265,58 +254,52 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
pub unsafe fn dc_continue_key_transfer(
context: &Context,
msg_id: uint32_t,
msg_id: u32,
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 mut norm_sc: *mut libc::c_char = ptr::null_mut();
let norm_sc;
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()
|| !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 {
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
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;
}
} else {
error!(context, "Message is no Autocrypt Setup Message.");
return false;
}
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
@@ -368,7 +351,7 @@ fn set_self_key(
error!(context, "File does not contain a private key.",);
}
let self_addr = context.sql.get_config(context, "configured_addr");
let self_addr = context.get_config(Config::ConfiguredAddr);
if self_addr.is_none() {
error!(context, "Missing self addr");
@@ -410,8 +393,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: size_t = 0i32 as size_t;
let mut indx: size_t = 0i32 as size_t;
let mut binary_bytes: libc::size_t = 0;
let mut indx: libc::size_t = 0;
let mut payload: *mut libc::c_char = ptr::null_mut();
fc_buf = dc_strdup(filecontent);
@@ -495,10 +478,9 @@ 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) {
what = job.param.get_int(Param::Cmd).unwrap_or_default();
let 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());
@@ -507,7 +489,8 @@ 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::IMEX_PROGRESS, 10 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::ImexProgress(10));
if !context.sql.is_open() {
error!(context, "Import/export: Database not opened.",);
} else {
@@ -526,7 +509,7 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
if ok_to_continue {
match what {
1 => {
if 0 != export_self_keys(context, param1.as_ptr()) {
if export_self_keys(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
success = 1
}
@@ -538,13 +521,13 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
}
}
11 => {
if 0 != export_backup(context, param1.as_ptr()) {
if export_backup(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
success = 1
}
}
12 => {
if 0 != import_backup(context, param1.as_ptr()) {
if import_backup(context, param1.as_ptr()) {
info!(context, "Import/export completed.",);
success = 1
}
@@ -556,63 +539,48 @@ pub unsafe fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) {
}
dc_free_ongoing(context);
}
context.call_cb(
Event::IMEX_PROGRESS,
(if 0 != success { 1000 } else { 0 }) as uintptr_t,
0 as uintptr_t,
);
context.call_cb(Event::ImexProgress(if 0 != success { 1000 } else { 0 }));
}
/*******************************************************************************
* Import backup
******************************************************************************/
// TODO should return bool /rtn
#[allow(non_snake_case)]
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> libc::c_int {
unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char) -> bool {
info!(
context,
"Import \"{}\" to \"{}\".",
as_str(backup_to_import),
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap())
context.get_dbfile().display()
);
if dc_is_configured(context) {
error!(context, "Cannot import backups to accounts in use.");
return 0;
return false;
}
&context.sql.close(&context);
dc_delete_file(context, context.get_dbfile().unwrap());
if dc_file_exist(context, context.get_dbfile().unwrap()) {
dc_delete_file(context, context.get_dbfile());
if dc_file_exist(context, context.get_dbfile()) {
error!(
context,
"Cannot import backups: Cannot delete the old file.",
);
return 0;
return false;
}
if !dc_copy_file(
context,
as_path(backup_to_import),
context.get_dbfile().unwrap(),
) {
return 0;
if !dc_copy_file(context, as_path(backup_to_import), context.get_dbfile()) {
return false;
}
/* error already logged */
/* re-open copied database file */
if !context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0)
{
return 0;
if !context.sql.open(&context, &context.get_dbfile(), 0) {
return false;
}
let total_files_cnt = context
.sql
.query_row_col::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![], 0)
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
.unwrap_or_default() as usize;
info!(
context,
@@ -653,19 +621,19 @@ unsafe fn import_backup(context: &Context, backup_to_import: *const libc::c_char
if permille > 990 {
permille = 990
}
context.call_cb(Event::IMEX_PROGRESS, permille as uintptr_t, 0);
context.call_cb(Event::ImexProgress(permille));
if file_blob.is_empty() {
continue;
}
let pathNfilename = format!("{}/{}", as_str(context.get_blobdir()), file_name);
if dc_write_file_safe(context, &pathNfilename, &file_blob) {
let pathNfilename = context.get_blobdir().join(file_name);
if dc_write_file(context, &pathNfilename, &file_blob) {
continue;
}
error!(
context,
"Storage full? Cannot write file {} with {} bytes.",
&pathNfilename,
pathNfilename.display(),
file_blob.len(),
);
// otherwise the user may believe the stuff is imported correctly, but there are files missing ...
@@ -686,7 +654,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() as libc::c_int
.is_ok()
}
/*******************************************************************************
@@ -694,11 +662,10 @@ 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) -> libc::c_int {
unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> bool {
let mut ok_to_continue: bool;
let mut success: libc::c_int = 0;
let mut success = false;
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)
@@ -707,13 +674,8 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
let res = chrono::NaiveDateTime::from_timestamp(now as i64, 0)
.format("delta-chat-%Y-%m-%d.bak")
.to_string();
let buffer = CString::yolo(res);
let dest_pathNfilename = dc_get_fine_pathNfilename(context, dir, buffer.as_ptr());
if dest_pathNfilename.is_null() {
error!(context, "Cannot get backup file name.",);
return success;
}
let dest_path_filename = dc_get_fine_path_filename(context, as_path(dir), res);
sql::housekeeping(context);
@@ -723,25 +685,16 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
info!(
context,
"Backup \"{}\" to \"{}\".",
context
.get_dbfile()
.as_ref()
.map_or("<<None>>", |p| p.to_str().unwrap()),
as_str(dest_pathNfilename),
context.get_dbfile().display(),
dest_path_filename.display(),
);
if dc_copy_file(
context,
context.get_dbfile().unwrap(),
as_path(dest_pathNfilename),
) {
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
if dc_copy_file(context, context.get_dbfile(), &dest_path_filename) {
context.sql.open(&context, &context.get_dbfile(), 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*/
let sql = Sql::new();
if sql.open(context, as_path(dest_pathNfilename), 0) {
if sql.open(context, &dest_path_filename, 0) {
if !sql.table_exists("backup_blobs") {
if sql::execute(
context,
@@ -761,14 +714,14 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
}
if ok_to_continue {
let mut total_files_cnt = 0;
let dir = std::path::Path::new(as_str(context.get_blobdir()));
if let Ok(dir_handle) = std::fs::read_dir(dir) {
let dir = 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, _| {
@@ -799,11 +752,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
if permille > 990 {
permille = 990;
}
context.call_cb(
Event::IMEX_PROGRESS,
permille as uintptr_t,
0 as uintptr_t,
);
context.call_cb(Event::ImexProgress(permille));
let name_f = entry.file_name();
let name = name_f.to_string_lossy();
@@ -812,12 +761,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
continue;
} else {
info!(context, "EXPORTing filename={}", name);
let curr_pathNfilename = format!(
"{}/{}",
as_str(context.get_blobdir()),
name
);
let curr_pathNfilename = context.get_blobdir().join(entry.file_name());
if let Some(buf) =
dc_read_file_safe(context, &curr_pathNfilename)
{
@@ -828,7 +772,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
error!(
context,
"Disk full? Cannot add file \"{}\" to backup.",
&curr_pathNfilename,
curr_pathNfilename.display(),
);
/* this is not recoverable! writing to the sqlite database should work! */
ok_to_continue = false;
@@ -847,7 +791,7 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
error!(
context,
"Backup: Cannot copy from blob-directory \"{}\".",
as_str(context.get_blobdir()),
context.get_blobdir().display(),
);
}
} else {
@@ -859,33 +803,26 @@ unsafe fn export_backup(context: &Context, dir: *const libc::c_char) -> libc::c_
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(
Event::IMEX_FILE_WRITTEN,
dest_pathNfilename as uintptr_t,
0,
);
success = 1;
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
success = true;
}
}
} else {
error!(
context,
"Backup: Cannot get info for blob-directory \"{}\".",
as_str(context.get_blobdir())
context.get_blobdir().display(),
);
};
}
}
}
if closed {
context
.sql
.open(&context, &context.get_dbfile().unwrap(), 0);
context.sql.open(&context, &context.get_dbfile(), 0);
}
if 0 != delete_dest_file {
dc_delete_file(context, as_path(dest_pathNfilename));
dc_delete_file(context, &dest_path_filename);
}
free(dest_pathNfilename as *mut libc::c_void);
success
}
@@ -902,10 +839,8 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
(currently, the last imported key is the standard key unless it contains the string "legacy" in its name) */
let mut imported_cnt: libc::c_int = 0;
let mut suffix: *mut libc::c_char = ptr::null_mut();
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: 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();
@@ -928,25 +863,21 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
{
continue;
}
free(path_plus_name as *mut libc::c_void);
path_plus_name = dc_mprintf(
b"%s/%s\x00" as *const u8 as *const libc::c_char,
dir_name,
name_c.as_ptr(),
);
info!(context, "Checking: {}", as_str(path_plus_name));
free(buf as *mut libc::c_void);
let path_plus_name = dir.join(entry.file_name());
info!(context, "Checking: {}", path_plus_name.display());
free(buf.cast());
buf = ptr::null_mut();
if 0 == dc_read_file(
context,
path_plus_name,
&mut buf as *mut *mut libc::c_char as *mut *mut libc::c_void,
&mut buf_bytes,
) || buf_bytes < 50
{
if let Some(buf_r) = dc_read_file_safe(context, &path_plus_name) {
buf = buf_r.as_ptr() as *mut _;
std::mem::forget(buf_r);
} else {
continue;
}
};
private_key = buf;
free(buf2 as *mut libc::c_void);
buf2 = dc_strdup(buf);
if dc_split_armored_data(
@@ -979,7 +910,7 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
info!(
context,
"Treating \"{}\" as a legacy private key.",
as_str(path_plus_name),
path_plus_name.display(),
);
set_default = 0i32
}
@@ -1005,15 +936,13 @@ unsafe fn import_self_keys(context: &Context, dir_name: *const libc::c_char) ->
}
free(suffix as *mut libc::c_void);
free(path_plus_name as *mut libc::c_void);
free(buf as *mut libc::c_void);
free(buf2 as *mut libc::c_void);
imported_cnt
}
// TODO should return bool /rtn
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc::c_int {
unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> bool {
let mut export_errors = 0;
context
@@ -1035,14 +964,14 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc:
for key_pair in keys {
let (id, public_key, private_key, is_default) = key_pair?;
if let Some(key) = public_key {
if 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
if 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 0 == export_key_to_asc_file(context, dir, id, &key, is_default) {
if export_key_to_asc_file(context, dir, id, &key, is_default) {
export_errors += 1;
}
} else {
@@ -1055,61 +984,45 @@ unsafe fn export_self_keys(context: &Context, dir: *const libc::c_char) -> libc:
)
.unwrap();
if export_errors == 0 {
1
} else {
0
}
export_errors == 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,
) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let file_name;
if 0 != is_default {
file_name = dc_mprintf(
b"%s/%s-key-default.asc\x00" as *const u8 as *const libc::c_char,
dir,
if key.is_public() {
b"public\x00" as *const u8 as *const libc::c_char
} else {
b"private\x00" as *const u8 as *const libc::c_char
},
)
} else {
file_name = dc_mprintf(
b"%s/%s-key-%i.asc\x00" as *const u8 as *const libc::c_char,
dir,
if key.is_public() {
b"public\x00" as *const u8 as *const libc::c_char
} else {
b"private\x00" as *const u8 as *const libc::c_char
},
id,
)
}
info!(context, "Exporting key {}", as_str(file_name),);
dc_delete_file(context, as_path(file_name));
if !key.write_asc_to_file(file_name, context) {
error!(context, "Cannot write key to {}", as_str(file_name),);
} else {
context.call_cb(
Event::IMEX_FILE_WRITTEN,
file_name as uintptr_t,
0i32 as uintptr_t,
) -> bool {
let mut success = false;
let dir = as_path(dir);
let file_name = if 0 != is_default {
let name = format!(
"{}-key-default.asc",
if key.is_public() { "public" } else { "private" },
);
success = 1i32
dir.join(name)
} else {
let name = format!(
"{}-key-{}.asc",
if key.is_public() { "public" } else { "private" },
id
);
dir.join(name)
};
info!(context, "Exporting key {}", file_name.display());
dc_delete_file(context, &file_name);
if !key.write_asc_to_file(&file_name, context) {
error!(context, "Cannot write key to {}", file_name.display());
} else {
context.call_cb(Event::ImexFileWritten(file_name.clone()));
success = true;
}
free(file_name as *mut libc::c_void);
success
}
@@ -1118,13 +1031,11 @@ 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(logging_cb));
let t = test_context(Some(Box::new(logging_cb)));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
@@ -1142,22 +1053,19 @@ mod tests {
assert!(msg.contains("-----END PGP MESSAGE-----\n"));
}
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)
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),
}
}
#[test]
fn test_render_setup_file_newline_replace() {
let t = test_context(Some(ac_setup_msg_cb));
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);

View File

@@ -1,7 +1,8 @@
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use chrono::TimeZone;
use mmime::clist::*;
use mmime::mailimf_types::*;
use mmime::mailimf_types_helper::*;
use mmime::mailmime_disposition::*;
@@ -14,7 +15,8 @@ use mmime::other::*;
use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{dc_get_version_str, Context};
use crate::context::{get_version_str, Context};
use crate::dc_mimeparser::SystemMessage;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
@@ -23,7 +25,6 @@ use crate::location;
use crate::message::*;
use crate::param::*;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
#[derive(Clone)]
@@ -31,7 +32,7 @@ use crate::x::*;
pub struct dc_mimefactory_t<'a> {
pub from_addr: *mut libc::c_char,
pub from_displayname: *mut libc::c_char,
pub selfstatus: *mut libc::c_char,
pub selfstatus: Option<String>,
pub recipients_names: *mut clist,
pub recipients_addr: *mut clist,
pub timestamp: i64,
@@ -39,14 +40,14 @@ pub struct dc_mimefactory_t<'a> {
pub loaded: dc_mimefactory_loaded_t,
pub msg: Message,
pub chat: Option<Chat>,
pub increation: libc::c_int,
pub increation: bool,
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: uint32_t,
pub out_last_added_location_id: u32,
pub error: *mut libc::c_char,
pub context: &'a Context,
}
@@ -56,7 +57,6 @@ impl<'a> Drop for dc_mimefactory_t<'a> {
unsafe {
free(self.from_addr as *mut libc::c_void);
free(self.from_displayname as *mut libc::c_void);
free(self.selfstatus as *mut libc::c_void);
free(self.rfc724_mid as *mut libc::c_void);
if !self.recipients_names.is_null() {
clist_free_content(self.recipients_names);
@@ -94,7 +94,7 @@ pub unsafe fn dc_mimefactory_load_msg(
let mut factory = dc_mimefactory_t {
from_addr: ptr::null_mut(),
from_displayname: ptr::null_mut(),
selfstatus: ptr::null_mut(),
selfstatus: None,
recipients_names: clist_new(),
recipients_addr: clist_new(),
timestamp: 0,
@@ -102,7 +102,7 @@ pub unsafe fn dc_mimefactory_load_msg(
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: Some(chat),
increation: 0,
increation: false,
in_reply_to: ptr::null_mut(),
references: ptr::null_mut(),
req_mdn: 0,
@@ -170,10 +170,10 @@ pub unsafe fn dc_mimefactory_load_msg(
)
.unwrap();
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
let command = factory.msg.param.get_cmd();
let msg = &factory.msg;
if command == 5 {
if command == SystemMessage::MemberRemovedFromGroup {
let email_to_remove = msg.param.get(Param::Arg).unwrap_or_default();
let email_to_remove_c = email_to_remove.strdup();
@@ -197,8 +197,8 @@ pub unsafe fn dc_mimefactory_load_msg(
}
}
}
if command != 6
&& command != 7
if command != SystemMessage::AutocryptSetupMessage
&& command != SystemMessage::SecurejoinMessage
&& 0 != context
.sql
.get_config_int(context, "mdns_enabled")
@@ -252,19 +252,21 @@ unsafe fn load_from(factory: &mut dc_mimefactory_t) {
.unwrap_or_default()
.strdup();
factory.selfstatus = context
.sql
.get_config(context, "selfstatus")
.unwrap_or_default()
.strdup();
if factory.selfstatus.is_null() {
factory.selfstatus = factory.context.stock_str(StockMessage::StatusLine).strdup();
};
factory.selfstatus = context.sql.get_config(context, "selfstatus");
if factory.selfstatus.is_none() {
factory.selfstatus = Some(
factory
.context
.stock_str(StockMessage::StatusLine)
.to_string(),
);
}
}
pub unsafe fn dc_mimefactory_load_mdn<'a>(
context: &'a Context,
msg_id: uint32_t,
msg_id: u32,
) -> Result<dc_mimefactory_t, Error> {
if 0 == context
.sql
@@ -282,7 +284,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
let mut factory = dc_mimefactory_t {
from_addr: ptr::null_mut(),
from_displayname: ptr::null_mut(),
selfstatus: ptr::null_mut(),
selfstatus: None,
recipients_names: clist_new(),
recipients_addr: clist_new(),
timestamp: 0,
@@ -290,7 +292,7 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
loaded: DC_MF_NOTHING_LOADED,
msg,
chat: None,
increation: 0,
increation: false,
in_reply_to: ptr::null_mut(),
references: ptr::null_mut(),
req_mdn: 0,
@@ -334,21 +336,16 @@ pub unsafe fn dc_mimefactory_load_mdn<'a>(
Ok(factory)
}
// TODO should return bool /rtn
pub unsafe fn dc_mimefactory_render(
context: &Context,
factory: &mut dc_mimefactory_t,
) -> libc::c_int {
pub unsafe fn dc_mimefactory_render(context: &Context, factory: &mut dc_mimefactory_t) -> bool {
let subject: *mut mailimf_subject;
let mut ok_to_continue = true;
let imf_fields: *mut mailimf_fields;
let mut message: *mut mailmime = ptr::null_mut();
let mut message_text: *mut libc::c_char = ptr::null_mut();
let mut message_text2: *mut libc::c_char = ptr::null_mut();
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: libc::c_int = 0;
let mut success = false;
let mut parts: libc::c_int = 0;
let mut e2ee_guaranteed: libc::c_int = 0;
let mut min_verified: libc::c_int = 0;
@@ -372,7 +369,7 @@ pub unsafe fn dc_mimefactory_render(
from,
mailimf_mailbox_new(
if !factory.from_displayname.is_null() {
dc_encode_header_words(factory.from_displayname)
dc_encode_header_words(as_str(factory.from_displayname))
} else {
ptr::null_mut()
},
@@ -406,7 +403,7 @@ pub unsafe fn dc_mimefactory_render(
MAILIMF_ADDRESS_MAILBOX as libc::c_int,
mailimf_mailbox_new(
if !name.is_null() {
dc_encode_header_words(name)
dc_encode_header_words(as_str(name))
} else {
ptr::null_mut()
},
@@ -461,20 +458,14 @@ pub unsafe fn dc_mimefactory_render(
.as_ref()
.map(|s| format!("/{}", s))
.unwrap_or_default();
let os_part = CString::new(os_part).expect("String -> CString conversion failed");
let version = dc_get_version_str();
let version = 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,
os_part.as_ptr(),
),
format!("Delta Chat Core {}{}", version, os_part).strdup(),
),
);
free(version.cast());
mailimf_fields_add(
imf_fields,
@@ -501,7 +492,8 @@ pub unsafe fn dc_mimefactory_render(
*********************************************************************/
let chat = factory.chat.as_ref().unwrap();
let mut meta_part: *mut mailmime = ptr::null_mut();
let mut placeholdertext: *mut libc::c_char = ptr::null_mut();
let mut placeholdertext = None;
if chat.typ == Chattype::VerifiedGroup {
mailimf_fields_add(
imf_fields,
@@ -534,7 +526,7 @@ pub unsafe fn dc_mimefactory_render(
}
/* build header etc. */
let command = factory.msg.param.get_int(Param::Cmd).unwrap_or_default();
let command = factory.msg.param.get_cmd();
if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
mailimf_fields_add(
imf_fields,
@@ -543,15 +535,15 @@ pub unsafe fn dc_mimefactory_render(
chat.grpid.strdup(),
),
);
let name = CString::yolo(chat.name.as_bytes());
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
strdup(b"Chat-Group-Name\x00" as *const u8 as *const libc::c_char),
dc_encode_header_words(name.as_ptr()),
dc_encode_header_words(&chat.name),
),
);
if command == DC_CMD_MEMBER_REMOVED_FROM_GROUP {
if command == SystemMessage::MemberRemovedFromGroup {
let email_to_remove = factory
.msg
.param
@@ -570,7 +562,7 @@ pub unsafe fn dc_mimefactory_render(
),
);
}
} else if command == DC_CMD_MEMBER_ADDED_TO_GROUP {
} else if command == SystemMessage::MemberAddedToGroup {
let msg = &factory.msg;
do_gossip = 1;
let email_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -601,7 +593,7 @@ pub unsafe fn dc_mimefactory_render(
),
);
}
} else if command == DC_CMD_GROUPNAME_CHANGED {
} else if command == SystemMessage::GroupNameChanged {
let msg = &factory.msg;
let value_to_add = msg.param.get(Param::Arg).unwrap_or_default().strdup();
@@ -614,7 +606,7 @@ pub unsafe fn dc_mimefactory_render(
value_to_add,
),
);
} else if command == DC_CMD_GROUPIMAGE_CHANGED {
} else if command == SystemMessage::GroupImageChanged {
let msg = &factory.msg;
grpimage = msg.param.get(Param::Arg);
if grpimage.is_none() {
@@ -628,7 +620,8 @@ pub unsafe fn dc_mimefactory_render(
}
}
}
if command == DC_CMD_LOCATION_STREAMING_ENABLED {
if command == SystemMessage::LocationStreamingEnabled {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -639,7 +632,7 @@ pub unsafe fn dc_mimefactory_render(
),
);
}
if command == DC_CMD_AUTOCRYPT_SETUP_MESSAGE {
if command == SystemMessage::AutocryptSetupMessage {
mailimf_fields_add(
imf_fields,
mailimf_field_new_custom(
@@ -647,12 +640,14 @@ pub unsafe fn dc_mimefactory_render(
strdup(b"v1\x00" as *const u8 as *const libc::c_char),
),
);
placeholdertext = factory
.context
.stock_str(StockMessage::AcSetupMsgBody)
.strdup();
placeholdertext = Some(
factory
.context
.stock_str(StockMessage::AcSetupMsgBody)
.to_string(),
);
}
if command == DC_CMD_SECUREJOIN_MESSAGE {
if command == SystemMessage::SecurejoinMessage {
let msg = &factory.msg;
let step = msg.param.get(Param::Arg).unwrap_or_default().strdup();
if strlen(step) > 0 {
@@ -726,6 +721,7 @@ pub unsafe fn dc_mimefactory_render(
}
}
}
if let Some(grpimage) = grpimage {
info!(factory.context, "setting group image '{}'", grpimage);
let mut meta = dc_msg_new_untyped();
@@ -773,78 +769,68 @@ pub unsafe fn dc_mimefactory_render(
imf_fields,
mailimf_field_new_custom(
strdup(b"Chat-Duration\x00" as *const u8 as *const libc::c_char),
dc_mprintf(
b"%i\x00" as *const u8 as *const libc::c_char,
duration_ms as libc::c_int,
),
duration_ms.to_string().strdup(),
),
);
}
}
afwd_email = factory.msg.param.exists(Param::Forwarded) as libc::c_int;
let mut fwdhint = ptr::null_mut();
if 0 != afwd_email {
fwdhint = dc_strdup(
b"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n\x00"
as *const u8 as *const libc::c_char,
let fwdhint = if 0 != afwd_email {
Some(
"---------- Forwarded message ----------\r\nFrom: Delta Chat\r\n\r\n"
.to_string(),
)
}
} else {
None
};
let final_text = {
if !placeholdertext.is_null() {
to_string(placeholdertext)
if let Some(ref text) = placeholdertext {
text
} else if let Some(ref text) = factory.msg.text {
text.clone()
text
} else {
"".into()
""
}
};
let final_text = CString::yolo(final_text);
let footer: *mut libc::c_char = factory.selfstatus;
message_text = dc_mprintf(
b"%s%s%s%s%s\x00" as *const u8 as *const libc::c_char,
if !fwdhint.is_null() {
fwdhint
let footer = factory.selfstatus.as_ref();
message_text = format!(
"{}{}{}{}{}",
if let Some(ref hint) = fwdhint {
hint
} else {
b"\x00" as *const u8 as *const libc::c_char
""
},
final_text.as_ptr(),
if final_text != CString::yolo("")
&& !footer.is_null()
&& 0 != *footer.offset(0isize) as libc::c_int
{
b"\r\n\r\n\x00" as *const u8 as *const libc::c_char
&final_text,
if !final_text.is_empty() && footer.is_some() {
"\r\n\r\n"
} else {
b"\x00" as *const u8 as *const libc::c_char
""
},
if !footer.is_null() && 0 != *footer.offset(0isize) as libc::c_int {
b"-- \r\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !footer.is_null() && 0 != *footer.offset(0isize) as libc::c_int {
if footer.is_some() { "-- \r\n" } else { "" },
if let Some(footer) = footer {
footer
} else {
b"\x00" as *const u8 as *const libc::c_char
""
},
);
)
.strdup();
let text_part: *mut mailmime = build_body_text(message_text);
mailmime_smart_add_part(message, text_part);
parts += 1;
free(fwdhint as *mut libc::c_void);
free(placeholdertext as *mut libc::c_void);
/* add attachment part */
if chat::msgtype_has_file(factory.msg.type_0) {
if !is_file_size_okay(context, &factory.msg) {
let error: *mut libc::c_char = dc_mprintf(
b"Message exceeds the recommended %i MB.\x00" as *const u8
as *const libc::c_char,
let error = format!(
"Message exceeds the recommended {} MB.",
24 * 1024 * 1024 / 4 * 3 / 1000 / 1000,
);
)
.strdup();
set_error(factory, error);
free(error as *mut libc::c_void);
free(error.cast());
ok_to_continue = false;
} else {
let file_part: *mut mailmime =
@@ -959,16 +945,15 @@ pub unsafe fn dc_mimefactory_render(
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 = 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,
factory.from_addr, factory.from_addr,
factory.msg.rfc724_mid
);
free(version.cast());
let version = get_version_str();
message_text2 = format!(
"Reporting-UA: Delta Chat {}\r\nOriginal-Recipient: rfc822;{}\r\nFinal-Recipient: rfc822;{}\r\nOriginal-Message-ID: <{}>\r\nDisposition: manual-action/MDN-sent-automatically; displayed\r\n",
version,
as_str(factory.from_addr),
as_str(factory.from_addr),
as_str(factory.msg.rfc724_mid)
).strdup();
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,
);
@@ -987,17 +972,19 @@ pub unsafe fn dc_mimefactory_render(
}
if ok_to_continue {
if factory.loaded as libc::c_uint == DC_MF_MDN_LOADED as libc::c_int as libc::c_uint {
let e = CString::new(factory.context.stock_str(StockMessage::ReadRcpt).as_ref())
.unwrap();
subject_str = dc_mprintf(
b"Chat: %s\x00" as *const u8 as *const libc::c_char,
e.as_ptr(),
);
let subject_str = if factory.loaded as libc::c_uint == DC_MF_MDN_LOADED as libc::c_uint
{
let e = factory.context.stock_str(StockMessage::ReadRcpt);
format!("Chat: {}", e)
} else {
subject_str =
get_subject(context, factory.chat.as_ref(), &mut factory.msg, afwd_email)
}
to_string(get_subject(
context,
factory.chat.as_ref(),
&mut factory.msg,
afwd_email,
))
};
subject = mailimf_subject_new(dc_encode_header_words(subject_str));
mailimf_fields_add(
imf_fields,
@@ -1046,7 +1033,7 @@ pub unsafe fn dc_mimefactory_render(
}
factory.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
mailmime_write_mem(factory.out, &mut col, message);
success = 1;
success = true;
}
}
@@ -1056,7 +1043,6 @@ pub unsafe fn dc_mimefactory_render(
e2ee_helper.thanks();
free(message_text as *mut libc::c_void);
free(message_text2 as *mut libc::c_void);
free(subject_str as *mut libc::c_void);
success
}
@@ -1074,34 +1060,17 @@ unsafe fn get_subject(
let chat = chat.unwrap();
let ret: *mut libc::c_char;
let raw_subject = {
dc_msg_get_summarytext_by_raw(msg.type_0, msg.text.as_ref(), &mut msg.param, 32, context)
.strdup()
};
let raw_subject =
dc_msg_get_summarytext_by_raw(msg.type_0, msg.text.as_ref(), &mut msg.param, 32, context);
let fwd = if 0 != afwd_email {
b"Fwd: \x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
};
if msg.param.get_int(Param::Cmd).unwrap_or_default() == DC_CMD_AUTOCRYPT_SETUP_MESSAGE {
let fwd = if 0 != afwd_email { "Fwd: " } else { "" };
if msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
ret = context.stock_str(StockMessage::AcSetupMsgSubject).strdup()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
ret = format!(
"Chat: {}: {}{}",
chat.name,
to_string(fwd),
to_string(raw_subject),
)
.strdup()
ret = format!("Chat: {}: {}{}", chat.name, fwd, raw_subject,).strdup();
} else {
ret = dc_mprintf(
b"Chat: %s%s\x00" as *const u8 as *const libc::c_char,
fwd,
raw_subject,
)
ret = format!("Chat: {}{}", fwd, raw_subject).strdup();
}
free(raw_subject as *mut libc::c_void);
ret
}
@@ -1153,13 +1122,12 @@ unsafe fn build_body_file(
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
let mut filename_to_send = ptr::null_mut();
let mut filename_encoded = ptr::null_mut();
if let Some(ref path_filename) = path_filename {
let suffix = dc_get_filesuffix_lc(path_filename);
if msg.type_0 == Viewtype::Voice {
let filename_to_send = if msg.type_0 == Viewtype::Voice {
let ts = chrono::Utc.timestamp(msg.timestamp_sort as i64, 0);
let suffix = if !suffix.is_null() {
@@ -1167,37 +1135,42 @@ unsafe fn build_body_file(
} else {
"dat".into()
};
let res = ts
.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", suffix))
.to_string();
filename_to_send = res.strdup();
ts.format(&format!("voice-message_%Y-%m-%d_%H-%M-%S.{}", suffix))
.to_string()
} else if msg.type_0 == Viewtype::Audio {
filename_to_send = dc_get_filename(path_filename)
Path::new(path_filename)
.file_name()
.map(|c| c.to_string_lossy().to_string())
.unwrap_or_default()
} else if msg.type_0 == Viewtype::Image || msg.type_0 == Viewtype::Gif {
if base_name.is_null() {
base_name = b"image\x00" as *const u8 as *const libc::c_char
}
filename_to_send = dc_mprintf(
b"%s.%s\x00" as *const u8 as *const libc::c_char,
base_name,
format!(
"{}.{}",
as_str(base_name),
if !suffix.is_null() {
suffix
as_str(suffix)
} else {
b"dat\x00" as *const u8 as *const libc::c_char
"dat"
},
)
} else if msg.type_0 == Viewtype::Video {
filename_to_send = dc_mprintf(
b"video.%s\x00" as *const u8 as *const libc::c_char,
format!(
"video.{}",
if !suffix.is_null() {
suffix
as_str(suffix)
} else {
b"dat\x00" as *const u8 as *const libc::c_char
"dat"
},
)
} else {
filename_to_send = dc_get_filename(path_filename)
}
Path::new(path_filename)
.file_name()
.map(|c| c.to_string_lossy().to_string())
.unwrap_or_default()
};
if mimetype.is_null() {
if suffix.is_null() {
mimetype =
@@ -1220,13 +1193,13 @@ unsafe fn build_body_file(
/* create mime part, for Content-Disposition, see RFC 2183.
`Content-Disposition: attachment` seems not to make a difference to `Content-Disposition: inline` at least on tested Thunderbird and Gma'l in 2017.
But I've heard about problems with inline and outl'k, so we just use the attachment-type until we run into other problems ... */
needs_ext = dc_needs_ext_header(as_str(filename_to_send));
needs_ext = dc_needs_ext_header(&filename_to_send);
mime_fields = mailmime_fields_new_filename(
MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int,
if needs_ext {
ptr::null_mut()
} else {
dc_strdup(filename_to_send)
filename_to_send.strdup()
},
MAILMIME_MECHANISM_BASE64 as libc::c_int,
);
@@ -1252,12 +1225,12 @@ unsafe fn build_body_file(
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
0 as size_t,
0 as libc::size_t,
mailmime_parameter_new(
strdup(
b"filename*\x00" as *const u8 as *const libc::c_char,
),
dc_encode_ext_header(as_str(filename_to_send)).strdup(),
dc_encode_ext_header(&filename_to_send).strdup(),
),
);
if !parm.is_null() {
@@ -1279,7 +1252,7 @@ unsafe fn build_body_file(
}
}
content = mailmime_content_new_with_str(mimetype);
filename_encoded = dc_encode_header_words(filename_to_send);
filename_encoded = dc_encode_header_words(&filename_to_send);
clist_insert_after(
(*content).ct_parameters,
(*(*content).ct_parameters).last,
@@ -1289,15 +1262,17 @@ unsafe fn build_body_file(
) as *mut libc::c_void,
);
mime_sub = mailmime_new_empty(content, mime_fields);
mailmime_set_body_file(mime_sub, dc_get_abs_path(context, path_filename));
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()));
if !ret_file_name_as_sent.is_null() {
*ret_file_name_as_sent = dc_strdup(filename_to_send)
*ret_file_name_as_sent = filename_to_send.strdup();
}
}
}
free(mimetype as *mut libc::c_void);
free(filename_to_send as *mut libc::c_void);
free(filename_encoded as *mut libc::c_void);
mime_sub

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -211,6 +211,16 @@ fn is_plain_quote(buf: &str) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
// proptest does not support [[:graphical:][:space:]] regex.
fn test_simplify_plain_text_fuzzy(input in "[!-~\t \n]+") {
let output = Simplify::new().simplify_plain_text(&input, true);
assert!(output.split('\n').all(|s| s != "-- "));
}
}
#[test]
fn test_simplify_trim() {

View File

@@ -9,7 +9,6 @@ use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/**
@@ -26,12 +25,15 @@ use crate::x::*;
* @return Returns the encoded string which must be free()'d when no longed needed.
* On errors, NULL is returned.
*/
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> *mut libc::c_char {
let to_encode =
CString::new(to_encode_r.as_ref().as_bytes()).expect("invalid cstring to_encode");
let mut ok_to_continue = true;
let mut ret_str: *mut libc::c_char = ptr::null_mut();
let mut cur: *const libc::c_char = to_encode;
let mut cur: *const libc::c_char = to_encode.as_ptr();
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if to_encode.is_null() || mmapstr.is_null() {
if mmapstr.is_null() {
ok_to_continue = false;
}
loop {
@@ -66,7 +68,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 size_t,
end.wrapping_offset_from(begin) as libc::size_t,
) {
ok_to_continue = false;
continue;
@@ -82,7 +84,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 size_t,
cur.wrapping_offset_from(end) as libc::size_t,
)
.is_null()
{
@@ -93,7 +95,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 size_t,
cur.wrapping_offset_from(begin) as libc::size_t,
)
.is_null()
{
@@ -122,10 +124,10 @@ unsafe fn quote_word(
display_charset: *const libc::c_char,
mmapstr: *mut MMAPString,
word: *const libc::c_char,
size: size_t,
size: libc::size_t,
) -> bool {
let mut cur: *const libc::c_char;
let mut i: size_t = 0i32 as size_t;
let mut i = 0;
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() {
@@ -188,7 +190,7 @@ unsafe fn get_word(
{
cur = cur.offset(1isize)
}
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as size_t);
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
*pend = cur;
}
@@ -197,9 +199,9 @@ unsafe fn get_word(
******************************************************************************/
/* see comment below */
unsafe fn to_be_quoted(word: *const libc::c_char, size: size_t) -> bool {
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
let mut cur: *const libc::c_char = word;
let mut i: size_t = 0i32 as size_t;
let mut i = 0;
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
@@ -222,7 +224,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: size_t = 0i32 as size_t;
let mut cur_token = 0;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
in_0,
@@ -356,12 +358,13 @@ mod tests {
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
free(buf1 as *mut libc::c_void);
buf1 = dc_encode_header_words(b"abcdef\x00" as *const u8 as *const libc::c_char);
buf1 = dc_encode_header_words("abcdef");
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "abcdef");
free(buf1 as *mut libc::c_void);
buf1 = dc_encode_header_words(
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char,
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
.unwrap(),
);
assert_eq!(
strncmp(buf1, b"=?utf-8\x00" as *const u8 as *const libc::c_char, 7),

View File

@@ -1,25 +1,23 @@
//! Some tools and enhancements to the used libraries, there should be
//! no references to Context and other "larger" entities here.
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::path::Path;
use std::ffi::{CStr, CString, OsString};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::SystemTime;
use std::{fmt, fs, ptr};
use chrono::{Local, TimeZone};
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 **********************************************************/
/* math tools */
pub fn dc_exactly_one_bit_set(v: libc::c_int) -> bool {
0 != v && 0 == v & (v - 1)
}
@@ -98,7 +96,7 @@ pub unsafe fn dc_str_replace(
}
unsafe fn dc_ltrim(buf: *mut libc::c_char) {
let mut len: size_t;
let mut len: libc::size_t;
let mut cur: *const libc::c_uchar;
if !buf.is_null() && 0 != *buf as libc::c_int {
len = strlen(buf);
@@ -118,7 +116,7 @@ unsafe fn dc_ltrim(buf: *mut libc::c_char) {
}
unsafe fn dc_rtrim(buf: *mut libc::c_char) {
let mut len: size_t;
let mut len: libc::size_t;
let mut cur: *mut libc::c_uchar;
if !buf.is_null() && 0 != *buf as libc::c_int {
len = strlen(buf);
@@ -301,9 +299,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: size_t = (p1 as uintptr_t).wrapping_sub(buf as uintptr_t) as size_t;
let used_bytes = (p1 as uintptr_t).wrapping_sub(buf as uintptr_t) as libc::size_t;
if dc_utf8_strnlen(buf, used_bytes) >= approx_characters as usize {
let buf_bytes: size_t = strlen(buf);
let buf_bytes = strlen(buf);
if buf_bytes.wrapping_sub(used_bytes) >= strlen(ellipse_utf8) {
strcpy(p1 as *mut libc::c_char, ellipse_utf8);
}
@@ -324,12 +322,12 @@ pub unsafe fn dc_truncate_n_unwrap_str(
};
}
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: size_t) -> size_t {
unsafe fn dc_utf8_strnlen(s: *const libc::c_char, n: libc::size_t) -> libc::size_t {
if s.is_null() {
return 0;
}
let mut j: size_t = 0;
let mut j: libc::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 +541,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: [uint32_t; 3] = [rng.gen(), rng.gen(), rng.gen()];
let buf: [u32; 3] = [rng.gen(), rng.gen(), rng.gen()];
encode_66bits_as_base64(buf[0usize], buf[1usize], buf[2usize])
}
@@ -570,7 +568,7 @@ fn encode_66bits_as_base64(v1: u32, v2: u32, fill: u32) -> String {
String::from_utf8(wrapped_writer).unwrap()
}
pub unsafe fn dc_create_incoming_rfc724_mid(
pub fn dc_create_incoming_rfc724_mid(
message_timestamp: i64,
contact_id_from: u32,
contact_ids_to: &Vec<u32>,
@@ -579,51 +577,36 @@ pub unsafe fn dc_create_incoming_rfc724_mid(
return ptr::null_mut();
}
/* find out the largest receiver ID (we could also take the smallest, but it should be unique) */
let largest_id_to = max(contact_ids_to.iter());
let largest_id_to = contact_ids_to.iter().max().copied().unwrap_or_default();
dc_mprintf(
b"%lu-%lu-%lu@stub\x00" as *const u8 as *const libc::c_char,
message_timestamp as libc::c_ulong,
contact_id_from as libc::c_ulong,
*largest_id_to.unwrap() as libc::c_ulong,
)
let result = format!(
"{}-{}-{}@stub",
message_timestamp, contact_id_from, largest_id_to
);
unsafe { result.strdup() }
}
/// Function generates a Message-ID that can be used for a new outgoing message.
/// - this function is called for all outgoing messages.
/// - the message ID should be globally unique
/// - do not add a counter or any private data as as this may give unneeded information to the receiver
pub unsafe fn dc_create_outgoing_rfc724_mid(
grpid: *const libc::c_char,
from_addr: *const libc::c_char,
) -> *mut libc::c_char {
/* Function generates a Message-ID that can be used for a new outgoing message.
- this function is called for all outgoing messages.
- the message ID should be globally unique
- do not add a counter or any private data as as this may give unneeded information to the receiver */
let mut rand1: *mut libc::c_char = ptr::null_mut();
let rand2: *mut libc::c_char = dc_create_id().strdup();
let ret: *mut libc::c_char;
let mut at_hostname: *const libc::c_char = strchr(from_addr, '@' as i32);
if at_hostname.is_null() {
at_hostname = b"@nohost\x00" as *const u8 as *const libc::c_char
}
if !grpid.is_null() {
ret = dc_mprintf(
b"Gr.%s.%s%s\x00" as *const u8 as *const libc::c_char,
grpid,
rand2,
at_hostname,
)
} else {
rand1 = dc_create_id().strdup();
ret = dc_mprintf(
b"Mr.%s.%s%s\x00" as *const u8 as *const libc::c_char,
rand1,
rand2,
at_hostname,
)
}
free(rand1 as *mut libc::c_void);
free(rand2 as *mut libc::c_void);
let rand2 = dc_create_id();
ret
let at_hostname = as_opt_str(strchr(from_addr, '@' as i32)).unwrap_or_else(|| "@nohost");
let ret = if !grpid.is_null() {
format!("Gr.{}.{}{}", as_str(grpid), rand2, at_hostname,)
} else {
let rand1 = dc_create_id();
format!("Mr.{}.{}{}", rand1, rand2, at_hostname)
};
ret.strdup()
}
/// Generate globally-unique message-id for a new outgoing message.
@@ -696,16 +679,6 @@ pub unsafe fn dc_extract_grpid_from_rfc724_mid_list(list: *const clist) -> *mut
ptr::null_mut()
}
#[allow(non_snake_case)]
unsafe fn dc_ensure_no_slash(pathNfilename: *mut libc::c_char) {
let path_len = strlen(pathNfilename);
if path_len > 0 && *pathNfilename.add(path_len - 1) as libc::c_int == '/' as i32
|| *pathNfilename.add(path_len - 1) as libc::c_int == '\\' as i32
{
*pathNfilename.add(path_len - 1) = 0 as libc::c_char;
}
}
pub fn dc_ensure_no_slash_safe(path: &str) -> &str {
if path.ends_with('/') || path.ends_with('\\') {
return &path[..path.len() - 1];
@@ -713,18 +686,12 @@ pub fn dc_ensure_no_slash_safe(path: &str) -> &str {
path
}
unsafe fn dc_validate_filename(filename: *mut libc::c_char) {
/* function modifies the given buffer and replaces all characters not valid in filenames by a "-" */
let mut p1: *mut libc::c_char = filename;
while 0 != *p1 {
if *p1 as libc::c_int == '/' as i32
|| *p1 as libc::c_int == '\\' as i32
|| *p1 as libc::c_int == ':' as i32
{
*p1 = '-' as i32 as libc::c_char
}
p1 = p1.offset(1isize)
}
/// Function modifies the given buffer and replaces all characters not valid in filenames by a "-".
fn validate_filename(filename: &str) -> String {
filename
.replace('/', "-")
.replace('\\', "-")
.replace(':', "-")
}
pub unsafe fn dc_get_filename(path_filename: impl AsRef<str>) -> *mut libc::c_char {
@@ -735,42 +702,6 @@ pub unsafe fn dc_get_filename(path_filename: impl AsRef<str>) -> *mut libc::c_ch
}
}
// the case of the suffix is preserved
#[allow(non_snake_case)]
unsafe fn dc_split_filename(
pathNfilename: *const libc::c_char,
ret_basename: *mut *mut libc::c_char,
ret_all_suffixes_incl_dot: *mut *mut libc::c_char,
) {
if pathNfilename.is_null() {
return;
}
/* splits a filename into basename and all suffixes, eg. "/path/foo.tar.gz" is split into "foo.tar" and ".gz",
(we use the _last_ dot which allows the usage inside the filename which are very usual;
maybe the detection could be more intelligent, however, for the moment, it is just file)
- if there is no suffix, the returned suffix string is empty, eg. "/path/foobar" is split into "foobar" and ""
- the case of the returned suffix is preserved; this is to allow reconstruction of (similar) names */
let basename: *mut libc::c_char = dc_get_filename(as_str(pathNfilename));
let suffix: *mut libc::c_char;
let p1: *mut libc::c_char = strrchr(basename, '.' as i32);
if !p1.is_null() {
suffix = dc_strdup(p1);
*p1 = 0 as libc::c_char
} else {
suffix = dc_strdup(ptr::null())
}
if !ret_basename.is_null() {
*ret_basename = basename
} else {
free(basename as *mut libc::c_void);
}
if !ret_all_suffixes_incl_dot.is_null() {
*ret_all_suffixes_incl_dot = suffix
} else {
free(suffix as *mut libc::c_void);
};
}
// the returned suffix is lower-case
#[allow(non_snake_case)]
pub unsafe fn dc_get_filesuffix_lc(path_filename: impl AsRef<str>) -> *mut libc::c_char {
@@ -792,57 +723,32 @@ 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_safe<P: AsRef<std::path::Path>>(
pub fn dc_get_abs_path<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") {
assert!(
context.has_blobdir(),
"Expected context to have blobdir to substitute $BLOBDIR",
);
std::path::PathBuf::from(as_str(context.get_blobdir())).join(p)
context.get_blobdir().join(p)
} else {
p.into()
}
}
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_file_exist(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
dc_get_abs_path_safe(context, &path).exists()
dc_get_abs_path(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);
pub fn dc_get_filebytes(context: &Context, path: impl AsRef<std::path::Path>) -> u64 {
let path_abs = dc_get_abs_path(context, &path);
match fs::metadata(&path_abs) {
Ok(meta) => meta.len() as uint64_t,
Ok(meta) => meta.len() as u64,
Err(_err) => 0,
}
}
pub fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path>) -> bool {
let path_abs = dc_get_abs_path_safe(context, &path);
let path_abs = dc_get_abs_path(context, &path);
if !path_abs.is_file() {
warn!(
context,
@@ -866,8 +772,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_safe(context, &src);
let dest_abs = dc_get_abs_path_safe(context, &dest);
let src_abs = dc_get_abs_path(context, &src);
let dest_abs = dc_get_abs_path(context, &dest);
match fs::copy(&src_abs, &dest_abs) {
Ok(_) => true,
Err(_) => {
@@ -883,7 +789,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_safe(context, &path);
let path_abs = dc_get_abs_path(context, &path);
if !path_abs.exists() {
match fs::create_dir_all(path_abs) {
Ok(_) => true,
@@ -901,24 +807,9 @@ pub fn dc_create_folder(context: &Context, path: impl AsRef<std::path::Path>) ->
}
}
#[allow(non_snake_case)]
pub unsafe fn dc_write_file(
context: &Context,
pathNfilename: *const libc::c_char,
buf: *const libc::c_void,
buf_bytes: size_t,
) -> libc::c_int {
let bytes = std::slice::from_raw_parts(buf as *const u8, buf_bytes);
dc_write_file_safe(context, as_str(pathNfilename), bytes) as libc::c_int
}
pub fn dc_write_file_safe<P: AsRef<std::path::Path>>(
context: &Context,
path: P,
buf: &[u8],
) -> bool {
let path_abs = dc_get_abs_path_safe(context, &path);
/// Write a the given content to provied file path.
pub fn dc_write_file(context: &Context, path: impl AsRef<Path>, buf: &[u8]) -> bool {
let path_abs = dc_get_abs_path(context, &path);
if let Err(_err) = fs::write(&path_abs, buf) {
warn!(
context,
@@ -937,7 +828,7 @@ pub unsafe fn dc_read_file(
context: &Context,
pathNfilename: *const libc::c_char,
buf: *mut *mut libc::c_void,
buf_bytes: *mut size_t,
buf_bytes: *mut libc::size_t,
) -> libc::c_int {
if pathNfilename.is_null() {
return 0;
@@ -953,7 +844,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_safe(context, &path);
let path_abs = dc_get_abs_path(context, &path);
match fs::read(&path_abs) {
Ok(bytes) => Some(bytes),
Err(_err) => {
@@ -967,104 +858,83 @@ pub fn dc_read_file_safe<P: AsRef<std::path::Path>>(context: &Context, path: P)
}
}
#[allow(non_snake_case)]
pub unsafe fn dc_get_fine_pathNfilename(
pub fn dc_get_fine_path_filename(
context: &Context,
pathNfolder: *const libc::c_char,
desired_filenameNsuffix__: *const libc::c_char,
) -> *mut libc::c_char {
let mut ret: *mut libc::c_char = ptr::null_mut();
let pathNfolder_wo_slash: *mut libc::c_char;
let filenameNsuffix: *mut libc::c_char;
let mut basename: *mut libc::c_char = ptr::null_mut();
let mut dotNSuffix: *mut libc::c_char = ptr::null_mut();
folder: impl AsRef<Path>,
desired_filename_suffix: impl AsRef<str>,
) -> PathBuf {
let now = time();
pathNfolder_wo_slash = dc_strdup(pathNfolder);
dc_ensure_no_slash(pathNfolder_wo_slash);
filenameNsuffix = dc_strdup(desired_filenameNsuffix__);
dc_validate_filename(filenameNsuffix);
dc_split_filename(filenameNsuffix, &mut basename, &mut dotNSuffix);
let folder = PathBuf::from(folder.as_ref());
let suffix = validate_filename(desired_filename_suffix.as_ref());
for i in 0..1000i64 {
/*no deadlocks, please*/
if 0 != i {
let idx = if i < 100 { i } else { now + i };
ret = dc_mprintf(
b"%s/%s-%lu%s\x00" as *const u8 as *const libc::c_char,
pathNfolder_wo_slash,
basename,
idx as libc::c_ulong,
dotNSuffix,
)
let file_name = PathBuf::from(suffix);
let extension = file_name.extension().map(|c| c.clone());
for i in 0..100_000 {
let ret = if i == 0 {
let mut folder = folder.clone();
folder.push(&file_name);
folder
} else {
ret = dc_mprintf(
b"%s/%s%s\x00" as *const u8 as *const libc::c_char,
pathNfolder_wo_slash,
basename,
dotNSuffix,
)
let idx = if i < 100 { i } else { now + i };
let file_name = if let Some(stem) = file_name.file_stem() {
let mut stem = stem.to_os_string();
stem.push(format!("-{}", idx));
stem
} else {
OsString::from(idx.to_string())
};
let mut folder = folder.clone();
folder.push(file_name);
if let Some(ext) = extension {
folder.set_extension(&ext);
}
folder
};
if !dc_file_exist(context, &ret) {
// fine filename found
return ret;
}
if !dc_file_exist(context, as_path(ret)) {
/* fine filename found */
break;
}
free(ret as *mut libc::c_void);
ret = ptr::null_mut();
}
free(filenameNsuffix as *mut libc::c_void);
free(basename as *mut libc::c_void);
free(dotNSuffix as *mut libc::c_void);
free(pathNfolder_wo_slash as *mut libc::c_void);
ret
panic!("Something is really wrong, you need to clean up your disk");
}
pub fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
path.as_ref().starts_with(as_str(context.get_blobdir()))
context
.get_blobdir()
.to_str()
.map(|s| path.as_ref().starts_with(s))
.unwrap_or_default()
|| path.as_ref().starts_with("$BLOBDIR")
}
fn dc_make_rel_path(context: &Context, path: &mut String) {
if path.starts_with(as_str(context.get_blobdir())) {
*path = path.replace("$BLOBDIR", as_str(context.get_blobdir()));
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());
}
}
pub fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool {
let mut success = false;
let mut filename = ptr::null_mut();
let mut blobdir_path = ptr::null_mut();
if dc_is_blobdir_path(context, &path) {
dc_make_rel_path(context, path);
success = true;
} else {
filename = unsafe { dc_get_filename(&path) };
if !(filename.is_null()
|| {
blobdir_path = unsafe {
dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
filename,
)
};
blobdir_path.is_null()
}
|| !dc_copy_file(context, &path, as_path(blobdir_path)))
{
*path = to_string(blobdir_path);
blobdir_path = ptr::null_mut();
dc_make_rel_path(context, path);
success = true;
}
return true;
}
unsafe {
free(blobdir_path.cast());
free(filename.cast());
let blobdir_path = dc_get_fine_path_filename(context, "$BLOBDIR", &path);
if dc_copy_file(context, &path, &blobdir_path) {
*path = blobdir_path.to_string_lossy().to_string();
dc_make_rel_path(context, path);
return true;
}
success
false
}
/// Error type for the [OsStrExt] trait
@@ -1939,4 +1809,14 @@ mod tests {
}
}
}
#[test]
fn test_dc_create_incoming_rfc724_mid() {
let res = dc_create_incoming_rfc724_mid(123, 45, &vec![6, 7]);
assert_eq!(as_str(res), "123-45-7@stub");
unsafe {
free(res.cast());
}
}
}

View File

@@ -30,10 +30,9 @@ use crate::keyring::*;
use crate::peerstate::*;
use crate::pgp::*;
use crate::securejoin::handle_degrade_event;
use crate::types::*;
use crate::x::*;
#[derive(Default)]
#[derive(Debug, Default)]
pub struct E2eeHelper {
pub encryption_successfull: bool,
cdata_to_free: Option<Box<dyn Any>>,
@@ -87,7 +86,7 @@ impl E2eeHelper {
EncryptPreference::NoPreference
};
let addr = context.sql.get_config(context, "configured_addr");
let addr = context.get_config(Config::ConfiguredAddr);
if let Some(addr) = addr {
let pubkey_ret = load_or_generate_self_public_key(context, &addr).map_err(|err| {
@@ -166,7 +165,7 @@ impl E2eeHelper {
let message_to_encrypt: *mut mailmime = mailmime_new(
MAILMIME_MESSAGE as libc::c_int,
ptr::null(),
0i32 as size_t,
0 as libc::size_t,
mailmime_fields_new_empty(),
mailmime_get_content_message(),
ptr::null_mut(),
@@ -312,7 +311,7 @@ impl E2eeHelper {
/* create MIME-structure that will contain the encrypted text */
let mut encrypted_part: *mut mailmime = new_data_part(
ptr::null_mut(),
0i32 as size_t,
0 as libc::size_t,
b"multipart/encrypted\x00" as *const u8
as *const libc::c_char
as *mut libc::c_char,
@@ -387,16 +386,17 @@ 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: *mut libc::c_char = ptr::null_mut();
let mut from = None;
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: *mut mailimf_field =
mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
let mut 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,28 +408,32 @@ impl E2eeHelper {
}
}
let mut peerstate = None;
let autocryptheader = Aheader::from_imffields(from, imffields);
if message_time > 0 && !from.is_null() {
peerstate = Peerstate::from_addr(context, &context.sql, as_str(from));
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);
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);
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);
}
} 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.sql.get_config(context, "configured_addr");
let self_addr = context.get_config(Config::ConfiguredAddr);
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(
context,
@@ -437,7 +441,8 @@ 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, as_str(from));
peerstate =
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
@@ -486,14 +491,12 @@ 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: size_t,
data_bytes: libc::size_t,
default_content_type: *mut libc::c_char,
default_encoding: libc::c_int,
) -> *mut mailmime {
@@ -744,7 +747,7 @@ unsafe fn decrypt_recursive(
&mut decrypted_mime,
) {
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
let mut dummy: size_t = 0i32 as size_t;
let mut dummy: libc::size_t = 0i32 as libc::size_t;
let mut test: *mut mailimf_fields = ptr::null_mut();
if mailimf_envelope_and_optional_fields_parse(
(*decrypted_mime).mm_mime_start,
@@ -831,7 +834,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: size_t = 0i32 as size_t;
let mut decoded_data_bytes: libc::size_t = 0;
let mut sth_decrypted = false;
*ret_decrypted_mime = ptr::null_mut();
@@ -877,7 +880,7 @@ unsafe fn decrypt_part(
}
} else {
let r: libc::c_int;
let mut current_index: size_t = 0i32 as size_t;
let mut current_index: libc::size_t = 0;
r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
@@ -914,7 +917,7 @@ unsafe fn decrypt_part(
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
let mut index: size_t = 0i32 as size_t;
let mut index: libc::size_t = 0;
let mut decrypted_mime: *mut mailmime = ptr::null_mut();
if mailmime_parse(
plain_buf as *const _,
@@ -1092,15 +1095,12 @@ 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_eq!(
mailmime_transfer_decode(
msg1,
&mut decoded_data,
&mut decoded_data_bytes,
&mut transfer_decoding_buffer,
),
1
);
assert!(mailmime_transfer_decode(
msg1,
&mut decoded_data,
&mut decoded_data_bytes,
&mut transfer_decoding_buffer,
));
println!(
"{:?}",
String::from_utf8_lossy(std::slice::from_raw_parts(

View File

@@ -22,6 +22,8 @@ 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>;
@@ -62,6 +64,12 @@ 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 Normal file
View File

@@ -0,0 +1,229 @@
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 },
}

View File

@@ -1,5 +1,6 @@
use std::ffi::CString;
use std::net;
use std::ptr;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Condvar, Mutex, RwLock,
@@ -8,10 +9,15 @@ 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::types::*;
use crate::param::Params;
const DC_IMAP_SEEN: usize = 0x0001;
const DC_REGENERATE: usize = 0x01;
@@ -25,15 +31,11 @@ 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>>,
@@ -41,6 +43,7 @@ pub struct Imap {
should_reconnect: AtomicBool,
}
#[derive(Debug)]
struct OAuth2 {
user: String,
access_token: String,
@@ -65,6 +68,7 @@ enum FolderMeaning {
Other,
}
#[derive(Debug)]
enum Client {
Secure(
imap::Client<native_tls::TlsStream<net::TcpStream>>,
@@ -73,11 +77,13 @@ 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>),
@@ -307,6 +313,7 @@ impl Session {
}
}
#[derive(Debug)]
struct ImapConfig {
pub addr: String,
pub imap_server: String,
@@ -346,21 +353,12 @@ impl Default for ImapConfig {
}
impl Imap {
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 {
pub fn new() -> 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),
}
@@ -440,14 +438,12 @@ impl Imap {
let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port;
log_event!(
emit_event!(
context,
Event::ERROR_NETWORK,
0,
"Could not connect to IMAP-server {}:{}. ({})",
imap_server,
imap_port,
err
Event::ErrorNetwork(format!(
"Could not connect to IMAP-server {}:{}. ({})",
imap_server, imap_port, err
))
);
return false;
@@ -463,7 +459,10 @@ impl Imap {
true
}
Err((err, _)) => {
log_event!(context, Event::ERROR_NETWORK, 0, "Cannot login ({})", err);
emit_event!(
context,
Event::ErrorNetwork(format!("Cannot login ({})", err))
);
self.unsetup_handle(context);
false
@@ -556,13 +555,12 @@ impl Imap {
let caps_list = caps
.iter()
.fold(String::new(), |s, c| s + &format!(" {:?}", c));
log_event!(
emit_event!(
context,
Event::IMAP_CONNECTED,
0,
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user,
caps_list,
Event::ImapConnected(format!(
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user, caps_list,
))
);
(false, can_idle, has_xlist)
}
@@ -694,7 +692,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) = (self.get_config)(context, &key) {
if let Some(entry) = context.sql.get_config(context, &key) {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
@@ -828,7 +826,7 @@ impl Imap {
if 0 == unsafe {
let message_id_c = CString::yolo(message_id);
(self.precheck_imf)(context, message_id_c.as_ptr(), folder.as_ref(), cur_uid)
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 {
@@ -892,7 +890,7 @@ impl Imap {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
(self.set_config)(context, &key, Some(&val));
context.sql.set_config(context, &key, Some(&val)).ok();
}
fn fetch_single_msg<S: AsRef<str>>(
@@ -962,7 +960,7 @@ impl Imap {
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap();
unsafe {
(self.receive_imf)(
dc_receive_imf(
context,
body.as_ptr() as *const libc::c_char,
body.len(),
@@ -1647,3 +1645,53 @@ 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
}

View File

@@ -3,6 +3,7 @@ use std::ptr;
use std::time::Duration;
use deltachat_derive::{FromSql, ToSql};
use mmime::clist::*;
use rand::{thread_rng, Rng};
use crate::chat;
@@ -12,13 +13,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
@@ -182,18 +183,16 @@ impl Job {
);
let chat_id: i32 = context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?",
params![self.foreign_id as i32],
0,
)
.unwrap_or_default();
context.call_cb(
Event::MSG_DELIVERED,
chat_id as uintptr_t,
self.foreign_id as uintptr_t,
);
context.call_cb(Event::MsgDelivered {
chat_id: chat_id as u32,
msg_id: self.foreign_id,
});
}
}
} else {
@@ -599,11 +598,10 @@ pub fn perform_smtp_idle(context: &Context) {
fn get_next_wakeup_time(context: &Context, thread: Thread) -> Duration {
let t: i64 = context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
params![thread],
0,
)
.unwrap_or_default();
@@ -644,7 +642,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: uint32_t) -> libc::c_int {
pub unsafe fn job_send_msg(context: &Context, msg_id: u32) -> libc::c_int {
let mut success = 0;
/* load message data */
@@ -682,7 +680,7 @@ pub unsafe fn job_send_msg(context: &Context, msg_id: uint32_t) -> libc::c_int {
}
}
/* create message */
if 0 == dc_mimefactory_render(context, &mut mimefactory) {
if !dc_mimefactory_render(context, &mut mimefactory) {
dc_set_msg_failed(context, msg_id, as_opt_str(mimefactory.error));
} else if 0
!= mimefactory
@@ -990,9 +988,9 @@ fn connect_to_inbox(context: &Context, inbox: &Imap) -> libc::c_int {
ret_connected
}
fn send_mdn(context: &Context, msg_id: uint32_t) {
fn send_mdn(context: &Context, msg_id: u32) {
if let Ok(mut mimefactory) = unsafe { dc_mimefactory_load_mdn(context, msg_id) } {
if 0 != unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
if unsafe { dc_mimefactory_render(context, &mut mimefactory) } {
add_smtp_job(context, Action::SendMdn, &mut mimefactory);
}
}
@@ -1000,38 +998,23 @@ fn send_mdn(context: &Context, msg_id: uint32_t) {
#[allow(non_snake_case)]
fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_t) -> libc::c_int {
let pathNfilename: *mut libc::c_char;
let mut success: libc::c_int = 0i32;
let mut recipients: *mut libc::c_char = ptr::null_mut();
let mut param = Params::new();
pathNfilename = unsafe {
dc_get_fine_pathNfilename(
context,
b"$BLOBDIR\x00" as *const u8 as *const libc::c_char,
mimefactory.rfc724_mid,
let path_filename =
dc_get_fine_path_filename(context, "$BLOBDIR", as_str(mimefactory.rfc724_mid));
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
(*mimefactory.out).len,
)
};
if pathNfilename.is_null() {
error!(
context,
"Could not find free file name for message with ID <{}>.",
to_string(mimefactory.rfc724_mid),
);
} else if 0
== unsafe {
dc_write_file(
context,
pathNfilename,
(*mimefactory.out).str_0 as *const libc::c_void,
(*mimefactory.out).len,
)
}
{
if !dc_write_file(context, &path_filename, bytes) {
error!(
context,
"Could not write message <{}> to \"{}\".",
to_string(mimefactory.rfc724_mid),
as_str(pathNfilename),
path_filename.display(),
);
} else {
recipients = unsafe {
@@ -1040,7 +1023,7 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
b"\x1e\x00" as *const u8 as *const libc::c_char,
)
};
param.set(Param::File, as_str(pathNfilename));
param.set(Param::File, path_filename.to_string_lossy());
param.set(Param::Recipients, as_str(recipients));
job_add(
context,
@@ -1059,7 +1042,6 @@ fn add_smtp_job(context: &Context, action: Action, mimefactory: &dc_mimefactory_
}
unsafe {
free(recipients.cast());
free(pathNfilename.cast());
}
success
}

View File

@@ -4,6 +4,7 @@ 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,

View File

@@ -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,7 +11,6 @@ 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 {
@@ -142,11 +141,10 @@ impl Key {
) -> Option<Self> {
let addr = self_addr.as_ref();
sql.query_row_col(
sql.query_get_value(
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))
}
@@ -156,11 +154,10 @@ impl Key {
self_addr: impl AsRef<str>,
sql: &Sql,
) -> Option<Self> {
sql.query_row_col(
sql.query_get_value(
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))
}
@@ -218,30 +215,15 @@ impl Key {
.expect("failed to serialize key")
}
pub fn write_asc_to_file(&self, file: *const libc::c_char, context: &Context) -> bool {
if file.is_null() {
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(context, &file, &file_content) {
return true;
} else {
error!(context, "Cannot write key to {}", file.as_ref().display());
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 {
@@ -251,23 +233,11 @@ 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,
@@ -313,14 +283,6 @@ 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()
@@ -329,14 +291,6 @@ 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::*;

View File

@@ -33,11 +33,10 @@ impl<'a> Keyring<'a> {
self_addr: impl AsRef<str>,
sql: &Sql,
) -> bool {
sql.query_row_col(
sql.query_get_value(
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))

View File

@@ -1,4 +1,4 @@
#![deny(clippy::correctness)]
#![deny(clippy::correctness, missing_debug_implementations)]
// 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,12 +18,17 @@ 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;
@@ -49,7 +54,6 @@ pub mod qr;
mod smtp;
pub mod sql;
mod stock;
pub mod types;
pub mod x;
pub mod dc_array;

View File

@@ -3,17 +3,16 @@ 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)]
@@ -225,11 +224,7 @@ 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::CHAT_MODIFIED,
chat_id as uintptr_t,
0i32 as uintptr_t,
);
context.call_cb(Event::ChatModified(chat_id));
if 0 != seconds {
schedule_MAYBE_SEND_LOCATIONS(context, 0i32);
job_add(
@@ -292,7 +287,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> l
}
}
if continue_streaming {
context.call_cb(Event::LOCATION_CHANGED, 1, 0);
context.call_cb(Event::LocationChanged(Some(1)));
};
schedule_MAYBE_SEND_LOCATIONS(context, 0);
}
@@ -369,7 +364,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::LOCATION_CHANGED, 0, 0);
context.call_cb(Event::LocationChanged(None));
Ok(())
}
@@ -653,11 +648,7 @@ 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::CHAT_MODIFIED,
chat_id as usize,
0,
);
context.call_cb(Event::ChatModified(chat_id));
}
}
}

View File

@@ -4,7 +4,8 @@ macro_rules! info {
info!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
log_event!($ctx, $crate::constants::Event::INFO, 0, $msg, $($args),*);
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Info(formatted));
};
}
@@ -14,7 +15,8 @@ macro_rules! warn {
warn!($ctx, $msg,)
};
($ctx:expr, $msg:expr, $($args:expr),* $(,)?) => {
log_event!($ctx, $crate::constants::Event::WARNING, 0, $msg, $($args),*);
let formatted = format!($msg, $($args),*);
emit_event!($ctx, $crate::Event::Warning(formatted));
};
}
@@ -24,26 +26,14 @@ 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),*);
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);
emit_event!($ctx, $crate::Event::Error(formatted));
};
}
#[macro_export]
macro_rules! emit_event {
($ctx:expr, $event:expr, $data1:expr, $data2:expr) => {
$ctx.call_cb($event, $data1 as libc::uintptr_t, $data2 as libc::uintptr_t);
($ctx:expr, $event:expr) => {
$ctx.call_cb($event);
};
}

View File

@@ -1,5 +1,5 @@
use std::ffi::CString;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::ptr;
use deltachat_derive::{FromSql, ToSql};
@@ -9,15 +9,16 @@ 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.
@@ -87,7 +88,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 0 != dc_msg_is_info(msg) || chat.is_self_talk() {
if dc_msg_is_info(msg) || chat.is_self_talk() {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
@@ -95,7 +96,7 @@ impl Lot {
self.text1_meaning = Meaning::Text1Self;
}
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
if 0 != dc_msg_is_info(msg) || contact.is_none() {
if dc_msg_is_info(msg) || contact.is_none() {
self.text1 = None;
self.text1_meaning = Meaning::None;
} else {
@@ -136,7 +137,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(Clone)]
#[derive(Debug, Clone)]
pub struct Message {
pub id: u32,
pub from_id: u32,
@@ -151,7 +152,7 @@ pub struct Message {
pub timestamp_rcvd: i64,
pub text: Option<String>,
pub rfc724_mid: *mut libc::c_char,
pub in_reply_to: *mut libc::c_char,
pub in_reply_to: Option<String>,
pub server_folder: Option<String>,
pub server_uid: u32,
// TODO: enum
@@ -173,11 +174,10 @@ 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_row_col(
let rawtxt: Option<String> = context.sql.query_get_value(
context,
"SELECT txt_raw FROM msgs WHERE id=?;",
params![msg_id as i32],
0,
);
if rawtxt.is_none() {
@@ -269,15 +269,10 @@ pub unsafe fn dc_get_msg_info(context: &Context, msg_id: u32) -> *mut libc::c_ch
_ => {}
}
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,
);
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);
}
free(p as *mut libc::c_void);
if msg.type_0 != Viewtype::Text {
ret += "Type: ";
@@ -328,7 +323,7 @@ pub fn dc_msg_new(viewtype: Viewtype) -> Message {
timestamp_rcvd: 0,
text: None,
rfc724_mid: std::ptr::null_mut(),
in_reply_to: std::ptr::null_mut(),
in_reply_to: None,
server_folder: None,
server_uid: 0,
is_dc_message: 0,
@@ -343,7 +338,6 @@ impl Drop for Message {
fn drop(&mut self) {
unsafe {
free(self.rfc724_mid.cast());
free(self.in_reply_to.cast());
}
}
}
@@ -378,17 +372,10 @@ 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) -> *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)
}
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))
}
/**
@@ -452,10 +439,7 @@ 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 = match row.get::<_, Option<String>>(2)? {
Some(s) => s.strdup(),
None => std::ptr::null_mut(),
};
msg.in_reply_to = row.get::<_, Option<String>>(2)?;
msg.server_folder = row.get::<_, Option<String>>(3)?;
msg.server_uid = row.get(4)?;
msg.move_state = row.get(5)?;
@@ -503,11 +487,10 @@ 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_row_col(
let headers: Option<String> = context.sql.query_get_value(
context,
"SELECT mime_headers FROM msgs WHERE id=?;",
params![msg_id as i32],
0,
);
if let Some(headers) = headers {
@@ -536,7 +519,10 @@ pub unsafe fn dc_delete_msgs(context: &Context, msg_ids: *const u32, msg_cnt: li
}
if 0 != msg_cnt {
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
job_kill_action(context, Action::Housekeeping);
job_add(context, Action::Housekeeping, 0, Params::new(), 10);
};
@@ -604,7 +590,10 @@ pub fn dc_markseen_msgs(context: &Context, msg_ids: *const u32, msg_cnt: usize)
}
if send_event {
context.call_cb(Event::MSGS_CHANGED, 0, 0);
context.call_cb(Event::MsgsChanged {
chat_id: 0,
msg_id: 0,
});
}
true
@@ -698,7 +687,7 @@ pub unsafe fn dc_msg_get_filename(msg: &Message) -> *mut libc::c_char {
}
}
pub fn dc_msg_get_filebytes(context: &Context, msg: &Message) -> uint64_t {
pub fn dc_msg_get_filebytes(context: &Context, msg: &Message) -> u64 {
if let Some(file) = msg.param.get(Param::File) {
return dc_get_filebytes(context, &file);
}
@@ -717,13 +706,8 @@ pub fn dc_msg_get_duration(msg: &Message) -> libc::c_int {
msg.param.get_int(Param::Duration).unwrap_or_default()
}
// 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_showpadlock(msg: &Message) -> bool {
msg.param.get_int(Param::GuranteeE2ee).unwrap_or_default() != 0
}
pub fn dc_msg_get_summary(context: &Context, msg: &mut Message, chat: Option<&Chat>) -> Lot {
@@ -784,7 +768,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_int(Param::Cmd) == Some(6) {
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
append_text = false;
context
.stock_str(StockMessage::AcSetupMsgSubject)
@@ -810,7 +794,7 @@ pub fn dc_msg_get_summarytext_by_raw(
}
}
_ => {
if param.get_int(Param::Cmd) != Some(9) {
if param.get_cmd() != SystemMessage::LocationOnly {
"".to_string()
} else {
append_text = false;
@@ -843,48 +827,27 @@ pub unsafe fn dc_msg_has_deviating_timestamp(msg: &Message) -> libc::c_int {
(sort_timestamp / 86400 != send_timestamp / 86400) as libc::c_int
}
// 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_sent(msg: &Message) -> bool {
msg.state as i32 >= MessageState::OutDelivered as i32
}
pub fn dc_msg_is_starred(msg: &Message) -> bool {
msg.starred
}
// 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_forwarded(msg: &Message) -> bool {
0 != msg.param.get_int(Param::Forwarded).unwrap_or_default()
}
// 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
pub fn dc_msg_is_info(msg: &Message) -> bool {
let cmd = msg.param.get_cmd();
msg.from_id == 2i32 as libc::c_uint
|| msg.to_id == 2i32 as libc::c_uint
|| 0 != cmd && cmd != 6i32
{
return 1;
}
0
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
}
// 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_increation(msg: &Message) -> bool {
chat::msgtype_has_file(msg.type_0) && msg.state == MessageState::OutPreparing
}
pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
@@ -892,33 +855,20 @@ pub fn dc_msg_is_setupmessage(msg: &Message) -> bool {
return false;
}
msg.param.get_int(Param::Cmd) == Some(6)
msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage
}
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) {
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 let Some(filename) = dc_msg_get_file(context, msg) {
if let Some(mut buf) = dc_read_file_safe(context, filename) {
if dc_split_armored_data(
buf,
buf.as_mut_ptr().cast(),
&mut buf_headerline,
&mut buf_setupcodebegin,
ptr::null_mut(),
@@ -934,8 +884,6 @@ 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 {
@@ -1035,11 +983,10 @@ pub fn dc_msg_exists(context: &Context, msg_id: u32) -> bool {
return false;
}
let chat_id: Option<u32> = context.sql.query_row_col(
let chat_id: Option<u32> = context.sql.query_get_value(
context,
"SELECT chat_id FROM msgs WHERE id=?;",
params![msg_id],
0,
);
if let Some(chat_id) = chat_id {
@@ -1083,11 +1030,10 @@ pub fn dc_set_msg_failed(context: &Context, msg_id: u32, error: Option<impl AsRe
)
.is_ok()
{
context.call_cb(
Event::MSG_FAILED,
msg.chat_id as uintptr_t,
msg_id as uintptr_t,
);
context.call_cb(Event::MsgFailed {
chat_id: msg.chat_id,
msg_id,
});
}
}
}
@@ -1158,11 +1104,10 @@ pub unsafe fn dc_mdn_from_ext(
/* send event about new state */
let ist_cnt: i32 = context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
params![*ret_msg_id as i32],
0,
)
.unwrap_or_default();
/*
@@ -1207,7 +1152,7 @@ pub fn dc_get_real_msg_cnt(context: &Context) -> libc::c_int {
}
}
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> size_t {
pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> libc::size_t {
match context.sql.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
@@ -1215,7 +1160,7 @@ pub fn dc_get_deaddrop_msg_cnt(context: &Context) -> size_t {
rusqlite::NO_PARAMS,
|row| row.get::<_, isize>(0),
) {
Ok(res) => res as size_t,
Ok(res) => res as libc::size_t,
Err(err) => {
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err);
0

View File

@@ -4,6 +4,7 @@ use std::str;
use num_traits::FromPrimitive;
use crate::dc_mimeparser::SystemMessage;
use crate::error;
/// Available param keys.
@@ -94,7 +95,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 {
write!(f, "\n")?;
writeln!(f)?;
}
write!(f, "{}={}", *key as u8 as char, value)?;
}
@@ -178,6 +179,13 @@ 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())

View File

@@ -499,7 +499,11 @@ mod tests {
let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64("xsBNBFztUVkBCADYaQl/UOUpRPd32nLRzx8eU0eI+jQEnG+g5anjYA+3oct1rROGl5SygjMULDKdaUy27O3o9Srsti0YjA7uxZnavIqhSopJhFidqY1M1wA9JZa/duucZdNwUGbjGIRsS/4Cjr5+3svscK24hVYub1dvDWXpwUTnj3K6xOEnJdoM+MhCqtSD5+zcJhFc9vyZm9ZTGWUxAhKh0iJTcCD8V6CQ3XZ2z9GruwzZT/FTFovWrz7m3TUI2OdSSHh0eZLRGEoxMCT/vzflAFGAr8ijCaRsEIfqP6FW8uQWnFTqkjxEUCZG6XkeFHB84aj5jqYG/1KCLjL5vEKwfl1tz/WnPhY7ABEBAAHNEDxoZWxsb0BtYWlsLmNvbT7CwIkEEAEIADMCGQEFAlztUVoCGwMECwkIBwYVCAkKCwIDFgIBFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7ijAwf+PTsuawUax9cNPn1bN90H+g9qyHZJMEwKXtUnNaXJxPW3iB7ThhpCiCzsZwP7+l7ArS8tmLeNDw2bENtcf1XCv4wovP2fdXOP3QOUUFX/GdakcTwv7DzC7CO0grB1HtaPhGw/6UX2o2cx2i9xiUf4Givq2MfCbgAW5zloH6WXGPb6yLQYJXxqDIphr4+uZDb+bMAyWHN/DUkAjHrV8nnVki7PMHqzzZpwglalxMX8RGeiGZE39ALJKL/Og87DMFah87/yoxQWGoS7Wqv0XDcCPKoTCPrpk8pOe2KEsq/lz215nefHd4aRpfUX5YCYa8HPvvfPQbGF73uvyQw5w7qjis7ATQRc7VFZAQgAt8ONdnX6KEEQ5Jw6ilJ+LBtY44SP5t0I3eK+goKepgIiKhjGDa+Mntyi4jdhH+HO6kvK5SHMh2sPp4rRO/WKHJwWFySyM1OdyiywhyH0J9R5rBY4vPHsJjf6vSKJdWLWT+ho1fNet2IIC+jVCYli91MAMbRvk6EKVj1nCc+67giOahXEkHt6xxkeCGlOvbw8hxGj1A8+AC1BLms/OR3oc4JMi9O3kq6uG0z9tlUEerac9HVwcjoO1XLe+hJhoT5H+TbnGjPuhuURP3pFiIKHpbRYgUfdSAY0dTObO7t4I5y/drPOrCTnWrBUg2wXAECUhpRKow9/ai2YemLv9KqhhwARAQABwsB2BBgBCAAgBQJc7VFaAhsMFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7jmyggAhs4QzCzIbT2OsAReBxkxtm0AI+g1HZ1KFKof5NDHfgv9C/Qu1I8mKEjlZzA4qFyPmLqntgwJ0RuFy6gLbljZBNCFO7vB478AhYtnWjuKZmA40HUPwcB1hEJ31c42akzfUbioY1TLLepngdsJg7Cm8O+rhI9+1WRA66haJDgFs793SVUDyJh8f9NX50l5zR87/bsV30CFSw0q4OSSy9VI/z+2g5khn1LnuuOrCfFnYIPYtJED1BfkXkosxGlgbzy79VvGmI9d23x4atDK7oBPCzIj+lP8sytJ0u3HOguXi9OgDitKy+Pt1r8gH8frdktMJr5Ts6DW+tIn2vR23KR8aA==", KeyType::Public).unwrap();
let pub_key = crate::key::Key::from_base64(
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let mut peerstate = Peerstate {
context: &ctx.ctx,
@@ -537,7 +541,11 @@ mod tests {
let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64("xsBNBFztUVkBCADYaQl/UOUpRPd32nLRzx8eU0eI+jQEnG+g5anjYA+3oct1rROGl5SygjMULDKdaUy27O3o9Srsti0YjA7uxZnavIqhSopJhFidqY1M1wA9JZa/duucZdNwUGbjGIRsS/4Cjr5+3svscK24hVYub1dvDWXpwUTnj3K6xOEnJdoM+MhCqtSD5+zcJhFc9vyZm9ZTGWUxAhKh0iJTcCD8V6CQ3XZ2z9GruwzZT/FTFovWrz7m3TUI2OdSSHh0eZLRGEoxMCT/vzflAFGAr8ijCaRsEIfqP6FW8uQWnFTqkjxEUCZG6XkeFHB84aj5jqYG/1KCLjL5vEKwfl1tz/WnPhY7ABEBAAHNEDxoZWxsb0BtYWlsLmNvbT7CwIkEEAEIADMCGQEFAlztUVoCGwMECwkIBwYVCAkKCwIDFgIBFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7ijAwf+PTsuawUax9cNPn1bN90H+g9qyHZJMEwKXtUnNaXJxPW3iB7ThhpCiCzsZwP7+l7ArS8tmLeNDw2bENtcf1XCv4wovP2fdXOP3QOUUFX/GdakcTwv7DzC7CO0grB1HtaPhGw/6UX2o2cx2i9xiUf4Givq2MfCbgAW5zloH6WXGPb6yLQYJXxqDIphr4+uZDb+bMAyWHN/DUkAjHrV8nnVki7PMHqzzZpwglalxMX8RGeiGZE39ALJKL/Og87DMFah87/yoxQWGoS7Wqv0XDcCPKoTCPrpk8pOe2KEsq/lz215nefHd4aRpfUX5YCYa8HPvvfPQbGF73uvyQw5w7qjis7ATQRc7VFZAQgAt8ONdnX6KEEQ5Jw6ilJ+LBtY44SP5t0I3eK+goKepgIiKhjGDa+Mntyi4jdhH+HO6kvK5SHMh2sPp4rRO/WKHJwWFySyM1OdyiywhyH0J9R5rBY4vPHsJjf6vSKJdWLWT+ho1fNet2IIC+jVCYli91MAMbRvk6EKVj1nCc+67giOahXEkHt6xxkeCGlOvbw8hxGj1A8+AC1BLms/OR3oc4JMi9O3kq6uG0z9tlUEerac9HVwcjoO1XLe+hJhoT5H+TbnGjPuhuURP3pFiIKHpbRYgUfdSAY0dTObO7t4I5y/drPOrCTnWrBUg2wXAECUhpRKow9/ai2YemLv9KqhhwARAQABwsB2BBgBCAAgBQJc7VFaAhsMFiEEgMjHGVbvLXe6ioRROg8oKCvye7gACgkQOg8oKCvye7jmyggAhs4QzCzIbT2OsAReBxkxtm0AI+g1HZ1KFKof5NDHfgv9C/Qu1I8mKEjlZzA4qFyPmLqntgwJ0RuFy6gLbljZBNCFO7vB478AhYtnWjuKZmA40HUPwcB1hEJ31c42akzfUbioY1TLLepngdsJg7Cm8O+rhI9+1WRA66haJDgFs793SVUDyJh8f9NX50l5zR87/bsV30CFSw0q4OSSy9VI/z+2g5khn1LnuuOrCfFnYIPYtJED1BfkXkosxGlgbzy79VvGmI9d23x4atDK7oBPCzIj+lP8sytJ0u3HOguXi9OgDitKy+Pt1r8gH8frdktMJr5Ts6DW+tIn2vR23KR8aA==", KeyType::Public).unwrap();
let pub_key = crate::key::Key::from_base64(
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let mut peerstate = Peerstate {
context: &ctx.ctx,

View File

@@ -14,7 +14,6 @@ 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(
@@ -25,7 +24,7 @@ pub unsafe fn dc_split_armored_data(
ret_base64: *mut *const libc::c_char,
) -> bool {
let mut success = false;
let mut line_chars: size_t = 0i32 as size_t;
let mut line_chars: libc::size_t = 0;
let mut line: *mut libc::c_char = buf;
let mut p1: *mut libc::c_char = buf;
let mut p2: *mut libc::c_char;
@@ -105,7 +104,7 @@ pub unsafe fn dc_split_armored_data(
}
p1 = p1.offset(1isize);
line = p1;
line_chars = 0i32 as size_t
line_chars = 0;
} else {
p1 = p1.offset(1isize);
line_chars = line_chars.wrapping_add(1)
@@ -290,10 +289,11 @@ pub fn dc_pgp_symm_decrypt(passphrase: &str, ctext: &[u8]) -> Option<Vec<u8>> {
enc_msg
.and_then(|msg| {
let mut decryptor = msg
.decrypt_with_password(|| passphrase.into())
.expect("failed decryption");
decryptor.next().expect("no message")
let mut decryptor = msg.decrypt_with_password(|| passphrase.into())?;
match decryptor.next() {
Some(x) => x,
None => Err(pgp::errors::Error::InvalidInput),
}
})
.and_then(|msg| msg.get_content())
.ok()

View File

@@ -70,19 +70,14 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
None => return format_err!("Invalid OPENPGP4FPR found").into(),
};
dbg!(fingerprint);
dbg!(fragment);
// replace & with \n to match expected param format
let fragment = fragment.replace('&', "\n");
dbg!(&fragment);
// Then parse the parameters
let param: Params = match fragment.parse() {
Ok(params) => params,
Err(err) => return err.into(),
};
dbg!(&param);
let addr = if let Some(addr) = param.get(Param::Forwarded) {
match normalize_address(addr) {

View File

@@ -1,17 +1,16 @@
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::*;
@@ -20,39 +19,32 @@ 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! progress {
($context:tt, $event:expr, $contact_id:expr, $progress:expr) => {
macro_rules! joiner_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"
);
$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
);
$context.call_cb($crate::events::Event::SecurejoinJoinerProgress {
contact_id: $contact_id,
progress: $progress,
});
};
}
macro_rules! inviter_progress {
($context:tt, $contact_id:expr, $progress:expr) => {
progress!(
$context,
Event::SECUREJOIN_INVITER_PROGRESS,
$contact_id,
$progress
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::SecurejoinInviterProgress {
contact_id: $contact_id,
progress: $progress,
});
};
}
@@ -71,7 +63,7 @@ macro_rules! get_qr_attr {
};
}
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: uint32_t) -> Option<String> {
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: u32) -> Option<String> {
/* =========================================================
==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ====
@@ -82,7 +74,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: uint32_t) -> Optio
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.sql.get_config(context, "configured_addr") {
let self_addr = match context.get_config(Config::ConfiguredAddr) {
Some(addr) => addr,
None => {
error!(context, "Not configured, cannot generate QR code.",);
@@ -139,7 +131,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: uint32_t) -> Optio
}
fn get_self_fingerprint(context: &Context) -> Option<String> {
if let Some(self_addr) = context.sql.get_config(context, "configured_addr") {
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
return Some(key.fingerprint());
}
@@ -147,7 +139,7 @@ fn get_self_fingerprint(context: &Context) -> Option<String> {
None
}
pub fn dc_join_securejoin(context: &Context, qr: &str) -> uint32_t {
pub fn dc_join_securejoin(context: &Context, qr: &str) -> u32 {
let cleanup =
|context: &Context, contact_chat_id: u32, ongoing_allocated: bool, join_vg: bool| {
let mut bob = context.bob.write().unwrap();
@@ -170,13 +162,13 @@ pub fn dc_join_securejoin(context: &Context, qr: &str) -> uint32_t {
if ongoing_allocated {
dc_free_ongoing(context);
}
ret_chat_id as uint32_t
ret_chat_id as u32
};
/* ==========================================================
==== Bob - the joiner's side =====
==== Step 2 in "Setup verified contact" protocol =====
========================================================== */
let mut contact_chat_id: uint32_t = 0;
let mut contact_chat_id: u32 = 0;
let mut join_vg: bool = false;
info!(context, "Requesting secure-join ...",);
@@ -268,7 +260,7 @@ fn check_exit(context: &Context) -> bool {
fn send_handshake_msg(
context: &Context,
contact_chat_id: uint32_t,
contact_chat_id: u32,
step: &str,
param2: impl AsRef<str>,
fingerprint: Option<String>,
@@ -305,7 +297,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: uint32_t) -> uint32_t {
fn chat_id_2_contact_id(context: &Context, contact_chat_id: u32) -> u32 {
let contacts = chat::get_chat_contacts(context, contact_chat_id);
if contacts.len() == 1 {
contacts[0]
@@ -340,15 +332,15 @@ fn fingerprint_equals_sender(
/* library private: secure-join */
pub fn handle_securejoin_handshake(
context: &Context,
mimeparser: &dc_mimeparser_t,
contact_id: uint32_t,
mimeparser: &MimeParser,
contact_id: u32,
) -> libc::c_int {
let own_fingerprint: String;
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return 0;
}
let step = match lookup_field(mimeparser, "Secure-Join") {
let step = match mimeparser.lookup_optional_field("Secure-Join") {
Some(s) => s,
None => {
return 0;
@@ -377,7 +369,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 lookup_field(mimeparser, "Secure-Join-Invitenumber") {
let invitenumber = match mimeparser.lookup_optional_field("Secure-Join-Invitenumber") {
Some(n) => n,
None => {
warn!(context, "Secure-join denied (invitenumber missing).",);
@@ -464,7 +456,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 lookup_field(mimeparser, "Secure-Join-Fingerprint") {
let fingerprint = match mimeparser.lookup_optional_field("Secure-Join-Fingerprint") {
Some(fp) => fp,
None => {
could_not_establish_secure_connection(
@@ -493,7 +485,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 lookup_field(mimeparser, "Secure-Join-Auth") {
let auth_0 = match mimeparser.lookup_optional_field("Secure-Join-Auth") {
Some(auth) => auth,
None => {
could_not_establish_secure_connection(
@@ -519,10 +511,12 @@ 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::CONTACTS_CHANGED, contact_id, 0);
emit_event!(context, Event::ContactsChanged(Some(contact_id)));
inviter_progress!(context, contact_id, 600);
if join_vg {
let field_grpid = lookup_field(mimeparser, "Secure-Join-Group").unwrap_or_default();
let field_grpid = mimeparser
.lookup_optional_field("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);
@@ -590,9 +584,10 @@ pub fn handle_securejoin_handshake(
return ret;
}
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
emit_event!(context, Event::CONTACTS_CHANGED, 0, 0);
let cg_member_added =
lookup_field(mimeparser, "Chat-Group-Member-Added").unwrap_or_default();
emit_event!(context, Event::ContactsChanged(None));
let cg_member_added = mimeparser
.lookup_optional_field("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;
@@ -643,8 +638,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: uint32_t) {
let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id);
fn secure_connection_established(context: &Context, contact_chat_id: u32) {
let contact_id: u32 = 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()
@@ -653,32 +648,10 @@ fn secure_connection_established(context: &Context, contact_chat_id: uint32_t) {
};
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::CHAT_MODIFIED, contact_chat_id, 0);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
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,
) {
fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32, 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(
@@ -715,10 +688,7 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
* Tools: Misc.
******************************************************************************/
fn encrypted_and_signed(
mimeparser: &dc_mimeparser_t,
expected_fingerprint: impl AsRef<str>,
) -> bool {
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
if !mimeparser.e2ee_helper.encrypted {
warn!(mimeparser.context, "Message not encrypted.",);
false
@@ -753,11 +723,10 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) {
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = context
.sql
.query_row_col(
.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr],
0,
)
.unwrap_or_default();
if contact_id > 0 {
@@ -772,7 +741,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::CHAT_MODIFIED, contact_chat_id, 0);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
}
}

View File

@@ -1,13 +1,15 @@
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.
@@ -50,7 +52,7 @@ impl Smtp {
}
if lp.send_server.is_empty() || lp.send_port == 0 {
log_event!(context, Event::ERROR_NETWORK, 0, "SMTP bad parameters.",);
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
}
self.from = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
@@ -109,13 +111,10 @@ impl Smtp {
.credentials(creds)
.connection_reuse(lettre::smtp::ConnectionReuseParameters::ReuseUnlimited);
self.transport = Some(client.transport());
log_event!(
context,
Event::SMTP_CONNECTED,
0,
context.call_cb(Event::SmtpConnected(format!(
"SMTP-LOGIN as {} ok",
lp.send_user,
);
)));
true
}
Err(err) => {
@@ -141,12 +140,9 @@ impl Smtp {
match transport.send(mail) {
Ok(_) => {
log_event!(
context,
Event::SMTP_MESSAGE_SENT,
0,
"Message was sent to SMTP server",
);
context.call_cb(Event::SmtpMessageSent(
"Message was sent to SMTP server".into(),
));
self.transport_connected = true;
1
}

View File

@@ -13,8 +13,10 @@ 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>>,
}
@@ -155,19 +157,13 @@ impl Sql {
.unwrap_or_default()
}
pub fn query_row_col<P, T>(
&self,
context: &Context,
query: &str,
params: P,
column: usize,
) -> Option<T>
pub fn query_get_value<P, T>(&self, context: &Context, query: &str, params: P) -> Option<T>
where
P: IntoIterator,
P::Item: rusqlite::ToSql,
T: rusqlite::types::FromSql,
{
match self.query_row(query, params, |row| row.get::<_, T>(column)) {
match self.query_row(query, params, |row| row.get::<_, T>(0)) {
Ok(res) => Some(res),
Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => None,
Err(Error::Sql(rusqlite::Error::InvalidColumnType(
@@ -238,11 +234,10 @@ impl Sql {
if !self.is_open() || key.as_ref().is_empty() {
return None;
}
self.query_row_col(
self.query_get_value(
context,
"SELECT value FROM config WHERE keyname=?;",
params![key.as_ref()],
0,
)
}
@@ -798,7 +793,7 @@ fn open(
let repl_from = sql
.get_config(context, "backup_for")
.unwrap_or_else(|| to_string(context.get_blobdir()));
.unwrap_or_else(|| context.get_blobdir().to_string_lossy().into());
let repl_from = dc_ensure_no_slash_safe(&repl_from);
sql.execute(
@@ -997,7 +992,7 @@ pub fn housekeeping(context: &Context) {
info!(context, "{} files in use.", files_in_use.len(),);
/* go through directory and delete unused files */
let p = std::path::Path::new(as_str(context.get_blobdir()));
let p = 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 */
@@ -1057,7 +1052,7 @@ pub fn housekeeping(context: &Context) {
warn!(
context,
"Housekeeping: Cannot open {}. ({})",
as_str(context.get_blobdir()),
context.get_blobdir().display(),
err
);
}

View File

@@ -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::GET_STRING, id as usize, 0) as *mut libc::c_char;
let ptr = self.call_cb(Event::GetString { id, count: 0 }) as *mut libc::c_char;
if ptr.is_null() {
Cow::Borrowed(id.fallback())
} else {
@@ -140,12 +140,14 @@ impl Context {
/// Return stock string, replacing placeholders with provided string.
///
/// This replaces both the *first* `%1$s` **and** `%1$d`
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
/// 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.
@@ -158,9 +160,10 @@ impl Context {
/// Return stock string, replacing 2 placeholders with provided string.
///
/// This replaces both the *first* `%1$s` **and** `%1$d`
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
/// placeholders with the string in `insert` and does the same for
/// `%2$s` and `%2$d` for `insert2`.
/// `%2$s`, `%2$d` and `%2$@` for `insert2`.
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
fn stock_string_repl_str2(
&self,
id: StockMessage,
@@ -170,8 +173,10 @@ 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
@@ -232,11 +237,8 @@ mod tests {
use super::*;
use crate::test_utils::*;
use std::ffi::CString;
use crate::constants::DC_CONTACT_ID_SELF;
use crate::context::dc_context_new;
use crate::types::uintptr_t;
use libc::uintptr_t;
use num_traits::ToPrimitive;
@@ -253,36 +255,32 @@ mod tests {
#[test]
fn test_stock_str() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(ctx.stock_str(StockMessage::NoMessages), "No messages.");
let t = dummy_context();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
}
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
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,
}
}
#[test]
fn test_stock_str_no_fallback() {
let t = test_context(Some(test_stock_str_no_fallback_cb));
let t = test_context(Some(Box::new(test_stock_str_no_fallback_cb)));
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "Hello there");
}
#[test]
fn test_stock_string_repl_str() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
// uses %1$s substitution
assert_eq!(
ctx.stock_string_repl_str(StockMessage::Member, "42"),
t.ctx.stock_string_repl_str(StockMessage::Member, "42"),
"42 member(s)"
);
// We have no string using %1$d to test...
@@ -290,36 +288,38 @@ mod tests {
#[test]
fn test_stock_string_repl_int() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
assert_eq!(
ctx.stock_string_repl_int(StockMessage::Member, 42),
t.ctx.stock_string_repl_int(StockMessage::Member, 42),
"42 member(s)"
);
}
#[test]
fn test_stock_string_repl_str2() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
assert_eq!(
ctx.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
t.ctx
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
"Response from foo: bar"
);
}
#[test]
fn test_stock_system_msg_simple() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
assert_eq!(
ctx.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
t.ctx
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
"Location streaming enabled."
)
}
#[test]
fn test_stock_system_msg_add_member_by_me() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
let t = dummy_context();
assert_eq!(
ctx.stock_system_msg(
t.ctx.stock_system_msg(
StockMessage::MsgAddMember,
"alice@example.com",
"",

View File

@@ -2,16 +2,14 @@
//!
//! 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::{Event, KeyType};
use crate::context::{dc_context_new, dc_open, Context};
use crate::constants::KeyType;
use crate::context::{Context, ContextCallback};
use crate::events::Event;
use crate::key;
use crate::types::dc_callback_t;
/// A Context and temporary directory.
///
@@ -28,18 +26,15 @@ pub struct TestContext {
/// "db.sqlite" in the [TestContext.dir] directory.
///
/// [Context]: crate::context::Context
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 }
}
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 }
}
/// Return a dummy [TestContext].
@@ -51,17 +46,11 @@ pub fn dummy_context() -> TestContext {
test_context(None)
}
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();
pub fn logging_cb(_ctx: &Context, evt: Event) -> uintptr_t {
match evt {
Event::INFO => println!("I: {}", to_str(d2)),
Event::WARNING => println!("W: {}", to_str(d2)),
Event::ERROR => println!("E: {}", to_str(d2)),
Event::Info(msg) => println!("I: {}", msg),
Event::Warning(msg) => println!("W: {}", msg),
Event::Error(msg) => println!("E: {}", msg),
_ => (),
}
0

View File

@@ -35,11 +35,10 @@ 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_row_col::<_, String>(
context.sql.query_get_value::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespace, foreign_id as i32],
0,
)
}

View File

@@ -1,43 +0,0 @@
#![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;

View File

@@ -33,13 +33,6 @@ pub fn strndup(s: *const libc::c_char, n: libc::c_ulong) -> *mut libc::c_char {
}
}
extern "C" {
pub fn clock() -> libc::clock_t;
// -- DC Methods
pub fn dc_mprintf(format: *const libc::c_char, _: ...) -> *mut libc::c_char;
}
pub(crate) unsafe fn strcasecmp(s1: *const libc::c_char, s2: *const libc::c_char) -> libc::c_int {
let s1 = std::ffi::CStr::from_ptr(s1)
.to_string_lossy()

1
test-data/key/public.asc Normal file
View File

@@ -0,0 +1 @@
xsBNBF086ewBCACmJKuoxIO6T87yi4Q3MyNpMch3Y8KrtHDQyUszU36eqM3Pmd1lFrbcCd8ZWo2pq6OJSwsM/jjRGn1zo2YOaQeJRRrC+KrKGqSUtRSYQBPrPjE2YjSXAMbu8jBI6VVUhHeghriBkK79PY9O/oRhIJC0p14IJe6CQ6OI2fTmTUHF9i/nJ3G4Wb3/K1bU3yVfyPZjTZQPYPvvh03vxHULKurtYkX5DTEMSWsF4qzLMps+l87VuLV9iQnbN7YMRLHHx2KkX5Ivi7JCefhCa54M0K3bDCCxuVWAM5wjQwNZjzR+WL+dYchwoFvuF8NrlzjM9dSv+2rn+J7f99ijSXarzPC7ABEBAAHNEzxhbGljZUBleGFtcGxlLmNvbT7CwIkEEAEIADMCGQEFAl086fgCGwMECwkIBwYVCAkKCwIDFgIBFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcei3ogf/cruUmQ+th52TFHTHdkw9OHUl3MrXtZ7QmHyOAFvbXE/6n5Eeh+eZoF8MWWV72m14Wbs+vTcNQkFVTdOPptkKA8e4cJqwDOHsyAnvQXZ7WNje9+BMzcoipIUawHP4ORFaPDsKLZQ0b4wBbKn8ziea6zjGF0/qljTdoxTtsYpv5wXYuhwbYklrLOqgSa5M7LXUe7E3g9mbg+9iX1GuB8m6GkquJN814Y+xny4xhZzGOfue6SeP12jJMNSjSP7416dRq7794VGnkkW9V/7oFEUKu5wO9FFbgDySOSlEjByGejSGuBmho0iJSjcPjZ7EY/j3M3orq4dpza5C82OeSvxDFc7ATQRdPOnsAQgA5oLxXRLnyugzOmNCy7dxV3JrDZscA6JNlJlDWIShT0YSs+zG9JzDeQql+sYXgUSxOoIayItuXtnFn7tstwGoOnYvadm/e5/7V5fKAQRtCtdN51av62n18Venlm0yNKpROPcZ6M/sc4m6uU6YRZ/a1idal8VGY0wLKlghjIBuIiBoVQ/RnoW+/fhmwIg08dQ5m8hQe3GEOZEeLrTWL/9awPlXK7Y+DmJOoR4qbHWEcRfbzS6q4zW8vk2ztB8ngwbnqYy8zrN1DCICN1gYdlU++uVw6Bb1XfY8Cdldh1VLKpF35mAmjxLZfVDcoObFH3Cv2GB7BEYxv86KC2Y6T74Q/wARAQABwsB2BBgBCAAgBQJdPOn4AhsMFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcegLxwf/dXshJnoWqEnRsf6rVb9/Mc66ti+NVQLfNd275dybh/QIJdK3NmSxdnTPIEJRVspJywJoupwXFNrnHG2Ff6QPvHqI+/oNu986r9d7Z1oQibbLHKt8t6kpOfg/xGxotagJuiCQvR9mMjD1DqsdB37SjDxGupZOOJSXWi6KX60IE+uM+QOBfeOZziQwuFmA5wV6RDXIeYJfqrcbeXeR1d0nfNpPHQR1gBiqmxNb6KBbdXD2+EXW60axC7D2b1APmzMlammDliPwsK9U1rc9nuquEBvGDOJf4K+Dzn+mDCqRpP6uAuQ7RKHyim4uyN0wwKObzPqgJCGwjTglkixw+aSTXw==

View File

@@ -0,0 +1,21 @@
Return-Path: <x@testrun.org>
Received: from hq5.merlinux.eu
by hq5.merlinux.eu (Dovecot) with LMTP id yRKOBakcfV1AewAAPzvFDg
; Sat, 14 Sep 2019 19:00:25 +0200
Received: from localhost (unknown 7.165.105.24])
by hq5.merlinux.eu (Postfix) with ESMTPSA id 8D9844E023;
Sat, 14 Sep 2019 19:00:22 +0200 (CEST)
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=testrun.org;
s=testrun; t=1568480425;
bh=GrziQ9bkbioILHZ1tEn81B53bDgaZK+ENbW4MUBiMUM=;
h=Date:From:To:In-Reply-To:References:Subject:From;
b=eHP/GdBQD1cq601Gaqpz7fd+nQvBJLH7c+bk9KGGC3BKZrRAzehEDOaFlGP2+9oFq
cl+y11nGowenVAw7M5ljjxCpOVy0Aa+atn3e7iZeufgOJPr5Hggg2nPe4qfyP6OmOJ
i9RazyXN1rp4fY7ku9x29g818UL8aWiloJHJ9wqDhMe8OXtIAhJA0gpHDCT1SLgo/a
Kv7/Yj4CA8XS/mRxkbBmpWgcF7SluNjuXU9Npo3aTfjQQ5xSzn72Jsai4n0ppLcQ5k
KobttFy+gkmfQRAe8v7vx51qn82BSsGRlxZWevOB9zit/uk7zb8ZbBqPMeCoWsihRM
LdSqwIpokmYmQ==
Date: Sat, 14 Sep 2019 19:00:13 +0200
From: lmn <x@tux.org>
To: abc <abc@bcd.com>, def <def@def.de>,
jik

View File

@@ -1,13 +1,13 @@
//! Stress some functions for testing; if used as a lib, this file is obsolete.
use std::collections::HashSet;
use std::path::PathBuf;
use std::ptr;
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::*;
@@ -15,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
@@ -30,103 +30,67 @@ static mut S_EM_SETUPFILE: *const libc::c_char =
as *const u8 as *const libc::c_char;
unsafe fn stress_functions(context: &Context) {
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/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
);
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"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);
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");
}
assert!(dc_write_file(context, "$BLOBDIR/foobar", b"content"));
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(
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 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn0, PathBuf::from("$BLOBDIR/foobar.dadada"));
assert!(dc_write_file(context, &fn0, b"content"));
let fn1 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn1, PathBuf::from("$BLOBDIR/foobar-1.dadada"));
assert!(dc_delete_file(context, &fn0));
let res = context.get_config(config::Config::SysConfigKeys).unwrap();
@@ -567,12 +531,7 @@ fn test_encryption_decryption() {
assert_eq!(plain, original_text);
}
unsafe extern "C" fn cb(
_context: &Context,
_event: Event,
_data1: uintptr_t,
_data2: uintptr_t,
) -> uintptr_t {
fn cb(_context: &Context, _event: Event) -> libc::uintptr_t {
0
}
@@ -582,21 +541,16 @@ struct TestContext {
dir: TempDir,
}
unsafe fn create_test_context() -> TestContext {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
fn create_test_context() -> TestContext {
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()
);
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
TestContext { ctx: ctx, dir: dir }
}
#[test]
fn test_dc_get_oauth2_url() {
let ctx = unsafe { create_test_context() };
let ctx = 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);
@@ -606,7 +560,7 @@ fn test_dc_get_oauth2_url() {
#[test]
fn test_dc_get_oauth2_addr() {
let ctx = unsafe { create_test_context() };
let ctx = create_test_context();
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
@@ -616,7 +570,7 @@ fn test_dc_get_oauth2_addr() {
#[test]
fn test_dc_get_oauth2_token() {
let ctx = unsafe { create_test_context() };
let ctx = create_test_context();
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0);
@@ -634,50 +588,33 @@ fn test_stress_tests() {
#[test]
fn test_get_contacts() {
unsafe {
let context = create_test_context();
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
assert_eq!(contacts.len(), 0);
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() {
unsafe {
let context = create_test_context();
let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
assert_ne!(contact1, 0);
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);
}
}
#[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);
}
assert_eq!(chat2.name, chat.name);
}