Compare commits

..

12 Commits

Author SHA1 Message Date
holger krekel
ad4be80b4e make smtp/imap connect() return bool instead of c-int 2019-07-17 10:25:25 +02:00
holger krekel
8737c1d142 cleanup some parts, add comments 2019-07-17 09:26:33 +02:00
holger krekel
964fe466cc wip-commit which passes all tests with proper finalization 2019-07-16 20:05:41 +02:00
holger krekel
43936e7db7 snapshot of my current debugging state 2019-07-16 16:17:42 +02:00
holger krekel
0e80ce9c39 more aggressively skip perform API when threads are closing 2019-07-16 12:57:19 +02:00
holger krekel
c652bae68a intermediate wip commit 2019-07-16 12:06:05 +02:00
holger krekel
bc904a495d add some logging, and a more precise teardown for online python tests 2019-07-16 11:18:56 +02:00
holger krekel
8d99444c6a fix std 2019-07-16 00:22:12 +02:00
holger krekel
9dab53e0af rustfmt 2019-07-16 00:20:54 +02:00
holger krekel
360089ac74 remove some debugging 2019-07-16 00:08:10 +02:00
holger krekel
e892c5cf4d fix test for events 2019-07-15 23:31:30 +02:00
holger krekel
9ad4c9a6fe wip try test that we see INFO events from the core 2019-07-15 22:51:57 +02:00
89 changed files with 15278 additions and 16473 deletions

View File

@@ -127,26 +127,26 @@ jobs:
machine: True
steps:
- checkout
# - run: docker pull deltachat/doxygen
# - run: docker pull deltachat/doxygen
- run: docker pull deltachat/coredeps
- run:
name: build docs, run tests and build wheels
- run:
name: build docs, run tests and build wheels
command: ci_scripts/ci_run.sh
environment:
TESTS: 1
DOCS: 1
- run:
name: copying docs and wheels to workspace
- run:
name: copying docs and wheels to workspace
command: |
mkdir -p workspace/python
# cp -av docs workspace/c-docs
cp -av python/.docker-tox/wheelhouse workspace/
cp -av python/doc/_build/ workspace/py-docs
- persist_to_workspace:
root: workspace
paths:
- persist_to_workspace:
root: workspace
paths:
# - c-docs
- py-docs
- wheelhouse
@@ -157,7 +157,7 @@ jobs:
- checkout
- attach_workspace:
at: workspace
- run: ls -laR workspace
- run: ls -laR workspace
- run: ci_scripts/ci_upload.sh workspace/py-docs workspace/wheelhouse
@@ -169,23 +169,18 @@ workflows:
- build_test_docs_wheel
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- build_test_docs_wheel
- cargo_fetch
- rustfmt:
requires:
- cargo_fetch
# Linux Desktop 64bit
# Linux Desktop
- test_x86_64-unknown-linux-gnu:
requires:
- cargo_fetch
# Linux Desktop 32bit
# - test_i686-unknown-linux-gnu:
# requires:
# - cargo_fetch
# Android 64bit
# Linux Desktop
# - test_aarch64-linux-android:
# requires:
# - cargo_fetch

2
.gitattributes vendored
View File

@@ -2,7 +2,7 @@
# ensures this even if the user has not set core.autocrlf.
* text=auto
# binary files should be detected by git, however, to be sure, you can add them here explicitly
# binary files should be detected by git, however, to be sure, you can add them here explictly
*.png binary
*.jpg binary
*.gif binary

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
# ignore vi temporaries
*~
@@ -16,5 +17,3 @@ python/.tox
*.egg-info
__pycache__
python/src/deltachat/capi*.so
python/liveconfig*

2965
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-alpha.3"
version = "1.0.0-alpha.2"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
license = "MPL"
@@ -16,6 +16,7 @@ hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
smallvec = "0.6.9"
libsqlite3-sys = { version = "0.14.0", features = ["bundled", "min_sqlite_version_3_7_16"] }
reqwest = "0.9.15"
num-derive = "0.2.5"
num-traits = "0.2.6"
@@ -25,7 +26,7 @@ imap = "1.0.1"
mmime = "0.1.0"
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"
percent-encoding = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4.6"
@@ -35,16 +36,6 @@ failure_derive = "0.1.5"
rustyline = "4.1.0"
lazy_static = "1.3.0"
regex = "1.1.6"
rusqlite = { version = "0.20", features = ["bundled"] }
addr = "0.2.0"
r2d2_sqlite = "0.12.0"
r2d2 = "0.8.5"
strum = "0.15.0"
strum_macros = "0.15.0"
thread-local-object = "0.1.0"
backtrace = "0.3.33"
byteorder = "1.3.1"
itertools = "0.8.0"
[dev-dependencies]
tempfile = "3.0"

View File

@@ -63,11 +63,6 @@ Single#10: yourfriends@email.org [yourfriends@email.org]
Message sent.
```
If `yourfriend@email.org` uses DeltaChat, but does not receive message just
sent, it is advisable to check `Spam` folder. It is known that at least
`gmx.com` treat such test messages as spam, unless told otherwise with web
interface.
List messages when inside a chat:
```

View File

@@ -1,10 +1,11 @@
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
TARGET: x86_64-pc-windows-msvc
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain nightly-2019-07-10
- rustup-init -yv --default-host %target% --default-toolchain none
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- rustc -vV
- cargo -vV
@@ -13,7 +14,7 @@ install:
build: false
test_script:
- cargo test --release
- cargo test --release
cache:
- target

View File

@@ -4,7 +4,6 @@ set -ex
export RUST_TEST_THREADS=1
export RUST_BACKTRACE=1
export RUSTFLAGS='--deny warnings'
export OPT="--target=$TARGET"
export OPT_RELEASE="--release ${OPT}"
export OPT_FFI_RELEASE="--manifest-path=deltachat-ffi/Cargo.toml --release"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-alpha.3"
version = "1.0.0-alpha.1"
description = "Deltachat FFI"
authors = ["dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2018"
@@ -18,11 +18,9 @@ crate-type = ["cdylib", "staticlib"]
deltachat = { path = "../", default-features = false }
libc = "0.2"
human-panic = "1.0.1"
num-traits = "0.2.6"
[features]
default = ["vendored", "nightly", "ringbuf"]
vendored = ["deltachat/vendored"]
nightly = ["deltachat/nightly"]
ringbuf = ["deltachat/ringbuf"]

View File

@@ -1,33 +0,0 @@
use std::io::Write;
use std::path::PathBuf;
use std::{env, fs};
fn main() {
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let target_path = out_path.join("../../..");
let target_triple = env::var("TARGET").unwrap();
// macOS or iOS, inherited from rpgp
let libs_priv = if target_triple.contains("apple") || target_triple.contains("darwin") {
// needed for OsRng
"-framework Security -framework Foundation"
} else {
""
};
let pkg_config = format!(
include_str!("deltachat.pc.in"),
name = "deltachat",
description = env::var("CARGO_PKG_DESCRIPTION").unwrap(),
url = env::var("CARGO_PKG_HOMEPAGE").unwrap_or("".to_string()),
version = env::var("CARGO_PKG_VERSION").unwrap(),
libs_priv = libs_priv,
prefix = env::var("PREFIX").unwrap_or("/usr/local".to_string()),
);
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
.unwrap()
.write_all(&pkg_config.as_bytes())
.unwrap();
}

View File

@@ -503,8 +503,8 @@ int dc_chat_is_sending_locations (const dc_chat_t*);
#define DC_STATE_OUT_MDN_RCVD 28
#define DC_MAX_GET_TEXT_LEN 30000 // approx. max. length returned by dc_msg_get_text()
#define DC_MAX_GET_INFO_LEN 100000 // approx. max. length returned by dc_get_msg_info()
#define DC_MAX_GET_TEXT_LEN 30000 // approx. max. lenght returned by dc_msg_get_text()
#define DC_MAX_GET_INFO_LEN 100000 // approx. max. lenght returned by dc_get_msg_info()
dc_msg_t* dc_msg_new (dc_context_t*, int viewtype);
@@ -667,7 +667,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t*);
* A voice message that was directly recorded by the user.
* For all other audio messages, the type #DC_MSG_AUDIO should be used.
* File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
* and retrieved via dc_msg_get_file(), dc_msg_get_duration()
* and retieved via dc_msg_get_file(), dc_msg_get_duration()
*/
#define DC_MSG_VOICE 41
@@ -768,9 +768,9 @@ int64_t dc_lot_get_timestamp (const dc_lot_t*);
* @}
*/
#define DC_LP_AUTH_FLAGS (DC_LP_AUTH_OAUTH2|DC_LP_AUTH_NORMAL) // if none of these flags are set, the default is chosen
#define DC_LP_IMAP_SOCKET_FLAGS (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
#define DC_LP_SMTP_SOCKET_FLAGS (DC_LP_SMTP_SOCKET_STARTTLS|DC_LP_SMTP_SOCKET_SSL|DC_LP_SMTP_SOCKET_PLAIN) // if none of these flags are set, the default is chosen
#define DC_LP_AUTH_FLAGS (DC_LP_AUTH_OAUTH2|DC_LP_AUTH_NORMAL) // if none of these flags are set, the default is choosen
#define DC_LP_IMAP_SOCKET_FLAGS (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 choosen
#define DC_LP_SMTP_SOCKET_FLAGS (DC_LP_SMTP_SOCKET_STARTTLS|DC_LP_SMTP_SOCKET_SSL|DC_LP_SMTP_SOCKET_PLAIN) // if none of these flags are set, the default is choosen
@@ -851,7 +851,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t*);
* 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
* As most things are asynchrounous, 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. dc_configure())
@@ -882,7 +882,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t*);
*
* 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.
* instread 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

View File

@@ -1,11 +0,0 @@
prefix={prefix}
libdir=${{prefix}}/lib
includedir=${{prefix}}/include
Name: {name}
Description: {description}
URL: {url}
Version: {version}
Cflags: -I${{includedir}}
Libs: -L${{libdir}} -ldeltachat
Libs.private: {libs_priv}

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,10 @@
use std::ffi::CString;
use std::str::FromStr;
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_array::*;
use deltachat::dc_chat::*;
use deltachat::dc_chatlist::*;
use deltachat::dc_configure::*;
use deltachat::dc_contact::*;
use deltachat::dc_imex::*;
use deltachat::dc_job::*;
use deltachat::dc_location::*;
@@ -16,9 +12,9 @@ use deltachat::dc_lot::*;
use deltachat::dc_msg::*;
use deltachat::dc_qr::*;
use deltachat::dc_receive_imf::*;
use deltachat::dc_sqlite3::*;
use deltachat::dc_tools::*;
use deltachat::peerstate::*;
use deltachat::sql;
use deltachat::types::*;
use deltachat::x::*;
use num_traits::FromPrimitive;
@@ -29,64 +25,65 @@ use num_traits::FromPrimitive;
pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
info!(context, 0, "Resetting tables ({})...", bits);
if 0 != bits & 1 {
sql::execute(context, &context.sql, "DELETE FROM jobs;", params![]).unwrap();
dc_sqlite3_execute(
context,
&context.sql,
b"DELETE FROM jobs;\x00" as *const u8 as *const libc::c_char,
);
info!(context, 0, "(1) Jobs reset.");
}
if 0 != bits & 2 {
sql::execute(
dc_sqlite3_execute(
context,
&context.sql,
"DELETE FROM acpeerstates;",
params![],
)
.unwrap();
b"DELETE FROM acpeerstates;\x00" as *const u8 as *const libc::c_char,
);
info!(context, 0, "(2) Peerstates reset.");
}
if 0 != bits & 4 {
sql::execute(context, &context.sql, "DELETE FROM keypairs;", params![]).unwrap();
dc_sqlite3_execute(
context,
&context.sql,
b"DELETE FROM keypairs;\x00" as *const u8 as *const libc::c_char,
);
info!(context, 0, "(4) Private keypairs reset.");
}
if 0 != bits & 8 {
sql::execute(
dc_sqlite3_execute(
context,
&context.sql,
"DELETE FROM contacts WHERE id>9;",
params![],
)
.unwrap();
sql::execute(
b"DELETE FROM contacts WHERE id>9;\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_execute(
context,
&context.sql,
"DELETE FROM chats WHERE id>9;",
params![],
)
.unwrap();
sql::execute(
b"DELETE FROM chats WHERE id>9;\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_execute(
context,
&context.sql,
"DELETE FROM chats_contacts;",
params![],
)
.unwrap();
sql::execute(
b"DELETE FROM chats_contacts;\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_execute(
context,
&context.sql,
"DELETE FROM msgs WHERE id>9;",
params![],
)
.unwrap();
sql::execute(
b"DELETE FROM msgs WHERE id>9;\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_execute(
context,
&context.sql,
"DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';",
params![],
)
.unwrap();
sql::execute(context, &context.sql, "DELETE FROM leftgrps;", params![]).unwrap();
b"DELETE FROM config WHERE keyname LIKE \'imap.%\' OR keyname LIKE \'configured%\';\x00"
as *const u8 as *const libc::c_char,
);
dc_sqlite3_execute(
context,
&context.sql,
b"DELETE FROM leftgrps;\x00" as *const u8 as *const libc::c_char,
);
info!(context, 0, "(8) Rest but server config reset.");
}
context.call_cb(Event::MSGS_CHANGED, 0, 0);
context.call_cb(Event::MSGS_CHANGED, 0 as uintptr_t, 0 as uintptr_t);
1
}
@@ -103,7 +100,14 @@ unsafe fn dc_poke_eml_file(context: &Context, filename: *const libc::c_char) ->
&mut data_bytes,
) == 0i32)
{
dc_receive_imf(context, data, data_bytes, "import", 0, 0);
dc_receive_imf(
context,
data,
data_bytes,
b"import\x00" as *const u8 as *const libc::c_char,
0i32 as uint32_t,
0i32 as uint32_t,
);
success = 1;
}
free(data as *mut libc::c_void);
@@ -134,20 +138,26 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
/* if `spec` is given, remember it for later usage; if it is not given, try to use the last one */
if !spec.is_null() {
real_spec = dc_strdup(spec);
context
.sql
.set_config(context, "import_spec", Some(as_str(real_spec)))
.unwrap();
dc_sqlite3_set_config(
context,
&context.sql,
b"import_spec\x00" as *const u8 as *const libc::c_char,
real_spec,
);
current_block = 7149356873433890176;
} else {
let rs = context.sql.get_config(context, "import_spec");
if rs.is_none() {
real_spec = dc_sqlite3_get_config(
context,
&context.sql,
b"import_spec\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
if real_spec.is_null() {
error!(context, 0, "Import: No file or folder given.");
current_block = 8522321847195001863;
} else {
current_block = 7149356873433890176;
}
real_spec = rs.unwrap_or_default().strdup();
}
match current_block {
8522321847195001863 => {}
@@ -184,7 +194,8 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
if name.ends_with(".eml") {
let path_plus_name = format!("{}/{}", as_str(real_spec), name);
info!(context, 0, "Import: {}", path_plus_name);
let path_plus_name_c = CString::yolo(path_plus_name);
let path_plus_name_c = to_cstring(path_plus_name);
if 0 != dc_poke_eml_file(context, path_plus_name_c.as_ptr()) {
read_cnt += 1
}
@@ -218,19 +229,18 @@ unsafe fn poke_spec(context: &Context, spec: *const libc::c_char) -> libc::c_int
}
unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: *mut dc_msg_t) {
let contact = Contact::get_by_id(context, dc_msg_get_from_id(msg)).expect("invalid contact");
let contact_name = contact.get_name();
let contact_id = contact.get_id();
let contact: *mut dc_contact_t = dc_get_contact(context, dc_msg_get_from_id(msg));
let contact_name: *mut libc::c_char = dc_contact_get_name(contact);
let contact_id: libc::c_int = dc_contact_get_id(contact) as libc::c_int;
let statestr = match dc_msg_get_state(msg) {
DC_STATE_OUT_PENDING => " o",
DC_STATE_OUT_DELIVERED => "",
DC_STATE_OUT_MDN_RCVD => " √√",
DC_STATE_OUT_FAILED => " !!",
20 => " o",
26 => "",
28 => " √√",
24 => " !!",
_ => "",
};
let temp2 = dc_timestamp_to_str(dc_msg_get_timestamp(msg));
let msgtext = dc_msg_get_text(msg);
let temp2: *mut libc::c_char = dc_timestamp_to_str(dc_msg_get_timestamp(msg));
let msgtext: *mut libc::c_char = dc_msg_get_text(msg);
info!(
context,
0,
@@ -243,15 +253,19 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: *mut dc_msg_t
""
},
if dc_msg_has_location(msg) { "📍" } else { "" },
&contact_name,
as_str(contact_name),
contact_id,
as_str(msgtext),
if dc_msg_is_starred(msg) { "" } else { "" },
if 0 != dc_msg_is_starred(msg) {
""
} else {
""
},
if dc_msg_get_from_id(msg) == 1 as libc::c_uint {
""
} else if dc_msg_get_state(msg) == DC_STATE_IN_SEEN {
} else if dc_msg_get_state(msg) == 16 {
"[SEEN]"
} else if dc_msg_get_state(msg) == DC_STATE_IN_NOTICED {
} else if dc_msg_get_state(msg) == 13 {
"[NOTICED]"
} else {
"[FRESH]"
@@ -262,9 +276,12 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: *mut dc_msg_t
""
},
statestr,
&temp2,
as_str(temp2),
);
free(msgtext as *mut libc::c_void);
free(temp2 as *mut libc::c_void);
free(contact_name as *mut libc::c_void);
dc_contact_unref(contact);
}
unsafe fn log_msglist(context: &Context, msglist: *mut dc_array_t) {
@@ -302,6 +319,7 @@ unsafe fn log_msglist(context: &Context, msglist: *mut dc_array_t) {
}
unsafe fn log_contactlist(context: &Context, contacts: *mut dc_array_t) {
let mut contact: *mut dc_contact_t;
if !dc_array_search_id(contacts, 1 as uint32_t, 0 as *mut size_t) {
dc_array_add_id(contacts, 1 as uint32_t);
}
@@ -310,12 +328,13 @@ unsafe fn log_contactlist(context: &Context, contacts: *mut dc_array_t) {
let contact_id = dc_array_get_id(contacts, i as size_t);
let line;
let mut line2 = "".to_string();
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
let name = contact.get_name();
let addr = contact.get_addr();
let verified_state = contact.is_verified();
let verified_str = if VerifiedStatus::Unverified != verified_state {
if verified_state == VerifiedStatus::BidirectVerified {
contact = dc_get_contact(context, contact_id);
if !contact.is_null() {
let name: *mut libc::c_char = dc_contact_get_name(contact);
let addr: *mut libc::c_char = dc_contact_get_addr(contact);
let verified_state: libc::c_int = dc_contact_is_verified(contact);
let verified_str = if 0 != verified_state {
if verified_state == 2 {
" √√"
} else {
""
@@ -325,26 +344,28 @@ unsafe fn log_contactlist(context: &Context, contacts: *mut dc_array_t) {
};
line = format!(
"{}{} <{}>",
if !name.is_empty() {
&name
if !name.is_null() && 0 != *name.offset(0isize) as libc::c_int {
as_str(name)
} else {
"<name unset>"
},
verified_str,
if !addr.is_empty() {
&addr
if !addr.is_null() && 0 != *addr.offset(0isize) as libc::c_int {
as_str(addr)
} else {
"addr unset"
}
);
let peerstate = Peerstate::from_addr(context, &context.sql, &addr);
let peerstate = Peerstate::from_addr(context, &context.sql, as_str(addr));
if peerstate.is_some() && contact_id != 1 as libc::c_uint {
line2 = format!(
", prefer-encrypt={}",
peerstate.as_ref().unwrap().prefer_encrypt
);
}
dc_contact_unref(contact);
free(name as *mut libc::c_void);
free(addr as *mut libc::c_void);
info!(context, 0, "Contact#{}: {}{}", contact_id, line, line2);
}
}
@@ -377,16 +398,18 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let mut args = line.splitn(3, ' ');
let arg0 = args.next().unwrap_or_default();
let arg1 = args.next().unwrap_or_default();
let arg1_c = if arg1.is_empty() {
let arg1_c = to_cstring(arg1);
let arg1_c_ptr = if arg1.is_empty() {
std::ptr::null()
} else {
arg1.strdup() as *const _
arg1_c.as_ptr()
};
let arg2 = args.next().unwrap_or_default();
let arg2_c = if arg2.is_empty() {
let arg2_c = to_cstring(arg2);
let arg2_c_ptr = if arg2.is_empty() {
std::ptr::null()
} else {
arg2.strdup() as *const _
arg2_c.as_ptr()
};
match arg0 {
@@ -440,7 +463,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
dellocations\n\
getlocations [<contact-id>]\n\
send <text>\n\
send-garbage\n\
sendimage <file> [<text>]\n\
sendfile <file>\n\
draft [<text>]\n\
@@ -477,10 +499,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
},
"auth" => {
if 0 == S_IS_AUTH {
let is_pw = context
.get_config(config::Config::MailPw)
.unwrap_or_default();
if arg1 == is_pw {
let is_pw =
dc_get_config(context, b"mail_pw\x00" as *const u8 as *const libc::c_char);
if arg1 == as_str(is_pw) {
S_IS_AUTH = 1;
} else {
println!("Bad password.");
@@ -492,7 +513,10 @@ 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");
ensure!(
0 != dc_open(context, arg1_c_ptr, 0 as *const libc::c_char),
"Open failed"
);
}
"close" => {
dc_close(context);
@@ -511,10 +535,10 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"get-setupcodebegin" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let msg_id: u32 = arg1.parse()?;
let msg_id: u32 = arg1.parse().unwrap();
let msg: *mut dc_msg_t = dc_get_msg(context, msg_id);
if dc_msg_is_setupmessage(msg) {
let setupcodebegin = dc_msg_get_setupcodebegin(msg);
if 0 != dc_msg_is_setupmessage(msg) {
let setupcodebegin: *mut libc::c_char = dc_msg_get_setupcodebegin(msg);
println!(
"The setup code for setup message Msg#{} starts with: {}",
msg_id,
@@ -531,7 +555,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
!arg1.is_empty() && !arg2.is_empty(),
"Arguments <msg-id> <setup-code> expected"
);
if 0 == dc_continue_key_transfer(context, arg1.parse()?, arg2_c) {
if 0 == dc_continue_key_transfer(context, arg1.parse().unwrap(), arg2_c_ptr) {
bail!("Continue key transfer failed");
}
}
@@ -546,7 +570,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
dc_imex(context, 12, arg1_c, 0 as *const libc::c_char);
dc_imex(context, 12, arg1_c_ptr, 0 as *const libc::c_char);
}
"export-keys" => {
dc_imex(context, 1, context.get_blobdir(), 0 as *const libc::c_char);
@@ -555,26 +579,39 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
dc_imex(context, 2, context.get_blobdir(), 0 as *const libc::c_char);
}
"export-setup" => {
let setup_code = dc_create_setup_code(context);
let setup_code: *mut libc::c_char = 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_content = dc_render_setup_file(context, &setup_code)?;
std::fs::write(as_str(file_name), file_content)?;
println!(
"Setup message written to: {}\nSetup code: {}",
as_str(file_name),
&setup_code,
);
let file_content: *mut libc::c_char;
file_content = dc_render_setup_file(context, setup_code);
if !file_content.is_null()
&& 0 != dc_write_file(
context,
file_name,
file_content as *const libc::c_void,
strlen(file_content),
)
{
println!(
"Setup message written to: {}\nSetup code: {}",
as_str(file_name),
as_str(setup_code),
)
} else {
bail!("");
}
free(file_content as *mut libc::c_void);
free(file_name as *mut libc::c_void);
free(setup_code as *mut libc::c_void);
}
"poke" => {
ensure!(0 != poke_spec(context, arg1_c), "Poke failed");
ensure!(0 != poke_spec(context, arg1_c_ptr), "Poke failed");
}
"reset" => {
ensure!(!arg1.is_empty(), "Argument <bits> missing: 1=jobs, 2=peerstates, 4=private keys, 8=rest but server config");
let bits: i32 = arg1.parse()?;
let bits: i32 = arg1.parse().unwrap();
ensure!(bits < 16, "<bits> must be lower than 16.");
ensure!(0 != dc_reset_tables(context, bits), "Reset failed");
}
@@ -583,15 +620,16 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"set" => {
ensure!(!arg1.is_empty(), "Argument <key> missing.");
let key = config::Config::from_str(&arg1)?;
let value = if arg2.is_empty() { None } else { Some(arg2) };
context.set_config(key, value)?;
ensure!(
0 != dc_set_config(context, arg1_c_ptr, arg2_c_ptr),
"Set config failed"
);
}
"get" => {
ensure!(!arg1.is_empty(), "Argument <key> missing.");
let key = config::Config::from_str(&arg1)?;
let val = context.get_config(key);
println!("{}={:?}", key, val);
let val = dc_get_config(context, arg1_c_ptr);
println!("{}={}", arg1, to_string(val));
free(val as *mut libc::c_void);
}
"info" => {
println!("{}", to_string(dc_get_info(context)));
@@ -600,14 +638,15 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
dc_maybe_network(context);
}
"housekeeping" => {
sql::housekeeping(context);
dc_housekeeping(context);
}
"listchats" | "listarchived" | "chats" => {
let listflags = if arg0 == "listarchived" { 0x01 } else { 0 };
let chatlist = Chatlist::try_load(context, listflags, Some(arg1), None)?;
let chatlist = dc_get_chatlist(context, listflags, arg1_c_ptr, 0 as uint32_t);
ensure!(!chatlist.is_null(), "Failed to retrieve chatlist");
let mut i: usize;
let cnt = chatlist.len();
let mut i: libc::c_int;
let cnt = dc_chatlist_get_cnt(chatlist) as libc::c_int;
if cnt > 0 {
info!(
context, 0,
@@ -616,8 +655,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
i = cnt - 1;
while i > 0 {
let chat = dc_get_chat(context, chatlist.get_chat_id(i));
while i >= 0 {
let chat = dc_get_chat(context, dc_chatlist_get_chat_id(chatlist, i as size_t));
let temp_subtitle = dc_chat_get_subtitle(chat);
let temp_name = dc_chat_get_name(chat);
info!(
@@ -632,7 +671,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
free(temp_subtitle as *mut libc::c_void);
free(temp_name as *mut libc::c_void);
let lot = chatlist.get_summary(i, chat);
let lot = dc_chatlist_get_summary(chatlist, i as size_t, chat);
let statestr = if 0 != dc_chat_get_archived(chat) {
" [Archived]"
} else {
@@ -655,7 +694,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
if !text1.is_null() { ": " } else { "" },
to_string(text2),
statestr,
&timestr,
as_str(timestr),
if 0 != dc_chat_is_sending_locations(chat) {
"📍"
} else {
@@ -664,6 +703,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
free(text1 as *mut libc::c_void);
free(text2 as *mut libc::c_void);
free(timestr as *mut libc::c_void);
dc_lot_unref(lot);
dc_chat_unref(chat);
info!(
@@ -674,10 +714,11 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
i -= 1
}
}
if dc_is_sending_locations_to_chat(context, 0 as uint32_t) {
if 0 != dc_is_sending_locations_to_chat(context, 0 as uint32_t) {
info!(context, 0, "Location streaming enabled.");
}
println!("{} chats", cnt);
dc_chatlist_unref(chatlist);
}
"chat" => {
if sel_chat.is_null() && arg1.is_empty() {
@@ -687,7 +728,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
dc_chat_unref(sel_chat);
}
if !arg1.is_empty() {
let chat_id = arg1.parse()?;
let chat_id = arg1.parse().unwrap();
println!("Selecting chat #{}", chat_id);
sel_chat = dc_get_chat(context, chat_id);
*context.cmdline_sel_chat_id.write().unwrap() = chat_id;
@@ -731,7 +772,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 contact_id: libc::c_int = arg1.parse().unwrap();
let chat_id: libc::c_int =
dc_create_chat_by_contact_id(context, contact_id as uint32_t) as libc::c_int;
if chat_id != 0 {
@@ -742,7 +783,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"createchatbymsg" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing");
let msg_id_0: libc::c_int = arg1.parse()?;
let msg_id_0: libc::c_int = arg1.parse().unwrap();
let chat_id_0: libc::c_int =
dc_create_chat_by_msg_id(context, msg_id_0 as uint32_t) as libc::c_int;
if chat_id_0 != 0 {
@@ -759,7 +800,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"creategroup" => {
ensure!(!arg1.is_empty(), "Argument <name> missing.");
let chat_id_1: libc::c_int = dc_create_group_chat(context, 0, arg1_c) as libc::c_int;
let chat_id_1: libc::c_int =
dc_create_group_chat(context, 0, arg1_c_ptr) as libc::c_int;
if chat_id_1 != 0 {
println!("Group#{} created successfully.", chat_id_1,);
} else {
@@ -768,7 +810,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"createverified" => {
ensure!(!arg1.is_empty(), "Argument <name> missing.");
let chat_id_2: libc::c_int = dc_create_group_chat(context, 1, arg1_c) as libc::c_int;
let chat_id_2: libc::c_int =
dc_create_group_chat(context, 1, arg1_c_ptr) as libc::c_int;
if chat_id_2 != 0 {
println!("VerifiedGroup#{} created successfully.", chat_id_2,);
} else {
@@ -779,7 +822,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!sel_chat.is_null(), "No chat selected");
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id_0: libc::c_int = arg1.parse()?;
let contact_id_0: libc::c_int = arg1.parse().unwrap();
if 0 != dc_add_contact_to_chat(
context,
dc_chat_get_id(sel_chat),
@@ -793,7 +836,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"removemember" => {
ensure!(!sel_chat.is_null(), "No chat selected.");
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id_1: libc::c_int = arg1.parse()?;
let contact_id_1: libc::c_int = arg1.parse().unwrap();
if 0 != dc_remove_contact_from_chat(
context,
dc_chat_get_id(sel_chat),
@@ -807,7 +850,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"groupname" => {
ensure!(!sel_chat.is_null(), "No chat selected.");
ensure!(!arg1.is_empty(), "Argument <name> missing.");
if 0 != dc_set_chat_name(context, dc_chat_get_id(sel_chat), arg1_c) {
if 0 != dc_set_chat_name(context, dc_chat_get_id(sel_chat), arg1_c_ptr) {
println!("Chat name set");
} else {
bail!("Failed to set chat name");
@@ -821,7 +864,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
context,
dc_chat_get_id(sel_chat),
if !arg1.is_empty() {
arg1_c
arg1_c_ptr
} else {
std::ptr::null_mut()
},
@@ -858,7 +901,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
0,
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} Msg#{} {}",
dc_array_get_id(loc, j as size_t),
&timestr_0,
as_str(timestr_0),
dc_array_get_latitude(loc, j as size_t),
dc_array_get_longitude(loc, j as size_t),
dc_array_get_accuracy(loc, j as size_t),
@@ -871,6 +914,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"-"
},
);
free(timestr_0 as *mut libc::c_void);
free(marker as *mut libc::c_void);
j += 1
}
@@ -883,17 +927,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!sel_chat.is_null(), "No chat selected.");
ensure!(!arg1.is_empty(), "No timeout given.");
let seconds = arg1.parse()?;
let seconds = arg1.parse().unwrap();
dc_send_locations_to_chat(context, dc_chat_get_id(sel_chat), seconds);
println!("Locations will be sent to Chat#{} for {} seconds. Use 'setlocation <lat> <lng>' to play around.", dc_chat_get_id(sel_chat), seconds);
println!("Locations will be sent to Chat#{} for {} seconds. Use \'setlocation <lat> <lng>\' to play around.", dc_chat_get_id(sel_chat), seconds);
}
"setlocation" => {
ensure!(
!arg1.is_empty() && !arg2.is_empty(),
"Latitude or longitude not given."
);
let latitude = arg1.parse()?;
let longitude = arg2.parse()?;
let latitude = arg1.parse().unwrap();
let longitude = arg2.parse().unwrap();
let continue_streaming = dc_set_location(context, latitude, longitude, 0.);
if 0 != continue_streaming {
@@ -909,9 +953,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!sel_chat.is_null(), "No chat selected.");
ensure!(!arg1.is_empty(), "No message text given.");
let msg = format!("{} {}", arg1, arg2);
let msg = to_cstring(format!("{} {}", arg1, arg2));
if 0 != dc_send_text_msg(context, dc_chat_get_id(sel_chat), msg) {
if 0 != dc_send_text_msg(context, dc_chat_get_id(sel_chat), msg.as_ptr()) {
println!("Message sent.");
} else {
bail!("Sending failed.");
@@ -919,7 +963,11 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"sendempty" => {
ensure!(!sel_chat.is_null(), "No chat selected.");
if 0 != dc_send_text_msg(context, dc_chat_get_id(sel_chat), "".into()) {
if 0 != dc_send_text_msg(
context,
dc_chat_get_id(sel_chat),
b"\x00" as *const u8 as *const libc::c_char,
) {
println!("Message sent.");
} else {
bail!("Sending failed.");
@@ -929,16 +977,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!sel_chat.is_null(), "No chat selected.");
ensure!(!arg1.is_empty() && !arg2.is_empty(), "No file given.");
let msg_0 = dc_msg_new(
context,
if arg0 == "sendimage" {
Viewtype::Image
} else {
Viewtype::File
},
);
dc_msg_set_file(msg_0, arg1_c, 0 as *const libc::c_char);
dc_msg_set_text(msg_0, arg2_c);
let msg_0 = dc_msg_new(context, if arg0 == "sendimage" { 20 } else { 60 });
dc_msg_set_file(msg_0, arg1_c_ptr, 0 as *const libc::c_char);
dc_msg_set_text(msg_0, arg2_c_ptr);
dc_send_msg(context, dc_chat_get_id(sel_chat), msg_0);
dc_msg_unref(msg_0);
}
@@ -951,7 +992,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
0 as libc::c_uint
};
let msglist_0 = dc_search_msgs(context, chat, arg1_c);
let msglist_0 = dc_search_msgs(context, chat, arg1_c_ptr);
if !msglist_0.is_null() {
log_msglist(context, msglist_0);
@@ -963,8 +1004,8 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!sel_chat.is_null(), "No chat selected.");
if !arg1.is_empty() {
let draft_0 = dc_msg_new(context, Viewtype::Text);
dc_msg_set_text(draft_0, arg1_c);
let draft_0 = dc_msg_new(context, 10);
dc_msg_set_text(draft_0, arg1_c_ptr);
dc_set_draft(context, dc_chat_get_id(sel_chat), draft_0);
dc_msg_unref(draft_0);
println!("Draft saved.");
@@ -976,13 +1017,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"listmedia" => {
ensure!(!sel_chat.is_null(), "No chat selected.");
let images = dc_get_chat_media(
context,
dc_chat_get_id(sel_chat),
Viewtype::Image,
Viewtype::Gif,
Viewtype::Video,
);
let images = dc_get_chat_media(context, dc_chat_get_id(sel_chat), 20, 21, 50);
let icnt: libc::c_int = dc_array_get_cnt(images) as libc::c_int;
println!("{} images or videos: ", icnt);
for i in 0..icnt {
@@ -998,17 +1033,17 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"archive" | "unarchive" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
let chat_id = arg1.parse()?;
let chat_id = arg1.parse().unwrap();
dc_archive_chat(context, chat_id, if arg0 == "archive" { 1 } else { 0 });
}
"delchat" => {
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
let chat_id = arg1.parse()?;
let chat_id = arg1.parse().unwrap();
dc_delete_chat(context, chat_id);
}
"msginfo" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let id = arg1.parse()?;
let id = arg1.parse().unwrap();
let res = dc_get_msg_info(context, id);
println!("{}", as_str(res));
}
@@ -1027,20 +1062,20 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
);
let mut msg_ids = [0; 1];
let chat_id = arg2.parse()?;
msg_ids[0] = arg1.parse()?;
let chat_id = arg2.parse().unwrap();
msg_ids[0] = arg1.parse().unwrap();
dc_forward_msgs(context, msg_ids.as_mut_ptr(), 1, chat_id);
}
"markseen" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut msg_ids = [0; 1];
msg_ids[0] = arg1.parse()?;
msg_ids[0] = arg1.parse().unwrap();
dc_markseen_msgs(context, msg_ids.as_mut_ptr(), 1);
}
"star" | "unstar" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut msg_ids = [0; 1];
msg_ids[0] = arg1.parse()?;
msg_ids[0] = arg1.parse().unwrap();
dc_star_msgs(
context,
msg_ids.as_mut_ptr(),
@@ -1051,19 +1086,19 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"delmsg" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let mut ids = [0; 1];
ids[0] = arg1.parse()?;
ids[0] = arg1.parse().unwrap();
dc_delete_msgs(context, ids.as_mut_ptr(), 1);
}
"listcontacts" | "contacts" | "listverified" => {
let contacts = Contact::get_all(
let contacts = dc_get_contacts(
context,
if arg0 == "listverified" {
0x1 | 0x2
} else {
0x2
},
Some(arg1),
)?;
arg1_c_ptr,
);
if !contacts.is_null() {
log_contactlist(context, contacts);
println!("{} contacts.", dc_array_get_cnt(contacts) as libc::c_int,);
@@ -1076,25 +1111,36 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
ensure!(!arg1.is_empty(), "Arguments [<name>] <addr> expected.");
if !arg2.is_empty() {
let book = format!("{}\n{}", arg1, arg2);
Contact::add_address_book(context, book)?;
let book = dc_mprintf(
b"%s\n%s\x00" as *const u8 as *const libc::c_char,
arg1_c_ptr,
arg2_c_ptr,
);
dc_add_address_book(context, book);
free(book as *mut libc::c_void);
} else {
Contact::create(context, "", arg1)?;
if 0 == dc_create_contact(context, 0 as *const libc::c_char, arg1_c_ptr) {
bail!("Failed to create contact");
}
}
}
"contactinfo" => {
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
let contact_id = arg1.parse()?;
let contact = Contact::get_by_id(context, contact_id)?;
let name_n_addr = contact.get_name_n_addr();
let contact_id = arg1.parse().unwrap();
let contact = dc_get_contact(context, contact_id);
let name_n_addr = dc_contact_get_name_n_addr(contact);
let mut res = format!("Contact info for: {}:\n\n", name_n_addr);
let mut res = format!("Contact info for: {}:\n\n", as_str(name_n_addr),);
free(name_n_addr as *mut libc::c_void);
dc_contact_unref(contact);
res += &Contact::get_encrinfo(context, contact_id);
let encrinfo = dc_get_contact_encrinfo(context, contact_id);
res += as_str(encrinfo);
free(encrinfo as *mut libc::c_void);
let chatlist = Chatlist::try_load(context, 0, None, Some(contact_id))?;
let chatlist_cnt = chatlist.len();
let chatlist = dc_get_chatlist(context, 0, 0 as *const libc::c_char, contact_id);
let chatlist_cnt = dc_chatlist_get_cnt(chatlist) as libc::c_int;
if chatlist_cnt > 0 {
res += &format!(
"\n\n{} chats shared with Contact#{}: ",
@@ -1104,21 +1150,23 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
if 0 != i {
res += ", ";
}
let chat = dc_get_chat(context, chatlist.get_chat_id(i));
let chat = dc_get_chat(context, dc_chatlist_get_chat_id(chatlist, i as size_t));
res += &format!("{}#{}", chat_prefix(chat), dc_chat_get_id(chat));
dc_chat_unref(chat);
}
}
dc_chatlist_unref(chatlist);
println!("{}", res);
}
"delcontact" => {
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
Contact::delete(context, arg1.parse()?)?;
if !dc_delete_contact(context, arg1.parse().unwrap()) {
bail!("Failed to delete contact");
}
}
"checkqr" => {
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
let res = dc_check_qr(context, arg1_c);
let res = dc_check_qr(context, arg1_c_ptr);
println!(
"state={}, id={}, text1={}, text2={}",
(*res).state as libc::c_int,
@@ -1130,8 +1178,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
}
"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 event = Event::from_u32(arg1.parse().unwrap()).unwrap();
let r = context.call_cb(event, 0 as uintptr_t, 0 as uintptr_t);
println!(
"Sending event {:?}({}), received value {}.",
@@ -1147,7 +1194,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
if 0 != dc_read_file(
context,
arg1_c,
arg1_c_ptr,
&mut buf as *mut *mut libc::c_uchar as *mut *mut libc::c_void,
&mut buf_bytes,
) {
@@ -1158,7 +1205,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
bail!("Command failed.");
}
}
"" => (),
_ => bail!("Unknown command: \"{}\" type ? for help.", arg0),
}
@@ -1166,8 +1212,5 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
dc_chat_unref(sel_chat);
}
free(arg1_c as *mut _);
free(arg2_c as *mut _);
Ok(())
}

View File

@@ -3,7 +3,6 @@
//!
//! Usage: cargo run --example repl --release -- <databasefile>
//! All further options can be set using the set-command (type ? for help).
#![feature(ptr_cast)]
#[macro_use]
extern crate deltachat;
@@ -11,14 +10,11 @@ extern crate deltachat;
extern crate failure;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate rusqlite;
use std::borrow::Cow::{self, Borrowed, Owned};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use deltachat::config;
use deltachat::constants::*;
use deltachat::context::*;
use deltachat::dc_configure::*;
@@ -392,13 +388,19 @@ fn main_0(args: Vec<String>) -> Result<(), failure::Error> {
let mut context = dc_context_new(
Some(receive_event),
0 as *mut libc::c_void,
Some("CLI".into()),
b"CLI\x00" as *const u8 as *const libc::c_char,
);
unsafe { dc_cmdline_skip_auth() };
if args.len() == 2 {
if unsafe { !dc_open(&mut context, &args[1], None) } {
if 0 == unsafe {
dc_open(
&mut context,
to_cstring(&args[1]).as_ptr(),
0 as *const libc::c_char,
)
} {
println!("Error: Cannot open {}.", args[0],);
}
} else if args.len() != 1 {
@@ -477,10 +479,11 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
let mut args = line.splitn(2, ' ');
let arg0 = args.next().unwrap_or_default();
let arg1 = args.next().unwrap_or_default();
let arg1_c = if arg1.is_empty() {
let arg1_c = to_cstring(arg1);
let arg1_c_ptr = if arg1.is_empty() {
std::ptr::null()
} else {
arg1.strdup()
arg1_c.as_ptr()
};
match arg0 {
@@ -509,20 +512,25 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
dc_configure(&ctx.read().unwrap());
}
"oauth2" => {
if let Some(addr) = ctx.read().unwrap().get_config(config::Config::Addr) {
let addr = dc_get_config(
&ctx.read().unwrap(),
b"addr\x00" as *const u8 as *const libc::c_char,
);
if addr.is_null() || *addr.offset(0isize) as libc::c_int == 0i32 {
println!("oauth2: set addr first.");
} else {
let oauth2_url = dc_get_oauth2_url(
&ctx.read().unwrap(),
&addr,
as_str(addr),
"chat.delta:/com.b44t.messenger",
);
if oauth2_url.is_none() {
println!("OAuth2 not available for {}.", &addr);
println!("OAuth2 not available for {}.", to_string(addr));
} else {
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{}", oauth2_url.unwrap());
}
} else {
println!("oauth2: set addr first.");
}
free(addr as *mut libc::c_void);
}
"clear" => {
println!("\n\n\n");
@@ -553,15 +561,13 @@ unsafe fn handle_cmd(line: &str, ctx: Arc<RwLock<Context>>) -> Result<ExitResult
"joinqr" => {
start_threads(ctx.clone());
if !arg0.is_empty() {
dc_join_securejoin(&ctx.read().unwrap(), arg1_c);
dc_join_securejoin(&ctx.read().unwrap(), arg1_c_ptr);
}
}
"exit" => return Ok(ExitResult::Exit),
_ => dc_cmdline(&ctx.read().unwrap(), line)?,
}
free(arg1_c as *mut _);
Ok(ExitResult::Continue)
}

View File

@@ -1,17 +1,16 @@
extern crate deltachat;
use std::ffi::CStr;
use std::ffi::{CStr, CString};
use std::sync::{Arc, RwLock};
use std::{thread, time};
use tempfile::tempdir;
use deltachat::chatlist::*;
use deltachat::config;
use deltachat::constants::Event;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_chat::*;
use deltachat::dc_chatlist::*;
use deltachat::dc_configure::*;
use deltachat::dc_contact::*;
use deltachat::dc_job::{
dc_perform_imap_fetch, dc_perform_imap_idle, dc_perform_imap_jobs, dc_perform_smtp_idle,
dc_perform_smtp_jobs,
@@ -41,7 +40,7 @@ extern "C" fn cb(_ctx: &Context, event: Event, data1: usize, data2: usize) -> us
fn main() {
unsafe {
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let ctx = dc_context_new(Some(cb), std::ptr::null_mut(), std::ptr::null_mut());
let running = Arc::new(RwLock::new(true));
let info = dc_get_info(&ctx);
let info_s = CStr::from_ptr(info);
@@ -76,34 +75,40 @@ fn main() {
});
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let dbfile = CString::new(dir.path().join("db.sqlite").to_str().unwrap()).unwrap();
println!("opening database {:?}", dbfile);
assert!(dc_open(&ctx, dbfile.to_str().unwrap(), None));
dc_open(&ctx, dbfile.as_ptr(), std::ptr::null());
println!("configuring");
let args = std::env::args().collect::<Vec<String>>();
assert_eq!(args.len(), 2, "missing password");
let pw = args[1].clone();
ctx.set_config(config::Config::Addr, Some("d@testrun.org"))
.unwrap();
ctx.set_config(config::Config::MailPw, Some(&pw)).unwrap();
let pw = std::env::args().collect::<Vec<String>>()[1].clone();
dc_set_config(
&ctx,
CString::new("addr").unwrap().as_ptr(),
CString::new("d@testrun.org").unwrap().as_ptr(),
);
dc_set_config(
&ctx,
CString::new("mail_pw").unwrap().as_ptr(),
CString::new(pw).unwrap().as_ptr(),
);
dc_configure(&ctx);
thread::sleep(duration);
let email = CString::new("dignifiedquire@gmail.com").unwrap();
println!("sending a message");
let contact_id =
Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
let contact_id = dc_create_contact(&ctx, std::ptr::null(), email.as_ptr());
let chat_id = dc_create_chat_by_contact_id(&ctx, contact_id);
dc_send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into());
let msg_text = CString::new("Hi, here is my first message!").unwrap();
dc_send_text_msg(&ctx, chat_id, msg_text.as_ptr());
println!("fetching chats..");
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
let chats = dc_get_chatlist(&ctx, 0, std::ptr::null(), 0);
for i in 0..chats.len() {
let summary = chats.get_summary(0, std::ptr::null_mut());
for i in 0..dc_chatlist_get_cnt(chats) {
let summary = dc_chatlist_get_summary(chats, 0, std::ptr::null_mut());
let text1 = dc_lot_get_text1(summary);
let text2 = dc_lot_get_text2(summary);
@@ -120,9 +125,10 @@ fn main() {
println!("chat: {} - {:?} - {:?}", i, text1_s, text2_s,);
dc_lot_unref(summary);
}
dc_chatlist_unref(chats);
thread::sleep(duration);
*running.clone().write().unwrap() = false;
println!("stopping threads");
// let msglist = dc_get_chat_msgs(&ctx, chat_id, 0, 0);
// for i in 0..dc_array_get_cnt(msglist) {
// let msg_id = dc_array_get_id(msglist, i);
@@ -133,9 +139,6 @@ fn main() {
// }
// dc_array_unref(msglist);
println!("stopping threads");
*running.clone().write().unwrap() = false;
deltachat::dc_job::dc_interrupt_imap_idle(&ctx);
deltachat::dc_job::dc_interrupt_smtp_idle(&ctx);

View File

@@ -4,12 +4,6 @@
- introduce automatic versioning via setuptools_scm,
based on py-X.Y.Z tags
- integrate latest DCC core-rust with dc_close() fixes
- provide a account.shutdown() method and improve termination
logic also in tests. also fixes output-clubbering during
test runs.
0.600.0
---------

View File

@@ -11,6 +11,8 @@ high level API reference
- :class:`deltachat.chatting.Contact`
- :class:`deltachat.chatting.Chat`
- :class:`deltachat.message.Message`
- :class:`deltachat.message.MessageType`
- :class:`deltachat.message.MessageState`
Account
-------

View File

@@ -288,6 +288,10 @@ intersphinx_mapping = {'http://docs.python.org/': None}
autodoc_member_order = "bysource"
# always document __init__ functions
def skip(app, what, name, obj, skip, options):
import attr
if name == "__init__":
if not hasattr(obj.im_class, "__attrs_attrs__"):
return False
return skip
def setup(app):

7
python/install_py_bindings.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -ex
cargo build -p deltachat_ffi
rm -rf build/ src/deltachat/*.so
DCC_RS_DEV=`pwd`/.. pip install -e .

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env python
"""
setup a python binding development in-place install with cargo debug symbols.
"""
import os
import subprocess
if __name__ == "__main__":
os.environ["DCC_RS_TARGET"] = target = "release"
toml = os.path.join(os.getcwd(), "..", "Cargo.toml")
assert os.path.exists(toml)
with open(toml) as f:
s = orig = f.read()
s += "\n"
s += "[profile.release]\n"
s += "debug = true\n"
with open(toml, "w") as f:
f.write(s)
print("temporarily modifying Cargo.toml to provide release build with debug symbols ")
try:
subprocess.check_call([
"cargo", "build", "-p", "deltachat_ffi", "--" + target
])
finally:
with open(toml, "w") as f:
f.write(orig)
print("\nreseted Cargo.toml to previous original state")
subprocess.check_call("rm -rf build/ src/deltachat/*.so" , shell=True)
subprocess.check_call([
"pip", "install", "-e", "."
])

View File

@@ -17,7 +17,7 @@ def main():
description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat',
long_description=long_description,
author='holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors',
install_requires=['cffi>=1.0.0', 'six'],
install_requires=['cffi>=1.0.0', 'attrs', 'six'],
packages=setuptools.find_packages('src'),
package_dir={'': 'src'},
cffi_modules=['src/deltachat/_build.py:ffibuilder'],

View File

@@ -61,10 +61,7 @@ def set_context_callback(dc_context, func):
def clear_context_callback(dc_context):
try:
_DC_CALLBACK_MAP.pop(dc_context, None)
except AttributeError:
pass
_DC_CALLBACK_MAP.pop(dc_context, None)
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):

View File

@@ -25,13 +25,11 @@ def ffibuilder():
else:
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
objs = [os.path.join(projdir, 'target', target, 'libdeltachat.a')]
assert os.path.exists(objs[0]), objs
incs = [os.path.join(projdir, 'deltachat-ffi')]
else:
libs = ['deltachat']
objs = []
incs = []
extra_link_args = []
builder = cffi.FFI()
builder.set_source(
'deltachat.capi',
@@ -71,8 +69,8 @@ def ffibuilder():
distutils.sysconfig.customize_compiler(cc)
tmpdir = tempfile.mkdtemp()
try:
src_name = os.path.join(tmpdir, "include.h")
dst_name = os.path.join(tmpdir, "expanded.h")
src_name = os.path.join(tmpdir, "prep.h")
dst_name = os.path.join(tmpdir, "prep2.c")
with open(src_name, "w") as src_fp:
src_fp.write('#include <deltachat.h>')
cc.preprocess(source=src_name,

View File

@@ -7,9 +7,9 @@ import re
import time
from array import array
try:
from queue import Queue, Empty
from queue import Queue
except ImportError:
from Queue import Queue, Empty
from Queue import Queue
import deltachat
from . import const
@@ -36,17 +36,14 @@ class Account(object):
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
_destroy_dc_context,
)
if eventlogging:
self._evlogger = EventLogger(self._dc_context, logid)
deltachat.set_context_callback(self._dc_context, self._process_event)
self._threads = IOThreads(self._dc_context, self._evlogger._log_event)
else:
self._threads = IOThreads(self._dc_context)
if hasattr(db_path, "encode"):
db_path = db_path.encode("utf8")
if not lib.dc_open(self._dc_context, db_path, ffi.NULL):
raise ValueError("Could not dc_open: {}".format(db_path))
if eventlogging:
self._evlogger = EventLogger(self._dc_context, logid)
deltachat.set_context_callback(self._dc_context, self._process_event)
self._threads = IOThreads(self._dc_context)
self._configkeys = self.get_config("sys.config_keys").split()
self._imex_completed = threading.Event()
@@ -142,6 +139,15 @@ class Account(object):
self.check_is_configured()
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF)
def create_message(self, view_type):
""" create a new non persistent message.
:param view_type: a string specifying "text", "video",
"image", "audio" or "file".
:returns: :class:`deltachat.message.Message` instance.
"""
return Message.new(self._dc_context, view_type)
def create_contact(self, email, name=None):
""" create a (new) Contact. If there already is a Contact
with that e-mail address, it is unblocked and its name is
@@ -157,17 +163,6 @@ class Account(object):
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
return Contact(self._dc_context, contact_id)
def delete_contact(self, contact):
""" delete a Contact.
:param contact: contact object obtained
:returns: True if deletion succeeded (contact was deleted)
"""
contact_id = contact.id
assert contact._dc_context == self._dc_context
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
return bool(lib.dc_delete_contact(self._dc_context, contact_id))
def get_contacts(self, query=None, with_self=False, only_verified=False):
""" get a (filtered) list of contacts.
@@ -203,7 +198,7 @@ class Account(object):
assert isinstance(contact, int)
contact_id = contact
chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id)
return Chat(self, chat_id)
return Chat(self._dc_context, chat_id)
def create_chat_by_message(self, message):
""" create or get an existing chat object for the
@@ -220,7 +215,7 @@ class Account(object):
assert isinstance(message, int)
msg_id = message
chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)
return Chat(self, chat_id)
return Chat(self._dc_context, chat_id)
def create_group_chat(self, name, verified=False):
""" create a new group chat object.
@@ -232,7 +227,7 @@ class Account(object):
"""
bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name)
return Chat(self, chat_id)
return Chat(self._dc_context, chat_id)
def get_chats(self):
""" return list of chats.
@@ -248,15 +243,15 @@ class Account(object):
chatlist = []
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
chatlist.append(Chat(self, chat_id))
chatlist.append(Chat(self._dc_context, chat_id))
return chatlist
def get_deaddrop_chat(self):
return Chat(self, const.DC_CHAT_ID_DEADDROP)
return Chat(self._dc_context, const.DC_CHAT_ID_DEADDROP)
def get_message_by_id(self, msg_id):
""" return Message instance. """
return Message.from_db(self, msg_id)
return Message.from_db(self._dc_context, msg_id)
def mark_seen_messages(self, messages):
""" mark the given set of messages as seen.
@@ -345,7 +340,7 @@ 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"):
if hasattr(self, "_dc_context"):
self.stop_threads(wait=False) # to interrupt idle and tell python threads to stop
lib.dc_close(self._dc_context)
self.stop_threads(wait=wait) # to wait for threads
@@ -367,11 +362,10 @@ class Account(object):
class IOThreads:
def __init__(self, dc_context, log_event=lambda *args: None):
def __init__(self, dc_context):
self._dc_context = dc_context
self._thread_quitflag = False
self._name2thread = {}
self._log_event = log_event
def is_started(self):
return len(self._name2thread) > 0
@@ -397,19 +391,17 @@ class IOThreads:
thread.join()
def imap_thread_run(self):
self._log_event("py-bindings-info", 0, "IMAP THREAD START")
while not self._thread_quitflag:
lib.dc_perform_imap_jobs(self._dc_context)
lib.dc_perform_imap_fetch(self._dc_context)
lib.dc_perform_imap_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "IMAP THREAD FINISHED")
print("IMAP_THREAD finished")
def smtp_thread_run(self):
self._log_event("py-bindings-info", 0, "SMTP THREAD START")
while not self._thread_quitflag:
lib.dc_perform_smtp_jobs(self._dc_context)
lib.dc_perform_smtp_idle(self._dc_context)
self._log_event("py-bindings-info", 0, "SMTP THREAD FINISHED")
print("SMTP_THREAD finished")
class EventLogger:
@@ -439,17 +431,6 @@ class EventLogger:
raise ValueError("{}({!r},{!r})".format(*ev))
return ev
def ensure_event_not_queued(self, event_name_regex):
__tracebackhide__ = True
rex = re.compile("(?:{}).*".format(event_name_regex))
while 1:
try:
ev = self._event_queue.get(False)
except Empty:
break
else:
assert not rex.match(ev[0]), "event found {}".format(ev)
def get_matching(self, event_name_regex, check_error=True):
self._log("-- waiting for event with regex: {} --".format(event_name_regex))
rex = re.compile("(?:{}).*".format(event_name_regex))

View File

@@ -1,30 +1,24 @@
""" chatting related objects: Contact, Chat, Message. """
import mimetypes
import os
from . import props
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi
from . import const
import attr
from attr import validators as v
from .message import Message
@attr.s
class Contact(object):
""" Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, dc_context, id):
self._dc_context = dc_context
self.id = id
def __eq__(self, other):
return self._dc_context == other._dc_context and self.id == other.id
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
id = attr.ib(validator=v.instance_of(int))
@property
def _dc_contact(self):
@@ -52,26 +46,14 @@ class Contact(object):
return lib.dc_contact_is_verified(self._dc_contact)
@attr.s
class Chat(object):
""" Chat object which manages members and through which you can send and retrieve messages.
You obtain instances of it through :class:`deltachat.account.Account`.
"""
def __init__(self, account, id):
self.account = account
self._dc_context = account._dc_context
self.id = id
def __eq__(self, other):
return self.id == getattr(other, "id", None) and \
self._dc_context == getattr(other, "_dc_context", None)
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "<Chat id={} name={} dc_context={}>".format(self.id, self.get_name(), self._dc_context)
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
id = attr.ib(validator=v.instance_of(int))
@property
def _dc_chat(self):
@@ -144,7 +126,7 @@ class Chat(object):
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
if msg_id == 0:
raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self.account, msg_id)
return Message.from_db(self._dc_context, msg_id)
def send_file(self, path, mime_type="application/octet-stream"):
""" send a file and return the resulting Message instance.
@@ -154,9 +136,14 @@ class Chat(object):
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
msg = self.prepare_message_file(path=path, mime_type=mime_type)
self.send_prepared(msg)
return msg
path = as_dc_charpointer(path)
mtype = as_dc_charpointer(mime_type)
msg = Message.new(self._dc_context, "file")
msg.set_file(path, mtype)
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self._dc_context, msg_id)
def send_image(self, path):
""" send an image message and return the resulting Message instance.
@@ -165,25 +152,14 @@ class Chat(object):
:raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance
"""
mime_type = mimetypes.guess_type(path)[0]
msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
self.send_prepared(msg)
return msg
if not os.path.exists(path):
raise ValueError("path does not exist: {!r}".format(path))
msg = Message.new(self._dc_context, "image")
msg.set_file(path)
msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg)
return Message.from_db(self._dc_context, msg_id)
def prepare_message(self, msg):
""" create a new prepared message.
:param msg: the message to be prepared.
:returns: :class:`deltachat.message.Message` instance.
"""
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be prepared")
# invalidate passed in message which is not safe to use anymore
msg._dc_msg = msg.id = None
return Message.from_db(self.account, msg_id)
def prepare_message_file(self, path, mime_type=None, view_type="file"):
def prepare_file(self, path, mime_type=None, view_type="file"):
""" prepare a message for sending and return the resulting Message instance.
To actually send the message, call :meth:`send_prepared`.
@@ -191,13 +167,18 @@ class Chat(object):
:param path: path to the file.
:param mime_type: the mime-type of this file, defaults to auto-detection.
:param view_type: "text", "image", "gif", "audio", "video", "file"
:param view_type: passed to :meth:`MessageType.new`.
:raises ValueError: if message can not be prepared/chat does not exist.
:returns: the resulting :class:`Message` instance
"""
msg = Message.new_empty(self.account, view_type)
msg.set_file(path, mime_type)
return self.prepare_message(msg)
path = as_dc_charpointer(path)
mtype = as_dc_charpointer(mime_type)
msg = Message.new(self._dc_context, view_type)
msg.set_file(path, mtype)
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg)
if msg_id == 0:
raise ValueError("message could not be prepared, does chat exist?")
return Message.from_db(self._dc_context, msg_id)
def send_prepared(self, message):
""" send a previously prepared message.
@@ -205,42 +186,12 @@ class Chat(object):
:param message: a :class:`Message` instance previously returned by
:meth:`prepare_file`.
:raises ValueError: if message can not be sent.
:returns: a :class:`deltachat.message.Message` instance as sent out.
:returns: a :class:`deltachat.message.Message` instance with updated state
"""
assert message.id != 0 and message.is_out_preparing()
# get a fresh copy of dc_msg, the core needs it
msg = Message.from_db(self.account, message.id)
# pass 0 as chat-id because core-docs say it's ok when out-preparing
sent_id = lib.dc_send_msg(self._dc_context, 0, msg._dc_msg)
if sent_id == 0:
msg_id = lib.dc_send_msg(self._dc_context, 0, message._dc_msg)
if msg_id == 0:
raise ValueError("message could not be sent")
assert sent_id == msg.id
# modify message in place to avoid bad state for the caller
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
def set_draft(self, message):
""" set message as draft.
:param message: a :class:`Message` instance
:returns: None
"""
if message is None:
lib.dc_set_draft(self._dc_context, self.id, ffi.NULL)
else:
lib.dc_set_draft(self._dc_context, self.id, message._dc_msg)
def get_draft(self):
""" get draft message for this chat.
:param message: a :class:`Message` instance
:returns: Message object or None (if no draft available)
"""
x = lib.dc_get_draft(self._dc_context, self.id)
if x == ffi.NULL:
return None
dc_msg = ffi.gc(x, lib.dc_msg_unref)
return Message(self.account, dc_msg)
return Message.from_db(self._dc_context, msg_id)
def get_messages(self):
""" return list of messages in this chat.
@@ -251,7 +202,7 @@ class Chat(object):
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0),
lib.dc_array_unref
)
return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x)))
return list(iter_array(dc_array, lambda x: Message.from_db(self._dc_context, x)))
def count_fresh_messages(self):
""" return number of fresh messages in this chat.

View File

@@ -1,55 +1,59 @@
""" chatting related objects: Contact, Chat, Message. """
import os
import shutil
from . import props
from .cutil import from_dc_charpointer, as_dc_charpointer
from .capi import lib, ffi
from . import const
from datetime import datetime
import attr
from attr import validators as v
@attr.s
class Message(object):
""" Message object.
You obtain instances of it through :class:`deltachat.account.Account` or
:class:`deltachat.chatting.Chat`.
"""
def __init__(self, account, dc_msg):
self.account = account
self._dc_context = account._dc_context
assert isinstance(self._dc_context, ffi.CData)
assert isinstance(dc_msg, ffi.CData)
assert dc_msg != ffi.NULL
self._dc_msg = dc_msg
self.id = lib.dc_msg_get_id(dc_msg)
assert self.id is not None and self.id >= 0, repr(self.id)
_dc_context = attr.ib(validator=v.instance_of(ffi.CData))
try:
id = attr.ib(validator=v.instance_of((int, long)))
except NameError: # py35
id = attr.ib(validator=v.instance_of(int))
def __eq__(self, other):
return self.account == other.account and self.id == other.id
def __repr__(self):
return "<Message id={} dc_context={}>".format(self.id, self._dc_context)
@classmethod
def from_db(cls, account, id):
assert id > 0
return cls(account, ffi.gc(
lib.dc_get_msg(account._dc_context, id),
@property
def _dc_msg(self):
if self.id > 0:
return ffi.gc(
lib.dc_get_msg(self._dc_context, self.id),
lib.dc_msg_unref
))
)
return self._dc_msg_volatile
@classmethod
def new_empty(cls, account, view_type):
""" create a non-persistent message.
def from_db(cls, _dc_context, id):
assert id > 0
return cls(_dc_context, id)
:param: view_type is "text", "audio", "video", "file"
"""
view_type_code = get_viewtype_code_from_name(view_type)
return Message(account, ffi.gc(
lib.dc_msg_new(account._dc_context, view_type_code),
@classmethod
def new(cls, dc_context, view_type):
""" create a non-persistent method. """
msg = cls(dc_context, 0)
view_type_code = MessageType.get_typecode(view_type)
msg._dc_msg_volatile = ffi.gc(
lib.dc_msg_new(dc_context, view_type_code),
lib.dc_msg_unref
))
)
return msg
def get_state(self):
""" get the message in/out state.
:returns: :class:`deltachat.message.MessageState`
"""
return MessageState(self)
@props.with_doc
def text(self):
@@ -58,9 +62,7 @@ class Message(object):
def set_text(self, text):
"""set text of this message. """
assert self.id > 0, "message not prepared"
assert self.is_out_preparing()
lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
return lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text))
@props.with_doc
def filename(self):
@@ -68,23 +70,9 @@ class Message(object):
return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg))
def set_file(self, path, mime_type=None):
"""set file for this message from path and mime_type. """
mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
if not os.path.exists(path):
raise ValueError("path does not exist: {!r}".format(path))
blobdir = self.account.get_blobdir()
if not path.startswith(blobdir):
for i in range(50):
ext = "" if i == 0 else "-" + str(i)
dest = os.path.join(blobdir, os.path.basename(path) + ext)
if os.path.exists(dest):
continue
shutil.copyfile(path, dest)
break
else:
raise ValueError("could not create blobdir-path for {}".format(path))
path = dest
assert path.startswith(blobdir), path
"""set file for this message. """
mtype = ffi.NULL if mime_type is None else mime_type
assert os.path.exists(path)
lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
@props.with_doc
@@ -97,17 +85,18 @@ class Message(object):
"""mime type of the file (if it exists)"""
return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg))
@props.with_doc
def view_type(self):
"""the view type of this message.
:returns: a :class:`deltachat.message.MessageType` instance.
"""
return MessageType(lib.dc_msg_get_viewtype(self._dc_msg))
def is_setup_message(self):
""" return True if this message is a setup message. """
return lib.dc_msg_is_setupmessage(self._dc_msg)
def get_message_info(self):
""" Return informational text for a single message.
The text is multiline and may contain eg. the raw text of the message.
"""
return from_dc_charpointer(lib.dc_get_msg_info(self._dc_context, self.id))
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))
@@ -155,7 +144,7 @@ class Message(object):
"""
from .chatting import Chat
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
return Chat(self.account, chat_id)
return Chat(self._dc_context, chat_id)
def get_sender_contact(self):
"""return the contact of who wrote the message.
@@ -166,20 +155,66 @@ class Message(object):
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
return Contact(self._dc_context, contact_id)
#
# Message State query methods
#
@attr.s
class MessageType(object):
""" DeltaChat message type, with is_* methods. """
_type = attr.ib(validator=v.instance_of(int))
_mapping = {
const.DC_MSG_TEXT: 'text',
const.DC_MSG_IMAGE: 'image',
const.DC_MSG_GIF: 'gif',
const.DC_MSG_AUDIO: 'audio',
const.DC_MSG_VIDEO: 'video',
const.DC_MSG_FILE: 'file'
}
@classmethod
def get_typecode(cls, view_type):
for code, value in cls._mapping.items():
if value == view_type:
return code
raise ValueError("message typecode not found for {!r}".format(view_type))
@props.with_doc
def name(self):
""" human readable type name. """
return self._mapping.get(self._type, "")
def is_text(self):
""" return True if it's a text message. """
return self._type == const.DC_MSG_TEXT
def is_image(self):
""" return True if it's an image message. """
return self._type == const.DC_MSG_IMAGE
def is_gif(self):
""" return True if it's a gif message. """
return self._type == const.DC_MSG_GIF
def is_audio(self):
""" return True if it's an audio message. """
return self._type == const.DC_MSG_AUDIO
def is_video(self):
""" return True if it's a video message. """
return self._type == const.DC_MSG_VIDEO
def is_file(self):
""" return True if it's a file message. """
return self._type == const.DC_MSG_FILE
@attr.s
class MessageState(object):
""" Current Message In/Out state, updated on each call of is_* methods.
"""
message = attr.ib(validator=v.instance_of(Message))
@property
def _msgstate(self):
if self.id == 0:
dc_msg = self.message._dc_msg
else:
# load message from db to get a fresh/current state
dc_msg = ffi.gc(
lib.dc_get_msg(self._dc_context, self.id),
lib.dc_msg_unref
)
return lib.dc_msg_get_state(dc_msg)
return lib.dc_msg_get_state(self.message._dc_msg)
def is_in_fresh(self):
""" return True if Message is incoming fresh message (un-noticed).
@@ -233,56 +268,3 @@ class Message(object):
state, you'll receive the event DC_EVENT_MSG_READ.
"""
return self._msgstate == const.DC_STATE_OUT_MDN_RCVD
#
# Message type query methods
#
@property
def _view_type(self):
assert self.id > 0
return lib.dc_msg_get_viewtype(self._dc_msg)
def is_text(self):
""" return True if it's a text message. """
return self._view_type == const.DC_MSG_TEXT
def is_image(self):
""" return True if it's an image message. """
return self._view_type == const.DC_MSG_IMAGE
def is_gif(self):
""" return True if it's a gif message. """
return self._view_type == const.DC_MSG_GIF
def is_audio(self):
""" return True if it's an audio message. """
return self._view_type == const.DC_MSG_AUDIO
def is_video(self):
""" return True if it's a video message. """
return self._view_type == const.DC_MSG_VIDEO
def is_file(self):
""" return True if it's a file message. """
return self._view_type == const.DC_MSG_FILE
# some code for handling DC_MSG_* view types
_view_type_mapping = {
const.DC_MSG_TEXT: 'text',
const.DC_MSG_IMAGE: 'image',
const.DC_MSG_GIF: 'gif',
const.DC_MSG_AUDIO: 'audio',
const.DC_MSG_VIDEO: 'video',
const.DC_MSG_FILE: 'file'
}
def get_viewtype_code_from_name(view_type_name):
for code, value in _view_type_mapping.items():
if value == view_type_name:
return code
raise ValueError("message typecode not found for {!r}, "
"available {!r}".format(view_type_name, list(_view_type_mapping.values())))

View File

@@ -16,14 +16,6 @@ def pytest_addoption(parser):
)
def pytest_configure(config):
cfg = config.getoption('--liveconfig')
if not cfg:
cfg = os.getenv('DCC_PY_LIVECONFIG')
if cfg:
config.option.liveconfig = cfg
@pytest.hookimpl(trylast=True)
def pytest_runtest_call(item):
# perform early finalization because we otherwise get cloberred
@@ -43,14 +35,10 @@ def pytest_report_header(config, startdir):
ac.shutdown()
finally:
os.remove(t)
summary = ['Deltachat core={} sqlite={}'.format(
info['deltachat_core_version'],
info['sqlite_version'],
)]
cfg = config.getoption('--liveconfig')
if cfg:
summary.append('Liveconfig: {}'.format(os.path.abspath(cfg)))
return summary
return "Deltachat core={} sqlite={}".format(
info['deltachat_core_version'],
info['sqlite_version'],
)
@pytest.fixture(scope="session")
@@ -86,7 +74,7 @@ def acfactory(pytestconfig, tmpdir, request):
def configlist(self):
configlist = []
for line in open(fn):
if line.strip() and not line.strip().startswith('#'):
if line.strip():
d = {}
for part in line.split():
name, value = part.split("=")

View File

@@ -1,19 +1,12 @@
from __future__ import print_function
import pytest
import os
from deltachat import const, Account
from deltachat.message import Message
from deltachat import const
from datetime import datetime, timedelta
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
class TestOfflineAccountBasic:
def test_wrong_db(self, tmpdir):
p = tmpdir.join("hello.db")
p.write("123")
with pytest.raises(ValueError):
Account(p.strpath)
class TestOfflineAccount:
def test_getinfo(self, acfactory):
ac1 = acfactory.get_unconfigured_account()
d = ac1.get_info()
@@ -58,22 +51,16 @@ class TestOfflineAccountBasic:
with pytest.raises(KeyError):
ac1.get_config("123123")
class TestOfflineContact:
def test_contact_attr(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
contact2 = ac1.create_contact(email="some1@hello.com", name="some1")
str(contact1)
repr(contact1)
assert contact1 == contact2
assert contact1.id
assert contact1.addr == "some1@hello.com"
assert contact1.display_name == "some1"
assert not contact1.is_blocked()
assert not contact1.is_verified()
def test_get_contacts_and_delete(self, acfactory):
def test_get_contacts(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@hello.com", name="some1")
contacts = ac1.get_contacts()
@@ -86,48 +73,26 @@ class TestOfflineContact:
contacts = ac1.get_contacts(with_self=True)
assert len(contacts) == 2
assert ac1.delete_contact(contact1)
assert contact1 not in ac1.get_contacts()
def test_get_contacts_and_delete_fails(self, acfactory):
def test_chat(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("one messae")
assert not ac1.delete_contact(contact1)
class TestOfflineChat:
@pytest.fixture
def ac1(self, acfactory):
return acfactory.get_configured_offline_account()
@pytest.fixture
def chat1(self, ac1):
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id
return chat
def test_display(self, chat1):
str(chat1)
repr(chat1)
def test_chat_idempotent(self, chat1, ac1):
contact1 = chat1.get_contacts()[0]
chat2 = ac1.create_chat_by_contact(contact1.id)
assert chat2.id == chat1.id
assert chat2.get_name() == chat1.get_name()
assert chat1 == chat2
assert not (chat1 != chat2)
assert chat2.id == chat.id
assert chat2.get_name() == chat.get_name()
assert chat == chat2
assert not (chat != chat2)
for ichat in ac1.get_chats():
if ichat.id == chat1.id:
if ichat.id == chat.id:
break
else:
pytest.fail("could not find chat")
def test_group_chat_creation(self, ac1):
def test_group_chat_creation(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
contact2 = ac1.create_contact("some2@hello.com", name="some2")
chat = ac1.create_group_chat(name="title1")
@@ -140,73 +105,64 @@ class TestOfflineChat:
chat.set_name("title2")
assert chat.get_name() == "title2"
def test_delete_and_send_fails(self, ac1, chat1):
chat1.delete()
def test_delete_and_send_fails(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.delete()
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
with pytest.raises(ValueError):
chat1.send_text("msg1")
chat.send_text("msg1")
def test_prepare_message_and_send(self, ac1, chat1):
msg = chat1.prepare_message(Message.new_empty(chat1.account, "text"))
msg.set_text("hello world")
assert msg.text == "hello world"
assert msg.id > 0
chat1.send_prepared(msg)
assert "Sent" in msg.get_message_info()
str(msg)
repr(msg)
assert msg == ac1.get_message_by_id(msg.id)
def test_create_message(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
message = ac1.create_message("text")
assert message.id == 0
assert message._dc_msg is message._dc_msg
message.set_text("hello")
assert message.text == "hello"
assert message.id == 0
def test_prepare_file(self, ac1, chat1):
blobdir = ac1.get_blobdir()
p = os.path.join(blobdir, "somedata.txt")
with open(p, "w") as f:
f.write("some data")
message = chat1.prepare_message_file(p)
assert message.id > 0
message.set_text("hello world")
assert message.is_out_preparing()
assert message.text == "hello world"
chat1.send_prepared(message)
assert "Sent" in message.get_message_info()
def test_message_eq_contains(self, chat1):
msg = chat1.send_text("msg1")
assert msg in chat1.get_messages()
assert not (msg not in chat1.get_messages())
str(msg)
repr(msg)
def test_message_send_text(self, chat1):
msg = chat1.send_text("msg1")
def test_message(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
msg = chat.send_text("msg1")
assert msg
assert msg.is_text()
assert not msg.is_audio()
assert not msg.is_video()
assert not msg.is_gif()
assert not msg.is_file()
assert not msg.is_image()
assert msg.view_type.is_text()
assert msg.view_type.name == "text"
assert not msg.view_type.is_audio()
assert not msg.view_type.is_video()
assert not msg.view_type.is_gif()
assert not msg.view_type.is_file()
assert not msg.view_type.is_image()
msg_state = msg.get_state()
assert not msg_state.is_in_fresh()
assert not msg_state.is_in_noticed()
assert not msg_state.is_in_seen()
assert msg_state.is_out_pending()
assert not msg_state.is_out_failed()
assert not msg_state.is_out_delivered()
assert not msg_state.is_out_mdn_received()
assert not msg.is_in_fresh()
assert not msg.is_in_noticed()
assert not msg.is_in_seen()
assert msg.is_out_pending()
assert not msg.is_out_failed()
assert not msg.is_out_delivered()
assert not msg.is_out_mdn_received()
def test_create_chat_by_mssage_id(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
msg = chat.send_text("msg1")
assert chat == ac1.create_chat_by_message(msg)
assert chat == ac1.create_chat_by_message(msg.id)
def test_create_chat_by_message_id(self, ac1, chat1):
msg = chat1.send_text("msg1")
assert chat1 == ac1.create_chat_by_message(msg)
assert chat1 == ac1.create_chat_by_message(msg.id)
def test_message_image(self, chat1, data, lp):
def test_message_image(self, acfactory, data, lp):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
with pytest.raises(ValueError):
chat1.send_image(path="notexists")
chat.send_image(path="notexists")
fn = data.get_path("d.png")
lp.sec("sending image")
msg = chat1.send_image(fn)
assert msg.is_image()
msg = chat.send_image(fn)
assert msg.view_type.name == "image"
assert msg
assert msg.id > 0
assert os.path.exists(msg.filename)
@@ -217,19 +173,20 @@ class TestOfflineChat:
("text/plain", "text/plain"),
("image/png", "image/png"),
])
def test_message_file(self, ac1, chat1, data, lp, typein, typeout):
def test_message_file(self, acfactory, data, lp, typein, typeout):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
lp.sec("sending file")
fn = data.get_path("r.txt")
msg = chat1.send_file(fn, typein)
msg = chat.send_file(fn, typein)
assert msg
assert msg.id > 0
assert msg.is_file()
assert msg.view_type.name == "file"
assert msg.view_type.is_file()
assert os.path.exists(msg.filename)
assert msg.filename.endswith(msg.basename)
assert msg.filemime == typeout
msg2 = chat1.send_file(fn, typein)
assert msg2 != msg
assert msg2.filename != msg.filename
def test_create_chat_mismatch(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
@@ -242,9 +199,12 @@ class TestOfflineChat:
with pytest.raises(ValueError):
ac2.create_chat_by_message(msg)
def test_chat_message_distinctions(self, ac1, chat1):
def test_chat_message_distinctions(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
past1s = datetime.utcnow() - timedelta(seconds=1)
msg = chat1.send_text("msg1")
msg = chat.send_text("msg1")
ts = msg.time_sent
assert msg.time_received is None
assert ts.strftime("Y")
@@ -252,7 +212,8 @@ class TestOfflineChat:
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
def test_basic_configure_ok_addr_setting_forbidden(self, ac1):
def test_basic_configure_ok_addr_setting_forbidden(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
assert ac1.get_config("mail_pw")
assert ac1.is_configured()
with pytest.raises(ValueError):
@@ -291,21 +252,11 @@ class TestOfflineChat:
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
def test_ac_setup_message_fails(self, ac1):
def test_ac_setup_message_fails(self, acfactory):
ac1 = acfactory.get_configured_offline_account()
with pytest.raises(RuntimeError):
ac1.initiate_key_transfer()
def test_set_get_draft(self, chat1):
msg = Message.new_empty(chat1.account, "text")
msg1 = chat1.prepare_message(msg)
msg1.set_text("hello")
chat1.set_draft(msg1)
msg1.set_text("obsolete")
msg2 = chat1.get_draft()
assert msg2.text == "hello"
chat1.set_draft(None)
assert chat1.get_draft() is None
class TestOnlineAccount:
def test_one_account_init(self, acfactory):
@@ -326,7 +277,7 @@ class TestOnlineAccount:
ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[1] == msg_out.id
def test_two_accounts_send_receive(self, acfactory):
def test_two_acocunts_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"))
@@ -394,7 +345,7 @@ class TestOnlineAccount:
evt_name, data1, data2 = ev
assert data1 == chat.id
assert data2 == msg_out.id
assert msg_out.is_out_delivered()
assert msg_out.get_state().is_out_delivered()
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
@@ -421,11 +372,10 @@ class TestOnlineAccount:
lp.sec("mark message as seen on ac2, wait for changes on ac1")
ac2.mark_seen_messages([msg_in])
lp.step("1")
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
assert ev[1] >= const.DC_CHAT_ID_LAST_SPECIAL
assert ev[2] >= const.DC_MSG_ID_LAST_SPECIAL
ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
lp.step("2")
assert msg_out.is_out_mdn_received()
# ac1._evlogger.get_info_matching("Message marked as seen")
assert msg_out.get_state().is_out_mdn_received()
def test_saved_mime_on_received_message(self, acfactory, lp):
lp.sec("starting accounts, waiting for configuration")
@@ -465,13 +415,13 @@ class TestOnlineAccount:
evt_name, data1, data2 = ev
assert data1 == chat.id
assert data2 == msg_out.id
assert msg_out.is_out_delivered()
assert msg_out.get_state().is_out_delivered()
lp.sec("wait for ac2 to receive message")
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_out.id
msg_in = ac2.get_message_by_id(msg_out.id)
assert msg_in.is_image()
assert msg_in.view_type.is_image()
assert os.path.exists(msg_in.filename)
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
@@ -507,7 +457,7 @@ class TestOnlineAccount:
wait_configuration_progress(ac1, 1000)
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
setup_code = ac1.initiate_key_transfer()
ac2._evlogger.set_timeout(30)
ac2._evlogger.set_timeout(10)
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()

View File

@@ -1,4 +1,6 @@
from __future__ import print_function
import os
import shutil
from filecmp import cmp
from deltachat import const
from conftest import wait_configuration_progress, wait_msgs_changed
@@ -11,15 +13,18 @@ class TestInCreation:
wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000)
blobdir = ac1.get_blobdir()
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_msgs_changed(ac1, 0, 0) # why no chat id?
lp.sec("create a message with a file in creation")
path = data.get_path("d.png")
prepared_original = chat.prepare_message_file(path)
assert prepared_original.is_out_preparing()
path = os.path.join(blobdir, "d.png")
open(path, 'a').close()
prepared_original = chat.prepare_file(path)
assert prepared_original.get_state().is_out_preparing()
wait_msgs_changed(ac1, chat.id, prepared_original.id)
lp.sec("forward the message while still in creation")
@@ -27,41 +32,36 @@ class TestInCreation:
chat2.add_contact(c2)
wait_msgs_changed(ac1, 0, 0) # why not chat id?
ac1.forward_messages([prepared_original], chat2)
# XXX there might be two EVENT_MSGS_CHANGED and only one of them
# is the one caused by forwarding
forwarded_id = wait_msgs_changed(ac1, chat2.id)
if forwarded_id == 0:
forwarded_id = wait_msgs_changed(ac1, chat2.id)
assert forwarded_id
forwarded_msg = ac1.get_message_by_id(forwarded_id)
assert forwarded_msg.is_out_preparing()
assert forwarded_msg.get_state().is_out_preparing()
lp.sec("finish creating the file and send it")
assert prepared_original.is_out_preparing()
chat.send_prepared(prepared_original)
assert prepared_original.is_out_pending() or prepared_original.is_out_delivered()
wait_msgs_changed(ac1, chat.id, prepared_original.id)
shutil.copy(data.get_path("d.png"), path)
sent_original = chat.send_prepared(prepared_original)
assert sent_original.id == prepared_original.id
state = sent_original.get_state()
assert state.is_out_pending() or state.is_out_delivered()
wait_msgs_changed(ac1, chat.id, sent_original.id)
lp.sec("expect the forwarded message to be sent now too")
wait_msgs_changed(ac1, chat2.id, forwarded_id)
fwd_msg = ac1.get_message_by_id(forwarded_id)
assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered()
state = ac1.get_message_by_id(forwarded_id).get_state()
assert state.is_out_pending() or state.is_out_delivered()
lp.sec("wait for the messages to be delivered to SMTP")
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
assert ev[1] == chat.id
assert ev[2] == prepared_original.id
assert ev[2] == sent_original.id
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
assert ev[1] == chat2.id
assert ev[2] == forwarded_id
lp.sec("wait1 for original or forwarded messages to arrive")
lp.sec("wait for both messages to arrive")
ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
received_original = ac2.get_message_by_id(ev1[2])
assert cmp(received_original.filename, path, False)
lp.sec("wait2 for original or forwarded messages to arrive")
ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
assert ev2[1] != ev1[1]

View File

@@ -1,7 +1,7 @@
from __future__ import print_function
from deltachat import capi, const, set_context_callback, clear_context_callback
import pytest
from deltachat import capi, Account, const, set_context_callback, clear_context_callback
from deltachat.capi import ffi
from deltachat.capi import lib
from deltachat.account import EventLogger
@@ -23,32 +23,17 @@ def test_dc_close_events():
evlog.set_timeout(5)
set_context_callback(ctx, lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2))
capi.lib.dc_close(ctx)
def find(info_string):
while 1:
ev = evlog.get_matching("DC_EVENT_INFO", check_error=False)
data2 = ev[2]
if info_string in data2:
return
else:
print("skipping event", *ev)
find("disconnecting INBOX-watch")
find("disconnecting sentbox-thread")
find("disconnecting mvbox-thread")
find("disconnecting SMTP")
find("Database closed")
# test that we get events from dc_close
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
print(evlog.get_matching("DC_EVENT_INFO", check_error=False))
def test_wrong_db(tmpdir):
dc_context = ffi.gc(
lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
p = tmpdir.join("hello.db")
# write an invalid database file
p.write("x123" * 10)
assert not lib.dc_open(dc_context, p.strpath.encode("ascii"), ffi.NULL)
tmpdir.join("hello.db").write("123")
with pytest.raises(ValueError):
Account(db_path=tmpdir.strpath)
def test_event_defines():
@@ -64,14 +49,3 @@ def test_sig():
assert sig(const.DC_EVENT_SMTP_CONNECTED) == 2
assert sig(const.DC_EVENT_IMAP_CONNECTED) == 2
assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT) == 2
def test_markseen_invalid_message_ids(acfactory):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@example.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("one messae")
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
msg_ids = [9]
lib.dc_markseen_msgs(ac1._dc_context, msg_ids, len(msg_ids))
ac1._evlogger.ensure_event_not_queued("DC_EVENT_WARNING|DC_EVENT_ERROR")

View File

@@ -8,13 +8,11 @@ envlist =
[testenv]
commands =
pytest -v -rsXx {posargs:tests}
pytest -rsXx {posargs:tests}
python tests/package_wheels.py {toxworkdir}/wheelhouse
passenv =
TRAVIS
DCC_RS_DEV
DCC_RS_TARGET
DCC_PY_LIVECONFIG
deps =
pytest
pytest-faulthandler

View File

@@ -1,3 +0,0 @@
pre-release-commit-message = "chore({{crate_name}}): release {{version}}"
pro-release-commit-message = "chore({{crate_name}}): starting development cycle for {{next_version}}"
no-dev-version = true

View File

@@ -23,10 +23,11 @@ if [ $? != 0 ]; then
fi
pushd python
if [ -e "./liveconfig" ]; then
export DCC_PY_LIVECONFIG=liveconfig
toxargs="$@"
if [ -e liveconfig ]; then
toxargs="--liveconfig liveconfig $@"
fi
tox "$@"
tox $toxargs
ret=$?
popd
exit $ret

View File

@@ -1 +1 @@
nightly-2019-07-10
nightly-2019-06-16

View File

@@ -1,13 +1,12 @@
use std::collections::BTreeMap;
use std::ffi::CStr;
use std::ffi::{CStr, CString};
use std::str::FromStr;
use std::{fmt, str};
use mmime::mailimf_types::*;
use crate::constants::*;
use crate::contact::*;
use crate::dc_tools::as_str;
use crate::dc_contact::*;
use crate::key::*;
/// Possible values for encryption preference
@@ -94,7 +93,9 @@ impl Aheader {
match Self::from_str(value) {
Ok(test) => {
if addr_cmp(&test.addr, as_str(wanted_from)) {
// TODO: implement rust-safe version of dc_addr_cmp
let addr = CString::new(test.addr.clone()).unwrap();
if unsafe { dc_addr_cmp(addr.as_ptr(), wanted_from) } == 0 {
if fine_header.is_none() {
fine_header = Some(test);
} else {

View File

@@ -1,334 +0,0 @@
use crate::constants::*;
use crate::contact::*;
use crate::context::*;
use crate::dc_chat::*;
use crate::dc_lot::*;
use crate::dc_msg::*;
use crate::dc_tools::*;
use crate::error::Result;
use crate::stock::StockMessage;
/// An object representing a single chatlist in memory.
///
/// Chatlist objects contain chat IDs and, if possible, message IDs belonging to them.
/// The chatlist object is not updated; if you want an update, you have to recreate the object.
///
/// For a **typical chat overview**, the idea is to get the list of all chats via dc_get_chatlist()
/// without any listflags (see below) and to implement a "virtual list" or so
/// (the count of chats is known by chatlist.len()).
///
/// Only for the items that are in view (the list may have several hundreds chats),
/// the UI should call chatlist.get_summary() then.
/// chatlist.get_summary() provides all elements needed for painting the item.
///
/// On a click of such an item, the UI should change to the chat view
/// and get all messages from this view via dc_get_chat_msgs().
/// Again, a "virtual list" is created (the count of messages is known)
/// and for each messages that is scrolled into view, dc_get_msg() is called then.
///
/// Why no listflags?
/// Without listflags, dc_get_chatlist() adds the deaddrop and the archive "link" automatically as needed.
/// The UI can just render these items differently then. Although the deaddrop link is currently always the
/// 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.
pub struct Chatlist<'a> {
context: &'a Context,
/// Stores pairs of `chat_id, message_id`
ids: Vec<(u32, u32)>,
}
impl<'a> Chatlist<'a> {
pub fn get_context(&self) -> &Context {
self.context
}
/// Get a list of chats.
/// The list can be filtered by query parameters.
///
/// The list is already sorted and starts with the most recent chat in use.
/// The sorting takes care of invalid sending dates, drafts and chats without messages.
/// Clients should not try to re-sort the list as this would be an expensive action
/// and would result in inconsistencies between clients.
///
/// To get information about each entry, use eg. chatlist.get_summary().
///
/// By default, the function adds some special entries to the list.
/// These special entries can be identified by the ID returned by chatlist.get_chat_id():
/// - DC_CHAT_ID_DEADDROP (1) - this special chat is present if there are
/// messages from addresses that have no relationship to the configured account.
/// The last of these messages is represented by DC_CHAT_ID_DEADDROP and you can retrieve details
/// about it with chatlist.get_msg_id(). Typically, the UI asks the user "Do you want to chat with NAME?"
/// and offers the options "Yes" (call dc_create_chat_by_msg_id()), "Never" (call dc_block_contact())
/// or "Not now".
/// The UI can also offer a "Close" button that calls dc_marknoticed_contact() then.
/// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has
/// archived _any_ chat using dc_archive_chat(). The UI should show a link as
/// "Show archived chats", if the user clicks this item, the UI should show a
/// list of all archived chats that can be created by this function hen using
/// the DC_GCL_ARCHIVED_ONLY flag.
/// - DC_CHAT_ID_ALLDONE_HINT (7) - this special chat is present
/// if DC_GCL_ADD_ALLDONE_HINT is added to listflags
/// and if there are only archived chats.
///
/// The `listflags` is a combination of flags:
/// - if the flag DC_GCL_ARCHIVED_ONLY is set, only archived chats are returned.
/// if DC_GCL_ARCHIVED_ONLY is not set, only unarchived chats are returned and
/// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived
/// chats
/// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
/// to the list (may be used eg. for selecting chats on forwarding, the flag is
/// not needed when DC_GCL_ARCHIVED_ONLY is already set)
/// - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT
/// is added as needed.
/// `query`: An optional query for filtering the list. Only chats matching this query
/// are returned.
/// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID
/// are returned.
pub fn try_load(
context: &'a Context,
listflags: usize,
query: Option<&str>,
query_contact_id: Option<u32>,
) -> Result<Self> {
let mut add_archived_link_item = 0;
// select with left join and minimum:
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same timestamp
// - the list starts with the newest chats
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let process_row = |row: &rusqlite::Row| {
let chat_id: i32 = row.get(0)?;
// TODO: verify that it is okay for this to be Null
let msg_id: i32 = row.get(1).unwrap_or_default();
Ok((chat_id as u32, msg_id as u32))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
};
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
let mut ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![query_contact_id as i32],
process_row,
process_rows,
)?
} else if 0 != listflags & DC_GCL_ARCHIVED_ONLY {
// show archived chats
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=1 GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)?
} else if let Some(query) = query {
let query = query.trim().to_string();
ensure!(!query.is_empty(), "missing query");
let str_like_cmd = format!("%{}%", query);
context.sql.query_map(
"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.name LIKE ? \
GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![str_like_cmd],
process_row,
process_rows,
)?
} else {
// show normal chatlist
let mut ids = context.sql.query_map(
"SELECT c.id, m.id FROM chats c \
LEFT JOIN msgs m \
ON c.id=m.chat_id \
AND m.timestamp=( SELECT MAX(timestamp) \
FROM msgs WHERE chat_id=c.id \
AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 \
AND c.blocked=0 AND c.archived=0 \
GROUP BY c.id \
ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;",
params![],
process_row,
process_rows,
)?;
if 0 == listflags & DC_GCL_NO_SPECIALS {
let last_deaddrop_fresh_msg_id = get_last_deaddrop_fresh_msg(context);
if last_deaddrop_fresh_msg_id > 0 {
ids.push((1, last_deaddrop_fresh_msg_id));
}
add_archived_link_item = 1;
}
ids
};
if 0 != add_archived_link_item && dc_get_archived_cnt(context) > 0 {
if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT {
ids.push((DC_CHAT_ID_ALLDONE_HINT as u32, 0));
}
ids.push((DC_CHAT_ID_ARCHIVED_LINK as u32, 0));
}
Ok(Chatlist { context, ids })
}
/// Find out the number of chats.
pub fn len(&self) -> usize {
self.ids.len()
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
/// Get a single chat ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_chat().
pub fn get_chat_id(&self, index: usize) -> u32 {
if index >= self.ids.len() {
return 0;
}
self.ids[index].0
}
/// Get a single message ID of a chatlist.
///
/// To get the message object from the message ID, use dc_get_msg().
pub fn get_msg_id(&self, index: usize) -> u32 {
if index >= self.ids.len() {
return 0;
}
self.ids[index].1
}
/// Get a summary for a chatlist index.
///
/// The summary is returned by a dc_lot_t object with the following fields:
///
/// - dc_lot_t::text1: contains the username or the strings "Me", "Draft" and so on.
/// The string may be colored by having a look at text1_meaning.
/// If there is no such name or it should not be displayed, the element is NULL.
/// - dc_lot_t::text1_meaning: one of DC_TEXT1_USERNAME, DC_TEXT1_SELF or DC_TEXT1_DRAFT.
/// Typically used to show dc_lot_t::text1 with different colors. 0 if not applicable.
/// - dc_lot_t::text2: contains an excerpt of the message text or strings as
/// "No messages". May be NULL of there is no such text (eg. for the archive link)
/// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable.
/// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()).
// 0 if not applicable.
pub unsafe fn get_summary(&self, index: usize, mut chat: *mut Chat<'a>) -> *mut dc_lot_t {
// The summary is created by the chat, not by the last message.
// This is because we may want to display drafts here or stuff as
// "is typing".
// Also, sth. as "No messages" would not work if the summary comes from a message.
let mut ret = dc_lot_new();
if index >= self.ids.len() {
(*ret).text2 = "ErrBadChatlistIndex".strdup();
return ret;
}
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
if chat.is_null() {
chat = dc_chat_new(self.context);
let chat_to_delete = chat;
if !dc_chat_load_from_db(chat, self.ids[index].0) {
(*ret).text2 = "ErrCannotReadChat".strdup();
dc_chat_unref(chat_to_delete);
return ret;
}
}
let lastmsg = if 0 != lastmsg_id {
let lastmsg = dc_msg_new_untyped(self.context);
dc_msg_load_from_db(lastmsg, self.context, lastmsg_id);
if (*lastmsg).from_id != 1 as libc::c_uint
&& ((*chat).type_0 == DC_CHAT_TYPE_GROUP
|| (*chat).type_0 == DC_CHAT_TYPE_VERIFIED_GROUP)
{
lastcontact = Contact::load_from_db(self.context, (*lastmsg).from_id).ok();
}
lastmsg
} else {
std::ptr::null_mut()
};
if (*chat).id == DC_CHAT_ID_ARCHIVED_LINK as u32 {
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
} else if lastmsg.is_null() || (*lastmsg).from_id == DC_CONTACT_ID_UNDEFINED as u32 {
(*ret).text2 = self.context.stock_str(StockMessage::NoMessages).strdup();
} else {
dc_lot_fill(ret, lastmsg, chat, lastcontact.as_ref(), self.context);
}
dc_msg_unref(lastmsg);
ret
}
}
pub fn dc_get_archived_cnt(context: &Context) -> u32 {
context
.sql
.query_row_col(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![],
0,
)
.unwrap_or_default()
}
fn get_last_deaddrop_fresh_msg(context: &Context) -> u32 {
// We have an index over the state-column, this should be sufficient as there are typically
// only few fresh messages.
context
.sql
.query_row_col(
context,
"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE m.state=10 \
AND m.hidden=0 \
AND c.blocked=2 \
ORDER BY m.timestamp DESC, m.id DESC;",
params![],
0,
)
.unwrap_or_default()
}

View File

@@ -1,165 +0,0 @@
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
use crate::constants::DC_VERSION_STR;
use crate::context::Context;
use crate::dc_job::*;
use crate::dc_tools::*;
use crate::error::Error;
use crate::stock::StockMessage;
/// The available configuration keys.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Display, EnumString, AsRefStr, EnumIter, EnumProperty,
)]
#[strum(serialize_all = "snake_case")]
pub enum Config {
Addr,
MailServer,
MailUser,
MailPw,
MailPort,
SendServer,
SendUser,
SendPw,
SendPort,
ServerFlags,
#[strum(props(default = "INBOX"))]
ImapFolder,
Displayname,
Selfstatus,
Selfavatar,
#[strum(props(default = "1"))]
E2eeEnabled,
#[strum(props(default = "1"))]
MdnsEnabled,
InboxWatch,
#[strum(props(default = "1"))]
SentboxWatch,
#[strum(props(default = "1"))]
MvboxWatch,
#[strum(props(default = "1"))]
MvboxMove,
#[strum(props(default = "0"))]
ShowEmails,
SaveMimeHeaders,
ConfiguredAddr,
ConfiguredMailServer,
ConfiguredMailUser,
ConfiguredMailPw,
ConfiguredMailPort,
ConfiguredSendServer,
ConfiguredSendUser,
ConfiguredSendPw,
ConfiguredSendPort,
ConfiguredServerFlags,
Configured,
// Deprecated
#[strum(serialize = "sys.version")]
SysVersion,
#[strum(serialize = "sys.msgsize_max_recommended")]
SysMsgsizeMaxRecommended,
#[strum(serialize = "sys.config_keys")]
SysConfigKeys,
}
impl Context {
/// Get a configuration key. Returns `None` if no value is set, and no default value found.
pub fn get_config(&self, key: Config) -> Option<String> {
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())
}
Config::SysVersion => Some(std::str::from_utf8(DC_VERSION_STR).unwrap().into()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", 24 * 1024 * 1024 / 4 * 3)),
Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_config(self, key),
};
if value.is_some() {
return value;
}
// Default values
match key {
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()),
_ => key.get_str("default").map(|s| s.to_string()),
}
}
/// Set the given config key.
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
pub fn set_config(&self, key: Config, value: Option<&str>) -> Result<(), Error> {
match key {
Config::Selfavatar if value.is_some() => {
let rel_path = std::fs::canonicalize(value.unwrap())?;
self.sql
.set_config(self, key, Some(&rel_path.to_string_lossy()))
}
Config::InboxWatch => {
let ret = self.sql.set_config(self, key, value);
unsafe { dc_interrupt_imap_idle(self) };
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_config(self, key, value);
unsafe { dc_interrupt_sentbox_idle(self) };
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_config(self, key, value);
unsafe { dc_interrupt_mvbox_idle(self) };
ret
}
Config::Selfstatus => {
let def = self.stock_str(StockMessage::StatusLine);
let val = if value.is_none() || value.unwrap() == def {
None
} else {
value
};
let ret = self.sql.set_config(self, key, val);
ret
}
_ => self.sql.set_config(self, key, value),
}
}
}
/// Returns all available configuration keys concated together.
fn get_config_keys_string() -> String {
let keys = Config::iter().fold(String::new(), |mut acc, key| {
acc += key.as_ref();
acc += " ";
acc
});
format!(" {} ", keys)
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use std::string::ToString;
#[test]
fn test_to_string() {
assert_eq!(Config::MailServer.to_string(), "mail_server");
assert_eq!(Config::from_str("mail_server"), Ok(Config::MailServer));
assert_eq!(Config::SysConfigKeys.to_string(), "sys.config_keys");
assert_eq!(
Config::from_str("sys.config_keys"),
Ok(Config::SysConfigKeys)
);
}
#[test]
fn test_default_prop() {
assert_eq!(Config::ImapFolder.get_str("default"), Some("INBOX"));
}
}

View File

@@ -1,10 +1,6 @@
//! Constants
#![allow(non_camel_case_types)]
use num_traits::{FromPrimitive, ToPrimitive};
use rusqlite as sql;
use rusqlite::types::*;
pub const DC_VERSION_STR: &'static [u8; 14] = b"1.0.0-alpha.3\x00";
pub const DC_VERSION_STR: &'static [u8; 14] = b"1.0.0-alpha.1\x00";
pub const DC_MOVE_STATE_MOVING: u32 = 3;
pub const DC_MOVE_STATE_STAY: u32 = 2;
@@ -63,33 +59,32 @@ pub const DC_CHAT_ID_ALLDONE_HINT: usize = 7;
/// larger chat IDs are "real" chats, their messages are "real" messages.
pub const DC_CHAT_ID_LAST_SPECIAL: usize = 9;
pub const DC_CHAT_TYPE_UNDEFINED: i32 = 0;
pub const DC_CHAT_TYPE_SINGLE: i32 = 100;
pub const DC_CHAT_TYPE_GROUP: i32 = 120;
pub const DC_CHAT_TYPE_VERIFIED_GROUP: i32 = 130;
pub const DC_CHAT_TYPE_UNDEFINED: usize = 0;
pub const DC_CHAT_TYPE_SINGLE: usize = 100;
pub const DC_CHAT_TYPE_GROUP: usize = 120;
pub const DC_CHAT_TYPE_VERIFIED_GROUP: usize = 130;
pub const DC_MSG_ID_MARKER1: usize = 1;
pub const DC_MSG_ID_DAYMARKER: usize = 9;
pub const DC_MSG_ID_LAST_SPECIAL: usize = 9;
pub const DC_STATE_UNDEFINED: i32 = 0;
pub const DC_STATE_IN_FRESH: i32 = 10;
pub const DC_STATE_IN_NOTICED: i32 = 13;
pub const DC_STATE_IN_SEEN: i32 = 16;
pub const DC_STATE_OUT_PREPARING: i32 = 18;
pub const DC_STATE_OUT_DRAFT: i32 = 19;
pub const DC_STATE_OUT_PENDING: i32 = 20;
pub const DC_STATE_OUT_FAILED: i32 = 24;
pub const DC_STATE_UNDEFINED: usize = 0;
pub const DC_STATE_IN_FRESH: usize = 10;
pub const DC_STATE_IN_NOTICED: usize = 13;
pub const DC_STATE_IN_SEEN: usize = 16;
pub const DC_STATE_OUT_PREPARING: usize = 18;
pub const DC_STATE_OUT_DRAFT: usize = 19;
pub const DC_STATE_OUT_PENDING: usize = 20;
pub const DC_STATE_OUT_FAILED: usize = 24;
/// to check if a mail was sent, use dc_msg_is_sent()
pub const DC_STATE_OUT_DELIVERED: i32 = 26;
pub const DC_STATE_OUT_MDN_RCVD: i32 = 28;
pub const DC_STATE_OUT_DELIVERED: usize = 26;
pub const DC_STATE_OUT_MDN_RCVD: usize = 28;
/// approx. max. length returned by dc_msg_get_text()
/// approx. max. lenght returned by dc_msg_get_text()
pub const DC_MAX_GET_TEXT_LEN: usize = 30000;
/// approx. max. length returned by dc_get_msg_info()
/// approx. max. lenght returned by dc_get_msg_info()
pub const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_UNDEFINED: usize = 0;
pub const DC_CONTACT_ID_SELF: usize = 1;
pub const DC_CONTACT_ID_DEVICE: usize = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: usize = 9;
@@ -100,6 +95,46 @@ pub const DC_TEXT1_SELF: usize = 3;
pub const DC_CREATE_MVBOX: usize = 1;
/// Text message.
/// The text of the message is set using dc_msg_set_text()
/// and retrieved with dc_msg_get_text().
pub const DC_MSG_TEXT: usize = 10;
/// Image message.
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
pub const DC_MSG_IMAGE: usize = 20;
/// Animated GIF message.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
pub const DC_MSG_GIF: usize = 21;
/// Message containing an Audio file.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
pub const DC_MSG_AUDIO: usize = 40;
/// A voice message that was directly recorded by the user.
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retieved via dc_msg_get_file(), dc_msg_get_duration()
pub const DC_MSG_VOICE: usize = 41;
/// Video messages.
/// File, width, height and durarion
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
/// and retrieved via
/// dc_msg_get_file(), dc_msg_get_width(),
/// dc_msg_get_height(), dc_msg_get_duration().
pub const DC_MSG_VIDEO: usize = 50;
/// Message containing any file, eg. a PDF.
/// The file is set via dc_msg_set_file()
/// and retrieved via dc_msg_get_file().
pub const DC_MSG_FILE: usize = 60;
// Flags for configuring IMAP and SMTP servers.
// These flags are optional
// and may be set together with the username, password etc.
@@ -138,87 +173,15 @@ pub const DC_LP_SMTP_SOCKET_SSL: usize = 0x20000;
/// 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
/// if none of these flags are set, the default is choosen
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
/// if none of these flags are set, the default is choosen
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
/// if none of these flags are set, the default is choosen
pub const DC_LP_SMTP_SOCKET_FLAGS: usize =
(DC_LP_SMTP_SOCKET_STARTTLS | DC_LP_SMTP_SOCKET_SSL | DC_LP_SMTP_SOCKET_PLAIN);
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
#[repr(i32)]
pub enum Viewtype {
Unknown = 0,
/// Text message.
/// The text of the message is set using dc_msg_set_text()
/// and retrieved with dc_msg_get_text().
Text = 10,
/// Image message.
/// If the image is an animated GIF, the type DC_MSG_GIF should be used.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension
/// and retrieved via dc_msg_set_file(), dc_msg_set_dimension().
Image = 20,
/// Animated GIF message.
/// File, width and height are set via dc_msg_set_file(), dc_msg_set_dimension()
/// and retrieved via dc_msg_get_file(), dc_msg_get_width(), dc_msg_get_height().
Gif = 21,
/// Message containing an Audio file.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration().
Audio = 40,
/// A voice message that was directly recorded by the user.
/// For all other audio messages, the type #DC_MSG_AUDIO should be used.
/// File and duration are set via dc_msg_set_file(), dc_msg_set_duration()
/// and retrieved via dc_msg_get_file(), dc_msg_get_duration()
Voice = 41,
/// Video messages.
/// File, width, height and durarion
/// are set via dc_msg_set_file(), dc_msg_set_dimension(), dc_msg_set_duration()
/// and retrieved via
/// dc_msg_get_file(), dc_msg_get_width(),
/// dc_msg_get_height(), dc_msg_get_duration().
Video = 50,
/// Message containing any file, eg. a PDF.
/// The file is set via dc_msg_set_file()
/// and retrieved via dc_msg_get_file().
File = 60,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derive_display_works_as_expected() {
assert_eq!(format!("{}", Viewtype::Audio), "Audio");
}
}
impl ToSql for Viewtype {
fn to_sql(&self) -> sql::Result<ToSqlOutput> {
let num: i64 = self
.to_i64()
.expect("impossible: Viewtype -> i64 conversion failed");
Ok(ToSqlOutput::Owned(Value::Integer(num)))
}
}
impl FromSql for Viewtype {
fn column_result(col: ValueRef) -> FromSqlResult<Self> {
let inner = FromSql::column_result(col)?;
FromPrimitive::from_i64(inner).ok_or(FromSqlError::InvalidType)
}
}
// These constants are used as events
// reported to the callback given to dc_context_new().
// If you do not want to handle an event, it is always safe to return 0,
@@ -274,7 +237,7 @@ pub enum Event {
/// 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
/// As most things are asynchrounous, 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. dc_configure())
@@ -302,7 +265,7 @@ pub enum Event {
///
/// 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.
/// instread 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
@@ -528,12 +491,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;

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,23 @@
use std::sync::{Arc, Condvar, Mutex, RwLock};
use crate::constants::*;
use crate::contact::*;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_job::*;
use crate::dc_jobthread::*;
use crate::dc_log::*;
use crate::dc_loginparam::*;
use crate::dc_lot::dc_lot_t;
use crate::dc_move::*;
use crate::dc_msg::*;
use crate::dc_receive_imf::*;
use crate::dc_sqlite3::*;
use crate::dc_stock::*;
use crate::dc_tools::*;
use crate::imap::*;
use crate::key::*;
use crate::param::Params;
use crate::smtp::*;
use crate::sql::Sql;
use crate::types::*;
use crate::x::*;
@@ -25,17 +26,17 @@ pub struct Context {
pub userdata: *mut libc::c_void,
pub dbfile: Arc<RwLock<*mut libc::c_char>>,
pub blobdir: Arc<RwLock<*mut libc::c_char>>,
pub sql: Sql,
pub sql: SQLite,
pub inbox: Arc<RwLock<Imap>>,
pub perform_inbox_jobs_needed: Arc<RwLock<bool>>,
pub probe_imap_network: Arc<RwLock<bool>>,
pub perform_inbox_jobs_needed: Arc<RwLock<i32>>,
pub probe_imap_network: Arc<RwLock<i32>>,
pub sentbox_thread: Arc<RwLock<dc_jobthread_t>>,
pub mvbox_thread: Arc<RwLock<dc_jobthread_t>>,
pub smtp: Arc<Mutex<Smtp>>,
pub smtp_state: Arc<(Mutex<SmtpState>, Condvar)>,
pub oauth2_critical: Arc<Mutex<()>>,
pub cb: Option<dc_callback_t>,
pub os_name: Option<String>,
pub os_name: *mut libc::c_char,
pub cmdline_sel_chat_id: Arc<RwLock<u32>>,
pub bob: Arc<RwLock<BobStatus>>,
pub last_smeared_timestamp: Arc<RwLock<i64>>,
@@ -106,19 +107,35 @@ impl Default for BobStatus {
#[derive(Default, Debug)]
pub struct SmtpState {
pub idle: bool,
pub suspended: bool,
pub doing_jobs: bool,
pub suspended: i32,
pub doing_jobs: i32,
pub perform_jobs_needed: i32,
pub probe_network: bool,
pub probe_network: i32,
}
// location handling
#[derive(Copy, Clone)]
#[repr(C)]
pub struct _dc_location {
pub location_id: uint32_t,
pub latitude: libc::c_double,
pub longitude: libc::c_double,
pub accuracy: libc::c_double,
pub timestamp: i64,
pub contact_id: uint32_t,
pub msg_id: uint32_t,
pub chat_id: uint32_t,
pub marker: *mut libc::c_char,
pub independent: uint32_t,
}
// create/open/config/information
pub fn dc_context_new(
cb: Option<dc_callback_t>,
userdata: *mut libc::c_void,
os_name: Option<String>,
os_name: *const libc::c_char,
) -> Context {
Context {
let context = Context {
blobdir: Arc::new(RwLock::new(std::ptr::null_mut())),
dbfile: Arc::new(RwLock::new(std::ptr::null_mut())),
inbox: Arc::new(RwLock::new({
@@ -131,9 +148,9 @@ pub fn dc_context_new(
})),
userdata,
cb,
os_name: os_name,
os_name: unsafe { dc_strdup_keep_null(os_name) },
running_state: Arc::new(RwLock::new(Default::default())),
sql: Sql::new(),
sql: SQLite::new(),
smtp: Arc::new(Mutex::new(Smtp::new())),
smtp_state: Arc::new((Mutex::new(Default::default()), Condvar::new())),
oauth2_critical: Arc::new(Mutex::new(())),
@@ -160,26 +177,17 @@ pub fn dc_context_new(
cb_receive_imf,
),
))),
probe_imap_network: Arc::new(RwLock::new(false)),
perform_inbox_jobs_needed: Arc::new(RwLock::new(false)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_crashes_on_context_deref() {
let mut ctx = dc_context_new(None, std::ptr::null_mut(), Some("Test OS".into()));
unsafe { dc_context_unref(&mut ctx) };
}
probe_imap_network: Arc::new(RwLock::new(0)),
perform_inbox_jobs_needed: Arc::new(RwLock::new(0)),
};
context
}
unsafe fn cb_receive_imf(
context: &Context,
imf_raw_not_terminated: *const libc::c_char,
imf_raw_bytes: size_t,
server_folder: &str,
server_folder: *const libc::c_char,
server_uid: uint32_t,
flags: uint32_t,
) {
@@ -196,7 +204,7 @@ unsafe fn cb_receive_imf(
unsafe fn cb_precheck_imf(
context: &Context,
rfc724_mid: *const libc::c_char,
server_folder: &str,
server_folder: *const libc::c_char,
server_uid: uint32_t,
) -> libc::c_int {
let mut rfc724_mid_exists: libc::c_int = 0i32;
@@ -215,36 +223,42 @@ unsafe fn cb_precheck_imf(
if *old_server_folder.offset(0isize) as libc::c_int == 0i32
&& old_server_uid == 0i32 as libc::c_uint
{
info!(
dc_log_info(
context,
0,
"[move] detected bbc-self {}",
as_str(rfc724_mid),
0i32,
b"[move] detected bbc-self %s\x00" as *const u8 as *const libc::c_char,
rfc724_mid,
);
mark_seen = 1i32
} else if as_str(old_server_folder) != server_folder {
info!(
} else if strcmp(old_server_folder, server_folder) != 0i32 {
dc_log_info(
context,
0,
"[move] detected moved message {}",
as_str(rfc724_mid),
0i32,
b"[move] detected moved message %s\x00" as *const u8 as *const libc::c_char,
rfc724_mid,
);
dc_update_msg_move_state(context, rfc724_mid, DC_MOVE_STATE_STAY);
}
if as_str(old_server_folder) != server_folder || old_server_uid != server_uid {
if strcmp(old_server_folder, server_folder) != 0i32 || old_server_uid != server_uid {
dc_update_server_uid(context, rfc724_mid, server_folder, server_uid);
}
dc_do_heuristics_moves(context, server_folder, msg_id);
if 0 != mark_seen {
dc_job_add(context, 130, msg_id as libc::c_int, Params::new(), 0);
dc_job_add(
context,
130i32,
msg_id as libc::c_int,
0 as *const libc::c_char,
0i32,
);
}
}
free(old_server_folder as *mut libc::c_void);
return rfc724_mid_exists;
}
fn cb_set_config(context: &Context, key: &str, value: Option<&str>) {
context.sql.set_config(context, key, value).ok();
unsafe fn cb_set_config(context: &Context, key: *const libc::c_char, value: *const libc::c_char) {
dc_sqlite3_set_config(context, &context.sql, key, value);
}
/* *
@@ -254,17 +268,24 @@ fn cb_set_config(context: &Context, key: &str, value: Option<&str>) {
*
* @private @memberof Context
*/
fn cb_get_config(context: &Context, key: &str) -> Option<String> {
context.sql.get_config(context, key)
unsafe fn cb_get_config(
context: &Context,
key: *const libc::c_char,
def: *const libc::c_char,
) -> *mut libc::c_char {
dc_sqlite3_get_config(context, &context.sql, key, def)
}
pub unsafe fn dc_context_unref(context: &mut Context) {
if 0 != dc_is_open(context) {
dc_close(context);
}
free(context.os_name as *mut libc::c_void);
}
pub unsafe fn dc_close(context: &Context) {
println!("disconnecting inbox watch yooaa");
info!(context, 0, "disconnecting INBOX-watch",);
context.inbox.read().unwrap().disconnect(context);
info!(context, 0, "disconnecting sentbox-thread",);
@@ -305,26 +326,32 @@ pub unsafe fn dc_get_userdata(context: &mut Context) -> *mut libc::c_void {
context.userdata as *mut _
}
pub unsafe fn dc_open(context: &Context, dbfile: &str, blobdir: Option<&str>) -> bool {
let mut success = false;
pub unsafe fn dc_open(
context: &Context,
dbfile: *const libc::c_char,
blobdir: *const libc::c_char,
) -> libc::c_int {
let mut success = 0;
if 0 != dc_is_open(context) {
return false;
return 0;
}
*context.dbfile.write().unwrap() = dbfile.strdup();
if blobdir.is_some() && blobdir.unwrap().len() > 0 {
let dir = dc_ensure_no_slash_safe(blobdir.unwrap()).strdup();
*context.blobdir.write().unwrap() = dir;
} else {
let dir = (dbfile.to_string() + "-blobs").strdup();
dc_create_folder(context, dir);
*context.blobdir.write().unwrap() = dir;
if !dbfile.is_null() {
*context.dbfile.write().unwrap() = dc_strdup(dbfile);
if !blobdir.is_null() && 0 != *blobdir.offset(0isize) as libc::c_int {
let dir = dc_strdup(blobdir);
dc_ensure_no_slash(dir);
*context.blobdir.write().unwrap() = dir;
} else {
let dir = dc_mprintf(b"%s-blobs\x00" as *const u8 as *const libc::c_char, dbfile);
dc_create_folder(context, dir);
*context.blobdir.write().unwrap() = dir;
}
// Create/open sqlite database, this may already use the blobdir
if context.sql.open(context, as_path(dbfile), 0) {
success = 1i32
}
}
// Create/open sqlite database, this may already use the blobdir
let dbfile_path = std::path::Path::new(dbfile);
if context.sql.open(context, dbfile_path, 0) {
success = true
}
if !success {
if 0 == success {
dc_close(context);
}
success
@@ -334,87 +361,357 @@ pub unsafe fn dc_get_blobdir(context: &Context) -> *mut libc::c_char {
dc_strdup(*context.blobdir.clone().read().unwrap())
}
pub unsafe fn dc_set_config(
context: &Context,
key: *const libc::c_char,
value: *const libc::c_char,
) -> libc::c_int {
let mut ret = 0;
let mut rel_path = 0 as *mut libc::c_char;
if key.is_null() || 0 == is_settable_config_key(key) {
return 0;
}
if strcmp(key, b"selfavatar\x00" as *const u8 as *const libc::c_char) == 0 && !value.is_null() {
rel_path = dc_strdup(value);
if !(0 == dc_make_rel_and_copy(context, &mut rel_path)) {
ret = dc_sqlite3_set_config(context, &context.sql, key, rel_path)
}
} else if strcmp(key, b"inbox_watch\x00" as *const u8 as *const libc::c_char) == 0 {
ret = dc_sqlite3_set_config(context, &context.sql, key, value);
dc_interrupt_imap_idle(context);
} else if strcmp(
key,
b"sentbox_watch\x00" as *const u8 as *const libc::c_char,
) == 0
{
ret = dc_sqlite3_set_config(context, &context.sql, key, value);
dc_interrupt_sentbox_idle(context);
} else if strcmp(key, b"mvbox_watch\x00" as *const u8 as *const libc::c_char) == 0 {
ret = dc_sqlite3_set_config(context, &context.sql, key, value);
dc_interrupt_mvbox_idle(context);
} else if strcmp(key, b"selfstatus\x00" as *const u8 as *const libc::c_char) == 0 {
let def = dc_stock_str(context, 13);
ret = dc_sqlite3_set_config(
context,
&context.sql,
key,
if value.is_null() || strcmp(value, def) == 0 {
0 as *const libc::c_char
} else {
value
},
);
free(def as *mut libc::c_void);
} else {
ret = dc_sqlite3_set_config(context, &context.sql, key, value);
}
free(rel_path as *mut libc::c_void);
ret
}
/* ******************************************************************************
* INI-handling, Information
******************************************************************************/
unsafe fn is_settable_config_key(key: *const libc::c_char) -> libc::c_int {
let mut i = 0;
while i
< (::std::mem::size_of::<[*const libc::c_char; 33]>())
.wrapping_div(::std::mem::size_of::<*mut libc::c_char>())
{
if strcmp(key, config_keys[i as usize]) == 0 {
return 1;
}
i += 1
}
0
}
static mut config_keys: [*const libc::c_char; 33] = [
b"addr\x00" as *const u8 as *const libc::c_char,
b"mail_server\x00" as *const u8 as *const libc::c_char,
b"mail_user\x00" as *const u8 as *const libc::c_char,
b"mail_pw\x00" as *const u8 as *const libc::c_char,
b"mail_port\x00" as *const u8 as *const libc::c_char,
b"send_server\x00" as *const u8 as *const libc::c_char,
b"send_user\x00" as *const u8 as *const libc::c_char,
b"send_pw\x00" as *const u8 as *const libc::c_char,
b"send_port\x00" as *const u8 as *const libc::c_char,
b"server_flags\x00" as *const u8 as *const libc::c_char,
b"imap_folder\x00" as *const u8 as *const libc::c_char,
b"displayname\x00" as *const u8 as *const libc::c_char,
b"selfstatus\x00" as *const u8 as *const libc::c_char,
b"selfavatar\x00" as *const u8 as *const libc::c_char,
b"e2ee_enabled\x00" as *const u8 as *const libc::c_char,
b"mdns_enabled\x00" as *const u8 as *const libc::c_char,
b"inbox_watch\x00" as *const u8 as *const libc::c_char,
b"sentbox_watch\x00" as *const u8 as *const libc::c_char,
b"mvbox_watch\x00" as *const u8 as *const libc::c_char,
b"mvbox_move\x00" as *const u8 as *const libc::c_char,
b"show_emails\x00" as *const u8 as *const libc::c_char,
b"save_mime_headers\x00" as *const u8 as *const libc::c_char,
b"configured_addr\x00" as *const u8 as *const libc::c_char,
b"configured_mail_server\x00" as *const u8 as *const libc::c_char,
b"configured_mail_user\x00" as *const u8 as *const libc::c_char,
b"configured_mail_pw\x00" as *const u8 as *const libc::c_char,
b"configured_mail_port\x00" as *const u8 as *const libc::c_char,
b"configured_send_server\x00" as *const u8 as *const libc::c_char,
b"configured_send_user\x00" as *const u8 as *const libc::c_char,
b"configured_send_pw\x00" as *const u8 as *const libc::c_char,
b"configured_send_port\x00" as *const u8 as *const libc::c_char,
b"configured_server_flags\x00" as *const u8 as *const libc::c_char,
b"configured\x00" as *const u8 as *const libc::c_char,
];
pub unsafe fn dc_get_config(context: &Context, key: *const libc::c_char) -> *mut libc::c_char {
let mut value = 0 as *mut libc::c_char;
if !key.is_null()
&& *key.offset(0isize) as libc::c_int == 's' as i32
&& *key.offset(1isize) as libc::c_int == 'y' as i32
&& *key.offset(2isize) as libc::c_int == 's' as i32
&& *key.offset(3isize) as libc::c_int == '.' as i32
{
return get_sys_config_str(key);
}
if key.is_null() || 0 == is_gettable_config_key(key) {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
if strcmp(key, b"selfavatar\x00" as *const u8 as *const libc::c_char) == 0 {
let rel_path: *mut libc::c_char =
dc_sqlite3_get_config(context, &context.sql, key, 0 as *const libc::c_char);
if !rel_path.is_null() {
value = dc_get_abs_path(context, rel_path);
free(rel_path as *mut libc::c_void);
}
} else {
value = dc_sqlite3_get_config(context, &context.sql, key, 0 as *const libc::c_char)
}
if value.is_null() {
if strcmp(key, b"e2ee_enabled\x00" as *const u8 as *const libc::c_char) == 0 {
value = dc_mprintf(b"%i\x00" as *const u8 as *const libc::c_char, 1)
} else if strcmp(key, b"mdns_enabled\x00" as *const u8 as *const libc::c_char) == 0 {
value = dc_mprintf(b"%i\x00" as *const u8 as *const libc::c_char, 1)
} else if strcmp(key, b"imap_folder\x00" as *const u8 as *const libc::c_char) == 0 {
value = dc_strdup(b"INBOX\x00" as *const u8 as *const libc::c_char)
} else if strcmp(key, b"inbox_watch\x00" as *const u8 as *const libc::c_char) == 0 {
value = dc_mprintf(b"%i\x00" as *const u8 as *const libc::c_char, 1)
} else if strcmp(
key,
b"sentbox_watch\x00" as *const u8 as *const libc::c_char,
) == 0
{
value = dc_mprintf(b"%i\x00" as *const u8 as *const libc::c_char, 1)
} else if strcmp(key, b"mvbox_watch\x00" as *const u8 as *const libc::c_char) == 0 {
value = dc_mprintf(b"%i\x00" as *const u8 as *const libc::c_char, 1)
} else if strcmp(key, b"mvbox_move\x00" as *const u8 as *const libc::c_char) == 0 {
value = dc_mprintf(b"%i\x00" as *const u8 as *const libc::c_char, 1)
} else if strcmp(key, b"show_emails\x00" as *const u8 as *const libc::c_char) == 0 {
value = dc_mprintf(b"%i\x00" as *const u8 as *const libc::c_char, 0)
} else if strcmp(key, b"selfstatus\x00" as *const u8 as *const libc::c_char) == 0 {
value = dc_stock_str(context, 13)
} else {
value = dc_mprintf(b"\x00" as *const u8 as *const libc::c_char)
}
}
value
}
unsafe fn is_gettable_config_key(key: *const libc::c_char) -> libc::c_int {
let mut i = 0;
while i
< (::std::mem::size_of::<[*const libc::c_char; 3]>())
.wrapping_div(::std::mem::size_of::<*mut libc::c_char>())
{
if strcmp(key, sys_config_keys[i as usize]) == 0 {
return 1;
}
i += 1
}
is_settable_config_key(key)
}
// deprecated
static mut sys_config_keys: [*const libc::c_char; 3] = [
b"sys.version\x00" as *const u8 as *const libc::c_char,
b"sys.msgsize_max_recommended\x00" as *const u8 as *const libc::c_char,
b"sys.config_keys\x00" as *const u8 as *const libc::c_char,
];
unsafe fn get_sys_config_str(key: *const libc::c_char) -> *mut libc::c_char {
if strcmp(key, b"sys.version\x00" as *const u8 as *const libc::c_char) == 0 {
return dc_strdup(DC_VERSION_STR as *const u8 as *const libc::c_char);
} else if strcmp(
key,
b"sys.msgsize_max_recommended\x00" as *const u8 as *const libc::c_char,
) == 0
{
return dc_mprintf(
b"%i\x00" as *const u8 as *const libc::c_char,
24 * 1024 * 1024 / 4 * 3,
);
} else if strcmp(
key,
b"sys.config_keys\x00" as *const u8 as *const libc::c_char,
) == 0
{
return get_config_keys_str();
} else {
return dc_strdup(0 as *const libc::c_char);
};
}
unsafe fn get_config_keys_str() -> *mut libc::c_char {
let mut ret = String::new();
let mut i = 0;
while i
< (::std::mem::size_of::<[*const libc::c_char; 33]>())
.wrapping_div(::std::mem::size_of::<*mut libc::c_char>())
{
if !ret.is_empty() {
ret += " ";
}
ret += &to_string(config_keys[i as usize]);
i += 1
}
let mut i = 0;
while i
< (::std::mem::size_of::<[*const libc::c_char; 3]>())
.wrapping_div(::std::mem::size_of::<*mut libc::c_char>())
{
if !ret.is_empty() {
ret += " ";
}
ret += &to_string(sys_config_keys[i as usize]);
i += 1
}
strdup(to_cstring(ret).as_ptr())
}
pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
let unset = "0";
let l = dc_loginparam_read(context, &context.sql, "");
let l2 = dc_loginparam_read(context, &context.sql, "configured_");
let displayname = context.sql.get_config(context, "displayname");
let l = dc_loginparam_new();
let l2 = dc_loginparam_new();
dc_loginparam_read(
context,
l,
&context.sql,
b"\x00" as *const u8 as *const libc::c_char,
);
dc_loginparam_read(
context,
l2,
&context.sql,
b"configured_\x00" as *const u8 as *const libc::c_char,
);
let displayname = dc_sqlite3_get_config(
context,
&context.sql,
b"displayname\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
let chats = dc_get_chat_cnt(context) as usize;
let real_msgs = dc_get_real_msg_cnt(context) as usize;
let deaddrop_msgs = dc_get_deaddrop_msg_cnt(context) as usize;
let contacts = Contact::get_real_cnt(context) as usize;
let is_configured = context
.sql
.get_config_int(context, "configured")
.unwrap_or_default();
let dbversion = context
.sql
.get_config_int(context, "dbversion")
.unwrap_or_default();
let e2ee_enabled = context
.sql
.get_config_int(context, "e2ee_enabled")
.unwrap_or_else(|| 1);
let mdns_enabled = context
.sql
.get_config_int(context, "mdns_enabled")
.unwrap_or_else(|| 1);
let prv_key_cnt: Option<isize> = context.sql.query_row_col(
let contacts = dc_get_real_contact_cnt(context) as usize;
let is_configured = dc_sqlite3_get_config_int(
context,
"SELECT COUNT(*) FROM keypairs;",
rusqlite::NO_PARAMS,
&context.sql,
b"configured\x00" as *const u8 as *const libc::c_char,
0,
);
let pub_key_cnt: Option<isize> = context.sql.query_row_col(
let dbversion = dc_sqlite3_get_config_int(
context,
"SELECT COUNT(*) FROM acpeerstates;",
rusqlite::NO_PARAMS,
&context.sql,
b"dbversion\x00" as *const u8 as *const libc::c_char,
0,
);
let e2ee_enabled = dc_sqlite3_get_config_int(
context,
&context.sql,
b"e2ee_enabled\x00" as *const u8 as *const libc::c_char,
1,
);
let mdns_enabled = dc_sqlite3_get_config_int(
context,
&context.sql,
b"mdns_enabled\x00" as *const u8 as *const libc::c_char,
1,
);
let mut stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT COUNT(*) FROM keypairs;\x00" as *const u8 as *const libc::c_char,
);
sqlite3_step(stmt);
let prv_key_cnt = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT COUNT(*) FROM acpeerstates;\x00" as *const u8 as *const libc::c_char,
);
sqlite3_step(stmt);
let pub_key_cnt = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
let fingerprint_str =
if let Some(key) = Key::from_self_public(context, (*l2).addr, &context.sql) {
key.fingerprint()
} else {
"<Not yet calculated>".into()
};
let fingerprint_str = if let Some(key) = Key::from_self_public(context, &l2.addr, &context.sql)
{
key.fingerprint()
} else {
"<Not yet calculated>".into()
};
let l_readable_str = dc_loginparam_get_readable(&l);
let l2_readable_str = dc_loginparam_get_readable(&l2);
let inbox_watch = context
.sql
.get_config_int(context, "inbox_watch")
.unwrap_or_else(|| 1);
let sentbox_watch = context
.sql
.get_config_int(context, "sentbox_watch")
.unwrap_or_else(|| 1);
let mvbox_watch = context
.sql
.get_config_int(context, "mvbox_watch")
.unwrap_or_else(|| 1);
let mvbox_move = context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1);
let folders_configured = context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default();
let configured_sentbox_folder = context
.sql
.get_config(context, "configured_sentbox_folder")
.unwrap_or_else(|| "<unset>".to_string());
let configured_mvbox_folder = context
.sql
.get_config(context, "configured_mvbox_folder")
.unwrap_or_else(|| "<unset>".to_string());
let l_readable_str = dc_loginparam_get_readable(l);
let l2_readable_str = dc_loginparam_get_readable(l2);
let inbox_watch = dc_sqlite3_get_config_int(
context,
&context.sql,
b"inbox_watch\x00" as *const u8 as *const libc::c_char,
1,
);
let sentbox_watch = dc_sqlite3_get_config_int(
context,
&context.sql,
b"sentbox_watch\x00" as *const u8 as *const libc::c_char,
1,
);
let mvbox_watch = dc_sqlite3_get_config_int(
context,
&context.sql,
b"mvbox_watch\x00" as *const u8 as *const libc::c_char,
1,
);
let mvbox_move = dc_sqlite3_get_config_int(
context,
&context.sql,
b"mvbox_move\x00" as *const u8 as *const libc::c_char,
1,
);
let folders_configured = dc_sqlite3_get_config_int(
context,
&context.sql,
b"folders_configured\x00" as *const u8 as *const libc::c_char,
0,
);
let configured_sentbox_folder = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_sentbox_folder\x00" as *const u8 as *const libc::c_char,
b"<unset>\x00" as *const u8 as *const libc::c_char,
);
let configured_mvbox_folder = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_mvbox_folder\x00" as *const u8 as *const libc::c_char,
b"<unset>\x00" as *const u8 as *const libc::c_char,
);
let res = format!(
"deltachat_core_version=v{}\n\
@@ -446,7 +743,7 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
fingerprint={}\n\
level=awesome\n",
as_str(DC_VERSION_STR as *const u8 as *const _),
rusqlite::version(),
as_str(libsqlite3_sys::SQLITE_VERSION as *const u8 as *const libc::c_char),
sqlite3_threadsafe(),
// arch
(::std::mem::size_of::<*mut libc::c_void>()).wrapping_mul(8),
@@ -465,129 +762,192 @@ pub unsafe fn dc_get_info(context: &Context) -> *mut libc::c_char {
} else {
unset
},
displayname.unwrap_or_else(|| unset.into()),
if !displayname.is_null() {
as_str(displayname)
} else {
unset
},
is_configured,
l_readable_str,
l2_readable_str,
as_str(l_readable_str),
as_str(l2_readable_str),
inbox_watch,
sentbox_watch,
mvbox_watch,
mvbox_move,
folders_configured,
configured_sentbox_folder,
configured_mvbox_folder,
as_str(configured_sentbox_folder),
as_str(configured_mvbox_folder),
mdns_enabled,
e2ee_enabled,
prv_key_cnt.unwrap_or_default(),
pub_key_cnt.unwrap_or_default(),
prv_key_cnt,
pub_key_cnt,
fingerprint_str,
);
res.strdup()
dc_loginparam_unref(l);
dc_loginparam_unref(l2);
free(displayname as *mut libc::c_void);
free(l_readable_str as *mut libc::c_void);
free(l2_readable_str as *mut libc::c_void);
free(configured_sentbox_folder as *mut libc::c_void);
free(configured_mvbox_folder as *mut libc::c_void);
strdup(to_cstring(res).as_ptr())
}
pub unsafe fn dc_get_version_str() -> *mut libc::c_char {
dc_strdup(DC_VERSION_STR as *const u8 as *const libc::c_char)
}
pub fn dc_get_fresh_msgs(context: &Context) -> *mut dc_array_t {
pub unsafe fn dc_get_fresh_msgs(context: &Context) -> *mut dc_array_t {
let show_deaddrop = 0;
context
.sql
.query_map(
"SELECT m.id FROM msgs m LEFT JOIN contacts ct \
ON m.from_id=ct.id LEFT JOIN chats c ON m.chat_id=c.id WHERE m.state=? \
AND m.hidden=0 \
AND m.chat_id>? \
AND ct.blocked=0 \
AND (c.blocked=0 OR c.blocked=?) ORDER BY m.timestamp DESC,m.id DESC;",
&[10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|row| row.get(0),
|rows| {
let mut ret = dc_array_t::new(128);
for row in rows {
let id = row?;
ret.add_id(id);
}
Ok(ret.into_raw())
},
)
.unwrap()
let ret = dc_array_new(128 as size_t);
let mut stmt = 0 as *mut sqlite3_stmt;
if !ret.is_null() {
stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT m.id FROM msgs m LEFT JOIN contacts ct \
ON m.from_id=ct.id LEFT JOIN chats c ON m.chat_id=c.id WHERE m.state=? \
AND m.hidden=0 \
AND m.chat_id>? \
AND ct.blocked=0 \
AND (c.blocked=0 OR c.blocked=?) ORDER BY m.timestamp DESC,m.id DESC;\x00"
as *const u8 as *const libc::c_char,
);
sqlite3_bind_int(stmt, 1, 10);
sqlite3_bind_int(stmt, 2, 9);
sqlite3_bind_int(stmt, 3, if 0 != show_deaddrop { 2 } else { 0 });
while sqlite3_step(stmt) == 100 {
dc_array_add_id(ret, sqlite3_column_int(stmt, 0) as uint32_t);
}
}
sqlite3_finalize(stmt);
ret
}
#[allow(non_snake_case)]
pub fn dc_search_msgs(
pub unsafe fn dc_search_msgs(
context: &Context,
chat_id: uint32_t,
query: *const libc::c_char,
) -> *mut dc_array_t {
if query.is_null() {
return std::ptr::null_mut();
let mut success = 0;
let ret = dc_array_new(100 as size_t);
let mut strLikeInText = 0 as *mut libc::c_char;
let mut strLikeBeg = 0 as *mut libc::c_char;
let mut real_query = 0 as *mut libc::c_char;
let mut stmt = 0 as *mut sqlite3_stmt;
if !(ret.is_null() || query.is_null()) {
real_query = dc_strdup(query);
dc_trim(real_query);
if *real_query.offset(0isize) as libc::c_int == 0 {
success = 1
} else {
strLikeInText = dc_mprintf(
b"%%%s%%\x00" as *const u8 as *const libc::c_char,
real_query,
);
strLikeBeg = dc_mprintf(b"%s%%\x00" as *const u8 as *const libc::c_char, real_query);
if 0 != chat_id {
stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id WHERE m.chat_id=? \
AND m.hidden=0 \
AND ct.blocked=0 AND (txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp,m.id;\x00"
as *const u8 as *const libc::c_char
);
sqlite3_bind_int(stmt, 1, chat_id as libc::c_int);
sqlite3_bind_text(stmt, 2, strLikeInText, -1, None);
sqlite3_bind_text(stmt, 3, strLikeBeg, -1, None);
} else {
let show_deaddrop = 0;
stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id \
LEFT JOIN chats c ON m.chat_id=c.id WHERE m.chat_id>9 AND m.hidden=0 \
AND (c.blocked=0 OR c.blocked=?) \
AND ct.blocked=0 AND (m.txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp DESC,m.id DESC;\x00"
as *const u8 as *const libc::c_char
);
sqlite3_bind_int(stmt, 1, if 0 != show_deaddrop { 2 } else { 0 });
sqlite3_bind_text(stmt, 2, strLikeInText, -1, None);
sqlite3_bind_text(stmt, 3, strLikeBeg, -1, None);
}
while sqlite3_step(stmt) == 100 {
dc_array_add_id(ret, sqlite3_column_int(stmt, 0) as uint32_t);
}
success = 1
}
}
let real_query = to_string(query).trim().to_string();
if real_query.is_empty() {
return std::ptr::null_mut();
}
let strLikeInText = format!("%{}%", &real_query);
let strLikeBeg = format!("{}%", &real_query);
free(strLikeInText as *mut libc::c_void);
free(strLikeBeg as *mut libc::c_void);
free(real_query as *mut libc::c_void);
sqlite3_finalize(stmt);
let query = if 0 != chat_id {
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id WHERE m.chat_id=? \
AND m.hidden=0 \
AND ct.blocked=0 AND (txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp,m.id;"
if 0 != success {
ret
} else {
"SELECT m.id, m.timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id \
LEFT JOIN chats c ON m.chat_id=c.id WHERE m.chat_id>9 AND m.hidden=0 \
AND (c.blocked=0 OR c.blocked=?) \
AND ct.blocked=0 AND (m.txt LIKE ? OR ct.name LIKE ?) ORDER BY m.timestamp DESC,m.id DESC;"
};
let mut ret = dc_array_t::new(100);
let success = context
.sql
.query_map(
query,
params![chat_id as libc::c_int, &strLikeInText, &strLikeBeg],
|row| row.get::<_, i32>(0),
|rows| {
for id in rows {
ret.add_id(id? as u32);
}
Ok(())
},
)
.is_ok();
if success {
return ret.into_raw();
}
std::ptr::null_mut()
}
pub fn dc_is_inbox(_context: &Context, folder_name: impl AsRef<str>) -> bool {
folder_name.as_ref() == "INBOX"
}
pub fn dc_is_sentbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
let sentbox_name = context.sql.get_config(context, "configured_sentbox_folder");
if let Some(name) = sentbox_name {
name == folder_name.as_ref()
} else {
false
if !ret.is_null() {
dc_array_unref(ret);
}
0 as *mut dc_array_t
}
}
pub fn dc_is_mvbox(context: &Context, folder_name: impl AsRef<str>) -> bool {
let mvbox_name = context.sql.get_config(context, "configured_mvbox_folder");
if let Some(name) = mvbox_name {
name == folder_name.as_ref()
} else {
false
pub unsafe fn dc_is_inbox(_context: &Context, folder_name: *const libc::c_char) -> libc::c_int {
let mut is_inbox = 0;
if !folder_name.is_null() {
is_inbox = if strcasecmp(
b"INBOX\x00" as *const u8 as *const libc::c_char,
folder_name,
) == 0
{
1
} else {
0
}
}
is_inbox
}
pub unsafe fn dc_is_sentbox(context: &Context, folder_name: *const libc::c_char) -> libc::c_int {
let sentbox_name = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_sentbox_folder\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
let mut is_sentbox = 0;
if !sentbox_name.is_null() && !folder_name.is_null() {
is_sentbox = if strcasecmp(sentbox_name, folder_name) == 0 {
1
} else {
0
}
}
free(sentbox_name as *mut libc::c_void);
is_sentbox
}
pub unsafe fn dc_is_mvbox(context: &Context, folder_name: *const libc::c_char) -> libc::c_int {
let mvbox_name = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_mvbox_folder\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
let mut is_mvbox = 0;
if !mvbox_name.is_null() && !folder_name.is_null() {
is_mvbox = if strcasecmp(mvbox_name, folder_name) == 0 {
1
} else {
0
}
}
free(mvbox_name as *mut libc::c_void);
is_mvbox
}

View File

@@ -1,175 +1,76 @@
use crate::dc_location::dc_location;
use crate::context::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/* * the structure behind dc_array_t */
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub enum dc_array_t {
Locations(Vec<dc_location>),
Uint(Vec<uintptr_t>),
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_array_t {
pub magic: uint32_t,
pub allocated: size_t,
pub count: size_t,
pub type_0: libc::c_int,
pub array: *mut uintptr_t,
}
impl dc_array_t {
pub fn new(capacity: usize) -> Self {
dc_array_t::Uint(Vec::with_capacity(capacity))
}
/// Constructs a new, empty `dc_array_t` holding locations with specified `capacity`.
pub fn new_locations(capacity: usize) -> Self {
dc_array_t::Locations(Vec::with_capacity(capacity))
}
pub fn into_raw(self) -> *mut Self {
Box::into_raw(Box::new(self))
}
pub fn add_uint(&mut self, item: uintptr_t) {
if let Self::Uint(array) = self {
array.push(item);
} else {
panic!("Attempt to add uint to array of other type");
}
}
pub fn add_id(&mut self, item: uint32_t) {
self.add_uint(item as uintptr_t);
}
pub fn add_location(&mut self, location: dc_location) {
if let Self::Locations(array) = self {
array.push(location)
} else {
panic!("Attempt to add a location to array of other type");
}
}
pub fn get_uint(&self, index: usize) -> uintptr_t {
if let Self::Uint(array) = self {
array[index]
} else {
panic!("Attempt to get uint from array of other type");
}
}
pub fn get_id(&self, index: usize) -> uint32_t {
match self {
Self::Locations(array) => array[index].location_id,
Self::Uint(array) => array[index] as uint32_t,
}
}
pub fn get_ptr(&self, index: size_t) -> *mut libc::c_void {
if let Self::Uint(array) = self {
array[index] as *mut libc::c_void
} else {
panic!("Not an array of pointers");
}
}
pub fn get_location(&self, index: usize) -> &dc_location {
if let Self::Locations(array) = self {
&array[index]
} else {
panic!("Not an array of locations")
}
}
pub fn get_latitude(&self, index: usize) -> libc::c_double {
self.get_location(index).latitude
}
pub fn get_longitude(&self, index: size_t) -> libc::c_double {
self.get_location(index).longitude
}
pub fn get_accuracy(&self, index: size_t) -> libc::c_double {
self.get_location(index).accuracy
}
pub fn get_timestamp(&self, index: size_t) -> i64 {
self.get_location(index).timestamp
}
pub fn get_chat_id(&self, index: size_t) -> uint32_t {
self.get_location(index).chat_id
}
pub fn get_contact_id(&self, index: size_t) -> uint32_t {
self.get_location(index).contact_id
}
pub fn get_msg_id(&self, index: size_t) -> uint32_t {
self.get_location(index).msg_id
}
pub fn is_empty(&self) -> bool {
match self {
Self::Locations(array) => array.is_empty(),
Self::Uint(array) => array.is_empty(),
}
}
/// Returns the number of elements in the array.
pub fn len(&self) -> usize {
match self {
Self::Locations(array) => array.len(),
Self::Uint(array) => array.len(),
}
}
pub fn clear(&mut self) {
match self {
Self::Locations(array) => array.clear(),
Self::Uint(array) => array.clear(),
}
}
pub fn search_id(&self, needle: uintptr_t) -> Option<usize> {
if let Self::Uint(array) = self {
for (i, &u) in array.iter().enumerate() {
if u == needle {
return Some(i);
}
}
None
} else {
panic!("Attempt to search for id in array of other type");
}
}
pub fn sort_ids(&mut self) {
if let dc_array_t::Uint(v) = self {
v.sort();
} else {
panic!("Attempt to sort array of something other than uints");
}
}
}
impl From<Vec<dc_location>> for dc_array_t {
fn from(array: Vec<dc_location>) -> Self {
dc_array_t::Locations(array)
}
}
pub unsafe fn dc_array_unref(array: *mut dc_array_t) {
if array.is_null() {
/**
* @class dc_array_t
*
* An object containing a simple array.
* This object is used in several places where functions need to return an array.
* The items of the array are typically IDs.
* To free an array object, use dc_array_unref().
*/
pub unsafe fn dc_array_unref(mut array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return;
}
Box::from_raw(array);
if (*array).type_0 == 1i32 {
dc_array_free_ptr(array);
}
free((*array).array as *mut libc::c_void);
(*array).magic = 0i32 as uint32_t;
free(array as *mut libc::c_void);
}
pub unsafe fn dc_array_add_uint(array: *mut dc_array_t, item: uintptr_t) {
if !array.is_null() {
(*array).add_uint(item);
pub unsafe fn dc_array_free_ptr(array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return;
}
let mut i: size_t = 0i32 as size_t;
while i < (*array).count {
if (*array).type_0 == 1i32 {
free(
(*(*(*array).array.offset(i as isize) as *mut _dc_location)).marker
as *mut libc::c_void,
);
}
free(*(*array).array.offset(i as isize) as *mut libc::c_void);
*(*array).array.offset(i as isize) = 0i32 as uintptr_t;
i = i.wrapping_add(1)
}
}
pub unsafe fn dc_array_add_uint(mut array: *mut dc_array_t, item: uintptr_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return;
}
if (*array).count == (*array).allocated {
let newsize = (*array).allocated.wrapping_mul(2).wrapping_add(10);
(*array).array = realloc(
(*array).array as *mut libc::c_void,
(newsize).wrapping_mul(::std::mem::size_of::<uintptr_t>()),
) as *mut uintptr_t;
assert!(!(*array).array.is_null());
(*array).allocated = newsize as size_t
}
*(*array).array.offset((*array).count as isize) = item;
(*array).count = (*array).count.wrapping_add(1);
}
pub unsafe fn dc_array_add_id(array: *mut dc_array_t, item: uint32_t) {
if !array.is_null() {
(*array).add_id(item);
}
dc_array_add_uint(array, item as uintptr_t);
}
pub unsafe fn dc_array_add_ptr(array: *mut dc_array_t, item: *mut libc::c_void) {
@@ -177,107 +78,130 @@ pub unsafe fn dc_array_add_ptr(array: *mut dc_array_t, item: *mut libc::c_void)
}
pub unsafe fn dc_array_get_cnt(array: *const dc_array_t) -> size_t {
if array.is_null() {
0
} else {
(*array).len()
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return 0i32 as size_t;
}
(*array).count
}
pub unsafe fn dc_array_get_uint(array: *const dc_array_t, index: size_t) -> uintptr_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_uint(index)
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
return 0i32 as uintptr_t;
}
*(*array).array.offset(index as isize)
}
pub unsafe fn dc_array_get_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_id(index)
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
return 0i32 as uint32_t;
}
if (*array).type_0 == 1i32 {
return (*(*(*array).array.offset(index as isize) as *mut _dc_location)).location_id;
}
*(*array).array.offset(index as isize) as uint32_t
}
pub unsafe fn dc_array_get_ptr(array: *const dc_array_t, index: size_t) -> *mut libc::c_void {
if array.is_null() || index >= (*array).len() {
std::ptr::null_mut()
} else {
(*array).get_ptr(index)
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || index >= (*array).count {
return 0 as *mut libc::c_void;
}
*(*array).array.offset(index as isize) as *mut libc::c_void
}
pub unsafe fn dc_array_get_latitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null() || index >= (*array).len() {
0.0
} else {
(*array).get_latitude(index)
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as libc::c_double;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).latitude
}
pub unsafe fn dc_array_get_longitude(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null() || index >= (*array).len() {
0.0
} else {
(*array).get_longitude(index)
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as libc::c_double;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).longitude
}
pub unsafe fn dc_array_get_accuracy(array: *const dc_array_t, index: size_t) -> libc::c_double {
if array.is_null() || index >= (*array).len() {
0.0
} else {
(*array).get_accuracy(index)
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as libc::c_double;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).accuracy
}
pub unsafe fn dc_array_get_timestamp(array: *const dc_array_t, index: size_t) -> i64 {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_timestamp(index)
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).timestamp
}
pub unsafe fn dc_array_get_chat_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_chat_id(index)
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as uint32_t;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).chat_id
}
pub unsafe fn dc_array_get_contact_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_contact_id(index)
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as uint32_t;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).contact_id
}
pub unsafe fn dc_array_get_msg_id(array: *const dc_array_t, index: size_t) -> uint32_t {
if array.is_null() || index >= (*array).len() {
0
} else {
(*array).get_msg_id(index)
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0i32 as uint32_t;
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).msg_id
}
pub unsafe fn dc_array_get_marker(array: *const dc_array_t, index: size_t) -> *mut libc::c_char {
if array.is_null() || index >= (*array).len() {
return std::ptr::null_mut();
}
if let dc_array_t::Locations(v) = &*array {
if let Some(s) = &v[index].marker {
s.strdup()
} else {
std::ptr::null_mut()
}
} else {
std::ptr::null_mut()
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0 as *mut libc::c_char;
}
dc_strdup_keep_null((*(*(*array).array.offset(index as isize) as *mut _dc_location)).marker)
}
/**
@@ -291,15 +215,16 @@ pub unsafe fn dc_array_get_marker(array: *const dc_array_t, index: size_t) -> *m
* 1=Location was reported independently.
*/
pub unsafe fn dc_array_is_independent(array: *const dc_array_t, index: size_t) -> libc::c_int {
if array.is_null() || index >= (*array).len() {
if array.is_null()
|| (*array).magic != 0xa11aai32 as libc::c_uint
|| index >= (*array).count
|| (*array).type_0 != 1i32
|| *(*array).array.offset(index as isize) == 0
{
return 0;
}
if let dc_array_t::Locations(v) = &*array {
v[index].independent as libc::c_int
} else {
panic!("Attempt to get location independent field from array of something other than locations");
}
(*(*(*array).array.offset(index as isize) as *mut _dc_location)).independent as libc::c_int
}
pub unsafe fn dc_array_search_id(
@@ -307,84 +232,176 @@ pub unsafe fn dc_array_search_id(
needle: uint32_t,
ret_index: *mut size_t,
) -> bool {
if array.is_null() {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return false;
}
if let Some(i) = (*array).search_id(needle as uintptr_t) {
if !ret_index.is_null() {
*ret_index = i
let data: *mut uintptr_t = (*array).array;
let mut i: size_t = 0;
let cnt: size_t = (*array).count;
while i < cnt {
if *data.offset(i as isize) == needle as size_t {
if !ret_index.is_null() {
*ret_index = i
}
return true;
}
true
} else {
false
i = i.wrapping_add(1)
}
false
}
pub unsafe fn dc_array_get_raw(array: *const dc_array_t) -> *const uintptr_t {
if array.is_null() {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return 0 as *const uintptr_t;
}
if let dc_array_t::Uint(v) = &*array {
v.as_ptr()
} else {
panic!("Attempt to convert array of something other than uints to raw");
(*array).array
}
pub unsafe fn dc_array_new(initsize: size_t) -> *mut dc_array_t {
dc_array_new_typed(0, initsize)
}
pub unsafe fn dc_array_new_typed(type_0: libc::c_int, initsize: size_t) -> *mut dc_array_t {
let mut array: *mut dc_array_t;
array = calloc(1, ::std::mem::size_of::<dc_array_t>()) as *mut dc_array_t;
assert!(!array.is_null());
(*array).magic = 0xa11aai32 as uint32_t;
(*array).count = 0i32 as size_t;
(*array).allocated = if initsize < 1 { 1 } else { initsize };
(*array).type_0 = type_0;
(*array).array = malloc(
(*array)
.allocated
.wrapping_mul(::std::mem::size_of::<uintptr_t>()),
) as *mut uintptr_t;
if (*array).array.is_null() {
exit(48i32);
}
array
}
pub fn dc_array_new(initsize: size_t) -> *mut dc_array_t {
dc_array_t::new(initsize).into_raw()
}
pub fn dc_array_new_locations(initsize: size_t) -> *mut dc_array_t {
dc_array_t::new_locations(initsize).into_raw()
}
pub unsafe fn dc_array_empty(array: *mut dc_array_t) {
if array.is_null() {
pub unsafe fn dc_array_empty(mut array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return;
}
(*array).clear()
(*array).count = 0i32 as size_t;
}
pub unsafe fn dc_array_duplicate(array: *const dc_array_t) -> *mut dc_array_t {
if array.is_null() {
std::ptr::null_mut()
} else {
(*array).clone().into_raw()
let mut ret: *mut dc_array_t;
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint {
return 0 as *mut dc_array_t;
}
ret = dc_array_new((*array).allocated);
(*ret).count = (*array).count;
memcpy(
(*ret).array as *mut libc::c_void,
(*array).array as *const libc::c_void,
(*array)
.count
.wrapping_mul(::std::mem::size_of::<uintptr_t>()),
);
ret
}
pub unsafe fn dc_array_sort_ids(array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || (*array).count <= 1 {
return;
}
qsort(
(*array).array as *mut libc::c_void,
(*array).count,
::std::mem::size_of::<uintptr_t>(),
Some(cmp_intptr_t),
);
}
unsafe extern "C" fn cmp_intptr_t(p1: *const libc::c_void, p2: *const libc::c_void) -> libc::c_int {
let v1: uintptr_t = *(p1 as *mut uintptr_t);
let v2: uintptr_t = *(p2 as *mut uintptr_t);
return if v1 < v2 {
-1i32
} else if v1 > v2 {
1i32
} else {
0i32
};
}
pub unsafe fn dc_array_sort_strings(array: *mut dc_array_t) {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || (*array).count <= 1 {
return;
}
qsort(
(*array).array as *mut libc::c_void,
(*array).count,
::std::mem::size_of::<*mut libc::c_char>(),
Some(cmp_strings_t),
);
}
unsafe extern "C" fn cmp_strings_t(
p1: *const libc::c_void,
p2: *const libc::c_void,
) -> libc::c_int {
let v1: *const libc::c_char = *(p1 as *mut *const libc::c_char);
let v2: *const libc::c_char = *(p2 as *mut *const libc::c_char);
strcmp(v1, v2)
}
pub unsafe fn dc_array_get_string(
array: *const dc_array_t,
sep: *const libc::c_char,
) -> *mut libc::c_char {
if array.is_null() || sep.is_null() {
if array.is_null() || (*array).magic != 0xa11aai32 as libc::c_uint || sep.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
if let dc_array_t::Uint(v) = &*array {
let cnt = v.len();
let sep = as_str(sep);
let cnt = (*array).count as usize;
let slice = std::slice::from_raw_parts((*array).array, cnt);
let sep = as_str(sep);
let res = v
.iter()
.enumerate()
.fold(String::with_capacity(2 * cnt), |res, (i, n)| {
if i == 0 {
res + &n.to_string()
} else {
res + sep + &n.to_string()
}
});
res.strdup()
} else {
panic!("Attempt to get string from array of other type");
let res = slice
.iter()
.enumerate()
.fold(String::with_capacity(2 * cnt), |mut res, (i, n)| {
if i == 0 {
res += &n.to_string();
} else {
res += sep;
res += &n.to_string();
}
res
});
strdup(to_cstring(res).as_ptr())
}
/// return comma-separated value-string from integer array
pub unsafe fn dc_arr_to_string(arr: *const uint32_t, cnt: libc::c_int) -> *mut libc::c_char {
if arr.is_null() || cnt == 0 {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
let slice = std::slice::from_raw_parts(arr, cnt as usize);
let res = slice.iter().enumerate().fold(
String::with_capacity(2 * cnt as usize),
|mut res, (i, n)| {
if i == 0 {
res += &n.to_string();
} else {
res += ",";
res += &n.to_string();
}
res
},
);
strdup(to_cstring(res).as_ptr())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::x::*;
use std::ffi::CStr;
#[test]
@@ -393,17 +410,22 @@ mod tests {
let arr = dc_array_new(7 as size_t);
assert_eq!(dc_array_get_cnt(arr), 0);
for i in 0..1000 {
let mut i: libc::c_int = 0;
while i < 1000 {
dc_array_add_id(arr, (i + 2) as uint32_t);
i += 1
}
assert_eq!(dc_array_get_cnt(arr), 1000);
for i in 0..1000 {
i = 0;
while i < 1000i32 {
assert_eq!(
dc_array_get_id(arr, i as size_t),
(i + 1i32 * 2i32) as libc::c_uint
);
i += 1
}
assert_eq!(dc_array_get_id(arr, -1i32 as size_t), 0);
@@ -420,7 +442,7 @@ mod tests {
dc_array_add_id(arr, 0 as uint32_t);
dc_array_add_id(arr, 5000 as uint32_t);
(*arr).sort_ids();
dc_array_sort_ids(arr);
assert_eq!(dc_array_get_id(arr, 0 as size_t), 0);
assert_eq!(dc_array_get_id(arr, 1 as size_t), 7);
@@ -434,6 +456,38 @@ mod tests {
);
free(str as *mut libc::c_void);
dc_array_empty(arr);
dc_array_add_ptr(
arr,
b"XX\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
);
dc_array_add_ptr(
arr,
b"item1\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
);
dc_array_add_ptr(
arr,
b"bbb\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
);
dc_array_add_ptr(
arr,
b"aaa\x00" as *const u8 as *const libc::c_char as *mut libc::c_void,
);
dc_array_sort_strings(arr);
let str = dc_array_get_ptr(arr, 0 as size_t) as *mut libc::c_char;
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "XX");
let str = dc_array_get_ptr(arr, 1 as size_t) as *mut libc::c_char;
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "aaa");
let str = dc_array_get_ptr(arr, 2 as size_t) as *mut libc::c_char;
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "bbb");
let str = dc_array_get_ptr(arr, 3 as size_t) as *mut libc::c_char;
assert_eq!(CStr::from_ptr(str).to_str().unwrap(), "item1");
dc_array_unref(arr);
}
}

File diff suppressed because it is too large Load Diff

368
src/dc_chatlist.rs Normal file
View File

@@ -0,0 +1,368 @@
use crate::context::*;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_lot::*;
use crate::dc_msg::*;
use crate::dc_sqlite3::*;
use crate::dc_stock::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/* * the structure behind dc_chatlist_t */
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_chatlist_t<'a> {
pub magic: uint32_t,
pub context: &'a Context,
pub cnt: size_t,
pub chatNlastmsg_ids: *mut dc_array_t,
}
// handle chatlists
pub unsafe fn dc_get_chatlist<'a>(
context: &'a Context,
listflags: libc::c_int,
query_str: *const libc::c_char,
query_id: uint32_t,
) -> *mut dc_chatlist_t<'a> {
let mut success: libc::c_int = 0i32;
let obj: *mut dc_chatlist_t = dc_chatlist_new(context);
if !(0 == dc_chatlist_load_from_db(obj, listflags, query_str, query_id)) {
success = 1i32
}
if 0 != success {
return obj;
} else {
dc_chatlist_unref(obj);
return 0 as *mut dc_chatlist_t;
};
}
/**
* @class dc_chatlist_t
*
* An object representing a single chatlist in memory.
* Chatlist objects contain chat IDs
* and, if possible, message IDs belonging to them.
* The chatlist object is not updated;
* if you want an update, you have to recreate the object.
*
* For a **typical chat overview**,
* the idea is to get the list of all chats via dc_get_chatlist()
* without any listflags (see below)
* and to implement a "virtual list" or so
* (the count of chats is known by dc_chatlist_get_cnt()).
*
* Only for the items that are in view
* (the list may have several hundreds chats),
* the UI should call dc_chatlist_get_summary() then.
* dc_chatlist_get_summary() provides all elements needed for painting the item.
*
* On a click of such an item,
* the UI should change to the chat view
* and get all messages from this view via dc_get_chat_msgs().
* Again, a "virtual list" is created
* (the count of messages is known)
* and for each messages that is scrolled into view, dc_get_msg() is called then.
*
* Why no listflags?
* Without listflags, dc_get_chatlist() adds the deaddrop
* and the archive "link" automatically as needed.
* The UI can just render these items differently then.
* Although the deaddrop link is currently always the 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.
*/
pub unsafe fn dc_chatlist_new(context: &Context) -> *mut dc_chatlist_t {
let mut chatlist: *mut dc_chatlist_t;
chatlist = calloc(1, ::std::mem::size_of::<dc_chatlist_t>()) as *mut dc_chatlist_t;
assert!(!chatlist.is_null());
(*chatlist).magic = 0xc4a71157u32;
(*chatlist).context = context;
(*chatlist).chatNlastmsg_ids = dc_array_new(128i32 as size_t);
assert!(!(*chatlist).chatNlastmsg_ids.is_null());
chatlist
}
pub unsafe fn dc_chatlist_unref(mut chatlist: *mut dc_chatlist_t) {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return;
}
dc_chatlist_empty(chatlist);
dc_array_unref((*chatlist).chatNlastmsg_ids);
(*chatlist).magic = 0i32 as uint32_t;
free(chatlist as *mut libc::c_void);
}
pub unsafe fn dc_chatlist_empty(mut chatlist: *mut dc_chatlist_t) {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return;
}
(*chatlist).cnt = 0i32 as size_t;
dc_array_empty((*chatlist).chatNlastmsg_ids);
}
/**
* Load a chatlist from the database to the chatlist object.
*
* @private @memberof dc_chatlist_t
*/
// TODO should return bool /rtn
unsafe fn dc_chatlist_load_from_db(
mut chatlist: *mut dc_chatlist_t,
listflags: libc::c_int,
query__: *const libc::c_char,
query_contact_id: uint32_t,
) -> libc::c_int {
let current_block: u64;
//clock_t start = clock();
let mut success: libc::c_int = 0i32;
let mut add_archived_link_item: libc::c_int = 0i32;
let mut stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
let mut strLikeCmd: *mut libc::c_char = 0 as *mut libc::c_char;
let mut query: *mut libc::c_char = 0 as *mut libc::c_char;
if !(chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32) {
dc_chatlist_empty(chatlist);
// select with left join and minimum:
// - the inner select must use `hidden` and _not_ `m.hidden`
// which would refer the outer select and take a lot of time
// - `GROUP BY` is needed several messages may have the same timestamp
// - the list starts with the newest chats
// nb: the query currently shows messages from blocked contacts in groups.
// however, for normal-groups, this is okay as the message is also returned by dc_get_chat_msgs()
// (otherwise it would be hard to follow conversations, wa and tg do the same)
// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
// shown at all permanent in the chatlist.
if 0 != query_contact_id {
stmt =
dc_sqlite3_prepare(
(*chatlist).context,
&(*chatlist).context.sql,
b"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id AND m.timestamp=( SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 AND c.blocked=0 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;\x00"
as *const u8 as *const libc::c_char
);
sqlite3_bind_int(stmt, 1i32, query_contact_id as libc::c_int);
current_block = 3437258052017859086;
} else if 0 != listflags & 0x1i32 {
stmt =
dc_sqlite3_prepare(
(*chatlist).context,
&(*chatlist).context.sql,
b"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id AND m.timestamp=( SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 AND c.blocked=0 AND c.archived=1 GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;\x00"
as *const u8 as *const libc::c_char);
current_block = 3437258052017859086;
} else if query__.is_null() {
if 0 == listflags & 0x2i32 {
let last_deaddrop_fresh_msg_id: uint32_t =
get_last_deaddrop_fresh_msg((*chatlist).context);
if last_deaddrop_fresh_msg_id > 0i32 as libc::c_uint {
dc_array_add_id((*chatlist).chatNlastmsg_ids, 1i32 as uint32_t);
dc_array_add_id((*chatlist).chatNlastmsg_ids, last_deaddrop_fresh_msg_id);
}
add_archived_link_item = 1i32
}
stmt =
dc_sqlite3_prepare(
(*chatlist).context,
&(*chatlist).context.sql,
b"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id AND m.timestamp=( SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 AND c.blocked=0 AND c.archived=0 GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;\x00"
as *const u8 as *const libc::c_char);
current_block = 3437258052017859086;
} else {
query = dc_strdup(query__);
dc_trim(query);
if *query.offset(0isize) as libc::c_int == 0i32 {
success = 1i32;
current_block = 15179736777190528364;
} else {
strLikeCmd = dc_mprintf(b"%%%s%%\x00" as *const u8 as *const libc::c_char, query);
stmt =
dc_sqlite3_prepare(
(*chatlist).context,
&(*chatlist).context.sql,
b"SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id AND m.timestamp=( SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND (hidden=0 OR (hidden=1 AND state=19))) WHERE c.id>9 AND c.blocked=0 AND c.name LIKE ? GROUP BY c.id ORDER BY IFNULL(m.timestamp,0) DESC, m.id DESC;\x00"
as *const u8 as
*const libc::c_char);
sqlite3_bind_text(stmt, 1i32, strLikeCmd, -1i32, None);
current_block = 3437258052017859086;
}
}
match current_block {
15179736777190528364 => {}
_ => {
while sqlite3_step(stmt) == 100i32 {
dc_array_add_id(
(*chatlist).chatNlastmsg_ids,
sqlite3_column_int(stmt, 0i32) as uint32_t,
);
dc_array_add_id(
(*chatlist).chatNlastmsg_ids,
sqlite3_column_int(stmt, 1i32) as uint32_t,
);
}
if 0 != add_archived_link_item && dc_get_archived_cnt((*chatlist).context) > 0i32 {
if dc_array_get_cnt((*chatlist).chatNlastmsg_ids) == 0
&& 0 != listflags & 0x4i32
{
dc_array_add_id((*chatlist).chatNlastmsg_ids, 7i32 as uint32_t);
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0i32 as uint32_t);
}
dc_array_add_id((*chatlist).chatNlastmsg_ids, 6i32 as uint32_t);
dc_array_add_id((*chatlist).chatNlastmsg_ids, 0i32 as uint32_t);
}
(*chatlist).cnt = dc_array_get_cnt((*chatlist).chatNlastmsg_ids).wrapping_div(2);
success = 1i32
}
}
}
sqlite3_finalize(stmt);
free(query as *mut libc::c_void);
free(strLikeCmd as *mut libc::c_void);
success
}
// Context functions to work with chatlist
pub unsafe fn dc_get_archived_cnt(context: &Context) -> libc::c_int {
let mut ret: libc::c_int = 0i32;
let stmt: *mut sqlite3_stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;\x00" as *const u8
as *const libc::c_char,
);
if sqlite3_step(stmt) == 100i32 {
ret = sqlite3_column_int(stmt, 0i32)
}
sqlite3_finalize(stmt);
ret
}
unsafe fn get_last_deaddrop_fresh_msg(context: &Context) -> uint32_t {
let mut ret: uint32_t = 0i32 as uint32_t;
let stmt: *mut sqlite3_stmt;
stmt =
dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT m.id FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id WHERE m.state=10 AND m.hidden=0 AND c.blocked=2 ORDER BY m.timestamp DESC, m.id DESC;\x00"
as *const u8 as *const libc::c_char);
/* we have an index over the state-column, this should be sufficient as there are typically only few fresh messages */
if !(sqlite3_step(stmt) != 100i32) {
ret = sqlite3_column_int(stmt, 0i32) as uint32_t
}
sqlite3_finalize(stmt);
ret
}
pub unsafe fn dc_chatlist_get_cnt(chatlist: *const dc_chatlist_t) -> size_t {
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 {
return 0i32 as size_t;
}
(*chatlist).cnt
}
pub unsafe fn dc_chatlist_get_chat_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
if chatlist.is_null()
|| (*chatlist).magic != 0xc4a71157u32
|| (*chatlist).chatNlastmsg_ids.is_null()
|| index >= (*chatlist).cnt
{
return 0i32 as uint32_t;
}
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2))
}
pub unsafe fn dc_chatlist_get_msg_id(chatlist: *const dc_chatlist_t, index: size_t) -> uint32_t {
if chatlist.is_null()
|| (*chatlist).magic != 0xc4a71157u32
|| (*chatlist).chatNlastmsg_ids.is_null()
|| index >= (*chatlist).cnt
{
return 0i32 as uint32_t;
}
dc_array_get_id(
(*chatlist).chatNlastmsg_ids,
index.wrapping_mul(2).wrapping_add(1),
)
}
pub unsafe fn dc_chatlist_get_summary<'a>(
chatlist: *const dc_chatlist_t<'a>,
index: size_t,
mut chat: *mut Chat<'a>,
) -> *mut dc_lot_t {
let current_block: u64;
/* The summary is created by the chat, not by the last message.
This is because we may want to display drafts here or stuff as
"is typing".
Also, sth. as "No messages" would not work if the summary comes from a
message. */
/* the function never returns NULL */
let mut ret: *mut dc_lot_t = dc_lot_new();
let lastmsg_id: uint32_t;
let mut lastmsg: *mut dc_msg_t = 0 as *mut dc_msg_t;
let mut lastcontact: *mut dc_contact_t = 0 as *mut dc_contact_t;
let mut chat_to_delete: *mut Chat = 0 as *mut Chat;
if chatlist.is_null() || (*chatlist).magic != 0xc4a71157u32 || index >= (*chatlist).cnt {
(*ret).text2 = dc_strdup(b"ErrBadChatlistIndex\x00" as *const u8 as *const libc::c_char)
} else {
lastmsg_id = dc_array_get_id(
(*chatlist).chatNlastmsg_ids,
index.wrapping_mul(2).wrapping_add(1),
);
if chat.is_null() {
chat = dc_chat_new((*chatlist).context);
chat_to_delete = chat;
if !dc_chat_load_from_db(
chat,
dc_array_get_id((*chatlist).chatNlastmsg_ids, index.wrapping_mul(2)),
) {
(*ret).text2 =
dc_strdup(b"ErrCannotReadChat\x00" as *const u8 as *const libc::c_char);
current_block = 3777403817673069519;
} else {
current_block = 7651349459974463963;
}
} else {
current_block = 7651349459974463963;
}
match current_block {
3777403817673069519 => {}
_ => {
if 0 != lastmsg_id {
lastmsg = dc_msg_new_untyped((*chatlist).context);
dc_msg_load_from_db(lastmsg, (*chatlist).context, lastmsg_id);
if (*lastmsg).from_id != 1i32 as libc::c_uint
&& ((*chat).type_0 == 120i32 || (*chat).type_0 == 130i32)
{
lastcontact = dc_contact_new((*chatlist).context);
dc_contact_load_from_db(
lastcontact,
&(*chatlist).context.sql,
(*lastmsg).from_id,
);
}
}
if (*chat).id == 6i32 as libc::c_uint {
(*ret).text2 = dc_strdup(0 as *const libc::c_char)
} else if lastmsg.is_null() || (*lastmsg).from_id == 0i32 as libc::c_uint {
(*ret).text2 = dc_stock_str((*chatlist).context, 1i32)
} else {
dc_lot_fill(ret, lastmsg, chat, lastcontact, (*chatlist).context);
}
}
}
}
dc_msg_unref(lastmsg);
dc_contact_unref(lastcontact);
dc_chat_unref(chat_to_delete);
ret
}

File diff suppressed because it is too large Load Diff

1288
src/dc_contact.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ lazy_static! {
struct Dehtml {
strbuilder: String,
add_text: AddText,
last_href: Option<String>,
last_href: *mut libc::c_char,
}
#[derive(Debug, PartialEq)]
@@ -32,7 +32,7 @@ pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char
let mut dehtml = Dehtml {
strbuilder: String::with_capacity(strlen(buf_terminated)),
add_text: AddText::YesRemoveLineEnds,
last_href: None,
last_href: 0 as *mut libc::c_char,
};
let mut saxparser = dc_saxparser_t {
starttag_cb: None,
@@ -51,8 +51,9 @@ pub unsafe fn dc_dehtml(buf_terminated: *mut libc::c_char) -> *mut libc::c_char
);
dc_saxparser_set_text_handler(&mut saxparser, Some(dehtml_text_cb));
dc_saxparser_parse(&mut saxparser, buf_terminated);
free(dehtml.last_href as *mut libc::c_void);
dehtml.strbuilder.strdup()
strdup(to_cstring(dehtml.strbuilder).as_ptr())
}
unsafe fn dehtml_text_cb(
@@ -65,11 +66,7 @@ unsafe fn dehtml_text_cb(
if dehtml.add_text == AddText::YesPreserveLineEnds
|| dehtml.add_text == AddText::YesRemoveLineEnds
{
let last_added = std::ffi::CStr::from_ptr(text)
.to_str()
.expect("invalid utf8");
// TODO: why does len does not match?
// assert_eq!(last_added.len(), len as usize);
let last_added = std::ffi::CStr::from_ptr(text).to_string_lossy();
if dehtml.add_text == AddText::YesRemoveLineEnds {
dehtml.strbuilder += LINE_RE.replace_all(last_added.as_ref(), "\r").as_ref();
@@ -89,10 +86,14 @@ unsafe fn dehtml_endtag_cb(userdata: *mut libc::c_void, tag: *const libc::c_char
dehtml.add_text = AddText::YesRemoveLineEnds;
}
"a" => {
if let Some(ref last_href) = dehtml.last_href.take() {
if !dehtml.last_href.is_null() {
dehtml.strbuilder += "](";
dehtml.strbuilder += last_href;
dehtml.strbuilder += std::ffi::CStr::from_ptr((*dehtml).last_href)
.to_string_lossy()
.as_ref();
dehtml.strbuilder += ")";
free(dehtml.last_href as *mut libc::c_void);
dehtml.last_href = 0 as *mut libc::c_char;
}
}
"b" | "strong" => {
@@ -130,13 +131,12 @@ unsafe fn dehtml_starttag_cb(
dehtml.add_text = AddText::YesPreserveLineEnds;
}
"a" => {
let text_c = std::ffi::CStr::from_ptr(dc_attr_find(
free(dehtml.last_href as *mut libc::c_void);
dehtml.last_href = dc_strdup_keep_null(dc_attr_find(
attr,
b"href\x00" as *const u8 as *const libc::c_char,
));
let text_r = text_c.to_str().expect("invalid utf8");
if !text_r.is_empty() {
dehtml.last_href = Some(text_r.to_string());
if !dehtml.last_href.is_null() {
dehtml.strbuilder += "[";
}
}

View File

@@ -1,5 +1,5 @@
use std::collections::HashSet;
use std::ffi::CStr;
use std::ffi::{CStr, CString};
use std::str::FromStr;
use mmime::clist::*;
@@ -17,8 +17,10 @@ use mmime::{mailmime_substitute, MAILIMF_NO_ERROR, MAIL_NO_ERROR};
use crate::aheader::*;
use crate::context::Context;
use crate::dc_log::*;
use crate::dc_mimeparser::*;
use crate::dc_securejoin::*;
use crate::dc_sqlite3::*;
use crate::dc_tools::*;
use crate::key::*;
use crate::keyring::*;
@@ -31,10 +33,9 @@ use crate::x::*;
// attachments of 25 mb brutto should work on the majority of providers
// (brutto examples: web.de=50, 1&1=40, t-online.de=32, gmail=25, posteo=50, yahoo=25, all-inkl=100).
// as an upper limit, we double the size; the core won't send messages larger than this
// to get the netto sizes, we subtract 1 mb header-overhead and the base64-overhead.
// to get the netto sizes, we substract 1 mb header-overhead and the base64-overhead.
// some defaults
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct dc_e2ee_helper_t {
pub encryption_successfull: libc::c_int,
pub cdata_to_free: *mut libc::c_void,
@@ -55,7 +56,6 @@ impl Default for dc_e2ee_helper_t {
}
}
#[allow(non_snake_case)]
pub unsafe fn dc_e2ee_encrypt(
context: &Context,
recipients_addr: *const clist,
@@ -66,7 +66,7 @@ pub unsafe fn dc_e2ee_encrypt(
mut in_out_message: *mut mailmime,
helper: &mut dc_e2ee_helper_t,
) {
let mut ok_to_continue = true;
let mut current_block: u64 = 0;
let mut col: libc::c_int = 0i32;
let mut do_encrypt: libc::c_int = 0i32;
/*just a pointer into mailmime structure, must not be freed*/
@@ -83,21 +83,27 @@ pub unsafe fn dc_e2ee_encrypt(
{
/* libEtPan's pgp_encrypt_mime() takes the parent as the new root. We just expect the root as being given to this function. */
let prefer_encrypt = if 0
!= context
.sql
.get_config_int(context, "e2ee_enabled")
.unwrap_or_default()
{
!= dc_sqlite3_get_config_int(
context,
&context.sql,
b"e2ee_enabled\x00" as *const u8 as *const libc::c_char,
1,
) {
EncryptPreference::Mutual
} else {
EncryptPreference::NoPreference
};
let addr = context.sql.get_config(context, "configured_addr");
let addr = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_addr\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
if let Some(addr) = addr {
if !addr.is_null() {
if let Some(public_key) =
load_or_generate_self_public_key(context, &addr, in_out_message)
load_or_generate_self_public_key(context, addr, in_out_message)
{
/*only for random-seed*/
if prefer_encrypt == EncryptPreference::Mutual || 0 != e2ee_guaranteed {
@@ -105,10 +111,15 @@ pub unsafe fn dc_e2ee_encrypt(
let mut iter1: *mut clistiter;
iter1 = (*recipients_addr).first;
while !iter1.is_null() {
let recipient_addr = to_string((*iter1).data as *const libc::c_char);
if recipient_addr != addr {
let recipient_addr: *const libc::c_char = (if !iter1.is_null() {
(*iter1).data
} else {
0 as *mut libc::c_void
})
as *const libc::c_char;
if strcasecmp(recipient_addr, addr) != 0 {
let peerstate =
Peerstate::from_addr(context, &context.sql, &recipient_addr);
Peerstate::from_addr(context, &context.sql, as_str(recipient_addr));
if peerstate.is_some()
&& (peerstate.as_ref().unwrap().prefer_encrypt
== EncryptPreference::Mutual
@@ -134,7 +145,7 @@ pub unsafe fn dc_e2ee_encrypt(
}
let sign_key = if 0 != do_encrypt {
keyring.add_ref(&public_key);
let key = Key::from_self_private(context, addr.clone(), &context.sql);
let key = Key::from_self_private(context, addr, &context.sql);
if key.is_none() {
do_encrypt = 0i32;
@@ -177,12 +188,16 @@ pub unsafe fn dc_e2ee_encrypt(
let p = peerstates[i as usize]
.render_gossip_header(min_verified as usize);
if let Some(header) = p {
if p.is_some() {
let header = to_cstring(p.unwrap());
mailimf_fields_add(
imffields_encrypted,
mailimf_field_new_custom(
"Autocrypt-Gossip".strdup(),
header.strdup(),
strdup(
b"Autocrypt-Gossip\x00" as *const u8
as *const libc::c_char,
),
strdup(header.as_ptr()),
),
);
}
@@ -283,7 +298,7 @@ pub unsafe fn dc_e2ee_encrypt(
);
mailmime_write_mem(plain, &mut col, message_to_encrypt);
if (*plain).str_0.is_null() || (*plain).len <= 0 {
ok_to_continue = false;
current_block = 14181132614457621749;
} else {
if let Some(ctext_v) = dc_pgp_pk_encrypt(
(*plain).str_0 as *const libc::c_void,
@@ -292,8 +307,10 @@ pub unsafe fn dc_e2ee_encrypt(
sign_key.as_ref(),
) {
let ctext_bytes = ctext_v.len();
let ctext = ctext_v.strdup();
helper.cdata_to_free = ctext as *mut _;
let ctext_c = CString::new(ctext_v).unwrap();
let ctext = strdup(ctext_c.as_ptr());
(*helper).cdata_to_free = ctext as *mut libc::c_void;
/* create MIME-structure that will contain the encrypted text */
let mut encrypted_part: *mut mailmime = new_data_part(
@@ -316,11 +333,11 @@ pub unsafe fn dc_e2ee_encrypt(
as *mut libc::c_char,
) as *mut libc::c_void,
);
static mut VERSION_CONTENT: [libc::c_char; 13] =
static mut version_content: [libc::c_char; 13] =
[86, 101, 114, 115, 105, 111, 110, 58, 32, 49, 13, 10, 0];
let version_mime: *mut mailmime = new_data_part(
VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void,
strlen(VERSION_CONTENT.as_mut_ptr()),
version_content.as_mut_ptr() as *mut libc::c_void,
strlen(version_content.as_mut_ptr()),
b"application/pgp-encrypted\x00" as *const u8
as *const libc::c_char
as *mut libc::c_char,
@@ -339,19 +356,28 @@ pub unsafe fn dc_e2ee_encrypt(
(*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
(*encrypted_part).mm_parent = in_out_message;
mailmime_free(message_to_encrypt);
helper.encryption_successfull = 1i32;
(*helper).encryption_successfull = 1i32;
current_block = 13824533195664196414;
}
}
} else {
current_block = 13824533195664196414;
}
if ok_to_continue {
let aheader = Aheader::new(addr, public_key, prefer_encrypt);
mailimf_fields_add(
imffields_unprotected,
mailimf_field_new_custom(
"Autocrypt".strdup(),
aheader.to_string().strdup(),
),
);
match current_block {
14181132614457621749 => {}
_ => {
let addr = CStr::from_ptr(addr).to_str().unwrap();
let aheader = Aheader::new(addr.into(), public_key, prefer_encrypt);
let rendered = CString::new(aheader.to_string()).unwrap();
mailimf_fields_add(
imffields_unprotected,
mailimf_field_new_custom(
strdup(b"Autocrypt\x00" as *const u8 as *const libc::c_char),
strdup(rendered.as_ptr()),
),
);
}
}
}
}
@@ -372,7 +398,7 @@ unsafe fn new_data_part(
default_content_type: *mut libc::c_char,
default_encoding: libc::c_int,
) -> *mut mailmime {
let mut ok_to_continue = true;
let mut current_block: u64;
//char basename_buf[PATH_MAX];
let mut encoding: *mut mailmime_mechanism;
let content: *mut mailmime_content;
@@ -392,7 +418,7 @@ unsafe fn new_data_part(
}
content = mailmime_content_new_with_str(content_type_str);
if content.is_null() {
ok_to_continue = false;
current_block = 16266721588079097885;
} else {
do_encoding = 1i32;
if (*(*content).ct_type).tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int {
@@ -420,44 +446,54 @@ unsafe fn new_data_part(
}
encoding = mailmime_mechanism_new(encoding_type, 0 as *mut libc::c_char);
if encoding.is_null() {
ok_to_continue = false;
}
}
if ok_to_continue {
mime_fields = mailmime_fields_new_with_data(
encoding,
0 as *mut libc::c_char,
0 as *mut libc::c_char,
0 as *mut mailmime_disposition,
0 as *mut mailmime_language,
);
if mime_fields.is_null() {
ok_to_continue = false;
current_block = 16266721588079097885;
} else {
mime = mailmime_new_empty(content, mime_fields);
if mime.is_null() {
mailmime_fields_free(mime_fields);
mailmime_content_free(content);
current_block = 11057878835866523405;
}
} else {
current_block = 11057878835866523405;
}
match current_block {
16266721588079097885 => {}
_ => {
mime_fields = mailmime_fields_new_with_data(
encoding,
0 as *mut libc::c_char,
0 as *mut libc::c_char,
0 as *mut mailmime_disposition,
0 as *mut mailmime_language,
);
if mime_fields.is_null() {
current_block = 16266721588079097885;
} else {
if !data.is_null()
&& data_bytes > 0
&& (*mime).mm_type == MAILMIME_SINGLE as libc::c_int
{
mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes);
mime = mailmime_new_empty(content, mime_fields);
if mime.is_null() {
mailmime_fields_free(mime_fields);
mailmime_content_free(content);
} else {
if !data.is_null()
&& data_bytes > 0
&& (*mime).mm_type == MAILMIME_SINGLE as libc::c_int
{
mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes);
}
return mime;
}
return mime;
current_block = 13668317689588454213;
}
}
}
}
if ok_to_continue == false {
if !encoding.is_null() {
mailmime_mechanism_free(encoding);
}
if !content.is_null() {
mailmime_content_free(content);
match current_block {
16266721588079097885 => {
if !encoding.is_null() {
mailmime_mechanism_free(encoding);
}
if !content.is_null() {
mailmime_content_free(content);
}
}
_ => {}
}
return 0 as *mut mailmime;
}
@@ -467,57 +503,68 @@ unsafe fn new_data_part(
******************************************************************************/
unsafe fn load_or_generate_self_public_key(
context: &Context,
self_addr: impl AsRef<str>,
self_addr: *const libc::c_char,
_random_data_mime: *mut mailmime,
) -> Option<Key> {
/* avoid double creation (we unlock the database during creation) */
static mut S_IN_KEY_CREATION: libc::c_int = 0;
static mut s_in_key_creation: libc::c_int = 0i32;
let mut key = Key::from_self_public(context, &self_addr, &context.sql);
let mut key = Key::from_self_public(context, self_addr, &context.sql);
if key.is_some() {
return key;
}
/* create the keypair - this may take a moment, however, as this is in a thread, this is no big deal */
if 0 != S_IN_KEY_CREATION {
if 0 != s_in_key_creation {
return None;
}
let key_creation_here = 1;
S_IN_KEY_CREATION = 1;
s_in_key_creation = 1;
let start = clock();
info!(
let start: libc::clock_t = clock();
dc_log_info(
context,
0, "Generating keypair with {} bits, e={} ...", 2048, 65537,
0i32,
b"Generating keypair with %i bits, e=%i ...\x00" as *const u8 as *const libc::c_char,
2048i32,
65537i32,
);
if let Some((public_key, private_key)) = dc_pgp_create_keypair(&self_addr) {
if let Some((public_key, private_key)) = dc_pgp_create_keypair(self_addr) {
if !dc_key_save_self_keypair(
context,
&public_key,
&private_key,
&self_addr,
self_addr,
1i32,
&context.sql,
) {
/*set default*/
warn!(context, 0, "Cannot save keypair.",);
} else {
info!(
dc_log_warning(
context,
0,
"Keypair generated in {:.3}s.",
clock().wrapping_sub(start) as libc::c_double / 1000000 as libc::c_double,
0i32,
b"Cannot save keypair.\x00" as *const u8 as *const libc::c_char,
);
} else {
dc_log_info(
context,
0i32,
b"Keypair generated in %.3f s.\x00" as *const u8 as *const libc::c_char,
clock().wrapping_sub(start) as libc::c_double / 1000000i32 as libc::c_double,
);
}
key = Some(public_key);
} else {
warn!(context, 0, "Cannot create keypair.");
dc_log_warning(
context,
0i32,
b"Cannot create keypair.\x00" as *const u8 as *const libc::c_char,
);
}
if 0 != key_creation_here {
S_IN_KEY_CREATION = 0;
s_in_key_creation = 0;
}
key
@@ -536,6 +583,7 @@ pub unsafe fn dc_e2ee_decrypt(
let imffields: *mut mailimf_fields = mailmime_find_mailimf_fields(in_out_message);
let mut message_time = 0;
let mut from: *mut libc::c_char = 0 as *mut libc::c_char;
let mut self_addr: *mut libc::c_char = 0 as *mut libc::c_char;
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut gossip_headers: *mut mailimf_fields = 0 as *mut mailimf_fields;
@@ -564,23 +612,28 @@ pub unsafe fn dc_e2ee_decrypt(
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.apply_header(&header, message_time as u64);
peerstate.save_to_db(&context.sql, false);
} else if message_time > peerstate.last_seen_autocrypt
} else if message_time as u64 > peerstate.last_seen_autocrypt
&& 0 == contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.degrade_encryption(message_time as u64);
peerstate.save_to_db(&context.sql, false);
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
let p = Peerstate::from_header(context, header, message_time as u64);
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");
if let Some(self_addr) = self_addr {
self_addr = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_addr\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
if !self_addr.is_null() {
if private_keyring.load_self_private_for_decrypting(context, self_addr, &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));
@@ -628,6 +681,7 @@ pub unsafe fn dc_e2ee_decrypt(
}
free(from as *mut libc::c_void);
free(self_addr as *mut libc::c_void);
}
unsafe fn update_gossip_peerstates(
@@ -668,10 +722,10 @@ unsafe fn update_gossip_peerstates(
let mut peerstate =
Peerstate::from_addr(context, &context.sql, &header.addr);
if let Some(ref mut peerstate) = peerstate {
peerstate.apply_gossip(header, message_time);
peerstate.apply_gossip(header, message_time as u64);
peerstate.save_to_db(&context.sql, false);
} else {
let p = Peerstate::from_gossip(context, header, message_time);
let p = Peerstate::from_gossip(context, header, message_time as u64);
p.save_to_db(&context.sql, true);
peerstate = Some(p);
}
@@ -683,11 +737,12 @@ unsafe fn update_gossip_peerstates(
gossipped_addr.insert(header.addr.clone());
} else {
info!(
dc_log_info(
context,
0,
"Ignoring gossipped \"{}\" as the address is not in To/Cc list.",
&header.addr,
0i32,
b"Ignoring gossipped \"%s\" as the address is not in To/Cc list.\x00"
as *const u8 as *const libc::c_char,
CString::new(header.addr.clone()).unwrap().as_ptr(),
);
}
}
@@ -812,14 +867,14 @@ unsafe fn decrypt_recursive(
}
unsafe fn decrypt_part(
context: &Context,
_context: &Context,
mime: *mut mailmime,
private_keyring: &Keyring,
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
ret_decrypted_mime: *mut *mut mailmime,
) -> libc::c_int {
let mut ok_to_continue = true;
let current_block: u64;
let mime_data: *mut mailmime_data;
let mut mime_transfer_encoding: libc::c_int = MAILMIME_MECHANISM_BINARY as libc::c_int;
/* mmap_string_unref()'d if set */
@@ -867,7 +922,9 @@ unsafe fn decrypt_part(
decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
if decoded_data.is_null() || decoded_data_bytes <= 0 {
/* no error - but no data */
ok_to_continue = false;
current_block = 2554982661806928548;
} else {
current_block = 4488286894823169796;
}
} else {
let r: libc::c_int;
@@ -884,49 +941,54 @@ unsafe fn decrypt_part(
|| transfer_decoding_buffer.is_null()
|| decoded_data_bytes <= 0
{
ok_to_continue = false;
current_block = 2554982661806928548;
} else {
decoded_data = transfer_decoding_buffer;
current_block = 4488286894823169796;
}
}
if ok_to_continue {
/* encrypted, decoded data in decoded_data now ... */
if !(0 == has_decrypted_pgp_armor(decoded_data, decoded_data_bytes as libc::c_int)) {
let add_signatures = if ret_valid_signatures.is_empty() {
Some(ret_valid_signatures)
} else {
None
};
/*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
if let Some(plain) = dc_pgp_pk_decrypt(
decoded_data as *const libc::c_void,
decoded_data_bytes,
&private_keyring,
&public_keyring_for_validate,
add_signatures,
) {
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 decrypted_mime: *mut mailmime = 0 as *mut mailmime;
if mailmime_parse(
plain_buf as *const _,
plain_bytes,
&mut index,
&mut decrypted_mime,
) != MAIL_NO_ERROR as libc::c_int
|| decrypted_mime.is_null()
{
if !decrypted_mime.is_null() {
mailmime_free(decrypted_mime);
}
match current_block {
2554982661806928548 => {}
_ => {
/* encrypted, decoded data in decoded_data now ... */
if !(0 == has_decrypted_pgp_armor(decoded_data, decoded_data_bytes as libc::c_int))
{
let add_signatures = if ret_valid_signatures.is_empty() {
Some(ret_valid_signatures)
} else {
*ret_decrypted_mime = decrypted_mime;
sth_decrypted = 1i32
None
};
/*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
if let Some(plain) = dc_pgp_pk_decrypt(
decoded_data as *const libc::c_void,
decoded_data_bytes,
&private_keyring,
&public_keyring_for_validate,
add_signatures,
) {
let plain_bytes = plain.len();
let plain_c = CString::new(plain).unwrap();
let plain_buf = strdup(plain_c.as_ptr());
let mut index: size_t = 0i32 as size_t;
let mut decrypted_mime: *mut mailmime = 0 as *mut mailmime;
if mailmime_parse(
plain_buf as *const libc::c_char,
plain_bytes,
&mut index,
&mut decrypted_mime,
) != MAIL_NO_ERROR as libc::c_int
|| decrypted_mime.is_null()
{
if !decrypted_mime.is_null() {
mailmime_free(decrypted_mime);
}
} else {
*ret_decrypted_mime = decrypted_mime;
sth_decrypted = 1i32
}
}
std::mem::forget(plain);
}
}
}
@@ -981,7 +1043,7 @@ unsafe fn has_decrypted_pgp_armor(
* that we could use the normal Autocrypt processing.
*
* @private
* @param mime The mime structure to check
* @param mime The mime struture to check
* @return 1=multipart/report found in MIME, 0=no multipart/report found
*/
// TODO should return bool /rtn
@@ -1041,84 +1103,25 @@ pub unsafe fn dc_ensure_secret_key_exists(context: &Context) -> libc::c_int {
(this is to gain some extra-random-seed by the message content and the timespan between program start and message sending) */
let mut success: libc::c_int = 0i32;
let self_addr = context.sql.get_config(context, "configured_addr");
if self_addr.is_none() {
warn!(
let self_addr = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_addr\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
if self_addr.is_null() {
dc_log_warning(
context,
0, "Cannot ensure secret key if context is not configured.",
0i32,
b"Cannot ensure secret key if context is not configured.\x00" as *const u8
as *const libc::c_char,
);
} else if load_or_generate_self_public_key(context, self_addr.unwrap(), 0 as *mut mailmime)
.is_some()
{
} else if load_or_generate_self_public_key(context, self_addr, 0 as *mut mailmime).is_some() {
/*no random text data for seeding available*/
success = 1;
success = 1i32
}
free(self_addr as *mut libc::c_void);
success
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mailmime_parse() {
let plain = b"Chat-Disposition-Notification-To: holger@deltachat.de
Chat-Group-ID: CovhGgau8M-
Chat-Group-Name: Delta Chat Dev
Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
=?utf-8?Q?all=3A?= rust core master ...
Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
Content-Transfer-Encoding: quoted-printable
sidenote for all: rust core master is broken currently ... so dont recomm=
end to try to run with desktop or ios unless you are ready to hunt bugs
-- =20
Sent with my Delta Chat Messenger: https://delta.chat";
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
let mut index = 0;
let mut decrypted_mime = std::ptr::null_mut();
let res = unsafe {
mailmime_parse(
plain_buf as *const _,
plain_bytes,
&mut index,
&mut decrypted_mime,
)
};
unsafe {
let msg1 = (*decrypted_mime).mm_data.mm_message.mm_msg_mime;
let mut decoded_data = 0 as *const libc::c_char;
let mut decoded_data_bytes = 0;
let mut transfer_decoding_buffer: *mut libc::c_char = 0 as *mut libc::c_char;
assert_eq!(
mailmime_transfer_decode(
msg1,
&mut decoded_data,
&mut decoded_data_bytes,
&mut transfer_decoding_buffer,
),
1
);
println!(
"{:?}",
String::from_utf8_lossy(std::slice::from_raw_parts(
decoded_data as *const u8,
decoded_data_bytes as usize,
))
);
free(decoded_data as *mut _);
}
assert_eq!(res, 0);
assert!(!decrypted_mime.is_null());
unsafe { free(decrypted_mime as *mut _) };
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,11 @@ use std::sync::{Arc, Condvar, Mutex};
use crate::context::Context;
use crate::dc_configure::*;
use crate::dc_sqlite3::*;
use crate::imap::Imap;
use crate::types::*;
use crate::x::*;
use std::ffi::CString;
#[repr(C)]
pub struct dc_jobthread_t {
@@ -129,32 +132,40 @@ pub unsafe fn dc_jobthread_fetch(
******************************************************************************/
unsafe fn connect_to_imap(context: &Context, jobthread: &dc_jobthread_t) -> libc::c_int {
let mut ret_connected: libc::c_int;
let mut mvbox_name: *mut libc::c_char = 0 as *mut libc::c_char;
if jobthread.imap.is_connected() {
return 1;
}
let mut ret_connected = dc_connect_to_configured_imap(context, &jobthread.imap);
if !(0 == ret_connected) {
if context
.sql
.get_config_int(context, "folders_configured")
.unwrap_or_default()
< 3
{
jobthread.imap.configure_folders(context, 0x1);
}
if let Some(mvbox_name) = context
.sql
.get_config(context, jobthread.folder_config_name)
{
jobthread.imap.set_watch_folder(mvbox_name);
} else {
jobthread.imap.disconnect(context);
ret_connected = 0;
ret_connected = 1;
} else {
ret_connected = dc_connect_to_configured_imap(context, &jobthread.imap);
if !(0 == ret_connected) {
if dc_sqlite3_get_config_int(
context,
&context.sql,
b"folders_configured\x00" as *const u8 as *const libc::c_char,
0,
) < 3
{
jobthread.imap.configure_folders(context, 0x1);
}
mvbox_name = dc_sqlite3_get_config(
context,
&context.sql,
CString::new(&jobthread.folder_config_name[..])
.unwrap()
.as_ptr(),
0 as *const libc::c_char,
);
if mvbox_name.is_null() {
jobthread.imap.disconnect(context);
ret_connected = 0;
} else {
jobthread.imap.set_watch_folder(mvbox_name);
}
}
}
free(mvbox_name as *mut libc::c_void);
ret_connected
}

File diff suppressed because it is too large Load Diff

142
src/dc_log.rs Normal file
View File

@@ -0,0 +1,142 @@
use crate::constants::Event;
use crate::context::Context;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
pub unsafe extern "C" fn dc_log_event(
context: &Context,
event_code: Event,
data1: libc::c_int,
msg: *const libc::c_char,
va: ...
) {
log_vprintf(context, event_code, data1, msg, va);
}
/* Asynchronous "Thread-errors" are reported by the dc_log_error()
function. These errors must be shown to the user by a bubble or so.
"Normal" errors are usually returned by a special value (null or so) and are
usually not reported using dc_log_error() - its up to the caller to
decide, what should be reported or done. However, these "Normal" errors
are usually logged by dc_log_warning(). */
unsafe fn log_vprintf(
context: &Context,
event: Event,
data1: libc::c_int,
msg_format: *const libc::c_char,
va_0: ::std::ffi::VaList,
) {
let msg: *mut libc::c_char;
if !msg_format.is_null() {
let mut tempbuf: [libc::c_char; 1025] = [0; 1025];
vsnprintf(
tempbuf.as_mut_ptr(),
1024i32 as libc::c_ulong,
msg_format,
va_0,
);
msg = dc_strdup(tempbuf.as_mut_ptr())
} else {
msg = dc_mprintf(
b"event #%i\x00" as *const u8 as *const libc::c_char,
event as libc::c_int,
)
}
context.call_cb(event, data1 as uintptr_t, msg as uintptr_t);
free(msg as *mut libc::c_void);
}
pub unsafe extern "C" fn dc_log_event_seq(
context: &Context,
event_code: Event,
sequence_start: *mut libc::c_int,
msg: *const libc::c_char,
va_0: ...
) {
if sequence_start.is_null() {
return;
}
log_vprintf(context, event_code, *sequence_start, msg, va_0);
*sequence_start = 0i32;
}
pub unsafe extern "C" fn dc_log_error(
context: &Context,
data1: libc::c_int,
msg: *const libc::c_char,
va_1: ...
) {
log_vprintf(context, Event::ERROR, data1, msg, va_1);
}
pub unsafe extern "C" fn dc_log_warning(
context: &Context,
data1: libc::c_int,
msg: *const libc::c_char,
va_2: ...
) {
log_vprintf(context, Event::WARNING, data1, msg, va_2);
}
pub unsafe extern "C" fn dc_log_info(
context: &Context,
data1: libc::c_int,
msg: *const libc::c_char,
va_3: ...
) {
log_vprintf(context, Event::INFO, data1, msg, va_3);
}
#[macro_export]
macro_rules! info {
($ctx:expr, $data1:expr, $msg:expr) => {
info!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {{
let formatted = format!($msg, $($args),*);
let formatted_c = $crate::dc_tools::to_cstring(formatted);
$ctx.call_cb($crate::constants::Event::INFO, $data1 as uintptr_t,
formatted_c.as_ptr() as uintptr_t)
}};
}
#[macro_export]
macro_rules! warn {
($ctx:expr, $data1:expr, $msg:expr) => {
warn!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
let formatted = format!($msg, $($args),*);
let formatted_c = $crate::dc_tools::to_cstring(formatted);
$ctx.call_cb($crate::constants::Event::WARNING, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t)
};
}
#[macro_export]
macro_rules! error {
($ctx:expr, $data1:expr, $msg:expr) => {
error!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
let formatted = format!($msg, $($args),*);
let formatted_c = $crate::dc_tools::to_cstring(formatted);
$ctx.call_cb($crate::constants::Event::ERROR, $data1 as uintptr_t,
formatted_c.as_ptr() as uintptr_t)
};
}
#[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 = $crate::dc_tools::to_cstring(formatted);
$ctx.call_cb($event, $data1 as uintptr_t,
formatted_c.as_ptr() as uintptr_t)
};
}

View File

@@ -1,174 +1,283 @@
use std::borrow::Cow;
use crate::context::Context;
use crate::sql::Sql;
use crate::dc_sqlite3::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
#[derive(Default, Debug)]
#[allow(non_camel_case_types)]
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_loginparam_t {
pub addr: String,
pub mail_server: String,
pub mail_user: String,
pub mail_pw: String,
pub addr: *mut libc::c_char,
pub mail_server: *mut libc::c_char,
pub mail_user: *mut libc::c_char,
pub mail_pw: *mut libc::c_char,
pub mail_port: i32,
pub send_server: String,
pub send_user: String,
pub send_pw: String,
pub send_server: *mut libc::c_char,
pub send_user: *mut libc::c_char,
pub send_pw: *mut libc::c_char,
pub send_port: i32,
pub server_flags: i32,
}
impl dc_loginparam_t {
pub fn addr_str(&self) -> &str {
self.addr.as_str()
pub unsafe fn dc_loginparam_new() -> *mut dc_loginparam_t {
let loginparam: *mut dc_loginparam_t;
loginparam = calloc(1, ::std::mem::size_of::<dc_loginparam_t>()) as *mut dc_loginparam_t;
assert!(!loginparam.is_null());
loginparam
}
pub unsafe fn dc_loginparam_unref(loginparam: *mut dc_loginparam_t) {
if loginparam.is_null() {
return;
}
dc_loginparam_empty(loginparam);
free(loginparam as *mut libc::c_void);
}
pub fn dc_loginparam_new() -> dc_loginparam_t {
Default::default()
}
pub fn dc_loginparam_read(
context: &Context,
sql: &Sql,
prefix: impl AsRef<str>,
) -> dc_loginparam_t {
let prefix = prefix.as_ref();
let key = format!("{}addr", prefix);
let addr = sql
.get_config(context, key)
.unwrap_or_default()
.trim()
.to_string();
let key = format!("{}mail_server", prefix);
let mail_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_port", prefix);
let mail_port = sql.get_config_int(context, key).unwrap_or_default();
let key = format!("{}mail_user", prefix);
let mail_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_server", prefix);
let send_server = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_port", prefix);
let send_port = sql.get_config_int(context, key).unwrap_or_default();
let key = format!("{}send_user", prefix);
let send_user = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}send_pw", prefix);
let send_pw = sql.get_config(context, key).unwrap_or_default();
let key = format!("{}server_flags", prefix);
let server_flags = sql.get_config_int(context, key).unwrap_or_default();
dc_loginparam_t {
addr: addr.to_string(),
mail_server,
mail_user,
mail_pw,
mail_port,
send_server,
send_user,
send_pw,
send_port,
server_flags,
/* clears all data and frees its memory. All pointers are NULL after this function is called. */
pub unsafe fn dc_loginparam_empty(mut loginparam: *mut dc_loginparam_t) {
if loginparam.is_null() {
return;
}
free((*loginparam).addr as *mut libc::c_void);
(*loginparam).addr = 0 as *mut libc::c_char;
free((*loginparam).mail_server as *mut libc::c_void);
(*loginparam).mail_server = 0 as *mut libc::c_char;
(*loginparam).mail_port = 0i32;
free((*loginparam).mail_user as *mut libc::c_void);
(*loginparam).mail_user = 0 as *mut libc::c_char;
free((*loginparam).mail_pw as *mut libc::c_void);
(*loginparam).mail_pw = 0 as *mut libc::c_char;
free((*loginparam).send_server as *mut libc::c_void);
(*loginparam).send_server = 0 as *mut libc::c_char;
(*loginparam).send_port = 0i32;
free((*loginparam).send_user as *mut libc::c_void);
(*loginparam).send_user = 0 as *mut libc::c_char;
free((*loginparam).send_pw as *mut libc::c_void);
(*loginparam).send_pw = 0 as *mut libc::c_char;
(*loginparam).server_flags = 0i32;
}
pub fn dc_loginparam_write(
pub unsafe fn dc_loginparam_read(
context: &Context,
loginparam: &dc_loginparam_t,
sql: &Sql,
prefix: impl AsRef<str>,
loginparam: *mut dc_loginparam_t,
sql: &SQLite,
prefix: *const libc::c_char,
) {
let prefix = prefix.as_ref();
let key = format!("{}addr", prefix);
sql.set_config(context, key, Some(&loginparam.addr)).ok();
let key = format!("{}mail_server", prefix);
sql.set_config(context, key, Some(&loginparam.mail_server))
.ok();
let key = format!("{}mail_port", prefix);
sql.set_config_int(context, key, loginparam.mail_port).ok();
let key = format!("{}mail_user", prefix);
sql.set_config(context, key, Some(&loginparam.mail_user))
.ok();
let key = format!("{}mail_pw", prefix);
sql.set_config(context, key, Some(&loginparam.mail_pw)).ok();
let key = format!("{}send_server", prefix);
sql.set_config(context, key, Some(&loginparam.send_server))
.ok();
let key = format!("{}send_port", prefix);
sql.set_config_int(context, key, loginparam.send_port).ok();
let key = format!("{}send_user", prefix);
sql.set_config(context, key, Some(&loginparam.send_user))
.ok();
let key = format!("{}send_pw", prefix);
sql.set_config(context, key, Some(&loginparam.send_pw)).ok();
let key = format!("{}server_flags", prefix);
sql.set_config_int(context, key, loginparam.server_flags)
.ok();
let mut key: *mut libc::c_char = 0 as *mut libc::c_char;
dc_loginparam_empty(loginparam);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"addr\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).addr = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"mail_server\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).mail_server = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"mail_port\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).mail_port = dc_sqlite3_get_config_int(context, sql, key, 0i32);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"mail_user\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).mail_user = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"mail_pw\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).mail_pw = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"send_server\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).send_server = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"send_port\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).send_port = dc_sqlite3_get_config_int(context, sql, key, 0i32);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"send_user\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).send_user = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"send_pw\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).send_pw = dc_sqlite3_get_config(context, sql, key, 0 as *const libc::c_char);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"server_flags\x00" as *const u8 as *const libc::c_char,
);
(*loginparam).server_flags = dc_sqlite3_get_config_int(context, sql, key, 0i32);
sqlite3_free(key as *mut libc::c_void);
}
fn unset_empty(s: &String) -> Cow<String> {
if s.is_empty() {
Cow::Owned("unset".to_string())
} else {
Cow::Borrowed(s)
pub unsafe fn dc_loginparam_write(
context: &Context,
loginparam: *const dc_loginparam_t,
sql: &SQLite,
prefix: *const libc::c_char,
) {
let mut key: *mut libc::c_char = 0 as *mut libc::c_char;
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"addr\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config(context, sql, key, (*loginparam).addr);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"mail_server\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config(context, sql, key, (*loginparam).mail_server);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"mail_port\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config_int(context, sql, key, (*loginparam).mail_port);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"mail_user\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config(context, sql, key, (*loginparam).mail_user);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"mail_pw\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config(context, sql, key, (*loginparam).mail_pw);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"send_server\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config(context, sql, key, (*loginparam).send_server);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"send_port\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config_int(context, sql, key, (*loginparam).send_port);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"send_user\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config(context, sql, key, (*loginparam).send_user);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"send_pw\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config(context, sql, key, (*loginparam).send_pw);
sqlite3_free(key as *mut libc::c_void);
key = sqlite3_mprintf(
b"%s%s\x00" as *const u8 as *const libc::c_char,
prefix,
b"server_flags\x00" as *const u8 as *const libc::c_char,
);
dc_sqlite3_set_config_int(context, sql, key, (*loginparam).server_flags);
sqlite3_free(key as *mut libc::c_void);
}
pub unsafe fn dc_loginparam_get_readable(loginparam: *const dc_loginparam_t) -> *mut libc::c_char {
let unset: *const libc::c_char = b"0\x00" as *const u8 as *const libc::c_char;
let pw: *const libc::c_char = b"***\x00" as *const u8 as *const libc::c_char;
if loginparam.is_null() {
return dc_strdup(0 as *const libc::c_char);
}
}
pub fn dc_loginparam_get_readable(loginparam: &dc_loginparam_t) -> String {
let unset = "0";
let pw = "***";
let flags_readable = get_readable_flags(loginparam.server_flags);
format!(
"{} {}:{}:{}:{} {}:{}:{}:{} {}",
unset_empty(&loginparam.addr),
unset_empty(&loginparam.mail_user),
if !loginparam.mail_pw.is_empty() {
let flags_readable: *mut libc::c_char = get_readable_flags((*loginparam).server_flags);
let ret: *mut libc::c_char = dc_mprintf(
b"%s %s:%s:%s:%i %s:%s:%s:%i %s\x00" as *const u8 as *const libc::c_char,
if !(*loginparam).addr.is_null() {
(*loginparam).addr
} else {
unset
},
if !(*loginparam).mail_user.is_null() {
(*loginparam).mail_user
} else {
unset
},
if !(*loginparam).mail_pw.is_null() {
pw
} else {
unset
},
unset_empty(&loginparam.mail_server),
loginparam.mail_port,
unset_empty(&loginparam.send_user),
if !loginparam.send_pw.is_empty() {
if !(*loginparam).mail_server.is_null() {
(*loginparam).mail_server
} else {
unset
},
(*loginparam).mail_port,
if !(*loginparam).send_user.is_null() {
(*loginparam).send_user
} else {
unset
},
if !(*loginparam).send_pw.is_null() {
pw
} else {
unset
},
unset_empty(&loginparam.send_server),
loginparam.send_port,
if !(*loginparam).send_server.is_null() {
(*loginparam).send_server
} else {
unset
},
(*loginparam).send_port,
flags_readable,
)
);
free(flags_readable as *mut libc::c_void);
ret
}
fn get_readable_flags(flags: i32) -> String {
fn get_readable_flags(flags: libc::c_int) -> *mut libc::c_char {
let mut res = String::new();
for bit in 0..31 {
if 0 != flags & 1 << bit {
let mut flag_added = 0;
let mut flag_added: libc::c_int = 0;
if 1 << bit == 0x2 {
res += "OAUTH2 ";
flag_added = 1;
@@ -210,5 +319,5 @@ fn get_readable_flags(flags: i32) -> String {
res += "0";
}
res
unsafe { strdup(to_cstring(res).as_ptr()) }
}

View File

@@ -1,13 +1,11 @@
use crate::contact::*;
use crate::context::Context;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_msg::*;
use crate::dc_stock::*;
use crate::dc_tools::*;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
use std::ffi::CString;
use std::ptr;
/* * Structure behind dc_lot_t */
#[derive(Copy, Clone)]
@@ -31,7 +29,7 @@ pub struct dc_lot_t {
* An object containing a set of values.
* The meaning of the values is defined by the function returning the object.
* Lot objects are created
* eg. by chatlist.get_summary() or dc_msg_get_summary().
* eg. by dc_chatlist_get_summary() or dc_msg_get_summary().
*
* NB: _Lot_ is used in the meaning _heap_ here.
*/
@@ -127,59 +125,43 @@ pub unsafe fn dc_lot_get_timestamp(lot: *const dc_lot_t) -> i64 {
/* in practice, the user additionally cuts the string himself pixel-accurate */
pub unsafe fn dc_lot_fill(
mut lot: *mut dc_lot_t,
msg: *mut dc_msg_t,
msg: *const dc_msg_t,
chat: *const Chat,
contact: Option<&Contact>,
contact: *const dc_contact_t,
context: &Context,
) {
if lot.is_null() || (*lot).magic != 0x107107i32 as libc::c_uint || msg.is_null() {
return;
}
if (*msg).state == 19i32 {
(*lot).text1 = context.stock_str(StockMessage::Draft).strdup();
(*lot).text1 = dc_stock_str(context, 3i32);
(*lot).text1_meaning = 1i32
} else if (*msg).from_id == 1i32 as libc::c_uint {
if 0 != dc_msg_is_info(msg) || 0 != dc_chat_is_self_talk(chat) {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else {
(*lot).text1 = context.stock_str(StockMessage::SelfMsg).strdup();
(*lot).text1 = dc_stock_str(context, 2i32);
(*lot).text1_meaning = 3i32
}
} else if chat.is_null() {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else if (*chat).type_0 == 120i32 || (*chat).type_0 == 130i32 {
if 0 != dc_msg_is_info(msg) || contact.is_none() {
if 0 != dc_msg_is_info(msg) || contact.is_null() {
(*lot).text1 = 0 as *mut libc::c_char;
(*lot).text1_meaning = 0i32
} else {
if !chat.is_null() && (*chat).id == 1i32 as libc::c_uint {
if let Some(contact) = contact {
(*lot).text1 = contact.get_display_name().strdup();
} else {
(*lot).text1 = std::ptr::null_mut();
}
(*lot).text1 = dc_contact_get_display_name(contact)
} else {
if let Some(contact) = contact {
(*lot).text1 = contact.get_first_name().strdup();
} else {
(*lot).text1 = std::ptr::null_mut();
}
(*lot).text1 = dc_contact_get_first_name(contact)
}
(*lot).text1_meaning = 2i32;
(*lot).text1_meaning = 2i32
}
}
let msgtext_c = (*msg)
.text
.as_ref()
.map(|s| CString::yolo(String::as_str(s)));
let msgtext_ptr = msgtext_c.map_or(ptr::null(), |s| s.as_ptr());
(*lot).text2 =
dc_msg_get_summarytext_by_raw((*msg).type_0, msgtext_ptr, &mut (*msg).param, 160, context);
dc_msg_get_summarytext_by_raw((*msg).type_0, (*msg).text, (*msg).param, 160i32, context);
(*lot).timestamp = dc_msg_get_timestamp(msg);
(*lot).state = (*msg).state;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,39 +2,44 @@ use crate::constants::*;
use crate::context::*;
use crate::dc_job::*;
use crate::dc_msg::*;
use crate::param::Params;
use crate::dc_sqlite3::*;
use crate::types::*;
pub unsafe fn dc_do_heuristics_moves(context: &Context, folder: &str, msg_id: u32) {
if context
.sql
.get_config_int(context, "mvbox_move")
.unwrap_or_else(|| 1)
== 0
pub unsafe fn dc_do_heuristics_moves(
context: &Context,
folder: *const libc::c_char,
msg_id: uint32_t,
) {
// for already seen messages, folder may be different from msg->folder
let mut msg: *mut dc_msg_t = 0 as *mut dc_msg_t;
let stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
if !(dc_sqlite3_get_config_int(
context,
&context.sql,
b"mvbox_move\x00" as *const u8 as *const libc::c_char,
1i32,
) == 0i32)
{
return;
if !(0 == dc_is_inbox(context, folder) && 0 == dc_is_sentbox(context, folder)) {
msg = dc_msg_new_load(context, msg_id);
if !(0 != dc_msg_is_setupmessage(msg)) {
// do not move setup messages;
// there may be a non-delta device that wants to handle it
if 0 != dc_is_mvbox(context, folder) {
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_STAY);
} else if 0 != (*msg).is_dc_message {
dc_job_add(
context,
200i32,
(*msg).id as libc::c_int,
0 as *const libc::c_char,
0i32,
);
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_MOVING);
}
}
}
}
if !dc_is_inbox(context, folder) && !dc_is_sentbox(context, folder) {
return;
}
let msg = dc_msg_new_load(context, msg_id);
if dc_msg_is_setupmessage(msg) {
// do not move setup messages;
// there may be a non-delta device that wants to handle it
dc_msg_unref(msg);
return;
}
if dc_is_mvbox(context, folder) {
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_STAY);
}
// 1 = dc message, 2 = reply to dc message
if 0 != (*msg).is_dc_message {
dc_job_add(context, 200, (*msg).id as libc::c_int, Params::new(), 0);
dc_update_msg_move_state(context, (*msg).rfc724_mid, DC_MOVE_STATE_MOVING);
}
sqlite3_finalize(stmt);
dc_msg_unref(msg);
}

File diff suppressed because it is too large Load Diff

437
src/dc_param.rs Normal file
View File

@@ -0,0 +1,437 @@
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/// for msgs and jobs
pub const DC_PARAM_FILE: char = 'f';
/// for msgs
pub const DC_PARAM_WIDTH: char = 'w';
/// for msgs
pub const DC_PARAM_HEIGHT: char = 'h';
/// for msgs
pub const DC_PARAM_DURATION: char = 'd';
/// for msgs
pub const DC_PARAM_MIMETYPE: char = 'm';
/// for msgs: incoming: message is encryoted, outgoing: guarantee E2EE or the message is not send
pub const DC_PARAM_GUARANTEE_E2EE: char = 'c';
/// for msgs: decrypted with validation errors or without mutual set, if neither 'c' nor 'e' are preset, the messages is only transport encrypted
pub const DC_PARAM_ERRONEOUS_E2EE: char = 'e';
/// for msgs: force unencrypted message, either DC_FP_ADD_AUTOCRYPT_HEADER (1), DC_FP_NO_AUTOCRYPT_HEADER (2) or 0
pub const DC_PARAM_FORCE_PLAINTEXT: char = 'u';
/// for msgs: an incoming message which requestes a MDN (aka read receipt)
pub const DC_PARAM_WANTS_MDN: char = 'r';
/// for msgs
pub const DC_PARAM_FORWARDED: char = 'a';
/// for msgs
pub const DC_PARAM_CMD: char = 'S';
/// for msgs
pub const DC_PARAM_CMD_ARG: char = 'E';
/// for msgs
pub const DC_PARAM_CMD_ARG2: char = 'F';
/// for msgs
pub const DC_PARAM_CMD_ARG3: char = 'G';
/// for msgs
pub const DC_PARAM_CMD_ARG4: char = 'H';
/// for msgs
pub const DC_PARAM_ERROR: char = 'L';
/// for msgs in PREPARING: space-separated list of message IDs of forwarded copies
pub const DC_PARAM_PREP_FORWARDS: char = 'P';
/// for msgs
pub const DC_PARAM_SET_LATITUDE: char = 'l';
/// for msgs
pub const DC_PARAM_SET_LONGITUDE: char = 'n';
/// for jobs
pub const DC_PARAM_SERVER_FOLDER: char = 'Z';
/// for jobs
pub const DC_PARAM_SERVER_UID: char = 'z';
/// for jobs
pub const DC_PARAM_ALSO_MOVE: char = 'M';
/// for jobs: space-separated list of message recipients
pub const DC_PARAM_RECIPIENTS: char = 'R';
/// for groups
pub const DC_PARAM_UNPROMOTED: char = 'U';
/// for groups and contacts
pub const DC_PARAM_PROFILE_IMAGE: char = 'i';
/// for chats
pub const DC_PARAM_SELFTALK: char = 'K';
// values for DC_PARAM_FORCE_PLAINTEXT
pub const DC_FP_ADD_AUTOCRYPT_HEADER: u8 = 1;
pub const DC_FP_NO_AUTOCRYPT_HEADER: u8 = 2;
/// An object for handling key=value parameter lists; for the key, curently only
/// a single character is allowed.
///
/// The object is used eg. by Chat or dc_msg_t, for readable paramter names,
/// these classes define some DC_PARAM_* constantats.
///
/// Only for library-internal use.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct dc_param_t {
pub packed: *mut libc::c_char,
}
// values for DC_PARAM_FORCE_PLAINTEXT
/* user functions */
pub unsafe fn dc_param_exists(param: *mut dc_param_t, key: libc::c_int) -> libc::c_int {
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
if param.is_null() || key == 0i32 {
return 0i32;
}
return if !find_param((*param).packed, key, &mut p2).is_null() {
1i32
} else {
0i32
};
}
unsafe extern "C" fn find_param(
haystack: *mut libc::c_char,
key: libc::c_int,
ret_p2: *mut *mut libc::c_char,
) -> *mut libc::c_char {
let mut p1: *mut libc::c_char;
let mut p2: *mut libc::c_char;
p1 = haystack;
loop {
if p1.is_null() || *p1 as libc::c_int == 0i32 {
return 0 as *mut libc::c_char;
} else {
if *p1 as libc::c_int == key && *p1.offset(1isize) as libc::c_int == '=' as i32 {
break;
}
p1 = strchr(p1, '\n' as i32);
if !p1.is_null() {
p1 = p1.offset(1isize)
}
}
}
p2 = strchr(p1, '\n' as i32);
if p2.is_null() {
p2 = &mut *p1.offset(strlen(p1) as isize) as *mut libc::c_char
}
*ret_p2 = p2;
p1
}
/* the value may be an empty string, "def" is returned only if the value unset. The result must be free()'d in any case. */
pub unsafe fn dc_param_get(
param: *const dc_param_t,
key: libc::c_int,
def: *const libc::c_char,
) -> *mut libc::c_char {
let mut p1: *mut libc::c_char;
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
let bak: libc::c_char;
let ret: *mut libc::c_char;
if param.is_null() || key == 0i32 {
return if !def.is_null() {
dc_strdup(def)
} else {
0 as *mut libc::c_char
};
}
p1 = find_param((*param).packed, key, &mut p2);
if p1.is_null() {
return if !def.is_null() {
dc_strdup(def)
} else {
0 as *mut libc::c_char
};
}
p1 = p1.offset(2isize);
bak = *p2;
*p2 = 0i32 as libc::c_char;
ret = dc_strdup(p1);
dc_rtrim(ret);
*p2 = bak;
ret
}
pub unsafe fn dc_param_get_int(
param: *const dc_param_t,
key: libc::c_int,
def: int32_t,
) -> int32_t {
if param.is_null() || key == 0i32 {
return def;
}
let s = dc_param_get(param, key, 0 as *const libc::c_char);
if s.is_null() {
return def;
}
let ret = as_str(s).parse().unwrap_or_default();
free(s as *mut libc::c_void);
ret
}
/**
* Get value of a parameter.
*
* @memberof dc_param_t
* @param param Parameter object to query.
* @param key Key of the parameter to get, one of the DC_PARAM_* constants.
* @param def Value to return if the parameter is not set.
* @return The stored value or the default value.
*/
pub unsafe fn dc_param_get_float(
param: *const dc_param_t,
key: libc::c_int,
def: libc::c_double,
) -> libc::c_double {
if param.is_null() || key == 0 {
return def;
}
let str = dc_param_get(param, key, std::ptr::null());
if str.is_null() {
return def;
}
let ret = dc_atof(str) as libc::c_double;
free(str as *mut libc::c_void);
ret
}
pub unsafe fn dc_param_set(
mut param: *mut dc_param_t,
key: libc::c_int,
value: *const libc::c_char,
) {
let mut old1: *mut libc::c_char;
let mut old2: *mut libc::c_char;
let new1: *mut libc::c_char;
if param.is_null() || key == 0i32 {
return;
}
old1 = (*param).packed;
old2 = 0 as *mut libc::c_char;
if !old1.is_null() {
let p1: *mut libc::c_char;
let mut p2: *mut libc::c_char = 0 as *mut libc::c_char;
p1 = find_param(old1, key, &mut p2);
if !p1.is_null() {
*p1 = 0i32 as libc::c_char;
old2 = p2
} else if value.is_null() {
return;
}
}
dc_rtrim(old1);
dc_ltrim(old2);
if !old1.is_null() && *old1.offset(0isize) as libc::c_int == 0i32 {
old1 = 0 as *mut libc::c_char
}
if !old2.is_null() && *old2.offset(0isize) as libc::c_int == 0i32 {
old2 = 0 as *mut libc::c_char
}
if !value.is_null() {
new1 = dc_mprintf(
b"%s%s%c=%s%s%s\x00" as *const u8 as *const libc::c_char,
if !old1.is_null() {
old1
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !old1.is_null() {
b"\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
key,
value,
if !old2.is_null() {
b"\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !old2.is_null() {
old2
} else {
b"\x00" as *const u8 as *const libc::c_char
},
)
} else {
new1 = dc_mprintf(
b"%s%s%s\x00" as *const u8 as *const libc::c_char,
if !old1.is_null() {
old1
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !old1.is_null() && !old2.is_null() {
b"\n\x00" as *const u8 as *const libc::c_char
} else {
b"\x00" as *const u8 as *const libc::c_char
},
if !old2.is_null() {
old2
} else {
b"\x00" as *const u8 as *const libc::c_char
},
)
}
free((*param).packed as *mut libc::c_void);
(*param).packed = new1;
}
pub unsafe fn dc_param_set_int(param: *mut dc_param_t, key: libc::c_int, value: int32_t) {
if param.is_null() || key == 0i32 {
return;
}
let value_str: *mut libc::c_char = dc_mprintf(
b"%i\x00" as *const u8 as *const libc::c_char,
value as libc::c_int,
);
if value_str.is_null() {
return;
}
dc_param_set(param, key, value_str);
free(value_str as *mut libc::c_void);
}
/* library-private */
pub unsafe fn dc_param_new() -> *mut dc_param_t {
let mut param: *mut dc_param_t;
param = calloc(1, ::std::mem::size_of::<dc_param_t>()) as *mut dc_param_t;
assert!(!param.is_null());
(*param).packed = calloc(1, 1) as *mut libc::c_char;
param
}
pub unsafe fn dc_param_empty(param: *mut dc_param_t) {
if param.is_null() {
return;
}
*(*param).packed.offset(0isize) = 0i32 as libc::c_char;
}
pub unsafe fn dc_param_unref(param: *mut dc_param_t) {
if param.is_null() {
return;
}
dc_param_empty(param);
free((*param).packed as *mut libc::c_void);
free(param as *mut libc::c_void);
}
pub unsafe fn dc_param_set_packed(mut param: *mut dc_param_t, packed: *const libc::c_char) {
if param.is_null() {
return;
}
dc_param_empty(param);
if !packed.is_null() {
free((*param).packed as *mut libc::c_void);
(*param).packed = dc_strdup(packed)
};
}
pub unsafe fn dc_param_set_urlencoded(mut param: *mut dc_param_t, urlencoded: *const libc::c_char) {
if param.is_null() {
return;
}
dc_param_empty(param);
if !urlencoded.is_null() {
free((*param).packed as *mut libc::c_void);
(*param).packed = dc_strdup(urlencoded);
dc_str_replace(
&mut (*param).packed,
b"&\x00" as *const u8 as *const libc::c_char,
b"\n\x00" as *const u8 as *const libc::c_char,
);
};
}
/**
* Set parameter to a float.
*
* @memberof dc_param_t
* @param param Parameter object to modify.
* @param key Key of the parameter to modify, one of the DC_PARAM_* constants.
* @param value Value to store for key.
* @return None.
*/
pub unsafe fn dc_param_set_float(param: *mut dc_param_t, key: libc::c_int, value: libc::c_double) {
if param.is_null() || key == 0 {
return;
}
let value_str = dc_ftoa(value);
if value_str.is_null() {
return;
}
dc_param_set(param, key, value_str);
free(value_str as *mut libc::c_void);
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn test_dc_param() {
unsafe {
let p1: *mut dc_param_t = dc_param_new();
dc_param_set_packed(
p1,
b"\r\n\r\na=1\nb=2\n\nc = 3 \x00" as *const u8 as *const libc::c_char,
);
assert_eq!(dc_param_get_int(p1, 'a' as i32, 0), 1);
assert_eq!(dc_param_get_int(p1, 'b' as i32, 0), 2);
assert_eq!(dc_param_get_int(p1, 'c' as i32, 0), 0);
assert_eq!(dc_param_exists(p1, 'c' as i32), 0);
dc_param_set_int(p1, 'd' as i32, 4i32);
assert_eq!(dc_param_get_int(p1, 'd' as i32, 0), 4);
dc_param_empty(p1);
dc_param_set(
p1,
'a' as i32,
b"foo\x00" as *const u8 as *const libc::c_char,
);
dc_param_set_int(p1, 'b' as i32, 2i32);
dc_param_set(p1, 'c' as i32, 0 as *const libc::c_char);
dc_param_set_int(p1, 'd' as i32, 4i32);
assert_eq!(
CStr::from_ptr((*p1).packed as *const libc::c_char)
.to_str()
.unwrap(),
"a=foo\nb=2\nd=4"
);
dc_param_set(p1, 'b' as i32, 0 as *const libc::c_char);
assert_eq!(
CStr::from_ptr((*p1).packed as *const libc::c_char)
.to_str()
.unwrap(),
"a=foo\nd=4",
);
dc_param_set(p1, 'a' as i32, 0 as *const libc::c_char);
dc_param_set(p1, 'd' as i32, 0 as *const libc::c_char);
assert_eq!(
CStr::from_ptr((*p1).packed as *const libc::c_char)
.to_str()
.unwrap(),
"",
);
dc_param_unref(p1);
}
}
}

View File

@@ -1,13 +1,12 @@
use percent_encoding::percent_decode_str;
use crate::contact::*;
use crate::context::Context;
use crate::dc_chat::*;
use crate::dc_contact::*;
use crate::dc_log::*;
use crate::dc_lot::*;
use crate::dc_param::*;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::key::*;
use crate::param::*;
use crate::peerstate::*;
use crate::types::*;
use crate::x::*;
@@ -39,7 +38,12 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc
let mut grpname: *mut libc::c_char = 0 as *mut libc::c_char;
(*qr_parsed).state = 0i32;
if !qr.is_null() {
info!(context, 0, "Scanned QR code: {}", as_str(qr),);
dc_log_info(
context,
0i32,
b"Scanned QR code: %s\x00" as *const u8 as *const libc::c_char,
qr,
);
/* split parameters from the qr code
------------------------------------ */
if strncasecmp(
@@ -56,39 +60,29 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc
if !fragment.is_null() {
*fragment = 0i32 as libc::c_char;
fragment = fragment.offset(1isize);
let param: Params = as_str(fragment).parse().expect("invalid params");
addr = param
.get(Param::Forwarded)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
let param: *mut dc_param_t = dc_param_new();
dc_param_set_urlencoded(param, fragment);
addr = dc_param_get(param, 'a' as i32, 0 as *const libc::c_char);
if !addr.is_null() {
if let Some(ref name_enc) = param.get(Param::SetLongitude) {
let name_r = percent_decode_str(name_enc)
.decode_utf8()
.expect("invalid name");
name = normalize_name(name_r).strdup();
let mut urlencoded: *mut libc::c_char =
dc_param_get(param, 'n' as i32, 0 as *const libc::c_char);
if !urlencoded.is_null() {
name = dc_urldecode(urlencoded);
dc_normalize_name(name);
free(urlencoded as *mut libc::c_void);
}
invitenumber = param
.get(Param::ProfileImage)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
auth = param
.get(Param::Auth)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
grpid = param
.get(Param::GroupId)
.map(|s| s.strdup())
.unwrap_or_else(|| std::ptr::null_mut());
invitenumber = dc_param_get(param, 'i' as i32, 0 as *const libc::c_char);
auth = dc_param_get(param, 's' as i32, 0 as *const libc::c_char);
grpid = dc_param_get(param, 'x' as i32, 0 as *const libc::c_char);
if !grpid.is_null() {
if let Some(grpname_enc) = param.get(Param::GroupName) {
let grpname_r = percent_decode_str(grpname_enc)
.decode_utf8()
.expect("invalid groupname");
grpname = grpname_r.strdup();
urlencoded = dc_param_get(param, 'g' as i32, 0 as *const libc::c_char);
if !urlencoded.is_null() {
grpname = dc_urldecode(urlencoded);
free(urlencoded as *mut libc::c_void);
}
}
}
dc_param_unref(param);
}
fingerprint = dc_normalize_fingerprint_c(payload);
current_block = 5023038348526654800;
@@ -151,8 +145,11 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc
strlen(b"BEGIN:VCARD\x00" as *const u8 as *const libc::c_char),
) == 0i32
{
let lines = dc_split_into_lines(qr);
for &key in &lines {
let lines: *mut carray = dc_split_into_lines(qr);
let mut i: libc::c_int = 0i32;
while (i as libc::c_uint) < carray_count(lines) {
let key: *mut libc::c_char =
carray_get(lines, i as libc::c_uint) as *mut libc::c_char;
dc_trim(key);
let mut value: *mut libc::c_char = strchr(key, ':' as i32);
if !value.is_null() {
@@ -185,9 +182,10 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc
b";\x00" as *const u8 as *const libc::c_char,
b",\x00" as *const u8 as *const libc::c_char,
);
name = normalize_name(as_str(name)).strdup();
dc_normalize_name(name);
}
}
i += 1
}
dc_free_splitted_lines(lines);
}
@@ -196,17 +194,17 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc
match current_block {
16562876845594826114 => {}
_ => {
/* check the parameters
/* check the paramters
---------------------- */
if !addr.is_null() {
/* urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases */
let mut temp: *mut libc::c_char = dc_urldecode(addr);
free(addr as *mut libc::c_void);
addr = temp;
temp = addr_normalize(as_str(addr)).strdup();
temp = dc_addr_normalize(addr);
free(addr as *mut libc::c_void);
addr = temp;
if !may_be_valid_addr(as_str(addr)) {
if !dc_may_be_valid_addr(addr) {
(*qr_parsed).state = 400i32;
(*qr_parsed).text1 = dc_strdup(
b"Bad e-mail address.\x00" as *const u8 as *const libc::c_char,
@@ -247,19 +245,23 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc
if addr.is_null() || invitenumber.is_null() || auth.is_null() {
if let Some(peerstate) = peerstate {
(*qr_parsed).state = 210i32;
let addr = peerstate
let c_addr = peerstate
.addr
.as_ref()
.map(|s| s.as_str())
.unwrap_or_else(|| "");
(*qr_parsed).id = Contact::add_or_lookup(
.map(to_cstring)
.unwrap_or_default();
let addr_ptr = if peerstate.addr.is_some() {
c_addr.as_ptr()
} else {
std::ptr::null()
};
(*qr_parsed).id = dc_add_or_lookup_contact(
context,
"",
addr,
Origin::UnhandledQrScan,
)
.map(|(id, _)| id)
.unwrap_or_default();
0 as *const libc::c_char,
addr_ptr,
0x80i32,
0 as *mut libc::c_int,
);
dc_create_or_lookup_nchat_by_contact_id(
context,
(*qr_parsed).id,
@@ -285,28 +287,26 @@ pub unsafe fn dc_check_qr(context: &Context, qr: *const libc::c_char) -> *mut dc
} else {
(*qr_parsed).state = 200i32
}
(*qr_parsed).id = Contact::add_or_lookup(
(*qr_parsed).id = dc_add_or_lookup_contact(
context,
as_str(name),
as_str(addr),
Origin::UnhandledQrScan,
)
.map(|(id, _)| id)
.unwrap_or_default();
name,
addr,
0x80i32,
0 as *mut libc::c_int,
);
(*qr_parsed).fingerprint = dc_strdup(fingerprint);
(*qr_parsed).invitenumber = dc_strdup(invitenumber);
(*qr_parsed).auth = dc_strdup(auth)
}
} else if !addr.is_null() {
(*qr_parsed).state = 320i32;
(*qr_parsed).id = Contact::add_or_lookup(
(*qr_parsed).id = dc_add_or_lookup_contact(
context,
as_str(name),
as_str(addr),
Origin::UnhandledQrScan,
name,
addr,
0x80i32,
0 as *mut libc::c_int,
)
.map(|(id, _)| id)
.unwrap_or_default();
} else if strstr(
qr,
b"http://\x00" as *const u8 as *const libc::c_char,

File diff suppressed because it is too large Load Diff

View File

@@ -12,13 +12,10 @@ pub struct dc_saxparser_t {
}
/* len is only informational, text is already null-terminated */
#[allow(non_camel_case_types)]
pub type dc_saxparser_text_cb_t =
Option<unsafe fn(_: *mut libc::c_void, _: *const libc::c_char, _: libc::c_int) -> ()>;
#[allow(non_camel_case_types)]
pub type dc_saxparser_endtag_cb_t =
Option<unsafe fn(_: *mut libc::c_void, _: *const libc::c_char) -> ()>;
#[allow(non_camel_case_types)]
pub type dc_saxparser_starttag_cb_t = Option<
unsafe fn(_: *mut libc::c_void, _: *const libc::c_char, _: *mut *mut libc::c_char) -> (),
>;
@@ -80,7 +77,7 @@ pub unsafe fn dc_saxparser_set_text_handler(
}
pub unsafe fn dc_saxparser_parse(saxparser: *mut dc_saxparser_t, buf_start__: *const libc::c_char) {
let mut is_valid = false;
let current_block: u64;
let mut bak: libc::c_char;
let buf_start: *mut libc::c_char;
let mut last_text_start: *mut libc::c_char;
@@ -99,7 +96,7 @@ pub unsafe fn dc_saxparser_parse(saxparser: *mut dc_saxparser_t, buf_start__: *c
p = buf_start;
loop {
if !(0 != *p) {
is_valid = true;
current_block = 13425230902034816933;
break;
}
if *p as libc::c_int == '<' as i32 {
@@ -113,6 +110,7 @@ pub unsafe fn dc_saxparser_parse(saxparser: *mut dc_saxparser_t, buf_start__: *c
if strncmp(p, b"!--\x00" as *const u8 as *const libc::c_char, 3) == 0i32 {
p = strstr(p, b"-->\x00" as *const u8 as *const libc::c_char);
if p.is_null() {
current_block = 7627180618761592946;
break;
}
p = p.offset(3isize)
@@ -136,6 +134,7 @@ pub unsafe fn dc_saxparser_parse(saxparser: *mut dc_saxparser_t, buf_start__: *c
strlen(text_beg),
'c' as i32 as libc::c_char,
);
current_block = 7627180618761592946;
break;
}
} else if strncmp(p, b"!DOCTYPE\x00" as *const u8 as *const libc::c_char, 8) == 0i32 {
@@ -147,11 +146,13 @@ pub unsafe fn dc_saxparser_parse(saxparser: *mut dc_saxparser_t, buf_start__: *c
}
if *p as libc::c_int == 0i32 {
/* unclosed doctype */
current_block = 7627180618761592946;
break;
} else if *p as libc::c_int == '[' as i32 {
p = strstr(p, b"]>\x00" as *const u8 as *const libc::c_char);
if p.is_null() {
/* unclosed inline doctype */
current_block = 7627180618761592946;
break;
} else {
p = p.offset(2isize)
@@ -163,6 +164,7 @@ pub unsafe fn dc_saxparser_parse(saxparser: *mut dc_saxparser_t, buf_start__: *c
p = strstr(p, b"?>\x00" as *const u8 as *const libc::c_char);
if p.is_null() {
/* unclosed processing instruction */
current_block = 7627180618761592946;
break;
} else {
p = p.offset(2isize)
@@ -323,6 +325,7 @@ pub unsafe fn dc_saxparser_parse(saxparser: *mut dc_saxparser_t, buf_start__: *c
p = strchr(p, '>' as i32);
if p.is_null() {
/* unclosed start-tag or end-tag */
current_block = 7627180618761592946;
break;
} else {
p = p.offset(1isize)
@@ -333,13 +336,16 @@ pub unsafe fn dc_saxparser_parse(saxparser: *mut dc_saxparser_t, buf_start__: *c
p = p.offset(1isize)
}
}
if is_valid {
call_text_cb(
saxparser,
last_text_start,
p.wrapping_offset_from(last_text_start) as size_t,
'&' as i32 as libc::c_char,
);
match current_block {
13425230902034816933 => {
call_text_cb(
saxparser,
last_text_start,
p.wrapping_offset_from(last_text_start) as size_t,
'&' as i32 as libc::c_char,
);
}
_ => {}
}
do_free_attr(attr.as_mut_ptr(), free_attr.as_mut_ptr());
free(buf_start as *mut libc::c_void);
@@ -486,19 +492,19 @@ unsafe fn xml_decode(mut s: *mut libc::c_char, type_0: libc::c_char) -> *mut lib
&& (type_0 as libc::c_int == '&' as i32 || type_0 as libc::c_int == ' ' as i32)
{
b = 0;
while !S_ENT[b as usize].is_null()
while !s_ent[b as usize].is_null()
&& 0 != strncmp(
s.offset(1isize),
S_ENT[b as usize],
strlen(S_ENT[b as usize]),
s_ent[b as usize],
strlen(s_ent[b as usize]),
)
{
b += 2;
}
let fresh5 = b;
b = b + 1;
if !S_ENT[fresh5 as usize].is_null() {
c = strlen(S_ENT[b as usize]) as isize;
if !s_ent[fresh5 as usize].is_null() {
c = strlen(s_ent[b as usize]) as isize;
e = strchr(s, ';' as i32);
if c - 1 > e.wrapping_offset_from(s) as isize {
d = s.wrapping_offset_from(r) as isize;
@@ -526,7 +532,7 @@ unsafe fn xml_decode(mut s: *mut libc::c_char, type_0: libc::c_char) -> *mut lib
e.offset(1isize) as *const libc::c_void,
strlen(e),
);
strncpy(s, S_ENT[b as usize], c as usize);
strncpy(s, s_ent[b as usize], c as usize);
} else {
s = s.offset(1isize)
}
@@ -560,7 +566,7 @@ NB: SAX = Simple API for XML */
/* ******************************************************************************
* Decoding text
******************************************************************************/
static mut S_ENT: [*const libc::c_char; 508] = [
static mut s_ent: [*const libc::c_char; 508] = [
b"lt;\x00" as *const u8 as *const libc::c_char,
b"<\x00" as *const u8 as *const libc::c_char,
b"gt;\x00" as *const u8 as *const libc::c_char,

View File

@@ -1,27 +1,26 @@
use std::ffi::CString;
use mmime::mailimf_types::*;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use crate::aheader::EncryptPreference;
use crate::constants::*;
use crate::contact::*;
use crate::constants::Event;
use crate::context::Context;
use crate::dc_array::*;
use crate::dc_chat::*;
use crate::dc_configure::*;
use crate::dc_contact::*;
use crate::dc_e2ee::*;
use crate::dc_log::*;
use crate::dc_lot::*;
use crate::dc_mimeparser::*;
use crate::dc_msg::*;
use crate::dc_param::*;
use crate::dc_qr::*;
use crate::dc_sqlite3::*;
use crate::dc_stock::*;
use crate::dc_strencode::*;
use crate::dc_token::*;
use crate::dc_tools::*;
use crate::key::*;
use crate::param::*;
use crate::peerstate::*;
use crate::stock::StockMessage;
use crate::types::*;
use crate::x::*;
@@ -29,110 +28,138 @@ pub unsafe fn dc_get_securejoin_qr(
context: &Context,
group_chat_id: uint32_t,
) -> *mut libc::c_char {
let current_block: u64;
/* =========================================================
==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ====
========================================================= */
let mut fingerprint = 0 as *mut libc::c_char;
let mut qr: *mut libc::c_char = 0 as *mut libc::c_char;
let self_addr: *mut libc::c_char;
let mut self_addr_urlencoded: *mut libc::c_char = 0 as *mut libc::c_char;
let mut self_name: *mut libc::c_char = 0 as *mut libc::c_char;
let mut self_name_urlencoded: *mut libc::c_char = 0 as *mut libc::c_char;
let mut fingerprint: *mut libc::c_char = 0 as *mut libc::c_char;
let mut invitenumber: *mut libc::c_char;
let mut auth: *mut libc::c_char;
let mut chat = 0 as *mut Chat;
let mut group_name = 0 as *mut libc::c_char;
let mut group_name_urlencoded = 0 as *mut libc::c_char;
let mut qr: Option<String> = None;
let mut chat: *mut Chat = 0 as *mut Chat;
let mut group_name: *mut libc::c_char = 0 as *mut libc::c_char;
let mut group_name_urlencoded: *mut libc::c_char = 0 as *mut libc::c_char;
dc_ensure_secret_key_exists(context);
invitenumber = dc_token_lookup(context, DC_TOKEN_INVITENUMBER, group_chat_id);
if invitenumber.is_null() {
invitenumber = dc_create_id().strdup();
invitenumber = dc_create_id();
dc_token_save(context, DC_TOKEN_INVITENUMBER, group_chat_id, invitenumber);
}
auth = dc_token_lookup(context, DC_TOKEN_AUTH, group_chat_id);
if auth.is_null() {
auth = dc_create_id().strdup();
auth = dc_create_id();
dc_token_save(context, DC_TOKEN_AUTH, group_chat_id, auth);
}
let self_addr = context.sql.get_config(context, "configured_addr");
let cleanup = |fingerprint, chat, group_name, group_name_urlencoded| {
free(fingerprint as *mut libc::c_void);
free(invitenumber as *mut libc::c_void);
free(auth as *mut libc::c_void);
dc_chat_unref(chat);
free(group_name as *mut libc::c_void);
free(group_name_urlencoded as *mut libc::c_void);
if let Some(qr) = qr {
qr.strdup()
} else {
std::ptr::null_mut()
}
};
if self_addr.is_none() {
error!(context, 0, "Not configured, cannot generate QR code.",);
return cleanup(fingerprint, chat, group_name, group_name_urlencoded);
}
let self_addr = self_addr.unwrap();
let self_name = context
.sql
.get_config(context, "displayname")
.unwrap_or_default();
fingerprint = get_self_fingerprint(context);
if fingerprint.is_null() {
return cleanup(fingerprint, chat, group_name, group_name_urlencoded);
}
let self_addr_urlencoded = utf8_percent_encode(&self_addr, NON_ALPHANUMERIC).to_string();
let self_name_urlencoded = utf8_percent_encode(&self_name, NON_ALPHANUMERIC).to_string();
qr = if 0 != group_chat_id {
chat = dc_get_chat(context, group_chat_id);
if chat.is_null() {
error!(
context,
0, "Cannot get QR-code for chat-id {}", group_chat_id,
);
return cleanup(fingerprint, chat, group_name, group_name_urlencoded);
}
group_name = dc_chat_get_name(chat);
group_name_urlencoded = dc_urlencode(group_name);
Some(format!(
"OPENPGP4FPR:{}#a={}&g={}&x={}&i={}&s={}",
as_str(fingerprint),
self_addr_urlencoded,
as_str(group_name_urlencoded),
as_str((*chat).grpid),
as_str(invitenumber),
as_str(auth),
))
self_addr = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_addr\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
if self_addr.is_null() {
dc_log_error(
context,
0i32,
b"Not configured, cannot generate QR code.\x00" as *const u8 as *const libc::c_char,
);
} else {
Some(format!(
"OPENPGP4FPR:{}#a={}&n={}&i={}&s={}",
as_str(fingerprint),
self_addr_urlencoded,
self_name_urlencoded,
as_str(invitenumber),
as_str(auth),
))
self_name = dc_sqlite3_get_config(
context,
&context.sql,
b"displayname\x00" as *const u8 as *const libc::c_char,
b"\x00" as *const u8 as *const libc::c_char,
);
fingerprint = get_self_fingerprint(context);
if !fingerprint.is_null() {
self_addr_urlencoded = dc_urlencode(self_addr);
self_name_urlencoded = dc_urlencode(self_name);
if 0 != group_chat_id {
chat = dc_get_chat(context, group_chat_id);
if chat.is_null() {
dc_log_error(
context,
0i32,
b"Cannot get QR-code for chat-id %i\x00" as *const u8
as *const libc::c_char,
group_chat_id,
);
current_block = 9531737720721467826;
} else {
group_name = dc_chat_get_name(chat);
group_name_urlencoded = dc_urlencode(group_name);
qr = dc_mprintf(
b"OPENPGP4FPR:%s#a=%s&g=%s&x=%s&i=%s&s=%s\x00" as *const u8
as *const libc::c_char,
fingerprint,
self_addr_urlencoded,
group_name_urlencoded,
(*chat).grpid,
invitenumber,
auth,
);
current_block = 1118134448028020070;
}
} else {
qr = dc_mprintf(
b"OPENPGP4FPR:%s#a=%s&n=%s&i=%s&s=%s\x00" as *const u8 as *const libc::c_char,
fingerprint,
self_addr_urlencoded,
self_name_urlencoded,
invitenumber,
auth,
);
current_block = 1118134448028020070;
}
match current_block {
9531737720721467826 => {}
_ => {
dc_log_info(
context,
0i32,
b"Generated QR code: %s\x00" as *const u8 as *const libc::c_char,
qr,
);
}
}
}
}
free(self_addr_urlencoded as *mut libc::c_void);
free(self_addr as *mut libc::c_void);
free(self_name as *mut libc::c_void);
free(self_name_urlencoded as *mut libc::c_void);
free(fingerprint as *mut libc::c_void);
free(invitenumber as *mut libc::c_void);
free(auth as *mut libc::c_void);
dc_chat_unref(chat);
free(group_name as *mut libc::c_void);
free(group_name_urlencoded as *mut libc::c_void);
return if !qr.is_null() {
qr
} else {
dc_strdup(0 as *const libc::c_char)
};
info!(context, 0, "Generated QR code: {}", qr.as_ref().unwrap());
cleanup(fingerprint, chat, group_name, group_name_urlencoded)
}
fn get_self_fingerprint(context: &Context) -> *mut libc::c_char {
if let Some(self_addr) = context.sql.get_config(context, "configured_addr") {
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
return key.fingerprint_c();
}
unsafe fn get_self_fingerprint(context: &Context) -> *mut libc::c_char {
let self_addr = dc_sqlite3_get_config(
context,
&context.sql,
b"configured_addr\x00" as *const u8 as *const libc::c_char,
0 as *const libc::c_char,
);
if self_addr.is_null() {
return std::ptr::null_mut();
}
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
return key.fingerprint_c();
}
std::ptr::null_mut()
@@ -148,17 +175,29 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) ->
let mut contact_chat_id: uint32_t = 0i32 as uint32_t;
let mut join_vg: libc::c_int = 0i32;
let mut qr_scan: *mut dc_lot_t = 0 as *mut dc_lot_t;
info!(context, 0, "Requesting secure-join ...",);
dc_log_info(
context,
0i32,
b"Requesting secure-join ...\x00" as *const u8 as *const libc::c_char,
);
dc_ensure_secret_key_exists(context);
ongoing_allocated = dc_alloc_ongoing(context);
if !(ongoing_allocated == 0i32) {
qr_scan = dc_check_qr(context, qr);
if qr_scan.is_null() || (*qr_scan).state != 200i32 && (*qr_scan).state != 202i32 {
error!(context, 0, "Unknown QR code.",);
dc_log_error(
context,
0i32,
b"Unknown QR code.\x00" as *const u8 as *const libc::c_char,
);
} else {
contact_chat_id = dc_create_chat_by_contact_id(context, (*qr_scan).id);
if contact_chat_id == 0i32 as libc::c_uint {
error!(context, 0, "Unknown contact.",);
dc_log_error(
context,
0i32,
b"Unknown contact.\x00" as *const u8 as *const libc::c_char,
);
} else if !(context
.running_state
.clone()
@@ -175,7 +214,11 @@ pub unsafe fn dc_join_securejoin(context: &Context, qr: *const libc::c_char) ->
}
if 0 != fingerprint_equals_sender(context, (*qr_scan).fingerprint, contact_chat_id)
{
info!(context, 0, "Taking protocol shortcut.");
dc_log_info(
context,
0i32,
b"Taking protocol shortcut.\x00" as *const u8 as *const libc::c_char,
);
context.bob.clone().write().unwrap().expects = 6;
context.call_cb(
Event::SECUREJOIN_JOINER_PROGRESS,
@@ -263,33 +306,29 @@ unsafe fn send_handshake_msg(
grpid: *const libc::c_char,
) {
let mut msg: *mut dc_msg_t = dc_msg_new_untyped(context);
(*msg).type_0 = Viewtype::Text;
(*msg).text = Some(format!("Secure-Join: {}", to_string(step)));
(*msg).hidden = 1;
(*msg).param.set_int(Param::Cmd, 7);
if step.is_null() {
(*msg).param.remove(Param::Arg);
} else {
(*msg).param.set(Param::Arg, as_str(step));
}
(*msg).type_0 = 10i32;
(*msg).text = dc_mprintf(
b"Secure-Join: %s\x00" as *const u8 as *const libc::c_char,
step,
);
(*msg).hidden = 1i32;
dc_param_set_int((*msg).param, 'S' as i32, 7i32);
dc_param_set((*msg).param, 'E' as i32, step);
if !param2.is_null() {
(*msg).param.set(Param::Arg2, as_str(param2));
dc_param_set((*msg).param, 'F' as i32, param2);
}
if !fingerprint.is_null() {
(*msg).param.set(Param::Arg3, as_str(fingerprint));
dc_param_set((*msg).param, 'G' as i32, fingerprint);
}
if !grpid.is_null() {
(*msg).param.set(Param::Arg4, as_str(grpid));
dc_param_set((*msg).param, 'H' as i32, grpid);
}
if strcmp(step, b"vg-request\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(step, b"vc-request\x00" as *const u8 as *const libc::c_char) == 0i32
{
(*msg).param.set_int(
Param::ForcePlaintext,
ForcePlaintext::AddAutocryptHeader as i32,
);
dc_param_set_int((*msg).param, 'u' as i32, 1i32);
} else {
(*msg).param.set_int(Param::GuranteeE2ee, 1);
dc_param_set_int((*msg).param, 'c' as i32, 1i32);
}
dc_send_msg(context, contact_chat_id, msg);
dc_msg_unref(msg);
@@ -315,23 +354,30 @@ unsafe fn fingerprint_equals_sender(
return 0;
}
let mut fingerprint_equal: libc::c_int = 0i32;
let contacts = dc_get_chat_contacts(context, contact_chat_id);
let contacts: *mut dc_array_t = dc_get_chat_contacts(context, contact_chat_id);
let contact: *mut dc_contact_t = dc_contact_new(context);
if !(dc_array_get_cnt(contacts) != 1) {
if let Ok(contact) = Contact::load_from_db(context, dc_array_get_id(contacts, 0)) {
if let Some(peerstate) = Peerstate::from_addr(context, &context.sql, contact.get_addr())
{
let fingerprint_normalized = dc_normalize_fingerprint(as_str(fingerprint));
if peerstate.public_key_fingerprint.is_some()
&& &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap()
{
fingerprint_equal = 1;
}
}
} else {
if !dc_contact_load_from_db(
contact,
&context.sql,
dc_array_get_id(contacts, 0i32 as size_t),
) {
return 0;
}
if let Some(peerstate) =
Peerstate::from_addr(context, &context.sql, as_str((*contact).addr))
{
let fingerprint_normalized = dc_normalize_fingerprint(as_str(fingerprint));
if peerstate.public_key_fingerprint.is_some()
&& &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap()
{
fingerprint_equal = 1;
}
}
}
dc_contact_unref(contact);
dc_array_unref(contacts);
fingerprint_equal
@@ -353,15 +399,16 @@ pub unsafe fn dc_handle_securejoin_handshake(
let mut contact_chat_id_blocked: libc::c_int = 0i32;
let mut grpid: *mut libc::c_char = 0 as *mut libc::c_char;
let mut ret: libc::c_int = 0i32;
let mut contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
if !(contact_id <= 9i32 as libc::c_uint) {
step = lookup_field(mimeparser, "Secure-Join");
if !step.is_null() {
info!(
dc_log_info(
context,
0,
">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received",
as_str(step),
0i32,
b">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'%s\' received\x00" as *const u8
as *const libc::c_char,
step,
);
join_vg = (strncmp(step, b"vg-\x00" as *const u8 as *const libc::c_char, 3) == 0)
as libc::c_int;
@@ -392,12 +439,11 @@ pub unsafe fn dc_handle_securejoin_handshake(
if invitenumber.is_null() {
warn!(context, 0, "Secure-join denied (invitenumber missing).",);
current_block = 4378276786830486580;
} else if !dc_token_exists(context, DC_TOKEN_INVITENUMBER, invitenumber) {
} else if dc_token_exists(context, DC_TOKEN_INVITENUMBER, invitenumber) == 0i32 {
warn!(context, 0, "Secure-join denied (bad invitenumber).",);
current_block = 4378276786830486580;
} else {
info!(context, 0, "Secure-join requested.",);
context.call_cb(
Event::SECUREJOIN_INVITER_PROGRESS,
contact_id as uintptr_t,
@@ -434,7 +480,12 @@ pub unsafe fn dc_handle_securejoin_handshake(
};
if cond {
warn!(context, 0, "auth-required message out of sync.",);
dc_log_warning(
context,
0i32,
b"auth-required message out of sync.\x00" as *const u8
as *const libc::c_char,
);
// no error, just aborted somehow or a mail from another handshake
current_block = 4378276786830486580;
} else {
@@ -474,7 +525,11 @@ pub unsafe fn dc_handle_securejoin_handshake(
end_bobs_joining(context, 0i32);
current_block = 4378276786830486580;
} else {
info!(context, 0, "Fingerprint verified.",);
dc_log_info(
context,
0i32,
b"Fingerprint verified.\x00" as *const u8 as *const libc::c_char,
);
own_fingerprint = get_self_fingerprint(context);
context.call_cb(
Event::SECUREJOIN_JOINER_PROGRESS,
@@ -538,7 +593,11 @@ pub unsafe fn dc_handle_securejoin_handshake(
);
current_block = 4378276786830486580;
} else {
info!(context, 0, "Fingerprint verified.",);
dc_log_info(
context,
0i32,
b"Fingerprint verified.\x00" as *const u8 as *const libc::c_char,
);
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
let auth_0: *const libc::c_char;
auth_0 = lookup_field(mimeparser, "Secure-Join-Auth");
@@ -549,7 +608,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
b"Auth not provided.\x00" as *const u8 as *const libc::c_char,
);
current_block = 4378276786830486580;
} else if !dc_token_exists(context, DC_TOKEN_AUTH, auth_0) {
} else if dc_token_exists(context, DC_TOKEN_AUTH, auth_0) == 0i32 {
could_not_establish_secure_connection(
context,
contact_chat_id,
@@ -565,12 +624,12 @@ pub unsafe fn dc_handle_securejoin_handshake(
);
current_block = 4378276786830486580;
} else {
Contact::scaleup_origin_by_id(
dc_scaleup_contact_origin(context, contact_id, 0x1000000i32);
dc_log_info(
context,
contact_id,
Origin::SecurejoinInvited,
0i32,
b"Auth verified.\x00" as *const u8 as *const libc::c_char,
);
info!(context, 0, "Auth verified.",);
secure_connection_established(context, contact_chat_id);
context.call_cb(
Event::CONTACTS_CHANGED,
@@ -591,7 +650,12 @@ pub unsafe fn dc_handle_securejoin_handshake(
0 as *mut libc::c_int,
);
if group_chat_id == 0i32 as libc::c_uint {
error!(context, 0, "Chat {} not found.", as_str(grpid),);
dc_log_error(
context,
0i32,
b"Chat %s not found.\x00" as *const u8 as *const libc::c_char,
grpid,
);
current_block = 4378276786830486580;
} else {
dc_add_contact_to_chat_ex(
@@ -633,7 +697,12 @@ pub unsafe fn dc_handle_securejoin_handshake(
ret = 0x1i32
}
if context.bob.clone().read().unwrap().expects != 6 {
info!(context, 0, "Message belongs to a different handshake.",);
dc_log_info(
context,
0i32,
b"Message belongs to a different handshake.\x00" as *const u8
as *const libc::c_char,
);
current_block = 4378276786830486580;
} else {
let cond = {
@@ -641,9 +710,11 @@ pub unsafe fn dc_handle_securejoin_handshake(
scan.is_null() || 0 != join_vg && (*scan).state != 202
};
if cond {
warn!(
dc_log_warning(
context,
0, "Message out of sync or belongs to a different handshake.",
0i32,
b"Message out of sync or belongs to a different handshake.\x00"
as *const u8 as *const libc::c_char,
);
current_block = 4378276786830486580;
} else {
@@ -696,29 +767,21 @@ pub unsafe fn dc_handle_securejoin_handshake(
);
current_block = 4378276786830486580;
} else {
Contact::scaleup_origin_by_id(
context,
contact_id,
Origin::SecurejoinJoined,
);
dc_scaleup_contact_origin(context, contact_id, 0x2000000i32);
context.call_cb(
Event::CONTACTS_CHANGED,
0i32 as uintptr_t,
0i32 as uintptr_t,
);
if 0 != join_vg {
if !addr_equals_self(
if 0 == dc_addr_equals_self(
context,
as_str(lookup_field(
mimeparser,
"Chat-Group-Member-Added",
)),
lookup_field(mimeparser, "Chat-Group-Member-Added"),
) {
info!(
context,
0,
"Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."
);
dc_log_info(context, 0i32,
b"Message belongs to a different handshake (scaled up contact anyway to allow creation of group).\x00"
as *const u8 as
*const libc::c_char);
current_block = 4378276786830486580;
} else {
current_block = 9180031981464905198;
@@ -760,26 +823,27 @@ pub unsafe fn dc_handle_securejoin_handshake(
==== Alice - the inviter side ====
==== Step 8 in "Out-of-band verified groups" protocol ====
============================================================ */
if let Ok(contact) = Contact::get_by_id(context, contact_id) {
if contact.is_verified() == VerifiedStatus::Unverified {
warn!(context, 0, "vg-member-added-received invalid.",);
current_block = 4378276786830486580;
} else {
context.call_cb(
Event::SECUREJOIN_INVITER_PROGRESS,
contact_id as uintptr_t,
800i32 as uintptr_t,
);
context.call_cb(
Event::SECUREJOIN_INVITER_PROGRESS,
contact_id as uintptr_t,
1000i32 as uintptr_t,
);
current_block = 10256747982273457880;
}
} else {
warn!(context, 0, "vg-member-added-received invalid.",);
contact = dc_get_contact(context, contact_id);
if contact.is_null() || 0 == dc_contact_is_verified(contact) {
dc_log_warning(
context,
0i32,
b"vg-member-added-received invalid.\x00" as *const u8
as *const libc::c_char,
);
current_block = 4378276786830486580;
} else {
context.call_cb(
Event::SECUREJOIN_INVITER_PROGRESS,
contact_id as uintptr_t,
800i32 as uintptr_t,
);
context.call_cb(
Event::SECUREJOIN_INVITER_PROGRESS,
contact_id as uintptr_t,
1000i32 as uintptr_t,
);
current_block = 10256747982273457880;
}
} else {
current_block = 10256747982273457880;
@@ -794,7 +858,7 @@ pub unsafe fn dc_handle_securejoin_handshake(
}
}
}
dc_contact_unref(contact);
free(scanned_fingerprint_of_alice as *mut libc::c_void);
free(auth as *mut libc::c_void);
free(own_fingerprint as *mut libc::c_void);
@@ -810,20 +874,24 @@ unsafe fn end_bobs_joining(context: &Context, status: libc::c_int) {
unsafe fn secure_connection_established(context: &Context, contact_chat_id: uint32_t) {
let contact_id: uint32_t = chat_id_2_contact_id(context, contact_chat_id);
let contact = Contact::get_by_id(context, contact_id);
let addr = if let Ok(ref contact) = contact {
contact.get_addr()
} else {
"?"
};
let msg =
CString::new(context.stock_string_repl_str(StockMessage::ContactVerified, addr)).unwrap();
dc_add_device_msg(context, contact_chat_id, msg.as_ptr());
let contact: *mut dc_contact_t = dc_get_contact(context, contact_id);
let msg: *mut libc::c_char = dc_stock_str_repl_string(
context,
35i32,
if !contact.is_null() {
(*contact).addr
} else {
b"?\x00" as *const u8 as *const libc::c_char
},
);
dc_add_device_msg(context, contact_chat_id, msg);
context.call_cb(
Event::CHAT_MODIFIED,
contact_chat_id as uintptr_t,
0i32 as uintptr_t,
);
free(msg as *mut libc::c_void);
dc_contact_unref(contact);
}
unsafe fn lookup_field(mimeparser: &dc_mimeparser_t, key: &str) -> *const libc::c_char {
@@ -849,18 +917,26 @@ unsafe fn could_not_establish_secure_connection(
details: *const libc::c_char,
) {
let contact_id: uint32_t = 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(
StockMessage::ContactNotVerified,
if let Ok(ref contact) = contact {
contact.get_addr()
let contact = dc_get_contact(context, contact_id);
let msg: *mut libc::c_char = dc_stock_str_repl_string(
context,
36i32,
if !contact.is_null() {
(*contact).addr
} else {
"?"
b"?\x00" as *const u8 as *const libc::c_char
},
);
let msg_c = CString::new(msg.as_str()).unwrap();
dc_add_device_msg(context, contact_chat_id, msg_c.as_ptr());
error!(context, 0, "{} ({})", msg, as_str(details));
dc_add_device_msg(context, contact_chat_id, msg);
dc_log_error(
context,
0i32,
b"%s (%s)\x00" as *const u8 as *const libc::c_char,
msg,
details,
);
free(msg as *mut libc::c_void);
dc_contact_unref(contact);
}
unsafe fn mark_peer_as_verified(
@@ -893,15 +969,27 @@ unsafe fn encrypted_and_signed(
expected_fingerprint: *const libc::c_char,
) -> libc::c_int {
if 0 == mimeparser.e2ee_helper.encrypted {
warn!(mimeparser.context, 0, "Message not encrypted.",);
dc_log_warning(
mimeparser.context,
0i32,
b"Message not encrypted.\x00" as *const u8 as *const libc::c_char,
);
return 0i32;
}
if mimeparser.e2ee_helper.signatures.len() <= 0 {
warn!(mimeparser.context, 0, "Message not signed.",);
dc_log_warning(
mimeparser.context,
0i32,
b"Message not signed.\x00" as *const u8 as *const libc::c_char,
);
return 0i32;
}
if expected_fingerprint.is_null() {
warn!(mimeparser.context, 0, "Fingerprint for comparison missing.",);
dc_log_warning(
mimeparser.context,
0i32,
b"Fingerprint for comparison missing.\x00" as *const u8 as *const libc::c_char,
);
return 0i32;
}
if !mimeparser
@@ -909,20 +997,23 @@ unsafe fn encrypted_and_signed(
.signatures
.contains(as_str(expected_fingerprint))
{
warn!(
dc_log_warning(
mimeparser.context,
0,
"Message does not match expected fingerprint {}.",
as_str(expected_fingerprint),
0i32,
b"Message does not match expected fingerprint %s.\x00" as *const u8
as *const libc::c_char,
expected_fingerprint,
);
return 0;
return 0i32;
}
1
}
pub unsafe fn dc_handle_degrade_event(context: &Context, peerstate: &Peerstate) {
let mut contact_chat_id = 0;
let stmt;
let contact_id: uint32_t;
let mut contact_chat_id: uint32_t = 0i32 as uint32_t;
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
@@ -930,36 +1021,38 @@ pub unsafe fn dc_handle_degrade_event(context: &Context, peerstate: &Peerstate)
// with things they cannot fix, so the user is just kicked from the verified group
// (and he will know this and can fix this)
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = context
.sql
.query_row_col(
context,
"SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr],
0,
)
.unwrap_or_default();
if contact_id > 0 {
stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT id FROM contacts WHERE addr=?;\x00" as *const u8 as *const libc::c_char,
);
let c_addr = peerstate.addr.as_ref().map(to_cstring).unwrap_or_default();
let c_addr_ptr = if peerstate.addr.is_some() {
c_addr.as_ptr()
} else {
std::ptr::null()
};
sqlite3_bind_text(stmt, 1i32, c_addr_ptr, -1i32, None);
sqlite3_step(stmt);
contact_id = sqlite3_column_int(stmt, 0i32) as uint32_t;
sqlite3_finalize(stmt);
if !(contact_id == 0i32 as libc::c_uint) {
dc_create_or_lookup_nchat_by_contact_id(
context,
contact_id as u32,
2,
contact_id,
2i32,
&mut contact_chat_id,
0 as *mut libc::c_int,
);
let peeraddr: &str = match peerstate.addr {
Some(ref addr) => &addr,
None => "",
};
let msg = CString::new(
context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr),
)
.unwrap();
dc_add_device_msg(context, contact_chat_id, msg.as_ptr());
let msg = dc_stock_str_repl_string(context, 37i32, c_addr_ptr);
dc_add_device_msg(context, contact_chat_id, msg);
free(msg as *mut libc::c_void);
context.call_cb(
Event::CHAT_MODIFIED,
contact_chat_id as uintptr_t,
0 as uintptr_t,
0i32 as uintptr_t,
);
}
}

View File

@@ -1,5 +1,6 @@
use crate::dc_dehtml::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
#[derive(Copy, Clone)]
@@ -10,214 +11,234 @@ pub struct dc_simplify_t {
pub is_cut_at_end: libc::c_int,
}
impl dc_simplify_t {
pub fn new() -> Self {
dc_simplify_t {
is_forwarded: 0,
is_cut_at_begin: 0,
is_cut_at_end: 0,
}
pub unsafe fn dc_simplify_new() -> *mut dc_simplify_t {
let simplify: *mut dc_simplify_t;
simplify = calloc(1, ::std::mem::size_of::<dc_simplify_t>()) as *mut dc_simplify_t;
assert!(!simplify.is_null());
simplify
}
pub unsafe fn dc_simplify_unref(simplify: *mut dc_simplify_t) {
if simplify.is_null() {
return;
}
free(simplify as *mut libc::c_void);
}
/// Simplify and normalise text: Remove quotes, signatures, unnecessary
/// lineends etc.
/// The data returned from simplify() must be free()'d when no longer used.
pub unsafe fn simplify(
&mut self,
in_unterminated: *const libc::c_char,
in_bytes: libc::c_int,
is_html: libc::c_int,
is_msgrmsg: libc::c_int,
) -> *mut libc::c_char {
if in_bytes <= 0 {
return "".strdup();
}
/* create a copy of the given buffer */
let mut out: *mut libc::c_char;
let mut temp: *mut libc::c_char;
self.is_forwarded = 0i32;
self.is_cut_at_begin = 0i32;
self.is_cut_at_end = 0i32;
out = strndup(
in_unterminated as *mut libc::c_char,
in_bytes as libc::c_ulong,
);
if out.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
if 0 != is_html {
temp = dc_dehtml(out);
if !temp.is_null() {
free(out as *mut libc::c_void);
out = temp
}
}
dc_remove_cr_chars(out);
temp = self.simplify_plain_text(out, is_msgrmsg);
/* Simplify and normalise text: Remove quotes, signatures, unnecessary
lineends etc.
The data returned from Simplify() must be free()'d when no longer used, private */
pub unsafe fn dc_simplify_simplify(
mut simplify: *mut dc_simplify_t,
in_unterminated: *const libc::c_char,
in_bytes: libc::c_int,
is_html: libc::c_int,
is_msgrmsg: libc::c_int,
) -> *mut libc::c_char {
/* create a copy of the given buffer */
let mut out: *mut libc::c_char;
let mut temp: *mut libc::c_char;
if simplify.is_null() || in_unterminated.is_null() || in_bytes <= 0i32 {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
(*simplify).is_forwarded = 0i32;
(*simplify).is_cut_at_begin = 0i32;
(*simplify).is_cut_at_end = 0i32;
out = strndup(
in_unterminated as *mut libc::c_char,
in_bytes as libc::c_ulong,
);
if out.is_null() {
return dc_strdup(b"\x00" as *const u8 as *const libc::c_char);
}
if 0 != is_html {
temp = dc_dehtml(out);
if !temp.is_null() {
free(out as *mut libc::c_void);
out = temp
}
dc_remove_cr_chars(out);
out
}
dc_remove_cr_chars(out);
temp = dc_simplify_simplify_plain_text(simplify, out, is_msgrmsg);
if !temp.is_null() {
free(out as *mut libc::c_void);
out = temp
}
dc_remove_cr_chars(out);
/**
* Simplify Plain Text
*/
#[allow(non_snake_case)]
unsafe fn simplify_plain_text(
&mut self,
buf_terminated: *const libc::c_char,
is_msgrmsg: libc::c_int,
) -> *mut libc::c_char {
/* This function ...
... removes all text after the line `-- ` (footer mark)
... removes full quotes at the beginning and at the end of the text -
these are all lines starting with the character `>`
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
/* split the given buffer into lines */
let lines = dc_split_into_lines(buf_terminated);
let mut l_first: usize = 0;
let mut l_last = lines.len();
let mut line: *mut libc::c_char;
let mut footer_mark: libc::c_int = 0i32;
for l in l_first..l_last {
line = lines[l];
if strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
{
footer_mark = 1i32
}
if strcmp(line, b"--\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"---\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"----\x00" as *const u8 as *const libc::c_char) == 0i32
{
footer_mark = 1i32;
self.is_cut_at_end = 1i32
}
if 0 != footer_mark {
l_last = l;
/* done */
out
}
/**
* Simplify Plain Text
*/
unsafe fn dc_simplify_simplify_plain_text(
mut simplify: *mut dc_simplify_t,
buf_terminated: *const libc::c_char,
is_msgrmsg: libc::c_int,
) -> *mut libc::c_char {
/* This function ...
... removes all text after the line `-- ` (footer mark)
... removes full quotes at the beginning and at the end of the text -
these are all lines starting with the character `>`
... remove a non-empty line before the removed quote (contains sth. like "On 2.9.2016, Bjoern wrote:" in different formats and lanugages) */
/* split the given buffer into lines */
let lines: *mut carray = dc_split_into_lines(buf_terminated);
let mut l: libc::c_int;
let mut l_first: libc::c_int = 0i32;
/* if l_last is -1, there are no lines */
let mut l_last: libc::c_int =
carray_count(lines).wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
let mut line: *mut libc::c_char;
let mut footer_mark: libc::c_int = 0i32;
l = l_first;
while l <= l_last {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"-- \x00" as *const u8 as *const libc::c_char) == 0i32
{
footer_mark = 1i32
}
if strcmp(line, b"--\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"---\x00" as *const u8 as *const libc::c_char) == 0i32
|| strcmp(line, b"----\x00" as *const u8 as *const libc::c_char) == 0i32
{
footer_mark = 1i32;
(*simplify).is_cut_at_end = 1i32
}
if 0 != footer_mark {
l_last = l - 1i32;
/* done */
break;
} else {
l += 1
}
}
if l_last - l_first + 1i32 >= 3i32 {
let line0: *mut libc::c_char =
carray_get(lines, l_first as libc::c_uint) as *mut libc::c_char;
let line1: *mut libc::c_char =
carray_get(lines, (l_first + 1i32) as libc::c_uint) as *mut libc::c_char;
let line2: *mut libc::c_char =
carray_get(lines, (l_first + 2i32) as libc::c_uint) as *mut libc::c_char;
if strcmp(
line0,
b"---------- Forwarded message ----------\x00" as *const u8 as *const libc::c_char,
) == 0i32
&& strncmp(line1, b"From: \x00" as *const u8 as *const libc::c_char, 6) == 0i32
&& *line2.offset(0isize) as libc::c_int == 0i32
{
(*simplify).is_forwarded = 1i32;
l_first += 3i32
}
}
l = l_first;
while l <= l_last {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if strncmp(line, b"-----\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"_____\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"=====\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"*****\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"~~~~~\x00" as *const u8 as *const libc::c_char, 5) == 0i32
{
l_last = l - 1i32;
(*simplify).is_cut_at_end = 1i32;
/* done */
break;
} else {
l += 1
}
}
if 0 == is_msgrmsg {
let mut l_lastQuotedLine: libc::c_int = -1i32;
l = l_last;
while l >= l_first {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if is_plain_quote(line) {
l_lastQuotedLine = l
} else if !is_empty_line(line) {
break;
}
l -= 1
}
if l_last > l_first + 2 {
let line0: *mut libc::c_char = lines[l_first];
let line1: *mut libc::c_char = lines[l_first + 1];
let line2: *mut libc::c_char = lines[l_first + 2];
if strcmp(
line0,
b"---------- Forwarded message ----------\x00" as *const u8 as *const libc::c_char,
) == 0i32
&& strncmp(line1, b"From: \x00" as *const u8 as *const libc::c_char, 6) == 0i32
&& *line2.offset(0isize) as libc::c_int == 0i32
{
self.is_forwarded = 1i32;
l_first += 3
if l_lastQuotedLine != -1i32 {
l_last = l_lastQuotedLine - 1i32;
(*simplify).is_cut_at_end = 1i32;
if l_last > 0i32 {
if is_empty_line(carray_get(lines, l_last as libc::c_uint) as *mut libc::c_char) {
l_last -= 1
}
}
if l_last > 0i32 {
line = carray_get(lines, l_last as libc::c_uint) as *mut libc::c_char;
if is_quoted_headline(line) {
l_last -= 1
}
}
}
for l in l_first..l_last {
line = lines[l];
if strncmp(line, b"-----\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"_____\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"=====\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"*****\x00" as *const u8 as *const libc::c_char, 5) == 0i32
|| strncmp(line, b"~~~~~\x00" as *const u8 as *const libc::c_char, 5) == 0i32
{
l_last = l;
self.is_cut_at_end = 1i32;
/* done */
break;
}
}
if 0 == is_msgrmsg {
let mut l_lastQuotedLine = None;
for l in (l_first..l_last).rev() {
line = lines[l];
if is_plain_quote(line) {
l_lastQuotedLine = Some(l)
} else if !is_empty_line(line) {
}
if 0 == is_msgrmsg {
let mut l_lastQuotedLine_0: libc::c_int = -1i32;
let mut hasQuotedHeadline: libc::c_int = 0i32;
l = l_first;
while l <= l_last {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if is_plain_quote(line) {
l_lastQuotedLine_0 = l
} else if !is_empty_line(line) {
if is_quoted_headline(line) && 0 == hasQuotedHeadline && l_lastQuotedLine_0 == -1i32
{
hasQuotedHeadline = 1i32
} else {
/* non-quoting line found */
break;
}
}
if l_lastQuotedLine.is_some() {
l_last = l_lastQuotedLine.unwrap();
self.is_cut_at_end = 1i32;
if l_last > 1 {
if is_empty_line(lines[l_last - 1]) {
l_last -= 1
}
}
if l_last > 1 {
line = lines[l_last - 1];
if is_quoted_headline(line) {
l_last -= 1
}
}
}
l += 1
}
if 0 == is_msgrmsg {
let mut l_lastQuotedLine_0 = None;
let mut hasQuotedHeadline = 0;
for l in l_first..l_last {
line = lines[l];
if is_plain_quote(line) {
l_lastQuotedLine_0 = Some(l)
} else if !is_empty_line(line) {
if is_quoted_headline(line)
&& 0 == hasQuotedHeadline
&& l_lastQuotedLine_0.is_none()
{
hasQuotedHeadline = 1i32
} else {
/* non-quoting line found */
break;
}
}
}
if l_lastQuotedLine_0.is_some() {
l_first = l_lastQuotedLine_0.unwrap() + 1;
self.is_cut_at_begin = 1i32
}
if l_lastQuotedLine_0 != -1i32 {
l_first = l_lastQuotedLine_0 + 1i32;
(*simplify).is_cut_at_begin = 1i32
}
/* re-create buffer from the remaining lines */
let mut ret = String::new();
if 0 != self.is_cut_at_begin {
ret += "[...]";
}
/* we write empty lines only in case and non-empty line follows */
let mut pending_linebreaks: libc::c_int = 0i32;
let mut content_lines_added: libc::c_int = 0i32;
for l in l_first..l_last {
line = lines[l];
if is_empty_line(line) {
pending_linebreaks += 1
} else {
if 0 != content_lines_added {
if pending_linebreaks > 2i32 {
pending_linebreaks = 2i32
}
while 0 != pending_linebreaks {
ret += "\n";
pending_linebreaks -= 1
}
}
// the incoming message might contain invalid UTF8
ret += &to_string_lossy(line);
content_lines_added += 1;
pending_linebreaks = 1i32
}
}
if 0 != self.is_cut_at_end && (0 == self.is_cut_at_begin || 0 != content_lines_added) {
ret += " [...]";
}
dc_free_splitted_lines(lines);
ret.strdup()
}
/* re-create buffer from the remaining lines */
let mut ret = String::new();
if 0 != (*simplify).is_cut_at_begin {
ret += "[...]";
}
/* we write empty lines only in case and non-empty line follows */
let mut pending_linebreaks: libc::c_int = 0i32;
let mut content_lines_added: libc::c_int = 0i32;
l = l_first;
while l <= l_last {
line = carray_get(lines, l as libc::c_uint) as *mut libc::c_char;
if is_empty_line(line) {
pending_linebreaks += 1
} else {
if 0 != content_lines_added {
if pending_linebreaks > 2i32 {
pending_linebreaks = 2i32
}
while 0 != pending_linebreaks {
ret += "\n";
pending_linebreaks -= 1
}
}
ret += &to_string(line);
content_lines_added += 1;
pending_linebreaks = 1i32
}
l += 1
}
if 0 != (*simplify).is_cut_at_end
&& (0 == (*simplify).is_cut_at_begin || 0 != content_lines_added)
{
ret += " [...]";
}
dc_free_splitted_lines(lines);
strdup(to_cstring(ret).as_ptr())
}
/**
@@ -240,7 +261,7 @@ unsafe fn is_quoted_headline(buf: *const libc::c_char) -> bool {
/* This function may be called for the line _directly_ before a quote.
The function checks if the line contains sth. like "On 01.02.2016, xy@z wrote:" in various languages.
- Currently, we simply check if the last character is a ':'.
- Checking for the existence of an email address may fail (headlines may show the user's name instead of the address) */
- Checking for the existance of an email address may fail (headlines may show the user's name instead of the address) */
let buf_len: libc::c_int = strlen(buf) as libc::c_int;
if buf_len > 80i32 {
return false;
@@ -268,11 +289,11 @@ mod tests {
#[test]
fn test_simplify_trim() {
unsafe {
let mut simplify = dc_simplify_t::new();
let simplify: *mut dc_simplify_t = dc_simplify_new();
let html: *const libc::c_char =
b"\r\r\nline1<br>\r\n\r\n\r\rline2\n\r\x00" as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
@@ -282,17 +303,18 @@ mod tests {
);
free(plain as *mut libc::c_void);
dc_simplify_unref(simplify);
}
}
#[test]
fn test_simplify_parse_href() {
unsafe {
let mut simplify = dc_simplify_t::new();
let simplify: *mut dc_simplify_t = dc_simplify_new();
let html: *const libc::c_char =
b"<a href=url>text</a\x00" as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
@@ -302,18 +324,19 @@ mod tests {
);
free(plain as *mut libc::c_void);
dc_simplify_unref(simplify);
}
}
#[test]
fn test_simplify_bold_text() {
unsafe {
let mut simplify = dc_simplify_t::new();
let simplify: *mut dc_simplify_t = dc_simplify_new();
let html: *const libc::c_char =
b"<!DOCTYPE name [<!DOCTYPE ...>]><!-- comment -->text <b><?php echo ... ?>bold</b><![CDATA[<>]]>\x00"
as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
assert_eq!(
CStr::from_ptr(plain as *const libc::c_char)
@@ -323,18 +346,19 @@ mod tests {
);
free(plain as *mut libc::c_void);
dc_simplify_unref(simplify);
}
}
#[test]
fn test_simplify_html_encoded() {
unsafe {
let mut simplify = dc_simplify_t::new();
let simplify: *mut dc_simplify_t = dc_simplify_new();
let html: *const libc::c_char =
b"&lt;&gt;&quot;&apos;&amp; &auml;&Auml;&ouml;&Ouml;&uuml;&Uuml;&szlig; foo&AElig;&ccedil;&Ccedil; &diams;&noent;&lrm;&rlm;&zwnj;&zwj;\x00"
as *const u8 as *const libc::c_char;
let plain: *mut libc::c_char =
simplify.simplify(html, strlen(html) as libc::c_int, 1, 0);
dc_simplify_simplify(simplify, html, strlen(html) as libc::c_int, 1, 0);
assert_eq!(
strcmp(plain,
@@ -344,6 +368,7 @@ mod tests {
);
free(plain as *mut libc::c_void);
dc_simplify_unref(simplify);
}
}
}

1605
src/dc_sqlite3.rs Normal file

File diff suppressed because it is too large Load Diff

338
src/dc_stock.rs Normal file
View File

@@ -0,0 +1,338 @@
use crate::constants::Event;
use crate::context::Context;
use crate::dc_contact::*;
use crate::dc_tools::*;
use crate::types::*;
use crate::x::*;
/* Return the string with the given ID by calling DC_EVENT_GET_STRING.
The result must be free()'d! */
pub unsafe fn dc_stock_str(context: &Context, id: libc::c_int) -> *mut libc::c_char {
return get_string(context, id, 0i32);
}
unsafe fn get_string(context: &Context, id: libc::c_int, qty: libc::c_int) -> *mut libc::c_char {
let mut ret: *mut libc::c_char;
ret =
context.call_cb(Event::GET_STRING, id as uintptr_t, qty as uintptr_t) as *mut libc::c_char;
if ret.is_null() {
ret = default_string(id)
}
ret
}
/* Add translated strings that are used by the messager backend.
As the logging functions may use these strings, do not log any
errors from here. */
unsafe fn default_string(id: libc::c_int) -> *mut libc::c_char {
// TODO match on enum values /rtn
match id {
1 => {
return dc_strdup(b"No messages.\x00" as *const u8 as
*const libc::c_char)
}
2 => {
return dc_strdup(b"Me\x00" as *const u8 as *const libc::c_char)
}
3 => {
return dc_strdup(b"Draft\x00" as *const u8 as *const libc::c_char)
}
4 => {
return dc_strdup(b"%1$s member(s)\x00" as *const u8 as
*const libc::c_char)
}
6 => {
return dc_strdup(b"%1$s contact(s)\x00" as *const u8 as
*const libc::c_char)
}
7 => {
return dc_strdup(b"Voice message\x00" as *const u8 as
*const libc::c_char)
}
8 => {
return dc_strdup(b"Contact requests\x00" as *const u8 as
*const libc::c_char)
}
9 => {
return dc_strdup(b"Image\x00" as *const u8 as *const libc::c_char)
}
23 => {
return dc_strdup(b"GIF\x00" as *const u8 as *const libc::c_char)
}
10 => {
return dc_strdup(b"Video\x00" as *const u8 as *const libc::c_char)
}
11 => {
return dc_strdup(b"Audio\x00" as *const u8 as *const libc::c_char)
}
12 => {
return dc_strdup(b"File\x00" as *const u8 as *const libc::c_char)
}
66 => {
return dc_strdup(b"Location\x00" as *const u8 as
*const libc::c_char)
}
24 => {
return dc_strdup(b"Encrypted message\x00" as *const u8 as
*const libc::c_char)
}
13 => {
return dc_strdup(b"Sent with my Delta Chat Messenger: https://delta.chat\x00"
as *const u8 as *const libc::c_char)
}
14 => {
return dc_strdup(b"Hello, I\'ve just created the group \"%1$s\" for us.\x00"
as *const u8 as *const libc::c_char)
}
15 => {
return dc_strdup(b"Group name changed from \"%1$s\" to \"%2$s\".\x00"
as *const u8 as *const libc::c_char)
}
16 => {
return dc_strdup(b"Group image changed.\x00" as *const u8 as
*const libc::c_char)
}
17 => {
return dc_strdup(b"Member %1$s added.\x00" as *const u8 as
*const libc::c_char)
}
18 => {
return dc_strdup(b"Member %1$s removed.\x00" as *const u8 as
*const libc::c_char)
}
19 => {
return dc_strdup(b"Group left.\x00" as *const u8 as
*const libc::c_char)
}
64 => {
return dc_strdup(b"Location streaming enabled.\x00" as *const u8
as *const libc::c_char)
}
65 => {
return dc_strdup(b"Location streaming disabled.\x00" as *const u8
as *const libc::c_char)
}
62 => {
return dc_strdup(b"%1$s by %2$s.\x00" as *const u8 as
*const libc::c_char)
}
63 => {
return dc_strdup(b"%1$s by me.\x00" as *const u8 as
*const libc::c_char)
}
25 => {
return dc_strdup(b"End-to-end encryption available.\x00" as
*const u8 as *const libc::c_char)
}
27 => {
return dc_strdup(b"Transport-encryption.\x00" as *const u8 as
*const libc::c_char)
}
28 => {
return dc_strdup(b"No encryption.\x00" as *const u8 as
*const libc::c_char)
}
30 => {
return dc_strdup(b"Fingerprints\x00" as *const u8 as
*const libc::c_char)
}
31 => {
return dc_strdup(b"Return receipt\x00" as *const u8 as
*const libc::c_char)
}
32 => {
return dc_strdup(b"This is a return receipt for the message \"%1$s\".\x00"
as *const u8 as *const libc::c_char)
}
33 => {
return dc_strdup(b"Group image deleted.\x00" as *const u8 as
*const libc::c_char)
}
34 => {
return dc_strdup(b"End-to-end encryption preferred.\x00" as
*const u8 as *const libc::c_char)
}
35 => {
return dc_strdup(b"%1$s verified.\x00" as *const u8 as
*const libc::c_char)
}
36 => {
return dc_strdup(b"Cannot verifiy %1$s\x00" as *const u8 as
*const libc::c_char)
}
37 => {
return dc_strdup(b"Changed setup for %1$s\x00" as *const u8 as
*const libc::c_char)
}
40 => {
return dc_strdup(b"Archived chats\x00" as *const u8 as
*const libc::c_char)
}
41 => {
return dc_strdup(b"Starred messages\x00" as *const u8 as
*const libc::c_char)
}
42 => {
return dc_strdup(b"Autocrypt Setup Message\x00" as *const u8 as
*const libc::c_char)
}
43 => {
return dc_strdup(b"This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device.\x00"
as *const u8 as *const libc::c_char)
}
50 => {
return dc_strdup(b"Messages I sent to myself\x00" as *const u8 as
*const libc::c_char)
}
29 => {
return dc_strdup(b"This message was encrypted for another setup.\x00"
as *const u8 as *const libc::c_char)
}
60 => {
return dc_strdup(b"Cannot login as %1$s.\x00" as *const u8 as
*const libc::c_char)
}
61 => {
return dc_strdup(b"Response from %1$s: %2$s\x00" as *const u8 as
*const libc::c_char)
}
_ => { }
}
dc_strdup(b"ErrStr\x00" as *const u8 as *const libc::c_char)
}
/* Replaces the first `%1$s` in the given String-ID by the given value.
The result must be free()'d! */
pub unsafe fn dc_stock_str_repl_string(
context: &Context,
id: libc::c_int,
to_insert: *const libc::c_char,
) -> *mut libc::c_char {
let mut ret: *mut libc::c_char = get_string(context, id, 0i32);
dc_str_replace(
&mut ret,
b"%1$s\x00" as *const u8 as *const libc::c_char,
to_insert,
);
dc_str_replace(
&mut ret,
b"%1$d\x00" as *const u8 as *const libc::c_char,
to_insert,
);
ret
}
pub unsafe fn dc_stock_str_repl_int(
context: &Context,
id: libc::c_int,
to_insert_int: libc::c_int,
) -> *mut libc::c_char {
let mut ret: *mut libc::c_char = get_string(context, id, to_insert_int);
let to_insert_str: *mut libc::c_char = dc_mprintf(
b"%i\x00" as *const u8 as *const libc::c_char,
to_insert_int as libc::c_int,
);
dc_str_replace(
&mut ret,
b"%1$s\x00" as *const u8 as *const libc::c_char,
to_insert_str,
);
dc_str_replace(
&mut ret,
b"%1$d\x00" as *const u8 as *const libc::c_char,
to_insert_str,
);
free(to_insert_str as *mut libc::c_void);
ret
}
/* Replaces the first `%1$s` and `%2$s` in the given String-ID by the two given strings.
The result must be free()'d! */
pub unsafe fn dc_stock_str_repl_string2(
context: &Context,
id: libc::c_int,
to_insert: *const libc::c_char,
to_insert2: *const libc::c_char,
) -> *mut libc::c_char {
let mut ret: *mut libc::c_char = get_string(context, id, 0i32);
dc_str_replace(
&mut ret,
b"%1$s\x00" as *const u8 as *const libc::c_char,
to_insert,
);
dc_str_replace(
&mut ret,
b"%1$d\x00" as *const u8 as *const libc::c_char,
to_insert,
);
dc_str_replace(
&mut ret,
b"%2$s\x00" as *const u8 as *const libc::c_char,
to_insert2,
);
dc_str_replace(
&mut ret,
b"%2$d\x00" as *const u8 as *const libc::c_char,
to_insert2,
);
ret
}
/* Misc. */
pub unsafe fn dc_stock_system_msg(
context: &Context,
str_id: libc::c_int,
mut param1: *const libc::c_char,
param2: *const libc::c_char,
from_id: uint32_t,
) -> *mut libc::c_char {
let ret: *mut libc::c_char;
let mut mod_contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
let mut mod_displayname: *mut libc::c_char = 0 as *mut libc::c_char;
let mut from_contact: *mut dc_contact_t = 0 as *mut dc_contact_t;
let mut from_displayname: *mut libc::c_char = 0 as *mut libc::c_char;
if str_id == 17i32 || str_id == 18i32 {
let mod_contact_id: uint32_t = dc_lookup_contact_id_by_addr(context, param1);
if mod_contact_id != 0i32 as libc::c_uint {
mod_contact = dc_get_contact(context, mod_contact_id);
mod_displayname = dc_contact_get_name_n_addr(mod_contact);
param1 = mod_displayname
}
}
let action: *mut libc::c_char = dc_stock_str_repl_string2(context, str_id, param1, param2);
if 0 != from_id {
if 0 != strlen(action)
&& *action.offset(strlen(action).wrapping_sub(1) as isize) as libc::c_int == '.' as i32
{
*action.offset(strlen(action).wrapping_sub(1) as isize) = 0i32 as libc::c_char
}
from_contact = dc_get_contact(context, from_id);
from_displayname = dc_contact_get_display_name(from_contact);
ret = dc_stock_str_repl_string2(
context,
if from_id == 1i32 as libc::c_uint {
63i32
} else {
62i32
},
action,
from_displayname,
)
} else {
ret = dc_strdup(action)
}
free(action as *mut libc::c_void);
free(from_displayname as *mut libc::c_void);
free(mod_displayname as *mut libc::c_void);
dc_contact_unref(from_contact);
dc_contact_unref(mod_contact);
ret
}

View File

@@ -64,11 +64,11 @@ pub unsafe fn dc_urlencode(to_encode: *const libc::c_char) -> *mut libc::c_char
* URL encoding and decoding, RFC 3986
******************************************************************************/
unsafe fn int_2_uppercase_hex(code: libc::c_char) -> libc::c_char {
static mut HEX: [libc::c_char; 17] = [
static mut hex: [libc::c_char; 17] = [
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 0,
];
HEX[(code as libc::c_int & 15i32) as usize]
hex[(code as libc::c_int & 15i32) as usize]
}
pub unsafe fn dc_urldecode(to_decode: *const libc::c_char) -> *mut libc::c_char {
@@ -120,90 +120,97 @@ fn hex_2_int(ch: libc::c_char) -> libc::c_char {
}
pub unsafe fn dc_encode_header_words(to_encode: *const libc::c_char) -> *mut libc::c_char {
let mut ok_to_continue = true;
let mut current_block: u64;
let mut ret_str: *mut libc::c_char = 0 as *mut libc::c_char;
let mut cur: *const libc::c_char = to_encode;
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() {
ok_to_continue = false;
current_block = 8550051112593613029;
} else {
current_block = 4644295000439058019;
}
loop {
if !ok_to_continue {
if !mmapstr.is_null() {
mmap_string_free(mmapstr);
}
break;
} else {
if *cur as libc::c_int != '\u{0}' as i32 {
let begin: *const libc::c_char;
let mut end: *const libc::c_char;
let mut do_quote: bool;
let mut quote_words: libc::c_int;
begin = cur;
end = begin;
quote_words = 0i32;
do_quote = true;
while *cur as libc::c_int != '\u{0}' as i32 {
get_word(cur, &mut cur, &mut do_quote);
if !do_quote {
break;
}
quote_words = 1i32;
end = cur;
if *cur as libc::c_int != '\u{0}' as i32 {
cur = cur.offset(1isize)
}
match current_block {
8550051112593613029 => {
if !mmapstr.is_null() {
mmap_string_free(mmapstr);
}
if 0 != quote_words {
if !quote_word(
b"utf-8\x00" as *const u8 as *const libc::c_char,
break;
}
_ => {
if *cur as libc::c_int != '\u{0}' as i32 {
let begin: *const libc::c_char;
let mut end: *const libc::c_char;
let mut do_quote: bool;
let mut quote_words: libc::c_int;
begin = cur;
end = begin;
quote_words = 0i32;
do_quote = true;
while *cur as libc::c_int != '\u{0}' as i32 {
get_word(cur, &mut cur, &mut do_quote);
if !do_quote {
break;
}
quote_words = 1i32;
end = cur;
if *cur as libc::c_int != '\u{0}' as i32 {
cur = cur.offset(1isize)
}
}
if 0 != quote_words {
if !quote_word(
b"utf-8\x00" as *const u8 as *const libc::c_char,
mmapstr,
begin,
end.wrapping_offset_from(begin) as size_t,
) {
current_block = 8550051112593613029;
continue;
}
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
if mmap_string_append_c(mmapstr, *end).is_null() {
current_block = 8550051112593613029;
continue;
}
end = end.offset(1isize)
}
if *end as libc::c_int != '\u{0}' as i32 {
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as size_t,
)
.is_null()
{
current_block = 8550051112593613029;
continue;
}
}
} else if mmap_string_append_len(
mmapstr,
begin,
end.wrapping_offset_from(begin) as size_t,
) {
ok_to_continue = false;
cur.wrapping_offset_from(begin) as size_t,
)
.is_null()
{
current_block = 8550051112593613029;
continue;
}
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
if mmap_string_append_c(mmapstr, *end).is_null() {
ok_to_continue = false;
continue;
}
end = end.offset(1isize)
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
current_block = 4644295000439058019;
continue;
}
if *end as libc::c_int != '\u{0}' as i32 {
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as size_t,
)
.is_null()
{
ok_to_continue = false;
continue;
}
if mmap_string_append_c(mmapstr, *cur).is_null() {
current_block = 8550051112593613029;
continue;
}
} else if mmap_string_append_len(
mmapstr,
begin,
cur.wrapping_offset_from(begin) as size_t,
)
.is_null()
{
ok_to_continue = false;
continue;
cur = cur.offset(1isize);
current_block = 4644295000439058019;
} else {
ret_str = strdup((*mmapstr).str_0);
current_block = 8550051112593613029;
}
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
continue;
}
if mmap_string_append_c(mmapstr, *cur).is_null() {
ok_to_continue = false;
continue;
}
cur = cur.offset(1isize);
} else {
ret_str = strdup((*mmapstr).str_0);
ok_to_continue = false;
}
}
}
@@ -371,7 +378,7 @@ pub unsafe fn dc_encode_modified_utf7(
if 0 != bitstogo {
let fresh8 = dst;
dst = dst.offset(1);
*fresh8 = BASE64CHARS
*fresh8 = base64chars
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
}
let fresh9 = dst;
@@ -442,7 +449,7 @@ pub unsafe fn dc_encode_modified_utf7(
bitstogo = bitstogo.wrapping_sub(6i32 as libc::c_uint);
let fresh14 = dst;
dst = dst.offset(1);
*fresh14 = BASE64CHARS[(if 0 != bitstogo {
*fresh14 = base64chars[(if 0 != bitstogo {
bitbuf >> bitstogo
} else {
bitbuf
@@ -458,7 +465,7 @@ pub unsafe fn dc_encode_modified_utf7(
if 0 != bitstogo {
let fresh15 = dst;
dst = dst.offset(1);
*fresh15 = BASE64CHARS
*fresh15 = base64chars
[(bitbuf << (6i32 as libc::c_uint).wrapping_sub(bitstogo) & 0x3f) as usize]
}
let fresh16 = dst;
@@ -475,7 +482,7 @@ pub unsafe fn dc_encode_modified_utf7(
******************************************************************************/
// UTF7 modified base64 alphabet
static mut BASE64CHARS: [libc::c_char; 65] = [
static mut base64chars: [libc::c_char; 65] = [
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
115, 116, 117, 118, 119, 120, 121, 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 44, 0,
@@ -510,7 +517,7 @@ pub unsafe fn dc_decode_modified_utf7(
);
i = 0i32 as libc::c_uint;
while (i as libc::c_ulong) < ::std::mem::size_of::<[libc::c_char; 65]>() as libc::c_ulong {
base64[BASE64CHARS[i as usize] as libc::c_uint as usize] = i as libc::c_uchar;
base64[base64chars[i as usize] as libc::c_uint as usize] = i as libc::c_uchar;
i = i.wrapping_add(1)
}
while *src as libc::c_int != '\u{0}' as i32 {
@@ -682,9 +689,10 @@ pub unsafe fn dc_decode_ext_header(to_decode: *const libc::c_char) -> *mut libc:
std::slice::from_raw_parts(decoded as *const u8, strlen(decoded));
let (res, _, _) = encoding.decode(data);
free(decoded as *mut _);
let r = std::ffi::CString::new(res.as_bytes()).unwrap();
decoded = dc_strdup(r.as_ptr());
free(decoded as *mut libc::c_void);
let res_c = CString::new(res.as_bytes()).unwrap();
decoded = strdup(res_c.as_ptr());
}
}
}
@@ -703,7 +711,7 @@ unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
assert!(!cur.is_null());
let bytes = std::slice::from_raw_parts(cur as *const _, strlen(cur));
let raw = CString::yolo(format!("={}", &hex::encode_upper(bytes)[..2]));
let raw = to_cstring(format!("={}", &hex::encode_upper(bytes)[..2]));
libc::memcpy(target as *mut _, raw.as_ptr() as *const _, 4);
}

View File

@@ -1,65 +1,76 @@
use crate::context::Context;
use crate::dc_sqlite3::*;
use crate::dc_tools::*;
use crate::sql;
use crate::types::*;
// Token namespaces
#[allow(non_camel_case_types)]
pub type dc_tokennamespc_t = usize;
pub type dc_tokennamespc_t = libc::c_uint;
pub const DC_TOKEN_AUTH: dc_tokennamespc_t = 110;
pub const DC_TOKEN_INVITENUMBER: dc_tokennamespc_t = 100;
// Functions to read/write token from/to the database. A token is any string associated with a key.
pub fn dc_token_save(
pub unsafe fn dc_token_save(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
foreign_id: uint32_t,
token: *const libc::c_char,
) -> bool {
if token.is_null() {
return false;
) {
let mut stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
if !token.is_null() {
// foreign_id may be 0
stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);\x00"
as *const u8 as *const libc::c_char,
);
sqlite3_bind_int(stmt, 1i32, namespc as libc::c_int);
sqlite3_bind_int(stmt, 2i32, foreign_id as libc::c_int);
sqlite3_bind_text(stmt, 3i32, token, -1i32, None);
sqlite3_bind_int64(stmt, 4i32, time() as sqlite3_int64);
sqlite3_step(stmt);
}
// foreign_id may be 0
sql::execute(
sqlite3_finalize(stmt);
}
pub unsafe fn dc_token_lookup(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: uint32_t,
) -> *mut libc::c_char {
let token: *mut libc::c_char;
let stmt: *mut sqlite3_stmt;
stmt = dc_sqlite3_prepare(
context,
&context.sql,
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespc as i32, foreign_id as i32, as_str(token), time()],
)
.is_ok()
b"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;\x00" as *const u8
as *const libc::c_char,
);
sqlite3_bind_int(stmt, 1i32, namespc as libc::c_int);
sqlite3_bind_int(stmt, 2i32, foreign_id as libc::c_int);
sqlite3_step(stmt);
token = dc_strdup_keep_null(sqlite3_column_text(stmt, 0i32) as *mut libc::c_char);
sqlite3_finalize(stmt);
token
}
pub fn dc_token_lookup(
context: &Context,
namespc: dc_tokennamespc_t,
foreign_id: u32,
) -> *mut libc::c_char {
context
.sql
.query_row_col::<_, String>(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespc as i32, foreign_id as i32],
0,
)
.map(|s| unsafe { s.strdup() })
.unwrap_or_else(|| std::ptr::null_mut())
}
pub fn dc_token_exists(
pub unsafe fn dc_token_exists(
context: &Context,
namespc: dc_tokennamespc_t,
token: *const libc::c_char,
) -> bool {
if token.is_null() {
return false;
) -> libc::c_int {
let mut exists: libc::c_int = 0i32;
let mut stmt: *mut sqlite3_stmt = 0 as *mut sqlite3_stmt;
if !token.is_null() {
stmt = dc_sqlite3_prepare(
context,
&context.sql,
b"SELECT id FROM tokens WHERE namespc=? AND token=?;\x00" as *const u8
as *const libc::c_char,
);
sqlite3_bind_int(stmt, 1i32, namespc as libc::c_int);
sqlite3_bind_text(stmt, 2i32, token, -1i32, None);
exists = (sqlite3_step(stmt) != 0i32) as libc::c_int
}
context
.sql
.exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
params![namespc as i32, as_str(token)],
)
.unwrap_or_default()
sqlite3_finalize(stmt);
return exists;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,111 +0,0 @@
use failure::Fail;
#[derive(Debug, Fail)]
pub enum Error {
#[fail(display = "Sqlite Error: {:?}", _0)]
Sql(rusqlite::Error),
#[fail(display = "Sqlite Connection Pool Error: {:?}", _0)]
ConnectionPool(r2d2::Error),
#[fail(display = "{:?}", _0)]
Failure(failure::Error),
#[fail(display = "Sqlite: Connection closed")]
SqlNoConnection,
#[fail(display = "Sqlite: Already open")]
SqlAlreadyOpen,
#[fail(display = "Sqlite: Failed to open")]
SqlFailedToOpen,
#[fail(display = "{:?}", _0)]
Io(std::io::Error),
#[fail(display = "{:?}", _0)]
Message(String),
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<rusqlite::Error> for Error {
fn from(err: rusqlite::Error) -> Error {
Error::Sql(err)
}
}
impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error {
Error::Failure(err)
}
}
impl From<r2d2::Error> for Error {
fn from(err: r2d2::Error) -> Error {
Error::ConnectionPool(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err)
}
}
#[macro_export]
macro_rules! bail {
($e:expr) => {
return Err($crate::error::Error::Message($e.to_string()));
};
($fmt:expr, $($arg:tt)+) => {
return Err($crate::error::Error::Message(format!($fmt, $($arg)+)));
};
}
#[macro_export]
macro_rules! format_err {
($e:expr) => {
$crate::error::Error::Message($e.to_string());
};
($fmt:expr, $($arg:tt)+) => {
$crate::error::Error::Message(format!($fmt, $($arg)+));
};
}
#[macro_export(local_inner_macros)]
macro_rules! ensure {
($cond:expr, $e:expr) => {
if !($cond) {
bail!($e);
}
};
($cond:expr, $fmt:expr, $($arg:tt)+) => {
if !($cond) {
bail!($fmt, $($arg)+);
}
};
}
#[macro_export]
macro_rules! ensure_eq {
($left:expr, $right:expr) => ({
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
bail!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`"#, left_val, right_val)
}
}
}
});
($left:expr, $right:expr,) => ({
ensure_eq!($left, $right)
});
($left:expr, $right:expr, $($arg:tt)+) => ({
match (&($left), &($right)) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
bail!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`: {}"#, left_val, right_val,
format_args!($($arg)+))
}
}
}
});
}

View File

@@ -6,7 +6,8 @@ use std::time::{Duration, SystemTime};
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::CStringExt;
use crate::dc_sqlite3::*;
use crate::dc_tools::{as_str, to_string};
use crate::oauth2::dc_get_oauth2_access_token;
use crate::types::*;
@@ -471,9 +472,24 @@ impl Imap {
fn unsetup_handle(&self, context: &Context) {
info!(context, 0, "IMAP unsetup_handle starts");
// XXX the next line currently can block even if all threads
// terminated already
let session = self.session.lock().unwrap().take();
info!(
context,
0, "IMAP unsetup_handle step 1 (closing down stream)."
0, "IMAP unsetup_handle step1 (acquired session.lock)"
);
if session.is_some() {
match session.unwrap().close() {
Ok(_) => {}
Err(err) => {
eprintln!("failed to close connection: {:?}", err);
}
}
}
info!(
context,
0, "IMAP unsetup_handle step 2 (closing down stream)."
);
let stream = self.stream.write().unwrap().take();
if stream.is_some() {
@@ -484,19 +500,6 @@ impl Imap {
}
}
}
info!(
context,
0, "IMAP unsetup_handle step 2 (acquiring session.lock)"
);
let session = self.session.lock().unwrap().take();
if session.is_some() {
match session.unwrap().close() {
Ok(_) => {}
Err(err) => {
eprintln!("failed to close connection: {:?}", err);
}
}
}
info!(context, 0, "IMAP unsetup_handle step 3 (clearing config).");
self.config.write().unwrap().selected_folder = None;
@@ -519,8 +522,12 @@ impl Imap {
cfg.watch_folder = None;
}
pub fn connect(&self, context: &Context, lp: &dc_loginparam_t) -> bool {
if lp.mail_server.is_empty() || lp.mail_user.is_empty() || lp.mail_pw.is_empty() {
pub fn connect(&self, context: &Context, lp: *const dc_loginparam_t) -> bool {
if lp.is_null() {
return false;
}
let lp = unsafe { *lp };
if lp.mail_server.is_null() || lp.mail_user.is_null() || lp.mail_pw.is_null() {
return false;
}
@@ -529,23 +536,23 @@ impl Imap {
}
{
let addr = &lp.addr;
let imap_server = &lp.mail_server;
let addr = as_str(lp.addr);
let imap_server = as_str(lp.mail_server);
let imap_port = lp.mail_port as u16;
let imap_user = &lp.mail_user;
let imap_pw = &lp.mail_pw;
let imap_user = as_str(lp.mail_user);
let imap_pw = as_str(lp.mail_pw);
let server_flags = lp.server_flags as usize;
let mut config = self.config.write().unwrap();
config.addr = addr.to_string();
config.imap_server = imap_server.to_string();
config.addr = addr.into();
config.imap_server = imap_server.into();
config.imap_port = imap_port.into();
config.imap_user = imap_user.to_string();
config.imap_pw = imap_pw.to_string();
config.imap_user = imap_user.into();
config.imap_pw = imap_pw.into();
config.server_flags = server_flags;
}
if !self.setup_handle_if_needed(context) {
if self.setup_handle_if_needed(context) {
self.free_connect_params();
return false;
}
@@ -556,24 +563,32 @@ impl Imap {
Some(ref mut session) => {
if let Ok(caps) = session.capabilities() {
if !context.sql.is_open() {
warn!(context, 0, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,);
warn!(
context,
0,
"IMAP-LOGIN as {} ok but ABORTING",
as_str(lp.mail_user),
);
teardown = true;
} else {
let can_idle = caps.has("IDLE");
let has_xlist = caps.has("XLIST");
let caps_list = caps.iter().fold(String::new(), |mut s, c| {
s += " ";
s += c;
s
});
log_event!(
context,
Event::IMAP_CONNECTED,
0,
"IMAP-LOGIN as {}, capabilities: {}",
lp.mail_user,
as_str(lp.mail_user),
caps_list,
);
self.config.write().unwrap().can_idle = can_idle;
self.config.write().unwrap().has_xlist = has_xlist;
*self.connected.lock().unwrap() = true;
@@ -604,8 +619,8 @@ impl Imap {
}
}
pub fn set_watch_folder(&self, watch_folder: String) {
self.config.write().unwrap().watch_folder = Some(watch_folder);
pub fn set_watch_folder(&self, watch_folder: *const libc::c_char) {
self.config.write().unwrap().watch_folder = Some(to_string(watch_folder));
}
pub fn fetch(&self, context: &Context) -> libc::c_int {
@@ -705,16 +720,24 @@ 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) {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
let val1 = unsafe {
(self.get_config)(
context,
CString::new(key).unwrap().as_ptr(),
0 as *const libc::c_char,
)
} else {
(0, 0)
};
if val1.is_null() {
return (0, 0);
}
let entry = as_str(val1);
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':');
(
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
parts.next().unwrap().parse().unwrap_or_else(|_| 0),
)
}
fn fetch_from_single_folder<S: AsRef<str>>(&self, context: &Context, folder: S) -> usize {
@@ -842,9 +865,10 @@ impl Imap {
.message_id
.expect("missing message id");
let message_id_c = CString::new(message_id).unwrap();
let folder_c = CString::new(folder.as_ref().to_owned()).unwrap();
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)
(self.precheck_imf)(context, message_id_c.as_ptr(), folder_c.as_ptr(), cur_uid)
} {
// check passed, go fetch the rest
if self.fetch_single_msg(context, &folder, cur_uid) == 0 {
@@ -912,7 +936,13 @@ impl Imap {
let key = format!("imap.mailbox.{}", folder.as_ref());
let val = format!("{}:{}", uidvalidity, lastseenuid);
(self.set_config)(context, &key, Some(&val));
unsafe {
(self.set_config)(
context,
CString::new(key).unwrap().as_ptr(),
CString::new(val).unwrap().as_ptr(),
)
};
}
fn fetch_single_msg<S: AsRef<str>>(
@@ -990,13 +1020,13 @@ impl Imap {
let flags = if is_seen { DC_IMAP_SEEN } else { 0 };
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap();
unsafe {
let folder_c = CString::new(folder.as_ref().to_owned()).unwrap();
(self.receive_imf)(
context,
body.as_ptr() as *const libc::c_char,
body.len(),
folder.as_ref(),
msg.body().unwrap().as_ptr() as *const libc::c_char,
msg.body().unwrap().len(),
folder_c.as_ptr(),
server_uid,
flags as u32,
);
@@ -1030,15 +1060,14 @@ impl Imap {
let (sender, receiver) = std::sync::mpsc::channel();
let v = self.watch.clone();
info!(context, 0, "IMAP-IDLE SPAWNING");
warn!(context, 0, "IMAP-IDLE SPAWNING");
std::thread::spawn(move || {
let &(ref lock, ref cvar) = &*v;
if let Some(ref mut session) = &mut *session.lock().unwrap() {
let mut idle = match session.idle() {
Ok(idle) => idle,
Err(err) => {
eprintln!("failed to setup idle: {:?}", err);
return;
panic!("failed to setup idle: {:?}", err);
}
};
@@ -1604,25 +1633,29 @@ impl Imap {
}
}
context
.sql
.set_config_int(context, "folders_configured", 3)
.ok();
if let Some(ref mvbox_folder) = mvbox_folder {
context
.sql
.set_config(context, "configured_mvbox_folder", Some(mvbox_folder))
.ok();
}
if let Some(ref sentbox_folder) = sentbox_folder {
context
.sql
.set_config(
unsafe {
dc_sqlite3_set_config_int(
context,
&context.sql,
b"folders_configured\x00" as *const u8 as *const libc::c_char,
3,
);
if let Some(ref mvbox_folder) = mvbox_folder {
dc_sqlite3_set_config(
context,
"configured_sentbox_folder",
Some(sentbox_folder.name()),
)
.ok();
&context.sql,
b"configured_mvbox_folder\x00" as *const u8 as *const libc::c_char,
CString::new(mvbox_folder.clone()).unwrap().as_ptr(),
);
}
if let Some(ref sentbox_folder) = sentbox_folder {
dc_sqlite3_set_config(
context,
&context.sql,
b"configured_sentbox_folder\x00" as *const u8 as *const libc::c_char,
CString::new(sentbox_folder.name()).unwrap().as_ptr(),
);
}
}
}

View File

@@ -10,8 +10,9 @@ use pgp::types::{KeyTrait, SecretKeyTrait};
use crate::constants::*;
use crate::context::Context;
use crate::dc_sqlite3::*;
use crate::dc_tools::*;
use crate::sql::{self, Sql};
use crate::types::*;
use crate::x::*;
#[derive(Debug, PartialEq, Eq, Clone)]
@@ -89,9 +90,6 @@ impl Key {
}
pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Option<Self> {
if 0 == bytes.len() {
return None;
}
let res: Result<Key, _> = match key_type {
KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes)).map(Into::into),
KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes)).map(Into::into),
@@ -115,6 +113,19 @@ impl Key {
Self::from_slice(bytes, key_type)
}
pub fn from_stmt(
stmt: *mut sqlite3_stmt,
index: libc::c_int,
key_type: KeyType,
) -> Option<Self> {
assert!(!stmt.is_null(), "missing statement");
let data = unsafe { sqlite3_column_blob(stmt, index) as *const u8 };
let len = unsafe { sqlite3_column_bytes(stmt, index) };
Self::from_binary(data, len, key_type)
}
pub fn from_armored_string(
data: &str,
key_type: KeyType,
@@ -147,32 +158,61 @@ impl Key {
pub fn from_self_public(
context: &Context,
self_addr: impl AsRef<str>,
sql: &Sql,
self_addr: *const libc::c_char,
sql: &SQLite,
) -> Option<Self> {
let addr = self_addr.as_ref();
if self_addr.is_null() {
return None;
}
sql.query_row_col(
context,
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
&[addr],
0,
)
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
let stmt = unsafe {
dc_sqlite3_prepare(
context,
sql,
b"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;\x00" as *const u8
as *const libc::c_char,
)
};
unsafe { sqlite3_bind_text(stmt, 1, self_addr, -1, None) };
let key = if unsafe { sqlite3_step(stmt) } == 100 {
Self::from_stmt(stmt, 0, KeyType::Public)
} else {
None
};
unsafe { sqlite3_finalize(stmt) };
key
}
pub fn from_self_private(
context: &Context,
self_addr: impl AsRef<str>,
sql: &Sql,
self_addr: *const libc::c_char,
sql: &SQLite,
) -> Option<Self> {
sql.query_row_col(
context,
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
&[self_addr.as_ref()],
0,
)
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
if self_addr.is_null() {
return None;
}
let stmt = unsafe {
dc_sqlite3_prepare(
context,
sql,
b"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;\x00" as *const u8
as *const libc::c_char,
)
};
unsafe { sqlite3_bind_text(stmt, 1, self_addr, -1, None) };
let key = if unsafe { sqlite3_step(stmt) } == 100 {
Self::from_stmt(stmt, 0, KeyType::Private)
} else {
None
};
unsafe { sqlite3_finalize(stmt) };
key
}
pub fn to_bytes(&self) -> Vec<u8> {
@@ -216,16 +256,22 @@ impl Key {
}
}
/// Each header line must be terminated by `\r\n`
pub fn to_asc(&self, header: Option<(&str, &str)>) -> String {
/// Each header line must be terminated by `\r\n`, the result must be freed.
pub fn to_asc_c(&self, header: Option<(&str, &str)>) -> *mut libc::c_char {
let headers = header.map(|(key, value)| {
let mut m = BTreeMap::new();
m.insert(key.to_string(), value.to_string());
m
});
self.to_armored_string(headers.as_ref())
.expect("failed to serialize key")
let buf = self
.to_armored_string(headers.as_ref())
.expect("failed to serialize key");
let buf_c = CString::new(buf).unwrap();
// need to use strdup to allocate the result with malloc
// so it can be `free`d later.
unsafe { strdup(buf_c.as_ptr()) }
}
pub fn write_asc_to_file(&self, file: *const libc::c_char, context: &Context) -> bool {
@@ -233,16 +279,15 @@ impl Key {
return false;
}
let file_content = self.to_asc(None);
let file_content_c = CString::new(file_content).unwrap();
let file_content = self.to_asc_c(None);
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(),
file_content as *const libc::c_void,
strlen(file_content),
)
} {
error!(context, 0, "Cannot write key to {}", to_string(file));
@@ -251,6 +296,8 @@ impl Key {
true
};
unsafe { free(file_content as *mut libc::c_void) };
success
}
@@ -293,16 +340,57 @@ pub fn dc_key_save_self_keypair(
context: &Context,
public_key: &Key,
private_key: &Key,
addr: impl AsRef<str>,
addr: *const libc::c_char,
is_default: libc::c_int,
sql: &Sql,
sql: &SQLite,
) -> bool {
sql::execute(
if addr.is_null() {
return false;
}
let stmt = unsafe {
dc_sqlite3_prepare(
context,
sql,
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);",
params![addr.as_ref(), is_default, public_key.to_bytes(), private_key.to_bytes(), time()],
).is_ok()
b"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);\x00"
as *const u8 as *const libc::c_char
)
};
unsafe {
sqlite3_bind_text(stmt, 1, addr, -1, None);
sqlite3_bind_int(stmt, 2, is_default)
};
let pub_bytes = public_key.to_bytes();
let sec_bytes = private_key.to_bytes();
unsafe {
sqlite3_bind_blob(
stmt,
3,
pub_bytes.as_ptr() as *const _,
pub_bytes.len() as libc::c_int,
None,
)
};
unsafe {
sqlite3_bind_blob(
stmt,
4,
sec_bytes.as_ptr() as *const _,
sec_bytes.len() as libc::c_int,
None,
)
};
unsafe { sqlite3_bind_int64(stmt, 5, time() as sqlite3_int64) };
let success = if unsafe { sqlite3_step(stmt) } == 101 {
true
} else {
false
};
unsafe { sqlite3_finalize(stmt) };
success
}
/// Make a fingerprint human-readable, in hex format.
@@ -438,7 +526,8 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[test]
#[ignore] // is too expensive
fn test_from_slice_roundtrip() {
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
let (public_key, private_key) =
crate::pgp::dc_pgp_create_keypair(CString::new("hello").unwrap().as_ptr()).unwrap();
let binary = public_key.to_bytes();
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
@@ -452,7 +541,8 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[test]
#[ignore] // is too expensive
fn test_ascii_roundtrip() {
let (public_key, private_key) = crate::pgp::dc_pgp_create_keypair("hello").unwrap();
let (public_key, private_key) =
crate::pgp::dc_pgp_create_keypair(CString::new("hello").unwrap().as_ptr()).unwrap();
let s = public_key.to_armored_string(None).unwrap();
let (public_key2, _) =

13
src/keyhistory.rs Normal file
View File

@@ -0,0 +1,13 @@
use crate::context::Context;
/* yes: uppercase */
/* library private: key-history */
pub fn dc_add_to_keyhistory(
_context: &Context,
_rfc724_mid: *const libc::c_char,
_sending_time: u64,
_addr: *const libc::c_char,
_fingerprint: *const libc::c_char,
) {
}

View File

@@ -2,8 +2,9 @@ use std::borrow::Cow;
use crate::constants::*;
use crate::context::Context;
use crate::dc_sqlite3::*;
use crate::key::*;
use crate::sql::Sql;
use crate::types::*;
#[derive(Default, Clone, Debug)]
pub struct Keyring<'a> {
@@ -30,17 +31,29 @@ impl<'a> Keyring<'a> {
pub fn load_self_private_for_decrypting(
&mut self,
context: &Context,
self_addr: impl AsRef<str>,
sql: &Sql,
self_addr: *const libc::c_char,
sql: &SQLite,
) -> bool {
sql.query_row_col(
context,
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
&[self_addr.as_ref()],
0,
)
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
.map(|key| self.add_owned(key))
.is_some()
// Can we prevent keyring and self_addr to be null?
if self_addr.is_null() {
return false;
}
let stmt = unsafe {
dc_sqlite3_prepare(
context,
sql,
b"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;\x00"
as *const u8 as *const libc::c_char,
)
};
unsafe { sqlite3_bind_text(stmt, 1, self_addr, -1, None) };
while unsafe { sqlite3_step(stmt) == 100 } {
if let Some(key) = Key::from_stmt(stmt, 0, KeyType::Private) {
self.add_owned(key);
}
}
unsafe { sqlite3_finalize(stmt) };
true
}
}

View File

@@ -1,4 +1,12 @@
#![feature(c_variadic, ptr_wrapping_offset_from, ptr_cast)]
#![allow(
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
non_upper_case_globals,
non_camel_case_types,
non_snake_case
)]
#![feature(c_variadic, ptr_wrapping_offset_from)]
#[macro_use]
extern crate failure_derive;
@@ -6,39 +14,29 @@ extern crate failure_derive;
extern crate num_derive;
#[macro_use]
extern crate smallvec;
#[macro_use]
extern crate rusqlite;
extern crate strum;
#[macro_use]
extern crate strum_macros;
#[macro_use]
mod log;
#[macro_use]
pub mod error;
pub mod dc_log;
pub mod aheader;
pub mod chatlist;
pub mod config;
pub mod constants;
pub mod contact;
pub mod context;
pub mod imap;
pub mod key;
pub mod keyhistory;
pub mod keyring;
pub mod oauth2;
pub mod param;
pub mod peerstate;
pub mod pgp;
pub mod smtp;
pub mod sql;
pub mod stock;
pub mod types;
pub mod x;
pub mod dc_array;
pub mod dc_chat;
pub mod dc_chatlist;
pub mod dc_configure;
pub mod dc_contact;
pub mod dc_dehtml;
pub mod dc_e2ee;
pub mod dc_imex;
@@ -51,16 +49,16 @@ pub mod dc_mimefactory;
pub mod dc_mimeparser;
pub mod dc_move;
pub mod dc_msg;
pub mod dc_param;
pub mod dc_qr;
pub mod dc_receive_imf;
pub mod dc_saxparser;
pub mod dc_securejoin;
pub mod dc_simplify;
pub mod dc_sqlite3;
pub mod dc_stock;
pub mod dc_strencode;
pub mod dc_token;
pub mod dc_tools;
pub use self::constants::*;
#[cfg(test)]
pub mod test_utils;

View File

@@ -1,59 +0,0 @@
#[macro_export]
macro_rules! info {
($ctx:expr, $data1:expr, $msg:expr) => {
info!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::INFO, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[macro_export]
macro_rules! warn {
($ctx:expr, $data1:expr, $msg:expr) => {
warn!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::WARNING, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[macro_export]
macro_rules! error {
($ctx:expr, $data1:expr, $msg:expr) => {
error!($ctx, $data1, $msg,)
};
($ctx:expr, $data1:expr, $msg:expr, $($args:expr),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
let formatted = format!($msg, $($args),*);
let formatted_c = std::ffi::CString::new(formatted).unwrap();
$ctx.call_cb($crate::constants::Event::ERROR, $data1 as libc::uintptr_t,
formatted_c.as_ptr() as libc::uintptr_t);
}};
}
#[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),* $(,)?) => {
#[allow(unused_unsafe)]
unsafe {
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);
}};
}

View File

@@ -1,10 +1,13 @@
use std::collections::HashMap;
use std::ffi::CString;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use serde::Deserialize;
use crate::context::Context;
use crate::dc_sqlite3::*;
use crate::dc_tools::*;
use crate::types::*;
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
@@ -48,17 +51,11 @@ pub fn dc_get_oauth2_url(
redirect_uri: impl AsRef<str>,
) -> Option<String> {
if let Some(oauth2) = Oauth2::from_address(addr) {
if context
.sql
.set_config(
context,
"oauth2_pending_redirect_uri",
Some(redirect_uri.as_ref()),
)
.is_err()
{
return None;
}
set_config(
context,
"oauth2_pending_redirect_uri",
redirect_uri.as_ref(),
);
let oauth2_url = replace_in_uri(&oauth2.get_code, "$CLIENT_ID", &oauth2.client_id);
let oauth2_url = replace_in_uri(&oauth2_url, "$REDIRECT_URI", redirect_uri.as_ref());
@@ -82,18 +79,16 @@ pub fn dc_get_oauth2_access_token(
// read generated token
if 0 == flags & 0x1 && !is_expired(context) {
let access_token = context.sql.get_config(context, "oauth2_access_token");
let access_token = get_config(context, "oauth2_access_token");
if access_token.is_some() {
// success
return access_token;
}
}
let refresh_token = context.sql.get_config(context, "oauth2_refresh_token");
let refresh_token_for = context
.sql
.get_config(context, "oauth2_refresh_token_for")
.unwrap_or_else(|| "unset".into());
let refresh_token = get_config(context, "oauth2_refresh_token");
let refresh_token_for =
get_config(context, "oauth2_refresh_token_for").unwrap_or_else(|| "unset".into());
let (redirect_uri, token_url, update_redirect_uri_on_success) =
if refresh_token.is_none() || refresh_token_for != code.as_ref() {
@@ -102,9 +97,7 @@ pub fn dc_get_oauth2_access_token(
0, "Generate OAuth2 refresh_token and access_token...",
);
(
context
.sql
.get_config(context, "oauth2_pending_redirect_uri")
get_config(context, "oauth2_pending_redirect_uri")
.unwrap_or_else(|| "unset".into()),
oauth2.init_token,
true,
@@ -115,10 +108,7 @@ pub fn dc_get_oauth2_access_token(
0, "Regenerate OAuth2 access_token by refresh_token...",
);
(
context
.sql
.get_config(context, "oauth2_redirect_uri")
.unwrap_or_else(|| "unset".into()),
get_config(context, "oauth2_redirect_uri").unwrap_or_else(|| "unset".into()),
oauth2.refresh_token,
false,
)
@@ -161,38 +151,23 @@ pub fn dc_get_oauth2_access_token(
println!("response: {:?}", &parsed);
let response = parsed.unwrap();
if let Some(ref token) = response.refresh_token {
context
.sql
.set_config(context, "oauth2_refresh_token", Some(token))
.ok();
context
.sql
.set_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
.ok();
set_config(context, "oauth2_refresh_token", token);
set_config(context, "oauth2_refresh_token_for", code.as_ref());
}
// after that, save the access token.
// if it's unset, we may get it in the next round as we have the refresh_token now.
if let Some(ref token) = response.access_token {
context
.sql
.set_config(context, "oauth2_access_token", Some(token))
.ok();
set_config(context, "oauth2_access_token", token);
let expires_in = response
.expires_in
// refresh a bet before
.map(|t| time() + t as i64 - 5)
.unwrap_or_else(|| 0);
context
.sql
.set_config_int64(context, "oauth2_timestamp_expires", expires_in)
.ok();
set_config_int64(context, "oauth2_timestamp_expires", expires_in);
if update_redirect_uri_on_success {
context
.sql
.set_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
.ok();
set_config(context, "oauth2_redirect_uri", redirect_uri.as_ref());
}
} else {
warn!(context, 0, "Failed to find OAuth2 access token");
@@ -304,11 +279,35 @@ impl Oauth2 {
}
}
fn get_config(context: &Context, key: &str) -> Option<String> {
let key_c = CString::new(key).unwrap();
let res =
unsafe { dc_sqlite3_get_config(context, &context.sql, key_c.as_ptr(), std::ptr::null()) };
if res.is_null() {
return None;
}
Some(to_string(res))
}
fn set_config(context: &Context, key: &str, value: &str) {
let key_c = CString::new(key).unwrap();
let value_c = CString::new(value).unwrap();
unsafe { dc_sqlite3_set_config(context, &context.sql, key_c.as_ptr(), value_c.as_ptr()) };
}
fn set_config_int64(context: &Context, key: &str, value: i64) {
let key_c = CString::new(key).unwrap();
unsafe { dc_sqlite3_set_config_int64(context, &context.sql, key_c.as_ptr(), value) };
}
fn is_expired(context: &Context) -> bool {
let expire_timestamp = context
.sql
.get_config_int64(context, "oauth2_timestamp_expires")
.unwrap_or_default();
let expire_timestamp = dc_sqlite3_get_config_int64(
context,
&context.sql,
b"oauth2_timestamp_expires\x00" as *const u8 as *const libc::c_char,
0i32 as int64_t,
);
if expire_timestamp <= 0 {
return false;
@@ -321,7 +320,7 @@ fn is_expired(context: &Context) -> bool {
}
fn replace_in_uri(uri: impl AsRef<str>, key: impl AsRef<str>, value: impl AsRef<str>) -> String {
let value_urlencoded = utf8_percent_encode(value.as_ref(), NON_ALPHANUMERIC).to_string();
let value_urlencoded = utf8_percent_encode(value.as_ref(), DEFAULT_ENCODE_SET).to_string();
uri.as_ref().replace(key.as_ref(), &value_urlencoded)
}
@@ -344,7 +343,7 @@ mod tests {
fn test_replace_in_uri() {
assert_eq!(
replace_in_uri("helloworld", "world", "a-b c"),
"helloa%2Db%20c"
"helloa-b%20c"
);
}

View File

@@ -1,238 +0,0 @@
use std::collections::BTreeMap;
use std::fmt;
use std::str;
use num_traits::FromPrimitive;
use crate::error;
/// Available param keys.
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash, PartialOrd, Ord, FromPrimitive)]
#[repr(u8)]
pub enum Param {
/// For messages and jobs
File = 'f' as u8,
/// For Messages
Width = 'w' as u8,
/// For Messages
Height = 'h' as u8,
/// For Messages
Duration = 'd' as u8,
/// For Messages
MimeType = 'm' as u8,
/// For Messages: message is encryoted, outgoing: guarantee E2EE or the message is not send
GuranteeE2ee = 'c' as u8,
/// For Messages: decrypted with validation errors or without mutual set, if neither
/// 'c' nor 'e' are preset, the messages is only transport encrypted.
ErroneousE2ee = 'e' as u8,
/// For Messages: force unencrypted message, either `ForcePlaintext::AddAutocryptHeader` (1),
/// `ForcePlaintext::NoAutocryptHeader` (2) or 0.
ForcePlaintext = 'u' as u8,
/// For Messages
WantsMdn = 'r' as u8,
/// For Messages
Forwarded = 'a' as u8,
/// For Messages
Cmd = 'S' as u8,
/// For Messages
Arg = 'E' as u8,
/// For Messages
Arg2 = 'F' as u8,
/// For Messages
Arg3 = 'G' as u8,
/// For Messages
Arg4 = 'H' as u8,
/// For Messages
Error = 'L' as u8,
/// For Messages: space-separated list of messaged IDs of forwarded copies.
PrepForwards = 'P' as u8,
/// For Jobs
SetLatitude = 'l' as u8,
/// For Jobs
SetLongitude = 'n' as u8,
/// For Jobs
ServerFolder = 'Z' as u8,
/// For Jobs
ServerUid = 'z' as u8,
/// For Jobs
AlsoMove = 'M' as u8,
/// For Jobs: space-separated list of message recipients
Recipients = 'R' as u8,
// For Groups
Unpromoted = 'U' as u8,
// For Groups and Contacts
ProfileImage = 'i' as u8,
// For Chats
Selftalk = 'K' as u8,
// For QR
Auth = 's' as u8,
// For QR
GroupId = 'x' as u8,
// For QR
GroupName = 'g' as u8,
}
/// Possible values for `Param::ForcePlaintext`.
#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive)]
#[repr(u8)]
pub enum ForcePlaintext {
AddAutocryptHeader = 1,
NoAutocryptHeader = 2,
}
/// An object for handling key=value parameter lists.
///
/// The structure is serialized by calling `to_string()` on it.
///
/// Only for library-internal use.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Params {
inner: BTreeMap<Param, String>,
}
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")?;
}
write!(f, "{}={}", *key as u8 as char, value)?;
}
Ok(())
}
}
impl str::FromStr for Params {
type Err = error::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut inner = BTreeMap::new();
for pair in s.trim().lines() {
let pair = pair.trim();
if pair.is_empty() {
continue;
}
// TODO: probably nicer using a regex
ensure!(pair.len() > 2, "Invalid key pair: '{}'", pair);
let mut split = pair.splitn(2, '=');
let key = split.next();
let value = split.next();
ensure!(key.is_some(), "Missing key");
ensure!(value.is_some(), "Missing value");
let key = key.unwrap().trim();
let value = value.unwrap().trim();
if let Some(key) = Param::from_u8(key.as_bytes()[0]) {
inner.insert(key, value.to_string());
} else {
bail!("Unknown key: {}", key);
}
}
Ok(Params { inner })
}
}
impl Params {
/// Create new empty params.
pub fn new() -> Self {
Default::default()
}
/// Get the value of the given key, return `None` if no value is set.
pub fn get(&self, key: Param) -> Option<&str> {
self.inner.get(&key).map(|s| s.as_str())
}
/// Check if the given key is set.
pub fn exists(&self, key: Param) -> bool {
self.inner.contains_key(&key)
}
/// Set the given key to the passed in value.
pub fn set(&mut self, key: Param, value: impl AsRef<str>) -> &mut Self {
self.inner.insert(key, value.as_ref().to_string());
self
}
/// Removes the given key, if it exists.
pub fn remove(&mut self, key: Param) -> &mut Self {
self.inner.remove(&key);
self
}
/// Check if there are any values in this.
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// Returns how many key-value pairs are set.
pub fn len(&self) -> usize {
self.inner.len()
}
/// Get the given parameter and parse as `i32`.
pub fn get_int(&self, key: Param) -> Option<i32> {
self.get(key).and_then(|s| s.parse().ok())
}
/// 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())
}
/// Set the given paramter to the passed in `i32`.
pub fn set_int(&mut self, key: Param, value: i32) -> &mut Self {
self.set(key, format!("{}", value));
self
}
/// Set the given parameter to the passed in `f64` .
pub fn set_float(&mut self, key: Param, value: f64) -> &mut Self {
self.set(key, format!("{}", value));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dc_param() {
let mut p1: Params = "\r\n\r\na=1\nf=2\n\nc = 3 ".parse().unwrap();
assert_eq!(p1.get_int(Param::Forwarded), Some(1));
assert_eq!(p1.get_int(Param::File), Some(2));
assert_eq!(p1.get_int(Param::Height), None);
assert!(!p1.exists(Param::Height));
p1.set_int(Param::Duration, 4);
assert_eq!(p1.get_int(Param::Duration), Some(4));
let mut p1 = Params::new();
p1.set(Param::Forwarded, "foo")
.set_int(Param::File, 2)
.remove(Param::GuranteeE2ee)
.set_int(Param::Duration, 4);
assert_eq!(p1.to_string(), "a=foo\nd=4\nf=2");
p1.remove(Param::File);
assert_eq!(p1.to_string(), "a=foo\nd=4",);
assert_eq!(p1.len(), 2);
p1.remove(Param::Forwarded);
p1.remove(Param::Duration);
assert_eq!(p1.to_string(), "",);
assert!(p1.is_empty());
assert_eq!(p1.len(), 0)
}
}

View File

@@ -1,4 +1,5 @@
use std::collections::HashSet;
use std::ffi::CString;
use std::fmt;
use num_traits::FromPrimitive;
@@ -7,20 +8,22 @@ use crate::aheader::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_chat::*;
use crate::dc_sqlite3::*;
use crate::dc_tools::{to_cstring, to_string};
use crate::key::*;
use crate::sql::{self, Sql};
use crate::types::*;
/// Peerstate represents the state of an Autocrypt peer.
pub struct Peerstate<'a> {
pub context: &'a Context,
pub addr: Option<String>,
pub last_seen: i64,
pub last_seen_autocrypt: i64,
pub last_seen: u64,
pub last_seen_autocrypt: u64,
pub prefer_encrypt: EncryptPreference,
pub public_key: Option<Key>,
pub public_key_fingerprint: Option<String>,
pub gossip_key: Option<Key>,
pub gossip_timestamp: i64,
pub gossip_timestamp: u64,
pub gossip_key_fingerprint: Option<String>,
verified_key: VerifiedKey,
pub verified_key_fingerprint: Option<String>,
@@ -138,7 +141,7 @@ impl<'a> Peerstate<'a> {
}
}
pub fn from_header(context: &'a Context, header: &Aheader, message_time: i64) -> Self {
pub fn from_header(context: &'a Context, header: &Aheader, message_time: u64) -> Self {
let mut res = Self::new(context);
res.addr = Some(header.addr.clone());
@@ -152,7 +155,7 @@ impl<'a> Peerstate<'a> {
res
}
pub fn from_gossip(context: &'a Context, gossip_header: &Aheader, message_time: i64) -> Self {
pub fn from_gossip(context: &'a Context, gossip_header: &Aheader, message_time: u64) -> Self {
let mut res = Self::new(context);
res.addr = Some(gossip_header.addr.clone());
@@ -164,70 +167,88 @@ impl<'a> Peerstate<'a> {
res
}
pub fn from_addr(context: &'a Context, _sql: &Sql, addr: &str) -> Option<Self> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;";
pub fn from_addr(context: &'a Context, sql: &SQLite, addr: &str) -> Option<Self> {
let mut res = None;
Self::from_stmt(context, query, &[addr])
let stmt = unsafe {
dc_sqlite3_prepare(
context,
sql,
b"SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;\x00"
as *const u8 as *const libc::c_char)
};
let addr_c = CString::new(addr.as_bytes()).unwrap();
unsafe { sqlite3_bind_text(stmt, 1, addr_c.as_ptr(), -1, None) };
if unsafe { sqlite3_step(stmt) } == 100 {
res = Some(Self::from_stmt(context, stmt));
}
unsafe { sqlite3_finalize(stmt) };
res
}
pub fn from_fingerprint(context: &'a Context, _sql: &Sql, fingerprint: &str) -> Option<Self> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint \
FROM acpeerstates \
WHERE public_key_fingerprint=? COLLATE NOCASE \
OR gossip_key_fingerprint=? COLLATE NOCASE \
ORDER BY public_key_fingerprint=? DESC;";
pub fn from_fingerprint(context: &'a Context, sql: &SQLite, fingerprint: &str) -> Option<Self> {
let mut res = None;
let fp = fingerprint.as_bytes();
Self::from_stmt(context, query, params![fp, fp, fp])
let stmt = unsafe {
dc_sqlite3_prepare(
context,
sql,
b"SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE public_key_fingerprint=? COLLATE NOCASE OR gossip_key_fingerprint=? COLLATE NOCASE ORDER BY public_key_fingerprint=? DESC;\x00"
as *const u8 as *const libc::c_char)
};
let fp_c = CString::new(fingerprint.as_bytes()).unwrap();
unsafe {
sqlite3_bind_text(stmt, 1, fp_c.as_ptr(), -1, None);
sqlite3_bind_text(stmt, 2, fp_c.as_ptr(), -1, None);
sqlite3_bind_text(stmt, 3, fp_c.as_ptr(), -1, None);
}
if unsafe { sqlite3_step(stmt) == 100 } {
res = Some(Self::from_stmt(context, stmt));
}
unsafe { sqlite3_finalize(stmt) };
res
}
fn from_stmt<P>(context: &'a Context, query: &str, params: P) -> Option<Self>
where
P: IntoIterator,
P::Item: rusqlite::ToSql,
{
context
.sql
.query_row(query, params, |row| {
let mut res = Self::new(context);
fn from_stmt(context: &'a Context, stmt: *mut sqlite3_stmt) -> Self {
let mut res = Self::new(context);
res.addr = Some(row.get(0)?);
res.last_seen = row.get(1)?;
res.last_seen_autocrypt = row.get(2)?;
res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default();
res.gossip_timestamp = row.get(5)?;
let pkf: String = row.get(7)?;
res.public_key_fingerprint = if pkf.is_empty() { None } else { Some(pkf) };
let gkf: String = row.get(8)?;
res.gossip_key_fingerprint = if gkf.is_empty() { None } else { Some(gkf) };
let vkf: String = row.get(10)?;
res.verified_key_fingerprint = if vkf.is_empty() { None } else { Some(vkf) };
res.addr = Some(to_string(unsafe {
sqlite3_column_text(stmt, 0) as *const _
}));
res.last_seen = unsafe { sqlite3_column_int64(stmt, 1) } as u64;
res.last_seen_autocrypt = unsafe { sqlite3_column_int64(stmt, 2) } as u64;
res.prefer_encrypt =
EncryptPreference::from_i32(unsafe { sqlite3_column_int(stmt, 3) }).unwrap_or_default();
res.gossip_timestamp = unsafe { sqlite3_column_int(stmt, 5) } as u64;
let pkf = to_string(unsafe { sqlite3_column_text(stmt, 7) as *const _ });
res.public_key_fingerprint = if pkf.is_empty() { None } else { Some(pkf) };
let gkf = to_string(unsafe { sqlite3_column_text(stmt, 8) as *const _ });
res.gossip_key_fingerprint = if gkf.is_empty() { None } else { Some(gkf) };
let vkf = to_string(unsafe { sqlite3_column_text(stmt, 10) as *const _ });
res.verified_key_fingerprint = if vkf.is_empty() { None } else { Some(vkf) };
res.public_key = row
.get(4)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
res.gossip_key = row
.get(6)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
let vk = row
.get(9)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
res.verified_key = if vk == res.gossip_key {
VerifiedKey::Gossip
} else if vk == res.public_key {
VerifiedKey::Public
} else {
VerifiedKey::None
};
if unsafe { sqlite3_column_type(stmt, 4) } != 5 {
res.public_key = Key::from_stmt(stmt, 4, KeyType::Public);
}
if unsafe { sqlite3_column_type(stmt, 6) } != 5 {
res.gossip_key = Key::from_stmt(stmt, 6, KeyType::Public);
}
if unsafe { sqlite3_column_type(stmt, 9) } != 5 {
let vk = Key::from_stmt(stmt, 9, KeyType::Public);
res.verified_key = if vk == res.gossip_key {
VerifiedKey::Gossip
} else if vk == res.public_key {
VerifiedKey::Public
} else {
VerifiedKey::None
};
}
Ok(res)
})
.ok()
res
}
pub fn recalc_fingerprint(&mut self) {
@@ -262,7 +283,7 @@ impl<'a> Peerstate<'a> {
}
}
pub fn degrade_encryption(&mut self, message_time: i64) {
pub fn degrade_encryption(&mut self, message_time: u64) {
if self.prefer_encrypt == EncryptPreference::Mutual {
self.degrade_event = Some(DegradeEvent::EncryptionPaused);
}
@@ -272,7 +293,7 @@ impl<'a> Peerstate<'a> {
self.to_save = Some(ToSave::All);
}
pub fn apply_header(&mut self, header: &Aheader, message_time: i64) {
pub fn apply_header(&mut self, header: &Aheader, message_time: u64) {
if self.addr.is_none()
|| self.addr.as_ref().unwrap().to_lowercase() != header.addr.to_lowercase()
{
@@ -304,7 +325,7 @@ impl<'a> Peerstate<'a> {
}
}
pub fn apply_gossip(&mut self, gossip_header: &Aheader, message_time: i64) {
pub fn apply_gossip(&mut self, gossip_header: &Aheader, message_time: u64) {
if self.addr.is_none()
|| self.addr.as_ref().unwrap().to_lowercase() != gossip_header.addr.to_lowercase()
{
@@ -379,7 +400,7 @@ impl<'a> Peerstate<'a> {
success
}
pub fn save_to_db(&self, sql: &Sql, create: bool) -> bool {
pub fn save_to_db(&self, sql: &SQLite, create: bool) -> bool {
let mut success = false;
if self.addr.is_none() {
@@ -387,59 +408,158 @@ impl<'a> Peerstate<'a> {
}
if create {
if sql::execute(
self.context,
sql,
"INSERT INTO acpeerstates (addr) VALUES(?);",
params![self.addr.as_ref().unwrap()],
)
.is_err()
{
return false;
let stmt = unsafe {
dc_sqlite3_prepare(
self.context,
sql,
b"INSERT INTO acpeerstates (addr) VALUES(?);\x00" as *const u8
as *const libc::c_char,
)
};
let addr_c = to_cstring(self.addr.as_ref().unwrap());
unsafe {
sqlite3_bind_text(stmt, 1, addr_c.as_ptr(), -1, None);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
}
if self.to_save == Some(ToSave::All) || create {
success = sql::execute(
self.context,
sql,
"UPDATE acpeerstates \
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
verified_key=?, verified_key_fingerprint=? \
WHERE addr=?;",
params![
self.last_seen,
self.last_seen_autocrypt,
self.prefer_encrypt as i64,
self.public_key.as_ref().map(|k| k.to_bytes()),
self.gossip_timestamp,
self.gossip_key.as_ref().map(|k| k.to_bytes()),
&self.public_key_fingerprint,
&self.gossip_key_fingerprint,
self.verified_key().map(|k| k.to_bytes()),
&self.verified_key_fingerprint,
&self.addr,
],
).is_ok();
let stmt = unsafe {
dc_sqlite3_prepare(
self.context, sql,
b"UPDATE acpeerstates \
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, verified_key=?, verified_key_fingerprint=? \
WHERE addr=?;\x00"
as *const u8 as *const libc::c_char
)
};
unsafe { sqlite3_bind_int64(stmt, 1, self.last_seen as sqlite3_int64) };
unsafe { sqlite3_bind_int64(stmt, 2, self.last_seen_autocrypt as sqlite3_int64) };
unsafe { sqlite3_bind_int64(stmt, 3, self.prefer_encrypt as sqlite3_int64) };
let pub_bytes = self
.public_key
.as_ref()
.map(|k| k.to_bytes())
.unwrap_or_default();
let gossip_bytes = self
.gossip_key
.as_ref()
.map(|k| k.to_bytes())
.unwrap_or_default();
let ver_bytes = self
.verified_key()
.as_ref()
.map(|k| k.to_bytes())
.unwrap_or_default();
let pkc = self
.public_key_fingerprint
.as_ref()
.map(to_cstring)
.unwrap_or_default();
let pkc_ptr = if self.public_key_fingerprint.is_some() {
pkc.as_ptr()
} else {
std::ptr::null()
};
let gkc = self
.gossip_key_fingerprint
.as_ref()
.map(to_cstring)
.unwrap_or_default();
let gkc_ptr = if self.gossip_key_fingerprint.is_some() {
gkc.as_ptr()
} else {
std::ptr::null_mut()
};
let vkc = self
.verified_key_fingerprint
.as_ref()
.map(to_cstring)
.unwrap_or_default();
let vkc_ptr = if self.verified_key_fingerprint.is_some() {
vkc.as_ptr()
} else {
std::ptr::null_mut()
};
let addr: String = self.addr.clone().unwrap_or_default();
let addr_c = to_cstring(addr);
unsafe {
sqlite3_bind_blob(
stmt,
4,
pub_bytes.as_ptr() as *const _,
pub_bytes.len() as libc::c_int,
SQLITE_TRANSIENT(),
)
};
unsafe { sqlite3_bind_int64(stmt, 5, self.gossip_timestamp as sqlite3_int64) };
unsafe {
sqlite3_bind_blob(
stmt,
6,
gossip_bytes.as_ptr() as *const _,
gossip_bytes.len() as libc::c_int,
SQLITE_TRANSIENT(),
)
};
unsafe { sqlite3_bind_text(stmt, 7, pkc_ptr as *const _, -1, SQLITE_TRANSIENT()) };
unsafe { sqlite3_bind_text(stmt, 8, gkc_ptr as *const _, -1, SQLITE_TRANSIENT()) };
unsafe {
sqlite3_bind_blob(
stmt,
9,
ver_bytes.as_ptr() as *const _,
ver_bytes.len() as libc::c_int,
SQLITE_TRANSIENT(),
)
};
unsafe { sqlite3_bind_text(stmt, 10, vkc_ptr as *const _, -1, SQLITE_TRANSIENT()) };
unsafe { sqlite3_bind_text(stmt, 11, addr_c.as_ptr(), -1, SQLITE_TRANSIENT()) };
if unsafe { sqlite3_step(stmt) } == 101 {
success = true;
}
unsafe { sqlite3_finalize(stmt) };
} else if self.to_save == Some(ToSave::Timestamps) {
success = sql::execute(
self.context,
sql,
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
WHERE addr=?;",
params![
self.last_seen,
self.last_seen_autocrypt,
self.gossip_timestamp,
&self.addr
],
)
.is_ok();
let stmt = unsafe {
dc_sqlite3_prepare(
&self.context,sql,
b"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? WHERE addr=?;\x00"
as *const u8 as *const libc::c_char)
};
let c_addr = self.addr.as_ref().map(to_cstring).unwrap_or_default();
let addr_ptr = if self.addr.is_some() {
c_addr.as_ptr()
} else {
std::ptr::null()
};
unsafe { sqlite3_bind_int64(stmt, 1, self.last_seen as sqlite3_int64) };
unsafe { sqlite3_bind_int64(stmt, 2, self.last_seen_autocrypt as sqlite3_int64) };
unsafe { sqlite3_bind_int64(stmt, 3, self.gossip_timestamp as sqlite3_int64) };
unsafe { sqlite3_bind_text(stmt, 4, addr_ptr, -1, SQLITE_TRANSIENT()) };
if unsafe { sqlite3_step(stmt) } == 101 {
success = true;
}
unsafe { sqlite3_finalize(stmt) };
}
if self.to_save == Some(ToSave::All) || create {
dc_reset_gossiped_timestamp(self.context, 0);
unsafe { dc_reset_gossiped_timestamp(self.context, 0 as uint32_t) };
}
success
@@ -462,11 +582,14 @@ mod tests {
use super::*;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use std::ffi::CStr;
use tempfile::{tempdir, TempDir};
use crate::context::*;
#[test]
fn test_peerstate_save_to_db() {
let ctx = crate::test_utils::dummy_context();
let ctx = unsafe { create_test_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();
@@ -504,4 +627,29 @@ mod tests {
ctx: Context,
dir: TempDir,
}
unsafe extern "C" fn cb(
_context: &Context,
_event: Event,
_data1: uintptr_t,
_data2: uintptr_t,
) -> uintptr_t {
0
}
unsafe fn create_test_context() -> TestContext {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), std::ptr::null_mut());
let dir = tempdir().unwrap();
let dbfile = CString::new(dir.path().join("db.sqlite").to_str().unwrap()).unwrap();
assert_eq!(
dc_open(&mut ctx, dbfile.as_ptr(), std::ptr::null()),
1,
"Failed to open {}",
CStr::from_ptr(dbfile.as_ptr() as *const libc::c_char)
.to_str()
.unwrap()
);
TestContext { ctx: ctx, dir: dir }
}
}

View File

@@ -17,14 +17,15 @@ use crate::keyring::*;
use crate::types::*;
use crate::x::*;
// TODO should return bool /rtn
pub unsafe fn dc_split_armored_data(
buf: *mut libc::c_char,
ret_headerline: *mut *const libc::c_char,
ret_setupcodebegin: *mut *const libc::c_char,
ret_preferencrypt: *mut *const libc::c_char,
ret_base64: *mut *const libc::c_char,
) -> bool {
let mut success = false;
) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let mut line_chars: size_t = 0i32 as size_t;
let mut line: *mut libc::c_char = buf;
let mut p1: *mut libc::c_char = buf;
@@ -127,7 +128,7 @@ pub unsafe fn dc_split_armored_data(
if !ret_base64.is_null() {
*ret_base64 = base64
}
success = true;
success = 1i32
}
}
}
@@ -136,8 +137,8 @@ pub unsafe fn dc_split_armored_data(
}
/// Create a new key pair.
pub fn dc_pgp_create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
let user_id = format!("<{}>", addr.as_ref());
pub fn dc_pgp_create_keypair(addr: *const libc::c_char) -> Option<(Key, Key)> {
let user_id = format!("<{}>", unsafe { CStr::from_ptr(addr).to_str().unwrap() });
let key_params = SecretKeyParamsBuilder::default()
.key_type(PgpKeyType::Rsa(2048))

View File

@@ -1,3 +1,5 @@
use std::ffi::CStr;
use lettre::smtp::client::net::*;
use lettre::*;
@@ -5,7 +7,9 @@ use crate::constants::Event;
use crate::constants::*;
use crate::context::Context;
use crate::dc_loginparam::*;
use crate::dc_tools::*;
use crate::oauth2::*;
use crate::types::*;
pub struct Smtp {
transport: Option<lettre::smtp::SmtpTransport>,
@@ -43,17 +47,30 @@ impl Smtp {
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &dc_loginparam_t) -> bool {
pub fn connect(&mut self, context: &Context, lp: *const dc_loginparam_t) -> bool {
if lp.is_null() {
return false;
}
if self.is_connected() {
warn!(context, 0, "SMTP already connected.");
return true;
}
if lp.send_server.is_empty() || lp.send_port == 0 {
// Safe because we checked for null pointer above.
let lp = unsafe { *lp };
if lp.addr.is_null() || lp.send_server.is_null() || lp.send_port == 0 {
log_event!(context, Event::ERROR_NETWORK, 0, "SMTP bad parameters.",);
}
self.from = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
let raw_addr = unsafe {
CStr::from_ptr(lp.addr)
.to_str()
.expect("invalid from address")
.to_string()
};
self.from = if let Ok(addr) = EmailAddress::new(raw_addr) {
Some(addr)
} else {
None
@@ -64,7 +81,11 @@ impl Smtp {
return false;
}
let domain = &lp.send_server;
let domain = unsafe {
CStr::from_ptr(lp.send_server)
.to_str()
.expect("invalid send server")
};
let port = lp.send_port as u16;
let tls = native_tls::TlsConnector::builder()
@@ -78,19 +99,19 @@ impl Smtp {
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let addr = as_str(lp.addr);
let send_pw = as_str(lp.send_pw);
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, 0);
if access_token.is_none() {
return false;
}
let user = &lp.send_user;
let user = as_str(lp.send_user);
lettre::smtp::authentication::Credentials::new(user.to_string(), access_token.unwrap())
lettre::smtp::authentication::Credentials::new(user.into(), access_token.unwrap())
} else {
// plain
let user = lp.send_user.clone();
let pw = lp.send_pw.clone();
let user = unsafe { CStr::from_ptr(lp.send_user).to_str().unwrap().to_string() };
let pw = unsafe { CStr::from_ptr(lp.send_pw).to_str().unwrap().to_string() };
lettre::smtp::authentication::Credentials::new(user, pw)
};
@@ -102,7 +123,7 @@ impl Smtp {
lettre::smtp::ClientSecurity::Wrapper(tls_parameters)
};
match lettre::smtp::SmtpClient::new((domain.as_str(), port), security) {
match lettre::smtp::SmtpClient::new((domain, port), security) {
Ok(client) => {
let client = client
.smtp_utf8(true)
@@ -114,7 +135,7 @@ impl Smtp {
Event::SMTP_CONNECTED,
0,
"SMTP-LOGIN as {} ok",
lp.send_user,
as_str(lp.send_user),
);
true
}

1141
src/sql.rs

File diff suppressed because it is too large Load Diff

View File

@@ -1,394 +0,0 @@
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 libc::free;
/// Stock strings
///
/// These identify the string to return in [Context.stock_str]. The
/// numbers must stay in sync with `deltachat.h` `DC_STR_*` constants.
///
/// See the `stock_*` methods on [Context] to use these.
///
/// [Context]: crate::context::Context
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, EnumProperty)]
#[repr(u32)]
pub enum StockMessage {
#[strum(props(fallback = "No messages."))]
NoMessages = 1,
#[strum(props(fallback = "Me"))]
SelfMsg = 2,
#[strum(props(fallback = "Draft"))]
Draft = 3,
#[strum(props(fallback = "%1$s member(s)"))]
Member = 4,
#[strum(props(fallback = "%1$s contact(s)"))]
Contact = 6,
#[strum(props(fallback = "Voice message"))]
VoiceMessage = 7,
#[strum(props(fallback = "Contact requests"))]
DeadDrop = 8,
#[strum(props(fallback = "Image"))]
Image = 9,
#[strum(props(fallback = "Video"))]
Video = 10,
#[strum(props(fallback = "Audio"))]
Audio = 11,
#[strum(props(fallback = "File"))]
File = 12,
#[strum(props(fallback = "Sent with my Delta Chat Messenger: https://delta.chat"))]
StatusLine = 13,
#[strum(props(fallback = "Hello, I\'ve just created the group \"%1$s\" for us."))]
NewGroupDraft = 14,
#[strum(props(fallback = "Group name changed from \"%1$s\" to \"%2$s\"."))]
MsgGrpName = 15,
#[strum(props(fallback = "Group image changed."))]
MsgGrpImgChanged = 16,
#[strum(props(fallback = "Member %1$s added."))]
MsgAddMember = 17,
#[strum(props(fallback = "Member %1$s removed."))]
MsgDelMember = 18,
#[strum(props(fallback = "Group left."))]
MsgGroupLeft = 19,
#[strum(props(fallback = "GIF"))]
Gif = 23,
#[strum(props(fallback = "Encrypted message"))]
EncryptedMsg = 24,
#[strum(props(fallback = "End-to-end encryption available."))]
E2eAvailable = 25,
#[strum(props(fallback = "Transport-encryption."))]
EncrTransp = 27,
#[strum(props(fallback = "No encryption."))]
EncrNone = 28,
#[strum(props(fallback = "This message was encrypted for another setup."))]
CantDecryptMsgBody = 29,
#[strum(props(fallback = "Fingerprints"))]
FingerPrints = 30,
#[strum(props(fallback = "Return receipt"))]
ReadRcpt = 31,
#[strum(props(fallback = "This is a return receipt for the message \"%1$s\"."))]
ReadRcptMailBody = 32,
#[strum(props(fallback = "Group image deleted."))]
MsgGrpImgDeleted = 33,
#[strum(props(fallback = "End-to-end encryption preferred."))]
E2ePreferred = 34,
#[strum(props(fallback = "%1$s verified."))]
ContactVerified = 35,
#[strum(props(fallback = "Cannot verify %1$s"))]
ContactNotVerified = 36,
#[strum(props(fallback = "Changed setup for %1$s"))]
ContactSetupChanged = 37,
#[strum(props(fallback = "Archived chats"))]
ArchivedChats = 40,
#[strum(props(fallback = "Starred messages"))]
StarredMsgs = 41,
#[strum(props(fallback = "Autocrypt Setup Message"))]
AcSetupMsgSubject = 42,
#[strum(props(
fallback = "This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device."
))]
AcSetupMsgBody = 43,
#[strum(props(fallback = "Messages I sent to myself"))]
SelfTalkSubTitle = 50,
#[strum(props(fallback = "Cannot login as %1$s."))]
CannotLogin = 60,
#[strum(props(fallback = "Response from %1$s: %2$s"))]
ServerResponse = 61,
#[strum(props(fallback = "%1$s by %2$s."))]
MsgActionByUser = 62,
#[strum(props(fallback = "%1$s by me."))]
MsgActionByMe = 63,
#[strum(props(fallback = "Location streaming enabled."))]
MsgLocationEnabled = 64,
#[strum(props(fallback = "Location streaming disabled."))]
MsgLocationDisabled = 65,
#[strum(props(fallback = "Location"))]
Location = 66,
}
impl StockMessage {
/// Default untranslated strings for stock messages.
///
/// These could be used in logging calls, so no logging here.
fn fallback(&self) -> &'static str {
self.get_str("fallback").unwrap()
}
}
impl Context {
/// Return the stock string for the [StockMessage].
///
/// If the context callback responds with a string to use, e.g. a
/// 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;
if ptr.is_null() {
Cow::Borrowed(id.fallback())
} else {
let ret = to_string(ptr);
unsafe { free(ptr as *mut libc::c_void) };
Cow::Owned(ret)
}
}
/// Return stock string, replacing placeholders with provided string.
///
/// This replaces both the *first* `%1$s` **and** `%1$d`
/// placeholders with the provided string.
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)
}
/// Return stock string, replacing placeholders with provided int.
///
/// Like [Context::stock_string_repl_str] but substitute the placeholders
/// with an integer.
pub fn stock_string_repl_int(&self, id: StockMessage, insert: i32) -> String {
self.stock_string_repl_str(id, format!("{}", insert).as_str())
}
/// Return stock string, replacing 2 placeholders with provided string.
///
/// This replaces both the *first* `%1$s` **and** `%1$d`
/// placeholders with the string in `insert` and does the same for
/// `%2$s` and `%2$d` for `insert2`.
fn stock_string_repl_str2(
&self,
id: StockMessage,
insert: impl AsRef<str>,
insert2: impl AsRef<str>,
) -> String {
self.stock_str(id)
.replacen("%1$s", insert.as_ref(), 1)
.replacen("%1$d", insert.as_ref(), 1)
.replacen("%2$s", insert2.as_ref(), 1)
.replacen("%2$d", insert2.as_ref(), 1)
}
/// Return some kind of stock message
///
/// If the `id` is [StockMessage::MsgAddMember] or
/// [StockMessage::MsgDelMember] then `param1` is considered to be the
/// contact address and will be replaced by that contact's display
/// name.
///
/// If `from_id` is not `0`, any trailing dot is removed from the
/// first stock string created so far. If the `from_id` contact is
/// the user itself, i.e. `DC_CONTACT_ID_SELF` the string is used
/// itself as param to the [StockMessage::MsgActionByMe] stock string
/// resulting in a string like "Member Alice added by me." (for
/// [StockMessage::MsgAddMember] as `id`). If the `from_id` contact
/// is any other user than the contact's display name is looked up and
/// used as the second parameter to [StockMessage::MsgActionByUser] with
/// again the original stock string being used as the first parameter,
/// resulting in a string like "Member Alice added by Bob.".
pub fn stock_system_msg(
&self,
id: StockMessage,
param1: impl AsRef<str>,
param2: impl AsRef<str>,
from_id: u32,
) -> String {
let insert1 = if id == StockMessage::MsgAddMember || id == StockMessage::MsgDelMember {
let contact_id = Contact::lookup_id_by_addr(self, param1.as_ref());
if contact_id != 0 {
Contact::get_by_id(self, contact_id)
.map(|contact| contact.get_name_n_addr())
.unwrap_or_default()
} else {
param1.as_ref().to_string()
}
} else {
param1.as_ref().to_string()
};
let action = self.stock_string_repl_str2(id, insert1, param2.as_ref().to_string());
let action1 = action.trim_end_matches('.');
match from_id {
0 => action,
1 => self.stock_string_repl_str(StockMessage::MsgActionByMe, action1), // DC_CONTACT_ID_SELF
_ => {
let displayname = Contact::get_by_id(self, from_id)
.map(|contact| contact.get_name_n_addr())
.unwrap_or_default();
self.stock_string_repl_str2(StockMessage::MsgActionByUser, action1, &displayname)
}
}
}
}
#[cfg(test)]
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 num_traits::ToPrimitive;
#[test]
fn test_enum_mapping() {
assert_eq!(StockMessage::NoMessages.to_usize().unwrap(), 1);
assert_eq!(StockMessage::SelfMsg.to_usize().unwrap(), 2);
}
#[test]
fn test_fallback() {
assert_eq!(StockMessage::NoMessages.fallback(), "No messages.");
}
#[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.");
}
unsafe extern "C" fn test_stock_str_no_fallback_cb(
_ctx: &Context,
evt: Event,
d1: uintptr_t,
_d2: uintptr_t,
) -> uintptr_t {
if evt == Event::GET_STRING && d1 == StockMessage::NoMessages.to_usize().unwrap() {
let tmp = CString::new("Hello there").unwrap();
dc_strdup(tmp.as_ptr()) as usize
} else {
0
}
}
#[test]
fn test_stock_str_no_fallback() {
let t = test_context(Some(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);
// uses %1$s substitution
assert_eq!(
ctx.stock_string_repl_str(StockMessage::Member, "42"),
"42 member(s)"
);
// We have no string using %1$d to test...
}
#[test]
fn test_stock_string_repl_int() {
let ctx = dc_context_new(None, std::ptr::null_mut(), None);
assert_eq!(
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);
assert_eq!(
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);
assert_eq!(
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);
assert_eq!(
ctx.stock_system_msg(
StockMessage::MsgAddMember,
"alice@example.com",
"",
DC_CONTACT_ID_SELF as u32
),
"Member alice@example.com added by me."
)
}
#[test]
fn test_stock_system_msg_add_member_by_me_with_displayname() {
let t = dummy_context();
Contact::create(&t.ctx, "Alice", "alice@example.com").expect("failed to create contact");
assert_eq!(
t.ctx.stock_system_msg(
StockMessage::MsgAddMember,
"alice@example.com",
"",
DC_CONTACT_ID_SELF as u32
),
"Member Alice (alice@example.com) added by me."
);
}
#[test]
fn test_stock_system_msg_add_member_by_other_with_displayname() {
let t = dummy_context();
let contact_id = {
Contact::create(&t.ctx, "Alice", "alice@example.com")
.expect("Failed to create contact Alice");
let id =
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob");
id
};
assert_eq!(
t.ctx.stock_system_msg(
StockMessage::MsgAddMember,
"alice@example.com",
"",
contact_id,
),
"Member Alice (alice@example.com) added by Bob (bob@example.com)."
);
}
#[test]
fn test_stock_system_msg_grp_name() {
let t = dummy_context();
assert_eq!(
t.ctx.stock_system_msg(
StockMessage::MsgGrpName,
"Some chat",
"Other chat",
DC_CONTACT_ID_SELF as u32
),
"Group name changed from \"Some chat\" to \"Other chat\" by me."
)
}
#[test]
fn test_stock_system_msg_grp_name_other() {
let t = dummy_context();
let id = Contact::create(&t.ctx, "Alice", "alice@example.com")
.expect("failed to create contact");
assert_eq!(
t.ctx
.stock_system_msg(StockMessage::MsgGrpName, "Some chat", "Other chat", id,),
"Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)."
)
}
}

View File

@@ -1,46 +0,0 @@
//! Utilities to help writing tests.
//!
//! This module is only compiled for test runs.
use tempfile::{tempdir, TempDir};
use crate::context::{dc_context_new, dc_open, Context};
use crate::types::dc_callback_t;
/// A Context and temporary directory.
///
/// The temporary directory can be used to store the SQLite database,
/// see e.g. [test_context] which does this.
pub struct TestContext {
pub ctx: Context,
pub dir: TempDir,
}
/// Create a new, opened [TestContext] using given callback.
///
/// The [Context] will be opened with the SQLite database named
/// "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 }
}
}
/// Return a dummy [TestContext].
///
/// The context will be opened and use the SQLite database as
/// specified in [test_context] but there is no callback hooked up,
/// i.e. [Context::call_cb] will always return `0`.
pub fn dummy_context() -> TestContext {
test_context(None)
}

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env python3
import os
import re
if __name__ == "__main__":
filestats = []
for fn in os.listdir():
if fn.endswith(".rs"):
s = open(fn).read()
s = re.sub(r'(?m)///.*$', '', s) # remove comments
unsafe = s.count("unsafe")
free = s.count("free(")
gotoblocks = s.count("current_block =")
filestats.append((fn, unsafe, free, gotoblocks))
sum_unsafe, sum_free, sum_gotoblocks = 0, 0, 0
for fn, unsafe, free, gotoblocks in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
print("{0: <30} unsafe: {1: >3} free: {2: >3} goto-blocks: {3: >3}".format(fn, unsafe, free, gotoblocks))
sum_unsafe += unsafe
sum_free += free
sum_gotoblocks += gotoblocks
print()
print("total unsafe:", sum_unsafe)
print("total free:", sum_free)
print("total gotoblocks:", sum_gotoblocks)

View File

@@ -1,9 +1,9 @@
#![allow(non_camel_case_types)]
use crate::constants::Event;
use crate::context::Context;
pub use libsqlite3_sys::*;
pub use mmime::carray::*;
pub use mmime::clist::*;
pub use rusqlite::ffi::*;
/// Callback function that should be given to dc_context_new().
///
@@ -22,7 +22,7 @@ pub type dc_receive_imf_t = unsafe fn(
_: &Context,
_: *const libc::c_char,
_: size_t,
_: &str,
_: *const libc::c_char,
_: uint32_t,
_: uint32_t,
) -> ();
@@ -32,9 +32,11 @@ 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>;
unsafe fn(_: &Context, _: *const libc::c_char, _: *const libc::c_char, _: u32) -> libc::c_int;
pub type dc_set_config_t =
unsafe fn(_: &Context, _: *const libc::c_char, _: *const libc::c_char) -> ();
pub type dc_get_config_t =
unsafe fn(_: &Context, _: *const libc::c_char, _: *const libc::c_char) -> *mut libc::c_char;
pub type sqlite_int64 = i64;
pub type sqlite3_int64 = sqlite_int64;

View File

@@ -1,3 +1,5 @@
use crate::types::*;
pub use libc::{
calloc, exit, free, malloc, memcmp, memcpy, memmove, memset, realloc, strcat, strchr, strcmp,
strcpy, strcspn, strlen, strncmp, strncpy, strrchr, strspn, strstr, strtol, system,
@@ -35,6 +37,20 @@ pub fn strndup(s: *const libc::c_char, n: libc::c_ulong) -> *mut libc::c_char {
extern "C" {
pub fn clock() -> libc::clock_t;
pub fn qsort(
__base: *mut libc::c_void,
__nel: size_t,
__width: size_t,
__compar: Option<
unsafe extern "C" fn(_: *const libc::c_void, _: *const libc::c_void) -> libc::c_int,
>,
);
pub fn vsnprintf(
_: *mut libc::c_char,
_: libc::c_ulong,
_: *const libc::c_char,
_: ::std::ffi::VaList,
) -> libc::c_int;
// -- DC Methods
pub fn dc_mprintf(format: *const libc::c_char, _: ...) -> *mut libc::c_char;

View File

@@ -6,12 +6,9 @@ use std::ffi::CString;
use mmime::mailimf_types::*;
use tempfile::{tempdir, TempDir};
use deltachat::config;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_array::*;
use deltachat::dc_chat::*;
use deltachat::dc_configure::*;
use deltachat::dc_imex::*;
use deltachat::dc_location::*;
@@ -124,15 +121,21 @@ unsafe fn stress_functions(context: &Context) {
context.get_blobdir(),
b"foobar\x00" as *const u8 as *const libc::c_char,
);
assert!(dc_is_blobdir_path(context, abs_path));
assert!(dc_is_blobdir_path(
context,
b"$BLOBDIR/fofo\x00" as *const u8 as *const libc::c_char,
));
assert!(!dc_is_blobdir_path(
context,
b"/BLOBDIR/fofo\x00" as *const u8 as *const libc::c_char,
));
assert_ne!(0, dc_is_blobdir_path(context, abs_path));
assert_ne!(
0,
dc_is_blobdir_path(
context,
b"$BLOBDIR/fofo\x00" as *const u8 as *const libc::c_char,
)
);
assert_eq!(
0,
dc_is_blobdir_path(
context,
b"/BLOBDIR/fofo\x00" as *const u8 as *const libc::c_char,
)
);
assert_ne!(0, dc_file_exist(context, abs_path));
free(abs_path as *mut libc::c_void);
assert_ne!(
@@ -169,7 +172,7 @@ unsafe fn stress_functions(context: &Context) {
"content"
);
free(buf as *mut _);
free(buf);
assert_ne!(
0,
dc_delete_file(
@@ -241,8 +244,15 @@ unsafe fn stress_functions(context: &Context) {
free(fn0 as *mut libc::c_void);
free(fn1 as *mut libc::c_void);
}
let keys = dc_get_config(
context,
b"sys.config_keys\x00" as *const u8 as *const libc::c_char,
);
assert!(!keys.is_null());
assert_ne!(0, *keys.offset(0isize) as libc::c_int);
let res = context.get_config(config::Config::SysConfigKeys).unwrap();
let res = format!(" {} ", as_str(keys));
free(keys as *mut libc::c_void);
assert!(!res.contains(" probably_never_a_key "));
assert!(res.contains(" addr "));
@@ -273,6 +283,7 @@ unsafe fn stress_functions(context: &Context) {
assert!(res.contains(" configured_send_port "));
assert!(res.contains(" configured_server_flags "));
let mut ok: libc::c_int;
let mut buf_0: *mut libc::c_char;
let mut headerline: *const libc::c_char = 0 as *const libc::c_char;
let mut setupcodebegin: *const libc::c_char = 0 as *const libc::c_char;
@@ -282,14 +293,14 @@ unsafe fn stress_functions(context: &Context) {
b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\ndata\n-----END PGP MESSAGE-----\x00" as *const u8
as *const libc::c_char,
);
let ok = dc_split_armored_data(
ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
0 as *mut *const libc::c_char,
&mut base64,
);
assert!(ok);
assert_eq!(ok, 1);
assert!(!headerline.is_null());
assert_eq!(
strcmp(
@@ -307,7 +318,7 @@ unsafe fn stress_functions(context: &Context) {
buf_0 =
strdup(b"-----BEGIN PGP MESSAGE-----\n\ndat1\n-----END PGP MESSAGE-----\n-----BEGIN PGP MESSAGE-----\n\ndat2\n-----END PGP MESSAGE-----\x00"
as *const u8 as *const libc::c_char);
let ok = dc_split_armored_data(
ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
@@ -315,7 +326,7 @@ unsafe fn stress_functions(context: &Context) {
&mut base64,
);
assert!(ok);
assert_eq!(ok, 1);
assert!(!headerline.is_null());
assert_eq!(
strcmp(
@@ -334,7 +345,7 @@ unsafe fn stress_functions(context: &Context) {
b"foo \n -----BEGIN PGP MESSAGE----- \n base64-123 \n -----END PGP MESSAGE-----\x00"
as *const u8 as *const libc::c_char,
);
let ok = dc_split_armored_data(
ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
@@ -342,7 +353,7 @@ unsafe fn stress_functions(context: &Context) {
&mut base64,
);
assert!(ok);
assert_eq!(ok, 1);
assert!(!headerline.is_null());
assert_eq!(
strcmp(
@@ -359,7 +370,7 @@ unsafe fn stress_functions(context: &Context) {
free(buf_0 as *mut libc::c_void);
buf_0 = strdup(b"foo-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char);
let ok = dc_split_armored_data(
ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
@@ -367,19 +378,19 @@ unsafe fn stress_functions(context: &Context) {
&mut base64,
);
assert!(!ok);
assert_eq!(ok, 0);
free(buf_0 as *mut libc::c_void);
buf_0 =
strdup(b"foo \n -----BEGIN PGP MESSAGE-----\n Passphrase-BeGIN : 23 \n \n base64-567 \r\n abc \n -----END PGP MESSAGE-----\n\n\n\x00"
as *const u8 as *const libc::c_char);
let ok = dc_split_armored_data(
ok = dc_split_armored_data(
buf_0,
&mut headerline,
&mut setupcodebegin,
0 as *mut *const libc::c_char,
&mut base64,
);
assert!(ok);
assert_eq!(ok, 1);
assert!(!headerline.is_null());
assert_eq!(
strcmp(
@@ -406,14 +417,14 @@ unsafe fn stress_functions(context: &Context) {
buf_0 =
strdup(b"-----BEGIN PGP PRIVATE KEY BLOCK-----\n Autocrypt-Prefer-Encrypt : mutual \n\nbase64\n-----END PGP PRIVATE KEY BLOCK-----\x00"
as *const u8 as *const libc::c_char);
let ok = dc_split_armored_data(
ok = dc_split_armored_data(
buf_0,
&mut headerline,
0 as *mut *const libc::c_char,
&mut preferencrypt,
&mut base64,
);
assert!(ok);
assert_eq!(ok, 1);
assert!(!headerline.is_null());
assert_eq!(
strcmp(
@@ -469,13 +480,16 @@ unsafe fn stress_functions(context: &Context) {
let mut setupcodebegin_0: *const libc::c_char = 0 as *const libc::c_char;
let mut preferencrypt_0: *const libc::c_char = 0 as *const libc::c_char;
buf_1 = strdup(S_EM_SETUPFILE);
assert!(dc_split_armored_data(
buf_1,
&mut headerline_0,
&mut setupcodebegin_0,
&mut preferencrypt_0,
0 as *mut *const libc::c_char,
));
assert_ne!(
0,
dc_split_armored_data(
buf_1,
&mut headerline_0,
&mut setupcodebegin_0,
&mut preferencrypt_0,
0 as *mut *const libc::c_char,
)
);
assert!(!headerline_0.is_null());
assert_eq!(
0,
@@ -495,13 +509,16 @@ unsafe fn stress_functions(context: &Context) {
free(buf_1 as *mut libc::c_void);
buf_1 = dc_decrypt_setup_file(context, S_EM_SETUPCODE, S_EM_SETUPFILE);
assert!(!buf_1.is_null());
assert!(dc_split_armored_data(
buf_1,
&mut headerline_0,
&mut setupcodebegin_0,
&mut preferencrypt_0,
0 as *mut *const libc::c_char,
));
assert_ne!(
0,
dc_split_armored_data(
buf_1,
&mut headerline_0,
&mut setupcodebegin_0,
&mut preferencrypt_0,
0 as *mut *const libc::c_char,
)
);
assert!(!headerline_0.is_null());
assert_eq!(
strcmp(
@@ -521,21 +538,65 @@ unsafe fn stress_functions(context: &Context) {
);
free(buf_1 as *mut libc::c_void);
if 0 != dc_is_configured(context) {
let setupcode = dc_create_setup_code(context);
let setupcode_c = CString::yolo(setupcode.clone());
let setupfile = dc_render_setup_file(context, &setupcode).unwrap();
let setupfile_c = CString::yolo(setupfile);
let setupcode: *mut libc::c_char;
let setupfile: *mut libc::c_char;
setupcode = dc_create_setup_code(context);
assert!(!setupcode.is_null());
assert_eq!(strlen(setupcode), 44);
assert!(
0 != !(*setupcode.offset(4isize) as libc::c_int == '-' as i32
&& *setupcode.offset(9isize) as libc::c_int == '-' as i32
&& *setupcode.offset(14isize) as libc::c_int == '-' as i32
&& *setupcode.offset(19isize) as libc::c_int == '-' as i32
&& *setupcode.offset(24isize) as libc::c_int == '-' as i32
&& *setupcode.offset(29isize) as libc::c_int == '-' as i32
&& *setupcode.offset(34isize) as libc::c_int == '-' as i32
&& *setupcode.offset(39isize) as libc::c_int == '-' as i32)
as usize
);
setupfile = dc_render_setup_file(context, setupcode);
assert!(!setupfile.is_null());
let buf_2: *mut libc::c_char = dc_strdup(setupfile);
let mut headerline_1: *const libc::c_char = 0 as *const libc::c_char;
let mut setupcodebegin_1: *const libc::c_char = 0 as *const libc::c_char;
assert_eq!(
0,
dc_split_armored_data(
buf_2,
&mut headerline_1,
&mut setupcodebegin_1,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
)
);
assert!(!headerline_1.is_null());
assert_eq!(
strcmp(
headerline_1,
b"-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char,
),
0
);
assert!(
!(!setupcodebegin_1.is_null()
&& strlen(setupcodebegin_1) == 2
&& strncmp(setupcodebegin_1, setupcode, 2) == 0i32)
);
free(buf_2 as *mut libc::c_void);
let payload: *mut libc::c_char;
let mut headerline_2: *const libc::c_char = 0 as *const libc::c_char;
payload = dc_decrypt_setup_file(context, setupcode_c.as_ptr(), setupfile_c.as_ptr());
payload = dc_decrypt_setup_file(context, setupcode, setupfile);
assert!(payload.is_null());
assert!(!dc_split_armored_data(
payload,
&mut headerline_2,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
));
assert_eq!(
0,
dc_split_armored_data(
payload,
&mut headerline_2,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
0 as *mut *const libc::c_char,
)
);
assert!(!headerline_2.is_null());
assert_eq!(
strcmp(
@@ -545,6 +606,8 @@ unsafe fn stress_functions(context: &Context) {
0
);
free(payload as *mut libc::c_void);
free(setupfile as *mut libc::c_void);
free(setupcode as *mut libc::c_void);
}
if 0 != dc_is_configured(context) {
@@ -606,11 +669,13 @@ fn test_encryption_decryption() {
j += 1
}
let (public_key, private_key) = dc_pgp_create_keypair("foo@bar.de").unwrap();
let (public_key, private_key) =
dc_pgp_create_keypair(b"foo@bar.de\x00" as *const u8 as *const libc::c_char).unwrap();
private_key.split_key().unwrap();
let (public_key2, private_key2) = dc_pgp_create_keypair("two@zwo.de").unwrap();
let (public_key2, private_key2) =
dc_pgp_create_keypair(b"two@zwo.de\x00" as *const u8 as *const libc::c_char).unwrap();
assert_ne!(public_key, public_key2);
@@ -632,7 +697,7 @@ fn test_encryption_decryption() {
assert!(ctext.starts_with("-----BEGIN PGP MESSAGE-----"));
let ctext_signed_bytes = ctext.len();
let ctext_signed = CString::yolo(ctext);
let ctext_signed = CString::new(ctext).unwrap();
let ctext = dc_pgp_pk_encrypt(
original_text as *const libc::c_void,
@@ -645,7 +710,7 @@ fn test_encryption_decryption() {
assert!(ctext.starts_with("-----BEGIN PGP MESSAGE-----"));
let ctext_unsigned_bytes = ctext.len();
let ctext_unsigned = CString::yolo(ctext);
let ctext_unsigned = CString::new(ctext).unwrap();
let mut keyring = Keyring::default();
keyring.add_owned(private_key);
@@ -722,7 +787,6 @@ fn test_encryption_decryption() {
Some(&mut valid_signatures),
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
valid_signatures.clear();
@@ -740,7 +804,6 @@ fn test_encryption_decryption() {
None,
)
.unwrap();
assert_eq!(std::str::from_utf8(&plain).unwrap(), as_str(original_text),);
}
}
@@ -761,14 +824,16 @@ struct TestContext {
}
unsafe fn create_test_context() -> TestContext {
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), None);
let mut ctx = dc_context_new(Some(cb), std::ptr::null_mut(), std::ptr::null_mut());
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
assert!(
dc_open(&mut ctx, dbfile.to_str().unwrap(), None),
let dbfile = CString::new(dir.path().join("db.sqlite").to_str().unwrap()).unwrap();
assert_eq!(
dc_open(&mut ctx, dbfile.as_ptr(), std::ptr::null()),
1,
"Failed to open {}",
dbfile.display()
as_str(dbfile.as_ptr() as *const libc::c_char)
);
TestContext { ctx: ctx, dir: dir }
}
@@ -781,31 +846,34 @@ fn test_dc_kml_parse() {
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</wHeN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></Placemark>\n</Document>\n</kml>\x00"
as *const u8 as *const libc::c_char;
let mut kml = dc_kml_parse(&context.ctx, xml, strlen(xml));
let kml: *mut dc_kml_t = dc_kml_parse(&context.ctx, xml, strlen(xml));
assert!(!kml.addr.is_null());
assert_eq!(as_str(kml.addr as *const libc::c_char), "user@example.org",);
assert!(!(*kml).addr.is_null());
assert_eq!(
as_str((*kml).addr as *const libc::c_char),
"user@example.org",
);
let locations_ref = &kml.locations.as_ref().unwrap();
assert_eq!(locations_ref.len(), 2);
assert_eq!(dc_array_get_cnt((*kml).locations), 2);
assert!(locations_ref[0].latitude > 53.6f64);
assert!(locations_ref[0].latitude < 53.8f64);
assert!(locations_ref[0].longitude > 9.3f64);
assert!(locations_ref[0].longitude < 9.5f64);
assert!(locations_ref[0].accuracy > 31.9f64);
assert!(locations_ref[0].accuracy < 32.1f64);
assert_eq!(locations_ref[0].timestamp, 1551906597);
assert!(dc_array_get_latitude((*kml).locations, 0) > 53.6f64);
assert!(dc_array_get_latitude((*kml).locations, 0) < 53.8f64);
assert!(dc_array_get_longitude((*kml).locations, 0) > 9.3f64);
assert!(dc_array_get_longitude((*kml).locations, 0) < 9.5f64);
assert!(dc_array_get_accuracy((*kml).locations, 0) > 31.9f64);
assert!(dc_array_get_accuracy((*kml).locations, 0) < 32.1f64);
assert_eq!(dc_array_get_timestamp((*kml).locations, 0), 1551906597);
assert!(locations_ref[1].latitude > 63.6f64);
assert!(locations_ref[1].latitude < 63.8f64);
assert!(locations_ref[1].longitude > 19.3f64);
assert!(locations_ref[1].longitude < 19.5f64);
assert!(locations_ref[1].accuracy > 2.4f64);
assert!(locations_ref[1].accuracy < 2.6f64);
assert_eq!(locations_ref[1].timestamp, 1544739072);
assert!(dc_array_get_latitude((*kml).locations, 1) > 63.6f64);
assert!(dc_array_get_latitude((*kml).locations, 1) < 63.8f64);
assert!(dc_array_get_longitude((*kml).locations, 1) > 19.3f64);
assert!(dc_array_get_longitude((*kml).locations, 1) < 19.5f64);
assert!(dc_array_get_accuracy((*kml).locations, 1) > 2.4f64);
assert!(dc_array_get_accuracy((*kml).locations, 1) < 2.6f64);
dc_kml_unref(&mut kml);
assert_eq!(dc_array_get_timestamp((*kml).locations, 1), 1544739072);
dc_kml_unref(kml);
}
}
@@ -842,7 +910,7 @@ fn test_dc_mimeparser_with_context() {
b"Chat-Version\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(as_str((*of).fld_value as *const libc::c_char), "1.0",);
assert_eq!(mimeparser.parts.len(), 1);
assert_eq!(carray_count(mimeparser.parts), 1);
dc_mimeparser_unref(&mut mimeparser);
}
@@ -855,7 +923,7 @@ fn test_dc_get_oauth2_url() {
let redirect_uri = "chat.delta:/com.b44t.messenger";
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
assert_eq!(res, Some("https://accounts.google.com/o/oauth2/auth?client_id=959970109878%2D4mvtgf6feshskf7695nfln6002mom908%2Eapps%2Egoogleusercontent%2Ecom&redirect_uri=chat%2Edelta%3A%2Fcom%2Eb44t%2Emessenger&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline".into()));
assert_eq!(res, Some("https://accounts.google.com/o/oauth2/auth?client_id=959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com&redirect_uri=chat.delta:/com.b44t.messenger&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline".into()));
}
#[test]
@@ -887,56 +955,15 @@ 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!(dc_array_get_cnt(contacts), 0);
dc_array_unref(contacts);
fn test_arr_to_string() {
let arr2: [uint32_t; 4] = [
0i32 as uint32_t,
12i32 as uint32_t,
133i32 as uint32_t,
1999999i32 as uint32_t,
];
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!(dc_array_get_cnt(contacts), 1);
dc_array_unref(contacts);
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
assert_eq!(dc_array_get_cnt(contacts), 0);
dc_array_unref(contacts);
}
}
#[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 chat_id = dc_create_chat_by_contact_id(&context.ctx, contact1);
assert!(chat_id > 9, "chat_id too small {}", chat_id);
let chat = dc_chat_new(&context.ctx);
assert!(dc_chat_load_from_db(chat, chat_id));
let chat2_id = dc_create_chat_by_contact_id(&context.ctx, contact1);
assert_eq!(chat2_id, chat_id);
let chat2 = dc_chat_new(&context.ctx);
assert!(dc_chat_load_from_db(chat2, chat2_id));
assert_eq!(as_str((*chat2).name), as_str((*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);
}
let str_0 = unsafe { dc_arr_to_string(arr2.as_ptr(), 4i32) };
assert_eq!(to_string(str_0), "0,12,133,1999999");
unsafe { free(str_0 as *mut _) };
}